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

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

核心概念:循环连接

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

  1. 输入序列: RNN 接收一个序列作为输入,例如:
    • 一个句子(单词序列)
    • 一段语音(音频帧序列)
    • 股票价格(时间点上的价格序列)
    • 视频帧序列
  2. 隐藏状态: RNN 内部维护一个隐藏状态。这个状态就像是网络的"记忆",它总结了网络在处理当前输入之前所"看到"的所有历史信息。
  3. 循环: 在处理序列中的每一个元素 时:
    • 当前输入 + 前一个隐藏状态: RNN 不仅考虑当前的输入数据,还会结合上一个时间步的隐藏状态。
    • 计算新状态和输出: 网络根据当前的输入前一个隐藏状态 ,通过激活函数(通常是 tanhReLU)计算出新的隐藏状态
    • 产生输出(可选): 这个新的隐藏状态可以用来产生当前时间步的输出(例如,预测序列的下一个元素、对当前元素进行分类等)。
    • 传递状态: 这个新的隐藏状态被传递到下一个时间步,作为处理下一个输入元素的"记忆"基础。
  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运行原理图:

尝试(代码实现)

python 复制代码
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)")

代码解释

python 复制代码
"""  
关键组件说明:  
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使模型学习序列模式  
"""

运行结果:

bash 复制代码
输入数据形状: 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
bash 复制代码
输入数据形状: 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
bash 复制代码
输入数据形状: 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
bash 复制代码
输入数据形状: 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
bash 复制代码
输入数据形状: 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
bash 复制代码
输入数据形状: 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)

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

python 复制代码
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)")

结果:

bash 复制代码
测试序列: [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)

这个损失率...,有点离谱啊。

尝试解决:归一化

python 复制代码
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)")

运行结果:

bash 复制代码
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或添加正则化
相关推荐
AI.NET 极客圈20 分钟前
.NET 原生驾驭 AI 新基建实战系列(四):Qdrant ── 实时高效的向量搜索利器
数据库·人工智能·.net
用户214118326360227 分钟前
dify案例分享--告别手工录入!Dify 工作流批量识别电子发票,5分钟生成Excel表格
前端·人工智能
SweetRetry28 分钟前
前端依赖管理实战:从臃肿到精简的优化之路
前端·人工智能
Icoolkj36 分钟前
Komiko 视频到视频功能炸裂上线!
人工智能·音视频
LLM大模型37 分钟前
LangChain篇-提示词工程应用实践
人工智能·程序员·llm
TiAmo zhang40 分钟前
人机融合智能 | “人智交互”跨学科新领域
人工智能
算家计算1 小时前
6GB显存玩转SD微调!LoRA-scripts本地部署教程,一键炼出专属AI画师
人工智能·开源
YYXZZ。。1 小时前
PyTorch——非线性激活(5)
人工智能·pytorch·python
孤独野指针*P1 小时前
释放模型潜力:浅谈目标检测微调技术(Fine-tuning)
人工智能·深度学习·yolo·计算机视觉·目标跟踪
机器学习之心1 小时前
Transformer-BiGRU多变量时序预测(Matlab完整源码和数据)
深度学习·matlab·transformer·bigru