优质博客推荐

网络模型讲解

专有名词

  • Downstream Tasks: 下游任务, 即要实现的目标任务, 类比操作系统上的应用

  • Pre-train: 公共基本, 处理下游任务前的基础模型, eg: BERT, 类比操作系统

  • Error surface(误差曲面)是机器学习和深度学习中一个重要的概念,特别是在优化算法和神经网络训练的背景下。误差曲面描述了模型参数空间中损失函数(或误差函数)的变化情况

  • η\eta :学习率, 即梯度下降时控制梯度下降的大小因素之一 eg: ηLw\eta\frac{\partial{L}}{\partial{w}}

  • Model Bias: 模型偏差, 模型和真实之间的差距, eg: 用线性预测折线

  • update: 经过一个batch更新了一次参数, 所以batch_size越小参数更新次数越多

  • epoch: 经过一轮, 所有的batch都过了一遍

  • Batch_Size: batch越小,那一个epoch中更新参数的次数会更多,batch越大,一个epoch中参数更新的次数会更小。但由于GPU可以并行计算,所以并不是batch越小,所需要的时间越少。优先选择较小的batch

  • Momentum: 不止考虑梯度下降的方向,还要考虑前面的梯度方向,就好比物理中的惯性,有可能避免局部最小

DL基础

基础模版

1
2
3
4
5
6
# Neural Network Training Setup
dataset = MyDataset(file) # read data via MyDataset
tr_set = DataLoader(dataset, batch_size, shuffle=True) # put dataset into Dataloader
model = MyModel().to(device) # construct model and move to device (cpu/cuda)
criterion = nn.MSELoss() # set loss function
optimizer = torch.optim.SGD(model.parameters(), learning_rate) # set optimizer
1
2
3
4
5
6
7
8
9
10
#Neural Network Training Loop
for epoch in range(n_epochs): # iterate n_epochs
model.train() # set model to train mode
for x, y in tr_set: # iterate through the dataloader
optimizer.zero_grad() # set gradient to zero 每一轮进行梯度清零
x, y = x.to(device), y.to(device) # move data to device (cpu/cuda)
pred = model(x) # forward pass (compute output)
loss = criterion(pred, y) # compute loss
loss.backward() # compute gradient (backpropagation) 反向传播计算梯度
optimizer.step() # update model with optimizer 参数优化,更新模型参数
1
2
3
4
5
6
7
8
# Neural Network Testing Loop
model.eval() # set model to evaluation mode
preds = []
for x in tt_set: # iterate through the dataloader
x = x.to(device) # move data to device (cpu/cuda)
with torch.no_grad(): # disable gradient calculation
pred = model(x) # forward pass (compute output)
preds.append(pred.cpu()) # collect prediction
1
2
3
4
5
6
# Save/Load Trained Models
# Save
torch.save(model.state_dict(), path)
# Load
ckpt = torch.load(path)
model.load_state_dict(ckpt)

见Loss知issue

image-20240815163223296
  • 判断在训练集上的Loss
    • 如果在训练集上Loss很大, 那优先判断是不是model bias, 将模型变复杂看Loss会不会更好
    • 如果模型复杂效果更差, 那便是Grad Optimization的问题
  • 然后判断在测试集上的Loss
  • 如果在测试集上Loss很大, 那优先判断是不是overfitting
  • 解决overfitting的方法有数据增强和收集数据增加训练集; 给模型增加限制,eg: 设置较少的参数/较少的特征或者公用参数; 提前停止; Dropout; Regularization

learnin_rate

Update learnin_rate

image-20240816150452849

Adagrad

不仅考虑现在的梯度还要考虑以前的梯度

θit+1θitησitgitσit=1t+1i=0t(git)2\theta^{t+1}_i\leftarrow\theta^{t}_i-\frac{\eta}{\sigma^t_i}g^t_i\\ \sigma^t_i=\sqrt{\frac{1}{t+1}\sum^t_{i=0}(g^t_i)^2}

image-20240816150620903

RMSProp

通过α\alpha控制是当前的梯度参考价值大还是现在的梯度参考价值大

θit+1θitησitgitσit=α(σit1)2+(1α)(git)2\theta^{t+1}_i\leftarrow\theta^{t}_i-\frac{\eta}{\sigma^t_i}g^t_i\\ \sigma^t_i=\sqrt{\alpha(\sigma^{t-1}_i)^2+(1-\alpha)(g^t_i)^2}

Adam

RMSProp + Momentum

Learning Rate Scheduling

θit+1θitηtσitgit\theta^{t+1}_i\leftarrow\theta^{t}_i-\frac{\eta^t}{\sigma^t_i}g^t_i

Learning Rate Decay

一开始距离终点很远,慢慢的距离终点在变近,所以η\eta慢慢变小,相当于踩刹车

  • 下图是在Adagrad的基础上增加Learning Rate Decay之后的效果
image-20240816150716831

Warm Up

Increase and then decrease 先变大后变小

Soft-max

输入: 通常被称为logit

image-20240816154030059

Loss of Classification

常用 Cross-entropy

image-20240816154406097

为什么要选 Cross-entropy, 因为Cross-entropy更容易走到loss小的区域, 而MSE的左上角比较平缓不容易走到右下角

image-20240816154812414

CNN

结构

  • 输入层Input layer
  • 卷积层 CONV layer
  • 池化层 Pooling layer
  • 全连接层 FC layer
  • 输出层 Output layer

Input layer

对原始图像做预处理: 去均值、归一化等

CONV layer

通过控制卷积核的大小,步幅和填充提取特征,每个神经元通过不同的卷积核关注图像的不同的特征

Pooling layer

利用特征不变形和特征降维压缩图像

流程

  • INPUT
  • [ [ CONV -> RELU ]N -> POOL? ]M
  • [ FC -> RELU ]K
  • FC

AlexNet

结构

5个卷积层后紧跟个全连接层, 采用ReLu激活函数,并在全连接层后增加Dropout层减少过拟合

VGG16

结构

采用连续的3×3卷积核代替AlexNet中的较大卷积核(11×11、5×5)

ResNet

残差结构

优势

可以较好的解决梯度消失|爆炸和模型退化的问题

  • 梯度消失: 假设每一层的梯度是一个小于1的数, 随着层数变多,前面层的梯度会越来越小
  • 模型退化: 随着层数的加深, 效果退化, 不如层数少的训练效果好

亮点

  • 提示出residual模块
  • 使用Batch Normalization加速训练(丢弃dropout)

DenseNet

结构

DenseNet的网络结构主要由DenseBlockTransition组成

​ 在DenseBlock中,各个层的特征图大小一致,可以在channel维度上连接。DenseBlock中的非线性组合函数H()H(⋅)采用的是BN+ReLU+3x3 Conv的结构,如图所示。另外值得注意的一点是,与ResNet不同,所有DenseBlock中各个层卷积之后均输出kk个特征图,即得到的特征图的channel数为kk ,或者说采用kk个卷积核。kk 在DenseNet称为growth rate,这是一个超参数。一般情况下使用较小的kk(比如12),就可以得到较佳的性能。假定输入层的特征图的channel数为k0k_0 ,那么LL层输入的channel数为 k0+k(L1)k_0+k(L−1),因此随着层数增加,尽管 kk 设定得较小,DenseBlock的输入会非常多,不过这是由于特征重用所造成的,每个层仅有 kk 个特征是自己独有的。

​ Transition层主要用于连接两个相邻的DenseBlock,整合上一个DenseBlock获得的特征,缩小上一个DenseBlock的宽高,达到下采样效果,特征图的宽高减半。Transition层包括一个1x1卷积(用于调整通道数)和2x2AvgPooling(用于降低特征图大小),结构为BN+ReLU+1x1 Conv+2x2 AvgPooling。因此,Transition层可以起到压缩模型的作用 。

👻 Transformer

结构

Transformer结构

残差和标准化

LayerNorm

原理

层归一化是一种常用的归一化技术,它在每一层的输出上进行归一化操作,使得输出的均值为0,方差为1。这样可以使得模型在训练过程中更稳定,因为它可以减少内部协变量的移动,即输入数据的分布在训练过程中的改变。此外,层归一化还可以加速模型的收敛速度,提高模型的训练效率。

公式

y=xE(x)Var(x)+ϵγ+βy=\frac{x-E(x)}{\sqrt{Var(x)+\epsilon}}*\gamma+\beta

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class LayerNorm(nn.Module):

def __init__(self, feature, eps=1e-6):
"""
nn.Parameter: 将参数告知优化器此参数需要优化(学习)
:param feature: self-attention 的 x 的大小
:param eps:
"""
super(LayerNorm, self).__init__()
self.gamma = nn.Parameter(torch.ones(feature))
self.beta = nn.Parameter(torch.zeros(feature))
self.eps = eps # 防止分母为0

def forward(self, x):
mean = x.mean(-1, keepdim=True) # 均值
std = x.std(-1, keepdim=True) #方差
return self.gamma * (x - mean) / (std + self.eps) + self.beta

Add&Norm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class SublayerConnection(nn.Module):
"""
这不仅仅做了残差,这是把残差和 layernorm 一起给做了

"""
def __init__(self, size, dropout=0.1):
super(SublayerConnection, self).__init__()
# 第一步做 dropout
self.dropout = nn.Dropout(p=dropout)
# 第二步做 layernorm
self.layer_norm = LayerNorm(size)


def forward(self, x, sublayer):
"""
:param x: 就是self-attention的输入
:param sublayer: self-attention层
:return:
"""
return self.layer_norm(x + self.dropout(sublayer(x)))

Attention

公式

Attention(Q,K,V)=softmax(QKTdk)VAttention(Q,K,V)=softmax(\frac{QK^T}{\sqrt{d_k}})V

Q|K|V

注意力分数

红圈中内容代表I am a stu中I字对I字的注意力分数

注意力权重

每一个字的注意力分数对每个字的一维特征进行加权求和

缩小点积范围

下图是Softmax函数的分布, 如果不缩小点积的范围那么大多数值会分布在两端,梯度很小的地方,会造成梯度消失

image-20240812163307888

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class MutiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super().__init__()
self.num_heads = num_heads
self.d_model = d_model

self.w_q = nn.Linear(d_model, num_heads)
self.w_k = nn.Linear(d_model, num_heads)
self.w_v = nn.Linear(d_model, num_heads)
self.w_combine = nn.Linear(d_model, num_heads)
self.softmax = nn.Softmax(dim=-1)

def forward(self, q, k, v, mask=None):
batch_size, time, dimension = q.shape
n_dim = self.d_model // self.num_heads
q, k, v = self.w_q(q), self.w_k(k), self.w_v(v)

q = q.view(batch_size, time, self.num_heads, n_dim).permute(0, 2, 1, 3)
k = k.view(batch_size, time, self.num_heads, n_dim).permute(0, 2, 1, 3)
v = v.view(batch_size, time, self.num_heads, n_dim).permute(0, 2, 1, 3)

score = q @ k.transpose(2, 3) / math.sqrt(n_dim)
if mask is not None:
score = score.masked_fill(mask == 0, -10000)
score = self.softmax(score) @ v
score = score.permute(0, 2, 1, 3).contiguous().view(batch_size, time, dimension)
out = self.w_combine(score)
return out

Position Embedding

公式

PE(pos,2i)=sin(pos/100002i/dmodel)PE(pos,2i+1)=cos(pos/100002i/dmodel)PE_{(pos,2i)}=sin(pos/10000^{2i/d_{model}})\\ PE_{(pos,2i+1)}=cos(pos/10000^{2i/d_{model}})

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=512, device='cuda'):
super().__init__()
# 创建一个形状为(max_len, d_model)的全零张量, 用于存放位置编码
self.encodeing = torch.zeros(max_len, d_model, device=device)
# 位置编码不需要计算梯度
self.encodeing.requires_grad = False
# 生成[0, 1, 2, ..., max_len - 1]的序列
pos = torch.arange(0, max_len, device=device)
# 转换为浮点数并增加维度, 方便进行广播运算
pos = pos.float().unsqueeze(dim=1)
# 生成[0, 2, 4, ..., d_model - 2]的序列并转换为浮点数
_2i = torch.arange(0, d_model, step=2, device=device).float()
self.encodeing[:, 0::2] = torch.sin(pos / 10000 ** (_2i / d_model))
self.encodeing[:, 1::2] = torch.cos(pos / 10000 ** (_2i / d_model))

def forward(self, x):
batch_size, seq_len = x.size()
return self.encodeing[:seq_len, :]

理由

如下图,attention的计算中并不包含位置信息,交换K和V的字顺序计算出的甲醛和值相同的,因此要引入位置信息

image-20240812163527089
1
2
nn.Embedding(src_vocab_size, d_model): 词汇表大小×词嵌入的维度
torch.unsqueeze(dim): 在指定位置添加一个维度