一维卷积原理

举例子来说明,假设有输入序列数据 [1, 2, 3, 4, 5] 和 一组权重数据 [1, 0, 1],做以下运算

红框内对位相乘然后相加 详情计算过程如下:
    1.  [1, 2, 3] * [1, 0, 1] =  1*1+2*0+3*1 = 4     
    2.  [2, 3, 4] * [1, 0, 1] = 2*1+3*0+4*1 = 6    
    3.  [3, 4, 5] * [1, 0, 1] = 3*1+4*0+5*1 = 8     


输出结果:[4, 6, 8]
像这样的计算叫卷积运算
[1, 0, 1]叫卷积核,长度==3

用 libtorch实现卷积运算代码

	torch::Tensor input = torch::tensor({ { 1.0, 2.0, 3.0, 4.0, 5.0  } });
	std::cout << "输入序列: \n" << input << std::endl;  
	//******* 输入序列: 1  2  3  4  5
	//卷积核形状:[out_channels=1, in_channels=1, kernel_size=3] 
	// 步长1,不用填充位,不用偏置
	torch::nn::Conv1d conv(torch::nn::Conv1dOptions(1, 1, 3).stride(1).padding(0).bias(false));

	conv->weight.data()=(torch::tensor( { 1.0, 0.0, 1.0 } ));
	std::cout << "卷积核: \n" << conv->weight << std::endl;
	////******* 卷积核:  1  0  1
	
	torch::Tensor output = conv->forward(input);   //卷积运算
	std::cout << "输出结果: \n" << output << std::endl;
	//******* 输出结果:   4  6  8

如果想要输出结果与输入数据长度一样,在原始数据左右两边填充0 如下  
[0,1, 2, 3, 4, 5,0]  与 [1, 0, 1]做以下运算
        1. [0, 1, 2] * [1, 0, 1] =  0*1+2*0+2*1 = 2
        2. [1, 2, 3] * [1, 0, 1] =  1*1+2*0+3*1 = 4
        3. [2, 3, 4] * [1, 0, 1] = 2*1+3*0+4*1 = 6
        4. [3, 4, 5] * [1, 0, 1] = 3*1+4*0+5*1 = 8  
        5. [4, 5, 0] * [1, 0, 1] = 4*1+5*0+0*1 = 4 
输出结果:[2, 4, 6, 8, 4]
把这个叫填充一般是填充0

用 libtorch实现卷积运算代码

	torch::Tensor input = torch::tensor({ { 1.0, 2.0, 3.0, 4.0, 5.0  } });
	std::cout << "输入序列: \n" << input << std::endl;  
	//******* 输入序列: 1  2  3  4  5
	//卷积核形状:[out_channels=1, in_channels=1, kernel_size=3] 
	// 步长1,用一个填充位
	torch::nn::Conv1d conv(torch::nn::Conv1dOptions(1, 1, 3).stride(1).padding(1).bias(false));

	conv->weight.data()=(torch::tensor( { 1.0, 0.0, 1.0 } ));
	std::cout << "卷积核: \n" << conv->weight << std::endl;
	////******* 卷积核:  1  0  1
	
	torch::Tensor output = conv->forward(input);   //卷积运算
	std::cout << "输出结果: \n" << output << std::endl;
	//******* 输出结果:    2  4  6  8  4

以上计算卷积核在原数据上一次只移动一个数据位其步长为1,如步长为2时


详情计算过程如下       

       1. [1, 2, 3] * [1, 0, 1] =  1*1+2*0+3*1 = 4
        2. [3, 4, 5] * [1, 0, 1] = 3*1+4*0+5*1 = 8 
输出结果:[4, 8]

用 libtorch实现卷积运算代码

	torch::Tensor input = torch::tensor({ { 1.0, 2.0, 3.0, 4.0, 5.0  } });
	std::cout << "输入序列: \n" << input << std::endl;  
	//******* 输入序列: 1  2  3  4  5
	//卷积核形状:[out_channels=1, in_channels=1, kernel_size=3] 
	// 步长2,
	torch::nn::Conv1d conv(torch::nn::Conv1dOptions(1, 1, 3).stride(2).padding(0).bias(false));

	conv->weight.data()=(torch::tensor( { 1.0, 0.0, 1.0 } ));
	std::cout << "卷积核: \n" << conv->weight << std::endl;
	////******* 卷积核:  1  0  1
	
	torch::Tensor output = conv->forward(input);   //卷积运算
	std::cout << "输出结果: \n" << output << std::endl;
	//******* 输出结果:    4  8


卷积运算过程:卷积核从左往右滑动加权求和

        inlength:原数据长度
        padding:填充位
        kernelsize:卷积核大小
        stride:步长
输出长度: (inlength  +  2*padding - (kernelsize-1) -1)/stride + 1

二维卷积原理

二维卷积运算过程和一维卷积一样它处理矩阵数据用举例子来说明
输入 5X5的矩阵数据:

1, 2, 3, 4, 5
1, 2, 3, 4, 5                            1,0,1
1, 2, 3, 4, 5             卷积核:   1,0,1
1, 2, 3, 4, 5                            1,0,1
1, 2, 3, 4, 5

  二维卷积运算过程

    1. 红框里面数据 与 卷积核  加权求和

  注意这里不是矩阵运算 而是对位相乘然后相加   

    计算1过程详情  (1*1+2*0+3*1) + (1*1+2*0+3*1) + (1*1+2*0+3*1) = 12

     

     2. 红框里面数据 与 卷积核  加权求和

        计算2过程详情  (2*1+3*0+4*1) + (2*1+3*0+4*1) + (2*1+3*0+4*1) = 18

        

     按照从左往右从上到下的计算方式 最后输出结果

用 libtorch实现卷积运算代码


	torch::Tensor input = torch::tensor({ { 1.0, 2.0, 3.0, 4.0, 5.0  },
										  { 1.0, 2.0, 3.0, 4.0, 5.0  },
										  { 1.0, 2.0, 3.0, 4.0, 5.0  },
										  { 1.0, 2.0, 3.0, 4.0, 5.0  },
										  { 1.0, 2.0, 3.0, 4.0, 5.0  },
		}).view({ 1, 1, 5, 5 });
	std::cout << "输入序列: \n" << input << std::endl;
	
	//卷积核形状:[out_channels=1, in_channels=1, kernel_size=3] 
	// 步长1,
	torch::nn::Conv2d conv(torch::nn::Conv2dOptions(1, 1, 3).stride(1).padding(0).bias(false));

	conv->weight.data() = torch::tensor( { { 1.0, 0.0, 1.0 }, { 1.0, 0.0, 1.0 }, { 1.0, 0.0, 1.0 } } );
	std::cout << "卷积核: \n" << conv->weight << std::endl;
	

	torch::Tensor output = conv->forward(input);   //卷积运算
	std::cout << "输出结果: \n" << output << std::endl;
	
	


/*******************运行结果*****************************

输入序列:
(1,1,.,.) =
  1  2  3  4  5
  1  2  3  4  5
  1  2  3  4  5
  1  2  3  4  5
  1  2  3  4  5
[ CPUFloatType{1,1,5,5} ]
卷积核:
(1,1,.,.) =
  1  0  1
  1  0  1
  1  0  1
[ CPUFloatType{1,1,3,3} ]
输出结果:
(1,1,.,.) =
  12  18  24
  12  18  24
  12  18  24
[ CPUFloatType{1,1,3,3} ]

*************************************************/

想要输出结果与输入数据长度一样,在原始数据左右两边增加两列填充0,上下两边增加两行填充0

填充后的数据,

0, 0, 0, 0, 0, 0,0
0,1, 2, 3, 4, 5,0
0,1, 2, 3, 4, 5,0
0,1, 2, 3, 4, 5,0
0,1, 2, 3, 4, 5,0
0,1, 2, 3, 4, 5,0
0,0,0, 0, 0, 0, 0

这里就不手动计算了,用 libtorch去实现卷积运算

	torch::Tensor input = torch::tensor({ { 1.0, 2.0, 3.0, 4.0, 5.0  },
										  { 1.0, 2.0, 3.0, 4.0, 5.0  },
										  { 1.0, 2.0, 3.0, 4.0, 5.0  },
										  { 1.0, 2.0, 3.0, 4.0, 5.0  },
										  { 1.0, 2.0, 3.0, 4.0, 5.0  },
		}).view({ 1, 1, 5, 5 });
	std::cout << "输入序列: \n" << input << std::endl;
	
	//卷积核形状:[out_channels=1, in_channels=1, kernel_size=3] 
	// 步长1,用一个填充位
	torch::nn::Conv2d conv(torch::nn::Conv2dOptions(1, 1, 3).stride(1).padding(1).bias(false));

	conv->weight.data() = torch::tensor( { { 1.0, 0.0, 1.0 }, { 1.0, 0.0, 1.0 }, { 1.0, 0.0, 1.0 } } );
	std::cout << "卷积核: \n" << conv->weight << std::endl;
	

	torch::Tensor output = conv->forward(input);   //卷积运算
	std::cout << "输出结果: \n" << output << std::endl;
	


/*******************运行结果*****************************

输入序列:
(1,1,.,.) =
  1  2  3  4  5
  1  2  3  4  5
  1  2  3  4  5
  1  2  3  4  5
  1  2  3  4  5
[ CPUFloatType{1,1,5,5} ]
卷积核:
(1,1,.,.) =
  1  0  1
  1  0  1
  1  0  1
[ CPUFloatType{1,1,3,3} ]
输出结果:
(1,1,.,.) =
   4   8  12  16   8
   6  12  18  24  12
   6  12  18  24  12
   6  12  18  24  12
   4   8  12  16   8
[ CPUFloatType{1,1,5,5} ]


*************************************************/

以上是卷积运算过程,在卷积神经网络的中有卷积层、池化层、全连接层。

池化

池化是一种用于降维、提取局部显著特征的操作,常用方式

    1. 最大池化, 在池化窗口内取最大值作为输出,示例: 窗口 [2, 5, 3] ,输出 5。
    2. 平均池化,在池化窗口内取平均值作为输出,平滑局部特征
    3. 随机池化,在池化窗口内随机采样

举例子来说明 如用最大池化方工,窗口3X3 在原数据上采样最大值,从左到右,从上到下采样

    第1步

第2步

用 libtorch实现最大池化代码

	torch::Tensor input = torch::tensor({ { 1.0, 2.0, 3.0, 4.0, 5.0  },
										  { 1.0, 2.0, 3.0, 4.0, 5.0  },
										  { 1.0, 2.0, 3.0, 4.0, 5.0  },
										  { 1.0, 2.0, 3.0, 4.0, 5.0  },
										  { 1.0, 2.0, 3.0, 4.0, 5.0  },
		}).view({ 1, 1, 5, 5 });
	///std::cout << "输入序列: \n" << input << std::endl;
	
	//卷积核形状:[out_channels=1, in_channels=1, kernel_size=3] 
	// 步长1,用一个填充位
	torch::nn::Conv2d conv(torch::nn::Conv2dOptions(1, 1, 3).stride(1).padding(1).bias(false));

	conv->weight.data() = torch::tensor( { { 1.0, 0.0, 1.0 }, { 1.0, 0.0, 1.0 }, { 1.0, 0.0, 1.0 } } );
	//std::cout << "卷积核: \n" << conv->weight << std::endl;
	

	torch::Tensor output = conv->forward(input);   //卷积运算
	std::cout << "卷积输出结果: \n" << output << std::endl;
	
	//最大池化 窗口大小 3X3 步长1 
	torch::nn::MaxPool2d max_pool2d(torch::nn::MaxPool2dOptions(3).stride(1));
	
	torch::Tensor output2 = max_pool2d->forward(output);

	
	std::cout << "池化输出结果:\n" << output2 << std::endl;
	

/*******************运行结果*****************************

卷积输出结果:
(1,1,.,.) =
   4   8  12  16   8
   6  12  18  24  12
   6  12  18  24  12
   6  12  18  24  12
   4   8  12  16   8
[ CPUFloatType{1,1,5,5} ]
池化输出结果:
(1,1,.,.) =
  18  24  24
  18  24  24
  18  24  24
[ CPUFloatType{1,1,3,3} ]


*************************************************/

 卷积神经网络

卷积在图像处理中主要用于特征提取、滤波和模式识别,通过卷积核(滤波器)对图像局部区域进行加权计算,生成特征图。

比如原始图              

 通过卷积核(边缘检测)生成特征图 

在图像处理中卷积核是已知,使用不同的卷积核提取多个维度的特征图,在卷积神经网络中卷积核是未知的(随机值),
构建一个模型把它训练出来跟神经网络求权重方式一样,卷积神经网络是由卷积层加一个神经网络

简单卷积神经网络结构:

1.卷积层  一个卷积核运算(提取特征)
2.激活函数 
3. 池化层
4.全连接层 将二维数据转换成一维向量数据(神经网络输入层-> 神经网络输出层)

复杂卷积神经网络结构

1.卷积层
2.激活函数
3. 池化层
4.卷积层
5.激活函数
6. 池化层
.....
全连接层
神经网络隐藏层1
神经网络隐藏层2
...
神经网络输出层

适用场景

图像分类、目标检测、语义分割、图像生成、人脸识别等,能自动捕捉图像的边缘、纹理、形状等层级特征。


感谢大家的支持,如要问题欢迎提问指正。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐