import torch
模型选择
训练误差:在训练集上的误差(train_loss)
泛化误差:在测试集上的误差(test_loss)
验证集:一个用来评估模型好坏的数据集
例如拿出50%的训练数据
不要和训练集混在一起
一定程度上能反映出模型超参数的好坏
测试集:只用一次的数据集。例如
未来的考试
我出价的房子的实际成交价
kaggle私有排行榜的数据集
K-则交叉验证
算法:
- 将数据分成K块
For i 1,…, K
使用第i块作为验证数据集,其余作为训练集
报告K个验证集误差的平均
常用:K=5或10
过拟合、欠拟合
简单数据、复杂模型 -》 过拟合
复杂数据、简单模型 -》 欠拟合
模型容量:
- 拟合各种函数的能力
- 低容量的模型难以拟合训练数据
- 高容量的模型可以记住所有的训练数据
估计模型容量
- 难以在不同种类的算法之间比较
- 给定一个模型种类,将有两个主要因素
- 参数个数
- 参数值的选择范围
数据复杂度
多个重要因素
- 样本个数
- 每个样本的元素个数
- 时间、空间结构
- 多样性
权重衰退 最常见的处理过拟合的方法 (weight Decay)
控制模型容量:
- 减少模型参数
- 使模型参数范围变小(权重衰退)
优化的是最小损失函数
- 加入正则项 ||W||² < θ(硬性限制,用的不多)
- 通常不限制bias(限制与否差距不大)
- 小的θ意味着更强的正则项
常用的:使用《均方范数》作为柔性限制
超参数λ控制了正则项的重要程度
- λ = 0: 无作用
- λ 趋近正无穷: w* 趋近 0
其中η是学习率
总结
- 权重衰退通过L2正则项使得模型参数不会过大,从而控制模型复杂度
- 正则项权重是控制模型复杂度的超参
Tips
- 权重衰退值一般是.1, .01, .001
- 效果其实不会太好
Dropout
动机
- 一个好的模型需要对输入数据的扰动鲁棒
- 使用有噪音的数据等价于Tikhonov正则
- Dropout:在层之间加入噪音
无偏差的加入噪音
即期望不变。
Dropout通常用在隐藏全连接层的输出上
Dropout本质上是一个正则项,只在训练中使用,它们影响模型参数的更新(model.eval())
在推理过程中,Dropout直接返回输入 h = Dropout(h)
这样也能保证确定性的输出
总结
- 丢弃法将一些输出项随机置为0来控制模型复杂度
- 常作用在多层感知机(MLP)的隐藏层输出上
- 丢弃概率p是控制模型复杂度的超参数
Tips
- 被dropout后,梯度也置0,权重也不更新
- 提出时,认为是“训练多个小神经网络,再求平均”,但后续实验认为应解释为“正则项”
- 每迭代一次,dropout一次
数值稳定性
带来的两个问题
- 梯度爆炸
- 使用ReLU作为激活函数
- 带来的问题:
- 值超过值域,对于16位浮点数尤为严重
- 学习率敏感
- 学习率太大 -> 大参数 -> 更大的梯度
- 学习率太小 -> 训练无进展
- 可能需要在训练中不断调整学习率
- 梯度消失
- 使用sigmoid作为激活函数
- 带来的问题:
- 梯度值变为0,对16位浮点数尤其严重
- 训练没有进展,无论如何选择学习率
- 对于底层尤其严重。即顶部较好,但无法使神经网络更深。
激活函数
让训练更加稳定
- 目标:让梯度值在合理的范围内
- 将乘法变为加法
- ResNet、Lstm
- 归一化
- 梯度归一化、梯度裁剪
- 合理的权重初始和激活函数
权重初始化
- 在合理区间内随机初始参数
- 训练开始时容易有数值不稳定
- 原理最优解的地方损失函数表面可能很复杂
- 最优解附近表面会比较平
- 使用N(0, 0.01)来初始化可能对小网络没问题,但不能保证深度神经网络
LeNet
主要用于手写数字的识别
卷积神经网络之父
LeNet = torch.nn.Sequential(
torch.nn.Conv2d(1, 6, kernel_size=5, padding=2, stride=1),
torch.nn.Sigmoid(),
torch.nn.AvgPool2d(kernel_size=2, stride=2),
torch.nn.Conv2d(6, 16, kernel_size=5),
torch.nn.Sigmoid(),
torch.nn.AvgPool2d(2),
torch.nn.Flatten(),
torch.nn.Linear(16 * 5 * 5, 120),
torch.nn.Sigmoid(),
torch.nn.Linear(120, 84),
torch.nn.Sigmoid(),
torch.nn.Linear(84, 10)
)
X = torch.rand(size=(1, 1, 28, 28), dtype=torch.float32)
for layer in LeNet:
X = layer(X)
print(layer.__class__.__name__, 'output shape:', X.shape)
Conv2d output shape: torch.Size([1, 6, 28, 28])
Sigmoid output shape: torch.Size([1, 6, 28, 28])
AvgPool2d output shape: torch.Size([1, 6, 14, 14])
Conv2d output shape: torch.Size([1, 16, 10, 10])
Sigmoid output shape: torch.Size([1, 16, 10, 10])
AvgPool2d output shape: torch.Size([1, 16, 5, 5])
Flatten output shape: torch.Size([1, 400])
Linear output shape: torch.Size([1, 120])
Sigmoid output shape: torch.Size([1, 120])
Linear output shape: torch.Size([1, 84])
Sigmoid output shape: torch.Size([1, 84])
Linear output shape: torch.Size([1, 10])
AlexNet
用于 ImageNet 数据集
赢得2012年ImageNet竞赛
更大更深的LeNet
主要改进:
Dropout
ReLu
MaxPooling
数据增强
计算机视觉方法论的改变
传统
Dataset -> 人工特征提取(特征工程)(opencv)(专家) -> SVM(传统机器学习分类)
人工智能
Dataset -> CNN学习特征 -> Softmax回归
AlexNet = torch.nn.Sequential(
torch.nn.Conv2d(1, 96, kernel_size=11, stride=4, padding=2), torch.nn.ReLU(),
torch.nn.MaxPool2d(kernel_size=3, stride=2), #
torch.nn.Conv2d(96, 128 * 2, kernel_size=5, padding=2), torch.nn.ReLU(),
torch.nn.MaxPool2d(kernel_size=3, stride=2),
torch.nn.Conv2d(128 * 2, 192 * 2, kernel_size=3, padding=1), torch.nn.ReLU(),
torch.nn.Conv2d(192 * 2, 192 * 2, kernel_size=3, padding=1), torch.nn.ReLU(),
torch.nn.Conv2d(192 * 2, 128 * 2, kernel_size=3, padding=1), torch.nn.ReLU(),
torch.nn.MaxPool2d(kernel_size=3, stride=2), # 6*6*256
torch.nn.Flatten(),
torch.nn.Linear(6 * 6 * 256, 2048 * 2), torch.nn.ReLU(), torch.nn.Dropout(p=0.5),
torch.nn.Linear(2048 * 2, 2048 * 2), torch.nn.ReLU(), torch.nn.Dropout(p=0.5),
torch.nn.Linear(2048 * 2, 1000), torch.nn.ReLU(),
)
X = torch.rand(size=(1, 1, 224, 224), dtype=torch.float32)
for layer in AlexNet:
X = layer(X)
print(layer.__class__.__name__, 'output shape:', X.shape)
Conv2d output shape: torch.Size([1, 96, 55, 55])
ReLU output shape: torch.Size([1, 96, 55, 55])
MaxPool2d output shape: torch.Size([1, 96, 27, 27])
Conv2d output shape: torch.Size([1, 256, 27, 27])
ReLU output shape: torch.Size([1, 256, 27, 27])
MaxPool2d output shape: torch.Size([1, 256, 13, 13])
Conv2d output shape: torch.Size([1, 384, 13, 13])
ReLU output shape: torch.Size([1, 384, 13, 13])
Conv2d output shape: torch.Size([1, 384, 13, 13])
ReLU output shape: torch.Size([1, 384, 13, 13])
Conv2d output shape: torch.Size([1, 256, 13, 13])
ReLU output shape: torch.Size([1, 256, 13, 13])
MaxPool2d output shape: torch.Size([1, 256, 6, 6])
Flatten output shape: torch.Size([1, 9216])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 1000])
ReLU output shape: torch.Size([1, 1000])
VGG 使用块的网络
13年的冠军
“更深、更大、效果更好”
卷积块的思想
VGG块: 用大量的块堆积起来
3 * 3 卷积 更深
5 * 5 卷积 浅
深但窄 效果更好
对于给定的感受野(与输出有关的输入图片的局部大小),采用堆积的小卷积核是优于采用大的卷积核,因为多层非线性层可以增加网络深度来保证学习更复杂的模式,而且代价还比较小(参数更少)。
不同次数的重复块得到:
VGG-16
VGG-19
VGG 更占内存,更慢,精度更高
使用重复的卷积块来构建深度卷积神经网络
不同的卷积块个数和超参数可以得到不同复杂度的变种
VGG的亮点
1.小卷积核组。作者通过堆叠多个33的卷积核(少数使用11)来替代大的卷积核,以减少所需参数;
2.小池化核。相比较于AlexNet使用的33的卷积核,VGG全部为22的卷积核;
3.网络更深特征图更宽。卷积核专注于扩大通道数,池化专注于缩小高和宽,使得模型更深更宽的同时,计算量的增加不断放缓;
4.将卷积核替代全连接。作者在测试阶段将三个全连接层替换为三个卷积,使得测试得到的模型结构可以接收任意高度或宽度的输入。
5.多尺度。作者从多尺度训练可以提升性能受到启发,训练和测试时使用整张图片的不同尺度的图像,以提高模型的性能。
6.去掉了LRN层。作者发现深度网络中LRN(Local Response Normalization,局部响应归一化)层作用不明显。
def vgg_block(num_convs, in_channels, out_channels):
layers = []
for _ in range(num_convs):
layers.append(torch.nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1))
layers.append(torch.nn.ReLU())
in_channels = out_channels
layers.append(torch.nn.MaxPool2d(kernel_size=2, stride=2))
return torch.nn.Sequential(*layers)
def vgg(conv_arch):
conv_blcks = []
in_channels = 1
for (num_convs, out_channels) in conv_arch:
conv_blcks.append(vgg_block(num_convs, in_channels, out_channels))
in_channels = out_channels
return torch.nn.Sequential(
*conv_blcks, torch.nn.Flatten(),
torch.nn.Linear(out_channels * 7 * 7, 4096), torch.nn.ReLU(),
torch.nn.Dropout(0.5), torch.nn.Linear(4096, 4096), torch.nn.ReLU(),
torch.nn.Dropout(0.5), torch.nn.Linear(4096, 10)
)
conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))
VGG = vgg(conv_arch)
X = torch.rand(size=(1, 1, 224, 224), dtype=torch.float32)
for layer in VGG:
X = layer(X)
print(layer.__class__.__name__, 'output shape:', X.shape)
Sequential output shape: torch.Size([1, 64, 112, 112])
Sequential output shape: torch.Size([1, 128, 56, 56])
Sequential output shape: torch.Size([1, 256, 28, 28])
Sequential output shape: torch.Size([1, 512, 14, 14])
Sequential output shape: torch.Size([1, 512, 7, 7])
Flatten output shape: torch.Size([1, 25088])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 10])
NiN 网络中的网络
全连接层的问题
- 卷积层需要较少的参数 ci * co * k²
- 但卷积层后的第一个全连接层的参数非常多
NiN的思想就是完全不要全连接层
NiN块
- 一个卷积层后跟两个全连接层
- 步幅1,无填充,输出形状和卷积层输出一样
- 起到全连接层的作用
NiN架构
- 无全连接层
- 交替使用NiN块和步幅为2的最大池化层
- 逐步减小高宽和增大通道数
- 最后使用全局平均池化层得到输出
- 其输入通道数为类别数
总结
- NiN块使用卷积层加两个1 * 1卷积层,后者对每个像素增加了非线性性
- NiN使用全局平均池化层来替代Vgg和AlexNet中的全连接层,不容易过拟合,更少的参数个数