卷积神经网络理解(1)

参考链接:https://blog.csdn.net/weixin_43973089/article/details/115921649

1、基本原理

 神经网络由大量的神经元相互连接而成。每个神经元接受线性组合的输入后,最开始只是简单的线性加权,后来给每个神经元加上了非线性的激活函数,从而进行非线性变换后输出。每两个神经元之间的连接代表加权值,称之为权重(weight)。不同的权重和激活函数,则会导致神经网络不同的输出。

举个手写识别的例子,给定一个未知数字,让神经网络识别是什么数字。此时的神经网络的输入由一组被输入图像的像素所激活的输入神经元所定义。在通过非线性激活函数进行非线性变换后,神经元被激活然后被传递到其他神经元。重复这一过程,直到最后一个输出神经元被激活。从而识别当前数字是什么字。

 神经网络的每个神经元如下:

 

 

 

 基本wx + b的形式,其中

  • equationequation表示输入向量
  • equationequation为权重,几个输入则意味着有几个权重,即每个输入都被赋予一个权重
  • b为偏置bias
  • g(z) 为激活函数
  • a 为输出

卷积神经网络是一种前馈神经网络,其基本原理是通过卷积操作提取输入数据的特征,然后通过池化操作减少特征的维度,最后通过全连接层进行分类或回归。

  • 卷积操作是卷积神经网络的核心,其作用是将输入数据与卷积核进行卷积运算,得到特征图。卷积核是一个小的矩阵,其大小通常为3x3或5x5,可以通过训练得到。卷积操作可以有效地提取输入数据的局部特征,例如图像中的边缘、纹理等。
  • 池化操作是卷积神经网络的另一个重要组成部分,其作用是减少特征图的维度,从而降低计算复杂度。常见的池化操作有最大池化和平均池化,其原理是在特定的区域内取最大值或平均值。

卷积

卷积说白了就是有一个卷积核(其实就是一个带权值的滑动窗口)在图像上从左到右,从上到下地扫描,每次扫描的时候都会将卷积核里的值所构成的矩阵与图像被卷积核覆盖的像素值矩阵做内积。整个过程如下图所示,其中黄色方框代表卷积核,绿色部分代表单通道图像,红色部分代表卷积计算后的结果,通常称为特征图:

 

**那为什么说卷积能够提取图像中物体的特征呢?**其实很好理解,上图中的卷积核中值的分布如上右图;
当这个卷积核卷积的时候就会在3行3列的小范围内计算出图像中几乎所有的 33 列子图像与卷积核的相似程度(也就是内积的计算结果)。
相似程度越高说明该区域中的像素值与卷积核越相似。
(上图的特征图中值为 4 的位置所对应到的源图像子区域中像素值的分布与卷积核值的分布最为接近)
这也就说明了卷积在提取特征时能够考虑到特征可能只占图像的一小部分,以及同样的特征可能出现在不同的图像中不同的位置这两个特点。
卷积核的值很明显是训练出来的!

对于一个图像,可以经过不止一个卷积核的卷积,得到多个特征图,特征图的个数与卷积核的个数相等。

当然,上述的卷积核仅仅是一个最简单的卷积核,除此之外,还有扩张卷积:包含一个叫做扩张率的参数,定义了卷积核内参数间的行(列)间隔数,或者又如转置卷积。
对于要处理的二维图像而言,其不仅有长和宽两个变量,还有一个称为“通道”的变量,如上述的例子,输入的图像可表示为(4, 4, 1),表示长和宽都是4,有1个通道,经过N个上述的 3 x 3 卷积核后,其可以变成(2, 2, N),即有了N个通道。在这里,可以把其看作是N个二维的图像组合而成的一个三维图像。
对于多通道的图像,假定其有N个通道,经过一个 3 x 3 的卷积核,那么这个卷积核也应该为(3, 3, N),使用这一个卷积核对每一个通道相同的位置进行卷积,得到了N个值,将这N个值求和,作为输出图像中的一个值。所以,得到的通道的数目只与卷积核的个数有关。

池化

(1)从上面卷积层可以看到,我们的目的是对一个图像进行特征提取,最终得到了一个N通道的图像,但是,如果卷积核的数量太多,那么得到的特征图数量也是非常多。这时就需要池化层来降低卷积层输出的特征维度,同时可以防止过拟合现象。

(2)由于图像具有一种“静态性”的属性,也就是在一个图像区域有用的特征极有可能在另一个区域同样有用,所以通过池化层可以来降低图像的维度。

这里只介绍我使用的一般池化的方法。一般池化包括平均池化最大池化两种。

(3)池化就是将输入图像进行缩小,减少像素信息,只保留重要信息。 池化的操作也很简单,通常情况下,池化区域是 2 行 2 列的大小,然后按一定规则转换成相应的值,例如最常用的最大池化( max pooling )。最大池化保留了每一小块内的最大值,也就是相当于保留了这一块最佳的匹配结果。举个例子,如下图中图像是 4 行 4 列的,池化区域是 2 行 2 列的,所以最终池化后的特征图是 2 行 2 列的。图像中粉色区域最大的值是 6 ,所以池化后特征图中粉色位置的值是 6 ,图像中绿色区域最大的值是 8 ,所以池化后特征图中绿色位置的值是 8 ,以此类推。

从上图可以看出,最大池化不仅仅缩小了图像的大小,减少后续卷积的计算量,而且保留了最佳的特征(如果图像是经过卷积后的特征图)。也就相当于把图缩小了,但主要特征还在,这就考虑到了缩放图像的大小对物体的特征影响可能不大的特点。

全连接网络

卷积与池化能够很好的提取图像中物体的特征,当提取好特征之后就可以着手开始使用全连接网络来进行分类了。全连接网络的大致结构如下:

 

其中输入层通常指的是对图像进行卷积,池化等计算之后并进行扁平后的特征图。隐藏层中每个方块代表一个神经元,每一个神经元可以看成是一个很简单的线性分类器和激活函数的组合。输出层中神经元的数量一般为标签类别的数量,激活函数为 softmax (因为将该图像是猫或者狗的得分进行概率化)。因此我们可以讲全连接网络理解成很多个简单的分类器的组合,来构建成一个非常强大的分类器。

参数学习

 

CNN的学习也是和神经网络一样,先经过前向传播,得到当前的预测值,再计算误差,将误差反向传播,更新全连接层的权值和卷积核的参数。这里存在一个问题,如果卷积核的参数都不同,那么需要更新的参数数量会非常多。为了解决这个问题,采用了参数共享的机制,即对于每一个卷积核,其对每个通道而言权值都是相同的。

和全连接神经网络相比,卷积神经网络的训练要复杂一些。但训练的原理是一样的:利用链式求导计算损失函数对每个权重的偏导数(梯度),然后根据梯度下降公式更新权重。训练算法依然是反向传播算法。
卷积神经网络的三个思路:

  • 局部连接

这个是最容易想到的,每个神经元不再和上一层的所有神经元相连,而只和一小部分神经元相连。这样就减少了很多参数。

  • 权值共享

一组连接可以共享同一个权重,而不是每个连接有一个不同的权重,这样又减少了很多参数。

  • 下采样

可以使用Pooling来减少每层的样本数,进一步减少参数数量,同时还可以提升模型的鲁棒性。
对于图像识别任务来说,卷积神经网络通过尽可能保留重要的参数,去掉大量不重要的参数,来达到更好的学习效果。

注意:前馈神经网络(Feedforward NN)指每个神经元只与前一层的神经元相连,数据从前向后单向传播的 NN。
其内部结构不会形成有向环(对比后面要讲到的 RNN/LSTM)。

 

  • 输入层(Input layer),众多神经元(Neuron)接受大量非线形输入讯息。输入的讯息称为输入向量。
  • 输出层(Output layer),讯息在神经元链接中传输、分析、权衡,形成输出结果。输出的讯息称为输出向量。
  • 隐藏层(Hidden layer),简称“隐层”,是输入层和输出层之间众多神经元和链接组成的各个层面。如果有多个隐藏层,则意味着多个激活函数。

 

注意:上图中左侧的蓝色大矩阵表示输入数据,在蓝色大矩阵上不断运动的绿色小矩阵叫做卷积核,每次卷积核运动到一个位置,
它的每个元素就与其覆盖的输入数据对应元素相乘求积,然后再将整个卷积核内求积的结果累加,结果填注到右侧红色小矩阵中。
卷积核横向每次平移一列,纵向每次平移一行。最后将输入数据矩阵完全覆盖后,生成完整的红色小矩阵就是卷积运算的结果。

CNN 经常被用于处理图像,那么对应的输入数据就是一张图片的像素信息。

对于这样的输入数据,第一层卷积层可能只能提取一些低级的特征,如边缘、线条、角等,更多层的网络再从低级特征中迭代提取更复杂的特征。

2、卷积神经网络的结构

 

输入层(Input Layer):接受原始数据输入,例如图像,其尺寸与输入图像的尺寸相匹配。
卷积层(Convolutional Layer):负责特征提取。卷积核在输入数据上滑动,计算每个位置的卷积,从而得到特征图。
多个卷积核可以提取多种特征。通常会使用ReLU等激活函数来引入非线性。 池化层(Pooling Layer):减小特征图的尺寸,同时保留最重要的信息。常用的池化操作是最大池化和平均池化。 全连接层(Fully Connected Layer):将之前层次提取的特征进行扁平化,并通过全连接操作将其与输出层相连接。 输出层(Output Layer):根据任务的不同,可以是一个全连接层,也可以是一个Softmax层,用于分类问题。 批归一化层(Batch Normalization Layer):用于加速训练过程,提升模型的泛化性能。 Dropout层:在训练过程中随机断开一部分神经元,防止过拟合。 残差连接(Residual Connection):引入跳跃连接,可以在深层网络中减轻梯度消失问题。 卷积核(Kernel):卷积操作的核心部分,可以将其视为特征检测器。

 

 

 

 卷积神经网络通常由多个卷积层、池化层和全连接层组成。其中,卷积层和池化层用于提取输入数据的特征,全连接层用于分类或回归。

卷积层通常包括多个卷积核,每个卷积核对应一个特征图。卷积操作可以通过卷积核在输入数据上滑动得到,得到的特征图可以通过激活函数进行非线性变换。常见的激活函数有ReLU、sigmoid和tanh等。

池化层通常用于减少特征图的维度,从而降低计算复杂度。最大池化和平均池化是常见的池化操作,其原理是在特定的区域内取最大值或平均值。

全连接层通常用于分类或回归,其作用是将特征图转换为输出结果。全连接层通常包括多个神经元,每个神经元对应一个输出类别或回归值。

如图可以看到:

(1)两个神经元,即depth = 2, 意味着有两个滤波器。

(2)数据窗口每次移动两个步长取3*3的局部数据,即stride = 2。

(3)边缘填充,zero - padding = 1, 主要是为了防止遗漏边缘的像素信息。

          然后分别以两个滤波器filter为轴滑动数组进行卷积计算,得到两组不同的结果。

理解:
(1)左边是输入(7*7*3中, 7*7 代表图像的像素/长宽,
        3代表R、G、B三个颜色通道)
(2)中间部分是两个不同的滤波器Filter w0、filter w1
(3)最右边则是两个不同的输出
(4)随着左边数据窗口的平移滑动,滤波器Filter w0 / Filter w1对不同的局部数据进行卷积计算。

局部感知:左边数据在变化,每次滤波器都是针对某一局部的数据窗口进行卷积,这就是所谓的CNN中的局部感知机制。打个比方,滤波器就像一双眼睛,人类视角有限,一眼望去,只能看到这世界的局部。如果一眼就看到全世界,你会累死,而且一下子接受全世界所有信息,你大脑接收不过来。当然,即便是看局部,针对局部里的信息人类双眼也是有偏重、偏好的。比如看美女,对脸、胸、腿是重点关注,所以这3个输入的权重相对较大。

参数共享:数据窗口滑动,导致输入在变化,但中间滤波器Filter w0的权重(即每个神经元连接数据窗口的权重)是固定不变的,这个权重不变即所谓的CNN中的参数(权重)共享机制。

卷积计算:

图中最左边的三个输入矩阵就是我们的相当于输入d=3时有三个通道图,每个通道图都有一个属于自己通道的卷积核,我们可以看到输出(output)的只有两个特征图意味着我们设置的输出的d=2,有几个输出通道就有几层卷积核(比如图中就有FilterW0和FilterW1),这意味着我们的卷积核数量就是输入d的个数乘以输出d的个数(图中就是2*3=6个),其中每一层通道图的计算与上文中提到的一层计算相同,再把每一个通道输出的输出再加起来就是绿色的输出数字啦!

举例:

绿色输出的第一个特征图的第一个值:

1通道x[ : :0] 1*1+1*0 = 1 (0像素点省略)

2通道x[ : :1] 1*0+1*(-1)+2*0 = -1

3通道x[ : :2] 2*0 = 0

b = 1

输出:1+(-1)+ 0 + 1(这个是b)= 1

 

绿色输出的第二个特征图的第一个值:

1通道x[ : :0] 1*0+1*0 = 0 (0像素点省略)

2通道x[ : :1] 1*0+1*(-1)+2*0 = -1

3通道x[ : :2] 2*0 = 0 

b = 0

输出:0+(-1)+ 0 + 1(这个是b)= 0

3、卷积神经网络的训练方法

卷积神经网络的训练方法通常采用反向传播算法,其基本原理是通过计算损失函数的梯度来更新网络参数。常见的损失函数有交叉熵、均方误差等。

反向传播算法可以分为两个步骤:前向传播和反向传播。前向传播是指将输入数据通过网络得到输出结果的过程,反向传播是指根据损失函数计算梯度并更新网络参数的过程。

在训练过程中,通常采用随机梯度下降(Stochastic Gradient Descent,SGD)算法来更新网络参数。SGD算法的基本原理是通过计算损失函数的梯度来更新网络参数,从而使损失函数最小化。

构建CNN模型:

 1 '''开始建立CNN网络'''
 2 class CNN(nn.Module):
 3     def __init__(self):
 4         super(CNN,self).__init__()
 5         '''
 6         一般来说,卷积网络包括以下内容:
 7         1.卷积层
 8         2.神经网络
 9         3.池化层
10         '''
11         self.conv1=nn.Sequential(
12             nn.Conv2d(              #--> (1,28,28)
13                 in_channels=1,      #传入的图片是几层的,灰色为1层,RGB为三层
14                 out_channels=16,    #输出的图片是几层
15                 kernel_size=5,      #代表扫描的区域点为5*5
16                 stride=1,           #就是每隔多少步跳一下
17                 padding=2,          #边框补全,其计算公式=(kernel_size-1)/2=(5-1)/2=2
18             ),    # 2d代表二维卷积           --> (16,28,28)
19             nn.ReLU(),              #非线性激活层
20             nn.MaxPool2d(kernel_size=2),    #设定这里的扫描区域为2*2,且取出该2*2中的最大值          --> (16,14,14)
21         )
22  
23         self.conv2=nn.Sequential(
24             nn.Conv2d(              #       --> (16,14,14)
25                 in_channels=16,     #这里的输入是上层的输出为16层
26                 out_channels=32,    #在这里我们需要将其输出为32层
27                 kernel_size=5,      #代表扫描的区域点为5*5
28                 stride=1,           #就是每隔多少步跳一下
29                 padding=2,          #边框补全,其计算公式=(kernel_size-1)/2=(5-1)/2=
30             ),                      #   --> (32,14,14)
31             nn.ReLU(),
32             nn.MaxPool2d(kernel_size=2),    #设定这里的扫描区域为2*2,且取出该2*2中的最大值     --> (32,7,7),这里是三维数据
33         )
34  
35         self.out=nn.Linear(32*7*7,10)       #注意一下这里的数据是二维的数据
36  
37     def forward(self,x):
38         x=self.conv1(x)
39         x=self.conv2(x)     #(batch,32,7,740         #然后接下来进行一下扩展展平的操作,将三维数据转为二维的数据
41         x=x.view(x.size(0),-1)    #(batch ,32 * 7 * 7)
42         output=self.out(x)
43         return output

 开始训练:

 1 # 添加优化方法
 2 optimizer=torch.optim.Adam(cnn.parameters(),lr=LR)
 3 # 指定损失函数使用交叉信息熵
 4 loss_fn=nn.CrossEntropyLoss()
 5  
 6  
 7 '''
 8 开始训练我们的模型哦
 9 '''
10 step=0
11 for epoch in range(EPOCH):
12     #加载训练数据
13     for step,data in enumerate(train_loader):
14         x,y=data
15         #分别得到训练数据的x和y的取值
16         b_x=Variable(x)
17         b_y=Variable(y)
18  
19         output=cnn(b_x)         #调用模型预测
20         loss=loss_fn(output,b_y)#计算损失值
21         optimizer.zero_grad()   #每一次循环之前,将梯度清零
22         loss.backward()         #反向传播
23         optimizer.step()        #梯度下降
24  
25         #每执行50次,输出一下当前epoch、loss、accuracy
26         if (step%50==0):
27             #计算一下模型预测正确率
28             test_output=cnn(test_x)
29             y_pred=torch.max(test_output,1)[1].data.squeeze()
30             accuracy=sum(y_pred==test_y).item()/test_y.size(0)
31  
32             print('now epoch :  ', epoch, '   |  loss : %.4f ' % loss.item(), '     |   accuracy :   ' , accuracy)
33  
34 '''
35 打印十个测试集的结果
36 '''
37 test_output=cnn(test_x[:10])
38 y_pred=torch.max(test_output,1)[1].data.squeeze()       #选取最大可能的数值所在的位置
39 print(y_pred.tolist(),'predecton Result')
40 print(test_y[:10].tolist(),'Real Result')

卷积层维度变化:

(1)输入1*28*28,即1通道,28*28维;

(2)卷积层-01:16*28*28,即16个卷积核,卷积核维度5*5,步长1,边缘填充2,维度计算公式B = (A + 2*P - K) / S + 1,即(28+2*2-5)/1 +1 = 28

(3)池化层:池化层为2*2,所以输出为16*14*14

(4)卷积层-02:32*14*14,即32卷积核,其它同卷积层-01

(5)池化层:池化层为2*2,所以输出为32*7*7;

(6)fc层:由于输出为1*10,即10个类别的概率,那么首先对最后的池化层进行压缩为二维(1,32*7*7),然后全连接层维度(32*7*7,10),最后(1,32*7*7)*(32*7*7,10)

 

4、例子

 1 import tensorflow as tf
 2 from tensorflow.examples.tutorials.mnist import input_data
 3  
 4 # 加载MNIST数据集
 5 mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
 6  
 7 # 定义输入和输出
 8 x = tf.placeholder(tf.float32, [None, 784])
 9 y_ = tf.placeholder(tf.float32, [None, 10])
10  
11 # 定义第一个卷积层
12 W_conv1 = tf.Variable(tf.truncated_normal([5, 5, 1, 32], stddev=0.1))
13 b_conv1 = tf.Variable(tf.constant(0.1, shape=[32]))
14 x_image = tf.reshape(x, [-1, 28, 28, 1])
15 h_conv1 = tf.nn.relu(tf.nn.conv2d(x_image, W_conv1, strides=[1, 1, 1, 1], padding='SAME') + b_conv1)
16 h_pool1 = tf.nn.max_pool(h_conv1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
17  
18 # 定义第二个卷积层
19 W_conv2 = tf.Variable(tf.truncated_normal([5, 5, 32, 64], stddev=0.1))
20 b_conv2 = tf.Variable(tf.constant(0.1, shape=[64]))
21 h_conv2 = tf.nn.relu(tf.nn.conv2d(h_pool1, W_conv2, strides=[1, 1, 1, 1], padding='SAME') + b_conv2)
22 h_pool2 = tf.nn.max_pool(h_conv2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
23  
24 # 定义全连接层
25 W_fc1 = tf.Variable(tf.truncated_normal([7 * 7 * 64, 1024], stddev=0.1))
26 b_fc1 = tf.Variable(tf.constant(0.1, shape=[1024]))
27 h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64])
28 h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
29  
30 # 定义输出层
31 W_fc2 = tf.Variable(tf.truncated_normal([1024, 10], stddev=0.1))
32 b_fc2 = tf.Variable(tf.constant(0.1, shape=[10]))
33 y_conv = tf.matmul(h_fc1, W_fc2) + b_fc2
34  
35 # 定义损失函数和优化器
36 cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv))
37 train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
38  
39 # 定义评估模型的准确率
40 correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
41 accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
42  
43 # 训练模型
44 with tf.Session() as sess:
45     sess.run(tf.global_variables_initializer())
46     for i in range(20000):
47         batch = mnist.train.next_batch(50)
48         if i % 100 == 0:
49             train_accuracy = accuracy.eval(feed_dict={x: batch[0], y_: batch[1]})
50             print("step %d, training accuracy %g" % (i, train_accuracy))
51         train_step.run(feed_dict={x: batch[0], y_: batch[1]})
52     print("test accuracy %g" % accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels}))

该代码使用TensorFlow实现了一个简单的卷积神经网络,用于对MNIST手写数字进行分类。该网络包括两个卷积层、两个池化层和一个全连接层,采用ReLU激活函数和交叉熵损失函数。在训练过程中,采用随机梯度下降算法来更新网络参数。最终,该网络在测试集上的准确率达到了99%以上。

 

 

 

posted @ 2024-01-29 11:07  taohuaxiaochunfeng  阅读(117)  评论(0)    收藏  举报