14

(图解)一步一步使用CPP实现深度学习中的卷积

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzA4MjY4NTk0NQ%3D%3D&%3Bmid=2247492333&%3Bidx=1&%3Bsn=0645569380ca1c1376dd45767b2343b9
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

【GiantPandaCV导语】 卷积操作在深度学习中的重要性,想必大家都很清楚了。接下来将通过图解的方式,使用cpp一步一步从简单到复杂来实现卷积操作。

  • 符号约定 F 为输入; width 为输入的宽; height 为输入的高; channel 为输入的通道; K 为kernel; kSizeX 为kernel的宽; kSizeY 为kernel的高; filters 为kernel的个数; O 为输出; outWidth 为输出的宽; outHeight 为输出的高; outChannel 为输出的通道;

  • 卷积输出尺寸计算公式

  • 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

322AN3m.png!mobile图1 最简单的3x3卷积

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[m*kSizeX+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 zUJvmaQ.png!mobile

      图2 最简单卷积(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+2*padY-(dilationY*(kSizeY-1)+1)) / strideY + 1;
int outW = (width+2*padX-(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[i*outW+j]+=K[m*kSizeX+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

2mia6zR.png!mobile图3 最简单卷积(2)

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+2*padY-(dilationY*(kSizeY-1)+1)) / strideY + 1;
int outW = (width+2*padX-(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[i*outW+j]+=K[m*kSizeX+n]*F[(m+i*strideY)*width+(n+j*strideX)];
}
}
}
}

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 QF3mmyv.png!mobile

      图4 考虑padding卷积
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+2*padY-(dilationY*(kSizeY-1)+1)) / strideY + 1;
int outW = (width+2*padX-(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+j*strideX-padX)>-1&&(m+i*strideY-padY>-1)&&(n+j*strideX-padX)<=width&&(m+i*strideY-padY>-1)<=height)
{
fVal = F[(m+i*strideY-padX)*width+(n+j*strideX-padY)];
}
O[i*outW+j]+=K[m*kSizeX+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 YJ3iueQ.png!mobile

      图5 多通道卷积
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+2*padY-(dilationY*(kSizeY-1)+1)) / strideY + 1;
int outW = (width+2*padX-(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+j*strideX-padX)>-1&&(m+i*strideY-padY>-1)&&(n+j*strideX-padX)<=width&&(m+i*strideY-padY>-1)<=height)
{
fVal = F[c*width*height + (m+i*strideY-padX)*width+(n+j*strideX-padY)];
}
O[i*outW+j]+=K[c*kSizeX*kSizeY+m*kSizeX+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

qqeE7n.png!mobile图6 多kernel卷积

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+2*padY-(dilationY*(kSizeY-1)+1)) / strideY + 1;
int outW = (width+2*padX-(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+j*strideX-padX)>-1&&(m+i*strideY-padY>-1)&&(n+j*strideX-padX)<=width&&(m+i*strideY-padY>-1)<=height)
{
fVal = F[c*width*height + (m+i*strideY-padX)*width+(n+j*strideX-padY)];
}
O[oc*outH*outW+i*outW+j]+=K[oc*outC*kSizeX*kSizeY+c*kSizeX*kSizeY+m*kSizeX+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[oc*outH*outW+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

3INRjeB.png!mobile图7 膨胀卷积

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+2*padY-(dilationY*(kSizeY-1)+1)) / strideY + 1;
int outW = (width+2*padX-(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+j*strideX)*dilationX-padX)>-1 && ((m+i*strideY)*dilationY-padY)>-1&&
((n+j*strideX)*dilationX-padX)<=width && ((m+i*strideY)*dilationY-padY>-1)<=height)
{
fVal = F[c*width*height + ((m+i*strideY)*dilationX-padX)*width+((n+j*strideX)*dilationY-padY)];
}
O[oc*outH*outW+i*outW+j]+=K[oc*outC*kSizeX*kSizeY+c*kSizeX*kSizeY+m*kSizeX+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[oc*outH*outW+i*outW+j]<<" ";
}
std::cout<<std::endl;
}
std::cout<<std::endl;
}
}
  • git源码https://github.com/msnh2012/ConvTest

  • 最后欢迎关注我和BBuf及公众号的小伙伴们一块维护的一个深度学习框架Msnhnet:  https://github.com/msnh2012/Msnhnet

欢迎关注GiantPandaCV, 在这里你将看到独家的深度学习分享,坚持原创,每天分享我们学习到的新鲜知识。( • ̀ω•́ )✧

有对文章相关的问题,或者想要加入交流群,欢迎添加BBuf微信:

uEjqIr7.png!mobile二维码

为了方便读者获取资料以及我们公众号的作者发布一些Github工程的更新,我们成立了一个QQ群,二维码如下,感兴趣可以加入。

j6NJfya.png!mobile公众号QQ交流群

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK