Multi-head Attention from Chapter 3
第二章没整理所以没发,凑合看吧
RNN(recruent neruo network)
早期翻译模型
- 长期依赖问题(Long-term Dependency Issue):
RNN在处理较长序列时会遇到困难,因为它们依赖于逐步更新的隐藏状态,长序列中的早期信息可能会逐渐被遗忘。因此,在较长句子中,很难保持准确的信息传递,这就影响了翻译的效果。 - 序列处理的效率低:
RNN需要按顺序处理数据,意味着它们无法进行并行计算。因此,处理时间较长的句子时,效率会很低。这与transformer不同,后者可以并行处理整个序列,从而显著加快了速度。
梯度消失和爆炸问题(Gradient Vanishing and Exploding Issues): - 在深度神经网络中,由于反向传播的过程,梯度可能会迅速衰减或增长。RNN在序列较长时尤为容易出现梯度消失,导致模型难以有效训练
Attention
- attention score:
dot product
给定输入序列中的每个元素,先通过线性变换生成查询向量(Query)、键向量(Key)、和值向量(Value),以及温度(T)。
Attention Score=\(\frac{Q\cdot K\cdot V^T}{\sqrt{d_k}}\)
Scaled Dot-Product
-
attention weights:
Use Softmax: The softmax function converts a vector of values into probabilities, scaling them so that they sum to 1. It does this by exponentiating each value and then dividing by the sum of these exponentials. -
Context vector
梯度消失问题
梯度消失,就是随着网络层数的增加,梯度在反向传播过程中逐渐减小,甚至接近于0,导致网络参数更新缓慢或停滞,影响模型的训练效果。
- 梯度消失产生的原因:
-
- 激活函数的导数: 许多常用的激活函数,如Sigmoid函数,其导数在饱和区域(即输入值很大或很小)接近于0。当网络层数较多时,多次乘以小于1的导数,就会导致梯度急剧减小。
-
- 链式法则: 神经网络的训练是通过链式法则来计算梯度的,每一层的梯度都是前面所有层的梯度的乘积。如果每一层的梯度都小于1,那么随着层数的增加,梯度就会越来越小。
- 解决梯度消失的方法:
-
- 选择合适的激活函数: ReLU、LeakyReLU等激活函数在大部分输入区域的导数为1,可以缓解梯度消失问题。
Batch Normalization: 通过对每一层的输入进行归一化,可以加速网络的收敛,并减轻梯度消失问题。
- 选择合适的激活函数: ReLU、LeakyReLU等激活函数在大部分输入区域的导数为1,可以缓解梯度消失问题。
-
- 残差网络: 通过引入残差连接,可以有效地缓解梯度消失问题,使得网络更容易训练。
LSTM、GRU等RNN变种: 这些RNN变种在设计上考虑到了梯度消失问题,通过门控机制来控制信息的流动,从而缓解梯度消失。
- 残差网络: 通过引入残差连接,可以有效地缓解梯度消失问题,使得网络更容易训练。
过拟合及其解决办法
过拟合,就是模型过于复杂,对训练数据拟合得过于完美,以至于对新的、未见过的数据泛化能力很差。
正则化
-
正则化的原理
正则化的基本思想是在损失函数中加入一个正则化项。这个正则化项通常是对模型参数的一种惩罚。通过最小化这个新的损失函数,可以使得模型的参数值不会过大,从而减小模型的复杂度,降低过拟合的风险。 -
常用的正则化方法
L1正则化: 也称为Lasso回归,它是在损失函数中加入模型参数的绝对值之和。L1正则化倾向于产生稀疏解,即很多参数的值为0,可以起到特征选择的作用。
L2正则化: 也称为Ridge回归,它是在损失函数中加入模型参数的平方和。L2正则化倾向于让参数的取值尽可能小,从而防止过拟合。
Dropout: 在训练过程中,随机地“丢弃”一部分神经元,迫使网络学习更加鲁棒的特征。
dropout
随机丢弃神经元: 在每次训练迭代中,Dropout会以一定的概率随机地“丢弃”一部分神经元,也就是暂时不考虑这些神经元的输出。
防止过拟合:
- 减少神经元之间的共适应: Dropout迫使每个神经元不能依赖于其他特定的神经元,从而减少了神经元之间的复杂的共适应关系,提高了模型的泛化能力。
- 类似于集成学习: 每一次训练,相当于训练了一个不同的神经网络,Dropout可以看作是对许多不同神经网络的集成,从而减少过拟合。
- 增强鲁棒性: Dropout使得网络对输入数据的噪声具有更强的鲁棒性。
线性变换
- 数学定义:
设 V 和 W 是两个向量空间,一个映射 T: V → W 称为线性变换,当且仅当对于任意向量 u, v ∈ V 和标量 c,满足以下两个条件:
加法性: T(u + v) = T(u) + T(v)
齐次性: T(cu) = cT(u)
- 线性投影层
它本质上是一个全连接层,通过矩阵乘法将输入的特征映射到另一个特征空间。 -
- 将高维特征映射到低维空间,从而减少计算量。将不同特征空间的特征映射到同一个空间,以便进行后续的计算。
-
- 结合非线性激活函数,可以引入模型的非线性表达能力,从而学习更复杂的特征表示。
-
- 将输出特征的维度调整为后续层所需要的维度,从而实现不同层之间的连接。
#创建为nn.Module的子类
class CausalSelfAttention(nn.Module):
def __init__(self, d_in, d_out, context_length, dropout, qkv_bias=False):
#super init通常出现在子类函数构造中以便先构造父类再构造子类,确保构造正确性
#也在构造子类时调用父类构造方法
super().__init__()
self.d_out = d_out#dimension of output
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)#query matrix
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)#key matrix
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)#value matrix
self.dropout = nn.Dropout(dropout) # 丢弃率,防止过拟合
#掩码矩阵(上三角矩阵)用于避免后面token影响前面token
#PyTorch's tril function with elements below the main diagonal
#(including the diagonal itself) set to 1 and above the main diagonal set to 0:
self.register_buffer('mask', torch.triu(torch.ones(context_length, context_length), diagonal=1)) # New
#前向传播
def forward(self, x):
b, n_tokens, d_in = x.shape # New batch dimension b
#计算键,查询,值向量
keys = self.W_key(x)
queries = self.W_query(x)
values = self.W_value(x)
attn_scores = queries @ keys.transpose(1, 2) # Changed transpose
#将前面的元素分数设为负无穷
attn_scores.masked_fill_( # New, _ ops are in-place
self.mask.bool()[:n_tokens, :n_tokens], -torch.inf)
#softmax使总和为1,除以维度根号防止数据过大
attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
#random dropout
attn_weights = self.dropout(attn_weights) # New
#计算上下文向量
context_vec = attn_weights @ values
return context_vec
class MultiHeadAttentionWrapper(nn.Module):
def __init__(self, d_in, d_out, context_length, dropout, num_heads, qkv_bias=False):
super().__init__()
self.heads = nn.ModuleList(
#for _ in range是因为不会使用这个值所以可以不写
#生成一个ModuleList包含num_heads个头
[CausalSelfAttention(d_in, d_out, context_length, dropout, qkv_bias)
for _ in range(num_heads)]
)
#nn.Linear是一个能可学习调整参数进行线性变换的子类
#将合并后的输出通过可学习投影层拟合生成最终格式固定的输出
self.out_proj = nn.Linear(d_out*num_heads, d_out*num_heads)
def forward(self, x):
#用cat函数进行张量维度拼接,dim=-1表示沿着最后一个维度拼接
context_vec = torch.cat([head(x) for head in self.heads], dim=-1)
#调用self.out_proj的call方法进行线性变换
return self.out_proj(context_vec)
#设置随机变量
torch.manual_seed(123)
#设置关联上下文长度
context_length = max_length
d_in = output_dim
#根据输入量设置输出量,压缩信息,提取特征,降低维度
num_heads=2
d_out = d_in // num_heads
mha = MultiHeadAttentionWrapper(d_in, d_out, context_length, 0.0, num_heads)
#许多putorch重写了__call__方法使其能像函数一样被调用
batch = input_embeddings
context_vecs = mha(batch)
print("context_vecs.shape:", context_vecs.shape)