这个实践例子展示了如何使用框架API应用SimpleRNN层及其输入/输出形状要求。我们将处理一个直接的序列预测任务:预测简单算术序列中的下一个数字。这个练习将巩固你对构建和训练基本RNN的理解。我们的目标是训练一个RNN,使其学会[10, 20, 30]、[25, 35, 45]等序列中的规律,并预测后续的数字。例如,给定输入序列[10, 20],模型应该学会预测30。1. 生成数据首先,我们需要一些数据。我们将创建合成序列,每个数字都比前一个大10。我们将生成(input_sequence, target_value)对。我们将使用长度为3的序列作为输入,以预测第四个元素。import numpy as np def generate_sequences(n_sequences=1000, sequence_length=4): """生成算术序列(步长为10),并将其分为X和y。""" X, y = [], [] for i in range(n_sequences): start_value = np.random.randint(0, 100) sequence = [start_value + j * 10 for j in range(sequence_length)] X.append(sequence[:-1]) # 输入序列(前3个元素) y.append(sequence[-1]) # 目标值(最后一个元素) return np.array(X), np.array(y) # 生成1000个样本,每个序列总共有4个数字 # 输入序列长度(时间步)将为3 X_train_raw, y_train = generate_sequences(n_sequences=1000, sequence_length=4) print("样本输入序列 (X):", X_train_raw[0]) print("样本目标值 (y):", y_train[0]) print("X_train_raw的形状:", X_train_raw.shape) print("y_train的形状:", y_train.shape) # 预期输出: # 样本输入序列 (X): [start_val, start_val+10, start_val+20] (e.g., [42 52 62]) # 样本目标值 (y): start_val+30 (e.g., 72) # X_train_raw的形状: (1000, 3) # y_train的形状: (1000,)2. 为RNN准备数据如前所述,TensorFlow或PyTorch等框架中的标准RNN层需要特定3D格式的输入数据:(批大小, 时间步, 特征)。batch_size(批大小):训练时一次处理的序列数量。我们将让框架在训练过程中处理此项,因此我们首先关注单个样本的形状。time_steps(时间步):输入序列的长度。在此例中,其长度为3(例如,[10, 20, 30])。features(特征):每个时间步的特征数量。由于序列中的每个数字都是单个值,因此特征数量为1。我们当前的X_train_raw形状为(1000, 3)。我们需要将其重塑为(1000, 3, 1)。# 将X重塑为[样本数, 时间步, 特征数] n_samples = X_train_raw.shape[0] n_time_steps = X_train_raw.shape[1] n_features = 1 # 每个时间步只有一个特征(数字本身) X_train = X_train_raw.reshape((n_samples, n_time_steps, n_features)) print("重塑后的X_train形状:", X_train.shape) # 预期输出: # 重塑后的X_train形状: (1000, 3, 1)我们也常对数据进行归一化,以提高训练的稳定性,尽管对于这个简单的算术任务,即使不进行归一化也可能收敛。为了演示,我们通过除以一个常数(例如100)进行简单缩放。# 归一化数据(可选但推荐) X_train = X_train / 100.0 y_train = y_train / 100.03. 构建简单RNN模型现在,我们使用TensorFlow的Keras API构建模型。我们将使用一个包含以下层的Sequential模型:一个SimpleRNN层:这是核心循环层。我们需要指定隐藏状态中的单元(神经元)数量。我们从5个单元开始。我们还需要提供input_shape,它对应于(time_steps, features),不包括批大小。一个Dense层:这是一个标准的完全连接层,将生成最终的输出预测。由于我们预测的是单个数字,它将有1个单元。# 导入TensorFlow import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import SimpleRNN, Dense # 定义模型 model = Sequential([ # 具有5个隐藏单元的SimpleRNN层。 # input_shape是(时间步, 特征) -> (3, 1) SimpleRNN(5, input_shape=(n_time_steps, n_features)), # 输出层:具有1个单元的Dense层,用于预测单个后续值 Dense(1) ]) # 显示模型架构 model.summary()model.summary()的输出将显示各层、它们的输出形状以及参数数量。注意SimpleRNN层如何处理序列并输出(None, 5)的形状,其中None代表批大小,5是隐藏单元的数量。此输出(最终隐藏状态)随后被送入Dense层。4. 编译模型在训练之前,我们需要编译模型。这包括指定:optimizer(优化器):用于更新模型权重的算法(例如,'adam'、'rmsprop')。Adam是一个常用且有效的选择。loss(损失函数):衡量模型预测与实际目标值之间差异的函数。由于这是一个回归任务(预测连续值),均方误差('mse')是合适的。# 编译模型 model.compile(optimizer='adam', loss='mse')5. 训练模型我们现在可以使用fit方法训练模型。我们提供训练数据(X_train, y_train),指定epochs(遍历整个数据集的次数),并可选地设置batch_size。我们还将使用一小部分数据进行训练期间的验证,以监测模型在未见过的样本上的表现。# 训练模型 # 使用一部分数据进行验证(例如,20%) history = model.fit(X_train, y_train, epochs=30, batch_size=32, validation_split=0.2, verbose=1) # verbose=1显示进度条 # verbose=2每轮显示一行 # verbose=0不显示任何内容在训练过程中,你会看到训练集和验证集的损失随轮次逐渐降低。这表明模型正在学习规律。我们可以通过绘制损失来可视化训练过程:import matplotlib.pyplot as plt # 绘制训练和验证损失值 plt.figure(figsize=(10, 6)) plt.plot(history.history['loss'], color='#1c7ed6', label='训练损失') plt.plot(history.history['val_loss'], color='#fd7e14', linestyle='--', label='验证损失') plt.title('模型训练损失') plt.ylabel('损失 (均方误差)') plt.xlabel('轮次') plt.legend(loc='upper right') plt.grid(True, linestyle='--', alpha=0.6) plt.show(){ "data": [ { "type": "scatter", "mode": "lines", "name": "训练损失", "x": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "y": [0.08, 0.008, 0.003, 0.001, 0.0005, 0.0002, 0.0001, 8e-05, 5e-05, 3e-05, 2e-05, 1.5e-05, 1e-05, 8e-06, 6e-06, 5e-06, 4e-06, 3e-06, 2.5e-06, 2e-06, 1.8e-06, 1.5e-06, 1.3e-06, 1.1e-06, 1e-06, 9e-07, 8e-07, 7e-07, 6.5e-07, 6e-07], "line": { "color": "#1c7ed6" } }, { "type": "scatter", "mode": "lines", "name": "验证损失", "x": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "y": [0.02, 0.004, 0.001, 0.0004, 0.0001, 5e-05, 3e-05, 2e-05, 1e-05, 9e-06, 7e-06, 6e-06, 5e-06, 4e-06, 3e-06, 2.5e-06, 2e-06, 1.8e-06, 1.5e-06, 1.3e-06, 1.1e-06, 1e-06, 9e-07, 8e-07, 7e-07, 6.5e-07, 6e-07, 5.5e-07, 5e-07, 4.8e-07], "line": { "color": "#fd7e14", "dash": "dash" } } ], "layout": { "title": { "text": "模型训练损失" }, "xaxis": { "title": { "text": "轮次" } }, "yaxis": { "title": { "text": "损失 (均方误差)" } }, "legend": { "traceorder": "normal" }, "autosize": true, "grid": { "columns": 1, "rows": 1 }, "template": "plotly_white" } }训练和验证损失随轮次降低,表明模型学习成功。6. 进行预测现在,让我们测试训练好的模型。我们将创建一个新的输入序列,像训练数据一样对其进行预处理(重塑和归一化),然后使用model.predict获取输出。记住将预测结果缩放回原始范围。# 示例:预测[50, 60, 70]之后的数字 # 预期预测值:80 input_sequence_raw = np.array([50, 60, 70]) # 1. 归一化 input_sequence_normalized = input_sequence_raw / 100.0 # 2. 重塑为(1, 时间步, 特征) -> (1, 3, 1) input_sequence_reshaped = input_sequence_normalized.reshape((1, n_time_steps, n_features)) # 3. 预测 predicted_normalized = model.predict(input_sequence_reshaped) # 4. 反归一化预测值 predicted_value = predicted_normalized[0, 0] * 100.0 print(f"输入序列: {input_sequence_raw}") print(f"预测的下一个值: {predicted_value:.2f}") # 预期输出: # 输入序列: [50 60 70] # 预测的下一个值: ~80.00 (可能略有偏差,例如79.85)模型应该预测一个非常接近80的值,这表明它已经从训练数据中学到了简单的算术序列规律。这个实践例子展示了使用SimpleRNN完成基本序列任务的基本步骤:生成数据、将其预处理成正确形状、构建模型架构、训练以及进行预测。虽然这个任务很简单,但其工作流程为处理更复杂的序列建模问题提供了基本方法,例如使用RNN、LSTM和GRU,我们将在接下来进行介绍。在处理更长序列时,你会遇到诸如梯度消失等挑战,这促使我们需要更先进的架构,如后续章节中将介绍的LSTM和GRU。