avatar

【Pytorch基础】卷积神经网络进阶(GoogLeNet)

回顾

 之前我们谈及的卷积神经网络都是串行的,现在来看下更复杂的卷积神经网络以及应用函数、封装等编程思想来实现它。

GoogLeNet

 GoogLeNet是谷歌(Google)研究出来的深度网络结构,为什么不叫“GoogleNet”,而叫“GoogLeNet”,据说是为了向“LeNet”致敬,因此取名为“GoogLeNet”。

网络结构图

看到上图这么复杂的一个网络结构,显然一个一个地去定义每一层既费时又费力,而且犯了编程大忌,会出现大量重复代码。因此应该找出结构中重复出现的单元,使用函数或类将其封装使其能够复用。

Inception Module

 不难发现,上图标识的Inception Module就是一个可复用的单元。

  • AveragePolling 为平均值池化,是池化的一种
  • (n) 表示该层的输出通道大小为 n
  • 图中绿色块Concatenate用来将不同卷积过程的结果按Channel(通道)维度拼接起来,其输入要求图像的Channel可以不同但高宽要相同,可通过设置padding和stride来控制图像大小。根据上图的模块来说,若输入为一个1x28x28的tensor,且输出长宽不变,则输出为3*24+16=88通道的28x28图像。
  • $1\times 1$卷积核及其作用:


    我们知道,卷积层可以改变通道的数量,通过1x1卷积层可以可观地减少运算规模,例如:

    • 未使用1x1卷积:一个卷积核对应一个输出通道,故需要32个5x5卷积核,又输出图像大小不变故要对输入做28x28次卷积运算,又每次卷积运算要作用于输入的所有192个通道,因此运算次数如上图所示为12042240次。
    • 使用1x1卷积:1x1卷积先将通道降为16,再去做5x5卷积可以明显降低运算量,仅为之前的1/10。因此,其在深度学习中非常有用。

观察Inception的结构,它由多条带有不同大小卷积核的路径构成。它的出现背景为在设计网络时我们不太清楚到底用什么样的卷积核好,因此干脆将多种卷积核都集成到一个模块里,这样一来在训练过程中若某卷积核对结果影响较大其权重就会自动增加而其他卷积核的权重就会变得相对较小。简单说来,就是该模块提供了多种候选卷积核,已让模型通过学习自己选择最优的一个。

Inception Module的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Inception(torch.nn.Module):
def __init__(self, in_channels):
super(Inception,self).__init__()
self.branch1x1 = torch.nn.Conv2d(in_channels,16,kernel_size=1)
self.branch5x5_1 = torch.nn.Conv2d(in_channels,16,kernel_size=1)
self.branch5x5_2 = torch.nn.Conv2d(16,24,kernel_size=5,padding=2)
self.branch3x3_1 = torch.nn.Conv2d(in_channels,16,kernel_size=1)
self.branch3x3_2 = torch.nn.Conv2d(16,24,kernel_size=3,padding=1)
self.branch3x3_3 = torch.nn.Conv2d(24,24,kernel_size=3,padding=1)
self.branch_pool = torch.nn.Conv2d(in_channels,24,kernel_size=1)

def forward(self,x):
branch1x1 = self.branch1x1(x)

branch5x5 = self.branch5x5_1(x)
branch5x5 = self.branch5x5_2(branch5x5)

branch3x3 = self.branch3x3_1(x)
branch3x3 = self.branch3x3_2(branch3x3)
branch3x3 = self.branch3x3_3(branch3x3)

branch_pool = F.avg_pool2d(x, kernel_size=3,stride=1,padding=1)
branch_pool = self.branch_pool(branch_pool)

outputs = [branch1x1,branch3x3,branch5x5,branch_pool]
return torch.cat(outputs,dim=1)

应用到手写数字识别网络中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import torch
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F
import torch.optim as optim

batch_size = 64

# 对数据的处理:神经网络希望输入的数据最好比较小,最好处于(-1,1)内,最好符合正态分布。
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307, ),(0.3081, ))
])

# 训练集
train_dataset = datasets.MNIST(root='./dataset/mnist/',train=True,download=True,transform=transform)
train_loader = DataLoader(train_dataset,shuffle=True,batch_size=batch_size)

# 测试集
test_dataset = datasets.MNIST(root='./dataset/mnist/',train=False,download=True,transform=transform)
test_loader = DataLoader(test_dataset,shuffle=False,batch_size=batch_size)

# 将Inception模块应用于网络中
class Net(torch.nn.Module):
def __init__(self):
super(Net,self).__init__()
self.conv1 = torch.nn.Conv2d(1,10,kernel_size=5)
self.conv2 = torch.nn.Conv2d(88,20,kernel_size=5)

self.incep1 = Inception(in_channels=10)
self.incep2 = Inception(in_channels=20)

self.mp = torch.nn.MaxPool2d(2)
self.fc1 = torch.nn.Linear(1408,512) # 由下面计算得 88 * 4 * 4 = 1408
self.fc2 = torch.nn.Linear(512,256)
self.fc3 = torch.nn.Linear(256,10)

def forward(self,x):
in_size = x.size(0) # 计算batchsize即样本数
x = F.relu(self.mp(self.conv1(x))) #28 -> 12, c = 10
x = self.incep1(x) # 12 -> 12 ,c = 88
x = F.relu(self.mp(self.conv2(x))) #12 -> 4 , c = 20
x = self.incep2(x) # 4->4 , c = 88
x = x.view(in_size,-1) # 转为线性层输入格式
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
return self.fc3(x)


model = Net()

# 损失函数和优化器
criterion = torch.nn.CrossEntropyLoss() # 交叉熵损失
optimizer = optim.SGD(model.parameters(),lr=0.001,momentum=0.5) # 随机梯度下降,带冲量

e_list = []
l_list = []
running_loss = 0.0
# 单轮训练的函数
def train(epoch):
running_loss = 0.0
Loss = 0.0
for batch_idx, data in enumerate(train_loader,0):
inputs, target = data
optimizer.zero_grad()

# 前馈计算
outputs = model(inputs)
# 损失计算
loss = criterion(outputs,target)
# 反馈计算
loss.backward()
optimizer.step()

running_loss += loss.item() # 累加损失
Loss += loss.item()
# 每300次迭代(minibatch)训练更新,计算一次平均损失
if batch_idx % 300 == 299:
print('[%d, %5d] loss: %.3f' % (epoch + 1, batch_idx + 1, running_loss / 300))
running_loss = 0.0
e_list.append(epoch)
l_list.append(running_loss/300)

def test():
correct = 0 # 预测正确数
total = 0 # 总样本数
with torch.no_grad(): # 声明不计算梯度
for data in test_loader:
images, labels = data
outputs = model(images)
_, predicted = torch.max(outputs.data, dim=1) # 按列找最大值的下标,返回两个:最大值,下标
total += labels.size(0) # labels矩阵的行数
correct += (predicted == labels).sum().item() # 相等为1,否则为0
print('Accuracy on test set: %d %%' % (100 * correct / total))


# 训练
if __name__ == '__main__':
for epoch in range(10):
train(epoch)
test()

收敛曲线

1
2
3
4
5
6
7
8
9
10
import matplotlib.pyplot as plt

plt.figure(figsize=(8,5))

plt.xlabel('epoch')
plt.ylabel('loss')

plt.plot(e_list,l_list,color='green')

plt.show()

文章作者: Liam
文章链接: https://www.ccyh.xyz/p/7c50.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Liam's Blog
ღ喜欢记得五星好评哦~
打赏
  • 微信
    微信
  • 支付寶
    支付寶

评论