基础部分在另一篇文章
之前学的卷积神经网络或者其他的 都是线性的,前一层的输出是下一层的输入。像下面这样串起来的。
而实际情况远比这复杂的多。
下图的网络结构叫 GoogLeNet。
蓝色 卷积层, 红色 池化层, 黄色,softmax ,绿色 是拼接层。
可以看到图中有很多相同的块,可以封装到一起(叫Inception)到时候调用就行了,不用像上一节一样一层一层写。
inception 内部结构:
注意图是从下到上走的流程。
会自动测试每条路径上的参数的合适情况,赋予不同的权重。
蓝色的那个块,显然就是在测试卷积核的大小。
Concatenate : 拼接全部路径上的张量 ,变成一个张量。
Average Pooling :均值池化。
黄色的块 1*1的卷积核,主要用于改变通道数量(可大幅降低运算量)。
整个过程中 W和H 不变 ,batch不变,通道可以变。
代码解读:
将上面的图反转过来看,就是从左到右执行。
括号里面的数字表示输出的通道数。
首先 最上层,第一个分支。
粉色框的 Average polling:
第一句 一般放在init里,定义了一个卷积对象。
avg.polling对象不用self定义出来,直接调用函数就行。
Pytorch里调用函数, F.avg.pool2d() 。实现粉色框。
然后将pool好的数据给了一开始定义的卷积对象中,得到第一个分支的输出。
第二层,第二个分支,只有一个 1*1的卷积核。
定义好直接放进去算结果就行了。
第三层,第三个分支。
显然是两个卷积层,所以定义两个卷积层,然后放进去X到第一个卷积层里算,得到结果放到第二个里面去算。
padding 的设置,用之前的方法 ,要求输入和输出的通道一样,则padding = 卷积核大小 //2 5 // 2 = 2
最后一层分支。
同样定义三个卷积层,分别写上输入和输出的通道。
显然最后面少一行 branch33 = self.baranch33_3(branch3*3)
最后吧上面四个分支得到的块拼接起来:
由Concatenate负责拼接
其中 barch1x1…这几个就是上面四个分支算出来的结果。
dim =1 : 张量参数 [b,c,w,h] 沿着 c 拼接。
上面的代码整合:
模型类的设计:
看到前面两个卷积层,第一个卷积层的输出通道和第二个卷积层的输入通道没匹配上。
这是因为其中inception的问题。
在上面inception的设计中可以发现,四个分支输出通道总和为88个通道,所以第二个卷积层接受的输入值是88通道。
这里的1408是进入最后一层线性层的输入。10是最后线性层的输出,对应0-9十个数嘛。
可以在进入线性层之前进行测试,看一下当前的x张量的结构, 使用 x.shape输出一下得到结果:torch.Size([64, 88, 4, 4])
第一个是batch 为:64。 所以后面8844(CWH)就是 要输入给线性层的值,一张图像包含1408个元素。
梯度消失: 由于做的是梯度下降-反向传播。在最后更新权重的时候, w = w -α*g , 链式法则求导,吧一连串的 的梯度进行相乘,如果每一个梯度都小于1,则相乘无限趋于0,那么权重则得不到什么更新,
解决梯度消失问题:
下面目的网络结构叫 Residual 网络。
算出结果 H(x)先不做激活,先加一个X再激活。
这里 算出来的结果 h(x) 和 x必须张量维度一样,就是 通道 高度 宽度都一样 ,才能相加。
Residual 代码:
这里面 用于创建卷积层的 Conv2d里输入通道和输出通道都是一致的。
后面算的时候就是,第一层先卷积然后激活,放到第二层去卷积但是不激活,让第二层的卷积结果和输入的X相加然后再激活(参考上面的流程图)。
整体结构:
GoogLeNet 网络 完整代码
import torch
from torch import optim, nn
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F# 数据准备
batch_size = 64
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 InceptionA(torch.nn.Module):def __init__(self, in_channels):super(InceptionA, self).__init__()# 第一个分支self.branch1_pool = nn.Conv2d(in_channels, 24, kernel_size=1)# 第二个分支self.branch1x1 = nn.Conv2d(in_channels, 16, kernel_size=1)# 第三个分支self.branch5x5_1 = nn.Conv2d(in_channels, 16, kernel_size=1)self.branch5x5_2 = nn.Conv2d(16, 24, kernel_size=5, padding=2)# 第四个分支self.branch1x3_1 = nn.Conv2d(in_channels, 16, kernel_size=1)self.branch1x3_2 = nn.Conv2d(16, 24, kernel_size=3, padding=1)self.branch1x3_3 = nn.Conv2d(24, 24, kernel_size=3, padding=1)def forward(self, x):# 处理分支一branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1)branch_pool = self.branch1_pool(branch_pool)# 处理分支二branch1x1 = self.branch1x1(x)# 处理分支三branch5x5 = self.branch5x5_1(x)branch5x5 = self.branch5x5_2(branch5x5)# 处理分支四branch3x3 = self.branch1x3_1(x)branch3x3 = self.branch1x3_2(branch3x3)branch3x3 = self.branch1x3_3(branch3x3)output = [branch1x1, branch5x5, branch3x3, branch_pool] # 输出通道共88个return torch.cat(output, dim=1)# 训练模型类class Net(nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 = nn.Conv2d(1, 10, kernel_size=5)self.conv2 = nn.Conv2d(88, 20, kernel_size=5)self.incep1 = InceptionA(in_channels=10)self.incep2 = InceptionA(in_channels=20)self.mp = nn.MaxPool2d(2) # 2*2的最大池化层self.fc = nn.Linear(1408, 10) # 全连接线性层def forward(self, x):in_size = x.size(0)# 卷积池化激活 inceptionx = F.relu(self.mp(self.conv1(x)))x = self.incep1(x)x = F.relu(self.mp(self.conv2(x)))x = self.incep2(x)# 变换张量为线性向量,然后进全连接层x = x.view(in_size, -1)x = self.fc(x)return xmodel = Net()# 损失函数
criterion = torch.nn.CrossEntropyLoss()
# 优化器
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)def train(epoch):runing_loss = 0.0for i, data in enumerate(train_loader):x, y = data# 清零 正向传播 损失函数 反向传播 更新optimizer.zero_grad()y_pre = model(x)loss = criterion(y_pre, y)loss.backward()optimizer.step()runing_loss += loss.item()# 每轮训练一共训练1W个样本,这里的runing_loss是1W个样本的总损失值,要看每一个样本的平均损失值, 记得除10000print("这是第 %d轮训练,当前损失值 %.5f" % (epoch + 1, runing_loss / 10000))def test(epoch):correct = 0total = 0with torch.no_grad():for data in test_loader:x, y = datapre_y = model(x)# 这里拿到的预测值 每一行都对应10个分类,这10个分类都有对应的概率,# 我们要拿到最大的那个概率和其对应的下标。j, pre_y = torch.max(pre_y.data, dim=1) # dim = 1 列是第0个维度,行是第1个维度total += y.size(0) # 统计方向0上的元素个数 即样本个数correct += (pre_y == y).sum().item() # 张量之间的比较运算print("第%d轮测试结束,当前正确率:%d %%" % (epoch + 1, correct / total * 100))if __name__ == '__main__':for epoch in range(10):train(epoch)test(epoch)得到结果:
这是第 1轮训练,当前损失值 0.04419
第1轮测试结束,当前正确率:96 %
..
..
..
..
..
这是第 10轮训练,当前损失值 0.00308
第10轮测试结束,当前正确率:98 %
Residual 网络 完整代码:
import torch
from torch import optim, nn
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F# 数据准备
batch_size = 64
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)# Residual 网络
class ResidualBlock(nn.Module):# 该网络的全程通道数一样def __init__(self, channels):super(ResidualBlock, self).__init__()self.channels = channelsself.conv1 = nn.Conv2d(channels, channels, kernel_size=3, padding=1)self.conv2 = nn.Conv2d(channels, channels, kernel_size=3, padding=1)def forward(self, x):y = F.relu(self.conv1(x))y = self.conv2(y)return F.relu(x + y)# 训练模型类class Net(nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 = nn.Conv2d(1, 16, kernel_size=5)self.conv2 = nn.Conv2d(16, 32, kernel_size=5)# self.incep1 = ResidualBlock(in_channels=10)# self.incep2 = ResidualBlock(in_channels=10)self.rblock1 = ResidualBlock(16)self.rblock2 = ResidualBlock(32)self.mp = nn.MaxPool2d(2) # 2*2的最大池化层self.fc = nn.Linear(512, 10) # 全连接线性层def forward(self, x):in_size = x.size(0)# 卷积池化激活 inceptionx = F.relu(self.mp(self.conv1(x)))x = self.rblock1(x)x = F.relu(self.mp(self.conv2(x)))x = self.rblock2(x)# 变换张量为线性向量,然后进全连接层x = x.view(in_size, -1)x = self.fc(x)return xmodel = Net()# 损失函数
criterion = torch.nn.CrossEntropyLoss()
# 优化器
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)def train(epoch):runing_loss = 0.0for i, data in enumerate(train_loader):x, y = data# 清零 正向传播 损失函数 反向传播 更新optimizer.zero_grad()y_pre = model(x)loss = criterion(y_pre, y)loss.backward()optimizer.step()runing_loss += loss.item()# 每轮训练一共训练1W个样本,这里的runing_loss是1W个样本的总损失值,要看每一个样本的平均损失值, 记得除10000print("这是第 %d轮训练,当前损失值 %.5f" % (epoch + 1, runing_loss / 10000))def test(epoch):correct = 0total = 0with torch.no_grad():for data in test_loader:x, y = datapre_y = model(x)# 这里拿到的预测值 每一行都对应10个分类,这10个分类都有对应的概率,# 我们要拿到最大的那个概率和其对应的下标。j, pre_y = torch.max(pre_y.data, dim=1) # dim = 1 列是第0个维度,行是第1个维度total += y.size(0) # 统计方向0上的元素个数 即样本个数correct += (pre_y == y).sum().item() # 张量之间的比较运算print("第%d轮测试结束,当前正确率:%d %%" % (epoch + 1, correct / total * 100))if __name__ == '__main__':for epoch in range(10):train(epoch)test(epoch)得到结果
这是第 1轮训练,当前损失值 0.02468
第1轮测试结束,当前正确率:97 %
.
.
.
这是第 6轮训练,当前损失值 0.00327
第6轮测试结束,当前正确率:99 %
这是第 7轮训练,当前损失值 0.00292
第7轮测试结束,当前正确率:98 %
.
.
.
这是第 9轮训练,当前损失值 0.00228
第9轮测试结束,当前正确率:99 %
这是第 10轮训练,当前损失值 0.00206
第10轮测试结束,当前正确率:98 %
可以看到结果中间有几次的准确率达到了99% ,所以不是训练越多越好,可能过拟合。