- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍦 参考文章:[365天深度学习训练营-第P1周:实现mnist手写数字识别](Pytorch实战 | 第P1周:实现mnist手写数字识别 (qq.com))**
- 🍖 原作者:K同学啊|接辅导、项目定制
🏡 我的环境:
语言环境:Python 3.6.13
编译器:Pycharm 2020.2
深度学习环境:Pytorch 1.10.0
显卡及显存: Tesla T4 16G(服务器)
🍺 要求:
- 了解Pytorch,并使用Pytorch构建一个深度学习程序(✔)
- 了解什么是深度学习(✔)
🍻拔高(可选)
- 学习文中提到的函数方法(✔)
目录
2.2.1 torchvision.datasets.MNIST
2.2.2 torch.utils.data.DataLoader
一、 前言
众所周知,MNIST是机器学习入门的hello word!它是一个大型的手写数字数据库,通常用于训练各种图像处理系统。MNIST 数据库包含 60,000 张训练图像和 10,000 张测试图像。传统机器学习方法已经用这个训练集和测试集进行了测试。结果如下表:
类型 | 准确率(%) |
线性分类器 | 92.4 |
非线性分类器 | 96.7 |
随机森林 | 97.2 |
可以看出,传统机器学习方法的准确率还有待提升,因此本文采用深度学习的方法来实现mnist手写数字识别。统计结果来源于MNIST 数据库-维基百科。
二、 准备工作
2.1 设置显卡GPU,没有用CPU
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import torchvision
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device(type='cuda')
2.2 导入数据
2.2.1 torchvision.datasets.MNIST
本文使用的Pytorch自带的数据库torchvision.datasets
,这里使用的是torchvision.datasets
中的MNIST
数据集。
函数原型:
torchvision.datasets.MNIST(root, train=True, transform=None, target_transform=None, download=False)
参数详解:
-
root ( string ) -- 数据集所在的根目录 。训练集文件目录/data/precessed/training.pt,测试集文件目录 /data/precessed/test.pt
-
train ( bool , optional ) – 如果为真,则创建训练集,否则创建测试集。
-
download ( bool , optional ) – 如果为 True,则从 Internet 下载数据集并将其放在根目录中。如果数据集已经下载,则不会再次下载。
-
transform ( callable , optional ) -- 一个函数/转换,它接收 PIL 图像并返回转换后的版本。例如,
transforms.RandomCrop
-
target_transform ( callable , optional ) – 接收目标并对其进行转换的函数/转换。
train_ds = torchvision.datasets.MNIST('data',
train=True,
transform=torchvision.transforms.ToTensor(), # 将数据类型转化为Tensor
download=True)
test_ds = torchvision.datasets.MNIST('data',
train=False,
transform=torchvision.transforms.ToTensor(), # 将数据类型转化为Tensor
download=True)
2.2.2 torch.utils.data.DataLoader
- 数据加载器。结合数据集和采样器,并提供给定数据集的可迭代对象。
- 支持具有单进程或多进程加载、自定义加载顺序和可选的自动批处理(整理)和内存固定的地图样式和可迭代样式数据集。
- 函数原型:
torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=None,...)
参数说明(常用有5个):
- dataset: Dataset类, 决定数据从哪读取以及如何读取。
- bathsize: 批大小。
- num_works: 是否多进程读取机制。
- shuffle: 每个epoch是否乱序。
- drop_last: 当样本数不能被batchsize整除时, 是否舍弃最后一批数据。
batch_size = 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)
# 取一个批次查看数据格式
# 数据的shape为:[batch_size, channel, height, weight]
# 其中batch_size为自己设定,channel,height和weight分别是图片的通道数,高度和宽度。
imgs, labels = next(iter(train_dl))
imgs.shape
测试结果
2.3 数据可视化
2.3.1 squeeze 函数
函数原型:
torch.squeeze( input , dim = None , * , out = None )
返回一个张量,其中所有input
大小为1的维度都已删除。
举例:
输入:AX1XBXCX1XD
输出:AXBXCXD
#%%
import numpy as np
# 指定图片大小,图像大小为20宽、5高的绘图(单位为英寸inch)
plt.figure(figsize=(20, 5))
for i, imgs in enumerate(imgs[:20]):
# 维度缩减
npimg = np.squeeze(imgs.numpy())
print(imgs.shape)
print( npimg.shape)
# 将整个figure分成2行10列,绘制第i+1个子图。
plt.subplot(2, 10, i+1)
plt.imshow(npimg, cmap=plt.cm.binary)
plt.axis('off')
plt.show()
#%%
三.CNN网络
构成:特征提取网络和分类网络
其中特征提取网络用于提取图片的特征,分类网络用于将图片进行分类。
函数:
3.1 nn.Conv2d
参考PyTorch官方文档,再结合这篇卷积神经网络(CNN)的整体框架及细节(详细简单)来理解卷积。
为卷积层,用于提取图片的特征,传入参数 (输入通道,输出通道,卷积核大小)
输入:(N,Cin,w,w)
输出:(N,Cout,o,o)
3.2 nn.MaxPool2d
为池化层,进行下采样。
3.3 nn.ReLU
激活函数,使模型可以拟合非线性数据
参数:
-
输入:任意数量的维度。
-
输出:与输入的形状相同。
3.4 nn.Linear
对输入数据应用线性变换。
参数:
-
in_features – 每个输入样本的大小
-
out_features – 每个输出样本的大小
3.5 nn.Sequential
详细解析可以参考这篇博客.pytorch中的顺序容器——torch.nn.Sequential
总的来说,构建模块有以下两种方式:
- 直接嵌套
- 以 orderdict 有序字典的方式进行传入
3.6 模型设计(改进CNN模型)
本文模型使用Sequential构建,第一层使用两个5*5的卷积核,第二层使用两个3*3的卷积核,层与层之间用dropout减少网络规模。更多提升方法参加这篇博客MNIST手写数字识别准确度提升最全、最实用的方法-基于tensorflow。
import torch
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential,ReLU,Dropout
num_classes = 10
class Model(nn.Module):
def __init__(self):
super(Model,self).__init__()
self.conv1=Sequential(
Conv2d(1, 32, kernel_size=5,padding='same'),# 32*28*28
ReLU(),
Conv2d(32, 32, kernel_size=5,padding='same'), # 32*28*28
ReLU(),
MaxPool2d(2), #高宽减半 #32*14*14
Dropout(0.25)
)
self.conv2=Sequential(
Conv2d(32, 64, kernel_size=3,padding='same'), # 64*14*14
ReLU(),
Conv2d(64, 64, kernel_size=3,padding='same'), # 64*14*14
ReLU(),
MaxPool2d(2), #高宽减半 # 64*7*7
Dropout(0.25)
)
self.fc=Sequential(
Linear(32*7*7,64),
ReLU(),
Linear(64,num_classes)
)
def forward(self, x):
batch_size = x.size(0)
x = self.conv1(x) # 一层卷积层,一层激活层,一层卷积层,一层激活层,一层Dropout
x = self.conv2(x) # 再来一次
x = x.view(batch_size, -1) # flatten 变成全连接网络需要的输入 (batch, 64,7,7) ==> (batch,64*7*7), -1 此处自动算出的是3136
x = self.fc(x)
return x
from torchinfo import summary
# 将模型转移到GPU中(我们模型运行均在GPU中进行)
model = Model().to(device)
summary(model)
四 训练模型
4.1 设置超参数
loss_fn = nn.CrossEntropyLoss() # 创建损失函数
learn_rate = 1e-3 # 学习率
opt = torch.optim.Adam(model.parameters(),lr=learn_rate)
4.2 编写训练函数
loss.backward()
- 反向传播,通过autograd包来实现的,autograd自动计算其对应的梯度。
- 如果没有进行
tensor.backward()
的话,梯度值将会是None,优先执行loss.backward()
函数来计算梯度
#%%
# 训练循环
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与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
4.3 编写测试函数
def test (dataloader, model, loss_fn):
size = len(dataloader.dataset) # 测试集的大小,一共10000张图片
num_batches = len(dataloader) # 批次数目,313(10000/32=312.5,向上取整)
test_loss, correct = 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_batches
return test_acc, test_loss
4.4 正式训练
epochs = 20
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')
可以看到模型准确率达到99.5%。
4.5 结果可视化
五 总结
1.了解pytorch开发基础流程,学习一些函数的使用方法。模型使用Sequential构建,第一层使用两个5*5的卷积核,第二层使用两个3*3的卷积核,层与层之间使用dropout减少网络规模,同时修改优化器为Adam,学习率要修改为0.001.
2.改进后的CNN模型准确率达到了99.5%。
3.其他提升准确率的方法可以参考这篇博客。MNIST手写数字识别准确度提升最全、最实用的方法-tensorflow。