466 lines
19 KiB
Python
466 lines
19 KiB
Python
|
|
# 导入PyTorch核心库,用于深度学习模型构建和训练
|
|||
|
|
import torch
|
|||
|
|
# 导入PyTorch神经网络模块,包含各种网络层的定义
|
|||
|
|
import torch.nn as nn
|
|||
|
|
# 导入NumPy,用于数值计算和数组操作
|
|||
|
|
import numpy as np
|
|||
|
|
# 导入Pandas,用于数据处理和分析
|
|||
|
|
import pandas as pd
|
|||
|
|
# 导入MetaTrader5接口,用于获取外汇交易数据
|
|||
|
|
import MetaTrader5 as mt5
|
|||
|
|
# 从scikit-learn导入数据归一化工具
|
|||
|
|
# 从scikit-learn导入数据归一化工具
|
|||
|
|
from sklearn.preprocessing import MinMaxScaler
|
|||
|
|
|
|||
|
|
# ==================== CUDA设备检查 ====================
|
|||
|
|
# 强制检查CUDA是否可用,如果不可用则给出警告和安装指引
|
|||
|
|
if not torch.cuda.is_available():
|
|||
|
|
# 打印分隔线,使警告信息更醒目
|
|||
|
|
print("=" * 60)
|
|||
|
|
# 提示用户CUDA不可用
|
|||
|
|
print("警告: CUDA不可用!")
|
|||
|
|
# 显示当前安装的PyTorch版本号
|
|||
|
|
print(f"当前PyTorch版本: {torch.__version__}")
|
|||
|
|
# 说明安装的是CPU版本
|
|||
|
|
print("您安装的是CPU版本的PyTorch")
|
|||
|
|
# 提供换行,使信息更清晰
|
|||
|
|
print("\n请卸载当前PyTorch并安装CUDA版本:")
|
|||
|
|
# 提供卸载当前版本的命令
|
|||
|
|
print("pip uninstall torch torchvision torchaudio")
|
|||
|
|
# 提供安装CUDA版本的命令(针对CUDA 12.1)
|
|||
|
|
print("pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121")
|
|||
|
|
# 打印分隔线
|
|||
|
|
print("=" * 60)
|
|||
|
|
# 询问用户是否继续使用CPU进行训练
|
|||
|
|
response = input("\n是否继续使用CPU训练? (y/n): ")
|
|||
|
|
# 如果用户不输入'y',则退出程序
|
|||
|
|
if response.lower() != 'y':
|
|||
|
|
quit()
|
|||
|
|
|
|||
|
|
# 根据CUDA是否可用来选择计算设备(GPU或CPU)
|
|||
|
|
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|||
|
|
# 打印分隔线
|
|||
|
|
print(f"\n{'='*60}")
|
|||
|
|
# 显示当前使用的计算设备
|
|||
|
|
print(f"使用设备: {device}")
|
|||
|
|
# 如果CUDA可用,显示GPU详细信息
|
|||
|
|
if torch.cuda.is_available():
|
|||
|
|
# 显示GPU型号名称
|
|||
|
|
print(f"GPU型号: {torch.cuda.get_device_name(0)}")
|
|||
|
|
# 显示CUDA版本
|
|||
|
|
print(f"CUDA版本: {torch.version.cuda}")
|
|||
|
|
# 显示GPU可用显存大小(转换为GB单位)
|
|||
|
|
print(f"可用显存: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.2f} GB")
|
|||
|
|
# 打印分隔线
|
|||
|
|
# 打印分隔线
|
|||
|
|
print(f"{'='*60}\n")
|
|||
|
|
|
|||
|
|
# ==================== 连接MT5并获取历史数据 ====================
|
|||
|
|
# 连接MT5获取数据
|
|||
|
|
print("正在连接MT5...")
|
|||
|
|
# 初始化MT5连接,如果失败则退出程序
|
|||
|
|
if not mt5.initialize():
|
|||
|
|
# 提示用户MT5初始化失败的原因
|
|||
|
|
print("MT5初始化失败,请确保MT5已启动并登录")
|
|||
|
|
# 退出程序
|
|||
|
|
quit()
|
|||
|
|
|
|||
|
|
# 导入datetime模块用于处理日期和时间
|
|||
|
|
from datetime import datetime, timedelta
|
|||
|
|
# 获取当前日期时间作为数据结束时间
|
|||
|
|
end_date = datetime.now()
|
|||
|
|
# 计算开始时间为当前时间往前推5年(5*365天)
|
|||
|
|
start_date = end_date - timedelta(days=5*365)
|
|||
|
|
|
|||
|
|
# 提示用户正在获取数据,显示数据范围
|
|||
|
|
print(f"正在获取EURUSD H1数据 ({start_date.date()} 至 {end_date.date()})...")
|
|||
|
|
# 从MT5获取EURUSD货币对的H1(1小时)时间框架的历史数据
|
|||
|
|
rates = mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_H1, start_date, end_date)
|
|||
|
|
# 关闭MT5连接,释放资源
|
|||
|
|
mt5.shutdown()
|
|||
|
|
|
|||
|
|
# 检查是否成功获取数据,如果没有数据则退出
|
|||
|
|
if rates is None or len(rates) == 0:
|
|||
|
|
# 提示用户获取数据失败的可能原因
|
|||
|
|
print("获取数据失败,请检查MT5中是否有EURUSD历史数据")
|
|||
|
|
# 退出程序
|
|||
|
|
quit()
|
|||
|
|
|
|||
|
|
# 将获取的数据转换为Pandas DataFrame格式,便于后续处理
|
|||
|
|
df = pd.DataFrame(rates)
|
|||
|
|
# 显示获取到的数据条数(使用千位分隔符格式化)
|
|||
|
|
# 显示获取到的数据条数(使用千位分隔符格式化)
|
|||
|
|
print(f"获取到 {len(df):,} 条EURUSD H1数据\n")
|
|||
|
|
|
|||
|
|
# ==================== 数据预处理 ====================
|
|||
|
|
# 数据归一化 - 创建MinMaxScaler对象,用于将数据缩放到0-1范围
|
|||
|
|
scaler = MinMaxScaler()
|
|||
|
|
# 对开盘价、最高价、最低价、收盘价和成交量进行归一化处理
|
|||
|
|
# fit_transform会先学习数据的最小值和最大值,然后进行归一化转换
|
|||
|
|
df[['open', 'high', 'low', 'close', 'tick_volume']] = scaler.fit_transform(
|
|||
|
|
df[['open', 'high', 'low', 'close', 'tick_volume']])
|
|||
|
|
|
|||
|
|
# ==================== 构造训练数据集 ====================
|
|||
|
|
# 构造训练数据 - 定义函数将时间序列数据转换为监督学习格式
|
|||
|
|
def create_dataset(data, lookback=10):
|
|||
|
|
"""
|
|||
|
|
将时间序列数据转换为监督学习数据集
|
|||
|
|
参数:
|
|||
|
|
data: 输入的时间序列数据
|
|||
|
|
lookback: 回溯窗口大小,表示用过去多少个时间步预测下一个时间步
|
|||
|
|
返回:
|
|||
|
|
X: 特征数据(过去lookback个时间步的数据)
|
|||
|
|
y: 标签数据(下一个时间步的收盘价)
|
|||
|
|
"""
|
|||
|
|
# 初始化特征列表和标签列表
|
|||
|
|
X, y = [], []
|
|||
|
|
# 遍历数据,构造训练样本
|
|||
|
|
# 减去lookback+1是为了确保每个样本都有对应的标签
|
|||
|
|
for i in range(len(data) - lookback - 1):
|
|||
|
|
# 提取从i到i+lookback的数据作为特征(输入)
|
|||
|
|
X.append(data[i:i+lookback])
|
|||
|
|
# 提取i+lookback位置的收盘价(索引3)作为标签(输出)
|
|||
|
|
y.append(data[i+lookback, 3]) # 3代表收盘价列
|
|||
|
|
# 将列表转换为NumPy数组,并指定数据类型为float32(节省内存)
|
|||
|
|
return np.array(X, dtype=np.float32), np.array(y, dtype=np.float32).reshape(-1, 1)
|
|||
|
|
|
|||
|
|
# 从DataFrame中提取需要的特征列(开、高、低、收、成交量)
|
|||
|
|
features = df[['open', 'high', 'low', 'close', 'tick_volume']].values
|
|||
|
|
# 调用create_dataset函数创建训练数据集,默认使用10个时间步作为回溯窗口
|
|||
|
|
X, y = create_dataset(features)
|
|||
|
|
|
|||
|
|
# ==================== 划分训练集和验证集 ====================
|
|||
|
|
# 计算训练集大小(80%用于训练)
|
|||
|
|
train_size = int(len(X) * 0.8)
|
|||
|
|
# 划分训练集特征数据(前80%)
|
|||
|
|
X_train_np = X[:train_size]
|
|||
|
|
# 划分训练集标签数据(前80%)
|
|||
|
|
y_train_np = y[:train_size]
|
|||
|
|
# 划分验证集特征数据(后20%)
|
|||
|
|
X_val_np = X[train_size:]
|
|||
|
|
# 划分验证集标签数据(后20%)
|
|||
|
|
y_val_np = y[train_size:]
|
|||
|
|
|
|||
|
|
# 将训练集NumPy数组转换为PyTorch张量,并移动到指定的计算设备(GPU或CPU)
|
|||
|
|
X_train = torch.from_numpy(X_train_np).to(device)
|
|||
|
|
# 将训练集标签数据也转换为PyTorch张量并移动到设备
|
|||
|
|
y_train = torch.from_numpy(y_train_np).to(device)
|
|||
|
|
# 将验证集特征数据转换为PyTorch张量并移动到设备
|
|||
|
|
X_val = torch.from_numpy(X_val_np).to(device)
|
|||
|
|
# 将验证集标签数据转换为PyTorch张量并移动到设备
|
|||
|
|
y_val = torch.from_numpy(y_val_np).to(device)
|
|||
|
|
|
|||
|
|
# 打印训练集和验证集的形状,便于确认数据维度
|
|||
|
|
print(f"训练集形状: {X_train.shape}") # 应该是 (训练样本数, 10, 5)
|
|||
|
|
print(f"训练标签形状: {y_train.shape}") # 应该是 (训练样本数, 1)
|
|||
|
|
print(f"验证集形状: {X_val.shape}") # 应该是 (验证样本数, 10, 5)
|
|||
|
|
print(f"验证标签形状: {y_val.shape}\n") # 应该是 (验证样本数, 1)
|
|||
|
|
|
|||
|
|
# ==================== 定义LSTM神经网络模型 ====================
|
|||
|
|
# LSTM模型 - 定义一个简单的LSTM神经网络类
|
|||
|
|
class SimpleLSTM(nn.Module):
|
|||
|
|
"""
|
|||
|
|
简单的LSTM神经网络模型
|
|||
|
|
用于预测外汇价格的下一个收盘价
|
|||
|
|
"""
|
|||
|
|
def __init__(self):
|
|||
|
|
"""初始化模型结构"""
|
|||
|
|
# 调用父类的初始化方法
|
|||
|
|
super().__init__()
|
|||
|
|
# 定义LSTM层:
|
|||
|
|
# input_size=5: 输入特征数(开、高、低、收、成交量)
|
|||
|
|
# hidden_size=20: LSTM隐藏层的神经元数量
|
|||
|
|
# num_layers=1: LSTM层数
|
|||
|
|
# batch_first=True: 输入数据的第一个维度是batch_size
|
|||
|
|
self.lstm = nn.LSTM(input_size=5, hidden_size=20, num_layers=1, batch_first=True)
|
|||
|
|
# 定义全连接层,将LSTM的输出(20维)映射到1维(预测的收盘价)
|
|||
|
|
self.fc = nn.Linear(20, 1)
|
|||
|
|
|
|||
|
|
def forward(self, x):
|
|||
|
|
"""
|
|||
|
|
前向传播函数
|
|||
|
|
参数:
|
|||
|
|
x: 输入数据,形状为 (batch_size, sequence_length, input_size)
|
|||
|
|
返回:
|
|||
|
|
预测值,形状为 (batch_size, 1)
|
|||
|
|
"""
|
|||
|
|
# 将输入数据传入LSTM层,获取输出
|
|||
|
|
# lstm_out形状: (batch_size, sequence_length, hidden_size)
|
|||
|
|
# _表示隐藏状态和细胞状态,这里不需要使用
|
|||
|
|
lstm_out, _ = self.lstm(x)
|
|||
|
|
# 取LSTM输出的最后一个时间步([:, -1, :]),传入全连接层得到最终预测
|
|||
|
|
return self.fc(lstm_out[:, -1, :])
|
|||
|
|
|
|||
|
|
# 创建模型实例并移动到指定设备(GPU或CPU)
|
|||
|
|
model = SimpleLSTM().to(device)
|
|||
|
|
# 定义优化器,使用Adam算法,学习率设为0.001
|
|||
|
|
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
|
|||
|
|
# 定义损失函数,使用均方误差(MSE)损失
|
|||
|
|
# 定义损失函数,使用均方误差(MSE)损失
|
|||
|
|
criterion = nn.MSELoss()
|
|||
|
|
|
|||
|
|
# ==================== 训练模型 ====================
|
|||
|
|
# 训练
|
|||
|
|
print("开始训练...")
|
|||
|
|
# 打印分隔线,使训练过程更清晰
|
|||
|
|
print(f"{'='*60}")
|
|||
|
|
|
|||
|
|
# 初始化记录训练过程的列表
|
|||
|
|
train_losses = [] # 记录每个epoch的训练损失
|
|||
|
|
val_losses = [] # 记录每个epoch的验证损失
|
|||
|
|
best_val_loss = float('inf') # 初始化最佳验证损失为无穷大
|
|||
|
|
best_epoch = 0 # 记录最佳模型对应的epoch
|
|||
|
|
patience = 20 # 早停的耐心值(验证损失20个epoch不下降就停止)
|
|||
|
|
patience_counter = 0 # 早停计数器
|
|||
|
|
best_model_state = None # 保存最佳模型的状态
|
|||
|
|
|
|||
|
|
# 训练最多500个epoch(但可能会提前停止)
|
|||
|
|
for epoch in range(500):
|
|||
|
|
# ========== 训练阶段 ==========
|
|||
|
|
model.train() # 设置模型为训练模式
|
|||
|
|
# 清空之前的梯度,防止梯度累积
|
|||
|
|
optimizer.zero_grad()
|
|||
|
|
# 前向传播:将训练数据输入模型,得到预测输出
|
|||
|
|
output = model(X_train)
|
|||
|
|
# 计算训练损失:比较预测值和真实值之间的差异
|
|||
|
|
train_loss = criterion(output, y_train)
|
|||
|
|
# 反向传播:计算损失函数对模型参数的梯度
|
|||
|
|
train_loss.backward()
|
|||
|
|
# 更新参数:根据梯度和学习率更新模型参数
|
|||
|
|
optimizer.step()
|
|||
|
|
|
|||
|
|
# ========== 验证阶段 ==========
|
|||
|
|
model.eval() # 设置模型为评估模式
|
|||
|
|
with torch.no_grad(): # 验证时不计算梯度,节省内存
|
|||
|
|
# 在验证集上进行前向传播
|
|||
|
|
val_output = model(X_val)
|
|||
|
|
# 计算验证损失
|
|||
|
|
val_loss = criterion(val_output, y_val)
|
|||
|
|
|
|||
|
|
# 记录本epoch的损失值
|
|||
|
|
train_losses.append(train_loss.item())
|
|||
|
|
val_losses.append(val_loss.item())
|
|||
|
|
|
|||
|
|
# 每10个epoch打印一次训练进度
|
|||
|
|
if (epoch + 1) % 10 == 0:
|
|||
|
|
# 显示当前epoch、训练损失和验证损失
|
|||
|
|
print(f"Epoch {epoch+1:3d} | 训练Loss: {train_loss.item():.6f} | 验证Loss: {val_loss.item():.6f}")
|
|||
|
|
|
|||
|
|
# ========== 早停机制 ==========
|
|||
|
|
# 如果当前验证损失是最好的
|
|||
|
|
if val_loss.item() < best_val_loss:
|
|||
|
|
best_val_loss = val_loss.item() # 更新最佳验证损失
|
|||
|
|
best_epoch = epoch + 1 # 记录最佳epoch
|
|||
|
|
patience_counter = 0 # 重置耐心计数器
|
|||
|
|
# 保存当前最佳模型的状态(深拷贝)
|
|||
|
|
best_model_state = {k: v.cpu().clone() for k, v in model.state_dict().items()}
|
|||
|
|
else:
|
|||
|
|
# 如果验证损失没有改善,增加计数器
|
|||
|
|
patience_counter += 1
|
|||
|
|
|
|||
|
|
# 如果连续patience个epoch验证损失都没有改善,提前停止训练
|
|||
|
|
if patience_counter >= patience:
|
|||
|
|
print(f"\n早停触发! 验证损失已经{patience}个epoch没有改善")
|
|||
|
|
print(f"最佳模型出现在Epoch {best_epoch},验证Loss: {best_val_loss:.6f}")
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
# 恢复最佳模型的参数
|
|||
|
|
if best_model_state is not None:
|
|||
|
|
model.load_state_dict(best_model_state)
|
|||
|
|
model = model.to(device) # 确保模型在正确的设备上
|
|||
|
|
print(f"\n已恢复最佳模型 (Epoch {best_epoch})")
|
|||
|
|
|
|||
|
|
# 打印分隔线,表示训练结束
|
|||
|
|
# 打印分隔线,表示训练结束
|
|||
|
|
print(f"{'='*60}\n")
|
|||
|
|
|
|||
|
|
# ==================== 绘制训练曲线 ====================
|
|||
|
|
# 绘制Loss曲线,可视化训练过程
|
|||
|
|
print("正在生成训练曲线图...")
|
|||
|
|
try:
|
|||
|
|
# 导入matplotlib绘图库
|
|||
|
|
import matplotlib.pyplot as plt
|
|||
|
|
# 导入os模块(如果之前没有导入)
|
|||
|
|
import os
|
|||
|
|
# 设置中文字体支持
|
|||
|
|
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
|
|||
|
|
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
|
|||
|
|
|
|||
|
|
# 创建图形和坐标轴
|
|||
|
|
plt.figure(figsize=(12, 5))
|
|||
|
|
|
|||
|
|
# 第一个子图:训练损失和验证损失
|
|||
|
|
plt.subplot(1, 2, 1)
|
|||
|
|
# 绘制训练损失曲线(蓝色)
|
|||
|
|
plt.plot(train_losses, label='训练Loss', color='blue', linewidth=2)
|
|||
|
|
# 绘制验证损失曲线(橙色)
|
|||
|
|
plt.plot(val_losses, label='验证Loss', color='orange', linewidth=2)
|
|||
|
|
# 标记最佳模型的位置(红色虚线)
|
|||
|
|
plt.axvline(x=best_epoch-1, color='red', linestyle='--', label=f'最佳模型 (Epoch {best_epoch})')
|
|||
|
|
# 设置x轴标签
|
|||
|
|
plt.xlabel('Epoch')
|
|||
|
|
# 设置y轴标签
|
|||
|
|
plt.ylabel('Loss')
|
|||
|
|
# 设置图表标题
|
|||
|
|
plt.title('训练和验证损失曲线')
|
|||
|
|
# 显示图例
|
|||
|
|
plt.legend()
|
|||
|
|
# 显示网格
|
|||
|
|
plt.grid(True, alpha=0.3)
|
|||
|
|
|
|||
|
|
# 第二个子图:放大后期的损失曲线(更清楚地看收敛情况)
|
|||
|
|
plt.subplot(1, 2, 2)
|
|||
|
|
# 只显示后80%的epoch,方便观察收敛细节
|
|||
|
|
start_idx = int(len(train_losses) * 0.2)
|
|||
|
|
plt.plot(range(start_idx, len(train_losses)), train_losses[start_idx:],
|
|||
|
|
label='训练Loss', color='blue', linewidth=2)
|
|||
|
|
plt.plot(range(start_idx, len(val_losses)), val_losses[start_idx:],
|
|||
|
|
label='验证Loss', color='orange', linewidth=2)
|
|||
|
|
plt.axvline(x=best_epoch-1, color='red', linestyle='--', label=f'最佳模型 (Epoch {best_epoch})')
|
|||
|
|
plt.xlabel('Epoch')
|
|||
|
|
plt.ylabel('Loss')
|
|||
|
|
plt.title('训练和验证损失曲线(后80%)')
|
|||
|
|
plt.legend()
|
|||
|
|
plt.grid(True, alpha=0.3)
|
|||
|
|
|
|||
|
|
# 调整子图之间的间距
|
|||
|
|
plt.tight_layout()
|
|||
|
|
# 保存图表到文件(获取当前脚本所在目录)
|
|||
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|||
|
|
loss_plot_path = os.path.join(current_dir, "training_loss.png")
|
|||
|
|
plt.savefig(loss_plot_path, dpi=150, bbox_inches='tight')
|
|||
|
|
print(f"训练曲线已保存: {loss_plot_path}")
|
|||
|
|
# 显示图表
|
|||
|
|
plt.show()
|
|||
|
|
except ImportError:
|
|||
|
|
# 如果没有安装matplotlib,跳过绘图
|
|||
|
|
print("未安装matplotlib库,跳过绘图。可运行: pip install matplotlib")
|
|||
|
|
except Exception as e:
|
|||
|
|
# 捕获其他绘图错误
|
|||
|
|
print(f"绘图时出错: {e}")
|
|||
|
|
|
|||
|
|
print(f"{'='*60}\n")
|
|||
|
|
|
|||
|
|
# ==================== 导出ONNX模型 ====================
|
|||
|
|
# 导出ONNX (先移到CPU) - ONNX是一种跨平台的模型格式
|
|||
|
|
print("正在导出ONNX模型...")
|
|||
|
|
# 将模型设置为评估模式(关闭dropout等训练特有的层)
|
|||
|
|
model.eval()
|
|||
|
|
# 将模型从GPU移动到CPU,因为ONNX导出在CPU上更稳定
|
|||
|
|
model_cpu = model.cpu()
|
|||
|
|
# 创建一个虚拟输入张量,用于ONNX导出时追踪模型结构
|
|||
|
|
# 形状为 (1, 10, 5):batch_size=1, sequence_length=10, input_size=5
|
|||
|
|
dummy_input = torch.randn(1, 10, 5)
|
|||
|
|
|
|||
|
|
# 导入os模块用于文件路径操作
|
|||
|
|
import os
|
|||
|
|
# 获取脚本所在目录的绝对路径
|
|||
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|||
|
|
# 构造ONNX模型的完整保存路径
|
|||
|
|
onnx_path = os.path.join(script_dir, "lstm_model.onnx")
|
|||
|
|
# 构造归一化参数的完整保存路径
|
|||
|
|
scaler_path = os.path.join(script_dir, "scaler_params.npy")
|
|||
|
|
|
|||
|
|
# 显示脚本所在目录
|
|||
|
|
print(f"脚本所在目录: {script_dir}")
|
|||
|
|
# 显示模型将要保存的路径
|
|||
|
|
print(f"模型将保存到: {onnx_path}\n")
|
|||
|
|
|
|||
|
|
# 使用try-except捕获可能的导出错误
|
|||
|
|
try:
|
|||
|
|
# 导出模型为ONNX格式
|
|||
|
|
torch.onnx.export(
|
|||
|
|
model_cpu, # 要导出的模型
|
|||
|
|
dummy_input, # 模型的示例输入
|
|||
|
|
onnx_path, # 保存路径
|
|||
|
|
export_params=True, # 导出模型参数
|
|||
|
|
opset_version=11, # ONNX算子集版本(11是较为通用的版本)
|
|||
|
|
do_constant_folding=True, # 是否执行常量折叠优化
|
|||
|
|
input_names=['input'], # 输入节点的名称
|
|||
|
|
output_names=['output'], # 输出节点的名称
|
|||
|
|
dynamic_axes={'input': {0: 'batch_size'}} # 动态维度设置(batch_size可变)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 验证文件是否真的生成
|
|||
|
|
if os.path.exists(onnx_path):
|
|||
|
|
# 获取生成的ONNX文件大小(转换为KB)
|
|||
|
|
file_size = os.path.getsize(onnx_path) / 1024
|
|||
|
|
# 显示保存成功信息
|
|||
|
|
print(f"ONNX模型已保存: {onnx_path}")
|
|||
|
|
# 显示文件大小
|
|||
|
|
print(f"文件大小: {file_size:.2f} KB")
|
|||
|
|
|
|||
|
|
# 验证ONNX模型的正确性
|
|||
|
|
import onnx
|
|||
|
|
# 加载刚才保存的ONNX模型
|
|||
|
|
onnx_model = onnx.load(onnx_path)
|
|||
|
|
# 检查模型格式是否正确
|
|||
|
|
onnx.checker.check_model(onnx_model)
|
|||
|
|
# 显示验证通过信息
|
|||
|
|
print("ONNX模型验证通过!")
|
|||
|
|
else:
|
|||
|
|
# 如果文件未生成,显示错误信息
|
|||
|
|
print(f"错误: ONNX文件未生成在 {onnx_path}")
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
# 捕获并显示导出过程中的任何异常
|
|||
|
|
print(f"导出ONNX失败: {e}")
|
|||
|
|
# 导入traceback模块
|
|||
|
|
import traceback
|
|||
|
|
# 导入traceback模块
|
|||
|
|
import traceback
|
|||
|
|
# 打印详细的错误堆栈信息,便于调试
|
|||
|
|
traceback.print_exc()
|
|||
|
|
|
|||
|
|
# ==================== 保存归一化参数 ====================
|
|||
|
|
# 保存归一化参数 - 将归一化时使用的最小值和最大值保存下来
|
|||
|
|
# 保存为字典格式,包含'min'和'max'两个键
|
|||
|
|
np.save(scaler_path, {'min': scaler.data_min_, 'max': scaler.data_max_})
|
|||
|
|
# 显示归一化参数保存成功信息
|
|||
|
|
print(f"归一化参数已保存: {scaler_path}")
|
|||
|
|
|
|||
|
|
# ==================== 输出归一化参数供MQL5使用 ====================
|
|||
|
|
# 输出归一化参数供MQL5使用 - 生成可以直接复制到MQL5代码中的数组声明
|
|||
|
|
# 打印分隔线
|
|||
|
|
print(f"\n{'='*60}")
|
|||
|
|
# 提示这些参数需要复制到EA代码中
|
|||
|
|
print("归一化参数 (复制到LSTM_EA.mq5):")
|
|||
|
|
# 打印分隔线
|
|||
|
|
print(f"{'='*60}")
|
|||
|
|
# 生成data_min数组的声明语句,包含5个特征的最小值(保留5位小数)
|
|||
|
|
print(f"double data_min[5] = {{{', '.join([f'{x:.5f}' for x in scaler.data_min_])}}};")
|
|||
|
|
# 生成data_max数组的声明语句,包含5个特征的最大值(保留5位小数)
|
|||
|
|
print(f"double data_max[5] = {{{', '.join([f'{x:.5f}' for x in scaler.data_max_])}}};")
|
|||
|
|
# 打印分隔线
|
|||
|
|
print(f"{'='*60}\n")
|
|||
|
|
|
|||
|
|
# ==================== 输出训练总结 ====================
|
|||
|
|
print(f"{'='*60}")
|
|||
|
|
print("训练总结:")
|
|||
|
|
print(f"{'='*60}")
|
|||
|
|
print(f"总训练轮数: {len(train_losses)} epochs")
|
|||
|
|
print(f"最佳模型: Epoch {best_epoch}")
|
|||
|
|
print(f"最佳验证Loss: {best_val_loss:.6f}")
|
|||
|
|
print(f"最终训练Loss: {train_losses[-1]:.6f}")
|
|||
|
|
print(f"最终验证Loss: {val_losses[-1]:.6f}")
|
|||
|
|
# 计算训练集和验证集的Loss差异,用于判断是否过拟合
|
|||
|
|
loss_diff = abs(train_losses[-1] - val_losses[-1])
|
|||
|
|
print(f"训练/验证Loss差异: {loss_diff:.6f}")
|
|||
|
|
if loss_diff < 0.0001:
|
|||
|
|
print("✓ 模型状态: 良好,没有明显的过拟合")
|
|||
|
|
elif loss_diff < 0.001:
|
|||
|
|
print("⚠ 模型状态: 尚可,有轻微的过拟合倾向")
|
|||
|
|
else:
|
|||
|
|
print("✗ 模型状态: 可能存在过拟合,建议调整模型复杂度或增加数据")
|
|||
|
|
print(f"{'='*60}\n")
|
|||
|
|
|
|||
|
|
# ==================== 输出完成信息和后续步骤 ====================
|
|||
|
|
# 显示训练完成信息
|
|||
|
|
print("训练完成! 下一步:")
|
|||
|
|
# 提示步骤1:将ONNX模型文件复制到MT5的指定目录
|
|||
|
|
print("1. 将 lstm_model.onnx 复制到 MT5/MQL5/Files/")
|
|||
|
|
# 提示步骤2:将归一化参数复制到EA代码中
|
|||
|
|
print("2. 将上面的归一化参数复制到 LSTM_EA.mq5")
|
|||
|
|
# 提示步骤3:编译EA并运行回测
|
|||
|
|
print("3. 编译并运行EA进行回测")
|