第1天:认识RNN及RNN初步实验(预测下一个数字)

article/2025/6/22 6:34:58

RNN(循环神经网络) 是一种专门设计用来处理序列数据的人工神经网络。它的核心思想是能够“记住”之前处理过的信息,并将其用于当前的计算,这使得它非常适合处理具有时间顺序或上下文依赖关系的数据。

核心概念:循环连接

RNN 与普通的前馈神经网络(如多层感知机)最根本的区别在于它引入了循环连接

  1. 输入序列: RNN 接收一个序列作为输入,例如:
    • 一个句子(单词序列)
    • 一段语音(音频帧序列)
    • 股票价格(时间点上的价格序列)
    • 视频帧序列
  2. 隐藏状态: RNN 内部维护一个隐藏状态。这个状态就像是网络的“记忆”,它总结了网络在处理当前输入之前所“看到”的所有历史信息。
  3. 循环: 在处理序列中的每一个元素时:
    • 当前输入 + 前一个隐藏状态: RNN 不仅考虑当前的输入数据,还会结合上一个时间步的隐藏状态。
    • 计算新状态和输出: 网络根据当前的输入前一个隐藏状态,通过激活函数(通常是 tanh 或 ReLU)计算出新的隐藏状态
    • 产生输出(可选): 这个新的隐藏状态可以用来产生当前时间步的输出(例如,预测序列的下一个元素、对当前元素进行分类等)。
    • 传递状态: 这个新的隐藏状态被传递到下一个时间步,作为处理下一个输入元素的“记忆”基础。
  4. 共享权重: RNN 在处理序列的每个时间步时,使用的是同一套网络参数(权重和偏置)。这意味着它用相同的规则(相同的函数)来处理序列中的每一个元素。

为什么需要 RNN?

传统的前馈神经网络在处理序列数据时存在明显缺陷:

  1. 固定输入大小: 它们要求输入数据具有固定长度(例如,固定数量的像素或特征)。序列数据(如不同长度的句子)很难直接适配。
  2. 忽略顺序/上下文: 它们独立处理每个输入,没有机制来记住或利用之前输入的信息。对于序列数据,顺序和上下文往往至关重要(例如,句子中单词的含义依赖于前面的单词)。

RNN 的优势(擅长处理的任务)

RNN 的“记忆”特性使其特别擅长以下任务:

  1. 自然语言处理:
    • 语言建模(预测下一个单词)
    • 机器翻译(输入源语言序列,输出目标语言序列)
    • 文本生成(写文章、诗歌、代码)
    • 情感分析(理解句子情感)
    • 命名实体识别(识别文本中的人名、地名等)
    • 语音识别(音频信号序列转文本)
  2. 时间序列预测:
    • 股票价格预测
    • 天气预测
    • 销售预测
  3. 序列标注:
    • 词性标注(为句子中的每个单词标注词性)
    • 语音分割
  4. 其他序列任务:
    • 视频分析(理解视频帧序列)
    • 音乐生成(生成音符序列)
    • 聊天机器人(对话是轮次的序列)

RNN 的挑战(经典问题)

标准的 RNN(通常称为 Vanilla RNN)在处理长序列时会遇到一些著名的困难:

  1. 梯度消失问题: 这是最主要的问题。在通过时间反向传播训练网络时,梯度(用于更新权重的误差信号)会随着时间步的推移而呈指数级衰减。当序列很长时,早期的输入对最终输出的影响梯度会变得非常小,导致网络很难学习到长距离的依赖关系(即序列开头的信息对序列结尾的影响)。
  2. 梯度爆炸问题: 相对少见但也会发生,梯度变得非常大,导致训练不稳定。
  3. 有限的记忆容量: 隐藏状态的大小固定,难以记住非常久远的信息。
  4. 计算效率: 由于其顺序处理的特性(必须一个时间步接一个时间步地计算),难以并行化,训练速度可能较慢。

改进的 RNN 结构

为了解决标准 RNN 的问题(尤其是梯度消失问题),研究者开发了更强大的变体:

  1. LSTM: 长短期记忆网络。引入了“门”机制(输入门、遗忘门、输出门)和一个额外的“细胞状态”,可以更精细地控制信息的保留、遗忘和输出,特别擅长学习长期依赖关系。
  2. GRU: 门控循环单元。LSTM 的一个简化版本,只有两个门(更新门、重置门),计算效率更高,在很多时候能达到与 LSTM 相当甚至更好的效果。
    总结:
    RNN 是一种具有内部循环连接的网络,允许信息在序列处理过程中持续存在(即拥有“记忆”)。这种结构使其成为处理序列数据(文本、语音、时间序列等)的自然选择。尽管标准 RNN 存在梯度消失等限制其处理长序列能力的问题,但其强大的变体 LSTM 和 GRU 已被证明在广泛的序列建模任务中极其成功,是深度学习和人工智能领域的一项基础性技术。当你看到机器翻译、智能对话或文本生成时,背后很可能就有 RNN 或其变体(LSTM/GRU)在工作。

RNN运行原理图:

deepseek_mermaid_20250601_8118f8.png

尝试(代码实现)

import torch  
import torch.nn as nn  # 设置随机种子保证可重复性  
torch.manual_seed(42)  # =====================  
# 1. 准备模拟数据  
# =====================  
# 创建一个简单的序列数据集:输入序列和对应的目标值  
# 输入序列: [0,1,2] -> 目标: 3; [1,2,3] -> 目标: 4 等  
seq_length = 3  # 输入序列长度  
data_size = 100  # 数据集大小  # 生成特征数据 (100个样本,每个样本是长度为3的序列)  
X = torch.stack([torch.arange(i, i + seq_length) for i in range(data_size)]).float()  
# 生成目标数据 (每个序列的下一个数字)  
y = torch.stack([torch.tensor([i + seq_length]) for i in range(data_size)]).float()  print("输入数据形状:", X.shape)  # torch.Size([100, 3])  
print("目标数据形状:", y.shape)  # torch.Size([100, 1])  # =====================  
# 2. 定义RNN模型  
# =====================  
class SimpleRNN(nn.Module):  def __init__(self, input_size, hidden_size, output_size):  super(SimpleRNN, self).__init__()  # 定义RNN层参数  self.hidden_size = hidden_size  # RNN层: (input_size, hidden_size)  # batch_first=True 表示输入数据的格式为 (batch, seq_len, features)        self.rnn = nn.RNN(  input_size=input_size,  hidden_size=hidden_size,  batch_first=True  # 输入/输出张量的第一个维度是batch  )  # 全连接输出层  self.fc = nn.Linear(hidden_size, output_size)  def forward(self, x):  # 初始化隐藏状态 (num_layers, batch_size, hidden_size)        # 这里只有一层RNN,所以num_layers=1  h0 = torch.zeros(1, x.size(0), self.hidden_size)  # RNN前向传播  # out: 所有时间步的隐藏状态 (batch, seq_len, hidden_size)        # hn: 最后一个时间步的隐藏状态 (1, batch, hidden_size)        out, hn = self.rnn(x.unsqueeze(2), h0)  # 注意: x.unsqueeze(2) 将形状从 [batch, seq_len] -> [batch, seq_len, 1]        # 因为RNN期望每个时间步有特征维度  # 只取最后一个时间步的输出 (许多序列任务只关心最后输出)  last_output = out[:, -1, :]  # 通过全连接层  output = self.fc(last_output)  return output  # =====================  
# 3. 初始化模型和训练设置  
# =====================  
# 模型参数  
input_size = 1  # 每个时间步的输入特征维度 (这里我们输入的是单个数字)  
hidden_size = 32  # RNN隐藏层大小  
output_size = 1  # 输出维度 (预测单个数字)  # 创建模型实例  
model = SimpleRNN(input_size, hidden_size, output_size)  # 损失函数和优化器  
criterion = nn.MSELoss()  # 均方误差损失 (回归任务)  
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)  # =====================  
# 4. 训练循环  
# =====================  
num_epochs = 100  for epoch in range(num_epochs):  # 前向传播  outputs = model(X)  loss = criterion(outputs, y)  # 反向传播和优化  optimizer.zero_grad()  # 清除历史梯度  loss.backward()  # 反向传播计算梯度  optimizer.step()  # 更新参数  # 每10轮打印一次损失  if (epoch + 1) % 10 == 0:  print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')  # =====================  
# 5. 测试模型  
# =====================  
# 创建测试序列  
test_seq = torch.tensor([7, 8, 9]).float().unsqueeze(0)  # 添加batch维度  
print("\n测试序列:", test_seq.squeeze().tolist())  # 预测下一个数字  
with torch.no_grad():  # 禁用梯度计算  prediction = model(test_seq)  print(f"预测下一个数字: {prediction.item():.1f} (期望值: 10.0)")

代码解释

"""  
关键组件说明:  
1. nn.RNN 层:  - input_size: 每个时间步输入的特征维度  - hidden_size: RNN隐藏状态的大小  - batch_first: 输入/输出张量格式为 (batch, seq_len, features)  
2. 隐藏状态 (hidden state):   - 存储序列的历史信息  - 初始化为全零张量 (形状: [num_layers, batch_size, hidden_size])  - 在序列处理过程中不断更新    
1. 前向传播流程:  - 输入形状: [batch_size, seq_len] -> 需要扩展为 [batch_size, seq_len, input_size]   - RNN输出两个结果:  out: 所有时间步的隐藏状态 [batch, seq_len, hidden_size]        hn: 最后一个时间步的隐藏状态 [num_layers, batch, hidden_size]   - 我们通常只关心最后一个时间步的输出 (out[:, -1, :])   - 将最后输出传入全连接层得到预测结果  4. 训练过程:  - 使用均方误差(MSE)作为回归任务的损失函数  - Adam优化器更新权重  - 循环多个epoch使模型学习序列模式  
"""

运行结果:

输入数据形状: torch.Size([100, 3])
目标数据形状: torch.Size([100, 1])
Epoch [10/100], Loss: 3239.7668
Epoch [20/100], Loss: 2960.7173
Epoch [30/100], Loss: 2692.1509
Epoch [40/100], Loss: 2433.7822
Epoch [50/100], Loss: 2194.8665
Epoch [60/100], Loss: 1978.9081
Epoch [70/100], Loss: 1784.7439
Epoch [80/100], Loss: 1610.4106
Epoch [90/100], Loss: 1454.0194
Epoch [100/100], Loss: 1313.7197测试序列: [7.0, 8.0, 9.0]
预测下一个数字: 10.1 (期望值: 10.0)

**改变一:修改hidden_size

hidden_size=8
输入数据形状: torch.Size([100, 3])
目标数据形状: torch.Size([100, 1])
Epoch [10/100], Loss: 3465.6765
Epoch [20/100], Loss: 3378.4478
Epoch [30/100], Loss: 3299.8362
Epoch [40/100], Loss: 3214.9880
Epoch [50/100], Loss: 3129.9934
Epoch [60/100], Loss: 3046.5991
Epoch [70/100], Loss: 2965.3425
Epoch [80/100], Loss: 2886.3469
Epoch [90/100], Loss: 2809.4622
Epoch [100/100], Loss: 2734.4731测试序列: [7.0, 8.0, 9.0]
预测下一个数字: 8.5 (期望值: 10.0)
hidden_size=16
输入数据形状: torch.Size([100, 3])
目标数据形状: torch.Size([100, 1])
Epoch [10/100], Loss: 3403.8201
Epoch [20/100], Loss: 3222.7463
Epoch [30/100], Loss: 3082.8389
Epoch [40/100], Loss: 2934.8223
Epoch [50/100], Loss: 2788.8025
Epoch [60/100], Loss: 2648.1326
Epoch [70/100], Loss: 2514.1960
Epoch [80/100], Loss: 2387.1860
Epoch [90/100], Loss: 2266.7705
Epoch [100/100], Loss: 2152.6624测试序列: [7.0, 8.0, 9.0]
预测下一个数字: 9.9 (期望值: 10.0)
hidden_size=64
输入数据形状: torch.Size([100, 3])
目标数据形状: torch.Size([100, 1])
Epoch [10/100], Loss: 2981.0894
Epoch [20/100], Loss: 2482.7971
Epoch [30/100], Loss: 2024.7319
Epoch [40/100], Loss: 1634.8406
Epoch [50/100], Loss: 1318.6863
Epoch [60/100], Loss: 1065.7627
Epoch [70/100], Loss: 864.2839
Epoch [80/100], Loss: 703.8886
Epoch [90/100], Loss: 575.9725
Epoch [100/100], Loss: 473.6202测试序列: [7.0, 8.0, 9.0]
预测下一个数字: 9.9 (期望值: 10.0)
#### hidden_size=128
输入数据形状: torch.Size([100, 3])
目标数据形状: torch.Size([100, 1])
Epoch [10/100], Loss: 2416.3115
Epoch [20/100], Loss: 1606.8107
Epoch [30/100], Loss: 1012.2352
Epoch [40/100], Loss: 632.2427
Epoch [50/100], Loss: 399.0681
Epoch [60/100], Loss: 257.7469
Epoch [70/100], Loss: 171.4580
Epoch [80/100], Loss: 117.6267
Epoch [90/100], Loss: 83.0710
Epoch [100/100], Loss: 60.3334测试序列: [7.0, 8.0, 9.0]
预测下一个数字: 10.0 (期望值: 10.0)
hidden_size=256
输入数据形状: torch.Size([100, 3])
目标数据形状: torch.Size([100, 1])
Epoch [10/100], Loss: 1700.6941
Epoch [20/100], Loss: 687.7560
Epoch [30/100], Loss: 225.4265
Epoch [40/100], Loss: 75.9410
Epoch [50/100], Loss: 28.6406
Epoch [60/100], Loss: 12.8711
Epoch [70/100], Loss: 6.8923
Epoch [80/100], Loss: 4.2395
Epoch [90/100], Loss: 2.8668
Epoch [100/100], Loss: 6.7286测试序列: [7.0, 8.0, 9.0]
预测下一个数字: 10.8 (期望值: 10.0)

改个脚本来自动测试一下:

import torch  
import torch.nn as nn  
from xformers.benchmarks.benchmark_sddmm import results  # 设置随机种子保证可重复性  
torch.manual_seed(42)  # =====================  
# 1. 准备模拟数据  
# =====================  
# 创建一个简单的序列数据集:输入序列和对应的目标值  
# 输入序列: [0,1,2] -> 目标: 3; [1,2,3] -> 目标: 4 等  
seq_length = 3  # 输入序列长度  
data_size = 100  # 数据集大小  # 生成特征数据 (100个样本,每个样本是长度为3的序列)  
X = torch.stack([torch.arange(i, i + seq_length) for i in range(data_size)]).float()  
# 生成目标数据 (每个序列的下一个数字)  
y = torch.stack([torch.tensor([i + seq_length]) for i in range(data_size)]).float()  print("输入数据形状:", X.shape)  # torch.Size([100, 3])  
print("目标数据形状:", y.shape)  # torch.Size([100, 1])  # =====================  
# 2. 定义RNN模型  
# =====================  
class SimpleRNN(nn.Module):  def __init__(self, input_size, hidden_size, output_size):  super(SimpleRNN, self).__init__()  # 定义RNN层参数  self.hidden_size = hidden_size  # RNN层: (input_size, hidden_size)  # batch_first=True 表示输入数据的格式为 (batch, seq_len, features)        self.rnn = nn.RNN(  input_size=input_size,  hidden_size=hidden_size,  batch_first=True  # 输入/输出张量的第一个维度是batch  )  # 全连接输出层  self.fc = nn.Linear(hidden_size, output_size)  def forward(self, x):  # 初始化隐藏状态 (num_layers, batch_size, hidden_size)        # 这里只有一层RNN,所以num_layers=1  h0 = torch.zeros(1, x.size(0), self.hidden_size)  # RNN前向传播  # out: 所有时间步的隐藏状态 (batch, seq_len, hidden_size)        # hn: 最后一个时间步的隐藏状态 (1, batch, hidden_size)        out, hn = self.rnn(x.unsqueeze(2), h0)  # 注意: x.unsqueeze(2) 将形状从 [batch, seq_len] -> [batch, seq_len, 1]        # 因为RNN期望每个时间步有特征维度  # 只取最后一个时间步的输出 (许多序列任务只关心最后输出)  last_output = out[:, -1, :]  # 通过全连接层  output = self.fc(last_output)  return output  # =====================  
# 3. 初始化模型和训练设置  
# =====================  
# 模型参数  
input_size = 1  # 每个时间步的输入特征维度 (这里我们输入的是单个数字)  
hidden_size = [8, 16, 32, 64, 128, 256]  # RNN隐藏层大小  
output_size = 1  # 输出维度 (预测单个数字)  
results = {}  # 创建模型实例  
for hs in hidden_size:  model = SimpleRNN(input_size, hs, output_size)  # 损失函数和优化器  criterion = nn.MSELoss()  # 均方误差损失 (回归任务)  optimizer = torch.optim.Adam(model.parameters(), lr=0.01)  # =====================  # 4. 训练循环  # =====================  num_epochs = 100  for epoch in range(num_epochs):  # 前向传播  outputs = model(X)  loss = criterion(outputs, y)  # 反向传播和优化  optimizer.zero_grad()  # 清除历史梯度  loss.backward()  # 反向传播计算梯度  optimizer.step()  # 更新参数  # 每10轮打印一次损失  if (epoch + 1) % 10 == 0:  print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')  # =====================  # 5. 测试模型  # =====================  # 创建测试序列  test_seq = torch.tensor([7, 8, 9]).float().unsqueeze(0)  # 添加batch维度  print("\n测试序列:", test_seq.squeeze().tolist())  # 预测下一个数字  with torch.no_grad():  # 禁用梯度计算  prediction = model(test_seq)  # print(f"预测下一个数字: {prediction.item():.1f} (期望值: 10.0)")  results[hs] = {  "final_loss": loss.item(),  "prediction": prediction.item()  }  # 打印结果  
for size, res in results.items():  print(f"hidden_size: {size:<3}, final_loss: {res['final_loss']:.5f}, prediction: {res['prediction']:.2f} (期望值=10)")

结果:

测试序列: [7.0, 8.0, 9.0]
hidden_size: 8  , final_loss: 2734.47314, prediction: 8.52 (期望值=10)
hidden_size: 16 , final_loss: 2143.95459, prediction: 9.99 (期望值=10)
hidden_size: 32 , final_loss: 1293.59521, prediction: 9.95 (期望值=10)
hidden_size: 64 , final_loss: 448.14648, prediction: 9.90 (期望值=10)
hidden_size: 128, final_loss: 60.28671, prediction: 9.60 (期望值=10)
hidden_size: 256, final_loss: 2.17465, prediction: 10.08 (期望值=10)

这个损失率…,有点离谱啊。
尝试解决:归一化

import torch  
import torch.nn as nn  
from xformers.benchmarks.benchmark_sddmm import results  # 设置随机种子保证可重复性  
torch.manual_seed(42)  # =====================  
# 1. 准备模拟数据  
# =====================  
# 创建一个简单的序列数据集:输入序列和对应的目标值  
# 输入序列: [0,1,2] -> 目标: 3; [1,2,3] -> 目标: 4 等  
seq_length = 3  # 输入序列长度  
data_size = 100  # 数据集大小  # 生成特征数据 (100个样本,每个样本是长度为3的序列)  
X_raw = torch.stack([torch.arange(i, i + seq_length) for i in range(data_size)]).float()  
# 生成目标数据 (每个序列的下一个数字)  
y_raw = torch.stack([torch.tensor([i + seq_length]) for i in range(data_size)]).float()  # print("输入数据形状:", X.shape)  # torch.Size([100, 3])  
# print("目标数据形状:", y.shape)  # torch.Size([100, 1])  # 数据归一化  
def normalize(tensor, mean=None, std=None):  if mean is None or std is None:  mean = tensor.mean()  std = tensor.std()  return (tensor - mean) / std, mean, std  # 归一化数据,并保存归一化参数  
X, X_mean, X_std = normalize(X_raw)  
y, y_mean, y_std = normalize(y_raw)  print(f"归一化后输入范围: {X.min().item():.4f} to {X.max().item():.4f}")  
print(f"归一化后目标范围: {y.min().item():.4f} to {y.max().item():.4f}")  # =====================  
# 2. 定义RNN模型  
# =====================  
class SimpleRNN(nn.Module):  def __init__(self, input_size, hidden_size, output_size):  super(SimpleRNN, self).__init__()  # 定义RNN层参数  self.hidden_size = hidden_size  # RNN层: (input_size, hidden_size)  # batch_first=True 表示输入数据的格式为 (batch, seq_len, features)        self.rnn = nn.RNN(  input_size=input_size,  hidden_size=hidden_size,  batch_first=True  # 输入/输出张量的第一个维度是batch  )  # 全连接输出层  self.fc = nn.Linear(hidden_size, output_size)  def forward(self, x):  # 初始化隐藏状态 (num_layers, batch_size, hidden_size)        # 这里只有一层RNN,所以num_layers=1  h0 = torch.zeros(1, x.size(0), self.hidden_size)  # RNN前向传播  # out: 所有时间步的隐藏状态 (batch, seq_len, hidden_size)        # hn: 最后一个时间步的隐藏状态 (1, batch, hidden_size)        out, hn = self.rnn(x.unsqueeze(2), h0)  # 注意: x.unsqueeze(2) 将形状从 [batch, seq_len] -> [batch, seq_len, 1]        # 因为RNN期望每个时间步有特征维度  # 只取最后一个时间步的输出 (许多序列任务只关心最后输出)  last_output = out[:, -1, :]  # 通过全连接层  output = self.fc(last_output)  return output  # =====================  
# 3. 初始化模型和训练设置  
# =====================  
# 模型参数  
input_size = 1  # 每个时间步的输入特征维度 (这里我们输入的是单个数字)  
hidden_size = [8, 16, 32, 64, 128, 256]  # RNN隐藏层大小  
output_size = 1  # 输出维度 (预测单个数字)  
results = {}  # 创建模型实例  
for hs in hidden_size:  model = SimpleRNN(input_size, hs, output_size)  # 损失函数和优化器  criterion = nn.MSELoss()  # 均方误差损失 (回归任务)  optimizer = torch.optim.Adam(model.parameters(), lr=0.01)  # =====================  # 4. 训练循环  # =====================  num_epochs = 100  for epoch in range(num_epochs):  # 前向传播  outputs = model(X)  loss = criterion(outputs, y)  # 反向传播和优化  optimizer.zero_grad()  # 清除历史梯度  loss.backward()  # 反向传播计算梯度  # 梯度裁剪(防止梯度爆炸)  torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)  optimizer.step()  # 更新参数  # 每10轮打印一次损失  if (epoch + 1) % 10 == 0:  print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')  # =====================  # 5. 测试模型  # =====================  # 创建测试序列  test_seq = torch.tensor([7, 8, 9]).float()  test_seq_norm = (test_seq - X_mean) / X_std  test_seq_norm = test_seq_norm.unsqueeze(0)  # 添加batch维度  # print("\n测试序列:", test_seq.squeeze().tolist())  # 预测下一个数字  with torch.no_grad():  # 禁用梯度计算  prediction_norm = model(test_seq_norm)  # print(f"预测下一个数字: {prediction.item():.1f} (期望值: 10.0)")  # 将预测结果反归一化到原始空间  prediction = prediction_norm * y_std + y_mean  results[hs] = {  "final_loss": loss.item(),  "prediction": prediction.item()  }  # 打印结果  
for size, res in results.items():  print(f"hidden_size: {size:<3}, final_loss: {res['final_loss']:.5f}, prediction: {res['prediction']:.2f} (期望值=10)")

运行结果:

hidden_size: 8  , final_loss: 0.00042, prediction: 10.35 (期望值=10)
hidden_size: 16 , final_loss: 0.00073, prediction: 10.73 (期望值=10)
hidden_size: 32 , final_loss: 0.00129, prediction: 10.18 (期望值=10)
hidden_size: 64 , final_loss: 0.00069, prediction: 10.54 (期望值=10)
hidden_size: 128, final_loss: 0.00040, prediction: 10.63 (期望值=10)
hidden_size: 256, final_loss: 0.01118, prediction: 8.55 (期望值=10)

结论

hidden_size(隐藏层大小)是RNN中最关键的超参数之一,它直接影响模型的表达能力、学习能力和最终结果。让我们深入分析它与结果的关系:

1. 模型容量与表达能力
  • 较小的hidden_size (如4-16):
    • ✅ 训练更快,内存占用小
    • ❌ 模型容量低,只能学习简单模式
    • ➡️ 适合简单序列(如等差数列)
  • 较大的hidden_size (如64-256):
    • ✅ 能学习复杂模式和非线性关系
    • ❌ 需要更多数据和训练时间
    • ❌ 可能过拟合(记忆训练数据但泛化差)
    • ➡️ 适合真实世界数据(如自然语言)
2. 实际建议(针对初学者)
  1. 起始点:从hidden_size=32开始(良好平衡点)
  2. 调整策略
    • 如果欠拟合(训练损失高)→ 增大hidden_size
    • 如果过拟合(训练损失低但测试差)→ 减小hidden_size或添加正则化

http://www.hkcw.cn/article/jFdEQFqEes.shtml

相关文章

蒋雨融回应读哈佛争议 否认靠走后门

蒋雨融回应读哈佛争议 否认靠走后门!6月3日,哈佛中国学生蒋雨融在微博上再次回应身份争议。她表示自己持有中国护照,没有任何海外国家的绿卡或身份。她在2020年暑假期间在瑞信实习直到9月开学,疫情期间都是远程工作。开学后,她在9月至11月离职,但仍在同一个组群聊中偶尔回…

东南亚电商市场缘何增长强劲 多因素协同发力

东南亚电商市场缘何增长强劲 多因素协同发力。在印度尼西亚雅加达的Tanah Abang市场,一位商家正在直播卖货。新加坡星展集团联合市场洞察公司Cube发布报告显示,东南亚电子商务正快速增长,预计到2030年,该地区电商销售额将升至4100亿美元,与2024年的1840亿美元相比,实现14…

上海通报迪士尼内游客打架 拍照争执引发冲突

近日,一段上海迪士尼度假区内情侣与带孩子的夫妇发生纠纷的视频引起了网友的关注。据浦东公安分局通报,5月31日18时许,警方接到报警称迪士尼乐园内有人打架。经初步调查,22岁的闫某某与女友在拍照时,因36岁的刘某某夫妻的女儿进入拍摄画面,双方发生口角并引发肢体冲突。冲…

美议员提议少操心中国多关心自己:中国学生成绩都高于美国学生

美议员提议少操心中国多关心自己。6月1日,美国国会众议员杰克奥金克洛斯接受福克斯新闻采访。其表示“中国有13亿人,中国学生在数学和自然科学方面的成绩都高于美国学生。我们少操心中国,多关心下自己吧!”美议员提议少操心中国多关心自己责任编辑:0882

马克龙接见大巴黎全队 庆祝欧冠胜利

当地时间本周日,法国总统马克龙在爱丽舍宫接见了巴黎圣日耳曼的球员们,并发表了讲话。巴黎圣日耳曼刚刚击败国际米兰赢得欧冠冠军,马克龙对球队表示祝贺:“感谢你们让法国年轻人梦想成真,终于是巴黎了!”他回忆起自己32年前在慕尼黑激动不已的经历,称赞球队以崇高而惊人…

C#数字图像处理(三)

文章目录 前言1.图像平移1.1 图像平移定义1.2 图像平移编程实例 2.图像镜像2.1 图像镜像定义2.2 图像镜像编程实例 3.图像缩放3.1 图像缩放定义3.2 灰度插值法3.3 图像缩放编程实例 4.图像旋转4.1 图像旋转定义4.2 图像旋转编程实例 前言 在某种意义上来说&#xff0c;图像的几…

一个「体虚」的人,手和脸上都有迹象

传统医学理论将“虚”分为阴虚、阳虚、气虚、血虚。其中,血虚是因为体内血不足,气虚则是气不足,阴虚与阳虚是人体脏腑阴阳平衡被打破而产生的证候。《生命时报》综合多位专家观点,详解不同体虚的特点,并给出调理方案。不同体虚,怎么分辨?中国中医科学院西苑医院脑病科副…

莫雷加德谈与樊振东成队友 超级期待他的加入

当地时间6月1日,樊振东即将加盟的德国萨尔布吕肯乒乓球俱乐部在欧洲乒乓球冠军联赛男团决赛中第三次夺得欧冠冠军。队中的瑞典选手莫雷加德凭借稳定发挥获评赛事MVP。欧洲乒联发布了莫雷加德赛后采访,他连用三个“really”表达对樊振东加盟的期待,并表示能和樊振东做队友非常…

杨幂酱园弄王许梅造型 举手投足间故事感十足

杨幂酱园弄王许梅造型,一个眼神带着悲悯、坚毅、不屈,举手投足间故事感十足,巨期待杨幂塑造的王许梅了!责任编辑:zx0002

高阶数据结构——并查集

1.并查集原理 在一些应用问题中&#xff0c;需要将n个不同的元素划分成一些不相交的集合。开始时&#xff0c;每个元素自成一个单元素集合&#xff0c;然后按一定的规律将归于同一组元素的集合合并。在此过程中要反复用到查询某一个元素归属于那个集合的运算。适合于描述这类问…

价值约4万欧元的马克龙蜡像被盗 环保人士抗议行动

当地时间6月2日,三名自称是游客的抗议者进入法国格雷万蜡像馆,盗走了法国总统马克龙的蜡像,并通过应急出口离开。该蜡像价值4万欧元。这三人实际上是环保人士,包括两名女子和一名男子。他们假扮游客进入蜡像馆后更换衣服冒充工作人员,用毯子包裹蜡像并通过紧急出口带出,将…

好消息连放8天坏消息4个月后 下轮假期待国庆中秋

今天是端午节假期的最后一天,大家可能已经在期待下一次休假了。根据国务院办公厅关于2025年部分节假日安排的通知,接下来的假期将在四个月后的国庆节和中秋节。这两个节日合并放假,总共将有八天的假期。责任编辑:zhangxiaohua

滕州男童家门口走失 家属急寻线索

6月1日,有网友发布视频称山东省滕州市姜屯镇黄坡村一名10岁男孩赵某超走失。孩子家属非常焦急,希望通过网络社交媒体寻求帮助。当天下午,赵某超的外公王先生表示,通过查看家门口的监控发现,孩子是5月31日下午5时左右走失的。当时孩子在家门口的监控中消失了几分钟后又返回…

苏敏走完戛纳红毯回国房车被淹了 人生重启的勇气

苏敏走完戛纳红毯回国房车被淹了 人生重启的勇气!2024年5月,法国戛纳电影节红毯上出现了一个让全场意外的身影。一位61岁的中国阿姨面对闪光灯笑得舒展又笃定。她穿着苏绣旗袍,头发利落地盘起,自信从容。当主持人介绍这是电影《出走的决心》原型苏敏时,现场响起一片惊呼。…

巴黎世家平角短裤造型裙子4500一条 时尚界的另类创新

巴黎世家平角短裤造型裙子4500一条!近日,巴黎世家推出的一款女款半身裙引发热议。不少网友认为这款裙子造型与平角短裤相似,纷纷吐槽“看不懂时尚”。据巴黎世家官网介绍,这款深蓝色弹力平纹针织半身裙亮相于2025秋季系列,采用弹力棉混纺平纹针织面料,设计为平角短裤造型…

当地回应松原一地出现龙卷风:受灾情况正在统计

网传吉林松原出现龙卷风,目击者拍摄到震撼画面镇政府:受灾情况正在统计。责任编辑:zx0002

【Kubernetes-1.30】--containerd部署

文章目录 一、环境准备1.1 三台服务器1.2 基础配置&#xff08;三台机通用&#xff09;1.3 关闭 Swap&#xff08;必须&#xff09;1.4 关闭防火墙&#xff08;可选&#xff09;1.5 加载必要模块 & 配置内核参数 二、安装容器运行时&#xff08;containerd 推荐&#xff09…

RGB888色彩格式转RGB565格式

一个RGB888格式的色彩值是三字节&#xff0c;有24个bit 一个RGB565格式的色彩值是双字节&#xff0c;有16个bit 将R值的高5位取出&#xff0c;G值的高6位去除&#xff0c;B值的高5位取出&#xff0c;按从高到低的顺序码放在一起后就是RGB565色彩值了 R (RGB888 & 0xF800…

java28

1.IO流续集 字节流和字符流的使用场景&#xff1a; 综合练习&#xff1a; 拷贝文件夹&#xff1a; 文件加密&#xff1a; 一个数字异或两次某个数字就会得到自己本身 修改文件中的数据&#xff1a; 改进&#xff1a; &#xff0c;bom头占3个字节 查看IDEA里面保存的文件是否…

【笔记】MinGW-w64 环境下安装 meson 工具链

&#x1f4dd; MinGW-w64 环境下安装 meson 工具链的安装笔记 ✅ 安装目标 在 MSYS2 MinGW-w64 x86_64 环境中&#xff0c;使用 pacman 安装构建工具 meson 及其依赖。 &#x1f9f0; 安装命令 pacman -S meson &#x1f4e6; 安装内容概览 包名版本描述pkgconf2.4.3-1提供…