Press "Enter" to skip to content

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

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

 

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

 

卷积输出尺寸计算公式

 

1. 最简单的3×3卷积首先, 我们不考虑任何padding, stride, 多维度等情况,来一个最简单的3×3卷积操作.计算思路很简单, 对应元素相乘最后相加即可.此处:

 

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

图1 最简单的3×3卷积

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

图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

图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

图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

图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

图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

图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

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注