简单的神经网络是串行的结构;非串行的网络结构具有一定的应用场景。
复杂网络会出现严重的代码冗余:GoogLeNet
C语言等:函数/类的方式。
Pytorch:通过将不同的神经层封装成一个块。
Inception Model:可以实现对多种超参数的选择,例如Kernel的大小,随着训练的进行,效果好的卷积核所在的层权重会越来越高。
不同的路径,Batch一定相同,要求输出的时候W与H必须相同,通道数可以不同,通过Concatenate操作,将不同的输出的张量拼接起来。
1*1的卷积核的作用:对不同的通道取权重,将不同通道的信息融合起来。在一次卷积的过程中,多个通道会合成一个通道,这个工作重复n次,即可输出n个通道。而这n个通道中的每个通道都是原来输入的通道的信息融合。
运算复杂度=卷积核的大小*图像大小*输入通道数*输出通道数
通过1*1的卷积可以使得通道数远远降低,使得运算复杂度降低。
因此这样的卷积核又称为Network in Network
Inception的类构造:
class InceptionA(nn.Module):
def __init__(self, in_channels): #输入通道可以变化
super(InceptionA, self).__init__()
self.branch1x1 = nn.Conv2d(in_channels, 16, kernel_size=1) #将这个函数声明为1*1
#的卷积,在forward调用
self.branch5x5_1 = nn.Conv2d(in_channels,16, kernel_size=1) #这里的通道分了两块
self.branch5x5_2 = nn.Conv2d(16, 24, kernel_size=5, padding=2) #第一块输出是16通道
#因此第二块的输入就是16,同时输出是24,利用padding扩展图像,使得图像输出大小不变
self.branch3x3_1 = nn.Conv2d(in_channels, 16, kernel_size=1) #这一条分支有三个步骤
self.branch3x3_2 = nn.Conv2d(16, 24, kernel_size=3, padding=1)
self.branch3x3_3 = nn.Conv2d(24, 24, kernel_size=3, padding=1)
self.branch_pool = nn.Conv2d(in_channels, 24, kernel_size=1) #池化分支的定义,给出
#输入输出的大小和一些参数,先声明,再forward里面调用。
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) #forward都是在调用init中
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) #先做一个平均池化
#再调用自己的函数,做1*1的卷积
outputs = [branch1x1, branch5x5, branch3x3, branch_pool]
return torch.cat(outputs, dim=1) #将输出按照1拼到一起,这个维度是由通道决定的
#张量的维度是[Batch Channel W H]按照Channel拼起来 就应该是第一个维度
网络的类的构造:
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) #声明了两个调用 输入分别是10
self.incep2 = InceptionA(in_channels=20) #和20
self.mp = nn.MaxPool2d(2) #声明最大池化层和全连接
self.fc = nn.Linear(1408, 10)
def forward(self, x):
in_size = x.size(0) #对函数的调用,先选出了输入的维度
x = F.relu(self.mp(self.conv1(x))) #对输入的x先做了一个卷积、池化和relu
x = self.incep1(x) #cov1的卷积层输出一定是10个通道
#卷积层的输出作为incep1的输入,无论输入通道是多少,输出通道总是88,这是由incep各个分支的
#通道数加起来决定的
x = F.relu(self.mp(self.conv2(x))) #同样的这个卷积层输入应当是88通道输出是20
x = self.incep2(x) #这里输入通道数是20,输出又变成了88
x = x.view(in_size, -1) #池化层减小了图片的大小,用view把张量展开成
#向量
x = self.fc(x) #再进入全连接做一个分类
return x
用这个替换上一讲的卷积神经网络,就能输出结果。
Residual Network:
梯度消失:当网络层数过多的时候,反向传播过程中,梯度若乘以一个小于1的数字,其值会越来越小,导致反向传播的梯度消失,无法到达最优点。可以利用逐层训练的方法进行训练,训练好一层后锁定,继续训练下一层,但是层数过多,十分复杂。
跳连接:在经过全连接层等后,先加上输入,再进行激活,这样就可以在原来的梯度上加上1,防止梯度消失。
这样的层,要求输入和输出的维度完全一致,才可以加起来。
class ResidualBlock(nn.Module):
def _init_ (self,channels):
super(ResidualBlock, self)._init_()
self.channels = channels
self.conv1 = nn.Conv2d(channels, channels,
kernel_size=3, padding=1)#卷积层输入和输出的通道保持一致,
#padding保证图像大小不变
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.mp = nn.MaxPool2d(2)
self.rblock1 = ResidualBlock(16)
self.rblock2 = ResidualBlock(32)
self.fc = nn.Linear(512, 10)
def forward(self, x):
in_size = x.size(0)
x = self.mp(F.relu(self.conv1(x)))
x = self.rblock1(x)
x = self.mp(F.relu(self.conv2(x)))
x = self.rblock2(x)
x = x.view(in_size, -1)
x = self.fc(x)
更多的RNN网络
He K, Zhang X, Ren S, et al. Identity Mappings in Deep Residual Lecture 11 Networks[C]
Huang G, Liu Z, Laurens V D M, et al. Densely Connected Convolutional Networks[J].2016: 2261-2269.
深度学习“花书”