RNN
介绍
RNN(Recurrent Neural Network) 是一种用于处理序列数据的神经网络模型,特别适用于
处理时间序列、语音、文本等具有顺序关系的数据。简单的神经网络都是水平方向的延申,RNN
可以关联不同的时刻,RNN的输出不仅取决于当前时刻的输出还取决于上一时刻隐藏层的输出。
(神经网络具有某种记忆的能力),公式表示如下:
结构
假设,这里我们的输入神经元(input_size ,输入特征数为3),隐藏层神经元个数是2
(hidden_size)。
假设现在我们想做一个情感识别任务,我们的词汇表有3个字,标签集是正面和反面,训练数据
是“不好看”,标签是负面。首先要做的是把词用词向量表示。
它的计算结构如下图:这里假设,这里我们的输入神经元(input_size ,输入特征数为3),隐藏
层神经元个数是2(hidden_size), RNN 层数是1(num_layers = 1),时间步是3(seq_length
= 3),样本的个数是1(batch_size = 1 )。
简单的输入和输出
import torch import torch.nn as nn import numpy as np # 设置随机种子以确保结果可复现 torch.manual_seed(42) # --- 1. 数据和维度 --- V = 3 # 输入特征数 (词汇表大小) H = 2 # 隐藏层大小 (H=2) # 输入数据: “不 好 看” (One-Hot) # 形状: (Seq_len=3, Input_size=3) X_data_np = np.array([ [0, 1, 0], # 不 [1, 0, 0], # 好 [0, 0, 1] # 看 ], dtype=np.float32) # 转换为 RNN 要求的形状: (T, B, V) => (3, 1, 3) X_tensor = torch.from_numpy(X_data_np).unsqueeze(1) SEQ_LEN = X_tensor.shape[0] BATCH_SIZE = X_tensor.shape[1] print("--- 步骤 1: 输入数据 X_tensor ---") print(f"数据形状 (T, B, V): {X_tensor.shape}") print(X_tensor) print("-" * 50) # --- 2. 简单的 RNN 模块实例化 --- # 定义 RNN 层 (包含了 U, W, b_h) rnn_module = nn.RNN( input_size=V, #词汇表的大小 hidden_size=H, #隐藏层的个数 num_layers=1, #RNN的层数 batch_first=False, # T 优先 nonlinearity='tanh' ) # 初始化初始隐藏状态 h0 (对应 h_{-1}) # 形状: (Num_layers, Batch_size, Hidden_size) => (1, 1, 2) h0 = torch.zeros(1, BATCH_SIZE, H) # --- 3. 前向传播并显示输出 --- # 执行 RNN 前向传播 # out_all_steps: 所有时间步的隐藏状态 (h1, h2, h3) # h_n_final: 最后一个时间步的隐藏状态 (h3) out_all_steps, h_n_final = rnn_module(X_tensor, h0) print("--- 步骤 2: RNN 模块输出解析 ---") # [A] out_all_steps: 所有时间步的隐藏状态 (h1, h2, h3) print("\n[A] out_all_steps (所有隐藏状态 h1...h3):") print(f"形状 (T, B, H): {out_all_steps.shape} -> ({SEQ_LEN}, {BATCH_SIZE}, {H})") print("---------------------------------------") print(f"h1 状态 ('不' 后的隐藏状态):\n{out_all_steps[0, 0].detach().numpy()}") print(f"h2 状态 ('好' 后的隐藏状态):\n{out_all_steps[1, 0].detach().numpy()}") print(f"h3 状态 ('看' 后的隐藏状态):\n{out_all_steps[2, 0].detach().numpy()}") # [B] h_n_final: 最终隐藏状态 (h3) print("\n[B] h_n_final (最终隐藏状态 h_T=h3):") print(f"形状 (L, B, H): {h_n_final.shape}") print("---------------------------------------") print(f"数据 (这是用于最终分类的关键向量):\n{h_n_final.squeeze().detach().numpy()}")
图解
unsqueeze(1) 在索引 1 的位置增加了一个维度,将形状变为 (3, 1, 3),符合 RNN 输入的标
准格式 (seq_len, batch, input_size),如果需要处理更大的批次数据,只需调整 B 维度的大小
即可。
模型的输入是隐藏状态,它的大小是(1*1*2),第1个数字代表RNN的层数,第2个1代表批次
数,第3个数字代表隐藏层神经元的个数。还有我们的数据输入,它的大小是(3*1*3)第1个数字
代表时间步(或者说是词汇表的大小),第2个数字代表批次数,第3个数字代表特征数(或者说是
输入神经元的个数)。然后进入到模型中(需要定义rnn的层数,词汇表的大小特征数,还有隐藏
层的个数),模型的最后的输出是所有隐藏层的状态和最后一个隐藏层的状态。
tensor.squeeze().detach()
先简化张量形状,再脱离梯度追踪,常用于提取模型输出的最终结果(如分类概率)。
编辑
代码实现
导入需要的包
import torch import torch.nn as nn import torch.optim as optim import numpy as np # 设置随机种子以确保结果可复现 torch.manual_seed(42)
定义模型
把最后一层隐藏层的状态当成神经元的输入,然后再经过激活函数计算概率。
# --- 1. 模型定义: SimpleRNN (Many-to-One) --- class SimpleRNN(nn.Module): def __init__(self, input_size, hidden_size, output_size): super(SimpleRNN, self).__init__() self.hidden_size = hidden_size # 定义 RNN 层 (包含了 U, W, b_h) # 激活函数默认为 'tanh' self.rnn = nn.RNN( input_size=input_size, hidden_size=hidden_size, num_layers=1, batch_first=False, # 输入形状要求 (序列长度, Batch大小, 特征维度) nonlinearity='tanh' ) # 定义全连接层 (包含了 V_out, b_o) # 将最后一个隐藏状态 (H=2) 映射到最终输出 (C=2) self.fc = nn.Linear(hidden_size, output_size) # PyTorch 会自动初始化 U, W, V_out 等参数,但我们也可以自定义初始化 # 实现前向传播 def forward(self, x): # x 形状: (Seq_len=3, Batch_size=1, Input_size=3) # 初始化初始隐藏状态 h_0 (对应 h_{-1}) # 形状: (Num_layers, Batch_size, Hidden_size) => (1, 1, 2) h0 = torch.zeros(1, x.size(1), self.hidden_size) # 1. RNN 计算 # out: 每一时间步的隐藏状态 (不使用) # h_n: 最后一个时间步的隐藏状态 (我们需要的) out, h_n = self.rnn(x, h0) # 2. 提取并准备最终隐藏状态 (h_T) # h_n 形状是 (1, 1, 2),我们需要移除第一个维度 (Num_layers) final_hidden_state = h_n.squeeze(0) # 形状变为 (Batch_size, Hidden_size) => (1, 2) # 3. 全连接层到输出 Logits (O) # output 形状: (Batch_size, Output_size) => (1, 2) logits = self.fc(final_hidden_state) return logits
定义数据
# --- 2. 数据准备与模型实例化 --- # 模型维度 V = 3 # 词汇表大小 H = 2 # 隐藏层大小 (H=2) C = 2 # 类别数 # 标签映射: 0 -> 正面, 1 -> 负面 LABEL_MAP = {0: "正面", 1: "负面"} # 学习率 LEARNING_RATE = 0.01 # 输入数据: “不 好 看” (One-Hot) # (Seq_len, Batch_size, Input_size) => (3, 1, 3) X_data_np = np.array([ [0, 1, 0], # 不 [1, 0, 0], # 好 [0, 0, 1] # 看 ], dtype=np.float32) X_tensor = torch.from_numpy(X_data_np).unsqueeze(1) print(X_tensor) # 目标标签: 负面 [0, 1],索引是 1 Y_true_index = torch.tensor([1], dtype=torch.long) # 形状: (1,) print(Y_true_index)
前向传播
# 模型、损失函数、优化器实例化 model = SimpleRNN(V, H, C) criterion = nn.CrossEntropyLoss() # 交叉熵损失 (自动包含 Softmax 激活) optimizer = optim.SGD(model.parameters(), lr=LEARNING_RATE) # -------------------------- 3. 训练循环:实现一次参数更新 -------------------------- print("--- 步骤 3: 准备进行参数更新 ---") print(f"初始模型参数总数: {sum(p.numel() for p in model.parameters())}") # --- 清除旧梯度 (重要步骤!) --- optimizer.zero_grad() # --------------------- A. 前向传播 (Forward Pass) --------------------- logits = model(X_tensor) print("\n--- 步骤 A: 前向传播结果 ---") print(f"输出 Logits (未激活) 形状: {logits.shape} -> {logits.detach().numpy()}")
反向传播更新参数
# --------------------- B. 损失计算 --------------------- loss = criterion(logits, Y_true_index) print(f"初始交叉熵损失 J: {loss.item():.6f}") # --------------------- C. 反向传播 (Backward Pass / Autograd) --------------------- loss.backward() print("\n--- 步骤 C: 反向传播结果 ---") rnn_weights = model.rnn.weight_hh_l0 print(f"RNN 循环权重 W 的梯度 (部分):\n{rnn_weights.grad.detach().numpy()[:2, :2]}")
全部代码
import torch import torch.nn as nn import torch.optim as optim import numpy as np # 设置随机种子以确保结果可复现 torch.manual_seed(42) # --- 1. 模型定义: SimpleRNN (Many-to-One) --- class SimpleRNN(nn.Module): def __init__(self, input_size, hidden_size, output_size): super(SimpleRNN, self).__init__() self.hidden_size = hidden_size # 定义 RNN 层 (包含了 U, W, b_h) # 激活函数默认为 'tanh' self.rnn = nn.RNN( input_size=input_size, hidden_size=hidden_size, num_layers=1, batch_first=False, # 输入形状要求 (序列长度, Batch大小, 特征维度) nonlinearity='tanh' ) # 定义全连接层 (包含了 V_out, b_o) # 将最后一个隐藏状态 (H=2) 映射到最终输出 (C=2) self.fc = nn.Linear(hidden_size, output_size) # PyTorch 会自动初始化 U, W, V_out 等参数,但我们也可以自定义初始化 # 实现前向传播 def forward(self, x): # x 形状: (Seq_len=3, Batch_size=1, Input_size=3) # 初始化初始隐藏状态 h_0 (对应 h_{-1}) # 形状: (Num_layers, Batch_size, Hidden_size) => (1, 1, 2) h0 = torch.zeros(1, x.size(1), self.hidden_size) # 1. RNN 计算 # out: 每一时间步的隐藏状态 (不使用) # h_n: 最后一个时间步的隐藏状态 (我们需要的) out, h_n = self.rnn(x, h0) # 2. 提取并准备最终隐藏状态 (h_T) # h_n 形状是 (1, 1, 2),我们需要移除第一个维度 (Num_layers) final_hidden_state = h_n.squeeze(0) # 形状变为 (Batch_size, Hidden_size) => (1, 2) # 3. 全连接层到输出 Logits (O) # output 形状: (Batch_size, Output_size) => (1, 2) logits = self.fc(final_hidden_state) return logits # --- 2. 数据准备与模型实例化 --- # 模型维度 V = 3 # 词汇表大小 H = 2 # 隐藏层大小 (H=2) C = 2 # 类别数 # 标签映射: 0 -> 正面, 1 -> 负面 LABEL_MAP = {0: "正面", 1: "负面"} # 学习率 LEARNING_RATE = 0.01 # 输入数据: “不 好 看” (One-Hot) # (Seq_len, Batch_size, Input_size) => (3, 1, 3) X_data_np = np.array([ [0, 1, 0], # 不 [1, 0, 0], # 好 [0, 0, 1] # 看 ], dtype=np.float32) X_tensor = torch.from_numpy(X_data_np).unsqueeze(1) # 目标标签: 负面 [0, 1],索引是 1 Y_true_index = torch.tensor([1], dtype=torch.long) # 形状: (1,) # 模型、损失函数、优化器实例化 model = SimpleRNN(V, H, C) criterion = nn.CrossEntropyLoss() # 交叉熵损失 (自动包含 Softmax 激活) optimizer = optim.SGD(model.parameters(), lr=LEARNING_RATE) # -------------------------- 3. 训练循环:实现一次参数更新 -------------------------- print("--- 步骤 3: 准备进行参数更新 ---") print(f"初始模型参数总数: {sum(p.numel() for p in model.parameters())}") # --- 清除旧梯度 (重要步骤!) --- optimizer.zero_grad() # --------------------- A. 前向传播 (Forward Pass) --------------------- logits = model(X_tensor) print("\n--- 步骤 A: 前向传播结果 ---") print(f"输出 Logits (未激活) 形状: {logits.shape} -> {logits.detach().numpy()}") # --------------------- B. 损失计算 --------------------- loss = criterion(logits, Y_true_index) print(f"初始交叉熵损失 J: {loss.item():.6f}") # --------------------- C. 反向传播 (Backward Pass / Autograd) --------------------- loss.backward() print("\n--- 步骤 C: 反向传播结果 ---") rnn_weights = model.rnn.weight_hh_l0 print(f"RNN 循环权重 W 的梯度 (部分):\n{rnn_weights.grad.detach().numpy()[:2, :2]}") # --------------------- D. 参数更新 --------------------- optimizer.step() print("\n--- 步骤 D: 参数更新完成 ---") print(f"更新后的 W (部分):\n{model.rnn.weight_hh_l0.detach().numpy()[:2, :2]}") # --------------------- 4. 验证:再次前向传播并输出分类结果 --------------------- logits_new = model(X_tensor) loss_new = criterion(logits_new, Y_true_index) probabilities_new = torch.softmax(logits_new, dim=1) # (1, 2) # 找到预测概率最高的索引 predicted_index = torch.argmax(probabilities_new, dim=1).item() predicted_label = LABEL_MAP[predicted_index] print("\n--- 步骤 4: 更新后验证与最终预测 ---") print(f"新的交叉熵损失 J_new: {loss_new.item():.6f}") print(f"新的预测概率 (正面/负面): {probabilities_new.detach().numpy()}") # 最终输出 print("---------------------------------------") print(f"✅ 模型最终预测情感: **{predicted_label}** (索引: {predicted_index})") print(f"✅ 损失变化: {loss.item():.6f} -> {loss_new.item():.6f}")
LSTM
介绍
LSTM(Long Short-Term Memory) 是一种特殊类型的循环神经网络(RNN),旨在解决传
统 RNN 在处理长序列时面临的梯度消失问题。LSTM 引入了特殊的结构(称为“门”)来控制信息
的流动,能够更好地捕捉长期依赖关系。(简单的RNN在信息记忆方面效果不好)
与RNN相比,除了输入和前一时刻还要包括当前时刻(日记的信息Ct),包含了,两个更细致
的操作,删除旧日记,增添新日记,f1函数就像是一个橡皮擦,根据昨天的记忆ht-1和今天的输入
Xt,决定要修改日记中的那些记录。 f2函数就像是一个铅笔,根据昨天的记忆ht-1和今天的输入
Xt,增加日记中的那些记录。f1和 f2进行运算得到新的日记啦。LSTM 就像延缓记忆衰退的良药,
可以带来更好的结果。
在一些教科书上,认为经过sigmoid之后的才是门。Ct和Ht只是状态更新。
输入门(Input Gate):决定当前输入的哪部分信息应该被存储到“记忆单元”(Cell State)中。
编辑
遗忘门(Forget Gate):决定前一时刻的“记忆单元”中哪些信息应该被遗忘。
编辑
输出门(Output Gate):决定记忆单元中的信息在当前时间步输出时的贡献。
编辑
简单的输入和输出
和上面不同的是这里的输出部分多了一个细胞的状态,然后输出也是多了一个细胞的状态,最后做
预测的时候也是让隐藏状态作为神经元的输入。(一般是用隐藏层的状态)。
import torch import torch.nn as nn import numpy as np # 设置随机种子以确保结果可复现 torch.manual_seed(42) # --- 1. 数据和维度 --- V = 3 # 输入特征数 (词汇表大小) H = 2 # 隐藏层大小 (H=2) # 输入数据: “不 好 看” (One-Hot) # 形状: (Seq_len=3, Input_size=3) X_data_np = np.array([ [0, 1, 0], # 不 [1, 0, 0], # 好 [0, 0, 1] # 看 ], dtype=np.float32) # 转换为 LSTM 要求的形状: (T, B, V) => (3, 1, 3) X_tensor = torch.from_numpy(X_data_np).unsqueeze(1) SEQ_LEN = X_tensor.shape[0] BATCH_SIZE = X_tensor.shape[1] print("--- 步骤 1: 输入数据 X_tensor ---") print(f"输入张量形状 (T, B, V): {X_tensor.shape}") print(X_tensor) print("-" * 50) # --- 2. 简单的 LSTM 模块实例化 --- # 定义 LSTM 层 (包含了 W_f, W_i, W_o, W_c 等所有权重和偏置) lstm_module = nn.LSTM( input_size=V, hidden_size=H, num_layers=1, batch_first=False # T 优先 ) # 初始化初始状态 (h0, c0) # 形状: (Num_layers, Batch_size, Hidden_size) => (1, 1, 2) # h0: 初始隐藏状态 h0 = torch.zeros(1, BATCH_SIZE, H) # c0: 初始细胞状态 (这是 LSTM 独有的!) c0 = torch.zeros(1, BATCH_SIZE, H) # --- 3. 前向传播并显示输出 --- # 执行 LSTM 前向传播 # out_all_steps: 所有时间步的隐藏状态 (h1, h2, h3) # (h_n_final, c_n_final): 最终的隐藏状态 h3 和细胞状态 c3 out_all_steps, (h_n_final, c_n_final) = lstm_module(X_tensor, (h0, c0)) print("--- 步骤 2: LSTM 模块输出解析 ---") # [A] out_all_steps: 所有时间步的隐藏状态 (h1, h2, h3) print("\n[A] out_all_steps (所有隐藏状态 h1...h3):") print(f"形状 (T, B, H): {out_all_steps.shape} -> ({SEQ_LEN}, {BATCH_SIZE}, {H})") print("---------------------------------------") print(f"h1 状态 ('不' 后的隐藏状态):\n{out_all_steps[0, 0].detach().numpy()}") print(f"h2 状态 ('好' 后的隐藏状态):\n{out_all_steps[1, 0].detach().numpy()}") print(f"h3 状态 ('看' 后的隐藏状态):\n{out_all_steps[2, 0].detach().numpy()}") # [B] h_n_final: 最终隐藏状态 (h3) print("\n[B] h_n_final (最终隐藏状态 h_T=h3):") print(f"形状 (L, B, H): {h_n_final.shape}") print("---------------------------------------") print(f"数据 (这是用于最终分类的关键向量):\n{h_n_final.squeeze().detach().numpy()}") # [C] c_n_final: 最终细胞状态 (c3) - LSTM 独有 print("\n[C] c_n_final (最终细胞状态 c_T=c3):") print(f"形状 (L, B, H): {c_n_final.shape}") print("---------------------------------------") print(f"数据 (这是 LSTM 内部的长时记忆):\n{c_n_final.squeeze().detach().numpy()}")
代码实现全过程
import torch import torch.nn as nn import torch.optim as optim import numpy as np # 设置随机种子以确保结果可复现 torch.manual_seed(42) # --- 1. 模型定义: SimpleLSTM (Many-to-One) --- class SimpleLSTM(nn.Module): def __init__(self, input_size, hidden_size, output_size): super(SimpleLSTM, self).__init__() self.hidden_size = hidden_size # 定义 LSTM 层 (替代 nn.RNN) self.lstm = nn.LSTM( input_size=input_size, hidden_size=hidden_size, num_layers=1, batch_first=False # 输入形状要求 (序列长度, Batch大小, 特征维度) ) # 定义全连接层 (与 RNN 结构相同) # 将最后一个隐藏状态 (H=2) 映射到最终输出 (C=2) self.fc = nn.Linear(hidden_size, output_size) # 实现前向传播 def forward(self, x): # x 形状: (Seq_len=3, Batch_size=1, Input_size=3) # 1. 初始化初始状态 (h_0, c_0) # 形状: (Num_layers, Batch_size, Hidden_size) => (1, 1, 2) h0 = torch.zeros(1, x.size(1), self.hidden_size) c0 = torch.zeros(1, x.size(1), self.hidden_size) # ❗ 必须初始化细胞状态 # 2. LSTM 计算 # out: 每一时间步的隐藏状态 # (h_n, c_n): 最后一个时间步的 (隐藏状态, 细胞状态) out, (h_n, c_n) = self.lstm(x, (h0, c0)) # 3. 提取并准备最终隐藏状态 (h_T) # h_n 形状是 (1, 1, 2),移除第一个维度 (Num_layers) final_hidden_state = h_n.squeeze(0) # 形状变为 (Batch_size, Hidden_size) => (1, 2) # 4. 全连接层到输出 Logits (O) logits = self.fc(final_hidden_state) return logits # --- 2. 数据准备与模型实例化 (与原代码相同) --- # 模型维度 V = 3 # 词汇表大小 H = 2 # 隐藏层大小 (H=2) C = 2 # 类别数 # 标签映射 LABEL_MAP = {0: "正面", 1: "负面"} LEARNING_RATE = 0.01 # 输入数据: “不 好 看” (One-Hot) X_data_np = np.array([ [0, 1, 0], # 不 [1, 0, 0], # 好 [0, 0, 1] # 看 ], dtype=np.float32) X_tensor = torch.from_numpy(X_data_np).unsqueeze(1) # 目标标签: 负面 [0, 1],索引是 1 Y_true_index = torch.tensor([1], dtype=torch.long) # 形状: (1,) # ❗ 模型实例化:使用 SimpleLSTM model = SimpleLSTM(V, H, C) criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=LEARNING_RATE) # -------------------------- 3. 训练循环:实现一次参数更新 -------------------------- print("--- 步骤 3: 准备进行参数更新 (LSTM) ---") print(f"初始模型参数总数: {sum(p.numel() for p in model.parameters())}") # 注意:LSTM 参数数量远多于 RNN (约是 4 倍) # --- 清除旧梯度 --- optimizer.zero_grad() # --------------------- A. 前向传播 (Forward Pass) --------------------- logits = model(X_tensor) print("\n--- 步骤 A: LSTM 前向传播结果 ---") print(f"输出 Logits (未激活) 形状: {logits.shape} -> {logits.detach().numpy()}") # --------------------- B. 损失计算 --------------------- loss = criterion(logits, Y_true_index) print(f"初始交叉熵损失 J: {loss.item():.6f}") # --------------------- C. 反向传播 (Backward Pass) --------------------- loss.backward() print("\n--- 步骤 C: LSTM 反向传播结果 ---") # 检查 LSTM 循环权重 (W_hh_l0) 的梯度是否已计算 # LSTM 的权重名包含了 gate 的信息,因此形状是 4*H x H lstm_weights = model.lstm.weight_hh_l0 print(f"LSTM 循环权重 W 的梯度 (部分, 对应 HxH 矩阵):\n{lstm_weights.grad.detach().numpy()[:2, :2]}") # --------------------- D. 参数更新 --------------------- optimizer.step() print("\n--- 步骤 D: 参数更新完成 ---") print(f"更新后的 LSTM 循环权重 W (部分):\n{model.lstm.weight_hh_l0.detach().numpy()[:2, :2]}") # --------------------- 4. 验证:再次前向传播并输出分类结果 --------------------- logits_new = model(X_tensor) loss_new = criterion(logits_new, Y_true_index) probabilities_new = torch.softmax(logits_new, dim=1) predicted_index = torch.argmax(probabilities_new, dim=1).item() predicted_label = LABEL_MAP[predicted_index] print("\n--- 步骤 4: 更新后验证与最终预测 ---") print(f"新的交叉熵损失 J_new: {loss_new.item():.6f}") print(f"新的预测概率 (正面/负面): {probabilities_new.detach().numpy()}") # 最终输出 print("---------------------------------------") print(f"✅ 模型最终预测情感: **{predicted_label}** (索引: {predicted_index})") print(f"✅ 损失变化: {loss.item():.6f} -> {loss_new.item():.6f}")
GRU
GRU的参数量更少
重置门和更新门
重置门 (Reset Gate, r_t)
直观含义:要不要把「过去的旧记忆」清空 / 弱化
在公式中的体现:它通过哈达玛乘积直接乘在历史记忆上:r_t *h_t-1。
更新门 (Update Gate, z_t)
直观含义:旧记忆 与 新记忆 各占多少比例
z_t = 0:多用新记忆,少用旧记忆
候选隐藏态
当前时刻,结合「被重置门筛选后的旧记忆」+「当前新输入」,生成的临时新记忆 。
用更新门做权重,加权融合「候选新记忆」和「历史旧记忆」,得到当前时刻最终记忆,保留多少
新候选记忆,保留多少旧历史记忆。
重置门:先决定「要不要忘掉过去」,过滤旧记忆
更新门:用过滤后的旧记忆 + 当前输入,生成新临时记忆
候选隐态:决定「旧记忆留多少、新记忆用多少」,生成当前新的临时记忆
最终隐态:按比例新旧融合,输出当前最终记忆,传给下一时刻