知识点
训练集与测试集的拆分
留出法
即对数据集进行随机拆分,一部分为训练集,一部分为测试集。模型结果依赖于拆分结果,且不稳定。
k折交叉验证
即将数据集分成k份(一般为5、10),从每份中挑取一个为测试集,其余的k-1训练集,最终得到k个评价模型,对这k个模型进行平均评价,结果较为稳定。
通过网格搜索调整超参数
超参数:在学习过程之前需要设置其值的一些变量,即预设值,而不是通过训练得到的参数数据。如深度学习中的学习速率等就是超参数。
网格搜索:
网格搜索结合k折交叉验证调整超参数
假设有A、B两个超参数值,A有3个预设值,B有5个预设值,那么总共有15个超参数对值。使用5折交叉验证拆分数据集,那么最终训练了5×15=75个模型。
神经网络
前馈神经网络(Feedforward Neural Network,FNN)
即每一层的神经元都和上下两层的每一个神经元完全连接,如图:
前馈神经网络的实现是较为简单的:
class MLP(nn.Module):
'''前馈神经网络'''
def __init__(self, dim: int, hidden_dim: int, dropout: float):
super().__init__()
# 定义第一层线性变换,从输入维度到隐藏维度
self.w1 = nn.Linear(dim, hidden_dim, bias=False)
# 定义第二层线性变换,从隐藏维度到输入维度
self.w2 = nn.Linear(hidden_dim, dim, bias=False)
# 定义dropout层,用于防止过拟合
self.dropout = nn.Dropout(dropout)
def forward(self, x):
# 前向传播函数
# 首先,输入x通过第一层线性变换和RELU激活函数
# 然后,结果乘以输入x通过第三层线性变换的结果
# 最后,通过第二层线性变换和dropout层
return self.dropout(self.w2(F.relu(self.w1(x))))
forward
函数本质就是先对输入进行一次线性变化,再非线性变化(激活函数),然后二次线性变化,最后再通过dropout
层。
常见的激活函数有:
其中,整流函数也称ReLU(Rectified Linear Unit)激活函数。
卷积神经网络(Convolutional Neural Network,CNN)
即训练参数量远小于前馈神经网络的卷积层来进行特征提取和学习,如图:
[28,28,]的图像 => conv1操作,[28,28,32],32为特征图数量 => pool1操作,[14,14,32] => conv2操作, [14,14,64] => pool2操作, [7,7,64] => fc1操作, [1,1,1024] => fc2操作,得到10个类别,即onehot编码。
循环神经网络(Recurrent Neural Network,RNN)
能够使用历史信息作为输入、包含环和自重复的网络,如图:
NLP的任务
自然语言处理(Natural Language Processing,NLP)作为人工智能领域的一个重要分支,旨在使计算机能够理解和处理人类语言,实现人机之间的自然交流。
在NLP的广阔研究领域中,有几个核心任务构成了NLP领域的基础,它们涵盖了从文本的基本处理到复杂的语义理解和生成的各个方面。这些任务包括但不限于中文分词、子词切分、词性标注、文本分类、实体识别、关系抽取、文本摘要、机器翻译以及自动问答系统的开发
。每一项任务都有其特定的挑战和应用场景,它们共同推动了语言技术的发展,为处理和分析日益增长的文本数据提供了强大的工具。
详见NLP任务。
Encoder – Decoder
Encoder 和 Decoder 内部传统神经网络的经典结构——前馈神经网络(FNN)、层归一化(Layer Norm)和残差连接(Residual Connection)
前馈神经网络(FNN)
每一个 Encoder Layer 都包含一个上文讲的注意力机制和一个前馈神经网络。
层归一化(Layer Norm)
是深度学习中经典的归一化操作。神经网络主流的归一化一般有两种,批归一化(Batch Norm)和层归一化(Layer Norm)。
归一化核心是为了让不同层输入的取值范围或者分布能够比较一致。
相较于 Batch Norm 在每一层统计所有样本的均值和方差,Layer Norm 在每个样本上计算其所有层的均值和方差,从而使每个样本的分布达到稳定。Layer Norm 的归一化方式其实和 Batch Norm 是完全一样的,只是统计统计量的维度不同。
简单地实现一个 Layer Norm 层:
class LayerNorm(nn.Module):
''' Layer Norm 层'''
def __init__(self, features, eps=1e-6):
super(LayerNorm, self).__init__()
# 线性矩阵做映射
self.a_2 = nn.Parameter(torch.ones(features))
self.b_2 = nn.Parameter(torch.zeros(features))
self.eps = eps
def forward(self, x):
# 在统计每个样本所有维度的值,求均值和方差
mean = x.mean(-1, keepdim=True) # mean: [bsz, max_len, 1]
std = x.std(-1, keepdim=True) # std: [bsz, max_len, 1]
# 注意这里也在最后一个维度发生了广播
return self.a_2 * (x - mean) / (std + self.eps) + self.b_2
残差连接(Residual Connection)
为了避免模型退化,Transformer 采用了残差连接的思想来连接每一个子层。
残差连接,即下一层的输入不仅是上一层的输出,还包括上一层的输入。残差连接允许最底层信息直接传到最高层,让高层专注于残差的学习。
例如,在 Encoder 中,在第一个子层,输入进入多头自注意力层的同时会直接传递到该层的输出,然后该层的输出会与原输入相加,再进行标准化。在第二个子层也是一样。即:
x=x+MultiHeadSelfAttention(LayerNorm(x))
output=x+FNN(LayerNorm(x))output=x+FNN(LayerNorm(x))
在代码实现中,通过在层的 forward 计算中加上原值来实现残差连接:
# 注意力计算
h = x + self.attention.forward(self.attention_norm(x))
# 经过前馈神经网络
out = h + self.feed_forward.forward(self.fnn_norm(h))
Encoder
在实现上述组件之后,我们可以搭建起 Transformer 的 Encoder。Encoder 由 N 个 Encoder Layer 组成,每一个 Encoder Layer 包括一个注意力层和一个前馈神经网络。因此,我们可以首先实现一个 Encoder Layer:
class EncoderLayer(nn.Module):
'''Encoder层'''
def __init__(self, args):
super().__init__()
# 一个 Layer 中有两个 LayerNorm,分别在 Attention 之前和 MLP 之前
self.attention_norm = LayerNorm(args.n_embd)
# Encoder 不需要掩码,传入 is_causal=False
self.attention = MultiHeadAttention(args, is_causal=False)
self.fnn_norm = LayerNorm(args.n_embd)
self.feed_forward = MLP(args)
def forward(self, x):
# Layer Norm
norm_x = self.attention_norm(x)
# 自注意力
h = x + self.attention.forward(norm_x, norm_x, norm_x)
# 经过前馈神经网络
out = h + self.feed_forward.forward(self.fnn_norm(h))
return out
然后我们搭建一个 Encoder,由 N 个 Encoder Layer 组成,在最后会加入一个 Layer Norm 实现规范化:
class Encoder(nn.Module):
'''Encoder 块'''
def __init__(self, args):
super(Encoder, self).__init__()
# 一个 Encoder 由 N 个 Encoder Layer 组成
self.layers = nn.ModuleList([EncoderLayer(args) for _ in range(args.n_layer)])
self.norm = LayerNorm(args.n_embd)
def forward(self, x):
"分别通过 N 层 Encoder Layer"
for layer in self.layers:
x = layer(x)
return self.norm(x)
通过 Encoder 的输出,就是输入编码之后的结果。
Decoder
类似的,我们也可以先搭建 Decoder Layer,再将 N 个 Decoder Layer 组装为 Decoder。
但是和 Encoder 不同的是,Decoder 由两个注意力层和一个前馈神经网络组成。第一个注意力层是一个掩码自注意力层,即使用 Mask 的注意力计算,保证每一个 token 只能使用该 token 之前的注意力分数;第二个注意力层是一个多头注意力层,该层将使用第一个注意力层的输出作为 query,使用 Encoder 的输出作为 key 和 value,来计算注意力分数。
最后,再经过前馈神经网络:
class DecoderLayer(nn.Module):
'''解码层'''
def __init__(self, args):
super().__init__()
# 一个 Layer 中有三个 LayerNorm,分别在 Mask Attention 之前、Self Attention 之前和 MLP 之前
self.attention_norm_1 = LayerNorm(args.n_embd)
# Decoder 的第一个部分是 Mask Attention,传入 is_causal=True
self.mask_attention = MultiHeadAttention(args, is_causal=True)
self.attention_norm_2 = LayerNorm(args.n_embd)
# Decoder 的第二个部分是 类似于 Encoder 的 Attention,传入 is_causal=False
self.attention = MultiHeadAttention(args, is_causal=False)
self.ffn_norm = LayerNorm(args.n_embd)
# 第三个部分是 MLP
self.feed_forward = MLP(args)
def forward(self, x, enc_out):
# Layer Norm
norm_x = self.attention_norm_1(x)
# 掩码自注意力
x = x + self.mask_attention.forward(norm_x, norm_x, norm_x)
# 多头注意力
norm_x = self.attention_norm_2(x)
h = x + self.attention.forward(norm_x, enc_out, enc_out)
# 经过前馈神经网络
out = h + self.feed_forward.forward(self.ffn_norm(h))
return out
然后同样的,我们搭建一个 Decoder 块:
class Decoder(nn.Module):
'''解码器'''
def __init__(self, args):
super(Decoder, self).__init__()
# 一个 Decoder 由 N 个 Decoder Layer 组成
self.layers = nn.ModuleList([DecoderLayer(args) for _ in range(args.n_layer)])
self.norm = LayerNorm(args.n_embd)
def forward(self, x, enc_out):
"Pass the input (and mask) through each layer in turn."
for layer in self.layers:
x = layer(x, enc_out)
return self.norm(x)
完成上述 Encoder、Decoder 的搭建,就完成了 Transformer 的核心部分,接下来将 Encoder、Decoder 拼接起来再加入 Embedding 层就可以搭建出完整的 Transformer 模型啦。