0. 回顾
主要掌握了全连接。 代码写作 nn.linear(A, B)。
它的作用是将矩阵从 A维度转为B维度。
比如 输入有4个变量, 回归任务, 需要输出1个值。 就使用: nn.linear(4, 1)
一个(16,4) 的矩阵 经过nn.linear(4, 1) 会转为 (16,1)
但是想中间多算几次, 加深网络加强效果。 可以 nn.linear(4, 64)- nn.linear(64, 1)
还有梯度下降算法
神经网络训练流程的几个模块:模型,数据,超参,训练过程
1.分类输出
现实中有很多问题并不是预测问题。
比如:
根据一封邮件的内容判断它是否是垃圾邮件。
根据两个人的样貌判断是否为直系亲属。
判断一个图片里的动物是鸟还是人,还是猫,狗。
以上都是分类任务
我们可以像回归任务那样输出一个值吗?
Linear (?,1)
根据值差判断类别可以吗?
比如得到预测值为1.3,有1,2,3三个类别
如果把1.3归为第一类,那么1.3与2相距0.7,与3相距1.7,可以说1.3比起第三类更像第二类吗?
显然不对
所以,我们不能只输出一个值,而是输出每种类别的概率,取最大
然后对于标签值,只在正确类别标1,其余标0,这样就可以求loss
2.图片输入处理
我们上次的数据是一些数字,直接转化为矩阵输入
那么其实图片也是这样的
图片有三个通道,R,G,B代表red,green,blue
2.1 卷积
3*224*224的图片,直接拉直?
那么最后进入全连接就是
linear(3*224*224,1000)
数据量实在太大,需要减小数据量
所以引入卷积来减少输入---去掉部分不重要的数据
我们人眼相当于制作一个框,框大小为9,依次比对,框扫过每行每列得到答案
(其实并不一定严格按照顺序)
卷积核的原理就是这样
将红色设为1,黑色设为-1
进行卷积,如果完全一致,那么就会得到9
我们识别图片的物体类别,完全不需要关注图片每一个部分,只需要小部分特征就好
那么怎么确定这个卷积核就代表鸟嘴?
这其实就是训练的目标,我们训练过程,就是在训练这个卷积核,就是在找什么样的卷积核适配鸟嘴
如果我们不想卷积将我们图片特征图变得越来越小,那么就可以加入padding
在周围加一圈0
从左侧一开始的深度为3的原始特征图,到右侧深度为5的新特征图,需要经过一个卷积层
原始特征图的深度就是卷积核的深度,卷积核的个数就是新特征图的深度
原始特征图是输入,不是参数;新特侦图是输出,不是参数。
卷积核是参数
这里参数量为5*3*3*3,即个数*深度*宽*高
以下是一些训练
- 3*3 、2*2
- 2*2
- 4*4
- 98*98
- 100*100
- 2*2、7*7
- 94*94
第二套
- 3
- 224*224
- 3*3*3、27
- 7*4*4
- 64*224*224
- 128*224*224、64、128*64*3*3
2.2 池化(Pooling)
特征图一直大小不变, 你怎么卷参数展平后都需要大量参数。
有两个方法减小特征图大小
原理:一条线上的点去掉一般,线基本不变
但是相较于增大步长,一般更青睐于池化
池化中一般更青睐最大池化,因为深度学习中越简单越好
●一张图片通常被转换为3*224*224.
●你能将其变为1024*7*7么?
●最少一次卷积就可以。 但在实战中, 常常卷积和池化相应变化。
● 3 * 224*224 -> 64*224*224 ->(128 * 224*224)->128*112*112
● –> 256*56*56 -> 512 * 28*28 -> 1024 * 14*14 ->1024*7*7
最后要输出一个类别,那么就要将特征图拉直输入全连接
2.3 loss
通过softmax,将输出值大小变为概率大小
使用交叉熵损失
例如
总体来看如下
和回归网络有很大相似之处
所以要训练,需要大量带标签的图片
3.图像分类网络历史
sota模型:sort state of the art
达到了艺术状态
3.1 AlexNet
现代神经网络热潮的开始 AlexNet
归一化可以在深度学习的各个角落看到。
它可以让模型关注数据的分布,而不受数据量纲的影响。
归一化可以 保持学习有效性, 缓解梯度消失和梯度爆炸。
Dropout 可以缓解 过拟合
计算特征图的变化过程
ps1:pool(3,2) 三个一组,步长为2
比如1234567
先在123中取3
再在345中取5
pool对特征图的影响主要看步数
ps2:向下取整
3*224*224->
64*54*54->64*27*27->
192*27*27->192*13*13->
384*13*13-3->256*13*13->256*13*13->
256*6*6->256*6*6->
9216->4096->4096->1000
下面是自己写一个alexnet
import torch.nn as nn class my_model(nn.Module): def __init__(self): super(my_model, self).__init__() self.conv1 = nn.Conv2d(3, 64, 11, stride=4,padding=2) self.pool1 = nn.MaxPool2d(3, 2) self.relu1 = nn.ReLU() self.conv2 = nn.Conv2d(64, 192, 5, padding=2) self.pool2 = nn.MaxPool2d(3, 2) self.relu2 = nn.ReLU() self.conv3 = nn.Conv2d(192, 384, 3, padding=1) self.conv4 = nn.Conv2d(384, 256, 3, padding=1) self.conv5 = nn.Conv2d(256, 256, 3, padding=1) self.pool3 = nn.MaxPool2d(3, 2) self.relu3 = nn.ReLU() self.pool4 = nn.AdaptiveAvgPool2d(6) self.relu4 = nn.ReLU() self.fc1 = nn.Linear(256 * 6 * 6, 4096) self.relu5 = nn.ReLU() self.fc2 = nn.Linear(4096, 4096) self.relu6 = nn.ReLU() self.fc3 = nn.Linear(4096, 1000) def forward(self, x): x = self.conv1(x) x = self.relu1(x) #注意激活函数也要定义 x = self.pool1(x) x = self.pool2(self.relu2(self.conv2(x))) x = self.conv3(x) x = self.conv4(x) x = self.conv5(x) x = self.pool3(self.relu3(x)) x = self.pool4(self.relu4(x)) x = x.view(x.size()[0], -1) # 将数据拉平,x.size()[0]表示batchsize,-1表示自动算出剩下的维度,flatten x=self.relu5(self.fc1(x)) x=self.relu6(self.fc2(x)) x=self.fc3(x) return x import torch model=my_model() img=torch.zeros((4,3,224,224)) y=model(img) print(y.shape)3.2 VGG
为什么要用小的卷积核代替大的呢?
节省参数量
图中5*5的卷积核与两个3*3的卷积核感受野一样大
但是参数量
5*5=25
2*3*3=18
节省了25-18=7
3*224*224->
64*224*224->64*224*224->64*112*112-> 参数量:64*3*3*3、64*64*3*3
128*112*112->128*112*112->128*56*56-> 参数量:128*64*3*3、128*128*3*3
256*56*56->256*56*56->256*28*28-> 参数量:256*128*3*3、256*256*3*3
512*28*28->512*28*28->512*14*14-> 参数量:512*256*3*3、512*512*3*3
512*14*14->512*14*14->512*7*7->参数量:512*512*3*3、512*512*3*3
512*7*7->
25088->4096->4096->1000
vgg中有很多重复的子模块
所以可以类套类来写
import torch.nn as nn class vgglayer(nn.Module): def __init__(self,in_channels,out_channels): super().__init__() #等于super(vgglayer, self).__init__() self.conv1 = nn.Conv2d(in_channels, out_channels, 3, padding=1) self.conv2 = nn.Conv2d(out_channels, out_channels, 3, padding=1) self.pool1 = nn.MaxPool2d(2) self.relu1 = nn.ReLU() def forward(self,x): x = self.conv1(x) x = self.relu1(x) x = self.conv2(x) x = self.relu1(x) x = self.pool1(x) return x class myvgg(nn.Module): def __init__(self): super(myvgg, self).__init__() self.layer1 = vgglayer(3,64) self.layer2 = vgglayer(64,128) self.layer3 = vgglayer(128,256) self.layer4 = vgglayer(256,512) self.layer5 = vgglayer(512,512) self.pool1 = nn.AdaptiveAvgPool2d(7) self.relu1 = nn.ReLU() self.fc1 = nn.Linear(25088,4096) self.relu2 = nn.ReLU() self.fc2 = nn.Linear(4096,4096) self.relu3 = nn.ReLU() self.fc3 = nn.Linear(4096,1000) def forward(self,x): x = self.layer1(x) x = self.layer2(x) x = self.layer3(x) x = self.layer4(x) x = self.layer5(x) x = self.pool1(x) x = x.view(x.size()[0], -1) x = self.relu2(self.fc1(x)) x = self.relu3(self.fc2(x)) x = self.fc3(x) return x def get_parameter_number(model): total_num = sum(p.numel() for p in model.parameters()) trainable_num = sum(p.numel() for p in model.parameters() if p.requires_grad) return {'Total': total_num, 'Trainable': trainable_num} import torch myVgg = myvgg() img = torch.zeros((1, 3, 224,224)) out = myVgg(img) print(get_parameter_number(myVgg)) print(get_parameter_number(myVgg.layer1)) print(get_parameter_number(myVgg.layer1.conv1))3.3 ResNet
创新在于残差连接和1*1卷积
残差连接用来防止梯度消失
1*1卷积用来改变维度数
如图,在卷积过程中使用1*1卷积,可以节省参数量
残差连接起作用的原因
在深层网络中,对于浅层网络的偏导是要通过很多层的偏导
如果每一层偏导小于1,那么乘下来对于浅层几乎等于0
如果每一层偏导大于1,那么乘下来对于浅层几乎等于无穷
由于加上了x,求偏导一定大于1,所以缓解了梯度消失
可以更新浅层网络
如果维度一样直接加,
如果不一样使用1*1卷积,调整步数
4. 透析
4.1 真正的推动者
4.2 卷积和全连接的关系
卷积是一个参数共享的不全连接
显著缩小参数量
4.3 玩特征
5.总结
深度学习还是多尝试,它是一个黑盒子