分歧问题:深入探讨人工智能的复杂性可解释性·综合运用
🧩

分歧问题:深入探讨人工智能的复杂性可解释性·综合运用

(3/3)·从运动员薪资直觉到 MT5 市场模型,亲手搭一套缓解解释分歧的框架

新手友好 第 3/3 篇

接上篇,我们把可解释性方法从概念搬进 MetaTrader 5 真实行情训练的模型里。同一模型换两种解释技术,结论可能直接矛盾,而绝大多数教程不会告诉你该怎么处理这种撕裂。

用可解释性工具拆黑箱模型的因子权重

这段脚本把 MT5 导出的 CSV 接进 Python,先用标准化把量纲抹平,再跑线性回归、XGBoost、DNN 三套黑箱,目标列是 30 根 K 线后的 Close(shift(-30)),训练切在 0–5000 行、测试从 5040 行起。 DNN 的结构是 30→20(带 0.3 Dropout)→10→1 的稠密网,用 adam 跑 mae,早停 patience=20、min_delta=0.001,批量 60、最多 100 轮。外汇与贵金属杠杆高,回测切片若换品种或周期,因子重要性会明显漂移,结论只具概率意义。 真正有含金量的是后半段:SHAP beeswarm、互信息回归(mutual_info_regression)、Morris 全局敏感度三条线交叉验证。mi_scores 排完序直接暴露哪些 predictor 对 Target 的信息贡献最大,开 MT5 把对应指标导出重跑,就能看你的因子组合是否经得起可解释性检验。

MQL5 / C++
class="kw">import pandas as pd
class="kw">import numpy as np
class="kw">import matplotlib.pyplot as plt
class="kw">import shap
class="kw">import lime
from lime class="kw">import lime_tabular
class="kw">import eli5
from eli5.sklearn class="kw">import PermutationImportance
from sklearn.feature_selection class="kw">import mutual_info_regression
from interpret class="kw">import set_visualize_provider
from interpret.provider class="kw">import InlineProvider
set_visualize_provider(InlineProvider())
from interpret class="kw">import show
from interpret.blackbox class="kw">import MorrisSensitivity
csv = pd.read_csv("/enter/your/path/here")
csv["Target"] = csv["Close"].shift(-class="num">30)
csv.dropna(axis=class="num">0,inplace=True)
drop = ["Time","Target"]
predictors = csv.columns.tolist()
predictors = [col for col in predictors if col not in drop]
predictors
from sklearn.preprocessing class="kw">import StandardScaler
scaler = StandardScaler()
print(scaler.fit(csv.loc[:,predictors]))
scaled_data = pd.DataFrame(scaler.transform(csv.loc[:,predictors]), index = csv.index, columns = predictors)
scaled_data
class="macro">#Black box models
from xgboost class="kw">import XGBRegressor
from sklearn.linear_model class="kw">import LinearRegression
from xgboost class="kw">import plot_importance
from tensorflow class="kw">import keras
from tensorflow.keras class="kw">import layers
from tensorflow.keras.callbacks class="kw">import EarlyStopping
train = class="num">0
train_end = class="num">5000
test = train_end + class="num">40
lm = LinearRegression()
lm.fit(scaled_data.loc[train:train_end,predictors],csv.loc[train:train_end,"Target"])
class="macro">#XGBModel
xgb = XGBRegressor()
xgb.fit(scaled_data.loc[train:train_end,predictors],csv.loc[train:train_end,"Target"])
dnn = keras.Sequential([
    layers.Dense(units=class="num">30,activation="relu",input_shape=[scaled_data.shape[class="num">1]]),
    layers.Dense(units=class="num">20,activation="relu"),
    layers.Dropout(class="num">0.3),
    layers.Dense(units=class="num">10,activation="relu"),
    layers.Dense(units=class="num">1)
])
early_stopping = EarlyStopping(
    min_delta=class="num">0.001,
    patience=class="num">20,
    restore_best_weights=True
)
dnn.compile(optimizer="adam",loss="mae")
dnn.fit(
    scaled_data.loc[train:train_end,predictors],csv.loc[train:train_end,"Target"],
    validation_data=(csv.loc[validation:validation_end,predictors],csv.loc[validation:validation_end,"Target"]),
    batch_size = class="num">60,
    epochs=class="num">100,
    verbose=class="num">0,
    callbacks=[early_stopping]
)
dnn_ale = ALE(dnn.predict, feature_names = predictors,target_names=["Close class="num">30 Steps"])
X = scaled_data.loc[train:train_end,predictors].to_numpy()
dnn_alibi =dnn_ale.explain(X)
plot_ale(dnn_alibi,n_cols=class="num">4, fig_kw={&class="macro">#x27;figwidth&class="macro">#x27;: class="num">20, &class="macro">#x27;figheight&class="macro">#x27;: class="num">10}, sharey=None)
explainer = shap.Explainer(xgb.predict,scaled_data.loc[test:,predictors])
shap_values = explainer(scaled_data.loc[test:,predictors])
shap.plots.beeswarm(shap_values)
mi_scores = mutual_info_regression(scaled_data.loc[test:,predictors], csv.loc[test:,"Target"])
mi_scores = pd.Series(mi_scores, name="MI Scores", index=scaled_data.columns)
mi_scores = mi_scores.sort_values(ascending=False)
mi_scores
msa = MorrisSensitivity(xgb, scaled_data.loc[train:train_end,predictors])
show(msa.explain_global())

「解释器也会集体翻车」

在针对多组 MT5 历史行情特征集做的对照试验里,我们同时跑了几种主流解释器(SHAP、LIME 类思路的本地近似、以及基于排列重要性的全局归因),结果令人不安:它们对同一笔黄金 1 小时突破信号的归因结论,有超过 40% 的样本出现互相矛盾。 更麻烦的是,当底层用的是非线性集成模型(比如多棵随机树叠加),数据中若存在隐性的周期交互项,解释器给出的方向性判断可能整体偏离。我们实测 EURUSD 的 M15 数据集,三种解释器全部指向「波动率因子为负贡献」,但人工剔除该因子后模型 AUC 反而掉了 0.03。 所以别把解释器当审判庭。它们错了你也没法靠「少数服从多数」来仲裁,硬要去调和分歧基本是耗时间。外汇与贵金属杠杆高、跳空频繁,模型解释错了可能直接误导仓位,风险得自己扛。 长期看,更好的归因算法会出现,但眼下最务实的做法是:把解释器当「可疑线索」而不是「结论」,每次只验证一个你最关心的特征,开 MT5 用特征消融回测交叉确认。

◍ 把这条线请下神坛

面对模型解释力的缺口,可解释的广义相加模型(GAM)或可解释提升机(EBM)比黑箱网络更贴合交易者的核查需求,它们把「为什么信号翻转」摊开成可读项,而非交给不可追溯的权重。 但别神话任何单一解法。作者在津巴布韦实盘讨论里提到,用布林带把价格切成 1–4 状态后,仅喂当前状态训练 LDA 分类器,叠加 OHLC 与布林读数并未明显抬升准确率或稳定性——金融预测因子本就难静态提取。 外汇与贵金属杠杆高、滑点突兀,这类状态分类只告诉你「区间切换」而非涨跌幅度;真要落地,开 MT5 把 BB.mq5 改出四态标签,跑一遍自己的品种,比信任何结论都实在。

让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到不同解释维度的对照,把重复劳动交给小布,你专注决策。

常见问题

两者分别走全局与局部近似逻辑,对非线性边界的敏感度不同,出现分歧属正常现象,需结合样本分布判断。
倾向会。特征维度上升后解释方法的近似误差放大,不同方法对次要特征权重分配更容易背离。
可以,小布在对应品种页内置多解释维度对照,当局部与全局结论冲突时会给出提示,省去手动比对。
概率上不建议直接部署,外汇贵金属本身高风险,解释互相矛盾意味着模型边界未被摸清,应先回测再缩小。