传统神经网络的输入是一维的数据(比如28*28的图片,需要转化为一维向量)。
而卷积神经网络的输入是一个三维的(比如RGB)。
结构
卷积神经网络有以下结构:
- 输入层
- 卷积层
- 池化层
- 全连接层
输入层
顾名思义,输入层就是输入数据(可以是图片等数据)。
卷积层
卷积是什么?
在数学和信号处理中,卷积是一种将两个函数(或信号)结合以生成新函数的操作。卷积的操作可以用于信号的平滑、特征提取等多种用途。对于离散信号,卷积操作可以描述为:
给定两个离散信号 f[n]f[n] 和 g[n]g[n],它们的卷积 (f∗g)[n](f∗g)[n] 定义为:
- f[n]:第一个信号或函数(通常是输入信号)。
- g[n]:第二个信号或函数(通常是卷积核或滤波器)。
- n:卷积结果的位置索引。
- k:求和的索引变量,遍历所有可能的重叠位置。
在计算卷积时,我们将卷积核 gg 在信号 ff 上滑动,并对每个位置计算重叠部分的加权和。
对于有限长信号 f=[f0,f1,…,fM−1]f=[f0,f1,…,fM−1] 和卷积核 g=[g0,g1,…,gN−1]g=[g0,g1,…,gN−1],卷积的具体计算公式可以写成:
案例引入一
如果有一只猫咪的图片,我需要进行训练分类。
我们知道猫咪的特征是耳朵、眼睛、鼻子。我在第二张图片上画的红色圈圈就是重要的特征。相反,绿色圈圈的就是不重要的特征。
我们要做的就是将这些重要的特征提取出来,将不重要的特征删除。
首先先看一下卷积的过程:
我们来仔细讲解以下这个过程:
假如我有一张5*5*3的图片作为输入。
我们首将5*5*1的这一层拿出来,作为案例。
我们接下来会设置一个权重矩阵,为3*3的矩阵,然后将所选区域与权重矩阵对应相乘得到一个特征点。然后得到一个特征图。
第一次:1*3+2*2+1*1+2*2=12。
第二次:1*2+2*1+2*1+1*2+2*2=12.0
第三次:
第四次:2*1+2*3+2*1=10
第五次、六次、七次、八次、九次。
一般一张RGB的图片有3个通道,我们需要在这三个通道上分别做卷积,然后相加。
案例引入二
注意因为我们的图片此时是3维的,分别有RGB三个通道,每个通道都是7*7的,,所以我们能的卷积核(也就是权重w),也要是3维的,这里我们选择的卷积核是3*3*3的我们也可以选择4*4*3的,总之最后的一个维度一定要是3,因为要对应前面的3个通道。从上面图片可以看出来,我们可以选择多个参数,上面我们选择了w0和w1。每个参数(这里的参数是二维的,如w0、w1),输出得到的output是3个通道的相加值。
比如w0[:,:,0]提取到的是0;w0[:,:,1]提取到的是2;w0[:,:,2]提取到的是0,三个数和常数相加0+0+2+1=3,就是输出的第一行第一列。
我们要求每一层的卷积卷积核的规格必须一样,比如w0核w1的规格必须一样(都是3*3),在不同的卷积层规格可以不一样。
我们再想一下,为什么要用多个w(w0、w1)?我们在神经网络中是如何提取特征的?使用多个神经元来提取每个特征并且给予一定的权重,就是这样不断训练确定参数以预测。CNN中的思路也是如此,用多个w(卷积核)来提取不同的特征。
举个例子,如果我们项通过一个神经网络预测房子的价格,我们会有以下结构:
这里我们没有建立正常的大家认知的一个网络,而是自己根据自己的想法来建立的。可以看出第四个神经元有两个输入分别是离学校距离、噪音程度,根据常识,我们可以判断出这是一个与“教育”有关的特征,这里的特征会自己训练出一个权重。
因为神经网络可以自己训练出权重,所以我们可以像下面这样,每个输入都指向每一个神经元,即使有的输入与这个神经元没有太大的关系,我们设置参数的时候还是可以将其权重设置的非常小,不会影响我们最终的结果。
卷积层涉及的参数
滑动窗口步长
每次提取特征的区域移动的单位,可以为1、2.....。
步长为1的窗口:
其中红色的是初始位置,蓝色的是向着右边移动一个单位的位置,绿色的是向下移动1个单位的位置。
步长为2的窗口:
注意:得到的特征图的大小与步长、卷积核大小有关
边缘填充
我们在进行特征提取的时候,有的点提取了很多次,有的点只提取了一次,有的提取了两次,比如下面3*3的一个卷积核来提取一个5*5的图片,有绿色点的被提取了两次。
再次提取,有绿色点的被提取了3次。
我们可以发现规律,越靠近中间位置的点,被提取的次数越多,在边缘的点被提取的次数非常少,但是我们在识别图片的时候,我们并不知道边缘地区的特征是否重要。因此我们要尽可能的将图片上面的特征全部提取。
有一个方法就是在图片周围加上一圈0或者两圈0,这样原来在边缘的点也变得在中间了。这就叫做边缘填充。为什么不添加1、2或者其他呢?因为如果添加其他的会影响得到的特征图。
卷积核个数
卷积核个数就是指选取的参数的量w0、w1、w2等有多少个卷积核就得到多少个特征图。
计算公式
我们通常不会只进行一次卷积,就像神经网络通常不会只设置一层隐藏层。
注意:卷积核的个数,会影响下一层的深度。本层的深度会影响下一层的卷积核的深度。
其中:W1、H1表示输入的宽度和高度。W2、H2表示输出的卷积核的宽度和长度。F表示卷积核长和宽的大小,P表示加几圈0,S表示步长。
举例:输入32*32*3,用10个5*5*3的卷积核来提取特征,步长为1,边界填充为2。
解:(32-5+2*2)/1+1=32。长和宽都不变,深度变为10(因为有10个卷积核)。
卷积参数共享
为了减少参数的个数,我们让每一个卷积核(wi)共享的提取每一个特征,即每一个卷积核不变(这是因为直观的想,图片每个地方的特征都不一样,应该在不同地方用不同的参数提取,但是这样参数就太大了,所以用一套参数来提取所有特征)。
池化层
池化层是用来压缩图像的长度和高度的,特征图的个数不会变。
下图中选择2*2正方形中最大的作为特征。
整体过程
每进行一次卷积操作,就要用激活函数激活,如果特征多了就用池化层压缩。最后得到的也是一个多维的数据,将其展为1为再进行回归或者预测。
基于tensorflow构建神经网络
本实验采用CIFAR10 数据集完成,包含 10 类,共 60000 张彩色图片,每类图片有 6000 张。此数据集中 50000 个样例被作为训练集,剩余 10000 个样例作为测试集。类之间相互独立,不存在重叠的部分。
导包:
import tensorflow as tf
from tensorflow.keras import datasets, layers, models
import matplotlib.pyplot as plt
下载数据:
(train_images, train_labels), (test_images, test_labels) = datasets.cifar10.load_data()
train_images, test_images = train_images / 255.0, test_images / 255.0
搭建模型:
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
layers.Conv2D
作用:Conv2D
是卷积神经网络中的一个卷积层,它主要用于从输入数据(通常是图像)中提取特征。卷积层通过滑动一个卷积核(或过滤器)在输入图像上执行卷积操作,生成特征图(或激活图)。这种操作可以帮助神经网络捕捉图像中的局部特征(如边缘、角点等)。
参数:
- filters: 卷积核的数量,即输出特征图的深度。更多的卷积核能提取更多的特征。
- 示例:
filters=32
表示使用 32 个卷积核,每个卷积核会产生一个特征图。
- 示例:
- kernel_size: 卷积核的大小,通常是一个整数或元组,表示卷积核的高度和宽度。
- 示例:
kernel_size=(3, 3)
表示卷积核的大小为 3x3 像素。
- 示例:
- activation: 激活函数,用于引入非线性特性,帮助模型学习复杂的模式。
- 示例:
activation='relu'
使用 ReLU 激活函数。
- 示例:
- input_shape: 输入数据的形状,仅在模型的第一层需要指定。它是一个元组,表示输入数据的高度、宽度和通道数。
- 示例:
input_shape=(32, 32, 3)
表示输入图像的高度和宽度均为 32 像素,且有 3 个颜色通道(RGB)。
- 示例:
具体情景:
假设我们在处理 32x32 像素的彩色图像(RGB),并希望提取 32 个特征图。我们使用的卷积层定义如下:
layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3))
- filters=32:产生 32 个特征图。
- kernel_size=(3, 3):每个卷积核的大小为 3x3 像素。
- activation=‘relu’:对卷积结果应用 ReLU 激活函数。
- input_shape=(32, 32, 3):输入是 32x32 像素的彩色图像。
layers.MaxPooling2D
作用:MaxPooling2D
是一个池化层,用于下采样(缩小)特征图的尺寸。池化操作可以减少特征图的空间维度,从而减少计算量,帮助防止过拟合,并使特征更具不变性。常用的池化方式是最大池化,它选择池化窗口内的最大值。
参数:
- pool_size: 池化窗口的大小,通常是一个整数或元组,表示池化窗口的高度和宽度。
- 示例:
pool_size=(2, 2)
表示池化窗口的大小为 2x2 像素。
- 示例:
- strides: 池化操作的步幅,表示每次池化窗口移动的步长。默认为池化窗口大小。
- 示例:
strides=(2, 2)
表示池化窗口每次移动 2 像素。
- 示例:
- padding: 池化操作的填充方式。通常设置为
'valid'
(无填充)或'same'
(填充以保持输出尺寸与输入尺寸相同)。通常池化层不需要填充。- 示例:默认
'valid'
。
- 示例:默认
具体情景:
在处理卷积层提取的特征图时,为了缩小特征图尺寸并减少计算量,可以使用最大池化层:
layers.MaxPooling2D((2, 2))
- pool_size=(2, 2):池化窗口大小为 2x2 像素。
- strides=(2, 2):池化窗口每次移动 2 像素。
为了完成模型,需要将卷积基(形状为 (4, 4, 64))的最后一个输出张量馈送到一个或多个 Dense 层以执行分类。Dense 层将向量作为输入(即 1 维),而当前输出为 3 维张量。首先,将 3 维输出展平(或展开)为 1 维,然后在顶部添加一个或多个 Dense 层。CIFAR 有 10 个输出类,因此使用具有 10 个输出的最终 Dense 层。
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10))
layers.Dense
作用:Dense
是全连接层,用于将前一层的所有神经元与当前层的所有神经元连接。全连接层用于将特征映射到分类或回归任务的输出空间。它通常位于网络的末尾,用于整合和总结提取到的特征。
参数:
- units: 神经元的数量,决定了全连接层的输出维度。
- 示例:
units=64
表示该层有 64 个神经元。
- 示例:
- activation: 激活函数,用于对神经元的线性组合结果进行非线性变换。
- 示例:
activation='relu'
使用 ReLU 激活函数。
- 示例:
- use_bias: 是否使用偏置项,默认值为
True
。可以选择是否添加偏置项。
具体情景:
在特征提取后,我们使用全连接层将特征映射到最终的类别或回归值:
layers.Dense(64, activation='relu')
- units=64:该层有 64 个神经元。
- activation=‘relu’:对神经元的线性组合结果应用 ReLU 激活函数。
在分类任务的输出层,我们会有一个没有激活函数的全连接层,以便在之后应用 softmax 或其他激活函数来获取类别概率:
layers.Dense(10)
- units=10:输出层有 10 个神经元,通常表示 10 个类别。
编译和训练
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['accuracy'])
history = model.fit(train_images, train_labels, epochs=10,
validation_data=(test_images, test_labels))