深度学习(2)——卷积神经网络CNN
作者:禅与计算机程序设计艺术
1.简介
CNN是Convolutional Neural Network的缩写,也即卷积神经网络。它主要应用于图像识别、理解和分类任务,并被认为是深度学习领域的重要模型之一。其基本架构通常包括多个卷积层与池化层,并通过全连接层进行连接。在过去的十多年中,卷积神经网络在计算机视觉领域引发了革命性的变化,在图像处理和分析方面取得了显著成效。本文旨在深入探讨CNN的核心概念及其运行机制。
2.基本概念术语说明
(1)图像特征提取
在计算机视觉领域中,图像特征指的是从图像的部分或整体属性中提取信息,并且这些信息能够辅助后续的任务如图象解析与推断。卷积神经网络(CNN)通过从输入图象中提取关键特征来实现识别特定物体的任务。
局部感知器(local receptive field,LRF)
在计算机视觉领域中,图像被定义为由像素构成的矩形矩阵。
池化层(pooling layer)
池化层在CNN架构中扮演着关键角色,在降低运算复杂度的同时实现信息的有效浓缩。该过程通过采样该区域内的像素值并进行汇总运算,在一定程度上保留了空间位置信息的同时显著降低了计算负荷。这一操作不仅能够有效减少模型所需参数数量还能显著降低运行时计算开销同时还能使模型更加鲁棒地处理输入数据并提升分类准确性。
多通道(multi-channel input)
CNN采用多通道设计,在实际应用中通常将输入图像划分为红绿蓝三种独立颜色通道来获取图像细节信息。各独立通道之间互不共享信息但在网络训练过程中可以通过多个卷积核和池化层进行提取和融合以增强特征表达能力。
(2)CNN基本结构
CNN的基本结构分为五个部分:
- 卷积层:卷积层的核心是卷积运算,卷积运算实际上是两个矩阵相乘的过程。先将输入图像作相应的扩充(padding),然后在卷积核的移动方向上滑动,逐点与卷积核相乘,再求和,最后应用激活函数。直观地来说,卷积层是对局部区域内的像素点进行加权求和的过程。
- 激活函数:激活函数是卷积层的输出结果经过非线性变换后的结果,其目的是为了引入非线性因素,增强网络的非线性拟合能力。常用的激活函数有Sigmoid,Tanh,ReLU等。
- 归一化:归一化是指数据标准化,目的是为了让数据具有零均值和单位方差。这样做的好处之一是使得训练阶段的数据收敛速度更快,减少过拟合发生的风险。归一化方法有BN(Batch normalization)和LN(Layer normalization)。
- 池化层:池化层是指对卷积层输出的结果进行下采样,缩小到下一级的空间尺度,从而达到减少参数数量和计算量的目的。池化层主要有最大池化和平均池化。
- 拼接层(concatenation layer):拼接层是指将不同层的输出结果拼接起来,作为最终的输出结果。
一般情况下,CNN包括以下几个模块:
- 卷积层:卷积层通常包含若干个卷积层块,每个卷积层块又包含若干个卷积层,通过不同大小的卷积核进行卷积操作,提取不同级别的图像特征。卷积层也可以用来做特征融合。
- 池化层:池化层通常包含若干个池化层,作用是对特征图进行下采样,缩小到下一级的空间尺度。
- 全连接层:全连接层可以看作是多层感知机(MLP),将卷积层输出的特征向量转换成一个定长向量,作为下游的分类器的输入。
(3)CNN参数共享及特征重用
参数共享和特征重用主要采用的两种技术。其中一种称为参数共享,在这种机制下,多个卷积层通常会使用相同的卷积核参数进行操作。另一种称为特征重用,则通过不同卷积层共用同一个卷积核的方式实现对局部感知单元的不同响应。具体而言,在这种机制下,各个不同的局部感知单元能够根据各自的位置特性获得独特的感知结果。值得注意的是,在这一过程中不仅能够减少模型所需的最大滤波器数量从而降低计算复杂度还能有效提升整体检测精度
最典型的参数共享实例是ResNet网络,在其设计中巧妙地引入了残差连接机制。这种机制使得每个卷积层都能够直接继承并利用上一层的所有输出信息,并在此基础上完成进一步的计算操作以实现特征重用功能。值得注意的是,在Inception模块中同样实现了参数共享的应用,在这一结构中将多种尺寸的卷积核并列设置于其中,并从各个层次提取丰富的特征信息以满足复杂的数据处理需求。
3.核心算法原理和具体操作步骤以及数学公式讲解
(1)卷积层的计算流程
卷积层的计算流程包括以下四步:
- 输入图像边缘填充(padding):向图像两侧边缘注入指定数量零值像素块。
- 卷积核滑动操作:根据卷积核大小及设定步长参数,在不同位置滑动卷积内核,并对对应区域进行逐元素乘法求和运算。
- 输出数值经过非线性变换处理:输出数值经过非线性转换后得到新的特征表示。
- 池化层的作用是对卷积层输出结果进行下采样处理:池化操作将高分辨率的空间信息转换为低分辨率特征表示的同时减少计算量并降低模型复杂度。
对于卷积层中的滤波器单元(即卷积核),我们定义其高度和宽度参数分别为K_h和K_w;同时设定其在水平和垂直方向上的偏移量分别为S_h和S_w。其中,在图像数据中滑动应用滤波器的过程可以用以下公式描述:其中(n, m)表示滤波器单元的起始位置坐标,在此定义中(row, column)坐标系被采用;而(K_h, K_w)则代表滤波器单元的整体尺寸参数;最后地,在二维矩阵空间中第$l行第j列的位置对应着特定的滤波器单元应用位置。
对于输入图像X和卷积核\theta,卷积运算可表示为: 其中*表示卷积操作。
在卷积核θ中,必须满足一定条件的正则化要求,在于通过限制其模长来防止模型过度拟合训练数据。常用的手段是基于L²范数的正则化技术,在于使得其模长被限定为固定值的同时引入了参数共享机制以避免过拟合等现象。
(2)激活函数的选择
选择合适的激活函数对神经网络模型的表现具有关键影响。常见使用的激活函包括sigmoid、tanh和relu等。通常而言,在神经网络模型中选择合适的激活函至关重要。其中relu的优势在于计算效率高(运算速度快),但存在容易导致梯度消失或爆炸的问题;sigmoid的优势在于其输出值被限制在[0,1]范围内,在分类问题中表现良好;然而它的主要缺点是容易饱和(当输入过大或过小时会趋于稳定),导致其变化趋势较为缓慢;而tanh则具有对称的输出范围[-1,1]和较低的饱和程度(导数值较小),尽管如此,在某些情况下其收敛速度可能会较慢。
(3)池化层的选择
在CNN模型中,池化层的类型对其性能表现具有显著影响。在CNN架构中存在两种主要的下采样机制:最大值下采样和平均值下采样。在最大值下采样中,系统会选取该区域的最大数值;而计算平均值下采样的过程中,则会对该区域的所有元素进行求均操作。与之相比,在最大值下采样模式下虽然能够有效提取关键特征点;但其缺点在于可能会忽略部分边缘信息;而采用平均值下采样的方式则可能造成信息丢失的问题。值得注意的是,在不同类型的池化结构中均可以通过引入衰减系数参数,在不同层级的特征提取过程中实现对输出特征保持稳定性的控制
池化层通常会减少输入的空间维度。
为了实现这一目标,通常会将输入划分为若干个局部区域,并对这些区域进行汇总处理。
具体来说,在设计时需要考虑以下几个关键参数:滤波器尺寸、步长间隔以及汇聚策略(即采用最大值还是平均值)。
(4)残差网络(Residual network)
残差网络代表了2015年ImageNet图像分类挑战赛的最佳解决方案,并以其独特的设计著称。该技术的核心创新在于将其卷积层分割为快路径和慢路径两个模块。具体而言,在线快通道主要用于提取输入数据的关键特征;而远线缓通道则专注于获取更高层次的信息。值得注意的是,在线快通道能够有效捕捉局部细节特征,并通过跳跃连接实现跨层级的信息传递;而远线缓通道则通过捕获更全局的空间关系来增强模型的整体表现力。这种设计使得残差网络在深度学习领域取得了显著的成功,并且在后续的研究中得到了广泛的应用和发展
4.具体代码实例和解释说明
(1)MNIST手写数字识别实验
以下是一段用于识别手写数字的MNIST数据集代码示例,在TensorFlow框架下开发。请确保已经安装并导入了必要的库与工具包随后,在编写代码之前,请确保已经安装并导入了必要的库与工具包
import tensorflow as tf
from tensorflow import keras
from sklearn.model_selection import train_test_split
import numpy as np
import matplotlib.pyplot as plt
代码解读
然后,加载MNIST数据集:
mnist = keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
代码解读
提醒大家注意的是:MNIST数据集属于分类问题的一种类型,并且因此仅有两个标签分别代表0到9的数字。
接着,将数据集进行预处理,将像素值从0255压缩到0 1:
x_train, x_test = x_train / 255.0, x_test / 255.0
代码解读
定义模型结构:
model = keras.models.Sequential([
keras.layers.Flatten(input_shape=(28, 28)),
keras.layers.Dense(128, activation='relu'),
keras.layers.Dropout(0.2),
keras.layers.Dense(10)
])
代码解读
该Flatten层用于将图像转换为一维数据序列,并将其压缩为适合后续处理的数据格式。值得注意的是,在整个神经网络架构中除了最后一层输出单元之外的所有中间各层均采用了Relu激活函数进行激活计算。为了防止模型出现过拟合现象通过Dropout机制可以有效防止模型过拟合问题从而降低整个网络的复杂度这有助于提升模型在未知数据上的泛化能力同时还能随机移除部分神经元节点进而降低网络复杂度以避免过于复杂的模型结构导致的学习风险
编译模型:
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['accuracy'])
代码解读
这里使用了Adam优化器,稀疏分类交叉熵损失函数,以及准确率指标。
训练模型:
history = model.fit(x_train, y_train, epochs=10, validation_split=0.1)
代码解读
这里设置了训练轮数为10,并且验证集占总数据集的10%。
最后,评估模型效果:
test_loss, test_acc = model.evaluate(x_test, y_test, verbose=2)
print('\nTest accuracy:', test_acc)
代码解读
这里打印了测试集上的准确率。
画出模型训练过程的精度曲线:
plt.plot(history.history['accuracy'], label='accuracy')
plt.plot(history.history['val_accuracy'], label = 'val_accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.ylim([0.5, 1])
plt.legend(loc='lower right')
代码解读
(2)AlexNet模型实现
Alex网作为深度神经网络(DNN)中的开创性研究, 在ImageInt分类任务中实现了显著的效果。本节内容将详细展示如何利用PyTorch框架构建一个基于Alex网架构的深度学习模型
import tensorflow as tf
from tensorflow import keras
class AlexNet(tf.keras.Model):
def __init__(self, num_classes=10):
super().__init__()
self.conv1 = keras.layers.Conv2D(filters=96,
kernel_size=[11, 11],
strides=[4, 4],
padding="same",
activation="relu")
self.maxpool1 = keras.layers.MaxPooling2D(pool_size=[3, 3], strides=[2, 2])
self.conv2 = keras.layers.Conv2D(filters=256,
kernel_size=[5, 5],
strides=[1, 1],
padding="same",
activation="relu")
self.maxpool2 = keras.layers.MaxPooling2D(pool_size=[3, 3], strides=[2, 2])
self.conv3 = keras.layers.Conv2D(filters=384,
kernel_size=[3, 3],
strides=[1, 1],
padding="same",
activation="relu")
self.conv4 = keras.layers.Conv2D(filters=384,
kernel_size=[3, 3],
strides=[1, 1],
padding="same",
activation="relu")
self.conv5 = keras.layers.Conv2D(filters=256,
kernel_size=[3, 3],
strides=[1, 1],
padding="same",
activation="relu")
self.maxpool5 = keras.layers.MaxPooling2D(pool_size=[3, 3], strides=[2, 2])
self.flatten = keras.layers.Flatten()
self.dense1 = keras.layers.Dense(units=4096, activation="relu")
self.dropout1 = keras.layers.Dropout(rate=0.5)
self.dense2 = keras.layers.Dense(units=4096, activation="relu")
self.dropout2 = keras.layers.Dropout(rate=0.5)
self.output_layer = keras.layers.Dense(num_classes, activation="softmax")
def call(self, inputs, training=False):
# Conv Block 1
conv1 = self.conv1(inputs)
maxpool1 = self.maxpool1(conv1)
# Conv Block 2
conv2 = self.conv2(maxpool1)
maxpool2 = self.maxpool2(conv2)
# Conv Block 3
conv3 = self.conv3(maxpool2)
# Conv Block 4
conv4 = self.conv4(conv3)
# Conv Block 5
conv5 = self.conv5(conv4)
maxpool5 = self.maxpool5(conv5)
flattened = self.flatten(maxpool5)
dense1 = self.dense1(flattened)
dropout1 = self.dropout1(dense1, training=training)
dense2 = self.dense2(dropout1)
dropout2 = self.dropout2(dense2, training=training)
output_layer = self.output_layer(dropout2)
return output_layer
if __name__ == '__main__':
alexnet = AlexNet()
print(alexnet.summary())
inputs = keras.Input((227, 227, 3))
outputs = alexnet(inputs)
new_model = keras.Model(inputs=inputs, outputs=outputs)
print(new_model.summary())
代码解读
该模型的代码实现较为简洁。为了构建该模型架构,我首先定义了一个自继承于tensorflow.keras.Model的AlexNet类,并重新实现了其call()方法以明确计算流程。其中包含了五个卷积模块以及三层全连接层结构,并且每一层模块均遵循AlexNet论文中所提出的构建方案。
随后我构建了一个AlexNet类的对象,并通过传入num_classes参数来指定模型最后一层全连接层所需的神经元数量。并最终完成该网络架构的所有输入与输出的操作
在当前操作中, 我基于AlexNet的对象生成了一个新的模型. 需要特别注意的是, 在此场景下所设定的输入尺寸应当满足227\times 227\times 3的要求, 因此我们在这里定义了一个名为inputs的输入张量.
至此,AlexNet模型的搭建就完成了。
