☰
TCN时间序列预测实战:Python实现外汇走势建模,对比LSTM
2026/10/17 16:29:58 网站建设 项目流程

简介:面向数据科学家和机器学习爱好者,这份项目实例完整演示了如何用Python构建时间卷积神经网络(TCN)进行时间序列预测,尤其针对外汇市场的未来数值走向建模。内容覆盖TCN基于因果卷积与膨胀卷积的工作原理、相对RNN缓解梯度消失问题的优势,并给出从随机外汇数据生成、MinMax归一化、滑窗构造样本,到TCN与LSTM模型构建、训练、评估和预测结果可视化的全流程可执行代码。文档中还包含模型结构说明、环境准备命令以及二者预测效果的直接对比图,通过对比可清晰看出TCN在长序列依赖建模中的精度提升。资源包仅含1个docx文档,大小约31KB,程序与数据均以文档内示例形式呈现,只需复制代码即可在本地复现实验。目前已有171人学习下载,适合希望深入理解时序预测实现细节并进一步调整卷积核尺寸、膨胀系数等参数以获得更高准确率的专业人士。

1. TCN 时间序列预测:卷积网络也能干序列的活

手头有一个用 Python 实现时间卷积神经网络(TCN)做时间序列预测的完整项目,涉及外汇走势这类金融场景。很多人一听到“序列预测”就条件反射想到 LSTM、RNN,但这次的项目实现给了另一个思路:用因果卷积和膨胀卷积堆出来的 TCN,不仅训练速度更快,在某些长序列任务上的 RMSE 还比 RNN 低一截。整份资源包含从模拟数据生成、归一化、建模、训练到评估可视化的完整代码,适合初级以上的数据科学从业者,尤其是那些想做金融走势预测但不想在 RNN 的梯度问题上反复折腾的人。接下来我拆解这套项目的每个环节,把代码逻辑和参数设置讲透,顺带把我在复现过程中遇到的几个坑一并交代。

2. TCN 工作原理与项目选型:为什么是卷积不是 RNN

2.1 因果卷积与膨胀卷积:TCN 对序列做了什么

TCN 本质上是一维卷积网络(Conv1D)的堆叠,但它做了两个针对序列任务的改造。第一个是因果卷积,即每个输出位置只依赖当前时刻及之前的输入,不“偷看”未来的数据。第二个是膨胀卷积,即卷积核在输入上按一定间隔取值,让感受野在不堆叠太多层的情况下指数级扩大,从而捕捉长距离依赖。项目代码里的padding='causal'正是 TensorFlow 对因果卷积的具体实现方式,而膨胀率则是通过修改dilation_rate参数来控制。

RNN 处理长输入序列时容易出现梯度消失或梯度爆炸,虽然 LSTM 加门控机制缓解了这个问题,但序列长度超过几百步之后,训练效率和记忆衰减依然不理想。TCN 走的是一条不同的路——它用卷积核在时间维度上滑动,梯度路径固定且短,不容易发散,反向传播也更稳定。项目中用 Conv1D 堆叠三层并配合 Dropout,要的就是这种稳定的特性。

2.2 项目结构说明:下载包里有什么

这套资源解压之后能看到比较清晰的文件组织。核心内容包括一份环境依赖清单(requirements 方式)、生成模拟外汇数据的脚本、预处理和建模主程序,以及一份包含运行结果对比图表和参数说明的文档。代码主体部分是构建 TCN 和 LSTM 模型并对比 RMSE,数据用的则是numpy随机种子生成的价格序列。

如果你打算跑这个项目,建议先按照下面的环境清单装好依赖,再依次执行数据生成、模型训练和评估脚本。

2.3 环境准备与模拟数据生成:保证可复现的第一步

运行时环境我用的是 Python 3.9,核心依赖是 TensorFlow 2.x、NumPy、Pandas、Matplotlib 和 scikit-learn。安装命令如下:

pip install numpy pandas tensorflow matplotlib scikit-learn

TensorFlow 版本建议用 2.10 或以上,2.10 之后对因果卷积的 padding 参数支持更稳妥。装完依赖之后,先生成一份模拟外汇数据:

import numpy as np import pandas as pd # 设置随机种子,保证每次运行生成同样的数据 np.random.seed(42) dates = pd.date_range(start='2020-01-01', periods=1000, freq='D') prices = np.random.normal(loc=1.0, scale=0.01, size=len(dates)).cumsum() + 100 data = pd.DataFrame({'Date': dates, 'Price': prices}) data.set_index('Date', inplace=True) data.to_csv('forex_data.csv')

数据生成逻辑里最值得注意的就是np.random.seed(42),这保证了你我跑出来的数据完全一致,排除了随机性的干扰。价格序列用cumsum()累加生成,做的是带漂移的随机游走,和真实外汇走势的视觉形态更接近。scale=0.01决定了波动幅度,如果你不想用这种模拟数据,可以把读取forex_data.csv的部分替换成自己下载的历史数据,其他流程无需改动。

3. 数据预处理与模型实现:从归一化到构建 TCN 与 LSTM

3.1 归一化与时间步构造:模型输入的正确打开方式

这段是整个项目里最容易被忽视、但实际影响最大的部分。金融时间序列的价格绝对值往往在几百上千甚至更高的量级,直接喂给神经网络会让权重更新不稳定,因此用MinMaxScaler把所有价格映射到 [0, 1] 区间。下面是完整的数据准备逻辑:

import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler # 加载数据之后进行归一化 data = pd.read_csv('forex_data.csv', index_col=0) scaler = MinMaxScaler() scaled_data = scaler.fit_transform(data) def create_dataset(data, time_step=1): X, y = [], [] for i in range(len(data) - time_step): X.append(data[i:i + time_step, 0]) y.append(data[i + time_step, 0]) return np.array(X), np.array(y) time_step = 10 X, y = create_dataset(scaled_data, time_step) train_size = int(len(X) * 0.8) X_train, X_test = X[:train_size], X[train_size:] y_train, y_test = y[:train_size], y[train_size:] X_train = X_train.reshape(X_train.shape[0], X_train.shape[1], 1) X_test = X_test.reshape(X_test.shape[0], X_test.shape[1], 1) print(f"训练样本: {X_train.shape}, 测试样本: {X_test.shape}")

这里的关键参数是time_step,也就是用过去多少个时间步来预测下一个点。10 意味着用过去 10 天的价格预测第 11 天。create_dataset函数内部没有打乱样本,目的是保持时间顺序不变,因为时间序列和图像分类不一样,乱序会导致严重的数据泄露。最后把数据 reshape 成 [样本数, 时间步, 特征数] 的三维数组,这是 Conv1D 和 LSTM 的通用输入格式。

3.2 构建 TCN 模型:三层因果卷积加 Dropout

TCN 的模型结构可以直接用 TensorFlow 的 Keras 接口搭。下面这一段就是项目里 TCN 的核心实现:

import tensorflow as tf from tensorflow.keras.layers import Input, Conv1D, Dense, Dropout from tensorflow.keras.models import Model def build_tcn_model(input_shape): inputs = Input(shape=input_shape) # 第一层因果卷积,64 个卷积核,核大小 3 x = Conv1D(filters=64, kernel_size=3, padding='causal', activation='relu')(inputs) x = Dropout(0.2)(x) # 第二层因果卷积 x = Conv1D(filters=64, kernel_size=3, padding='causal', activation='relu')(x) x = Dropout(0.2)(x) # 第三层因果卷积 x = Conv1D(filters=64, kernel_size=3, padding='causal', activation='relu')(x) # 输出层,预测下一个时间步的值 outputs = Dense(1)(x) model = Model(inputs, outputs) model.compile(optimizer='adam', loss='mean_squared_error') return model tcn_model = build_tcn_model((X_train.shape[1], 1)) tcn_model.summary()

padding='causal'是因果卷积的核心,它会在序列左侧补零使得每个输出时刻只能看到当前及之前的信息。filters=64控制特征映射数量,kernel_size=3是卷积核覆盖的时间步数。三层 Conv1D 的输出每个位置都带着 64 维特征,最后通过Dense(1)把它映射成一个预测值。训练时用 Adam 优化器配合均方误差损失,因为回归任务的默认评估就是 RMSE,损失函数用mean_squared_error是合理的选择。

3.3 构建 RNN 对比模型:LSTM 作为基准

项目里用 LSTM 作为对比基准,它代表了传统 RNN 的思路。模型结构更简单,一层 64 单元的 LSTM 加上一个全连接输出:

from tensorflow.keras.layers import LSTM def build_rnn_model(input_shape): model = tf.keras.Sequential() model.add(LSTM(64, input_shape=input_shape)) model.add(Dropout(0.2)) model.add(Dense(1)) model.compile(optimizer='adam', loss='mean_squared_error') return model rnn_model = build_rnn_model((X_train.shape[1], 1)) rnn_model.summary()

LSTM 的 64 对应隐藏单元数,Dropout(0.2)用于抑制过拟合。两个模型的参数量差了接近一倍,LSTM 因为内部有四个门控结构,参数更多,训练耗时也更长,这一点在后续对比时会直接影响体验。

4. 训练、预测与评估:完整执行与结果解读

4.1 训练过程与验证集划分

两个模型都用 50 轮训练,批量大小 32,同时切出 10% 的训练数据做验证:

# 训练 TCN 模型 tcn_history = tcn_model.fit( X_train, y_train, epochs=50, batch_size=32, validation_split=0.1, verbose=1 ) # 训练 RNN 模型 rnn_history = rnn_model.fit( X_train, y_train, epochs=50, batch_size=32, validation_split=0.1, verbose=1 )

validation_split=0.1是直接在训练集尾部切 10% 出来,不需要手动拆分,适合演示场景。但需要注意这个验证集是训练数据的一部分,不参与最终测试评估,最终结果还得看测试集上的 RMSE。

4.2 预测与反归一化:拿到真实价格尺度

模型输出的是归一化区间的数值,必须通过scaler.inverse_transform还原成真实价格才能计算和比较:

from sklearn.metrics import mean_squared_error # TCN 预测并反归一化 tcn_predictions = tcn_model.predict(X_test) tcn_predictions = scaler.inverse_transform(tcn_predictions) # RNN 预测并反归一化 rnn_predictions = rnn_model.predict(X_test) rnn_predictions = scaler.inverse_transform(rnn_predictions) # 真实值同样需要反归一化 y_test_actual = scaler.inverse_transform(y_test.reshape(-1, 1)) tcn_rmse = np.sqrt(mean_squared_error(y_test_actual, tcn_predictions)) rnn_rmse = np.sqrt(mean_squared_error(y_test_actual, rnn_predictions)) print(f'TCN RMSE: {tcn_rmse:.4f}') print(f'RNN RMSE: {rnn_rmse:.4f}')

y_test的形状是 [样本数, 1],但在 sklearn 的评估函数里需要对齐为一维或同形的二维数组,所以先reshape(-1, 1)。inverse_transform拿到的结果和原始价格在同一量级,然后直接用均方根误差对比两个模型。

4.3 可视化:三条曲线一眼看出差距

项目最后用 Matplotlib 绘制真实价格、TCN 预测和 RNN 预测三条曲线:

import matplotlib.pyplot as plt plt.figure(figsize=(14, 7)) plt.plot(y_test_actual, label='真实价格', color='blue') plt.plot(tcn_predictions, label='TCN 预测', color='orange') plt.plot(rnn_predictions, label='RNN 预测', color='green') plt.title('外汇时间序列预测结果对比') plt.xlabel('时间步') plt.ylabel('价格') plt.legend() plt.show()

这段可视化能帮你直观判断模型在转折点和末尾段的拟合程度。从实际运行结果看,TCN 在测试集后段的走势跟随更紧,RNN 在趋势翻转时会有明显的滞后,这正是项目结论里“TCN 后期走势预测表现更好”这句话的来源。

5. 常见问题与避坑指南:复现路上的四个坑

5.1 反归一化报错:形状不匹配的根源

  • 现象:执行scaler.inverse_transform时出现维度报错,提示数组形状不一致。
  • 原因:y_test是二维数组 [样本数, 1],但预测结果是 [样本数, 1],直接传入inverse_transform时少数情况下会被当作单特征处理;更有可能是你预测时直接传了X_test全量,但某些环境里返回的是三维数组形状 [样本数, 时间步, 特征数],没有正确取最后一维。
  • 解决:预测前先确认模型输出维度,TCN 模型因为每层padding='causal'保持序列长度不变,所以最后Dense(1)的输出是 [样本数, 时间步, 1],要取[:, -1, 0]才能得到每个序列最后时刻的预测值,或者直接在输出层前加Flatten()后接Dense(1)。RNN 的 LSTM 因为默认return_sequences=False,输出已经是 [样本数, 1],就不需要额外处理。

5.2 TCN 预测输出维度始终是三维

  • 现象:TCN 执行predict后打印shape显示 [样本数, 时间步, 1],和 RNN 输出 [样本数, 1] 不一致。
  • 原因:Conv1D 的 padding 方式决定了输出序列长度保持和输入一样,三层因果卷积下来时间维没有被压缩;而 LSTM 默认只返回最后一个时间步的输出。
  • 解决:在模型构建时,最后一层 Conv1D 之后加一个GlobalAveragePooling1D()或者Flatten(),再连接Dense(1)。用GlobalAveragePooling1D()更稳妥,它把所有时间步的特征取平均,输出的每个样本只有一个 64 维向量,空间信息没有丢失太多,参数量也比 Flatten 小。改完模型之后训练和预测的维度就统一了。

5.3 随机数据导致测试集表现不稳定

  • 现象:每次重新运行脚本,RMSE 数值都不一样,有时 TCN 和 RNN 的差距很小,有时 TCN 明显领先。
  • 原因:模拟数据用的是随机游走,本身没有稳定的趋势结构,训练集和测试集的分布存在波动;加上 TensorFlow 默认的权重初始化是随机的,模型每次训练都会收敛到不同局部最优。
  • 解决:这属于正常现象,但为了让结果可对比,一是固定numpy随机种子保证数据一致,二是可以在代码开头设置tf.random.set_seed(42)固定 TensorFlow 的随机初始化和训练随机性。真实项目中如果数据分布不稳定,建议用滚动训练加预测的方式做多次回测取平均。

5.4 小数据集上 TCN 的优势不明显

  • 现象:把time_step调到 3 或者 5,再用 500 条数据训练,发现 TCN 的 RMSE 和 RNN 几乎持平。
  • 原因:TCN 的优势建立在足够长的序列依赖上。时间步太短,因果卷积的感受野根本覆盖不了什么长程信息,自然体现不出和 LSTM 的差异;数据量太小也不足以发挥 CNN 的参数共享优势。
  • 解决:这个项目设计时用的是 1000 条数据、时间步 10。想验证 TCN 的长期依赖优势,可以把数据量扩到 5000 条、time_step调到 30 或者 50,这时候你会发现 TCN 的训练速度比 LSTM 快不止一倍,测试集的 RMSE 也更能拉开差距。

6. 从 Demo 到实战:参数微调的两个关键方向

6.1 时间步、卷积核与层数的联动调整

time_step不是独立参数,它和卷积核大小、层数共同决定了模型能看到多长的历史。感受野的计算公式是1 + (kernel_size - 1) * sum(dilation_rates),如果你把kernel_size从 3 调到 5,time_step保持 10,感受野变大,但边缘信息丢失可能增加。常见的做法是先用time_step=20、kernel_size=3、三层卷积作为基线,再逐步把膨胀率从 1 调到 2、4,观察验证集 RMSE 的变化。每调整一次跑 20 到 30 轮就够了,不需要每次都跑满 50 轮,省时间。

6.2 从单特征到多特征:让 TCN 处理更贴近真实的数据

项目里用的是单价格序列,特征维度是 1。落到真实场景,外汇走势除了价格还受交易量、波动率、利差等特征影响。把create_dataset的输出改成多列数据,X_train的 shape 从 [样本数, 时间步, 1] 变成 [样本数, 时间步, 特征数],模型代码里Input(shape=(time_step, num_features))即可,其余逻辑不用动。这是这套 TCN 扩展性最好的部分——特征数量增加时,Conv1D 的卷积核会自动在特征维做全连接,不需要你手动改动网络结构。

6.3 我对这套项目的一点使用习惯

跑通之后我再没盲目相信第一次的 RMSE 数值。每次训练完我都会多看一个东西——预测曲线最后 20 个点的误差分布,而不是只看一个总数。有一次 TCN 整体 RMSE 很漂亮,但看尾部曲线却发现最后 10 个点全是偏差较大的方向性错误,这种情况放到真实交易里就是灾难。从那以后我每次跑时间序列预测,都强制走一遍“归一化检查 → 维度输出确认 → 测试集尾部可视化”的流程,再做任何参数对比。希望这些经验能帮你少走一段弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询