引言
手写数字识别,也就是让机器能够习得图片中的手写数字,并能正确归类。
本文使用 pytorch 搭建一个简单的神经网络,实现手写数字的识别,
从本文,你可了解到:
1、搭建神经网络的流程
2、完成手写数字识别模型
3、pytorch基本库
1.准备数据
''' 1. 导人必要的模块 '''
import numpy as np
import torch
# 导入 pytorch 内置的 mnist 数据
from torchvision.datasets import mnist
#导入预处理模块
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
#导入nn及优化器
import torch.nn.functional as F
import torch.optim as optim
from torch import nn
其中,torch.nn 是 pytorch 中重要的神经网络高级封装,其封装了:常见的网络层,如:卷积,以及优化器等。
我们这里使用 mnist 数据集,其里面就包括了 手写数字识别的数据集。
transforms 和 DataLoader 主要用来做数据的下载和预处理。
torch.optim 是我们使用的优化器
下面我们定义一些超参数:
''' 2. 定义一些超参数 '''
train_batch_size = 64 # 训练批次
test_batch_size = 128 # 测试批次
learning_rate = 0.01 # 学习率
num_epoches = 20
lr = 0.01
momentum = 0.5
接下来,下载 mnist 数据集,并封装到 DataLoader 中:
''' 3. 下载数据并对数据进行预处理 '''
#定义预处理函数,这些预处理依次放在Compose函数中。
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize([0.5], [0.5])])
#下载数据,并对数据进行预处理
train_dataset = mnist.MNIST('/Users/zhouzhan/Documents/to_github/NLPLearn/Deep-Learning/data', train=True, transform=transform, download=True)
test_dataset = mnist.MNIST('/Users/zhouzhan/Documents/to_github/NLPLearn/Deep-Learning/data', train=False, transform=transform)
#dataloader是一个可迭代对象,可以使用迭代器一样使用。
train_loader = DataLoader(train_dataset, batch_size=train_batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=test_batch_size, shuffle=False)
2.可视化数据源
下载成功后,我们看一下数据集长什么样:
import matplotlib.pyplot as plt
%matplotlib inline
examples = enumerate(test_loader)
batch_idx, (example_data, example_targets) = next(examples)
fig = plt.figure()
for i in range(6):
plt.subplot(2,3,i+1)
plt.tight_layout()
plt.imshow(example_data[i][0], cmap='gray', interpolation='none')
plt.title("Ground Truth: {}".format(example_targets[i]))
plt.xticks([])
plt.yticks([])
运行结果:
3.构建模型
构建模型,即:构建神经网络模型
其搭建神经网络所需组件:
- 层:神经网络的层级
- 模型:层构成的网络
- 损失函数:学习过程中的目标函数,即:损失函数最小化
- 优化器:如何使损失函数最小化的方法
首先,是构建网络模型,模型是由层构成的网络,我们的模型有2个隐藏层,且每层都含有一个激活函数 ReLU,最后使用 torch.max(out,1) 找出张量 out 最大值对应索引作为预测值:
代码如下:
''' 1. 构建网络 '''
class Net(nn.Module):
"""
使用sequential构建网络,Sequential()函数的功能是将网络的层组合到一起
"""
def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
super(Net, self).__init__()
# 第一层网络
self.layer1 = nn.Sequential(nn.Linear(in_dim, n_hidden_1),nn.BatchNorm1d(n_hidden_1))
# 第二层网络
self.layer2 = nn.Sequential(nn.Linear(n_hidden_1, n_hidden_2),nn.BatchNorm1d(n_hidden_2))
# 输出层
self.layer3 = nn.Sequential(nn.Linear(n_hidden_2, out_dim))
def forward(self, x):
x = F.relu(self.layer1(x))
x = F.relu(self.layer2(x))
x = self.layer3(x)
return x
我们定义了一个 class Net,它继承 nn.Module 类(它是所有网络的基类)
nn.Module 类里面定义了很多模型,如:卷积层、全连接层、池化层等,一般定义网络都需基层该类。
其中,__ init __ 方法,用于定义网络;forward 方法,实现前向传播。
forward函数:任务是把输入层、网络层、输出层链接起来,实现信息的前向传导。
nn.Sequential:一个有序的容器,它可将神经网络模块依次添加到计算图中执行。
实例化网络:
''' 2. 实例化网络 '''
#检测是否有可用的GPU,有则使用,否则使用CPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
#实例化网络
model = Net(28 * 28, 300, 100, 10)
model.to(device)
最后,我们定义损失函数和优化器,则网络构造完毕:
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum
criterion: 交叉熵损失
optimizer:SGD优化器(随机梯度下降算法)
4.训练模型
训练模型的代码有点长,
根据之前设置的 num_epoches 进行多次训练和预测,
其目的是通过训练,学习到适合的参数,动态修改学习率
其步骤如下:
- 动态修改学习率;
- model.train(),将模型设置为训练模式;
- 前向传播;
- 反向传播;
- 计算训练误差;
- model.eval(),将模型设置为预测模式;
- 预测模型;
- 计算预测误差。
根据以上步骤,其代码实现如下:
''' 1. 训练模型 '''
# 开始训练
losses = []
acces = []
eval_losses = []
eval_acces = []
for epoch in range(num_epoches):
train_loss = 0
train_acc = 0
model.train()
#动态修改参数学习率
if epoch%5==0:
optimizer.param_groups[0]['lr']*=0.1
for img, label in train_loader:
img=img.to(device)
label = label.to(device)
img = img.view(img.size(0), -1)
# 前向传播
out = model(img)
loss = criterion(out, label)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 记录误差
train_loss += loss.item()
# 计算分类的准确率
_, pred = out.max(1)
num_correct = (pred == label).sum().item()
acc = num_correct / img.shape[0]
train_acc += acc
losses.append(train_loss / len(train_loader))
acces.append(train_acc / len(train_loader))
# 在测试集上检验效果
eval_loss = 0
eval_acc = 0
# 将模型改为预测模式
model.eval()
for img, label in test_loader:
img=img.to(device)
label = label.to(device)
img = img.view(img.size(0), -1)
out = model(img)
loss = criterion(out, label)
# 记录误差
eval_loss += loss.item()
# 记录准确率
_, pred = out.max(1)
num_correct = (pred == label).sum().item()
acc = num_correct / img.shape[0]
eval_acc += acc
eval_losses.append(eval_loss / len(test_loader))
eval_acces.append(eval_acc / len(test_loader))
print('epoch: {}, Train Loss: {:.4f}, Train Acc: {:.4f}, Test Loss: {:.4f}, Test Acc: {:.4f}'
.format(epoch, train_loss / len(train_loader), train_acc / len(train_loader),
eval_loss / len(test_loader), eval_acc / len(test_loader)))
下面进行关键代码解释:
前向传播,就是将图片输入到模型中,得出结果,并计算出损失值
# 前向传播
out = model(img)
loss = criterion(out, label)
反向传播,
zero_grad():将梯度清零,因为缺省情况下梯度是累加的,所以需要手动清零;
backward():自动生成梯度;
step():执行优化器,把梯度传播回每个网络。
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
最后的结果:
我们看看损失函数:
''' 2. 可视化训练及测试损失值 '''
plt.title('train loss')
plt.plot(np.arange(len(losses)), losses)
plt.legend(['Train Loss'], loc='upper right')
5.总结
从这个例子中,我们学到了:
- mnist 数据集 是一个不错的入门学习数据集;
- 数据集的下载与预处理,可使用:transforms 和 DataLoader 来完成;
- 使用 matplotlib.pyplot 来做可视化操作;
- 通过继承 nn.Module 来构建神经网络模型;
-
训练模型,最后得出结果
最后,你可以根据本文,自己手写代码完成,学习效果更佳哦~