卷积神经网络理解(3)
1、定义
LeNet是深度学习领域的一个经典卷积神经网络模型,由Yann LeCun等人于1998年提出,被广泛应用于手写数字识别和其他图像识别任务。
LeNet的网络结构相对简单,包含两个卷积层和三个全连接层,是卷积神经网络的基础。
LeNet对于现代的图像识别任务来说可能过于简单,但其对于深度学习的发展有着重要的贡献,是深度学习领域不可或缺的一部分。
总体来看,LeNet(LeNet-5)由两个部分组成:
卷积编码器:由两个卷积层组成;
全连接层密集块:由三个全连接层组成。
输入层:接收输入的图像数据,一般为灰度图或者RGB彩色图像。
第一个卷积层:对输入进行卷积操作,使用的卷积核的大小为5x5,输出通道数为6,不使用填充,步幅为1。
第一个池化层:对第一个卷积层的输出进行下采样,使用的窗口大小为2x2,步幅为2,池化方式为平均池化或最大池化。
第二个卷积层:对第一个池化层的输出进行卷积操作,使用的卷积核的大小为5x5,输出通道数为16,不使用填充,步幅为1。
第二个池化层:对第二个卷积层的输出进行下采样,使用的窗口大小为2x2,步幅为2,池化方式为平均池化或最大池化。
全连接层1:将第二个池化层的输出展开成向量,并经过一个全连接层,输出大小为120。
全连接层2:对第一层全连接层的输出进行处理,输出大小为84。
输出层:对第二层全连接层的输出进行处理,输出大小为分类任务的类别数目。
手写数字识别的架构如图所示。
在此图示中,输入图片的尺寸为32*32,图示有误
C1层(卷积层):6@28×28 该层使用了6个卷积核,每个卷积核的大小为5×5,这样就得到了6个feature map(特征图)。 每个卷积核(5×5)与原始的输入图像(32×32)进行卷积,这样得到的feature map(特征图)大小为(32-5+1)×(32-5+1)= 28×28 S2层(下采样层,也称池化层):6@14×14 这一层主要是做池化或者特征映射(特征降维),池化单元为2×2,因此,6个特征图的大小经池化后即变为14×14。
回顾本文刚开始讲到的池化操作,池化单元之间没有重叠,在池化区域内进行聚合统计后得到新的特征值,因此经2×2池化后,每两行两列重新算出一个特征值出来,相当于图像大小减半,
因此卷积后的28×28图像经2×2池化后就变为14×14。 C3层(卷积层):16@10×10 C3层有16个卷积核,卷积模板大小为5×5。每个卷积核都与S2的6@14*14进行互关系运算,得到16个通道 与C1层的分析类似,C3层的特征图大小为(14-5+1)×(14-5+1)= 10×10 S4(下采样层,也称池化层):16@5×5 与S2的分析类似,池化单元大小为2×2,因此,该层与C3一样共有16个特征图,每个特征图的大小为5×5。 C5层(卷积层/全连接层):120 该层有120个卷积核,每个卷积核的大小仍为5×5,因此有120个特征图。由于S4层的大小为5×5,而该层的卷积核大小也是5×5,
因此特征图大小为(5-5+1)×(5-5+1)= 1×1。这样该层就刚好变成了全连接,这只是巧合,如果原始输入的图像比较大,则该层就不是全连接了。 F6层(全连接层):84 F6层有84个单元,之所以选这个数字的原因是来自于输出层的设计,对应于一个7×12的比特图。 OUTPUT层(输出层):10 Output层也是全连接层,共有10个节点,分别代表数字0到9。如果第i个节点的值为0,则表示网络识别的结果是数字i。
2、实现
模型
1 import torch
2 from torch import nn
3 from d2l import torch as d2l
4
5 net = nn.Sequential(
6 nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(),
7 nn.AvgPool2d(kernel_size=2, stride=2),
8 nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(),
9 nn.AvgPool2d(kernel_size=2, stride=2),
10 nn.Flatten(),
11 nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(),
12 nn.Linear(120, 84), nn.Sigmoid(),
13 nn.Linear(84, 10))
或者是
1 import torch.nn as nn
2 import torch.nn.functional as F
3
4 class LeNet(nn.Module):
5 def __init__(self):
6 super(LeNet, self).__init__()
7 self.conv1 = nn.Conv2d(3, 16, 5) # in_channels=3 out_channels=16 kernel=5
8 self.pool1 = nn.MaxPool2d(2, 2)
9 self.conv2 = nn.Conv2d(16, 32, 5)
10 self.pool2 = nn.MaxPool2d(2, 2)
11 self.fc1 = nn.Linear(32*5*5, 120)
12 self.fc2 = nn.Linear(120, 84)
13 self.fc3 = nn.Linear(84, 10)
14 # 调用上面定义的函数
15 def forward(self, x):
16 x = F.relu(self.conv1(x)) # input(3, 32, 32) output(16, 28, 28)
17 x = self.pool1(x) # output(16, 14, 14)
18 x = F.relu(self.conv2(x)) # output(32, 10, 10)
19 x = self.pool2(x) # output(32, 5, 5)
20 x = x.view(-1, 32*5*5) # output(32*5*5)
21 x = F.relu(self.fc1(x)) # output(120)
22 x = F.relu(self.fc2(x)) # output(84)
23 x = self.fc3(x) # output(10)
24 return x
训练
1 import torch
2 import torchvision
3 import torch.nn as nn
4
5 from model import LeNet
6 import torch.optim as optim
7 import torchvision.transforms as transforms
8 from torch.utils.data import DataLoader
9
10 def main():
11 transform = transforms.Compose(
12 [transforms.ToTensor(),
13 transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
14
15 # 50000张训练图片
16 # 第一次使用时要将download设置为True才会自动去下载数据集
17 train_set = torchvision.datasets.CIFAR10(root='./data', train=True,
18 download=False, transform=transform)
19 train_loader = DataLoader(train_set, batch_size=36,
20 shuffle=True, num_workers=0)
21
22 # 10000张验证图片
23 # 第一次使用时要将download设置为True才会自动去下载数据集
24 val_set = torchvision.datasets.CIFAR10(root='./data', train=False,
25 download=False, transform=transform)
26 val_loader = DataLoader(val_set, batch_size=5000,
27 shuffle=False, num_workers=0)
28 val_data_iter = iter(val_loader)
29 val_image, val_label = val_data_iter.next()
30
31 # classes = ('plane', 'car', 'bird', 'cat',
32 # 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
33
34 # 定义模型和损失函数
35 net = LeNet()
36 loss_function = nn.CrossEntropyLoss()
37
38 # 定义优化器
39 optimizer = optim.Adam(net.parameters(),lr=0.001)
40
41 # 对训练集迭代5次
42 epochs = 5
43 for epoch in range(epochs):
44 running_loss = 0
45 for step,data in enumerate(train_loader,start=0): # step从start开始
46 inputs,labels = data
47 # 梯度清零
48 optimizer.zero_grad()
49
50 outputs = net(inputs)
51 # 计算损失函数
52 loss = loss_function(outputs,labels)
53 # 反向传播并更新参数
54 loss.backward()
55 optimizer.step()
56
57 running_loss += loss.item()
58 if step%500 == 499:
59 with torch.no_grad():
60 outputs = net(val_image)
61 predict_y = torch.max(outputs,dim=1)[1]
62 accuracy = (predict_y==val_label).sum().item() / val_label.size(0)
63 print('[%d %3d] train_loss: %.3f test_accuracy: %.3f' %
64 (epoch+1,step+1,running_loss/500,accuracy))
65 running_loss = 0
66
67 print('Finished Training')
68 save_path = './Lenet.pth'
69 torch.save(net.state_dict(),save_path)
70 if __name__ == '__main__':
71 main()
这段代码是一个基于LeNet网络结构进行CIFAR10分类的训练代码,主要步骤如下:
导入必要的Python包,包括PyTorch中的torch、torchvision、torch.nn等模块,以及用于数据加载的DataLoader。
定义数据预处理,包括将图像转换为Tensor格式、标准化处理等操作。
加载训练集和验证集,使用DataLoader将数据分批次读取。
定义LeNet网络结构和损失函数,此处使用交叉熵损失函数。
定义Adam优化器。
进行训练,共进行5个epoch,每个epoch对训练集进行一次完整的遍历,每次遍历对数据分批次读取并进行网络的前向计算、反向传播、参数更新等操作。
每500个batch输出一次训练损失和测试精度。
保存训练好的模型参数。
测试
1 import torch
2 import torchvision.transforms as transforms
3 from PIL import Image
4
5 from model import LeNet
6
7
8 def main():
9 transform = transforms.Compose(
10 [transforms.Resize((32, 32)), # 对输入的图片尺寸进行调整
11 transforms.ToTensor(), # Convert a ``PIL Image`` or ``numpy.ndarray`` to tensor. (H x W x C)->(C x H x W)
12 transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
13
14 classes = ('plane', 'car', 'bird', 'cat',
15 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
16
17 net = LeNet()
18 net.load_state_dict(torch.load('Lenet.pth')) # 加载模型
19
20 im = Image.open('data/plane.jpg')
21 im = transform(im) # [C, H, W]
22 # 增加一个维度,batch
23 im = torch.unsqueeze(im, dim=0) # [N, C, H, W] 增加一个batch维度
24
25 with torch.no_grad():
26 outputs = net(im)
27 predict = torch.max(outputs, dim=1)[1].numpy()
28 print(classes[int(predict)])
29
30 if __name__ == '__main__':
31 main()