整体架构:
- 卷积层(Convolutional Layer):包含卷积操作,使用卷积核对输入的特征图进行卷积运算,提取图像的空间特征。卷积层通常与激活函数层结合使用。
- 池化层(Pooling Layer):用于下采样,通过最大值池化或平均值池化降低特征图的维度,减少过拟合。池化层没有激活函数。
- 全连接层(Fully Connected Layer):将上一层的输出reshape为一维,实现分类或回归预测。相当于常规神经网络的隐藏层和输出层(softmax)。
- 激活函数(Activation Function):一般在卷积层和全连接层后使用,主要用于非线性变换,如ReLU、Sigmoid等。
- 批规范化(Batch Normalization):加速深层网络的训练,提高模型的泛化能力。一般在激活函数之后使用。
- 损失函数(Loss Function):用于计算网络的预测输出与真实标签之间的误差,指导网络进行优化更新,如交叉熵损失函数等。
- 优化器(Optimizer):如Adam、SGD等,用于更新网络中各层的参数,优化损失函数。
卷积层+池化层的组合可以在隐藏层出现很多次,上图中出现两次。而实际上这个次数是根据模型的需要而来的。当然我们也可以灵活使用使用卷积层+卷积层,或者卷积层+卷积层+池化层的组合,这些在构建模型的时候没有限制。但是最常见的CNN都是若干卷积层+池化层的组合。
卷积层
卷积层通过卷积操作来提取图像的特征。
卷积概念的理解
其主要工作原理如下:
- 利用卷积核对输入的特征图进行滑动扫描,在每个位置上计算与卷积核的内积,得到特征图中的每个像素点的特征响应。
- 对特征响应应用非线性激活函数(如ReLU),得到激活特征图。
- 重复上述步骤,每层提取更高层次的特征,形成特征层。
那么,卷积层是如何具体提取特征的呢?
这主要体现在卷积核的选择上:
- 利用小的卷积核(3x3或5x5大小)可以检测输入特征图中的小型特征,如边缘、线条等,从而实现低层特征的提取。
- 使用更大的卷积核(7x7或更大)可以检测更大尺度的特征,如物体部件等,从而提取更高层次的特征。
- 通过调整卷积核个数,可以提取不同类型的特征,检测不同概念的特征块。
- 通过多层卷积操作,低层特征会组合成更高层的语义特征,实现对图像特征的层层抽象。
除卷积核设定外,其他因素如步长、填充、池化等也会影响特征的提取:
- 步长控制卷积操作的滑动步长,小步长可以提取更细致的特征,大步长会略过一些特征。
- 填充可扩充输入特征图,使其长度和宽度不变,利于检测图像边缘处的特征。
- 池化用于整合相近特征,提高特征鲁棒性。池化步长控制整合的粒度。
所以,卷积层通过调整卷积核、填充、步长、池化等参数以及多层叠加,可以实现对图像从低层到高层的特征提取和抽象。这也是卷积神经网络能在图像领域表现优异的原因。
Pytorch 代码示例:
import torch
import torch.nn as nn
# 定义卷积层
conv1 = nn.Conv2d(
in_channels=3, # 输入特征图的通道数。对于RGB图像为3
out_channels=32, # 输出通道数,即卷积核个数,即输出特征图的通道数。
kernel_size=3, # 卷积核尺寸
stride=1, # 步长
padding=1 # 填充,这里使用填充1,输出特征图大小不变。
)
# 前向传播
x = torch.randn(64, 3, 256, 256) # 输入特征图,大小为[batch_size, 通道数, 高, 宽]
conv1_output = conv1(x) # conv1_output:卷积层输出,大小为[batch_size, 卷积核个数, 输出高, 输出宽]
Tensorflow 代码示例:
# 定义卷积层
conv1 = tf.keras.layers.Conv2D(
filters=32, # 卷积核个数, 即输出特征图的维度。如设置为32,得到32个特征图。
kernel_size=3, # 卷积核尺寸, 常用的是2x2, 3x3。卷积核越大,可以检测到更大范围内的特征,提取更高级的特征。
strides=1, # 步长, 默认为1,表示沿着输入特征图滑动一次。大于1时,可以使特征图缩小,加快计算。
padding='same', # 填充方法。"same"表示在输入周围填充0,使输出特征图大小不变。"valid"表示无填充。
activation='relu' # 激活函数
)
# 调用卷积层,inputs:输入特征图。
# conv1_output:卷积层输出,即输出特征图。
conv1_output = conv1(inputs)
Pytorch的卷积层与Tensorflow的主要差异在于:
- Pytorch需要手动设置卷积层的权重初始化,而Tensorflow会自动初始化。
- Pytorch的卷积计算是在特征图所有位置并行进行,Tensorflow默认是滑动窗口的方式。
- Pytorch需要手动设置反向传播过程,Tensorflow会自动求取关于各参数的梯度。
除此之外,两者的卷积计算本质是一致的,都是利用卷积核实现特征提取。
Keras 代码示例:
from tensorflow.keras.layers import Conv2D
# 定义卷积层
conv1 = Conv2D(
filters=32, # 卷积核个数
kernel_size=3, # 卷积核尺寸,卷积核的高和宽。
strides=1, # 步长
padding='same', # 填充方式
activation='relu' # 激活函数
)
# 输入特征图,使用Keras的Input层定义。
inputs = tf.keras.layers.Input(shape=(256, 256, 3))
# 卷积计算
conv1_output = conv1(inputs)
# 构建模型,Keras的模型。输入为inputs,输出为conv1_output。
model = tf.keras.models.Model(inputs=inputs, outputs=conv1_output)
Keras的主要优点是简洁高效,可以快速实现卷积神经网络的搭建。与纯TensorFlow相比,只需要关注模型的层次构建,可以避免许多低级实现细节。所以,利用Keras可以更加直观地理解卷积层以及整个卷积神经网络。
池化层
池化层(Pooling Layer)是卷积神经网络中的重要组成层。对输入张量的各个子矩阵进行压缩。假如是2x2的池化,那么就将子矩阵的每2x2个元素变成一个元素,如果是3x3的池化,那么就将子矩阵的每3x3个元素变成一个元素,这样输入矩阵的维度就变小了。
其主要作用是:
- 下采样:通过减少特征图的高和宽,降低特征图的维度,减少计算量和参数量。
- 提取主要特征:通过最大值或平均值运算保留关键特征,抛弃冗余信息。
常用的池化方法有:
- 最大池化(Max Pooling):选择池化窗口内的最大值作为输出。
- 平均池化(Average Pooling):计算池化窗口内所有值的平均值作为输出。
池化层的主要工作原理是:
- 将输入特征图划分成多个窗口。 比如输入的若干矩阵是NN维的,而我们的池化大小是kk的区域,则输出的矩阵是 ( N / K , N / K ) (N/K,N/K) (N/K,N/K) 维度的
- 在每个窗口内计算最大值或平均值。
- 最大值或平均值构成输出特征图。
- 重复该步骤,可以有效减少特征图尺寸,并在一定程度上抑制特征图中的随机噪声,提高模型的泛化能力。
池化层是卷积神经网络中常用的下采样方式,其作用是缩小特征图大小,提高网络的鲁棒性。与卷积层相比,池化层没有学习到的参数,只通过简单的最大值或平均值运算实现特征的压缩与抽取。
池化层一般直接跟在卷积层后面,用于处理卷积层的输出特征图。
Pytorch 代码示例:
import torch
import torch.nn as nn
# 定义池化层
pool1 = nn.MaxPool2d(
kernel_size=2, # 池化窗口大小,常用2x2或3x3。
stride=2 # 步长,默认为kernel_size大小。
)
# 输入特征图,一般为卷积层的输出。大小为[batch_size, 通道数, 高, 宽]。
x = torch.randn(64, 32, 32, 32)
# 池化计算,池化层输出。大小减半。
pool1_output = pool1(x)
Tensorflow 代码示例:
pool1 = tf.keras.layers.MaxPool2D(
pool_size=2, # 池化窗口大小,通常是2x2或3x3。
strides=2, # 步长。默认为pool_size大小。
padding='same' # 填充方式
)
# 池化计算
# conv1_output:卷积层的输出,作为池化层的输入。
# pool1_output:池化层输出,特征图大小减半。
pool1_output = pool1(conv1_output)
Pytorch的池化层与Tensorflow的主要差异在于:
- Pytorch需要手动设置池化层,Tensorflow有现成的Pool层可用。
- Pytorch的池化计算是在特征图的所有位置并行进行,Tensorflow默认是滑动窗口方式。
- Pytorch需要手动设置反向传播,Tensorflow会自动计算关于各参数的梯度。
除此之外,两者的池化计算原理是一致的,都是通过最大值或平均值运算实现特征图的下采样与主要特征的提取。
Keras 代码示例:
from tensorflow.keras.layers import MaxPool2D
# 定义池化层
pool1 = MaxPool2D(
pool_size=2, # 池化窗口大小,池化窗口的高和宽。这里设置为2x2。
strides=2, # 步长
padding='same' # 填充方式
)
# inputs:输入特征图。一般为卷积层的输出。
inputs = tf.keras.layers.Input(shape=(256, 256, 3))
# 池化计算
pool1_output = pool1(inputs)
# 构建模型
model = tf.keras.models.Model(inputs=inputs, outputs=pool1_output)
Keras的池化层与Pytorch和Tensorflow的主要差异在于:
- Keras中Pool层已经内置,无需手动定义。
- Keras会自动设置池化层的参数,无需手动指定初始化方法。
- Keras会自动进行反向传播,无需手动设置。
除此之外,各个框架的池化计算本质都是一样的,都是通过最大值或平均值提取特征图中的主要特征,并实现特征图尺寸的下采样。
全连接层
其作用是:
- 进行特征空间变换: 将上一层的输出特征作为输入,通过线性变换转换为新的特征空间。
- 实现特征的定制组合: 全连接层的权重可以自由地组合上一层不同特征,提取复杂的特征表示。
- 添加非线性映射: 一般在全连接层后面加入激活函数,引入非线性变换,提高模型的拟合能力。
全连接层的工作原理是:
- 将输入特征展平为向量。
- 与权重W相乘,加上偏置b,得到线性变换后的输出。
- 应用激活函数引入非线性,得到最终输出。
- 通过修改神经元个数和激活函数,可以得到不同的特征表达。
- 叠加多个全连接层可以实现对特征的层层抽象。
全连接层是构建神经网络的核心,其参数数量也是网络参数量的绝大部分。
全连接层由权重W和偏置b组成,计算过程为:
output = activation(W * input + b)
其中,W的大小为[上一层神经元数,本层神经元数]
,b的大小为[本层神经元数]
。
Pytorch 代码示例:
import torch
import torch.nn as nn
# 定义全连接层
class FullyConnectedLayer(nn.Module):
def __init__(self, input_size, output_size):
super(FullyConnectedLayer, self).__init__()
# 定义全连接层参数
# 输入维度,上一层神经元个数。
# 输出维度,本层神经元个数。
self.W = nn.Parameter(torch.randn(input_size, output_size))
self.b = nn.Parameter(torch.randn(output_size))
def forward(self, x):
# 前向传播
# - x:输入特征,大小为[batch_size, 输入维度]。
# - W:全连接层权重,大小为[输入维度, 输出维度]。
# - b:全连接层偏置,大小为[输出维度]。
return torch.matmul(x, self.W) + self.b
def activation(self, x):
# ReLU激活函数
return torch.max(torch.zeros_like(x), x)
# 输入特征
x = torch.randn(64, 784)
# 实例化全连接层
fc = FullyConnectedLayer(784, 256)
# 全连接计算
# - x:全连接层输出,大小为[batch_size, 输出维度]。
x = fc.forward(x)
# 添加激活函数
x = fc.activation(x)
# 输出
print(x.shape) # torch.Size([64, 256])
Tensorflow 代码示例:
# 定义全连接层
dense1 = tf.keras.layers.Dense(
units=256, # 输出维度,即全连接层神经元个数。
activation='relu' # 激活函数
)
# 全连接计算,pool1_output:输入特征,一般为池化层输出。
# dense1_output:全连接层输出。
dense1_output = dense1(pool1_output)
# 构建模型
model = tf.keras.models.Model(inputs=inputs, outputs=dense1_output)
Keras 代码示例:
# 在Keras中,全连接层由Dense实现。示例代码如下:
from tensorflow.keras.layers import Dense
# 定义全连接层
dense1 = Dense(256, activation='relu') # 定义全连接层,输出维度256,激活函数ReLU。
# 输入特征
inputs = tf.keras.layers.Input(shape=(1024,))
# 全连接计算
# inputs:输入特征,大小为[batch_size, 上一层神经元数]。
# dense1_output:全连接层输出,大小为[batch_size, 输出维度]。
dense1_output = dense1(inputs)
# 构建模型
model = tf.keras.models.Model(inputs=inputs, outputs=dense1_output)
Keras的全连接层与Pytorch和Tensorflow的主要差异在于:
- Keras无需手动设置全连接层参数初始化和激活函数。
- Keras会自动进行反向传播计算,无需手动设置。
- Keras的Dense层简洁易用,只需指定输出维度和激活函数即可。
除此之外,三种框架的全连接计算本质上是一致的,都是通过线性变换和非线性激活来实现特征的表达与抽象。
批规范化
批规范化(Batch Normalization)是一种在神经网络内对激活值进行标准化的技术。
主要作用是:
- 神经网络内部梯度消失/爆炸问题。通过对激活值标准化,可以使得梯度处于更加稳定的范围内,缓解梯度消失/爆炸问题。
- 加速神经网络的训练。批规范化使得训练过程中各层的输入分布较为稳定,避免了过大或过小的输入导致的训练过程变慢。
- 减少神经网络的过拟合。批规范化具有正则化的效果,可以在一定程度上减轻过拟合。
批规范化的计算公式为:
y = gamma * (x - mean) / sqrt(var + eps) + beta
其中:
- x:输入特征,大小为[batch_size, 特征维度]。
- mean和var:x的均值和方差。
- gamma和beta:可学习的参数,分别初始化为1和0。
- eps:一个很小的常数,用于防止除0错误。
- y:批规范化输出,大小与x相同。
批规范化在训练和测试阶段的计算略有不同:
- 训练阶段:mean和var使用当前batch的均值和方差。
- 测试阶段:mean和var使用训练好的全局均值和方差。
Pytorch 代码示例:
# 这里给出一维特征的示例代码:
import torch
import torch.nn as nn
# 定义批规范化层
bn1 = nn.BatchNorm1d(256) # 定义批规范化层,特征维度256。
# 输入特征,大小为[batch_size, 特征维度]。
x = torch.randn(64, 256)
# 批规范化计算
bn1_output = bn1(x)
print(bn1_output.shape) # torch.Size([64, 256])
批规范化层包含gamma、beta、running_mean和running_var四个参数:
- gamma和beta: 仿射变换参数,初始化默认为1和0。
- running_mean和running_var: 训练过程中全局均值和方差的计算值。
批规范化的计算过程为:
# 训练阶段:
mean = x.mean(dim=0)
var = x.var(dim=0)
x_hat = (x - mean) / torch.sqrt(var + eps)
y = gamma * x_hat + beta # 更新gamma和beta的参数。
# 测试阶段:使用训练阶段学到的全局mean和var。
x_hat = (x - running_mean) / torch.sqrt(running_var + eps)
y = gamma * x_hat + beta # gamma和beta的参数保持不变。
在Pytorch中,批规范化层由nn.BatchNorm1d(对一维特征)和nn.BatchNorm2d(对二维特征)实现。
与Keras相比,Pytorch的批规范化需要手动设置更多细节:
- 需要手动指定BatchNorm层特征维度。Keras会自动推断。
- 需要手动设置batchnorm层在训练和测试阶段使用不同的均值和方差。Keras会自动处理。
- 需要手动指定gamma和beta的初始值。Keras默认初始化为1和0。
- 需要手动设置反向传播过程。Keras会自动计算各参数梯度。
除此之外,两种框架的批规范化计算本质上是一致的,都是对特征进行标准化,使其分布更加稳定,达到缓解梯度消失、加速训练和减轻过拟合的目的。
Tensorflow 代码示例:
# 定义批规范化层
bn1 = tf.keras.layers.BatchNormalization()
# 应用批规范化
bn1_output = bn1(dense1_output)
# 构建模型
model = tf.keras.models.Model(inputs=inputs, outputs=bn1_output)
Keras中的BatchNormalization层已经包括了gamma、beta、mean和var参数,并会在训练过程中自动更新。
Pytorch的批规范化与Tensorflow的主要差异在于:
- Pytorch需要手动设置批规范化层,Tensorflow有现成的BatchNormalization层。
- Pytorch需要手动计算 mean、var和 x_hat,Tensorflow会自动计算。
- Pytorch需要手动更新running_mean和running_var,Tensorflow会自动更新。
- Pytorch需要手动设置反向传播,Tensorflow会自动计算各参数的梯度。
除此之外,两者的批规范化本质和作用是一致的,都是用于激活值的标准化,以缓解梯度消失/爆炸问题,加速训练和减轻过拟合。
整体代码-Pytorch版本
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader
# 定义网络
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 32, 3)
self.conv2 = nn.Conv2d(32, 64, 3)
self.pool = nn.MaxPool2d(2, 2)
self.bn1 = nn.BatchNorm2d(64)
self.fc1 = nn.Linear(64 * 6 * 6, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = F.relu(self.conv1(x))
x = self.pool(x)
x = F.relu(self.conv2(x))
x = self.pool(x)
x = self.bn1(x)
x = x.view(-1, 64 * 6 * 6)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
# 定义损失函数和优化器
net = Net()
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(net.parameters(), lr=0.001)
train_data = dataset() # 自己构建
train_loader = DataLoader(dataset=train_data, batch_size= BATCH_SIZE, shuffle=True, num_workers=4)
# 训练网络
for epoch in range(5):
for batch_idx, (data, target) in enumerate(train_loader):
# 前向传播
output = net(data)
# 计算损失
loss = criterion(output, target)
# 反向传播
optimizer.zero_grad()
loss.backward()
# 更新参数
optimizer.step()