目录
4.2、pytorch的代码实现
5.2、将激活函数改为ReLu函数,lr=0.1,epoch=20
1、LeNet网络
LeNet是Yann LeCun在1998年发表的论文《Gradient-Based Learning Applied to Document Recognition》中提出来的一个模型,是第一个利用反向传播实现卷积的神经网络。最早应用于识别手写数字和印刷字符。由于模型比较简单,适合新手入门学习。
LeNet模型经历过3个发展阶段:
LeNet-1:5 层模型,一个简单的 CNN。
LeNet-4:6 层模型,是 LeNet-1 的改进版本。
LeNet-5:7 层模型,现在最常用的版本
其中LeNet是以LeCun的名字命名,现在最常用的是LeNet-5.
2、LeNet-1
2.1 各层介绍
2.1.1 卷据层C
目的:提取输入的不同特征。
第一层卷积层只能提取低级的特征如边缘、线条和角等,更多层能提取更复杂的特征。
2.2.2 采样层S
又分为上采样和下采样两种。
上采样主要目的是放大原图像,从而可以显示在更高分辨率的显示设备上;
下采样又称为池化层,主要目的:1、使得图像符合显示区域的大小;2、生成对应图像的缩略图。
经过卷积后,图片大小没变,通道数变多;经过池化后,图片尺寸变小。卷积的作用是减少噪声,更好的提取图像特征。池化层的主要作用就是减少数据,降低数据纬度的同时保留最重要的信息。
2.2.3 全连接层F
全连接层一般用于 CNN 的最后几层,负责提取卷积和池化之后的特征。
各个层的参数配置如下表所示。
layer_name | kernal_size | kernal_num | padding | stride |
C1卷积层 | 5 | 4 | 0 | 1 |
S2均值池化层 | 2 | 0 | 2 | |
C3卷积层 | 5 | 12 | 0 | 1 |
S4均值池化层 | 2 | 0 | 2 | |
F5全连接层 |
输出参数的计算公式为:
比如第1层,输入图片尺寸为32*32,也就是input_size=32,kernel_size=5,padding=0,stride=1,带入上式,计算出output_size=28,通道数为6。具体过程参考文献[2]的内容。
2.2 pytorch的代码实现
2.2.1 导入需要的库和数据
import torch
import torchvision
import torch.nn as nn
import torchvision.transforms as transforms
import torch.nn.functional as F
import numpy as np
import matplotlib.pyplot as plt
trans_to_tensor = transforms.Compose([
transforms.ToTensor()
])
data_train = torchvision.datasets.MNIST(
'./data',
train=True,
transform=trans_to_tensor,
download=True)
data_test = torchvision.datasets.MNIST(
'./data',
train=False,
transform=trans_to_tensor,
download=True)
data_train, data_test
'''
(Dataset MNIST
Number of datapoints: 60000
Root location: ./data
Split: Train
StandardTransform
Transform: Compose(
ToTensor()
),
Dataset MNIST
Number of datapoints: 10000
Root location: ./data
Split: Test
StandardTransform
Transform: Compose(
ToTensor()
))
'''
代码运行完,MNIST数据集就下载到本地。
随即可视化一个数据
train_loader = torch.utils.data.DataLoader(data_train, batch_size=100, shuffle=True)
x, y = next(iter(train_loader))
plt.imshow(x[0].squeeze(0), cmap='gray'), y[0]
输出:
2.2.2 设计训练网络:
def test(net):
net.eval()
test_loader = torch.utils.data.DataLoader(data_train, batch_size=10000, shuffle=False)
test_data = next(iter(test_loader))
with torch.no_grad():
x, y = test_data[0], test_data[1]
outputs = net(x)
pred = torch.max(outputs, 1)[1]
print(f'test acc: {sum(pred == y) / outputs.shape[0]}')
net.train()
def fit(net, epoch=1):
net.train()
run_loss = 0
for num_epoch in range(epoch):
print(f'epoch {num_epoch}')
for i, data in enumerate(train_loader):
x, y = data[0], data[1]
outputs = net(x)
loss = criterion(outputs, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
run_loss += loss.item()
if i % 100 == 99:
print(f'[{(i+1) * 100} / 60000] loss={run_loss / 100}')
run_loss = 0
test(net)
2.2.3 设计LeNet1网络
class LeNet1(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(1, 4, [5, 5])
self.pool1 = nn.AvgPool2d([2, 2])
self.conv2 = nn.Conv2d(4, 12, [5, 5])
self.pool2 = nn.AvgPool2d([2, 2])
self.fc1 = nn.Linear(12 * 4 * 4, 10)
def forward(self, x):
x = torch.tanh(self.conv1(x))
x = self.pool1(x)
x = torch.tanh(self.conv2(x))
x = self.pool2(x)
x = x.view(-1, 12 * 4 * 4)
x = self.fc1(x)
return x
net_1 = LeNet1()
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(net_1.parameters())
fit(net_1, epoch=20)
训练结果
epoch 0 [10000 / 60000] loss=1.4352531778812407 test acc: 0.8274000287055969 [20000 / 60000] loss=0.5417736759781837 test acc: 0.8774999976158142 [30000 / 60000] loss=0.4155699674785137 test acc: 0.8949999809265137 [40000 / 60000] loss=0.3654126772284508 test acc: 0.9072999954223633 [50000 / 60000] loss=0.3255393598973751 test acc: 0.916100025177002 [60000 / 60000] loss=0.2761921301484108 test acc: 0.9244999885559082 ... epoch 19 [10000 / 60000] loss=0.03766480586025864 test acc: 0.9886999726295471 [20000 / 60000] loss=0.03968163290992379 test acc: 0.9879999756813049 [30000 / 60000] loss=0.04004986075684428 test acc: 0.9886999726295471 [40000 / 60000] loss=0.03971000960096717 test acc: 0.9872999787330627 [50000 / 60000] loss=0.041877440316602586 test acc: 0.9883000254631042 [60000 / 60000] loss=0.04341953368857503 test acc: 0.9886999726295471 经过20个epoch以后,精度达到98.9%。
3、LeNet-4
LeNet-4比LeNet-1多一个全连接层。
layer_name | kernal_size | kernal_num | padding | stride |
C1卷积层 | 5 | 4 | 0 | 1 |
S2均值池化层 | 2 | 0 | 2 | |
C3卷积层 | 5 | 16 | 0 | 1 |
S4均值池化层 | 2 | 0 | 2 | |
F5全连接层 | 120 | |||
F6全连接层 | 10 |
模型实现代码:
class LeNet4(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(1, 4, [5, 5])
self.pool1 = nn.AvgPool2d([2, 2],stride=2)
self.conv2 = nn.Conv2d(4, 16, [5, 5])
self.pool2 = nn.AvgPool2d([2, 2],stride=2)
self.fc1 = nn.Linear(16 * 4 * 4, 120)
self.fc2 = nn.Linear(120,10)
def forward(self, x):
x = torch.tanh(self.conv1(x))
x = self.pool1(x)
x = torch.tanh(self.conv2(x))
x = self.pool2(x)
x = x.view(-1, 16 * 4 * 4)
x = self.fc1(x)
x = self.fc2(x)
return x
net_1 = LeNet4()
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(net_1.parameters())
fit(net_1, epoch=20)
训练结果:
训练结果能达到100%。
4、LeNet-5
4.1 模型结构
从上图中可以看出,LeNet-5网络由7层构成:2个卷积层,2个下采样层,3个全连接层。
各个层的参数配置如下表所示。
其中,核尺寸:5*5*1/1,6代表:卷积核尺寸为5*5*1, 步长为1,个数为6。
输出参数的计算公式为:
比如第1层,输入图片尺寸为32*32,也就是input_size=32,kernel_size=5,padding=0,stride=1,带入上式,计算出output_size=28,通道数为6。具体过程参考文献[2]的内容。
4.2、pytorch的代码实现
代码的实现主要参考李沐的《动手学深度学习》教材上的代码[5]:
setp1:导入需要的库
#导入需要的库
import torch
from torch import nn
from d2l import torch as d2l #pip install d2l
导入需要的库,由于d2l中已经有数据集,所以导入这三个库就行了。
step2:建立网络结构
下图对网络结构进行了简化,编写程序的时候可以按照下图逐个输入即可。
#编写网络结构,根据LeNet的网络结构编写
#sequential是一个序列容器,模块将按照在构造函数中传递的顺序被添加到里面,
#最后返回最后一个模块的输出
net=nn.Sequential(
nn.Conv2d(in_channels=1,out_channels=6,kernel_size=5,padding=2), #第1个卷积层
nn.Sigmoid(), #sigmoid激活层
nn.AvgPool2d(kernel_size=2,stride=2), #平均池化层
nn.Conv2d(in_channels=6,out_channels=16,kernel_size=5),#第2个卷积层
nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2,stride=2),
#三个全连接层
nn.Flatten(), #将连续的几个维度展平成一个tensor
nn.Linear(in_features=16*5*5,out_features=120),
nn.Sigmoid(),
nn.Linear(in_features=120,out_features=84),
nn.Sigmoid(),
nn.Linear(in_features=84,out_features=10) #因为是10个类,所以最后输出特征为10
)
模型修改的时候,就可以修改网络结构。
sttep3:查看各层是否满足要求
#随机产生一个单通道的数据,大小为28*28,数据格式为float32
X=torch.rand(size=(1,1,28,28),dtype=torch.float32)
for layer in net:
X=layer(X)
print(layer.__class__.__name__,'output shape:\t',X.shape)
打印出来的层为:
step4:导入数据集
batch_size=256
train_iter,test_iter=d2l.load_data_fashion_mnist(batch_size=batch_size)
setp5:定义计算精度函数和训练函数
gpu计算精度函数:
def evaluate_accuracy_gpu(net, data_iter, device=None): #@save
"""使用GPU计算模型在数据集上的精度"""
if isinstance(net, nn.Module):
net.eval() # 设置为评估模式
if not device:
device = next(iter(net.parameters())).device
# 正确预测的数量,总预测的数量
metric = d2l.Accumulator(2)
with torch.no_grad():
for X, y in data_iter:
if isinstance(X, list):
# BERT微调所需的(之后将介绍)
X = [x.to(device) for x in X]
else:
X = X.to(device)
y = y.to(device)
metric.add(d2l.accuracy(net(X), y), y.numel())
return metric[0] / metric[1]
训练函数:
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):
"""用GPU训练模型(在第六章定义)"""
def init_weights(m):
if type(m) == nn.Linear or type(m) == nn.Conv2d:
nn.init.xavier_uniform_(m.weight)
net.apply(init_weights)
print('training on', device)
net.to(device)
optimizer = torch.optim.SGD(net.parameters(), lr=lr)
loss = nn.CrossEntropyLoss()
animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
legend=['train loss', 'train acc', 'test acc'])
timer, num_batches = d2l.Timer(), len(train_iter)
for epoch in range(num_epochs):
# 训练损失之和,训练准确率之和,样本数
metric = d2l.Accumulator(3)
net.train()
for i, (X, y) in enumerate(train_iter):
timer.start()
optimizer.zero_grad()
X, y = X.to(device), y.to(device)
y_hat = net(X)
l = loss(y_hat, y)
l.backward()
optimizer.step()
with torch.no_grad():
metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])
timer.stop()
train_l = metric[0] / metric[2]
train_acc = metric[1] / metric[2]
if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
animator.add(epoch + (i + 1) / num_batches,
(train_l, train_acc, None))
test_acc = evaluate_accuracy_gpu(net, test_iter)
animator.add(epoch + 1, (None, None, test_acc))
print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, '
f'test acc {test_acc:.3f}')
print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec '
f'on {str(device)}')
step6:训练模型
lr, num_epochs = 0.9, 10
train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
因为模型数据每次都会有所偏差,所以代码运行4次的结果:
从结果可以看出,loss值0.46-0.8之间,训练集精度在0.82左右,变化很小。测试集从0.7-0.82之间变化,变化值还是比较大。而且普遍表现出来数据集在训练集上表现好,测试集上表现差的情况,有过拟合现象。
为了提高精度,做一些修改。
5、模型的修改和测试
5.1、将两个平均池化层修改为最大池化层
net = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(),
# nn.AvgPool2d(kernel_size=2, stride=2),
nn.MaxPool2d(kernel_size=2,stride=2),
nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(),
# nn.AvgPool2d(kernel_size=2, stride=2),
nn.MaxPool2d(kernel_size=2,stride=2),
nn.Flatten(),
nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(),
nn.Linear(120, 84), nn.Sigmoid(),
nn.Linear(84, 10))
输出4次结果:
从结果可以看出,loss变化浮动比较大,测试集上数据有所改善,但是还是有过拟合现象。
5.2、将激活函数改为ReLu函数,lr=0.1,epoch=20
net = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.ReLU(),
# nn.AvgPool2d(kernel_size=2, stride=2),
nn.MaxPool2d(kernel_size=2,stride=2),
nn.Conv2d(6, 16, kernel_size=5), nn.ReLU(),
# nn.AvgPool2d(kernel_size=2, stride=2),
nn.MaxPool2d(kernel_size=2,stride=2),
nn.Flatten(),
nn.Linear(16 * 5 * 5, 120), nn.ReLU(),
nn.Linear(120, 84), nn.ReLU(),
nn.Linear(84, 10))
为了防止不收敛,将学习率由0.9改为0.1,epoch由10改为20.
输出结果:
从结果可以看出,loss变化很明显,由0.4到0.2左右了。训练集精度达到了0.9以上,测试集精度也到了0.88左右。
5.3、增加卷积层
net = nn.Sequential(
nn.Conv2d(1, 8, kernel_size=5, padding=2), nn.ReLU(),
nn.AvgPool2d(kernel_size=2, stride=2),
# nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(8, 16, kernel_size=3, padding=1), nn.ReLU(),
nn.AvgPool2d(kernel_size=2, stride=2),
# nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(16, 32, kernel_size=3, padding=1), nn.ReLU(),
nn.AvgPool2d(kernel_size=2, stride=2),
# nn.MaxPool2d(kernel_size=2, stride=2),
nn.Flatten(),
nn.Linear(32 * 3 * 3, 128), nn.Sigmoid(),
nn.Linear(128, 64), nn.Sigmoid(),
nn.Linear(64, 32), nn.Sigmoid(),
nn.Linear(32, 10)
)
输出结果:
从结果可以看出,加上卷积层的效果会变差。
6、用手写字体MNIST测试
拿最好的结果进行训练,导入MNIST数据集进行测试。
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
batch_size=256
trans = transforms.ToTensor()
mnist_train=datasets.MNIST(root='./data/',train=True,transform=trans,download=True)
# mnist_train = datasets.MNIST(root="./data", train=True, transform=trans, download=True)
mnist_test = datasets.MNIST(root="./data", train=False, transform=trans, download=True)
train_iter = torch.utils.data.DataLoader(mnist_train, batch_size, shuffle=True,num_workers=0)
test_iter = torch.utils.data.DataLoader(mnist_test, batch_size, shuffle=True,num_workers=0)
结果非常好,已经达到99%。
参考文献:
[3] 从0开始撸代码--手把手教你搭建LeNet-5网络模型
[6] 采样层介绍