目录
[1. 基本变量与集合](#1. 基本变量与集合)
[2. 门控机制 (Gates)](#2. 门控机制 (Gates))
[3. 激活函数](#3. 激活函数)
[4. 运算符](#4. 运算符)
[5. 权重与偏置参数](#5. 权重与偏置参数)
介绍



Ct可能是-2到2之间,所以后面再来一个tanh缩小范围-1到1之间
1. 基本变量与集合
-
t:当前的时间步(Time step)。
-
Xt:在 t时刻的输入数据(Input)。
-
Ht :在 t时刻的隐藏状态(Hidden state),也称为输出值。它包含了当前时间步的上下文信息。
-
Ct:在 t时刻的细胞状态(Cell state),即记忆(Memory)。这是 LSTM 的核心,负责在不同的时间步之间传递信息。
-
Ct−1:上一个时间步的细胞状态。
-
C~t :在 t时刻的候选记忆(Candidate memory),表示当前输入可能带来的新信息。
2. 门控机制 (Gates)
LSTM 通过三个"门"来控制信息的保留与遗忘,它们的值都在 0 到 1 之间:
-
It(Input gate / 输入门) :决定当前哪些新信息(C~t)会被加入到细胞状态 Ct中。
-
Ft(Forget gate / 遗忘门) :决定从上一个细胞状态 Ct−1中丢弃(遗忘)多少旧信息。
-
Ot(Output gate / 输出门):决定当前细胞状态 Ct中的哪些信息会被输出到隐藏状态 Ht。
3. 激活函数
-
σ(Sigmoid):Sigmoid 激活函数。它将数值映射到 (0,1)的区间内,非常适合用来做门控开关。
-
tanh :双曲正切激活函数。它将数值映射到 (−1,1)的区间内,主要用于调节数据的范围并生成候选记忆。
4. 运算符
-
⊙(Hadamard product 哈达玛乘积 / 逐元素乘法):表示两个相同维度的矩阵或向量对应位置的元素相乘。在图中表现为带有圆圈的乘号(⨀)。
-
+:矩阵或向量的加法运算,用于信息的累加。
5. 权重与偏置参数
-
W:权重矩阵(Weight matrix)。
- 下标规则:例如 Wxi表示输入 X到输入门 I的权重; Whf表示隐藏状态 H到遗忘门 F的权重。
-
b:偏置向量(Bias vector)。
- 下标对应相应的门,例如 bi是输入门的偏置,bf是遗忘门的偏置。
实现:
python
# 导入必要的库
import torch
from torch import nn
from d2l import torch as d2l
# 设置超参数
batch_size, num_steps = 32, 35
# 加载时间机器数据集,返回迭代器和词汇表
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
# 初始化 LSTM 的参数
def get_lstm_params(vocab_size, num_hiddens, device):
"""
vocab_size: 词汇表大小(也是输入和输出的维度)
num_hiddens: 隐藏单元数量
device: 运行设备(CPU/GPU)
"""
num_inputs = num_outputs = vocab_size
# 辅助函数:生成均值为0、标准差0.01的正态分布随机张量
def normal(shape):
return torch.randn(size=shape, device=device) * 0.01
# 辅助函数:一次性生成输入权重、隐藏权重和偏置(均为三维)
def three():
return (
normal((num_inputs, num_hiddens)), # 输入到门的权重
normal((num_hiddens, num_hiddens)), # 上一隐藏状态到门的权重
torch.zeros(num_hiddens, device=device) # 偏置
)
# 四个门的参数:输入门、遗忘门、输出门、候选记忆元
W_xi, W_hi, b_i = three() # 输入门参数
W_xf, W_hf, b_f = three() # 遗忘门参数
W_xo, W_ho, b_o = three() # 输出门参数
W_xc, W_hc, b_c = three() # 候选记忆元参数
# 输出层参数(将隐藏状态映射到词汇表大小的输出)
W_hq = normal((num_hiddens, num_outputs))
b_q = torch.zeros(num_outputs, device=device)
# 将所有参数放入列表,并开启梯度追踪
params = [
W_xi, W_hi, b_i,
W_xf, W_hf, b_f,
W_xo, W_ho, b_o,
W_xc, W_hc, b_c,
W_hq, b_q
]
for param in params:
param.requires_grad_(True)
return params
# 初始化 LSTM 状态(隐藏状态 H 和细胞状态 C)
def init_lstm_state(batch_size, num_hiddens, device):
"""
返回两个全零张量:(H, C),形状均为 (batch_size, num_hiddens)
"""
return (
torch.zeros((batch_size, num_hiddens), device=device),
torch.zeros((batch_size, num_hiddens), device=device)
)
# LSTM 前向传播
def lstm(inputs, state, params):
"""
inputs: 输入序列,形状 (num_steps, batch_size, vocab_size)
state: 初始状态 (H, C)
params: 所有可学习参数
"""
# 解包参数
[W_xi, W_hi, b_i,
W_xf, W_hf, b_f,
W_xo, W_ho, b_o,
W_xc, W_hc, b_c,
W_hq, b_q] = params
# 获取初始状态
(H, C) = state
outputs = []
# 按时间步遍历输入
for X in inputs:
# 输入门 I
I = torch.sigmoid((X @ W_xi) + (H @ W_hi) + b_i)
# 遗忘门 F
F = torch.sigmoid((X @ W_xf) + (H @ W_hf) + b_f)
# 输出门 O
O = torch.sigmoid((X @ W_xo) + (H @ W_ho) + b_o)
# 候选记忆元 C_tilda
C_tilda = torch.tanh((X @ W_xc) + (H @ W_hc) + b_c)
# 更新细胞状态:遗忘旧信息 + 输入新信息
C = F * C + I * C_tilda
# 更新隐藏状态:输出门控制 tanh(C) 的输出
H = O * torch.tanh(C)
# 计算当前时间步的输出(通过输出层)
Y = (H @ W_hq) + b_q
outputs.append(Y)
# 将所有时间步的输出拼接在一起,并返回最终状态
return torch.cat(outputs, dim=0), (H, C)
# 训练和预测
vocab_size, num_hiddens, device = len(vocab), 256, d2l.try_gpu()
num_epochs, lr = 500, 1
# 创建自定义 RNN 模型(基于手动实现的 LSTM)
model = d2l.RNNModelScratch(
len(vocab), num_hiddens, device,
get_lstm_params, # 参数初始化函数
init_lstm_state, # 状态初始化函数
lstm # 前向传播函数
)
# 开始训练
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)
| 部分 | 说明 |
|---|---|
**get_lstm_params** |
初始化四个门的权重(输入、遗忘、输出、候选)和输出层的权重,每个门包含 W_x(输入→门)、W_h(隐藏→门)、b(偏置)。 |
**init_lstm_state** |
返回全零的隐藏状态 H和细胞状态 C,形状均为 (batch_size, num_hiddens)。 |
lstm前向函数 |
按时间步依次计算三个门和候选记忆,然后更新 C和 H,最后通过输出层得到每个时间步的预测。 |
| 简洁实现 | 直接用 nn.LSTM替代手动实现,更高效且稳定。 |
python
# 输入维度等于词汇表大小
num_inputs = vocab_size
# 使用 PyTorch 内置的 LSTM 层
lstm_layer = nn.LSTM(num_inputs, num_hiddens)
# 将其包装成适用于语言模型的 RNN 模型
model = d2l.RNNModel(lstm_layer, len(vocab))
model = model.to(device)
# 训练
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)

