目录
摘要
本篇文章继续学习尚硅谷深度学习教程,学习内容是卷积神经网络中的池化层和深度卷积网络相关知识,以及实际案例代码学习
1.池化层
池化层缩小长、宽方向上的空间来进行降 维,能够缩减模型的大小并提高计算速度。例如,对数据进行步幅为2的2×2的Max池化:


除了Max池化(计算窗口内的最大值),还有Average池化(平均池化,计算窗口内的平均值)等。一般会将池化的大小窗口和步幅设置为相同的值,比如2×2的窗口大小,步幅会设置为2。池化同样也可以设置填充。
和卷积层不同,池化层没有要学习的参数,并且池化运算按通道独立进行,经过池化运算后数据的通道数不会发生变化。

池化的另一个特点是对微小偏差具有鲁棒性,数据发生微小偏差时,池化可能会返回相同的结果。例如,数据在宽度方向上偏离1个元素,返回的结果不变。

API使用:
python
# 最大池化
torch.nn.MaxPool2d(kernel_size, stride, padding)
# 平均池化
torch.nn.AvgPool2d(kernel_size, stride, padding)
import torch
import matplotlib.pyplot as plt
# 读取图片
img = plt.imread("data/duck.jpg")
print("图片数据形状:", img.shape)
# 将图片数据转换为张量并改变形状
input = torch.tensor(img).permute(2, 0, 1).float()
print("输入特征图形状:", input.shape)
# 初始化卷积核
conv = torch.nn.Conv2d(in_channels=3, out_channels=3, kernel_size=9, stride=3, padding=0, bias=False)
# 对输入特征图进行卷积操作
output1 = conv(input)
print("卷积后输出特征图形状:", output1.shape)
# 初始化池化层
pool = torch.nn.MaxPool2d(kernel_size=6, stride=6, padding=1)
# 进行池化操作
output2 = pool(output1)
print("池化后输出特征图形状:", output2.shape)
# 将输出特征图转换为图片
output1 = torch.clamp(output1.int(), 0, 255) # 限制输出在0到255之间
output1 = output1.permute(1, 2, 0).detach().numpy()
output2 = torch.clamp(output2.int(), 0, 255) # 限制输出在0到255之间
output2 = output2.permute(1, 2, 0).detach().numpy()
fig, ax = plt.subplots(1, 3, figsize=(15, 5))
ax[0].imshow(img)
ax[1].imshow(output1)
ax[2].imshow(output2)
plt.axis("off")
plt.show()
2.深度卷积神经网络
将神经网络的层数加深,可以更有效地提取层次信息,还可以减少参数数量,从而让学习更加高效。
基于CNN构建深度神经网络,可以提取出更多的图片信息,大幅提高图像识别精度。在人工智能的发展历程中,正是深度卷积神经网络,掀起了最近一轮深度学习的热潮。
AlexNet
2012年由Alex Krizhevsky、Ilya Sutskever与 Geoffrey Hinton合作提出,是一个基于CNN构建的神经网络模型,主要架构包含8层(5个卷积层+3个全连接层),激活函数使用 ReLU,最后经全连接层输出结果,并且使用了Dropout。

AlexNet在当年的ImageNet图像分类挑战赛中大幅刷新性能纪录,引发广泛关注;这被认为是深度学习兴起的标志。
VGG
2014年由牛津大学 Visual Geometry Group(视觉几何组)提出。VGG网络由多个卷积-池化层堆叠构成,将有权重的层(卷积层或全连接层)叠加至 16 或 19 层,也被称为VGG-16和VGG-19。

VGG结构简单,应用性强,因此得到了大量技术人员的青睐。
GoogleNet
2014年由Google团队提出的深度卷积神经网络架构。

整体上看,GoogleNet有更加复杂的网络结构,不过它的底层依然和CNN相同。它的特点是,引入了"Inception结构",使得网络不仅纵向上有深度,在横向上也有深度。

横向上使用多个大小不同的滤波器、最后再合并;有利于 减少参数数量、解决梯度消失问题。
- ResNet
2015年由微软团队(何恺明等人)提出,比之前的网络具有更深的结构。
为了解决深度网络的梯度消失问题,ResNet以 VGG 为基础,引入了"快捷结构"。这样一来,网络学习的目标就由原始的输出变为了
,这被称为"残差学习";引入的这个恒等映射被称为"残差连接"(或者"跳跃连接"),这种网络结构也被称为"残差网络"(Residual Network,ResNet)。

ResNet有效地解决了深度网络中的梯度消失和梯度爆炸问题,在加深层的同时,提高了网络性能。
3.案例:服装分类
使用Fashion MNIST数据集:https://www.kaggle.com/datasets/zalando-research/fashionmnist。
数据集中每个样本都是28×28的灰度图像,与来自10个类别的标签相关联。标签对应如下:
- 0:T恤/上衣
- 1:裤子
- 2:套头衫
- 3:连衣裙
- 4:外套
- 5:凉鞋
- 6:衬衫
- 7:运动鞋
- 8:包
- 9:靴子
数据在csv文件中,每行数据为1个样本,第1列为标签,2到785列为784个像素。我们需要将其转换为28×28的形状:
python
import torch
import pandas as pd
import torch.nn as nn
import matplotlib.pyplot as plt
from torch.utils.data import TensorDataset, DataLoader
# 读取数据
fashion_mnist_train = pd.read_csv("data/fashion-mnist_train.csv")
fashion_mnist_test = pd.read_csv("data/fashion-mnist_test.csv")
# 将数据转换为张量,原数据形状为n×1×784,转换为n×1×28×28的张量
X_train = torch.tensor(fashion_mnist_train.iloc[:, 1:].values, dtype=torch.float32).reshape(-1, 1, 28, 28)
y_train = torch.tensor(fashion_mnist_train.iloc[:, 0].values, dtype=torch.int64)
X_test = torch.tensor(fashion_mnist_test.iloc[:, 1:].values, dtype=torch.float32).reshape(-1, 1, 28, 28)
y_test = torch.tensor(fashion_mnist_test.iloc[:, 0].values, dtype=torch.int64)
plt.imshow(X_train[12345, 0, :, :], cmap="gray")
plt.show()
# 构建数据集
train_dataset = TensorDataset(X_train, y_train)
test_dataset = TensorDataset(X_test, y_test)
搭建如下结构的模型:

python
# 搭建模型
model = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5, padding=2),
nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5),
nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Flatten(), # 拉平
nn.Linear(400, 120),
nn.Sigmoid(),
nn.Linear(120, 84),
nn.Sigmoid(),
nn.Linear(84, 10),
)
#给模型一个输入,测试各层输出值形状是否符合预期:
# 查看各层输出数据的形状
X = torch.rand(size=(1, 1, 28, 28), dtype=torch.float32)
for layer in model:
X = layer(X)
print(f"{layer.__class__.__name__:<12}output shape: {X.shape}")
先初始化线性层和卷积层的权重参数。使用交叉熵损失函数和SGD优化方法。每个epoch中在训练集上训练模型,并在验证集上验证模型的准确率。
python
# 模型训练
def train(model, train_dataset, test_dataset, lr, epoch_num, batch_size, device):
def init_weights(layer):
# 对线性层和卷积层使用Xavier均匀分布初始化参数
if type(layer) == nn.Linear or type(layer) == nn.Conv2d:
nn.init.xavier_uniform_(layer.weight)
model.apply(init_weights) # 初始化参数
model.to(device) # 将模型加载到设备
loss = nn.CrossEntropyLoss() # 损失函数
optimizer = torch.optim.SGD(model.parameters(), lr=lr) # 优化器
for epoch in range(epoch_num):
# 训练过程
model.train() # 将模型设置为训练模式
train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
loss_accumulate = 0
train_correct_accumulate = 0
for batch_count, (X, y) in enumerate(train_loader):
# 前向传播
X, y = X.to(device), y.to(device)
output = model(X)
# 反向传播
loss_value = loss(output, y)
optimizer.zero_grad()
loss_value.backward()
optimizer.step()
# 累加损失
loss_accumulate += loss_value.item()
# 累加正确输出的数量
_, pred = output.max(1)
train_correct_accumulate += pred.eq(y).sum()
# 打印进度条
print(f"\repoch:{epoch:0>2}[{'='*(int((batch_count+1) / len(train_loader) * 50)):<50}]", end="")
this_loss = loss_accumulate / len(train_loader) # 计算平均损失
this_train_correct = train_correct_accumulate / len(train_dataset) # 计算训练准确率
# 验证过程
model.eval() # 将模型设置为评估模式
test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=True)
test_correct_accumulate = 0
with torch.no_grad(): # 关闭梯度计算
for X, y in test_loader:
# 前向传播
X, y = X.to(device), y.to(device)
output = model(X)
# 累加正确输出的数量
_, pred = output.max(1)
test_correct_accumulate += pred.eq(y).sum()
this_test_correct = test_correct_accumulate / len(test_dataset) # 计算验证准确率
# 打印损失,训练准确率,验证准确率
print(f" loss:{this_loss:.6f}, train_acc:{this_train_correct:.6f}, test_acc:{this_test_correct:.6f}")
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 如果cude可用则使用cuda,否则使用cpu
train(model, train_dataset, test_dataset, lr=0.9, epoch_num=20, batch_size=256, device=device)