近几年,基于深度学习的应用越来越多,可以说深度学习已经遍及我们生活的各个角落。同时卷积神经网络(CNN: Convolution Neural Network)在计算机视觉领域大放光彩,作为一名没有跨入深度学习门槛的萌新,在迷茫了一阵时间以后,还是打算从最基本的LeNet-5网络学起,并用现在比较流行的Pytorch将其实现。通过写博客,一方面可以加深自己的理解,另一方面希望能够帮助需要的人。由于水平有限,博客中不免存在一些问题,请在评论区告诉我。
我们在平时所说的LeNet一般都指的是LeNet-5,出自论文Gradient-Based Learning Applied to Document Recognition,LeNet-5网络非常适合于手写数字的识别,比较熟悉LeNet-5网络的话可以直接跳入下一节。
下图是LeNet-5的网络结构。
该网络包含7层(输入层不算入层数),由两个卷积层,两个池化层,三个全连接层组成。
这一层的输入就是原始图像的像素,在论文中,LeNet模型接受的输入层大小为32 * 32 * 1,卷积层过滤器的尺寸为5 * 5,深度为6,步长为1,所以这一层的输出尺寸为32 - 5 + 1 = 28,同时采用ReLu函数作为激活函数。
这一层的输入为第一层的输出,输入尺寸为28 * 28 * 6,池化层采用的过滤器大小为2 * 2,长和宽的步长均为2 * 2,采用最大池化的方式,所以本层的输出尺寸为14 * 14 * 6。
这一层输入尺寸为14 * 14 * 6,过滤器尺寸为5 * 5,深度为16,步长为1,所以这一层的输出尺寸为14 - 5 + 1 = 10,同时采用ReLu函数作为激活函数。
本层的输入尺寸为10 * 10 * 16,其他参数与第二层保持一致,所以本层的输出尺寸为5 * 5 * 16。
本层的输入尺寸为5 * 5 * 16,在全连接层,首先将输入矩阵拉成一个向量,这里将把输入转换成一个5 * 5 * 16维的向量,输出向量的维数为120,将输入向量与输出向量全连接,参数矩阵的尺寸为[5 * 5 * 16, 120]。
本层输入向量维数为120,输出向量维数为84,将输入向量与输出向量全连接,参数矩阵的尺寸为[120, 84]。
本层输入向量维数为84,输出向量维数为10,将输入与输出进行全连接,参数矩阵的尺寸为[84, 10]。最终输出是一个范围为[0, 1]的10维向量,依次代表0-9,取10个数中最大的值为预测结果。
代码基于Pytorch框架实现,由三个文件model.py, train.py, test.py组成。
使用MNIST数据集进行训练与测试,该数据集中包含60000张手写图片用于训练,10000张手写图片用于测试。
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# 卷积层
# i --> input channels
# 6 --> output channels
# 5 --> kernel size
self.conv1 = nn.Conv2d(1, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
# 全连接层
# 16 * 4 * 4 --> input vector dimensions
# 120 --> output vector dimensions
self.fc1 = nn.Linear(16 * 4 * 4, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
# 卷积 --> ReLu --> 池化
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
x = F.max_pool2d(F.relu(self.conv2(x)), (2, 2))
# reshape, '-1'表示自适应
# x = (n * 16 * 4 * 4) --> n : input channels
# x.size()[0] == n --> input channels
x = x.view(x.size()[0], -1)
# 全连接层
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
import torchvision as tv
import torchvision.transforms as transforms
import torch
import torch.nn as nn
from torch import optim
from torch.utils.data import DataLoader
from torch.autograd import Variable
from model import Net
if __name__ == '__main__':
# 定义对数据的预处理
transform = transforms.Compose([
transforms.ToTensor(), # 转换为Tensor,并归一化至[0, 1]
])
# 训练集
trainset = tv.datasets.MNIST(
root='data/',
train=True,
download=True,
transform=transform
)
trainloader = DataLoader(
dataset=trainset,
batch_size=4,
shuffle=True
)
# MNIST数据集中的十种标签
classes = ('0', '1', '2', '3', '4',
'5', '6', '7', '8', '9')
# 创建网络模型
net = Net()
if torch.cuda.is_available():
# 使用GPU
net.cuda()
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.005, momentum=0.9)
for epoch in range(5):
running_loss = 0.0
for i, data in enumerate(trainloader):
# 输入数据
inputs, labels = data
if torch.cuda.is_available():
inputs = inputs.cuda()
labels = labels.cuda()
inputs, labels = Variable(inputs), Variable(labels)
# 梯度清0
optimizer.zero_grad()
# forward + backward
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
# 更新参数
optimizer.step()
# 打印log信息
running_loss += loss.item()
# 每2000个batch打印一次训练状态
if i % 100 == 99:
print('[{}/{}][{}/{}] loss: {:.3f}'.format(epoch + 1, 5, (i + 1) * 4, len(trainset), running_loss / 100))
running_loss = 0.0
# 保存参数文件
torch.save(net.state_dict(), 'checkpoints/model_{}.pth'.format(epoch + 1))
print('model_{}.pth saved'.format(epoch + 1))
print('Finished Training')
打印结果
[1/5][400/60000] loss: 2.303
[1/5][800/60000] loss: 2.261
[1/5][1200/60000] loss: 1.767
[1/5][1600/60000] loss: 0.909
[1/5][2000/60000] loss: 0.724
[1/5][2400/60000] loss: 0.506
[1/5][2800/60000] loss: 0.524
[1/5][3200/60000] loss: 0.472
[1/5][3600/60000] loss: 0.452
...
...
...
[5/5][56800/60000] loss: 0.048
[5/5][57200/60000] loss: 0.025
[5/5][57600/60000] loss: 0.037
[5/5][58000/60000] loss: 0.037
[5/5][58400/60000] loss: 0.062
[5/5][58800/60000] loss: 0.021
[5/5][59200/60000] loss: 0.011
[5/5][59600/60000] loss: 0.004
[5/5][60000/60000] loss: 0.040
import torchvision as tv
import torchvision.transforms as transforms
import torch
from torch.utils.data import DataLoader
from torch.autograd import Variable
from model import Net
def get_dataset():
# 数据预处理
transform = transforms.Compose([
transforms.ToTensor()
])
# 测试集
testset = tv.datasets.MNIST(
root='data/',
train=False,
download=False,
transform=transform,
)
testloadter = DataLoader(
dataset = testset,
batch_size = 4,
shuffle = True,
)
return len(testset), testloadter
def main():
# 数字类别
classes = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
# 初始化网络参数
model = Net()
# 加载模型
model.load_state_dict(torch.load('checkpoints/model_5.pth'))
if torch.cuda.is_available():
# 使用GPU
model.cuda()
# get dataloader
data_len, dataloader = get_dataset()
# 预测正确的个数
correct_num = 0
for i, data in enumerate(dataloader):
inputs, labels = data
if torch.cuda.is_available():
inputs = inputs.cuda()
labels = labels.cuda()
inputs, labels = Variable(inputs), Variable(labels)
outputs = model(inputs)
# 取最大值为预测结果
_, predicted = torch.max(outputs, 1)
for j in range(len(predicted)):
predicted_num = predicted[j].item()
label_num = labels[j].item()
# 预测值与标签值进行比较
if predicted_num == label_num:
correct_num += 1
# 计算预测准确率
correct_rate = correct_num / data_len
print('correct rate is {:.3f}%'.format(correct_rate * 100))
if __name__ == "__main__":
main()
运行代码,测试准确率
correct rate is 98.360%