一、前置工作—确定神经网络结构
搭建神经网络完成某项特定任务,就像做一项工程,需要了解该项目的大致基本情况,准备图纸,才能确定每一步的落实方案。我在学校学习时忽视了这一点,只是老师下达了任务便埋头去构建网络,这其实不利于我对神经网络的深入理解。
首先,应该了解数据的基本情况,如手写数字识别任务中,图片的大小是多少,数据集规模有多大等。
在构建网络的部分,我们需要了解确定的是需要多少层卷积、全连接层,当然,为了填入具体的参数,也必须了解图片大小。通常深度学习项目需要一个卷积神经网络结构图,后续搭建网络都依据该图展开。现实中,设计每层卷积层输出通道也是需要通过训练才能得出最好参数的(相当于确定隐藏层),但在学习过程中直接采用较好的参数训练即可。
已知MNIST数据集图像大小为28×28的灰度图,设置神经网络卷积核为5*5,第一层输出通道数为6,第二层输出通道数为16,那么可以大致绘出神经网络结构图如下:
从左至右,最左的1@28×28表示输入图像(其实一般LeNet图不会标出,此处是为了方便理解)。输入进入网络,进行第一层卷积,第一层卷积输出通道为6,原图像大小不变,便得出6@28×28;卷积下一步即池化,这里采用2*2最大池化,即将原图像缩小到原来的一半,缩小的每个区域取最大值代替,依照这个流程完成若干层卷积池化的构造。在进入全连接层前,要注意先展平,最后输出分类结果数即可(0-9有10个数字,所以最终输出应为10).
二、开始搭建神经网络
根据上面画出的神经网络结构图,已经能够开始搭建神经网络了。
首先导入必要的库:
# 实现基本运算:
import torch
# 搭建网络结构:
import torch.nn as nn
# 实现前馈运算:
import torch.functional as f
开始编写神经网络架构:
# file:CNN.py
Class ConvNet(nn.Module):
"""编写一个卷积神经网络类"""
def __init__(self):
""" 初始化网络,将网络需要的模块拼凑出来。 """
super(ConvNet, self).__init__()
# 卷积层:
self.conv1 = nn.Conv2d(1, 6, 5, padding=2)
self.conv2 = nn.Conv2d(6, 16, 5, padding=2)
# 最大池化处理:
self.pooling = nn.MaxPool2d(2, 2)
# 全连接层:
self.fc1 = nn.Linear(16*7*7, 512)
self.fc2 = nn.Linear(512, 10)
def forward(self, x):
"""前馈函数"""
x = f.relu(self.conv1(x)) # = [b, 6, 28, 28]
x = self.pooling(x) # = [b, 6, 14, 14]
x = f.relu(self.conv2(x)) # = [b, 16, 14, 14]
x = self.pooling(x) # = [b, 16, 7, 7]
x = x.view(x.shape[0], -1)# = [b, 16 * 7 * 7]
x = f.relu(self.fc1(x))
x = self.fc2(x)
output = f.log_softmax(x, dim=1)
return output
为了后续计算准确率需要,此处还应加上一个计算准确率的方法:
def cal_correction(output, target):
"""计算准确率"""
# 返回预测结果:
pred = torch.argmax(output)
# 比对并统计:
correct = pred.eq(target.data.view_as(output)).sum()
# 返回百分比:
percentage = (correct / len(target)) * 100
return percentage
神经网络的结构搭建完成了,有几个地方我在学习的时候比较有疑惑,应该解释一下。
①关于nn.Module和super().init()
理解nn.Module为何物,首先就应理解python中类的定义。在默认情况下,python类定义时为:class Class(object). object表示父类,python中默认所有的类都可以继承父类object. 这里填入nn.Module的含义即把其当作父类继承,而当填入继承对象时,下文必须搭配super().init()语句,才能够完成继承父类的操作。
nn.Module为神经网络父类模型,继承了nn.Module后,模型能够完成forward以及bp的操作。
②[b, c, w, h]
在编写初始化方法中第一层全连接层输入时,常常难以确定,其实非常简单,只要在forward方法中推一遍即可。[b, c, w, h]含义从左到右为[batch_size, channels(输出通道数), 图像宽度, 图像高度], 已知图像尺寸为28×28,输出通道数即每层卷积核的输出数量。进入全连接层前先展平,只保留batch_size,其他元素相乘输入全连接层,这样一来就解释得清楚了。
三、导入数据
按理来说导入数据应该是第一步,因为需要将数据导入进来,对数据有初步的认识。这里是基于我对Mnist数据有了大致的认识的情况下,我会选择先搭建好神经网络。
# file:Dataloader.py
"""导入必要的库"""
# 实现基本运算:
import torch
# 数据装载器:
from torch.utils.data import DataLoader as dataloader
# 读取数据集:
import torchvision.datasets as datasets
# 数据转换器:
import torchvision.transforms as transforms
设置一个数据转换器,否则原始数据无法直接用来训练。
transformer = transforms.Compose([
# 转化成张量:
transforms.ToTensor(),
# 标准化数据:
transforms.Normalize((0.1307,),(0.3081,))]
编写一个函数一步获取训练数据:
def get_train_data():
"""获取训练数据"""
# 下载训练数据:
train_data = datasets.MNIST(root='./',
download=True,
train=True,
transform=transformer)
# 装载训练数据:
train_loader = dataloader(train_data,
batch_size=64,
shuffle=True)
return train_data, train_loader
获取测试、验证数据:
def test_val_data():
"""获取测试、验证数据"""
# 下载数据
t_v_data = datasets.MNIST(root='./',
download=True,
train=False,
transform=transformer)
# 设置采样器(一半用于验证、一半用于测试):
indices = range(len(t_v_data))
test_sampler = torch.utlis.data.sampler.SubsetRandomSampler(indices[:5000])
val_sampler = torch.utlis.data.sampler.SubsetRandomSampler(indices[5000:])
# 装载数据:
test_dataloader = dataloader(t_v_data,
sampler=test_sampler,
batch_size=64)
val_dataloader = dataloader(t_v_data,
sampler=val_sampler,
batch_size=64)
return t_v_data, test_dataloader, val_dataloader
在主函数中调用这两个方法即可实现数据的读取。
四、开始训练
训练过程按照前馈神经网络的流程编写即可:
# file:train_test.py
# 将先前编写好的计算准确率方法导入:
from CNN import cal_corrction
def train_model(model, train_data, val_data, batch, epoch, loss_func, opt)
""" @参数说明:
model - 需要训练的模型;
train_data - 训练数据;
val_data - 验证数据;
batch - 每次训练的批量;
epoch - 训练轮数;
loss_func - 损失函数;
opt - 优化器."""
for i in range(epoch):
# 定义记录损失值、精度的容器:
train_corrections = []
train_losses = []
for idx, (img, label) in enumerate(train_data):
# 数据处理:
# 转化成cuda类型:
img, label = img.to('cuda'), label.to('cuda')
# 保留梯度:
img = img.clone().requires_grad_(True)
label = label.clone().detach()
""" ----- 前向传播 ----- """
# 设置模型为训练模式:
model.train()
# 将数据喂入网络:
output = model(img)
# 计算精度:
train_acc = cal_correction(output, label)
train_corrections.append(train_acc)
# 计算损失值:
train_loss = loss_func(output, label)
train_losses.append(train_loss)
# 清空优化器梯度:
opt.zero_grad()
""" ----- 反向传播 ----- """
# 反传损失值:
train_loss.backward()
# 用优化器梯度下降:
opt.step()
if idx % (batch * 100) == 0:
"""每训练好100批次验证并展示训练结果"""
model.eval()
val_record = []
for (data, target) in val_data:
# 转化数据类型:
data, target = data.to('cuda'), target.to('cuda')
data, target = data.clone().requires_grad_(True), target.clone().detach()
# 将数据喂入:
out = model(data)
# 预测准确率:
val_acc = cal_correction(out, target)
val_record.append(val_acc)
# 打印训练、验证结果:
print(f'epoch{i+1}: Train Acc={train_corrections[-1]} Train Loss={train_losses[-1]} Val Acc={val_record[-1]}')
还应该加入测试环节,检验该模型的泛化能力。
def test_model(model, test_data):
"""测试模型"""
# 定义记录测试精度的容器:
test_acc = []
for idx, (img, label) in enumerate(test_data):
# 处理数据:
img, label = img.to('cuda'), label.to('cuda')
img, label = img.clone().requires_grad_(True), label.clone().detach()
# 投入数据:
output = model(img)
# 比对结果:
acc = cal_correction(output, label)
test_acc.append(acc)
print(f'Test Acc={test_acc[-1]}%')
完成模型训练、测试环节的编写后,观察其训练过程:
# file:main.py
# 实现相关运算(这里主要是损失函数、优化器等):
import torch
# 导入神经网络模型:
from CNN import ConvNet
# 导入数据:
from Dataloader import get_train_data, test_val_data
# 导入训练、测试方法:
from train_test import train_model, test_model
"""超参数"""
batch_size = 64
epochs = 10
learning_rate = 0.001
momentum = 0.9
# 实例化神经网络模型:
cnn_model = ConvNet().cuda()
# 定义损失函数(交叉熵):
loss_func = torch.nn.CrossEntropyLoss()
# 定义优化器(随机梯度下降):
sgd_opt = torch.optim.SGD(cnn_model.parameters(),
# 学习率:
lr=learning_rate,
# 动量:
momentum=momentum)
# 读取数据:
_, train_loader = get_train_data()
_, test_loader, val_loader = test_val_data()
# 开始训练:
train_model(cnn_model, train_loader, val_loader, batch_size, epochs, loss_func, sgd_opt)
test_model(cnn_model, test_loader)
# 保存模型,以便之后直接使用模型进行可视化结果测试:
torch.save(cnn_model.state_dict(), './cnnmodel')
五、可视化测试模型识别能力
上面已经完成了神经网络的训练,如果能够带上图片观察其识别结果,那么将能够更直观地了解该模型的识别能力:
# file:test.py
import random
import torch
import pylab
import matplotlib.pyplot as plt
from Dataloader import get_train_data
from ConvNetwork import ConvNet
# 实例化模型
test_net = ConvNet()
# 读取已保存的模型
test_net.load_state_dict(torch.load('./Models/ConvNet4Mnist1217.pth'))
# 随机生产一张图片
x = random.randint(0, 59999)
train_data, _ = get_train_data()
# 丢入网络预测
output = test_net(train_data[x][0].unsqueeze(0))
pred = torch.argmax(output)
print(f"这张图片被识别为数字{pred}.")
print(f"这张图片实际为{train_data[x][1]}.")
if pred == train_data[x][1]:
print("识别正确!")
else:
print("识别错误!")
# 显示图像
plt.imshow(train_data[x][0].squeeze(0))
pylab.show()
ok了,基本都能识别正确。