大模型学习入门——Day4: Encoder-Decoder

笔记参考教程------DataWhale快乐学习LLM

Seq2Seq,即序列到序列,是一种经典 NLP 任务。具体而言,是指模型输入的是一个自然语言序列 x 1 , x 2 , x 3 , . . . x_1, x_2, x_3,... x1,x2,x3,...,然后输出一个序列(长度可能不等)。事实上,Seq2Seq 是 NLP 最经典的任务,几乎所有的 NLP 任务都可以视为 Seq2Seq 任务。但这一看似直观的任务背后,隐藏着怎样的技术实现?为什么 Encoder-Decoder 结构会成为 Seq2Seq 的标配设计?本文将拆解这一经典框架,探讨它是如何通过'编码-解码'的协作,让机器学会'理解'并'生成'自然语言的。

Encoder 和 Decoder

一、把大象装进冰箱:核心思想

想象一下,你要请一位不精通中文的朋友帮你取一本中文书,书名是《时间简史》。你该怎么做?

  1. 你(编码器 - Encoder) :你不会逐字翻译"时间简史"的发音。相反,你会先理解这本书的核心信息------"一本关于宇宙和时间的科普书,作者是霍金,封面是黑色的,有个漩涡"。你把这些复杂的信息编码成一个简洁、浓缩的"思想摘要"。
  2. 思想摘要(上下文向量 - Context Vector):这就是你脑中的那个核心概念。
  3. 你的朋友(解码器 - Decoder) :他接收到你这个"思想摘要"后,开始解码。他根据这个摘要,在他的知识库里搜索,然后用他自己的语言(比如肢体动作和简单的英文单词)来一步步确认:"Ah, physics book? Hawking? Black cover?"。最终,他找到了正确的书。

这个过程完美地诠释了Encoder-Decoder架构的精髓:

  • Encoder (编码器) :负责"阅读"和"理解"输入序列(比如一句中文),并将其压缩成一个固定长度的、蕴含了全部语义的数学向量------上下文向量 (Context Vector, C)。这个向量就像是我们前面提到的"思想摘要"。
  • Decoder (解码器):负责接收这个"思想摘要" (Context Vector),然后根据它,一步步地生成目标序列(比如一句英文)。

这个"一压一解"的框架,可以处理一个可变长度的输入序列,并生成一个可变长度的Output序列,因此在许多领域大放异彩:

  • 机器翻译:输入中文,输出英文。
  • 文本摘要:输入长篇文章,输出简短摘要。
  • 对话系统(聊天机器人):输入一个问题,输出一个回答。
  • 图像描述:输入一张图片(用CNN编码),输出一句话描述。

二、深入内部:Encoder和Decoder的实现

一、基础模块:前馈神经网络 (FFN)

前馈神经网络是最基本的神经网络结构之一,由若干层神经元组成,信息单向流动。每一个 Encoder Layer 都包含一个上文讲的注意力机制和一个前馈神经网络。在Encoder-Decoder架构中,FFN常用于对每个位置的隐藏状态进行非线性变换。

公式:

一个两层的前馈神经网络可以表示为:

FFN ( x ) = W 2 ⋅ σ ( W 1 ⋅ x + b 1 ) + b 2 \text{FFN}(x) = W_2 \cdot \sigma(W_1 \cdot x + b_1) + b_2 FFN(x)=W2⋅σ(W1⋅x+b1)+b2

其中:

  • x x x 是输入向量。
  • W _ 1 W\_1 W_1 和 b _ 1 b\_1 b_1 是第一层线性变换的权重矩阵和偏置向量。
  • s i g m a \\sigma sigma 是激活函数(例如 ReLU)。
  • W _ 2 W\_2 W_2 和 b _ 2 b\_2 b_2 是第二层线性变换的权重矩阵和偏置向量。

PyTorch代码:

以下是一个简单的两层前馈神经网络的PyTorch实现:

python 复制代码
import torch
import torch.nn as nn


class FeedForwardNetwork(nn.Module):
 def __init__(self, input_dim, hidden_dim, output_dim, dropout=0.1):
 super(FeedForwardNetwork, self).__init__()
 self.linear1 = nn.Linear(input_dim, hidden_dim)
 self.relu = nn.ReLU()
 self.dropout = nn.Dropout(dropout)
 self.linear2 = nn.Linear(hidden_dim, output_dim)


 def forward(self, x):
 x = self.linear1(x)
 x = self.relu(x)
 x = self.linear2(x)
 x = self.dropout(x)
 return x


# 示例
input_dim = 512
hidden_dim = 2048
output_dim = 512
ffn = FeedForwardNetwork(input_dim, hidden_dim, output_dim)
input_tensor = torch.randn(32, 50, input_dim) # (batch_size, seq_len, input_dim)
output_tensor = ffn(input_tensor)
print("FFN Output Shape:", output_tensor.shape)

在Encoder和Decoder的每个块中,FFN通常应用于注意力机制的输出,以进一步提取特征。

二、加速训练:层归一化 (Layer Normalization)

归一化核心是为了让不同层输入的取值范围或者分布能够比较一致。由于深度神经网络中每一层的输入都是上一层的输出,因此多层传递下,对网络中较高的层,之前的所有神经层的参数变化会导致其输入的分布发生较大的改变。也就是说,随着神经网络参数的更新,各层的输出分布是不相同的,且差异会随着网络深度的增大而增大。但是,需要预测的条件分布始终是相同的,从而也就造成了预测的误差。

层归一化是一种归一化技术,它在每个样本的特征维度 上计算均值和标准差,并对该样本的所有特征进行归一化。这有助于缓解内部协变量偏移问题,加速模型收敛,并提高训练的稳定性。
公式:

给定一个输入向量 x = ( x _ 1 , x _ 2 , . . . , x _ D ) x = (x\_1, x\_2, ..., x\_D) x=(x_1,x_2,...,x_D),层归一化的计算过程如下:

  1. 计算均值 μ \mu μ:
    μ = 1 D ∑ x i \mu = \frac{1}{D} \sum{x_i} μ=D1∑xi

  2. 计算标准差 σ \sigma σ:
    σ = 1 D ∑ ( x i − μ ) 2 \sigma = \sqrt{\frac{1}{D} \sum{(x_i-\mu)^2}} σ=D1∑(xi−μ)2

  3. 归一化:
    x ^ _ i = x _ i − μ σ 2 + ϵ \hat{x}\_i = \frac{x\_i - \mu}{\sqrt{\sigma^2+\epsilon}} x^_i=σ2+ϵ x_i−μ
    ϵ \epsilon ϵ是一个很小的正数,防止除数为0

  4. 缩放和平移(可选,模型学习参数 γ \gamma γ 和 β \beta β):
    y _ i = γ x ^ _ i + β y\_i = \gamma \hat{x}\_i + \beta y_i=γx^_i+β

PyTorch代码:

python 复制代码
class LayerNorm(nn.Module):
 def __init__(self, features_dim, epsilon=1e-6):
 super(LayerNorm, self).__init__()
 self.gamma = nn.Parameter(torch.ones(features_dim))
 self.beta = nn.Parameter(torch.zeros(features_dim))
 self.epsilon = epsilon


 def forward(self, x):
 mean = x.mean(-1, keepdim=True)
 std = x.std(-1, keepdim=True)
 normalized_x = (x - mean) / (std + self.epsilon)
 return self.gamma * normalized_x + self.beta


# 示例
features_dim = 512
layer_norm = LayerNorm(features_dim)
input_tensor = torch.randn(32, 50, features_dim)
output_tensor = layer_norm(input_tensor)
print("LayerNorm Output Shape:", output_tensor.shape)

在Encoder-Decoder架构中,LayerNorm通常应用于每个子层的输入和输出(例如,在自注意力之后、FFN之后),并结合残差连接。

三、构建更深的网络:残差连接 (Residual Connection)

随着网络层数的加深,梯度消失或梯度爆炸以及网络退化问题会变得更加严重。残差连接通过将浅层的输入直接添加到深层的输出来缓解这些问题。

公式:

一个包含残差连接的模块可以表示为:

Output = SubLayer ( x ) + x \text{Output} = \text{SubLayer}(x) + x Output=SubLayer(x)+x

其中:

  • x x x 是子层的输入。

  • SubLayer ( x ) \text{SubLayer}(x) SubLayer(x) 是子层(例如,自注意力或FFN)的输出。

  • Output \text{Output} Output 是残差连接后的输出。

    注意力计算

    h = x + self.attention.forward(self.attention_norm(x))

    经过前馈神经网络

    out = h + self.feed_forward.forward(self.fnn_norm(h))

在Transformer等现代Encoder-Decoder架构中,每个Encoder和Decoder层都包含多个子层(如自注意力和FFN),并且每个子层都使用了残差连接和层归一化。

在上文代码中,self.attention_norm 和 self.fnn_norm 都是 LayerNorm 层,self.attn 是注意力层,而 self.feed_forward 是前馈神经网络。

四、上下文的理解者:Encoder

Encoder的任务是将输入的序列(例如,一个句子中的单词序列)转换成一个固定长度的上下文向量(或一系列上下文向量),该向量应该捕获输入序列的关键信息。

在Transformer中,Encoder由多个相同的Encoder层堆叠而成。每个Encoder层通常包含以下子层:

  1. 多头自注意力 (Multi-Head Self-Attention):让模型能够关注输入序列中的不同位置,捕捉词语之间的依赖关系。
  2. 残差连接和层归一化 (Add & Norm):将自注意力的输出与输入相加,并进行层归一化。
  3. 前馈神经网络 (Feed-Forward Network):对每个位置的表示进行独立的非线性变换。
  4. 残差连接和层归一化 (Add & Norm) :将FFN的输出与输入相加,并进行层归一化。
    简化版Encoder代码框架:
python 复制代码
class EncoderLayer(nn.Module):
 def __init__(self, dim, self_attn, feed_forward, dropout):
 super(EncoderLayer, self).__init__()
 self.self_attn = self_attn
 self.feed_forward = feed_forward
 self.norm1 = LayerNorm(dim)
 self.norm2 = LayerNorm(dim)
 self.dropout = nn.Dropout(dropout)


 def forward(self, x, mask):
 # Self-Attention
 attn_output = self.self_attn(x, x, x, mask=mask) # Query, Key, Value
 x = x + self.dropout(self.norm1(attn_output))
 # Feed-Forward
 ff_output = self.feed_forward(x)
 x = x + self.dropout(self.norm2(ff_output))
 return x


class Encoder(nn.Module):
 def __init__(self, num_layers, dim, self_attn, feed_forward, dropout):
 super(Encoder, self).__init__()
 self.layers = nn.ModuleList([
 EncoderLayer(dim, self_attn, feed_forward, dropout)
 for _ in range(num_layers)
 ])
 self.norm = LayerNorm(dim)


 def forward(self, x, mask):
 for layer in self.layers:
 x = layer(x, mask)
 return self.norm(x)


# 注意:这里的 self_attn 和 feed_forward 需要是实际的模块实例

Encoder的最终输出是一系列与输入序列长度相同的上下文向量,每个向量都编码了输入序列在对应位置周围的信息。

五、目标序列的生成器:Decoder

Decoder的任务是接收Encoder产生的上下文信息,并逐步生成目标输出序列。

与Encoder类似,Transformer中的Decoder也由多个相同的Decoder层堆叠而成。每个Decoder层通常包含以下子层:

  1. 带掩码的多头自注意力 (Masked Multi-Head Self-Attention):与Encoder的自注意力类似,但引入了掩码机制,防止Decoder在生成某个位置的词语时"看到"未来的词语。这保证了Decoder是自回归的。
  2. 残差连接和层归一化 (Add & Norm)
  3. 多头注意力 (Multi-Head Attention) 对Encoder输出的注意力:Decoder的这一层接收Decoder前一层的输出和Encoder的输出,让Decoder能够关注输入序列的相关部分,类似于传统Encoder-Decoder模型中的注意力机制。
  4. 残差连接和层归一化 (Add & Norm)
  5. 前馈神经网络 (Feed-Forward Network)
  6. 残差连接和层归一化 (Add & Norm)

简化版Decoder代码框架:

python 复制代码
class DecoderLayer(nn.Module):
 def __init__(self, dim, self_attn, cross_attn, feed_forward, dropout):
 super(DecoderLayer, self).__init__()
 self.self_attn = self_attn
 self.cross_attn = cross_attn
 self.feed_forward = feed_forward
 self.norm1 = LayerNorm(dim)
 self.norm2 = LayerNorm(dim)
 self.norm3 = LayerNorm(dim)
 self.dropout = nn.Dropout(dropout)


 def forward(self, x, memory, src_mask, tgt_mask):
 # Masked Self-Attention
 attn1_output = self.self_attn(x, x, x, mask=tgt_mask)
 x = x + self.dropout(self.norm1(attn1_output))
 # Attention over Encoder output (memory)
 attn2_output = self.cross_attn(x, memory, memory, mask=src_mask)
 x = x + self.dropout(self.norm2(attn2_output))
 # Feed-Forward
 ff_output = self.feed_forward(x)
 x = x + self.dropout(self.norm3(ff_output))
 return x


class Decoder(nn.Module):
 def __init__(self, num_layers, dim, self_attn, cross_attn, feed_forward, dropout):
 super(Decoder, self).__init__()
 self.layers = nn.ModuleList([
 DecoderLayer(dim, self_attn, cross_attn, feed_forward, dropout)
 for _ in range(num_layers)
 ])
 self.norm = LayerNorm(dim)


 def forward(self, x, memory, src_mask, tgt_mask):
 for layer in self.layers:
 x = layer(x, memory, src_mask, tgt_mask)
 return self.norm(x)


# 注意:这里的 self_attn, cross_attn 和 feed_forward 需要是实际的模块实例

Decoder的输入是目标序列的embedding(通常会进行移位和掩码操作),以及Encoder的输出(通常称为memory)。Decoder逐步生成目标序列,直到生成结束符。

相关推荐
知青春之宝贵2 小时前
BEV感知-课程学习详细记录(自动驾驶之心课程)
学习
HeartException2 小时前
Spring Boot + MyBatis Plus + SpringAI + Vue 毕设项目开发全解析(源码)
人工智能·spring boot·学习
蚊子不吸吸3 小时前
在Docker、KVM、K8S常见主要命令以及在Centos7.9中部署的关键步骤学习备存
linux·学习·docker·kubernetes·centos·k8s·kvm
谷雪_6584 小时前
学习华为 ensp 的学习心得体会
网络·学习·华为·网络工程·ensp
懒惰的bit4 小时前
STM32F103C8T6 学习笔记摘要(三)
笔记·stm32·学习
killer Curry5 小时前
B站 XMCVE Pwn入门课程学习笔记(4)
笔记·学习
F_D_Z5 小时前
【格与代数系统】偏序关系、偏序集与全序集
学习·模糊理论·偏序集·全序集·二元关系
懒惰的bit5 小时前
STM32F103C8T6 学习笔记摘要(一)
笔记·stm32·学习
李元豪5 小时前
强化学习所有所有算法对比【智鹿ai学习记录】
人工智能·学习·算法