将您自己的 LLM 集成到 EA 中(第 5 部分):使用 LLM 开发和测试交易策略(三) 适配器微调·综合运用
(3/3)· 从环境配置到性能横评,用适配器微调给 GPT-2 收尾,并指明接 EA 的下一步
「三种微调路线在收盘序列上的实测分野」
把全参数、LoRA、适配器三种微调放在同一组黄金/外汇式收盘序列上跑,效率差一眼可见。LoRA 训练耗时 69.56 秒、吃 4.1G 显存,生成一条序列 1.24 秒;全参数训练 101.79 秒、5.67G 显存、生成 0.88 秒;适配器训练 104.44 秒、5.52G 显存、生成 0.88 秒。LoRA 最省资源,全参和适配器在推理速度上略快半档。 准确度用最后一行前 20 个收盘价做输入,激进拉长到 40 步预测。全参数 MSE 1.257e-07、NRMSE 0.432;LoRA MSE 1.016e-07、NRMSE 0.389;适配器 MSE 1.564e-07、NRMSE 0.494。LoRA 在误差上反而压过全参,适配器最弱,外汇与贵金属序列的高噪声可能放大了适配器的拟合偏差。 指标量级不一致,画图时直接 plt.yscale('log') 才能同框比。下面这段 test.py 把三个模型从本地文件夹加载并推理,改 fine_tuning_path 等变量就能在 MT5 导出的 csv 上复跑。
<span class="keyword">class="kw">import</span> time <span class="keyword">class="kw">import</span> pandas <span class="keyword">as</span> pd <span class="keyword">from</span> transformers <span class="keyword">class="kw">import</span> GPT2LMHeadModel, GPT2Tokenizer, GPT2Config <span class="keyword">from</span> sklearn.metrics <span class="keyword">class="kw">import</span> mean_squared_error <span class="keyword">class="kw">import</span> torch <span class="keyword">class="kw">import</span> numpy <span class="keyword">as</span> np <span class="keyword">from</span> peft <span class="keyword">class="kw">import</span> PeftModel <span class="keyword">class="kw">import</span> matplotlib.pyplot <span class="keyword">as</span> plt <span class="keyword">from</span> adapter_tuning <span class="keyword">class="kw">import</span> GPT2LMHeadModelWithAdapters df = pd.read_csv(<span class="class="type">class="kw">string">&class="macro">#x27;llm_data.csv&class="macro">#x27;</span>) dvc=<span class="class="type">class="kw">string">&class="macro">#x27;cuda&class="macro">#x27;</span> <span class="keyword">if</span> torch.cuda.is_available() <span class="keyword">else</span> <span class="class="type">class="kw">string">&class="macro">#x27;cpu&class="macro">#x27;</span> base_model=<span class="class="type">class="kw">string">&class="macro">#x27;gpt2&class="macro">#x27;</span> fine_tuning_path=<span class="class="type">class="kw">string">&class="macro">#x27;./gpt2_stock&class="macro">#x27;</span> lora_tuning_path =<span class="class="type">class="kw">string">&class="macro">#x27;./gpt2_LORA_None&class="macro">#x27;</span> adpter_tuning_path=<span class="class="type">class="kw">string">&class="macro">#x27;./gpt2_Adapter-tuning&class="macro">#x27;</span> pre_length=<span class="number">class="num">40</span> 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) model_adapter_tuning = GPT2LMHeadModelWithAdapters.from_pretrained(adpter_tuning_path).to(dvc) input_data=df.iloc[:,<span class="number">class="num">1</span>:<span class="number">class="num">20</span>].values[-<span class="number">class="num">1</span>] true_prices= df.iloc[-<span class="number">class="num">1</span>:,<span class="number">class="num">21</span>:].values.tolist()[<span class="number">class="num">0</span>] prompt = <span class="class="type">class="kw">string">&class="macro">#x27; &class="macro">#x27;</span>.join(<span class="built_in">map</span>(<span class="built_in">str</span>, input_data)) <span class="keyword">def</span> generater(model): <span class="keyword">global</span> true_prices model.<span class="built_in">eval</span>() token=tokenizer.encode(prompt, return_tensors=<span class="class="type">class="kw">string">&class="macro">#x27;pt&class="macro">#x27;</span>).to(dvc) start_=time.time() generated = tokenizer.decode(model.generate(token, do_sample=<span class="literal">True</span>, max_length=<span class="number">class="num">200</span>)[<span class="number">class="num">0</span>], skip_special_tokens=<span class="literal">True</span>) end_=time.time() <span class="built_in">print</span>(<span class="class="type">class="kw">string">f&class="macro">#x27;generate time:<span class="subst">{end_-start_}</span>&class="macro">#x27;</span>) generated_prices=generated.split(<span class="class="type">class="kw">string">&class="macro">#x27;\n&class="macro">#x27;</span>)[<span class="number">class="num">0</span>] generated_prices=<span class="built_in">list</span>(<span class="built_in">map</span>(<span class="built_in">class="type">class="kw">float</span>,generated_prices.split())) generated_prices=generated_prices[<span class="number">class="num">0</span>:pre_length] <span class="comment"># def trim_lists(a, b):</span> <span class="comment"># min_len = min(len(a), len(b))</span>
◍ 三类微调方案的回测指标对照
这段脚本把 fine-tuning、lora-tuning、adapter-tuning 三种模型在价格生成任务上的表现直接拉成可比数据。generater 函数先打印 input_data、true_prices、generated_prices,再用 mean_squared_error 算 MSE,顺手给出 RMSE 与 NRMSE——NRMSE 用 rmse 除以真实价最大值与生成价最小值之差做归一,避免量纲干扰。 groups_chart 里写死了三组硬件与时延底数:full fine-tune 训练 101.79s、推理 1.243s、占显存 5.67GB;LoRA 训练 69.56s、推理 0.877s、占 4.10GB;adapter 训练 104.44s、推理 0.883s、占 5.52GB。在 log 坐标下并列 Train_time、Infer_time、Memory、MSE、RMSE、NRMSE 六项,差距一眼可辨。 实盘意义在于:LoRA 在训练耗时和显存上明显占优,若你用 MT5 + Python 桥接做 AIGC 辅助报价,倾向优先试 lora_tuning。外汇与贵金属波动剧烈、杠杆风险高,任何生成价都只是概率参考,不能直接当入场依据。 把这段代码丢进本地 Python(需 numpy、sklearn、matplotlib)跑一遍,Comparison.png 和 predication.png 会落盘;重点看 NRMSE 那根柱,谁低谁对真实序列还原更稳。
# class="kw">return a[:min_len], b[:min_len] # true_prices,generated_prices=trim_lists(true_prices,generated_prices) print(f"class="kw">input data:{input_data}") print(f"true prices:{true_prices}") print(f"generated prices:{generated_prices}") mse = mean_squared_error(true_prices[:pre_length], 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 def plot_(a,b,c,title): plt.figure(figsize=(class="num">7, class="num">6)) if title==&class="macro">#x27;predication&class="macro">#x27;: plt.plot(true_prices[:pre_length], 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.plot(c,label=&class="macro">#x27;adapter_tuning&class="macro">#x27;,marker=&class="macro">#x27;d&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") def groups_chart(a,b,c,models): metrics = [&class="macro">#x27;Train_time(s)&class="macro">#x27;, &class="macro">#x27;Infer_time(s)&class="macro">#x27;, &class="macro">#x27;Memory(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">7, class="num">6)) 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]] c=[class="num">104.4355,class="num">0.883,class="num">5.52,c[class="num">1],c[class="num">2],c[class="num">3]]# class="num">104.4355s,VRAM:class="num">5.52G generate_runtime:class="num">0.882792s bar_width = class="num">0.2 r1 = np.arange(len(metrics)) r2 = [x + bar_width for x in r1] r3 = [x + bar_width for x in r2] 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]) plt.bar(r3, c, class="type">class="kw">color=&class="macro">#x27;g&class="macro">#x27;, width=bar_width, edgecolor=&class="macro">#x27;grey&class="macro">#x27;, label=models[class="num">2]) 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.show() plt.savefig(&class="macro">#x27;Comparison.png&class="macro">#x27;) fine_tuning_result = generater(model_fine_tuning) lora_tuning_result = generater(model_lora_tuning) adapter_tuning_result=generater(model_adapter_tuning) plot_(fine_tuning_result[class="num">0],lora_tuning_result[class="num">0],adapter_tuning_result[class="num">0],title=&class="macro">#x27;predication&class="macro">#x27;) groups_chart(fine_tuning_result,lora_tuning_result,adapter_tuning_result,models=[&class="macro">#x27;fine-tuning&class="macro">#x27;,&class="macro">#x27;lora-tuning&class="macro">#x27;,&class="macro">#x27;adapter-tuning&class="macro">#x27;])
把工具请下神坛
适配器微调比 LoRA 多耗约 10%~20% 的 VRAM,训练步长也略长,但它把任务专属信息锁在独立模块里,换策略时不用动底座权重。全参数微调依旧是精度基线,LoRA 拼效率,适配器拼可插拔——选哪个只看你手头显卡和要跑几个品种。 下一阶段直接拿训好的模型写 EA 策略做回测,外汇与贵金属杠杆高、滑点跳空频繁,模型信号只是概率倾向而非保本凭证。代码包里 adapter_tuning.py 与 test.py 已就绪,自己一步步跑通比看十篇闲谈更有用。