笔记:卷积神经网络

从全连接到卷积

在这里插入图片描述
在图片中找Pattern的两个原则(Inductive bias):

  1. 平移不变性
  2. 局部性

而卷积层就是通过感受野(Receptive Field) + 权重共享(Parameter Sharing) 实现

网络越深,就可以侦测到越大的Pattern,因为同样的Pattern可以出现在不同的地方,所以神经元之间可以共享参数

不同的角度来看卷积层:

在这里插入图片描述

卷积:二维交叉相关(Cov2d)

在这里插入图片描述

对全连接层使用平移不变性局部性得到卷积层
在这里插入图片描述

一维交叉相关(Cov1d):文本、语言、时间序列

三维交叉相关(Cov3d):视频、医学图像、气象地图

卷积层

kernel

将输入和kernel矩阵进行交叉相关运算,然后加上bias得到输出(输出的就是feature map)
kernel和bias是可学习的参数
kernel的size时超参数(控制局部性,保证权重不会因为输入的变大而变得特别大)
【根据李宏毅老师说的,CNN的capacity是小于DNN的】
在这里插入图片描述

不同的卷积核带来不同的效果

在这里插入图片描述
而神经网络就是学习这个来得到想要的输出

Padding

由于卷积核会导致输出变小,想要做深的网络就要填充(在周围加入额外的raw/col)
在这里插入图片描述
通常填充的行高(ph)和列高(pw)取:ph=kh-1;pw=kw-1

Stride

由于通常kernel比较小3.3 or 5.5,需要大量计算才能得到较小输出,于是有了步幅(移动窗口的时候移动的格数:如高度3,宽度2)
在这里插入图片描述

  • kernel_size, padding, stride是卷积层的超参数
  • 填充在输入周围添加额外的行列控制输出形状的减少量
  • 步幅是每次滑动核窗口的行列步长,可成倍减少输出形状
多输入/多输出通道

这俩是一般卷积层中要详细设计的超参数
多输入通道

  • 每个通道都有一个卷积核,结果是所有通道卷积结果的和
    在这里插入图片描述
    在这里插入图片描述
    输入和核都是多通道,输出是单通道

多输出通道
卷积核多了一个输出通道数的维度
在这里插入图片描述

多输入和多输出通道到底干啥

  • 每个输出通道可以识别特定模式
  • 输入通道核识别并组合输入中的模式
    (此外,1 * 1 卷积层 [kernel的h和w都是1] 它不识别空间模式,只做通道融合

一个完整的二维卷积层的样子:
在这里插入图片描述

池化层

卷积对位置敏感,并且需要一定程度的平移不变性。故通过pooling缓解卷积的位置敏感性

二维最大池化

返回滑动窗口中的最大值(允许图像存在一定程度偏移,类似模糊化的效果)
在这里插入图片描述
在这里插入图片描述
超参数和卷积层类似,都有窗口大小,填充和步幅,没有可学习的参数;输入通道数=输出通道数

平均池化层

将最大池化层中“max”操作替换为平均(有点柔和化的效果)
在这里插入图片描述

一些经典的CNN架构

LeNet

在这里插入图片描述
LeNet是早期成功的神经网路,先用卷积学习图片空间信息,然后用全连接层转换到类别空间

class Reshape(nn.Module):
    def forward(self,x):
        return x.view(-1,1,28,28) #批量数不变,通道数变1,高宽变28

net = nn.Sequential(
    Reshape(),nn.Conv2d(1,6,kernel_size=5,padding=2),nn.Sigmoid(),
	nn.AvgPool2d(kernel_size=2,stride=2),
	nn.Conv2d(6,16,kernel_size=5), nn.Sigmoid(),
	nn.AvgPool2d(kernel_size=2,stride=2), 
	nn.Flatten(),
	nn.Linear(16*5*5,120),
	nn.Linear(120,84), nn.Sigmoid(),
	nn.Linear(84,10)
)

# 迭代看每一层的情况
X = torch.rand(size=(1,1,28,28),dtype=torch.float32)
for layer in net:
    X = layer(X)
    print(layer.__class__.__name__, 'out shape: \t',X.shape)
    
# 使用GPU训练
def train(net,train_iter,test_iter,epochs,lr,device):
    def init_weights(m):
        if type(m) == nn.Linear or type(m) == nn.Conv2d:
            nn.init.xavier_uniforn_(m.weights)
    net.apply(init_weights) #参数初始化
    net.to(device) #使用GPU
    optimizer = torch.optim.SGD(net.parameters(),lr=lr) #定义优化器
    loss = nn.CrossEntrophyLoss() #定义loss func
    # 迭代训练
    for epoch in range(epochs):
        net.train(), 
        for i, (X,y) in enumerate(train_iter):
            optimizer.zero_grad() #优化器梯度清零
            X,y = X.to(device),y.to(device) #数据移至GPU
            y_hat = net(X) #预测值
            l = loss(y_hat,y) #计算损失
            l.backward() #对损失进行bp
            optimizer.step() #优化一步

AlexNet

在这里插入图片描述

模型设计

激活函数

使用ReLU()而不是Sigmoid(),原因在模型选择那里讲了,运算容易而且梯度好

容量控制和预处理

通过Dropout控制全连接层的模型复杂度,而LeNet只使用Weight Decay,并且AlexNet使用了Data Augmentation

实现

net = nn.Sequential(
	nn.Conv2d(1,96,kernel_size=11,stride=4,padding=1),nn.ReLU(),
    nn.MaxPool2d(kernel_size=3,stride=2),
    nn.Conv2d(96,256,kernel_size=5,padding=2),nn.ReLU(),
    nn.MaxPool2d(kernel_size=3,stride=2),
    nn.Conv2d(256,384,kernel_size=3,padding=1),nn.ReLU(),
    nn.Conv2d(384,384,kernel_size=3,padding=1),nn.ReLU(),
    nn.Conv2d(384,256,kernel_size=3,padding=1),nn.ReLU(),
    nn.MaxPool2d(kernel_size=3,stride=2), nn.Flatten(),
    nn.Linear(6400,4096),nn.ReLU(),nn.Dropout(p=0.5),
    nn.Linear(4096,4096),nn.ReLU(),nn.Dropout(p=0.5),
    nn.Linear(4096,10)
)

VGG

在这里插入图片描述

VGG块

用的3 * 3的卷积,深但窄的效果更好(同样的计算开销下)

def vgg_block(num_convs,in_channels,out_channels):
    layers = []
    for _ in range(num_convs):
        layers.append(nn.Conv2d(in_channels,out_channels,kernel_size=3,padding=1))
        layers.append(nn.ReLU())
        in_channels = out_channels
        layers.append(nn.MaxPool2d(kernel_size=2,tride=2))
        return nn.Sequential(*layers)

超参数:层数和通道数

VGG架构

多个VGG块后接全连接层,不同次数的重复块得到不同的架构,如VGG-16,VGG-19

def vgg(conv_arch):
    conv_blks = []
    in_channels = 1
    # 卷积层部分
    for (num_convs,out_channels) in conv_arch:                                                     conv_blks.append(vgg_block(num_convs,in_channels,out_channels))
        in_channels = out_channels 
    	return nn.Sequential(
    		*conv_blks, nn.Flatten(),
        	# 全连接层部分
        	nn.Linear(out_channels*7*7,4096), nn.ReLU(),nn.Dropout(p=0.5),
        	nn.Linear(4096,4096), nn.ReLU(), nn.Dropout(p=0.5),
        	nn.Linear(4096,10)
    	)
conv_arch = ((1,64),(1,128),(2,256),(2,512),(2,512)) #VGG架构,每一块里多少层卷积核多少输出通道; [五大块,为什么5?因为224除以5次2后是7]
net = vgg(conv_arch)

NiN(网络中的网络)

AlexNet和VGG的改进主要在于如何扩大和加深卷积层+Pooling层,而如果使用了全连接层,可能会完全放弃表征的空间结构, 并且第一个全连接层的参数特别大,且容易过拟合,NiN提出了一个解决方案:在每个pixel的通道上分别使用MLP(对每个像素增加了非线性性)

NiN块

在这里插入图片描述

def nin_block():
    return nn.Sequential(
    	nn.Conv2d(in_channels,out_channels,kernel_size,strides,padding), nn.ReLU(),
        # 然后两个1 * 1卷积层
        nn.Conv2d(in_channels,out_channels,kernel_size=1), nn.ReLU(),
        nn.Conv2d(in_channels,out_channels,kernel_size=1), nn.ReLU()
    )

NiN 架构

在这里插入图片描述

无全连接层,交替使用 NiN块 和 stride=2 的MaxPooling,逐步减小高宽增大通道数,最后使用全局平均池化层(池化层的高宽等于输入的高宽)得到输出,其通道数设置成了类别数(用这个替代全连接不同意过拟合,以及更少的参数个数)

net = nn.Sequential(
    nin_block(1,96,kernel_size=11,strides=4,padding=0), #灰度图所以in_channel设成了1
    nn.MaxPool2d(3,stride=2),
    nin_block(96,256,kernel_size=5,strides=1,padding=2),
    nn.MaxPool2d(3,stride=2),
    nin_block(256,384,kernel_size=3,strides=1,padding=1),
    nn.MaxPool2d(3,stride=2),
    nn.Dropout(0.5),
    nin_block(384,10,kernel_size=3,strides=1,padding=1),
    nn.AdaptiveAvgPool2d((1,1)),
    nn.Flatten()
)

GoogLeNet

并行连接

Inception块

4个路径从不同层面抽取信息,然后在输出通道合并
主要优点:模型参数小,计算复杂度低
在这里插入图片描述

1 * 1 卷积层的作用:https://blog.csdn.net/ding_programmer/article/details/104109895

变种
  • Inception-BN(v2)
  • Inception-V3:(修改了Inception块)替换5 * 5为多个3 * 3卷积层;替换5 * 5为1 * 7和7 * 1的卷积层;替换3 * 3 为1 * 3和3 * 1的卷积层;更深
  • Inception-V4:使用ResBlock
class Inception(nn.Module):
    def __init__(self,in_channels,c1,c2,c3,c4,**kwargs): #c1,c2,c3,c4是每一条path里的channel数
        super(Inception,self).__init__(**kwargs)
        # 线路1,单1x1卷积层
        self.p1_1 = nn.Conv2d(in_channels,c1,kernel_size=1)
        self.p2_1 = nn.Conv2d(in_channels,c2[0],kernel_size=1)
        self.p2_2 = nn.Conv2d(c2[0],c2[1],kernel_size=3,padding=1) 
        # 线路3,1x1卷积层后接5x5卷积层
		self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1)
		self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2) 
        # 线路4,3x3最⼤汇聚层后接1x1卷积层
		self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
		self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1)
	def forward(self, x):
		p1 = F.relu(self.p1_1(x))
		p2 = F.relu(self.p2_2(F.relu(self.p2_1(x))))
		p3 = F.relu(self.p3_2(F.relu(self.p3_1(x))))
		p4 = F.relu(self.p4_2(self.p4_1(x)))
		# 在通道维度上拼接输出
		return torch.cat((p1, p2, p3, p4), dim=1)

GoogLeNet Model

在这里插入图片描述

stage1 = nn.Sequential(
    nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
	nn.ReLU(),
	nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)

stage2 = nn.Sequential(
    nn.Conv2d(64, 64, kernel_size=1),
	nn.ReLU(),
	nn.Conv2d(64, 192, kernel_size=3, padding=1),
	nn.ReLU(),
	nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)

stage3 = nn.Sequential(
    Inception(192, 64, (96, 128), (16, 32), 32),
	Inception(256, 128, (128, 192), (32, 96), 64),
	nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)

stage4 = nn.Sequential(
    Inception(480, 192, (96, 208), (16, 48), 64),
	Inception(512, 160, (112, 224), (24, 64), 64),
	Inception(512, 128, (128, 256), (24, 64), 64),
	Inception(512, 112, (144, 288), (32, 64), 64),
	Inception(528, 256, (160, 320), (32, 128), 128),
	nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)

stage5 = nn.Sequential(
    Inception(832, 256, (160, 320), (32, 128), 128),
	Inception(832, 384, (192, 384), (48, 128), 128),
	nn.AdaptiveAvgPool2d((1,1)),
	nn.Flatten()
)

net = nn.Sequential(stage1, stage2, stage3, stage4, stage5, nn.Linear(1024, 10))

Batch Normalization

提出背景

一个深层的神经网络中,常常后面的层训练的比较快,底部的层训练的比较慢(数据在最底部,损失在最后面),因为梯度从顶传到底一直乘会变得很小。

而每一次底部的权重一变,上面的又得重新训练(白学了),那么就存在一个问题:训练在持续进行的时候,能否在改变底部的时候,避免变化顶部层?

BN的思路

固定小批量里面的均值和方差
在这里插入图片描述
然后再做额外的调整(可学习的参数γ、β)
在这里插入图片描述
常常作用在全连接层和卷积层之后,激活函数前;也可以作用在全连接和卷积的输入上,使得输入的数据方差和均值比较好【BN是一个线性变换】

对全连接层:作用在特征维上
对卷积层:作用在通道维上

一般来说,BN层可以加速收敛速度,但不改变模型精度(学习率可以稍微比较大)

BN在做什么?

最初论文试想用它来减少内部协变量转移;
后续有论文指出他可能就是通过在每个小批量里加入噪音来控制模型复杂度,因此没必要和Dropout混合使用

ResNet

在这里插入图片描述
对于非嵌套类函数,较复杂的函数类不能保证更接近“真”函数(model bias),这种在嵌套函数类中不会发生
核心思想:每个附加层都应该更容易地包含原始函数作为其元素之一

残差块

在这里插入图片描述
在这里插入图片描述

class Residual(nn.Module):
    def __init__(self,in_channels,num_channels,use_1x1conv=False,strides=1):
        super().__init__()
        self.conv1 = nn.Conv2d(in_channels,num_channels,kernel_size=3,padding=1,stride=strides)
        self.conv2 = nn.Conv2d(num_channels,num_channels,kernel_size=3,padding=1)
        if use_1x1conv:
            self.conv3 = nn.Conv2d(in_channels,num_channels,kernel_size=1,stride=strides)
        else:
            self.conv3 = None
        self.bn1 = nn.BatchNorm2d(num_channels)
        self.bn2 = nn.BatchNorm2d(num__channels)
    def forward(self,X):
        Y = F.relu(self.bn1(self.conv1(x)))
        Y = self.bn2(self.conv2(Y))
        if self.conv3:
            X = self.conv3(X)
        Y += X #经典短路操作
        return F.relu(Y)

ResNet 架构

就是ResBlock堆叠,和VGG差不多

DenseNet

从ResNet到DenseNet

ResNet用泰勒展开就是 f ( x ) = x + g ( x ) f(x)=x+g(x) f(x)=x+g(x),一个线性项+一个非线性项,如果再将f拓展成超过两部分的信息就是DenseNet
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

稠密块

def conv_block(in_channels,num_channels):
    return nn.Sequential(
    	nn.BatchNorm2d(in_channels),nn.ReLU(),
        nn.Conv2d(in_channels,num_channels,kernel_size=3,padding=1)
    )

class DenseBlock(nn.Module):
    def __init__(self,num_convs,in_channels,num_channels):
        super(DenseBlock,self).__init__()
        layer=[]
        for i in range(num_convs):
            layer.append(conv_block())
        self.net = nn.Sequential(*layer)
    def forward(self,X):
        for blk in self.net:
            Y = blk(X)
            X = torch.cat((X,Y),dim=1)
        return X

CNN的一些问题

CNN并不能处理图像放大缩小或旋转的问题(所以在图像识别的时候往往要做Data Augmentation)【Spatial Transformer Layer可以处理】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值