来源:原创 msnh2012 GiantPandaCV@微信公众号
【GiantPandaCV导语】卷积操作在深度学习中的重要性,想必大家都很清楚了。接下来将通过图解的方式,使用cpp一步一步从简单到复杂来实现卷积操作。
- 符号约定**F为输入;width为输入的宽;height为输入的高;channel为输入的通道;K为kernel;kSizeX为kernel的宽;kSizeY为kernel的高;filters为kernel的个数;O为输出;outWidth为输出的宽;outHeight为输出的高;outChannel**为输出的通道;
- 卷积输出尺寸计算公式$W_2=\frac{W_1+2padding - (dilation(ksize-1)+1)}{stride}+1$
-
1. 最简单的3x3卷积首先, 我们不考虑任何padding, stride, 多维度等情况,来一个最简单的3x3卷积操作.计算思路很简单, 对应元素相乘最后相加即可.此处:
- width=3
- height=3
- channel=1
- paddingX=0
- paddingY=0
- strideX=1
- strideY=1
- dilationX=1
- dilationY=1
- kSizeX=3
- kSizeY=3
- filters=1可根据卷积输出尺寸计算公式,得到:
- outWidth=1
- outHeight=1
- outChannel=1
-
cpp代码:
`void demo0()
{
float F[] = {1,2,3,4,5,6,7,8,9};
float K[] = {1,2,3,4,5,6,7,8,9};
float O = 0;
int width = 3;
int height = 3;
int kSizeX = 3;
int kSizeY = 3;
for(int m=0;m<kSizeY;m++)
{
for(int n=0;n<kSizeX;n++)
{
O+=K[mkSizeX+n]F[m*width+n];
}
}
std::cout<<O<<" ";
}
`
-
2. 最简单卷积(1)接下来考虑能适用于任何尺寸的简单卷积, 如输入为4x4x1, kernel为3x3x1. 这里考虑卷积步长为1, 则此处的参数为:cpp代码:
- width=4
- height=4
- channel=1
- paddingX=0
- paddingY=0
- strideX=1
- strideY=1
- dilationX=1
- dilationY=1
- kSizeX=3
- kSizeY=3
- filters=1可根据卷积输出尺寸计算公式,得到:
- outWidth=2
- outHeight=2
-
outChannel=1
- ![]
`void demo1()
{
float F[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
float K[] = {1,2,3,4,5,6,7,8,9};
float O[] = {0,0,0,0};
int padX = 0;
int padY = 0;
int dilationX = 1;
int dilationY = 1;
int strideX = 1;
int strideY = 1;
int width = 4;
int height = 4;
int kSizeX = 3;
int kSizeY = 3;
int outH = (height+2padY-(dilationY(kSizeY-1)+1)) / strideY + 1;
int outW = (width+2padX-(dilationX(kSizeX-1)+1)) / strideX + 1;
for(int i=0;i<outH;i++)
{
for(int j=0;j<outW;j++)
{
for(int m=0;m<kSizeY;m++)
{
for(int n=0;n<kSizeX;n++)
{
O[ioutW+j]+=K[mkSizeX+n]F[(m+i)width+(n+j)];
}
}
}
}
for (int i = 0; i < outH; ++i)
{
for (int j = 0; j < outW; ++j)
{
std::cout<<O[i*outW+j]<<" ";
}
std::cout<<std::endl;
}
}
`
-
3. 最简单卷积(2)接下来考虑在步长上为任意步长的卷积, 如输入为4x4x1, kernel为2x2x1. 这里考虑卷积步长为2, 则此处的参数为:
- width=4
- height=4
- channel=1
- paddingX=0
- paddingY=0
- strideX=2
- strideY=2
- dilationX=1
- dilationY=1
- kSizeX=2
- kSizeY=2
- filters=1可根据卷积输出尺寸计算公式,得到:
- outWidth=2
- outHeight=2
- outChannel=1
cpp代码:
`void demo2()
{
float F[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
float K[] = {1,2,3,4};
float O[] = {0,0,0,0};
int padX = 0;
int padY = 0;
int dilationX = 1;
int dilationY = 1;
int strideX = 2;
int strideY = 2;
int width = 4;
int height = 4;
int kSizeX = 2;
int kSizeY = 2;
int outH = (height+2padY-(dilationY(kSizeY-1)+1)) / strideY + 1;
int outW = (width+2padX-(dilationX(kSizeX-1)+1)) / strideX + 1;
for(int i=0;i<outH;i++)
{
for(int j=0;j<outW;j++)
{
for(int m=0;m<kSizeY;m++)
{
for(int n=0;n<kSizeX;n++)
{
O[ioutW+j]+=K[mkSizeX+n]F[(m+istrideY)width+(n+jstrideX)];
}
}
}
}
for (int i = 0; i < outH; ++i)
{
for (int j = 0; j < outW; ++j)
{
std::cout<<O[i*outW+j]<<" ";
}
std::cout<<std::endl;
}
}
`
-
4. 带padding的卷积接下来考虑带padding的卷积, 如输入为4x4x1, kernel为3x3x1. 卷积步长为1, padding为1 则此处的参数为:cpp代码:
- width=4
- height=4
- channel=1
- paddingX=1
- paddingY=1
- strideX=1
- strideY=1
- dilationX=1
- dilationY=1
- kSizeX=3
- kSizeY=3
- filters=1可根据卷积输出尺寸计算公式,得到:
- outWidth=2
- outHeight=2
- outChannel=1
-
`void demo3()
{
float F[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
float K[] = {1,2,3,4,5,6,7,8,9};
float O[] = {0,0,0,0};
int padX = 1;
int padY = 1;
int dilationX = 1;
int dilationY = 1;
int strideX = 2;
int strideY = 2;
int width = 4;
int height = 4;
int kSizeX = 3;
int kSizeY = 3;
int outH = (height+2padY-(dilationY(kSizeY-1)+1)) / strideY + 1;
int outW = (width+2padX-(dilationX(kSizeX-1)+1)) / strideX + 1;
for(int i=0;i<outH;i++)
{
for(int j=0;j<outW;j++)
{
for(int m=0;m<kSizeY;m++)
{
for(int n=0;n<kSizeX;n++)
{
float fVal = 0;
//考虑边界强情况
if((n+jstrideX-padX)>-1&&(m+istrideY-padY>-1)&&(n+jstrideX-padX)<=width&&(m+istrideY-padY>-1)<=height)
{
fVal = F[(m+istrideY-padX)width+(n+jstrideX-padY)];
}
O[ioutW+j]+=K[mkSizeX+n]fVal;
}
}
}
}
for (int i = 0; i < outH; ++i)
{
for (int j = 0; j < outW; ++j)
{
std::cout<<O[i*outW+j]<<" ";
}
std::cout<<std::endl;
}
}
`
-
5. 多通道卷积接下来考虑多通道卷积, 如输入为4x4x1, kernel为3x3x1. 卷积步长为1, padding为1, 输入通道为2, 则此处的参数为:cpp代码:
- width=4
- height=4
- channel=2
- paddingX=1
- paddingY=1
- strideX=1
- strideY=1
- dilationX=1
- dilationY=1
- kSizeX=3
- kSizeY=3
- filters=1可根据卷积输出尺寸计算公式,得到:
- outWidth=2
- outHeight=2
- outChannel=1
`void demo4()
{
float F[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
float K[] = {1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9};
float O[] = {0,0,0,0};
int padX = 1;
int padY = 1;
int dilationX = 1;
int dilationY = 1;
int strideX = 2;
int strideY = 2;
int width = 4;
int height = 4;
int kSizeX = 3;
int kSizeY = 3;
int channel = 2;
int outH = (height+2padY-(dilationY(kSizeY-1)+1)) / strideY + 1;
int outW = (width+2padX-(dilationX(kSizeX-1)+1)) / strideX + 1;
for (int c = 0; c < channel; ++c)
{
for(int i=0;i<outH;i++)
{
for(int j=0;j<outW;j++)
{
for(int m=0;m<kSizeY;m++)
{
for(int n=0;n<kSizeX;n++)
{
float fVal = 0;
if((n+jstrideX-padX)>-1&&(m+istrideY-padY>-1)&&(n+jstrideX-padX)<=width&&(m+istrideY-padY>-1)<=height)
{
fVal = F[cwidthheight + (m+istrideY-padX)width+(n+jstrideX-padY)];
}
O[ioutW+j]+=K[ckSizeXkSizeY+mkSizeX+n]fVal;
}
}
}
}
}
for (int i = 0; i < outH; ++i)
{
for (int j = 0; j < outW; ++j)
{
std::cout<<O[i*outW+j]<<" ";
}
std::cout<<std::endl;
}
}
`
-
6. 多kernel卷积接下来考虑多kernel卷积, 如输入为4x4x1, kernel为3x3x1. 卷积步长为1, padding为1, 输入通道为2, filters为2, 则此处的参数为:
- width=4
- height=4
- channel=2
- paddingX=1
- paddingY=1
- strideX=1
- strideY=1
- dilationX=1
- dilationY=1
- kSizeX=3
- kSizeY=3
- filters=2可根据卷积输出尺寸计算公式,得到:
- outWidth=2
- outHeight=2
- outChannel=2
cpp代码:
`void demo5()
{
float F[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
float K[] = {1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,
1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9
};
float O[] = {0,0,0,0,0,0,0,0};
int padX = 1;
int padY = 1;
int dilationX = 1;
int dilationY = 1;
int strideX = 2;
int strideY = 2;
int width = 4;
int height = 4;
int kSizeX = 3;
int kSizeY = 3;
int channel = 2;
int filters = 2;
int outH = (height+2padY-(dilationY(kSizeY-1)+1)) / strideY + 1;
int outW = (width+2padX-(dilationX(kSizeX-1)+1)) / strideX + 1;
int outC = filters;
for (int oc = 0; oc < outC; ++oc)
{
for (int c = 0; c < channel; ++c)
{
for(int i=0;i<outH;i++)
{
for(int j=0;j<outW;j++)
{
for(int m=0;m<kSizeY;m++)
{
for(int n=0;n<kSizeX;n++)
{
float fVal = 0;
if((n+jstrideX-padX)>-1&&(m+istrideY-padY>-1)&&(n+jstrideX-padX)<=width&&(m+istrideY-padY>-1)<=height)
{
fVal = F[cwidthheight + (m+istrideY-padX)width+(n+jstrideX-padY)];
}
O[ocoutHoutW+ioutW+j]+=K[ocoutCkSizeXkSizeY+ckSizeXkSizeY+mkSizeX+n]*fVal;
}
}
}
}
}
}
for (int oc = 0; oc < outC; ++oc)
{
for (int i = 0; i < outH; ++i)
{
for (int j = 0; j < outW; ++j)
{
std::cout<<O[ocoutHoutW+i*outW+j]<<" ";
}
std::cout<<std::endl;
}
std::cout<<std::endl<<std::endl;
}
}
`
-
7. 膨胀卷积接下来考虑多膨胀卷积, 如输入为4x4x1, kernel为3x3x1. 卷积步长为1, padding为1, 输入通道为2, filters为2, dilate为2则此处的参数为:
- width=4
- height=4
- channel=2
- paddingX=1
- paddingY=1
- strideX=1
- strideY=1
- dilationX=2
- dilationY=2
- kSizeX=3
- kSizeY=3
- filters=2可根据卷积输出尺寸计算公式,得到:
- outWidth=2
- outHeight=2
- outChannel=2
cpp代码:
`void demo6()
{
float F[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
float K[] = {1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,
1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9
};
float O[] = {0,0,0,0,0,0,0,0};
int padX = 1;
int padY = 1;
int dilationX = 2;
int dilationY = 2;
int strideX = 1;
int strideY = 1;
int width = 4;
int height = 4;
int kSizeX = 3;
int kSizeY = 3;
int channel = 2;
int filters = 2;
int outH = (height+2padY-(dilationY(kSizeY-1)+1)) / strideY + 1;
int outW = (width+2padX-(dilationX(kSizeX-1)+1)) / strideX + 1;
int outC = filters;
for (int oc = 0; oc < outC; ++oc)
{
for (int c = 0; c < channel; ++c)
{
for(int i=0;i<outH;i++)
{
for(int j=0;j<outW;j++)
{
for(int m=0;m<kSizeY;m++)
{
for(int n=0;n<kSizeX;n++)
{
float fVal = 0;
if( ((n+jstrideX)dilationX-padX)>-1 && ((m+istrideY)dilationY-padY)>-1&&
((n+jstrideX)dilationX-padX)<=width && ((m+istrideY)dilationY-padY>-1)<=height)
{
fVal = F[cwidthheight + ((m+istrideY)dilationX-padX)width+((n+jstrideX)dilationY-padY)];
}
O[ocoutHoutW+ioutW+j]+=K[ocoutCkSizeXkSizeY+ckSizeXkSizeY+mkSizeX+n]*fVal;
}
}
}
}
}
}
for (int oc = 0; oc < outC; ++oc)
{
for (int i = 0; i < outH; ++i)
{
for (int j = 0; j < outW; ++j)
{
std::cout<<O[ocoutHoutW+i*outW+j]<<" ";
}
std::cout<<std::endl;
}
std::cout<<std::endl;
}
}
`