1、前期准备
1、导入GPU
作用:导入必要的库和检查是否支持cuda
import numpy as np
import torch
import torchvision # 视觉库
import torch.nn as nn # 神经网络库
import matplotlib.pylab as plt
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device)
print(torch.__version__)
print(torchvision.__version__)
输出结果如下:
cuda
2.4.0
0.19.0
2、导入数据
train_ds = torchvision.datasets.CIFAR10('data', train=True, transform=torchvision.transforms.ToTensor(), download=True)
test_ds = torchvision.datasets.CIFAR10('data', train=False, transform=torchvision.transforms.ToTensor(), download=True)
-
函数原型:
-
torchvision.datasets.CIFAR10(root, train=True, transform=None, target_transform=None, download=False)
root
:数据地址,如果从网上下载数据,则自动会创建文件目录data
并且数据存入train
:训练集还是测试集,训练集为True,训练集为Falsetransform
:数据类型taget_transform
:接受目标并对其进行转换的函数/转换download
:是否从网上下载数据(pytorch有个好处就是可以自动从网上下载数据)
# 动态加载数据
batch_size = 32 # 每一批加载32个数据,可以自己根据数据定
train_dl = torch.utils.data.DataLoader(train_ds, batch_size=batch_size, shuffle=True)
test_dl = torch.utils.data.DataLoader(test_ds, batch_size=batch_size)
- 注意:每一批加载的数据是随机划分的
- DataLoader是pytorch的动态数据加载器,支持并行化,可以加快存储数据
查看自己上面划分数据的信息:
# 取一个批次查看数据
# 数据得shape为:[batch_size, channel, heigh, weight]
# batch_size 是自己设定得,channel,height,weight分别是图片得通道数,高度,宽度
imgs, labels = next(iter(train_dl))
imgs.shape
数据信息输出如下:
torch.Size([32, 3, 32, 32])
3、数据可视化
展示20张图片:
# 输出一部分图片,可视化
plt.figure(figsize=(20,5))
for i, imgs in enumerate(imgs[: 20]):
# 进行轴变换
npimg = imgs.numpy().transpose((1, 2, 0))
# 拆分子图
plt.subplot(2, 10, i + 1)
plt.imshow(npimg, cmap=plt.cm.binary)
plt.axis('off')
2、构建CNN神经网络
CNN网络一般由三部分构成:
- 卷积层:提取图片特征
- 池化层:用于数据降维
- 全连接层:特征提取,用来输出想要的结果,即作用是降维,可以将数据降到自己想要的维度
1、卷积层原理介绍与计算
# 函数原型
torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)
-
in_channels:输入通道数
-
out_channels:输出通道数
-
kernel_size:卷积层大小
-
stride:卷积步伐大小
-
padding:添加到输入的所有四个边的填充。默认值:0
-
dilation :扩张操作,控制kernel点(卷积核点)的间距,默认值:1。
-
groups:将输入通道分组成多个子组,每个子组使用一组卷积核来处理。默认值为 1,表示不进行分组卷积。
-
padding_mode:‘zeros’, ‘reflect’, ‘replicate’或’circular’. 默认:‘zeros’
卷积维度的计算
卷积输出维度计算公式如下:
$o=\lfloor\frac{(w-k+2p)}s+1\rfloor $
- 输入图片矩阵大小:w * w
- 卷积核大小:k * k
- 步长:s
- 填充大小:p
本文模型构建的卷积计算在下面模型构建流程中
2、池化层原理介绍与计算
函数原型
torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
- kernel_size:最大的窗口大小
- stride:窗口的步幅,默认值为
kernel_size
- padding:填充值,默认为0
- dilation:控制窗口中元素步幅的参数,默认为1
池化层的计算
- I n p u t : ( N , C , H i n , W i n ) o r ( C , H i n , W i n ) O u t p u t : ( N , C , H o u t , W o u t ) o r ( C , H o u t , W o u t ) \begin{aligned}&\mathrm{~Input:~(N,~C,~H_{in},~W_{in})~or~(C,~H_{in},~W_{in})}\\&\mathrm{~Output:~(N,~C,~H_{out},~W_{out})~or~(C,~H_{out},~W_{out})}\end{aligned} Input: (N, C, Hin, Win) or (C, Hin, Win) Output: (N, C, Hout, Wout) or (C, Hout, Wout)
H o u t = ⌊ H i n + 2 ∗ p a d d i n g [ 0 ] − d i l a t i o n [ 0 ] × ( k e r n e l s i z e [ 0 ] − 1 ) − 1 s t r i d e [ 0 ] + 1 ⌋ W o u t = ⌊ W i n + 2 ∗ p a d d i n g [ 1 ] − d i l a t i o n [ 1 ] × ( k e r n e l s i z e [ 1 ] − 1 ) − 1 s t r i d e [ 1 ] + 1 ⌋ \begin{gathered} H_{out} =\left\lfloor\frac{H_{\mathrm{in~}}+2*\mathrm{~padding~}[0]-\mathrm{~dilation~}[0]\times(\mathrm{~kernel_size~}[0]-1)-1}{\mathrm{stride}[0]}+1\right\rfloor \\ W_{\mathrm{out}} =\left\lfloor\frac{W_{\mathrm{in~}}+2*\mathrm{~padding~}[1]-\mathrm{~dilation~}[1]\times(\mathrm{~kernel_size~}[1]-1)-1}{\mathrm{stride~}[1]}+1\right\rfloor \end{gathered} Hout=⌊stride[0]Hin +2∗ padding [0]− dilation [0]×( kernelsize [0]−1)−1+1⌋Wout=⌊stride [1]Win +2∗ padding [1]− dilation [1]×( kernelsize [1]−1)−1+1⌋
案例解释在下面模型构建流程中
3、模型构建流程
-
本文构建的CNN神经网络,有三层卷积层和三层池化层,分类类别为10类。
-
Model的构建函数,作用是分别构建卷积层、池化层和全连接层
-
Model中forward()函数,作用是构建CNN神经网络,
-
卷积层的计算:以卷积层一nn.Conv2d(3, 64, kernel_size=3)为例
- 参数解释:输入通道为3,输出通道为64,卷积核大小为3
- 输入图片通道和大小(3, 32, 32),通过卷积层一得到**(64, 30, 30)**,30计算过程如下:
- [(32 - 3 + 2 * 0) / 1 + 1] == 30
-
池化层的计算:一池化层一nn.MaxPool2d(kernel_size=2)为例
- 输入参数:池化核大小为2
- 通过卷积层一得到数据(64, 30, 30),在通过池化层一得到结果(64, 15, 15),结合**(2、池化层的计算公式可知)**, 15就算过程如下(以Hout为例):
- Hin:30, padding[0]:0,dilation[0]:1,kernelize:2,stride:1,带入公式有:[(30 + 2 * 0 - 1 * (2 - 1) - 1) / 1 + 1] == 30
-
构建模型代码如下
import torch.nn.functional as F
num_classes = 10 # 一共有10种类别
class Model(nn.Module):
def __init__(self):
super().__init__()
# 特征提起网络
self.conv1 = nn.Conv2d(3, 64, kernel_size=3) # 第一层卷积层,卷积核大小 3 * 3
self.pool1 = nn.MaxPool2d(kernel_size=2) # 第一层池化层,池化核大小 2 * 2
self.conv2 = nn.Conv2d(64, 64, kernel_size=3) # 第二层卷积层,卷积核大小为 3 * 3
self.pool2 = nn.MaxPool2d(kernel_size=2) # 第二层池化层,池化核大小 2 * 2
self.conv3 = nn.Conv2d(64, 128, kernel_size=3) # 第三层卷积层,卷积大小 3 * 3
self.pool3 = nn.MaxPool2d(kernel_size=2) # 第三层池化层,卷积大小 2 * 2
# 分类网络
self.fc1 = nn.Linear(512, 256) # 512维度降到256维度
self.fc2 = nn.Linear(256, num_classes) # 256维度降到 10 维度,对应分成10类
# 前向传播,即构建cnn网络:卷积、池化…… 全连接
def forward(self, x):
x = self.pool1(F.relu(self.conv1(x)))
x = self.pool2(F.relu(self.conv2(x)))
x = self.pool3(F.relu(self.conv3(x)))
x = torch.flatten(x, start_dim=1) # 将 x 多维数据 ** 展开到一维 **
x = F.relu(self.fc1(x))
x = self.fc2(x)
return x
4、将模型导入GPU中
# 打印并且加载模型
from torchinfo import summary
# 将模型转移到GPU种
model = Model().to(device)
summary(model)
构建的模型参数如下:
=================================================================
Layer (type:depth-idx) Param #
=================================================================
Model --
├─Conv2d: 1-1 1,792
├─MaxPool2d: 1-2 --
├─Conv2d: 1-3 36,928
├─MaxPool2d: 1-4 --
├─Conv2d: 1-5 73,856
├─MaxPool2d: 1-6 --
├─Linear: 1-7 131,328
├─Linear: 1-8 2,570
=================================================================
Total params: 246,474
Trainable params: 246,474
Non-trainable params: 0
=================================================================
3、模型训练
1、设置超参数
loss_fn = nn.CrossEntropyLoss() # 插件损失函数
learn_rate = 1e-2 # 设置学习率
opt = torch.optim.SGD(model.parameters(), lr=learn_rate) # 初始化梯度下降
-
loss_fn = nn.CrossEntropyLoss()
这一行代码创建了一个
CrossEntropyLoss
对象,它是PyTorch中用于多分类任务的标准损失函数。CrossEntropyLoss
结合了log_softmax
和NLLLoss
(负对数似然损失)的功能,它可以接收未归一化的预测输出(通常来自模型的输出层)和真实的标签,然后计算损失。在多分类问题中,目标是使模型预测的类别的概率最大,而CrossEntropyLoss
正好可以衡量模型预测概率分布与真实标签之间的差距。 -
learn_rate = 1e-2
这里设置了学习率(
learn_rate
)为1e-2
,即0.01。**学习率是梯度下降算法中的一个重要超参数,**它决定了权重更新的步长。较高的学习率可以让模型快速收敛,但可能会导致训练不稳定;较低的学习率可以使训练过程更加稳定,但可能需要更多的迭代次数才能收敛。 -
opt = torch.optim.SGD(model.parameters(), lr = learn_rate)
这一行代码初始化了一个随机梯度下降(Stochastic Gradient Descent, SGD)优化器,它将用于更新模型的参数以最小化损失函数。
model.parameters()
返回模型的所有可训练参数,这些参数将在每次迭代中根据计算出的梯度进行更新。lr
参数指定了学习率,这里是之前定义的learn_rate
。
2、编写训练函数
训练函数通常有一个循环,里面包含有(与深度学习基础一案例一样):
- 前向传播:将输入数据通过模型得到预测输出
- 计算损失:使用
loss_fn
来计算预测值和实际值的损失 - 反向传播:使用
loss.backward()
来自动计算损失关于模型参数的梯度 - 更新权重:使用
opt.step()
来更具计算出的梯度和学习率更新模型参数 - 清理梯度:在每一次迭代前开始,调用
opt.zero_grad()
来清理梯度缓存,防止梯度积累
训练函数参数解析
- dataloader,传入的是动态加载的数据
- model,前面构建好的模型(导入了GPU)
- loss_fn,损失函数
- optimizer,初始化梯度下降
def train(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset) # 训练集的大小,一共60000张图片
num_batches = len(dataloader) # 批次数目,60000/32
train_acc, train_loss = 0, 0 # 初始化准确率和损失
for X, y in dataloader: # 获取数据和标签
X, y = X.to(device), y.to(device) # 模型已经导入GPU中,
# 计算acc和loss
pred = model(X) # 网络输出
loss = loss_fn(pred, y) # 计算网络输出和真实值之间的差距
# 反向传播
optimizer.zero_grad() # grad属性归零,即梯度归零
loss.backward() # 反向传播
optimizer.step() # 更新权重
# 记录 acc 和 loss
train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()
train_loss += loss.item()
train_acc /= size
train_loss /= num_batches
return train_acc, train_loss
训练函数API介绍
- optimizer.zero_grad():会遍历模型所有参数,降反向传播的梯度设置为0(数据经过神经网络的一个神经元中,如果没有达到要求回反向传播继续计算)
- loss.backward():反向传播中自动跟新梯度,必须在optimizer.step()前
- optimizer.step():通过梯度下降法更新模型参数的值
3、编写测试函数
测试函数和训练函数思路大概一样
def test(dataloader, model, loss_fn):
size = len(dataloader.dataset) # 测试集的大小,一共 10000张图片
num_batches = len(dataloader) # 批次数目,10000/32
test_loss, test_acc = 0, 0
# 当不进行训练的时候,停止梯度更新,节省计算内存消耗
with torch.no_grad():
for imgs, target in dataloader:
imgs, target = imgs.to(device), target.to(device)
# 计算loss
target_pred = model(imgs)
loss = loss_fn(target_pred, target)
test_loss += loss.item()
test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item()
test_acc /= size
test_loss /= num_classes
return test_acc, test_loss
4、正式训练
epochs = 10
train_loss = []
train_acc = []
test_loss = []
test_acc = []
for epoch in range(epochs):
model.train()
epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, opt)
model.eval()
epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)
train_acc.append(epoch_train_acc)
train_loss.append(epoch_train_loss)
test_acc.append(epoch_test_acc)
test_loss.append(epoch_test_loss)
template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}')
print(template.format(epoch+1, epoch_train_acc*100, epoch_train_loss, epoch_test_acc*100, epoch_test_loss))
print('Done')
Epoch: 1, Train_acc:14.4%, Train_loss:2.273, Test_acc:22.1%,Test_loss:66.199
Epoch: 2, Train_acc:26.6%, Train_loss:1.982, Test_acc:27.9%,Test_loss:62.358
Epoch: 3, Train_acc:36.3%, Train_loss:1.747, Test_acc:39.5%,Test_loss:51.392
Epoch: 4, Train_acc:42.3%, Train_loss:1.585, Test_acc:40.7%,Test_loss:50.576
Epoch: 5, Train_acc:46.2%, Train_loss:1.481, Test_acc:46.0%,Test_loss:46.421
Epoch: 6, Train_acc:50.2%, Train_loss:1.384, Test_acc:52.3%,Test_loss:41.735
Epoch: 7, Train_acc:53.8%, Train_loss:1.303, Test_acc:54.8%,Test_loss:39.633
Epoch: 8, Train_acc:56.3%, Train_loss:1.235, Test_acc:55.8%,Test_loss:39.389
Epoch: 9, Train_acc:58.6%, Train_loss:1.174, Test_acc:59.2%,Test_loss:36.206
Epoch:10, Train_acc:60.8%, Train_loss:1.120, Test_acc:61.0%,Test_loss:34.810
Done
5、结果可视化
import matplotlib.pyplot as plt
#隐藏警告
import warnings
warnings.filterwarnings("ignore") # 忽略警告信息
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
plt.rcParams['figure.dpi'] = 100 # 分辨率
epochs_range = range(epochs)
plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, train_acc, label='Training Accurary')
plt.plot(epochs_range, test_acc, label='Test Accurary')
plt.legend(loc='lower right')
plt.title('Training and Validation Accurary')
plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss, label='Training Loss')
plt.plot(epochs_range, test_loss, label='Test Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
- 通过上面分析,在准确度上,训练姐和测试集都达到了百分之60多,但是在损失函数上,训练集的loss函数非常特别,理解不了
6、总结
1、CNN模型总结
- CNN模型主要由卷积层、池化层、全连接层组成,卷积层用于特征提取,池化层用于降维,全连接层用于将维度展开计算与分类,进行特征提取原因:每一图片有很多地方是相同的,通过特征提取可以降低计算难度。
- 一张图片在pytorch可以存储为:(颜色通道数量,图片长维度,图宽度维度)
- 熟悉了卷积层如何进行、池化层如何进行的数学原理
2、API总结
optimizer.zero_grad()
:在每一次反向传播的时候梯度设置为0loss.backward()
:反向传播中自动跟新梯度,必须在optimizer.step()前optimizer.step()
:通过梯度下降法更新模型参数的值
3、待解决
卷积神经网络参数不太明白:
-
padding:添加到输入的所有四个边的填充。默认值:0
-
dilation :扩张操作,控制kernel点(卷积核点)的间距,默认值:1。
-
groups:将输入通道分组成多个子组,每个子组使用一组卷积核来处理。默认值为 1,表示不进行分组卷积。
-
padding_mode:‘zeros’, ‘reflect’, ‘replicate’或’circular’. 默认:‘zeros’
-
训练集的loss函数非常特别,理解不了