- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊 | 接辅导、项目定制
目录
- 0. 总结
- 1. 数据导入部分
- 2. 模型构建部分
- 3. 训练前的准备
- 4. 定义训练函数
- 5. 定义测试函数
- 6. 训练过程
0. 总结
总结:
-
数据导入部分:数据导入使用了torchvision自带的数据集,获取到数据后需要使用torch.utils.data中的DataLoader()加载数据
-
模型构建部分:有两个部分一个初始化部分(init())列出了网络结构的所有层,比如卷积层池化层等。第二个部分是前向传播部分,定义了数据在各层的处理过程。
-
训练前的准备:在这之前需要定义损失函数,学习率,以及根据学习率定义优化器(例如SGD随机梯度下降),用来在训练中更新参数,最小化损失函数。
-
定义训练函数:函数的传入的参数有四个,分别是设置好的DataLoader(),定义好的模型,损失函数,优化器。函数内部初始化损失准确率为0,接着开始循环,使用DataLoader()获取一个批次的数据,对这个批次的数据带入模型得到预测值,然后使用损失函数计算得到损失值。接下来就是进行反向传播以及使用优化器优化参数,梯度清零放在反向传播之前或者是使用优化器优化之后都是可以的。将 optimizer.zero_grad() 放在了每个批次处理的开始,这是最标准和常见的做法。这样可以确保每次迭代处理一个新批次时,梯度是从零开始累加的。准确率是通过累计预测正确的数量得到的,处理每个批次的数据后都要不断累加正确的个数,最终的准确率是由预测正确的数量除以所有样本得数量得到的。损失值也是类似每次循环都累计损失值,最终的损失值是总的损失值除以训练批次得到的
-
定义测试函数:函数传入的参数相比训练函数少了优化器,只需传入设置好的DataLoader(),定义好的模型,损失函数。此外除了处理批次数据时无需再设置梯度清零、返向传播以及优化器优化参数,其余部分均和训练函数保持一致。
-
训练过程:定义训练次数,有几次就使用整个数据集进行几次训练,初始化四个空list分别存储每次训练及测试的准确率及损失。使用model.train()开启训练模式,调用训练函数得到准确率及损失。使用model.eval()将模型设置为评估模式,调用测试函数得到准确率及损失。接着就是将得到的训练及测试的准确率及损失存储到相应list中并合并打印出来,得到每一次整体训练后的准确率及损失。
-
模型的保存,调取及使用,暂时没有看到这部分,但是训练好的模型肯定是会用到这步的,需要自己添加进去。在PyTorch中,通常使用 torch.save(model.state_dict(), ‘model.pth’) 保存模型的参数,使用 model.load_state_dict(torch.load(‘model.pth’)) 加载参数。
1. 数据导入部分
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import torchvisiondevice = torch.device("cuda" if torch.cuda.is_available() else "cpu")device
device(type='cpu')
print(torch.__version__) # 查看pytorch版本
1.9.0+cpu
# 导入数据
train_ds = torchvision.datasets.MNIST('data', train = True,transform = torchvision.transforms.ToTensor(),download = True
)
test_ds = torchvision.datasets.MNIST('data',train = False,transform = torchvision.transforms.ToTensor(),download = True
)
batch_size = 32# shuffle = True 意味着每次迭代数据集时,数据都会被随机打乱。
# 因此,当您从train_dl中获取一个批次的数据时,每次都可能得到不同的图像
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
)
# 取一个批次查看数据格式
# 数据的shape为:[batch_size,channel,height,weight]
# 其中batch_size为自己设定,channel,height,weight分别对应图片的通道数,高度和宽度
imgs,labels = next(iter(train_dl)) # 由于数据加载器被设置为随机打乱数据(shuffle=True),因此每次调用next函数时,都会从数据集中随机选择一个批次的数据。
imgs.shape
torch.Size([32, 1, 28, 28])
import numpy as np#指定图片大小,图像大小为20宽,5高的绘图(单位为英寸inch)
plt.figure(figsize=(20,5))
for i,img in enumerate(imgs[:20]):# 维度缩减npimg = np.squeeze(img.numpy())plt.subplot(2,10,i+1) # 将整个figure分成2行10列,绘制第i+1个子图plt.imshow(npimg,cmap=plt.cm.binary)plt.axis('off') # 这行代码关闭了当前子图的坐标轴,使得图像没有任何坐标轴标签或刻度。
2. 模型构建部分
# 模型构建
import torch.nn.functional as Fnum_classes = 10 # 图片的类别数class Model(nn.Module):def __init__(self):super().__init__() # super(Model,self).__init__() 的简化写法# 特征提取网络self.conv1 = nn.Conv2d(1,32,kernel_size = 3) # 第一层卷积,卷积核大小为3*3self.pool1 = nn.MaxPool2d(2) # 设置池化层,池化核大小为2*2self.conv2 = nn.Conv2d(32,64,kernel_size = 3) # 第二层卷积,卷积核大小为3*3self.pool2 = nn.MaxPool2d(2)# 分类网络self.fc1 = nn.Linear(1600,64)self.fc2 = nn.Linear(64,num_classes)# 前向传播def forward(self,x):x = self.pool1(F.relu(self.conv1(x)))x = self.pool2(F.relu(self.conv2(x)))x = torch.flatten(x,start_dim=1) # x.view(x.size(0), -1) 展平张量x = F.relu(self.fc1(x))x = self.fc2(x)return x
# !pip install torchinfo -i https://pypi.mirrors.ustc.edu.cn/simple/
# 查看模型结构
model = Model()
model
Model((conv1): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1))(pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)(conv2): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1))(pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)(fc1): Linear(in_features=1600, out_features=64, bias=True)(fc2): Linear(in_features=64, out_features=10, bias=True)
)
# 打印模型
from torchinfo import summarymodel = Model().to(device) # 在指定的设备(device,可能是CPU或CUDA/GPU)上实例化原始Model。summary(model) # 使用torchinfo库中的summary函数来打印模型的摘要。
=================================================================
Layer (type:depth-idx) Param #
=================================================================
Model --
├─Conv2d: 1-1 320
├─MaxPool2d: 1-2 --
├─Conv2d: 1-3 18,496
├─MaxPool2d: 1-4 --
├─Linear: 1-5 102,464
├─Linear: 1-6 650
=================================================================
Total params: 121,930
Trainable params: 121,930
Non-trainable params: 0
=================================================================
3. 训练前的准备
# 训练模型# 设置超参数
loss_fn = nn.CrossEntropyLoss() # 创建损失函数
learn_rate = 1e-2 # 学习率
opt = torch.optim.SGD(model.parameters(),lr=learn_rate)
4. 定义训练函数
# 训练循环
def train(dataloader,model,loss_fn,optimizer):size = len(dataloader.dataset) # 训练集的大小,一共60000张图片num_batches = len(dataloader) # 批次数目,1875(60000/32)train_loss,train_acc = 0,0 # 初始化训练损失和正确率for X,y in dataloader: # 获取图片及其标签X,y = X.to(device),y.to(device)# 计算预测误差pred = model(X) # 网络输出loss = loss_fn(pred,y) # 计算网络输出值和真实值之间的差距,targets为真实值,计算二者差值即为损失# 反向传播optimizer.zero_grad() # grad属性归零loss.backward() # 反向传播optimizer.step() # 每一步自动更新# 记录acc与losstrain_acc += (pred.argmax(1) == y).type(torch.float).sum().item()train_loss += loss.item()train_acc /= sizetrain_loss /= num_batchesreturn train_acc,train_loss
5. 定义测试函数
# 编写测试函数
def test(dataloader,model,loss_fn):size = len(dataloader.dataset) # 测试集的大小,一共10000张图片num_batches = len(dataloader) # 批次数目,313(10000/32=312.5)test_loss,test_acc = 0,0# 当不进行训练时,停止梯度更新,节省计算内存消耗with torch.no_grad():for imgs,target in dataloader:imgs,target = imgs.to(device),target.to(device)# 计算losstarget_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 /= sizetest_loss /= num_batchesreturn test_acc,test_loss
6. 训练过程
# 训练
epochs = 5
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')
C:\Users\chengyuanting\.conda\envs\pytorch_cpu\lib\site-packages\torch\nn\functional.py:718: UserWarning: Named tensors and all their associated APIs are an experimental feature and subject to change. Please do not use them for anything important until they are released as stable. (Triggered internally at ..\c10/core/TensorImpl.h:1156.)return torch.max_pool2d(input, kernel_size, stride, padding, dilation, ceil_mode)Epoch: 1,Train_acc:77.5%,Train_loss:0.753,Test_acc:0.2%,Test_loss:0.000
Epoch: 2,Train_acc:94.3%,Train_loss:0.190,Test_acc:0.1%,Test_loss:0.000
Epoch: 3,Train_acc:96.2%,Train_loss:0.125,Test_acc:0.2%,Test_loss:0.000
Epoch: 4,Train_acc:96.9%,Train_loss:0.098,Test_acc:0.2%,Test_loss:0.000
Epoch: 5,Train_acc:97.5%,Train_loss:0.081,Test_acc:0.2%,Test_loss:0.000
Done
# 结果可视化
import matplotlib.pyplot as plt
# 隐藏警告
import warnings
warnings.filterwarnings("ignore") # 忽略警告信息
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['figure.dpi'] = 100epochs_range = range(epochs)plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)plt.plot(epochs_range, train_acc, label='Training Accuracy')
plt.plot(epochs_range, test_acc, label='Test Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')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()