前言
一、导入相关库,设置GPU
二、导入MNIST手写数字数据集
三、部分数据可视化
四、创建简易的CNN网络模型
五、编写训练函数
六、编写测试函数
七、开始训练
八、结果可视化
总结
前言
本文为🔗365天深度学习训练营 中的学习记录博客
原作者:K同学啊
一、导入相关库,设置GPU
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import torchvision
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
设置用于计算的设备。如果电脑中有可用的GPU,将使用第一个GPU设备进行运算,没有则使用CPU。
二、导入MNIST手写数字数据集
代码如下:
train_dataset = torchvision.datasets.MNIST(root='./data', train=True,
transform=torchvision.transforms.ToTensor(), download=True)
test_dataset = torchvision.datasets.MNIST(root='./data', train=False,
batch_size = 32
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size)
通过torchvsion.datasets.MNIST函数获取MNIST数据集,root表示数据集要存储的路径,train=True表示选取训练集,train=False表示选取测试集,transform=torchvision.transforms.ToTensor()是将数据转化为tensor类型。download=True代表从网上下载数据集并存储到root目录下。
使用torch.utils.data.DataLoader函数来分别加载训练集和测试集,batch_size为32,意为每批选取32张照片。shuffle=True表示随机打乱顺序。其它常用参数有num_workers : 用于数据加载的进程数。默认值为0。
imgs, labels = next(iter(train_loader))
imgs.shape
选择第一个批次查看数据,数据的形状为[batch_size, channel, height, width]。
iter(train_loader)将训练数据加载器转化为一个数据迭代器,然后使用next()函数获取数据迭代器中的下一个元素,由于是第一次使用next()函数,所以会从训练数据集中加载第一个批次数据。
可以看到第一个批次的图片数据形状为[batch_size=32, channel=1, height=28, width=28]。图片是黑白图片,所有channel=1,其像素为28*28。
三、部分数据可视化
import numpy as np
# 指定图片大小, 图像大小为宽20高5的绘图(英寸inch)
plt.figure(figsize=(20, 5))
for i, imgs in enumerate(imgs[:20]): # 显示前20张图片
img = np.squeeze(imgs.numpy()) # 维度缩减=>(32, 28, 28)
# 将整个figure分成2行10列,绘制第i+1个子图
plt.subplot(2, 10, i+1)
plt.imshow(img, cmap=plt.cm.binary) # 显示灰度图
plt.axis('off') # 不显示坐标轴
plt.show()
首先设置画布大小为宽度20,高度5,单位为inch。然后绘制第一个批次的前20张图片。先用np.squeeze(imgs.numpy())函数将图片的第"1"个维度也就是channel维度删掉,所以shape由[32, 1, 28, 28]变为[32, 28, 28],然后分成2行10列进行绘制,cmap=plt.cm.binary显示灰度图,plt.axis('off')表示不显示坐标轴。
四、创建简易的CNN网络模型
import torch.nn.functional as F
num_classes = 10 # 图片的类数
class model(nn.Module):
def __init__(self):
super(model, self).__init__()
# 特征提取网络
self.conv1 = nn.Conv2d(1, 32, kernel_size=3)
self.pool1 = nn.MaxPool2d(2)
self.conv2 = nn.Conv2d(32, 64, kernel_size=3)
self.pool2 = nn.MaxPool2d(2)
# 分类网络
self.fc1 = nn.Linear(5*5*64, 64)
self.fc2 = nn.Linear(64, num_classes)
def forward(self, x): # 前向传播
x = self.conv1(x)
x = F.relu(x)
x = self.pool1(x)
x = self.conv2(x)
x = F.relu(x)
x = self.pool2(x)
x = torch.flatten(x, start_dim=1)
x = self.fc1(x)
x = F.relu(x)
x = self.fc2(x)
return x
网络结构图:
首先进行特征提取,经过一个卷积核为3x3的卷积层,此时输出通道为32,图片尺寸为26x26,然后经过relu激活函数,再经过一个池化核为2x2的最大池化层,图片尺寸变为原来的一半,即13x13。通过一个卷积核为3x3的卷积层,通道数变成原来的2倍,即64,图片尺寸变为11x11,随后通过relu激活函数,再进入一个池化核为2x2的最大池化层,尺寸变为5x5。
然后进行分类,通过torch.flatten函数将数据展平,通过第一个全连接层,input_features=5*5*64,out_features=64,使用relu激活函数,最后经过第二个全连接层,将64降维到num_classes=10,即图片的类别数。
# 加载并打印模型
from torchinfo import summary
model = model().to(device)
summary(model)
打印模型查看
五、编写训练函数
# 设置超参数
loss_fn = nn.CrossEntropyLoss() # 创建损失函数
learning_rate = 1e-2
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
设置训练所需的一些超参数,选择交叉熵损失函数为损失函数,学习率设置为0.01,优化器选用的SGD即随机梯度下降,也可以使用Adam等其它优化器。
### 编写训练函数 ###
def train(train_loader, model, loss_fn, optimizer):
size = len(train_loader.dataset) # 训练集的大小, 一共60000张图片
num_batches = len(train_loader) # 批次数目,1875(60000/32)
train_loss, train_acc = 0, 0 # 初始化训练损失和准确率
for x, y in train_loader:
x, y = x.to(device), y.to(device)
pred = model(x) # 网络输出
loss = loss_fn(pred, y) # 计算网络输出和真实值之间的差距, targets为真实值, 计算二者差值即为损失
optimizer.zero_grad() # 梯度清零
loss.backward() # 反向传播
optimizer.step() # 更新参数
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
len(train_loader.dataset)为训练集样本的数量,一共60000张图片。len(train_loader)是批次的数目,一共是60000张照片,batch_size=32,所有批次数目是60000/32=1875。通过for循环遍历每个批次,先将数据和标签拷贝到GPU上运算,model(x)得到网络的输出,然后通过输出和真实标签值计算得到损失函数值,将梯度清零,然后反向传播,不断地更新权重参数。
(pred.argmax(1) == y).sum().item()表示计算预测正确的样本数量,.item()将结果转换为python类型标量值,便于在python中使用。
最后函数返回训练准确率和训练损失。
六、编写测试函数
测试中不进行梯度下降来更新网络权重参数,因此不需要传入优化器。
def test(test_loader, model, loss_fn):
size = len(test_loader.dataset) # 测试集的大小, 一共10000张照片
num_batches = len(test_loader) # 批次数目,313(10000/32=312.5向上取整)
test_loss, test_acc = 0, 0
# 当不进行训练时,停止梯度更新,节省计算内存消耗
with torch.no_grad():
for imgs, targets in test_loader:
imgs, targets = imgs.to(device), targets.to(device)
target_pred = model(imgs)
loss = loss_fn(target_pred, targets)
test_loss += loss.item()
test_acc += (target_pred.argmax(1) == targets).type(torch.float).sum().item()
test_acc /= size
test_loss /= num_batches
return test_acc, test_loss
with.torch.no_grad(),测试时停止梯度更新,可以节省计算内存消耗。其余部分和训练函数大致相同。
七、开始训练
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_loader, model, loss_fn, optimizer)
model.eval()
epoch_test_acc, epoch_test_loss = test(test_loader, 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')
设置epochs = 10,即进行10轮训练,将训练损失、训练准确率、测试损失、测试准确率存入空列表,以便后续进行可视化。model.train()开启训练模式,也就是启用Batch Normalization和Dropout。由于测试时不需要批量正则化和dropout,因此在测试时需要添加model.eval(),不启用BN和Dropout。
八、结果可视化
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 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()
最终绘制损失函数和准确率。
总结
本文通过使用简易的CNN模型实现了MNIST手写数字数据集的识别。
主要流程如下:
1. 导入相关的库,配置设备GPUorCPU
2. 通过torchvison.datasets.MNIST函数导入MNIST数据集,并通过torch.utils.data.DataLoader函数按批次加载数据集
3. 可以选择查看一个批次的图片,将其可视化。可以通过next(iter())函数实现。
4. 建立卷积神经网络模型,分为特征提取网络和分类网络,特征提取一般步骤为:卷积-激活-池化,不断重复,提取图片特征。然后将特征展平进入全连接层进行最后的分类,将特征降维到图片的类别数。
5. 编写训练函数和测试函数,首先要设置超参数,学习率,优化器,损失函数。然后分批加载数据集,计算预测输出,损失函数,梯度清零,反向传播,参数更新。最后函数返回损失函数值和准确率。
6. 设置epoch,进行主循环。注意训练时使用model.train(),测试使用model.eval()。
7. 最后可以将训练loss、测试loss、训练准确率、测试准确率进行可视化。