LeNet-5网络结构
LeNet-5是卷积神经网络模型的早期代表,它由LeCun在1998年提出。该模型采用顺序结构,主要包括7层(2个卷积层、2个池化层和3个全连接层),卷积层和池化层交替排列。以mnist手写数字分类为例构建一个LeNet-5模型。每个手写数字图片样本的宽与高均为28像素,样本标签值是0~9,代表0至9十个数字。
图1. LeNet-5模型
每个手写数字图片样本的宽与高均为28像素,样本标签值是0~9,代表0至9十个数字。
图1. LeNet-5模型
下面详细解析LeNet-5模型的正向传播过程。
(1)卷积层C1
C1层的输入数据形状大小为R1×28×28,表示通道数量为1,行与列的大小都为28。输出数据形状大小为R 6 × 24 × 24 表示通道数量为6,行与列维都为24。
卷积核。L1层的卷积核形状大小R 6 × 1 × 5 × 5 为,偏置项形状大小为6。
这里有两个问题很关键:一是,为什么通道数从1变成了6呢?原因是模型的卷积层L1设定了6个卷积核,每个卷积核都与输入数据发生运算,最终分别得到6组数据。二是,为什么行列大小从28变成了24呢?原因是每个卷积核的行维与列维都为5,卷积核(5×5)在输入数据(28×28)上移动,且每次移动步长为1,那么输出数据的行列大小分别为28-5+1=24。
(2)池化层S1
L2层的输入数据大小要和L1层的输出数据大小保持一致。输入数据形状大小为R 6 × 24 × 24 ,表示通道数量为6,行与列的大小都为24。L2层的输出数据形状大小为R 6 × 12 × 12 表示通道数量为6,行与列维都为12。
为什么行列大小从24变成了12呢?原因是池化层中的过滤器形状大小为2×2,其在输入数据(24×24)上移动,且每次移动步长(跨距)为2,每次选择4个数(2×2)中最大值作为输出,那么输出数据的行列大小分别为24÷2=12。
(3)卷积层C2
L3层的输入数据形状大小为R6×12×12,表示通道数量为6,行与列的大小都为12。L3层的输出数据形状大小为R16×8×8,表示通道数量为16,行与列维都为8。
卷积核。L3层的卷积核形状大小为Rm×16×6×5×5,偏置项形状大小为16。
(4)池化层S2
L4层的输入数据形状大小与L3层的输出数据大小一致。L4层的输入数据形状大小为R16×8×8,表示通道数量为16,行与列的大小都为8。L4层的输出数据形状大小为R 16 × 4 × 4 表示通道数量为16,行与列维都为4。
(5)卷积层C3
由于L5层是线性层,其输入大小为一维,所以需要把L4层的输出数据大小进行重新划分。L4层的输出形状大小为R16×4×4,则L5层的一维输入形状大小为16×4×4=256。L4层的一维输出大小为120。
(6)线性层F1
L6层的输入特征数量为120。L6层的输出特征数量为84。
(7)线性层F2
L7层的输入特征数量为84。L7层的输出特征数量为10。
由于是分类问题,我们选择交叉熵损失函数。交叉熵主要用于衡量估计值与真实值之间的差距。交叉熵值越小,模型预测效果越好。
定义好了正向传播过程之后,接着随机化初始参数,然后便可以计算出每层的结果,每次将得到m×10的矩阵作为预测结果,其中m是小批量样本数。接下来进行反向传播过程,预测结果与真实结果之间肯定存在差异,以缩减该差异作为目标,计算模型参数梯度。进行多轮迭代,便可以优化模型,使得预测结果与真实结果之间更加接近。
1. 数据的下载
from torchvision.datasets import MNIST import torch import torchvision.transforms as transforms train_dataset=MNIST(root="./data/",train=True,transform=transforms.ToTensor(),download=True) test_dataset=MNIST(root="./data/",train=False,transform=transforms.ToTensor())
len(train_dataset),len(test_dataset)
(60000, 10000)
train_dataset[0][0].shape
torch.Size([1, 28, 28])
train_dataset[0][0].shape
torch.Size([1, 28, 28])
train_dataset[0][1]
5
2. 定义模型
from torch import nn
nn.Conv2d?
class Lenet5(nn.Module): def __init__(self): super(Lenet5,self).__init__() #1+ 28-5/(1)==24 self.features=nn.Sequential( #定义第一个卷积层 nn.Conv2d(in_channels=1,out_channels=6,kernel_size=(5,5),stride=1), nn.ReLU(), nn.AvgPool2d(kernel_size=2,stride=2), #6*12*12 #定义第二个卷积层 nn.Conv2d(in_channels=6,out_channels=16,kernel_size=(5,5),stride=1), #1+12-5/(1)=16*8*8 nn.ReLU(), nn.MaxPool2d(kernel_size=2,stride=2), #1+(8-2)/(2)=4 #16*4*4 ) #定义全连接层 self.classfier=nn.Sequential(nn.Linear(in_features=256,out_features=120), nn.ReLU(), nn.Linear(in_features=120,out_features=84), nn.ReLU(), nn.Linear(in_features=84,out_features=10), ) def forward(self,x): x=self.features(x) x=torch.flatten(x,1) result=self.classfier(x) return result
3. 新建模型
model=Lenet5() device=torch.device("cuda:0" if torch.cuda.is_available() else "cpu") model=model.to(device)
4. 从数据集中分批量读取数据
from torch.utils.data import DataLoader
DataLoader?
from torch.utils.data import DataLoader batch_size=32 train_loader=DataLoader(dataset=train_dataset,batch_size=batch_size,shuffle=True) test_loader=DataLoader(dataset=test_dataset,batch_size=batch_size,shuffle=False)
5. 定义损失函数
from torch import optim
loss_fun=nn.CrossEntropyLoss() loss_lst=[]
6. 定义优化器
optimizer=optim.SGD(params=model.parameters(),lr=0.001,momentum=0.9)
7. 开始训练
import time start_time=time.time() #训练的迭代次数 for i in range(10): loss_i=0 for j,(batch_data,batch_label) in enumerate(train_loader): #清空优化器的梯度 optimizer.zero_grad() #模型前向预测 pred=model(batch_data) loss=loss_fun(pred,batch_label) loss_i+=loss loss.backward() optimizer.step() if (j+1)%200==0: print("第%d次训练,第%d批次,损失为%.2f"%(i,j,loss_i/200)) loss_i=0 end_time=time.time() print("共训练了%d 秒"%(end_time-start_time))
第0次训练,第199批次,损失为2.30 第0次训练,第399批次,损失为2.29 第0次训练,第599批次,损失为2.28 第0次训练,第799批次,损失为2.23 第0次训练,第999批次,损失为1.86 第0次训练,第1199批次,损失为0.81 第0次训练,第1399批次,损失为0.55 第0次训练,第1599批次,损失为0.46 第0次训练,第1799批次,损失为0.40 第1次训练,第199批次,损失为0.33 第1次训练,第399批次,损失为0.29 第1次训练,第599批次,损失为0.27 第1次训练,第799批次,损失为0.28 第1次训练,第999批次,损失为0.25 第1次训练,第1199批次,损失为0.22 第1次训练,第1399批次,损失为0.23 第1次训练,第1599批次,损失为0.22 第1次训练,第1799批次,损失为0.19 第2次训练,第199批次,损失为0.17 第2次训练,第399批次,损失为0.17 第2次训练,第599批次,损失为0.16 第2次训练,第799批次,损失为0.17 第2次训练,第999批次,损失为0.15 第2次训练,第1199批次,损失为0.15 第2次训练,第1399批次,损失为0.14 第2次训练,第1599批次,损失为0.14 第2次训练,第1799批次,损失为0.13 第3次训练,第199批次,损失为0.12 第3次训练,第399批次,损失为0.13 第3次训练,第599批次,损失为0.12 第3次训练,第799批次,损失为0.12 第3次训练,第999批次,损失为0.13 第3次训练,第1199批次,损失为0.12 第3次训练,第1399批次,损失为0.10 第3次训练,第1599批次,损失为0.11 第3次训练,第1799批次,损失为0.10 第4次训练,第199批次,损失为0.11 第4次训练,第399批次,损失为0.10 第4次训练,第599批次,损失为0.10 第4次训练,第799批次,损失为0.08 第4次训练,第999批次,损失为0.09 第4次训练,第1199批次,损失为0.09 第4次训练,第1399批次,损失为0.10 第4次训练,第1599批次,损失为0.08 第4次训练,第1799批次,损失为0.08 第5次训练,第199批次,损失为0.09 第5次训练,第399批次,损失为0.07 第5次训练,第599批次,损失为0.09 第5次训练,第799批次,损失为0.08 第5次训练,第999批次,损失为0.08 第5次训练,第1199批次,损失为0.08 第5次训练,第1399批次,损失为0.08 第5次训练,第1599批次,损失为0.07 第5次训练,第1799批次,损失为0.08 第6次训练,第199批次,损失为0.08 第6次训练,第399批次,损失为0.07 第6次训练,第599批次,损失为0.07 第6次训练,第799批次,损失为0.07 第6次训练,第999批次,损失为0.08 第6次训练,第1199批次,损失为0.07 第6次训练,第1399批次,损失为0.07 第6次训练,第1599批次,损失为0.07 第6次训练,第1799批次,损失为0.08 第7次训练,第199批次,损失为0.07 第7次训练,第399批次,损失为0.07 第7次训练,第599批次,损失为0.07 第7次训练,第799批次,损失为0.06 第7次训练,第999批次,损失为0.07 第7次训练,第1199批次,损失为0.06 第7次训练,第1399批次,损失为0.06 第7次训练,第1599批次,损失为0.07 第7次训练,第1799批次,损失为0.06 第8次训练,第199批次,损失为0.05 第8次训练,第399批次,损失为0.05 第8次训练,第599批次,损失为0.06 第8次训练,第799批次,损失为0.06 第8次训练,第999批次,损失为0.07 第8次训练,第1199批次,损失为0.06 第8次训练,第1399批次,损失为0.07 第8次训练,第1599批次,损失为0.06 第8次训练,第1799批次,损失为0.05 第9次训练,第199批次,损失为0.05 第9次训练,第399批次,损失为0.05 第9次训练,第599批次,损失为0.05 第9次训练,第799批次,损失为0.05 第9次训练,第999批次,损失为0.05 第9次训练,第1199批次,损失为0.06 第9次训练,第1399批次,损失为0.05 第9次训练,第1599批次,损失为0.05 第9次训练,第1799批次,损失为0.05 共训练了148 秒
8. 测试和保存模型
len(test_dataset)
10000
correct=0 for batch_data,batch_label in test_loader: pred_test=model(batch_data) pred_result=torch.max(pred_test.data,1)[1] correct+=(pred_result==batch_label).sum() print("准确率为:%.2f%%"%(correct/len(test_dataset)))
准确率为:0.98%
#保存模型 torch.save(model, './model-cifar10.pth')
9. 手写体图片的可视化
from torchvision import transforms as T
import torch
import numpy as np from PIL import Image normalize = T.Normalize(mean=[127.5, 127.5, 127.5], std=[127.5, 127.5, 127.5]) arr1=np.random.rand(300, 320, 3) * 255 fake_img = T.ToPILImage()(arr1.astype("uint8")) fake_img.show() fake_img = normalize(T.ToTensor()(arr1)) print(fake_img.shape) print(fake_img)
torch.Size([3, 300, 320]) tensor([[[-0.9172, -0.8087, 0.5650, ..., 0.5297, 0.8186, 0.3312], [-0.3795, -0.7144, 0.7482, ..., 0.7777, 0.0563, 0.9862], [ 0.4713, 0.1514, 0.1433, ..., 0.1218, 0.5960, 0.0122], ..., [ 0.7886, -0.8431, 0.2048, ..., 0.0880, 0.8566, -0.7309], [-0.5249, -0.2610, 0.6604, ..., -0.5265, -0.8607, 0.8407], [-0.0764, -0.6659, -0.7282, ..., 0.6114, -0.8531, 0.8591]], [[-0.7804, -0.9011, 0.7292, ..., -0.7269, 0.4730, -0.4985], [ 0.5025, -0.9715, -0.5368, ..., -0.3784, 0.2336, -0.7914], [-0.3683, 0.5105, 0.4923, ..., 0.4562, 0.1588, 0.0781], ..., [-0.7712, 0.4029, 0.5997, ..., 0.6086, -0.6148, 0.8007], [ 0.9939, 0.0161, -0.9449, ..., -0.6050, -0.3625, 0.0129], [-0.2682, -0.1006, -0.7786, ..., 0.0569, 0.0279, -0.3509]], [[-0.9476, 0.3883, 0.4793, ..., -0.2685, 0.9854, 0.9068], [ 0.4380, 0.1821, -0.1389, ..., -0.8316, 0.5408, -0.2924], [-0.3324, -0.8534, -0.9868, ..., -0.8449, -0.3564, -0.9859], ..., [ 0.9973, 0.4672, -0.4873, ..., -0.5094, -0.6851, 0.2794], [ 0.9954, 0.8549, 0.1814, ..., -0.7077, -0.7606, 0.4524], [ 0.6209, 0.5317, -0.1966, ..., -0.8245, -0.8593, -0.1789]]], dtype=torch.float64)
len(train_dataset)
60000
train_dataset[0][0].shape
torch.Size([1, 28, 28])
import matplotlib.pyplot as plt plt.imshow(train_dataset[3][0][0],cmap="gray")
<matplotlib.image.AxesImage at 0x217dc3c6bd0>
10. 多幅图片的可视化
from matplotlib import pyplot as plt plt.figure(figsize=(20,15)) cols=10 rows=10 for i in range(0,rows): for j in range(0,cols): idx=j+i*cols plt.subplot(rows,cols,idx+1) plt.imshow(train_dataset[idx][0][0]) plt.axis('off')
import numpy as np img10 = np.stack(list(train_dataset[i][0][0] for i in range(10)), axis=1).reshape(28,280) plt.imshow(img10) plt.axis('off')
(-0.5, 279.5, 27.5, -0.5)
img100 = np.stack( tuple(np.stack (tuple(train_dataset[j*10+i][0][0] for i in range(10)), axis=1).reshape(28,280) for j in range(10)),axis=0).reshape(280,280) plt.imshow(img100) plt.axis('off')
(-0.5, 279.5, 279.5, -0.5)
思考题
- 测试集中有哪些识别错误的手写数字图片? 汇集整理并分析原因?