扩展1 - 普通的 Encoder-Decoder 框架
1 课程概览
本课扩展讲解普通的 Encoder-Decoder 框架。编码器将输入序列通过非线性变换得到中间语义张量 C,解码器基于 C 和历史信息生成目标序列。source 和 target 可以是同一种语言(文本生成)或不同语言(机器翻译)。
2 核心概念与定义
- Encoder-Decoder 框架:句子生成句子的通用处理模型。
- source:输入序列(数据源)。
- target:目标序列(期望输出)。
- 非线性变换:通过激活函数(如 GRU)进行的变换。
- 文本生成:source 和 target 是同一种语言。
- 机器翻译:source 和 target 是不同语言。
3 模型与算法详解
普通 Encoder-Decoder 框架
输入序列 X → [编码器] → 中间语义张量 C → [解码器] → 输出序列 Y
任务分类
| source 和 target | 任务类型 | 示例 |
|---|---|---|
| 同一种语言 | 文本生成 | 中文 → 中文 |
| 不同语言 | 机器翻译 | 英文 → 中文 |
编码器(Encoder)
- 对输入句子进行编码
- 通过非线性变换(GRU 层)得到中间语义张量 C
- GRU 层 = 加权求和 + 激活函数
解码器(Decoder)
- 根据 C 和之前生成的历史预测 $Y_t$
- $S_t$ 记录历史信息(已生成的词)
历史信息的作用
预测第 10 个词,前 9 个词得知道。
- C:记录所有输入词的信息
- $S_t$:记录已生成的词的信息
示例:
- $S_2$ 记录 "welcome to"
- 生成 "Beijing" 时,需要 C(所有输入词)和 $S_2$(已生成的 "welcome to")
4 数学原理与推导
编码过程
$$h_t = f(x_t, h_{t-1})$$ $$C = h_T$$
其中 $f$ 是非线性变换(如 GRU),$h_T$ 是最后一个时间步的隐藏状态。
解码过程
$$Y_t = g(C, S_{t-1})$$
其中:
- $C$:中间语义张量(固定)
- $S_{t-1}$:上一时刻的隐藏状态(历史信息)
- $g$:解码函数
普通 Encoder-Decoder 特点
- 所有时间步用同一个 C
- $Y_1 = g(C, S_0)$
- $Y_2 = g(C, S_1)$
- $Y_3 = g(C, S_2)$
5 代码示例
import torch
import torch.nn as nn
class Encoder(nn.Module):
"""普通编码器"""
def __init__(self, input_size, hidden_size):
super().__init__()
self.gru = nn.GRU(input_size, hidden_size)
def forward(self, x, hidden):
"""
:param x: 输入序列 (seq_len, batch, input_size)
:param hidden: 初始隐藏状态
:return: output, hidden(hidden 即为 C)
"""
output, hidden = self.gru(x, hidden)
return output, hidden # hidden 是中间语义张量 C
class Decoder(nn.Module):
"""普通解码器(不加注意力)"""
def __init__(self, hidden_size, output_size):
super().__init__()
self.gru = nn.GRU(hidden_size, hidden_size)
self.linear = nn.Linear(hidden_size, output_size)
self.softmax = nn.LogSoftmax(dim=1)
def forward(self, C, hidden):
"""
:param C: 中间语义张量(固定,所有时间步用同一个)
:param hidden: 上一时刻隐藏状态 S_{t-1}
"""
# 所有时间步用同一个 C
output, hidden = self.gru(C, hidden)
output = self.linear(output)
output = self.softmax(output)
return output, hidden
class EncoderDecoder(nn.Module):
"""普通 Encoder-Decoder 框架"""
def __init__(self, encoder, decoder):
super().__init__()
self.encoder = encoder
self.decoder = decoder
def forward(self, src, trg_len):
# 1. 编码
encoder_output, C = self.encoder(src, None)
# 2. 解码(所有时间步用同一个 C)
outputs = []
hidden = None
for t in range(trg_len):
output, hidden = self.decoder(C, hidden)
outputs.append(output)
return torch.stack(outputs)
6 重难点与易错提醒
- ❗重点:普通 Encoder-Decoder 框架中,所有时间步用同一个 C。
- ❗重点:source 和 target 同语言是文本生成,不同语言是机器翻译。
- ⚠️易错:$S_t$ 记录的是已生成的词,不是输入词。
- 💡深入理解:编码器通过非线性变换(GRU)得到 C。
- 💡深入理解:解码器基于 C 和历史信息 $S_{t-1}$ 生成 $Y_t$。
7 课堂问答精选
Q1:普通 Encoder-Decoder 框架的流程是什么?
A:输入序列 X 经过编码器(非线性变换)得到中间语义张量 C,解码器基于 C 和历史信息 $S_{t-1}$ 生成目标序列 Y。
Q2:source 和 target 同语言和不同语言有什么区别?
A:同语言是文本生成(如中文 → 中文),不同语言是机器翻译(如英文 → 中文)。底层架构相同。
Q3:C 和 $S_t$ 分别记录什么信息?
A:C 记录所有输入词的信息(编码器的总输出),$S_t$ 记录已生成的词的信息(解码器的隐藏状态)。
Q4:普通 Encoder-Decoder 框架有什么问题?
A:所有时间步用同一个 C,导致翻译时对所有词的关注度相同,效果不佳。需要加入注意力机制,让每个时间步用不同的 $C_t$。
8 本课小结
- 普通 Encoder-Decoder:输入 → 编码器 → C → 解码器 → 输出。
- 编码器:非线性变换(GRU)得到 C。
- 解码器:基于 C 和 $S_{t-1}$ 生成 $Y_t$。
- 所有时间步用同一个 C。
- 同语言:文本生成;不同语言:机器翻译。
9 延伸思考
- 普通 Encoder-Decoder 框架有什么局限性?
- 如何改进以让每个时间步用不同的 C?