卷积神经网络(CNN)详解:从基础操作到网络构建
1. 池化操作(Pooling)
池化是卷积神经网络(CNN)中的第二个基本操作,相较于卷积操作,它更容易理解。下面以最大池化(Max Pooling)为例进行详细说明。
假设我们有一个 4×4 的矩阵 A:
[
A =
\begin{pmatrix}
1 & 2 & 3 & 4 \
5 & 6 & 7 & 8 \
9 & 10 & 11 & 12 \
13 & 14 & 15 & 16
\end{pmatrix}
]
要进行最大池化,我们需要定义一个大小为 (n_K×n_K) 的区域,这里假设 (n_K = 2)。具体步骤如下:
1. 从矩阵 A 的左上角开始,选取一个 2×2 的区域,即:
[
\begin{pmatrix}
1 & 2 \
5 & 6
\end{pmatrix}
]
对这个区域内的元素应用最大函数,得到最大值,记为 (B_1),即 (B_1 = \max{1, 2, 5, 6} = 6)。
2. 将 2×2 的窗口向右移动两列(步长 (s = 2)),选取新的区域:
[
\begin{pmatrix}
3 & 4 \
7 & 8
\end{pmatrix}
]
同样应用最大函数,得到 (B_2 = \max{3, 4, 7, 8} = 8)。
3. 当无法再向右移动窗口时,将窗口向下移动两行,从矩阵 A 的左侧重新开始选取区域:
[
\begin{pmatrix}
9 & 10 \
13 & 14
\end{pmatrix}
]
得到 (B_3 = \max{9, 10, 13, 14} = 14)。
4. 最后,选取矩阵 A 右下角的 2×2 区域:
[
\begin{pmatrix}
11 & 12 \
15 & 16
\end{pmatrix}
]
得到 (B_4 = \max{11, 12, 15, 16} = 16)。
最终,我们将得到的四个值 (B_1)、(B_2)、(B_3) 和 (B_4) 构建成一个输出张量 B:
[
B =
\begin{pmatrix}
6 & 8 \
14 & 16
\end{pmatrix}
]
最大池化操作的输入是矩阵 A、步长 (s) 和核大小 (n_K),输出是一个新的矩阵 B,其维度计算公式与卷积操作相同:
[
n_B = \frac{n_A - n_K}{s} + 1
]
除了最大池化,还有平均池化(Average Pooling),它返回所选区域内元素的平均值。不过,最大池化是最常用的池化操作,平均池化虽然使用频率较低,但在特定的网络架构中也会出现。
2. 填充操作(Padding)
在处理图像时,有时卷积操作的结果矩阵维度与原始图像不同,这可能不是最优的。此时,可以使用填充操作。填充的基本思想是在最终图像的顶部、底部添加像素行,在右侧和左侧添加像素列,并填充一些值,使结果矩阵的大小与原始矩阵相同。常见的填充策略包括用零填充、用最近像素的值填充等。
假设我们有一个矩阵,经过零填充后可能如下所示:
import numpy as np
ex_out_padded = np.array([[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 30., 30., 0., 0., 0.],
[ 0., 0., 0., 30., 30., 0., 0., 0.],
[ 0., 0., 0., 30., 30., 0., 0., 0.],
[ 0., 0., 0., 30., 30., 0., 0., 0.],
[ 0., 0., 0., 30., 30., 0., 0., 0.],
[ 0., 0., 0., 30., 30., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.]])
如果使用填充 (p)(填充行和列的宽度),卷积和池化操作后矩阵 B 的最终维度计算公式为:
[
n_B = \left\lfloor\frac{n_A + 2p - n_K}{s} + 1\right\rfloor
]
需要注意的是,在处理真实图像时,彩色图像通常编码为三个通道(RGB),这意味着卷积和池化操作需要在三个维度(宽度、高度和颜色通道)上执行,会增加算法的复杂度。
3. CNN 的构建模块
卷积和池化操作是构建 CNN 层的基础。典型的 CNN 中通常包含以下几种层:
-
卷积层(Convolutional Layers)
:输入一个张量(可能是三维的,因为有三个颜色通道),例如具有特定维度的图像。应用一定数量的核(通常为 10、16 或更多),添加偏置,应用激活函数(如 ReLU)以引入非线性,最终产生输出矩阵 B。
-
池化层(Pooling Layers)
:通常用 POOL 和一个数字表示,如 POOL1。输入一个张量,对其应用池化操作后输出另一个张量。池化层没有需要学习的参数,但引入了额外的超参数 (n_K) 和步长 (s)。通常在池化层中不使用填充,因为池化的一个常见目的是减少张量的维度。
-
全连接层(Fully Connected Layers)
:与之前章节中介绍的层相同,即神经元与前一层和后一层的所有神经元都相连。
4. 卷积层详解
卷积层的输入是一个张量,例如图像。它会应用多个核(通常用 (n_c) 表示核的数量,也称为通道数),每个核独立地对输入进行卷积操作,然后将结果堆叠起来。最终的输出不再是一个简单的二维矩阵,而是一个三维张量 (\hat{B}),其维度为 (n_B×n_B×n_c)。
卷积层中网络在训练阶段学习的参数是核的元素。假设我们有 (n_c) 个维度为 (n_K×n_K) 的核,那么卷积层中的参数数量为 (n_K^2n_c)。这个数量与输入图像的大小无关,有助于减少过拟合,特别是在处理大尺寸输入图像时。
以下是一个简单的 mermaid 流程图,展示了卷积层的工作流程:
graph TD;
A[输入图像] --> B[应用多个核];
B --> C[每个核独立卷积];
C --> D[结果堆叠];
D --> E[输出三维张量];
5. 池化层详解
池化层通常跟在卷积层之后,用于减少特征图的维度。常见的池化操作有最大池化和平均池化,其中最大池化更为常用。
池化层的主要作用包括:
-
减少计算量
:通过降低特征图的维度,减少后续层的计算负担。
-
增强特征的鲁棒性
:对局部区域进行池化操作,使得特征对输入的小变化具有更强的鲁棒性。
以下是池化层的操作步骤总结:
| 步骤 | 操作 |
| ---- | ---- |
| 1 | 定义池化窗口大小 (n_K) 和步长 (s) |
| 2 | 从输入特征图的左上角开始,选取 (n_K×n_K) 的区域 |
| 3 | 对选取的区域应用池化函数(如最大函数或平均函数) |
| 4 | 将池化窗口按照步长 (s) 移动,重复步骤 2 和 3,直到覆盖整个输入特征图 |
6. 层的堆叠
在 CNN 中,通常会将卷积层和池化层依次堆叠在一起。卷积层后面通常跟着池化层,有时这两层一起被称为一个层,因为池化层没有可学习的权重,被视为与卷积层相关的简单操作。
以下是一个简单的 mermaid 流程图,展示了卷积层和池化层的堆叠方式:
graph TD;
A[输入] --> B[卷积层];
B --> C[池化层];
C --> D[卷积层];
D --> E[池化层];
E --> F[全连接层];
F --> G[输出层];
7. 构建 CNN 示例
下面我们将构建一个简单的 CNN 网络,以帮助你了解整个过程和代码实现。我们将使用 Zalando 数据集进行训练,网络架构如下:
1.
卷积层 1
:使用 6 个 5×5 的滤波器,步长 (s = 1)。
2.
最大池化层 1
:窗口大小为 2×2,步长 (s = 2)。
3. 对前一层的输出应用 ReLU 激活函数。
4.
卷积层 2
:使用 16 个 5×5 的滤波器,步长 (s = 1)。
5.
最大池化层 2
:窗口大小为 2×2,步长 (s = 2)。
6. 对前一层的输出应用 ReLU 激活函数。
7.
全连接层 1
:包含 128 个神经元,激活函数为 ReLU。
8.
全连接层 2
:包含 10 个神经元,用于对 Zalando 数据集进行分类。
9.
Softmax 输出神经元
。
以下是具体的代码实现:
import pandas as pd
import numpy as np
import tensorflow as tf
# 导入数据集
data_train = pd.read_csv('fashion-mnist_train.csv', header = 0)
data_test = pd.read_csv('fashion-mnist_test.csv', header = 0)
# 准备数据
labels = data_train['label'].values.reshape(1, 60000)
labels_ = np.zeros((60000, 10))
labels_[np.arange(60000), labels] = 1
labels_ = labels_.transpose()
train = data_train.drop('label', axis=1)
labels_dev = data_test['label'].values.reshape(1, 10000)
labels_dev_ = np.zeros((10000, 10))
labels_dev_[np.arange(10000), labels_dev] = 1
test = data_test.drop('label', axis=1)
# 归一化数据
train = np.array(train / 255.0)
dev = np.array(test / 255.0)
labels_ = np.array(labels_)
labels_test_ = np.array(labels_dev_)
# 定义占位符
x = tf.placeholder(tf.float32, shape=[None, 28*28])
x_image = tf.reshape(x, [-1, 28, 28, 1])
y_true = tf.placeholder(tf.float32, shape=[None, 10])
y_true_scalar = tf.argmax(y_true, axis=1)
# 定义构建卷积层的函数
def new_conv_layer(input, num_input_channels, filter_size, num_filters):
shape = [filter_size, filter_size, num_input_channels, num_filters]
weights = tf.Variable(tf.truncated_normal(shape, stddev=0.05))
biases = tf.Variable(tf.constant(0.05, shape=[num_filters]))
layer = tf.nn.conv2d(input=input, filter=weights, strides=[1, 1, 1, 1], padding='SAME')
layer += biases
return layer, weights
# 定义构建池化层的函数
def new_pool_layer(input):
layer = tf.nn.max_pool(value=input, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
return layer
# 定义应用 ReLU 激活函数的函数
def new_relu_layer(input_layer):
layer = tf.nn.relu(input_layer)
return layer
# 定义构建全连接层的函数
def new_fc_layer(input, num_inputs, num_outputs):
weights = tf.Variable(tf.truncated_normal([num_inputs, num_outputs], stddev=0.05))
biases = tf.Variable(tf.constant(0.05, shape=[num_outputs]))
layer = tf.matmul(input, weights) + biases
return layer
# 构建网络
layer_conv1, weights_conv1 = new_conv_layer(input=x_image, num_input_channels=1, filter_size=5, num_filters=6)
layer_pool1 = new_pool_layer(layer_conv1)
layer_relu1 = new_relu_layer(layer_pool1)
layer_conv2, weights_conv2 = new_conv_layer(input=layer_relu1, num_input_channels=6, filter_size=5, num_filters=16)
layer_pool2 = new_pool_layer(layer_conv2)
layer_relu2 = new_relu_layer(layer_pool2)
# 扁平化特征图
num_features = layer_relu2.get_shape()[1:4].num_elements()
layer_flat = tf.reshape(layer_relu2, [-1, num_features])
# 构建全连接层
layer_fc1 = new_fc_layer(layer_flat, num_inputs=num_features, num_outputs=128)
layer_relu3 = new_relu_layer(layer_fc1)
layer_fc2 = new_fc_layer(input=layer_relu3, num_inputs=128, num_outputs=10)
# 评估预测结果
y_pred = tf.nn.softmax(layer_fc2)
y_pred_scalar = tf.argmax(y_pred, axis=1)
# 定义损失函数
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=layer_fc2, labels=y_true))
# 定义优化器
optimizer = tf.train.AdamOptimizer(learning_rate=1e-4).minimize(cost)
# 定义评估准确率的操作
correct_prediction = tf.equal(y_pred_scalar, y_true_scalar)
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
# 训练网络
num_epochs = 10
batch_size = 100
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for epoch in range(num_epochs):
train_accuracy = 0
# 这里省略了具体的批次训练代码,可根据实际情况补充
通过以上步骤,我们完成了一个简单 CNN 网络的构建和训练。在实际应用中,还可以进行超参数调优和模型优化,以提高模型的性能。
卷积神经网络(CNN)详解:从基础操作到网络构建
8. 代码解释与关键要点
为了更好地理解上述构建 CNN 网络的代码,下面对关键部分进行详细解释。
8.1 数据准备
data_train = pd.read_csv('fashion-mnist_train.csv', header = 0)
data_test = pd.read_csv('fashion-mnist_test.csv', header = 0)
这两行代码使用
pandas
库读取训练集和测试集数据,文件格式为 CSV。
labels = data_train['label'].values.reshape(1, 60000)
labels_ = np.zeros((60000, 10))
labels_[np.arange(60000), labels] = 1
labels_ = labels_.transpose()
train = data_train.drop('label', axis=1)
此部分代码处理训练集的标签,将标签转换为独热编码(One-Hot Encoding)形式,同时分离出训练数据。
labels_dev = data_test['label'].values.reshape(1, 10000)
labels_dev_ = np.zeros((10000, 10))
labels_dev_[np.arange(10000), labels_dev] = 1
test = data_test.drop('label', axis=1)
类似地,对测试集的标签进行处理并分离出测试数据。
train = np.array(train / 255.0)
dev = np.array(test / 255.0)
labels_ = np.array(labels_)
labels_test_ = np.array(labels_dev_)
将训练数据和测试数据进行归一化处理,将像素值缩放到 0 到 1 之间,有助于模型的训练和收敛。
8.2 占位符定义
x = tf.placeholder(tf.float32, shape=[None, 28*28])
x_image = tf.reshape(x, [-1, 28, 28, 1])
y_true = tf.placeholder(tf.float32, shape=[None, 10])
y_true_scalar = tf.argmax(y_true, axis=1)
-
x是输入数据的占位符,形状为[None, 28*28],None表示可以接受任意数量的样本。 -
x_image将输入数据从一维向量重新调整为二维图像的形式,形状为[-1, 28, 28, 1],其中-1表示自动计算该维度的大小,1表示单通道(灰度图像)。 -
y_true是真实标签的占位符,形状为[None, 10],表示 10 个类别。 -
y_true_scalar是将真实标签从独热编码转换为标量形式。
8.3 层构建函数
-
卷积层函数
new_conv_layer:
def new_conv_layer(input, num_input_channels, filter_size, num_filters):
shape = [filter_size, filter_size, num_input_channels, num_filters]
weights = tf.Variable(tf.truncated_normal(shape, stddev=0.05))
biases = tf.Variable(tf.constant(0.05, shape=[num_filters]))
layer = tf.nn.conv2d(input=input, filter=weights, strides=[1, 1, 1, 1], padding='SAME')
layer += biases
return layer, weights
该函数用于构建卷积层,首先定义卷积核的形状,然后初始化权重和偏置,使用
tf.nn.conv2d
函数进行卷积操作,最后加上偏置并返回卷积层和权重。
-
池化层函数
new_pool_layer:
def new_pool_layer(input):
layer = tf.nn.max_pool(value=input, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
return layer
此函数使用
tf.nn.max_pool
函数进行最大池化操作,池化窗口大小为
2x2
,步长为 2。
-
ReLU 激活函数层
new_relu_layer:
def new_relu_layer(input_layer):
layer = tf.nn.relu(input_layer)
return layer
该函数使用
tf.nn.relu
函数对输入层应用 ReLU 激活函数。
-
全连接层函数
new_fc_layer:
def new_fc_layer(input, num_inputs, num_outputs):
weights = tf.Variable(tf.truncated_normal([num_inputs, num_outputs], stddev=0.05))
biases = tf.Variable(tf.constant(0.05, shape=[num_outputs]))
layer = tf.matmul(input, weights) + biases
return layer
该函数用于构建全连接层,初始化权重和偏置,使用
tf.matmul
函数进行矩阵乘法,然后加上偏置并返回全连接层。
8.4 网络构建与训练
layer_conv1, weights_conv1 = new_conv_layer(input=x_image, num_input_channels=1, filter_size=5, num_filters=6)
layer_pool1 = new_pool_layer(layer_conv1)
layer_relu1 = new_relu_layer(layer_pool1)
layer_conv2, weights_conv2 = new_conv_layer(input=layer_relu1, num_input_channels=6, filter_size=5, num_filters=16)
layer_pool2 = new_pool_layer(layer_conv2)
layer_relu2 = new_relu_layer(layer_pool2)
按照之前定义的网络架构,依次构建卷积层、池化层和 ReLU 激活函数层。
num_features = layer_relu2.get_shape()[1:4].num_elements()
layer_flat = tf.reshape(layer_relu2, [-1, num_features])
将卷积层和池化层的输出进行扁平化处理,以便输入到全连接层。
layer_fc1 = new_fc_layer(layer_flat, num_inputs=num_features, num_outputs=128)
layer_relu3 = new_relu_layer(layer_fc1)
layer_fc2 = new_fc_layer(input=layer_relu3, num_inputs=128, num_outputs=10)
构建全连接层并应用 ReLU 激活函数。
y_pred = tf.nn.softmax(layer_fc2)
y_pred_scalar = tf.argmax(y_pred, axis=1)
使用
tf.nn.softmax
函数将全连接层的输出转换为概率分布,然后将预测结果转换为标量形式。
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=layer_fc2, labels=y_true))
optimizer = tf.train.AdamOptimizer(learning_rate=1e-4).minimize(cost)
定义损失函数为交叉熵损失,使用 Adam 优化器进行模型训练,学习率设置为
1e-4
。
correct_prediction = tf.equal(y_pred_scalar, y_true_scalar)
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
定义准确率评估操作,通过比较预测结果和真实标签来计算准确率。
num_epochs = 10
batch_size = 100
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for epoch in range(num_epochs):
train_accuracy = 0
# 这里省略了具体的批次训练代码,可根据实际情况补充
设置训练的轮数和批次大小,初始化所有变量,然后进行训练。在每一轮训练中,可根据实际情况补充具体的批次训练代码。
9. CNN 的优势与应用场景
9.1 优势
- 利用二维信息 :CNN 能够充分利用输入图像的二维结构信息,而不是将图像展平为一维向量,这使得模型能够更好地捕捉图像中的空间特征。
- 参数共享 :卷积层中的卷积核在整个图像上共享,大大减少了模型的参数数量,降低了计算复杂度,同时也有助于减少过拟合。
- 特征提取能力 :通过卷积和池化操作,CNN 能够自动提取图像中的不同层次的特征,从低级的边缘、纹理特征到高级的物体形状、语义特征。
9.2 应用场景
- 图像分类 :如识别图片中的物体类别,例如判断一张图片是猫还是狗。
- 目标检测 :在图像中定位和识别多个目标物体,例如在一张街道照片中检测出汽车、行人等。
- 语义分割 :将图像中的每个像素分配到不同的类别,例如将医学图像中的肿瘤区域分割出来。
10. 总结
本文详细介绍了卷积神经网络(CNN)的基本操作,包括池化、填充,以及 CNN 的构建模块,如卷积层、池化层和全连接层。通过一个具体的代码示例,展示了如何构建和训练一个简单的 CNN 网络。同时,对代码的关键部分进行了详细解释,并阐述了 CNN 的优势和应用场景。
在实际应用中,可以根据具体任务和数据集的特点,调整网络架构和超参数,进一步优化模型的性能。例如,可以尝试不同的卷积核大小、数量,调整池化窗口和步长,或者使用不同的激活函数和优化器。此外,还可以进行数据增强、正则化等操作,以提高模型的泛化能力。
希望本文能够帮助读者更好地理解 CNN 的原理和实现,为进一步深入学习和应用 CNN 奠定基础。
以下是一个简单的 mermaid 流程图,展示 CNN 整体的工作流程:
graph TD;
A[输入图像] --> B[卷积层];
B --> C[池化层];
C --> D[ReLU激活];
D --> E[卷积层];
E --> F[池化层];
F --> G[ReLU激活];
G --> H[扁平化];
H --> I[全连接层];
I --> J[ReLU激活];
J --> K[全连接层];
K --> L[Softmax输出];
L --> M[预测结果];
通过这个流程图,可以更直观地看到 CNN 从输入图像到最终预测结果的整个过程。
超级会员免费看

被折叠的 条评论
为什么被折叠?



