3.7 批量归一化
-
批量归一化(Batch Normalization,BN)的作用是让误差表面变得平整。
- 不同参数给损失函数带来的斜率变化的差异,源自不同参数取值范围的差异,即量纲的不同
- 假如权重有一个小变化 Δ ω \Delta \omega Δω,参数 x 1 x_1 x1取值1,参数 x 2 x_2 x2取值100, Δ ω × x 1 \Delta \omega \times {x_1} Δω×x1和 Δ ω × x 2 \Delta \omega \times {x_2} Δω×x2有一个数量级上的差异,给损失函数带来的斜率变化也会有很大差异
- 解决思路:让不同维度的参数取值范围相同,这一类方法叫做特征归一化(feature normalization),常用方法是Z值归一化(Z-score normalization)
- 对不同特征的同一维度做归一化,让它们的数值在0上下,这样可以构造一个好的误差表面
- 注意是对不同特征同一维度的数据做归一化,而不是同一个特征内部数据做归一化
- 归一化的目标是让不同特征之间的数据具有可比性
- 特征内部的归一化会破坏特征内部样本之间的相对信息
- 不同参数给损失函数带来的斜率变化的差异,源自不同参数取值范围的差异,即量纲的不同
-
深度网络中间运算产生的 z i z^i zi、 a i a^i ai(如图)也是特征,也要做归一化
- 如果激活函数用sigmoid,适合对 z i z^i zi做归一化,因为sigmoid在0附近斜率大,把 z i z^i zi的值挪到0附近,算梯度时会得到大的值
- 特征归一化放在激活函数之前、之后在实现上没有太大的差别。
-
原本独立的 x ~ 1 \widetilde{x}^1 x 1、 x ~ 2 \widetilde{x}^2 x 2、 x ~ 3 \widetilde{x}^3 x 3,因为均值 μ \mu μ和标准差 σ \sigma σ产生了关联,改变其中一个 z i z^i zi值,其他的 z ~ i \widetilde{z}^i z i也会连带产生变化,如图
-
实际实现时只对一个批量里面的数据做归一化,所以称为批量归一化。适用于批量大的,因为够大的批量才算得出 μ \mu μ和 σ \sigma σ。批量如果比较大、足以表示整个数据集的分布,只需在一个批量上而不是整个数据集上做特征归一化作为近似。
-
批量归一化之后,网络还可以学习缩放和偏移参数 γ \gamma γ 和 β \beta β,调整归一化后的数据,使其具有合适的尺度和偏移: z ^ i = γ ⊙ z ^ i + β \hat{z}^i = \gamma \odot \hat{z}^i + \beta z^i=γ⊙z^i+β
- 需要加加
γ
\gamma
γ 和
β
\beta
β,是因为归一化后
z
^
\hat{z}
z^平均值为0,会带来一些问题,比如
- 激活函数的输入会常落在非线性函数的平坦区域,这些区域梯度接近于零,会导致梯度消失
- 网络层输出的变化范围被限制在一个较小的区域内,导致模型的表达能力受限,无法有效表达复杂的模式或特征
- 训练中中间层输出平均值为0,可能导致正权重和负权重的更新不对称,导致训练不稳定,或者梯度朝某个方向偏移
- 实际训练中会在已经找到一个比较好的误差表面、走到一个比较好的位置后,再把 γ \gamma γ 、 β \beta β慢慢地加进去,不会导致最初参数取值范围不同
- 需要加加
γ
\gamma
γ 和
β
\beta
β,是因为归一化后
z
^
\hat{z}
z^平均值为0,会带来一些问题,比如
-
测试时没有批量,可直接用训练阶段所有批量的 μ t \mu^t μt 和 σ t \sigma^t σt 的平均值 μ ‾ \overline{\mu} μ 和 σ ‾ \overline{\sigma} σ
- 计算方法为移动平均(moving average) μ ‾ → p μ ‾ + ( 1 − p ) μ t \overline{\mu} \rightarrow p\overline{\mu} +(1-p)\mu^t μ→pμ+(1−p)μt
- p p p 为超参数,PyTorch里常设置为0.1
-
做批量归一化后误差表面会比较平滑、容易训练,可以把学习率设大一点
4 卷积神经网络
-
把图像输入模型
- 先用三维张量描述图像,三维分别为图像的宽、高、通道(channel)数
- 把三维张量展平(flattening),所有的元素按顺序排列,变成一个长度为宽 × \times × 高 × \times × 通道数的一维向量
-
全连接网络的权重数 = = = 图像的宽 × \times × 高 × \times × 通道数 × \times × 神经元数,更多的参数带来更好的弹性和能力,也更容易过拟合
- 全连接网络如果只想看一个范围,可以把连接到其他输入特征的权重设为 0,这样网络的弹性会变小
-
图像本身的特性让图像模型不需要全连接
- 模式(pattern)可以代表某种物体,不需要检测整张图像
- 用合适大小的感受野(receptive field)来检测模式,通常为 3 × 3 × 3 3 \times 3 \times 3 3×3×3
- 多个神经元可以守备同一个感受野
- 移动感受野的量称为步幅(stride),通常不会设太大,以保证感受野之间有重叠,防止模式出现在两个不重叠感受野的交界上而检测不到
- 同样的模式可能会出现在图像的不同区域
- 检测同一个模式的神经元工作内容一样,只是各自守备的范围不一样,不用单独训练每一个神经元的参数,让它们参数共享(parameter sharing),即权重相同
- 这组共用的权重值就是一个滤波器(filter)
- 卷积层(convolutional layer) = = = 感受野 + + + 参数共享,用到卷积层的网络叫卷积神经网络
- 一个滤波器会产生一组数字,所有滤波器产生的数字放在一起称为特征映射(feature map),即一张图像通过一个卷积层会产生一个特征映射
- 特征映射可以看作是一张新的图像,它的宽、高要根据输入图像的尺寸、滤波器的尺寸、步幅(stride)和填充(padding)来计算,通道数为滤波器的数目。
-
假设输入图像的大小为 H in × W in H_{\text{in}} \times W_{\text{in}} Hin×Win,滤波器的大小为 F × F F \times F F×F,步幅为 S S S,填充为 P P P。那么卷积层的输出特征图的空间维度 H out × W out H_{\text{out}} \times W_{\text{out}} Hout×Wout 可以通过以下公式计算:
输出高度(Height of the feature map, H out H_{\text{out}} Hout):
H out = ⌊ H in − F + 2 P S ⌋ + 1 H_{\text{out}} = \left\lfloor \frac{H_{\text{in}} - F + 2P}{S} \right\rfloor + 1 Hout=⌊SHin−F+2P⌋+1
输出宽度(Width of the feature map, W out W_{\text{out}} Wout):
W out = ⌊ W in − F + 2 P S ⌋ + 1 W_{\text{out}} = \left\lfloor \frac{W_{\text{in}} - F + 2P}{S} \right\rfloor + 1 Wout=⌊SWin−F+2P⌋+1
-
- 大小为
3
×
3
3\times3
3×3 的滤波器虽然小,但经过层层网络后它看的范围会越来越大,所以网络够深就可以检测到比较大的模式
- 如果每个卷积层的滤波器大小都为
3
×
3
3\times3
3×3,第二层输入的每一个值都是由第一层的一个
3
×
3
3\times3
3×3 区域计算而来,那么第二层的
3
×
3
3\times3
3×3 区域在原始图像上覆盖的范围是
5
×
5
5\times5
5×5,如图
- 依此类推,层数越深, 3 × 3 3\times3 3×3 区域所覆盖的原始图像范围会越来越大
- 如果每个卷积层的滤波器大小都为
3
×
3
3\times3
3×3,第二层输入的每一个值都是由第一层的一个
3
×
3
3\times3
3×3 区域计算而来,那么第二层的
3
×
3
3\times3
3×3 区域在原始图像上覆盖的范围是
5
×
5
5\times5
5×5,如图
- 下采样不影响模式检测
- 用汇聚(pooling)把图像的宽和高变小,常用的有最大汇聚(max pooling)、平均汇聚(mean pooling)
- 假设要检测的是非常微细的东西,下采样会让它损失必要信息,影响模型的性能
- 模式(pattern)可以代表某种物体,不需要检测整张图像
-
CNN 如果能用在一个问题上,这个问题要具备跟图像共通的特性
- 只需要看小范围就可以知道很多重要的模式
- 同样的模式可能会出现在不同的位置
-
CNN 不能处理图像放大缩小或者是旋转的问题
- 肉眼看起来两张图像一模一样,但如果把它们“拉直”成向量,里面的数值不一样
- 做图像识别时要做数据增强,即让卷积神经网络看过不同大小的模式、看过物体旋转后的样子
- Special Transformer Layer 网络架构可以处理这个问题
6 自注意力机制
19 ChatGPT
- ChatGPT 的特点
- 每次的输出都不一样,再问一样的问题可能会得到很不一样的答案
- 可以继续追问,前面的对话会自动成为后面对话的context
- 对 ChatGPT 回答的误解
- 是“罐头信息”,即开发者事先编好的答案,有人问问题时它从里面随机挑一个拿出来
- 是从网络搜索来的
- 文字生成和考虑context
- 原理:以一个句子作为输入,给每一个后面可能接的符号一个概率,输出概率分布后,根据概率分布采样词汇
- 很复杂,可能有1700亿参数
- ChatGPT 的学习过程
- 如果只依赖人类能提供的监督式学习数据,机器的知识会很少,很多问题都回答不了
- 开发者从网络上爬到的45TB原始数据中选了570GB做训练,得到了GPT3,这一过程是预训练
- 机器学习需要成对的数据如果是用一些方法无痛生成,这种学习方式就叫做自监督学习,通过自监督式学习得到的
模型又叫做基石模型 - ChatGPT 可能不需要翻译引擎,因为在多种语言上做过预训练之后,接下来只要教模型某一个语言的某一个任务,它就可以自动学会其他语言以及同样的任务
- 机器学习需要成对的数据如果是用一些方法无痛生成,这种学习方式就叫做自监督学习,通过自监督式学习得到的
- GPT3经过人类的介入,使用监督式学习得到了ChatGPT,这一过程是微调
- ChatGPT 除了有监督式的学习,还用了强化学习中常见的 PPO 算法,有助于人类偷懒,以及解决人类自己都不知道答案的问题
- 催生的新研究
- 如何使用prompting精准提出需求
- 神经编辑(neural editing),即如何让机器修改一个错误,而不要弄错更多地方
- 判断输出的内容是否由 AI 生成
- Machine unlearning,让模型忘记它读到的机密信息
HW4 Self-Attention
-
模型的任务是使用transformer架构,把输入音频文件分类为不同的演讲者
-
模型的框架
-
直接运行初始代码得到的准确率
Step 70000, best model saved. (accuracy=0.4945)
-
分类结果的统计条形图
-
可优化的参数
- Transformer编码器
- num_layers:Transformer编码层的数量,更多层可以捕获更复杂的特征,但也更容易过拟合
- 规范:层归一化组件
- Transformer编码器层
- d_model:输入的预期特征数量,更大的维度能够捕获更多的特征
- nhead:多头注意力的数量,增加头的数量能够让模型从多个不同的子空间中提取特征
- dim_feedforward:feedforward网络模型的维度(默认=2048),增加它的维度可以提高模型的容量
- dropout:dropout的比率(默认=0.1),适当增加 Dropout 可提高模型的泛化能力
- 数据加载器
- batch_size,适度增加 Batch size 能加速训练
- 学习速率时间表
- optimizer: 基类是torch.optim.Optimizer,可选具体的torch.optim.SGD、torch.optim.Adam、torch.optim.RMSprop
- num_warmup_steps: 预热步数,较大的预热步数可以让学习率在初期更加平滑增长,有助于稳定训练。但这个模型比较小,可以设置较小的值比如500以内,以便尽快进入正式的学习阶段
- num_training_steps: 训练的总步数,通常等于数据集的批次数乘以训练轮数。增加总步数,余弦衰减的周期会拉长,学习率下降得更缓慢
- num_cycles: 控制余弦函数的周期数,默认值为0.5表示一个完整的下降周期,如果为1.0会有一个完整的余弦曲线
- 参数调整:num_layers设置为2,nhead设置为4
- 准确率比初始程序的0.4945高一点
Step 70000, best model saved. (accuracy=0.5088)
- 分类结果的统计条形图
- Transformer编码器
-
构造 Transformer 的变体 Conformer
- Conformer 结合了卷积神经网络(CNN)和 Transformer,使用卷积层捕获局部信息,同时用 Transformer 的自注意力机制来捕获长距离依赖关系
- 原本的 Transformer 代码如下
import torch import torch.nn as nn import torch.nn.functional as F class Classifier(nn.Module): def __init__(self, d_model=80, n_spks=600, dropout=0.1): super().__init__() # Project the dimension of features from that of input into d_model. self.prenet = nn.Linear(40, d_model) # TODO: # Change Transformer to Conformer. # https://arxiv.org/abs/2005.08100 self.encoder_layer = nn.TransformerEncoderLayer( d_model=d_model, dim_feedforward=256, nhead=2 ) # self.encoder = nn.TransformerEncoder(self.encoder_layer, num_layers=2) # Project the the dimension of features from d_model into speaker nums. self.pred_layer = nn.Sequential( nn.Linear(d_model, d_model), nn.Sigmoid(), nn.Linear(d_model, n_spks), ) def forward(self, mels): """ args: mels: (batch size, length, 40) return: out: (batch size, n_spks) """ # out: (batch size, length, d_model) out = self.prenet(mels) # out: (length, batch size, d_model) out = out.permute(1, 0, 2) # The encoder layer expect features in the shape of (length, batch size, d_model). out = self.encoder_layer(out) # out: (batch size, length, d_model) out = out.transpose(0, 1) # mean pooling stats = out.mean(dim=1) # out: (batch, n_spks) out = self.pred_layer(stats) return out
- 改为Conformer
import torch import torch.nn as nn import torch.nn.functional as F from conformer import ConformerBlock # 假设你已经安装了 Conformer 库 class Classifier(nn.Module): def __init__(self, d_model=80, n_spks=600, dropout=0.1): super().__init__() # Project the dimension of features from that of input into d_model. self.prenet = nn.Linear(40, d_model) # TODO: # Change Transformer to Conformer. # https://arxiv.org/abs/2005.08100 # 第一处修改,用 ConformerBlock 替换 TransformerEncoderLayer # dim 特征维度,dim_head 每个头的维度,heads 自注意力机制中的头数,ff_mult 前馈网络的倍数,conv_kernel_size 卷积层的核大小 self.encoder_layer = ConformerBlock( dim=d_model, dim_head=64, heads=4, ff_mult=4, conv_kernel_size=31 ) # Project the the dimension of features from d_model into speaker nums. self.pred_layer = nn.Sequential( nn.Linear(d_model, d_model), nn.Sigmoid(), nn.Linear(d_model, n_spks), ) def forward(self, mels): """ args: mels: (batch size, length, 40) return: out: (batch size, n_spks) """ # out: (batch size, length, d_model) out = self.prenet(mels) # out: (length, batch size, d_model) out = out.permute(1, 0, 2) # The encoder layer expect features in the shape of (length, batch size, d_model). out = self.encoder_layer(out) # out: (batch size, length, d_model) # 第二处修改,将 ConformerBlock 层处理后的张量再转换回原始的顺序 out = out.permute(1, 0, 2) # mean pooling stats = out.mean(dim=1) # out: (batch, n_spks) out = self.pred_layer(stats) return out
- 准确率比初始程序的0.4945高一点
Step 70000, best model saved. (accuracy=0.5282)
- 分类结果的统计条形图
-
实现 Self-Attention Pooling 和 Additive Margin Softmax,以进一步提高性能