将您自己的 LLM 集成到 EA 中(第 5 部分):使用 LLM 开发和测试交易策略(二)-LoRA-调优·综合运用
对齐真实与生成序列再算误差
用 LLM 生成价格序列后,第一步不是直接看涨跌,而是先把真实序列和模型输出截成等长。trim_lists 取两者最短长度做切片,避免后续算 MSE 时因维度不一致报错。 误差评估用三组指标:MSE 是均方误差,rmse 取其平方根回到价格量纲,nrmse 再除以真实值极差做归一。若 true_prices 极差接近 0,nrmse 会爆大,这种样本直接剔除别进回测。 生成阶段用 model.generate 跑 200 token,do_sample=True 代表带随机性,同一 prompt 每次结果可能不同。把输出按换行切出首行、转 float 列表,再截到 len(true_prices) 长度,这一步决定了你后面画图时两根曲线的可比性。 plot_ 里只在 title=='prediction' 才画真实值,其余情况只叠加 fine_tuning 与 lora_tuning 两条线。跑完建议把 png 存下来,肉眼比对 X 与方块标记偏离度,比单看 RMSE 数字更直觉。外汇与贵金属波动剧烈,此类生成结果仅作概率参考,实盘前务必在 MT5 历史数据上验证。
def trim_lists(a, b): min_len = min(len(a), len(b)) class="kw">return a[:min_len], b[:min_len] true_prices, generated_prices = trim_lists(true_prices, generated_prices) print(f"Input data: {input_data}") print(f"True prices: {true_prices}") print(f"Generated prices: {generated_prices}") mse = mean_squared_error(true_prices, generated_prices) print(&class="macro">#x27;MSE:&class="macro">#x27;, mse) rmse = np.sqrt(mse) nrmse = rmse / (np.max(true_prices) - np.min(generated_prices)) print(f"RMSE: {rmse}, NRMSE: {nrmse}") # generater 节选 generated = tokenizer.decode( model.generate(token, do_sample=True, max_length=class="num">200)[class="num">0], skip_special_tokens=True ) generated_prices = generated.split(&class="macro">#x27;\n&class="macro">#x27;)[class="num">0] generated_prices = list(map(class="type">float, generated_prices.split())) generated_prices = generated_prices[:len(true_prices)] # plot_ 节选 plt.figure(figsize=(class="num">10, class="num">6)) if title == &class="macro">#x27;prediction&class="macro">#x27;: plt.plot(true_prices, label=&class="macro">#x27;True Values&class="macro">#x27;, marker=&class="macro">#x27;o&class="macro">#x27;) plt.plot(a, label=&class="macro">#x27;fine_tuning&class="macro">#x27;, marker=&class="macro">#x27;x&class="macro">#x27;) plt.plot(b, label=&class="macro">#x27;lora_tuning&class="macro">#x27;, marker=&class="macro">#x27;s&class="macro">#x27;) plt.title(title) plt.xlabel(&class="macro">#x27;Index&class="macro">#x27;) plt.ylabel(&class="macro">#x27;Value&class="macro">#x27;) plt.legend() plt.savefig(f"{title}.png")
「两种微调路线的对比图怎么画」
把全量微调(fine-tuning)和 LoRA 微调(lora-tuning)的预测表现并排看,最直观的办法是用 matplotlib 画分组柱状图。下面这段 Python 脚本先给两组模型灌入最近一条样本(df.iloc[:,1:20].values[-1]),再各自生成 200 token 以内的价格序列,最后用 plt.bar 分左右两簇展示。 柱宽设成 0.2,r1 为模型 a 的基准位置,r2 在 r1 基础上右移一个 bar_width,这样两组柱子不会重叠。y 轴切到对数刻度(plt.yscale('log'))是因为两类指标量纲差太大,线性轴会把小值压成一条线。 实测里模型 a 的首项指标读到 101.7946、模型 b 为 69.5605,差距接近 1.46 倍;若直接线性轴看图,b 组后几项(0.877、4.10)基本看不见。图存成 Comparison.png,不弹窗,方便你批量跑完再翻。 外汇与贵金属价格受宏观事件扰动大,这类生成结果只反映历史样本上的统计倾向,实盘使用前请在 MT5 用真实 tick 复核,杠杆品种高风险。
class="kw">import time class="kw">import pandas as pd from transformers class="kw">import GPT2LMHeadModel, GPT2Tokenizer, GPT2Config from sklearn.metrics class="kw">import mean_squared_error class="kw">import torch class="kw">import numpy as np from peft class="kw">import PeftModel class="kw">import matplotlib.pyplot as plt # Load the dataset df = pd.read_csv(&class="macro">#x27;llm_data.csv&class="macro">#x27;) # Define the device(GPU if available) dvc = &class="macro">#x27;cuda&class="macro">#x27; if torch.cuda.is_available() else &class="macro">#x27;cpu&class="macro">#x27; # Model paths and base settings base_model = &class="macro">#x27;gpt2&class="macro">#x27; fine_tuning_path = &class="macro">#x27;./gpt2_stock&class="macro">#x27; lora_tuning_path = &class="macro">#x27;./gpt2_LORA_None&class="macro">#x27; # Load the tokenizer and models tokenizer = GPT2Tokenizer.from_pretrained(base_model) model_fine_tuning = GPT2LMHeadModel.from_pretrained(fine_tuning_path).to(dvc) model_lora_tuning = GPT2LMHeadModel.from_pretrained(base_model) model_lora_tuning = PeftModel.from_pretrained(model_lora_tuning, lora_tuning_path).to(dvc) # Extract the input data and true prices from the dataset input_data = df.iloc[:, class="num">1:class="num">20].values[-class="num">1] true_prices = df.iloc[-class="num">1:, class="num">21:].values.tolist()[class="num">0] prompt = &class="macro">#x27; &class="macro">#x27;.join(map(str, input_data)) # Function to generate predictions def generater(model): global true_prices model.eval() # Tokenization and text generation token = tokenizer.encode(prompt, return_tensors=&class="macro">#x27;pt&class="macro">#x27;).to(dvc) start_ = time.time() generated = tokenizer.decode( model.generate(token, do_sample=True, max_length=class="num">200)[class="num">0], skip_special_tokens=True ) end_ = time.time() print(f&class="macro">#x27;Generate time: {end_ - start_}&class="macro">#x27;) # Processing generated prices generated_prices = generated.split(&class="macro">#x27;\n&class="macro">#x27;)[class="num">0] generated_prices = list(map(class="type">float, generated_prices.split())) generated_prices = generated_prices[:len(true_prices)] # Function to trim lists to the same length def trim_lists(a, b): pass plt.figure(figsize=(class="num">10, class="num">6)) # Update values for model a and b a = [class="num">101.7946, class="num">1.243, class="num">5.67, a[class="num">1], a[class="num">2], a[class="num">3]] b = [class="num">69.5605, class="num">0.877, class="num">4.10, b[class="num">1], b[class="num">2], b[class="num">3]] # Bar width for each group of bars bar_width = class="num">0.2 # Set the positions of the bars r1 = np.arange(len(metrics)) # Positions for model a r2 = [x + bar_width for x in r1] # Positions for model b # Plot bars for both models plt.bar(r1, a, class="type">class="kw">color=&class="macro">#x27;r&class="macro">#x27;, width=bar_width, edgecolor=&class="macro">#x27;grey&class="macro">#x27;, label=models[class="num">0]) plt.bar(r2, b, class="type">class="kw">color=&class="macro">#x27;b&class="macro">#x27;, width=bar_width, edgecolor=&class="macro">#x27;grey&class="macro">#x27;, label=models[class="num">1]) # Set log scale for y-axis plt.yscale(&class="macro">#x27;log&class="macro">#x27;) # Set labels and title plt.xlabel(&class="macro">#x27;Metrics&class="macro">#x27;, fontweight=&class="macro">#x27;bold&class="macro">#x27;) plt.xticks([r + bar_width / class="num">2 for r in range(len(metrics))], metrics) # Center the x-axis ticks plt.ylabel(&class="macro">#x27;Values(log scale)&class="macro">#x27;, fontweight=&class="macro">#x27;bold&class="macro">#x27;) plt.title(&class="macro">#x27;Model Comparison&class="macro">#x27;) # Display legend and save the plot plt.legend() # plt.show() # Uncomment to display the plot plt.savefig(&class="macro">#x27;Comparison.png&class="macro">#x27;) fine_tuning_result = generater(model_fine_tuning) lora_tuning_result = generater(model_lora_tuning) plot_(fine_tuning_result[class="num">0],lora_tuning_result[class="num">0],title=&class="macro">#x27;predication&class="macro">#x27;) groups_chart(fine_tuning_result,lora_tuning_result,models=[&class="macro">#x27;fine-tuning&class="macro">#x27;,&class="macro">#x27;lora-tuning&class="macro">#x27;])
◍ 对齐价格序列并量化两种微调的偏差
把真实行情和模型生成的价格放在一起比之前,得先截到同样长度。trim_lists 用 min(len(a), len(b)) 取短边,再对两个列表做切片,避免后面算 MSE 时维度对不上。 误差计算走的是均方误差路线:mse = mean_squared_error(true_prices, generated_prices),rmse 开根号,nrmse 用 rmse 除以真实价最大值与生成价最小值之差做归一。外汇与贵金属价格序列波动大,NRMSE 若超过 0.1 往往意味着生成轨迹已严重偏离实盘,仅作模型筛选参考,实盘高风险依旧。 groups_chart 里两组模型的硬指标已经跑出:全量微调训练 101.7946 秒、推理 1.243 秒、显存 5.67 GB;LoRA 微调对应 69.5605 秒、0.877 秒、4.10 GB。柱状图用对数纵轴,差距一眼可辨。 直接在 MT5 外接的 Python 环境里复跑 generater 拿到四个返回值,再调 plot_ 和 groups_chart,就能把 fine-tuning 与 lora-tuning 的预测线和指标柱图存成 png,肉眼核对哪条线更贴 true_prices。
min_len = min(len(a), len(b)) class="kw">return a[:min_len], b[:min_len] # Trim the true prices and generated prices true_prices, generated_prices = trim_lists(true_prices, generated_prices) # Output metrics print(f"Input data: {input_data}") print(f"True prices: {true_prices}") print(f"Generated prices: {generated_prices}") mse = mean_squared_error(true_prices, generated_prices) print(&class="macro">#x27;MSE:&class="macro">#x27;, mse) rmse = np.sqrt(mse) nrmse = rmse / (np.max(true_prices) - np.min(generated_prices)) print(f"RMSE: {rmse}, NRMSE: {nrmse}") class="kw">return generated_prices, mse, rmse, nrmse # Function to plot the comparison between true prices and predictions def plot_(a, b, title): plt.figure(figsize=(class="num">10, class="num">6)) if title == &class="macro">#x27;prediction&class="macro">#x27;: plt.plot(true_prices, label=&class="macro">#x27;True Values&class="macro">#x27;, marker=&class="macro">#x27;o&class="macro">#x27;) plt.plot(a, label=&class="macro">#x27;fine_tuning&class="macro">#x27;, marker=&class="macro">#x27;x&class="macro">#x27;) plt.plot(b, label=&class="macro">#x27;lora_tuning&class="macro">#x27;, marker=&class="macro">#x27;s&class="macro">#x27;) plt.title(title) plt.xlabel(&class="macro">#x27;Index&class="macro">#x27;) plt.ylabel(&class="macro">#x27;Value&class="macro">#x27;) plt.legend() plt.savefig(f"{title}.png") # Function to generate a bar chart comparing different metrics between models def groups_chart(a, b, models): metrics = [&class="macro">#x27;Train Time(s)&class="macro">#x27;, &class="macro">#x27;Inference Time(s)&class="macro">#x27;, &class="macro">#x27;Memory Usage(GB)&class="macro">#x27;, &class="macro">#x27;MSE&class="macro">#x27;, &class="macro">#x27;RMSE&class="macro">#x27;, &class="macro">#x27;NRMSE&class="macro">#x27;] plt.figure(figsize=(class="num">10, class="num">6)) # Data for the metrics a = [class="num">101.7946, class="num">1.243, class="num">5.67, a[class="num">1], a[class="num">2], a[class="num">3]] b = [class="num">69.5605, class="num">0.877, class="num">4.10, b[class="num">1], b[class="num">2], b[class="num">3]] bar_width = class="num">0.2 r1 = np.arange(len(metrics)) r2 = [x + bar_width for x in r1] # Plotting bars for both models plt.bar(r1, a, class="type">class="kw">color=&class="macro">#x27;r&class="macro">#x27;, width=bar_width, edgecolor=&class="macro">#x27;grey&class="macro">#x27;, label=models[class="num">0]) plt.bar(r2, b, class="type">class="kw">color=&class="macro">#x27;b&class="macro">#x27;, width=bar_width, edgecolor=&class="macro">#x27;grey&class="macro">#x27;, label=models[class="num">1]) # Set y-axis to log scale for better visibility of differences plt.yscale(&class="macro">#x27;log&class="macro">#x27;) plt.xlabel(&class="macro">#x27;Metrics&class="macro">#x27;, fontweight=&class="macro">#x27;bold&class="macro">#x27;) plt.xticks([r + bar_width for r in range(len(metrics))], metrics) plt.ylabel(&class="macro">#x27;Values(log scale)&class="macro">#x27;, fontweight=&class="macro">#x27;bold&class="macro">#x27;) plt.title(&class="macro">#x27;Model Comparison&class="macro">#x27;) plt.legend() plt.savefig(&class="macro">#x27;Comparison.png&class="macro">#x27;) # Generate results for both fine-tuned and LORA-tuned models fine_tuning_result = generater(model_fine_tuning) lora_tuning_result = generater(model_lora_tuning) # Plot the prediction comparison plot_(fine_tuning_result[class="num">0], lora_tuning_result[class="num">0], title=&class="macro">#x27;prediction&class="macro">#x27;) # Generate the comparison chart for the models groups_chart(fine_tuning_result, lora_tuning_result, models=[&class="macro">#x27;fine-tuning&class="macro">#x27;, &class="macro">#x27;lora-tuning&class="macro">#x27;])
下一步该怎么试模型
这一轮我们用 LoRA 对 GPT-2 做了微调,并横向比了几种训练设定,结论很直接:没有哪种方法在所有行情里都占优,选哪个取决于你策略样本的分布和标注质量。附带的数据集 llm_data.csv 有 1139.04 KB、train.txt 有 1123.41 KB,脚本 lora-tuning.py 和 test.py 分别 2.64 KB、3.23 KB,直接拉到本地就能复跑。 模型参数尺度摆在那,GPT-2 微调后的输出可能和理想交易信号差得远,但调参和验证的流程是通用的。别指望靠几组例子就定死最佳模型,同架构不同参数量表现都能分化。 真要落地,建议先按本文的 LoRA 配置跑通 test.py,把预测和你的价格行为信号叠一起看吻合度。外汇和贵金属波动大、杠杆高,模型只是辅助,实盘前务必小仓位验证。