
分歧问题:深入探讨人工智能的复杂性可解释性
分歧问题
分歧问题是可解释人工智能(XAI,Explainable Artificial Intelligence)的一个重要研究领域。XAI 旨在帮助我们了解人工智能模型如何做出决策,但这说起来容易做起来难。
我们都知道,机器学习模型和可用数据集正在变得越来越大、越来越复杂。事实上,开发机器学习算法的数据科学家无法准确解释他们的算法在所有可能的数据集中的行为。 可解释的人工智能(XAI)可以帮助我们建立对模型的信任,解释模型的功能,并验证模型是否可以部署到生产中;但尽管这听起来很有希望,本文将向读者展示为什么我们不能盲目相信可解释的人工智能技术应用所提供的任何解释。
目录
概述
机器学习让我们能够学习数据中存在的关系和交互,但我们如何才能学习模型中存在的关系和交互呢? 我们回答这个问题的最好方法就是采用模型解释技术。在讨论中,我们将探讨几种不同的解释技术。通过模型解释技术,我们可以回答以下问题:- 我们的模型发现哪些特征信息量最大?
- 如果我们的数据集中有 1000 个特征,我们如何区分信息量最大和最小的特征?
- 当一个特征发生变化时,我们模型的输出会如何变化?
- 哪些特征值得进一步设计?
上述问题的答案具有实质价值,但有一个重大障碍可能会阻碍我们的前进。我们面临的挑战是,有时我们可能会观察到评估同一模型的不同解释之间存在分歧。遗憾的是,在撰写本报告时,还没有任何全球公认的过程来解决这一问题,但今天我们将下定决心,建立我们自己的框架来缓解这一问题。
将人工智能融入更广泛的应用是全球趋势。然而,我们必须能够彻底解释我们的模型、它所学到的任何关联以及它的决策过程,然后才能考虑信任模型。这种理想的特性被称为 "可解释性"。可解释人工智能(XAI)具有巨大的潜力,可以追踪任何给定模型是如何达到其预测的。可解释的人工智能之所以存在,是因为随着我们的建模技术变得越来越复杂,我们解释它们的能力逐渐减弱。
首先,我们将首先通过一个简单的例子,使用大多数读者可能已经具备领域知识的概念。通过一起解决一个较简单的问题,我们将很快形成对不同可解释性技术的直觉。完成这个示例后,我们就可以将我们的技能应用于根据从 MetaTrader 5 终端获取的真实市场数据训练的任何机器学习模型。
我们要考虑的示例是一个简单的问题,即根据运动员的身体素质估算其工资。显而易见,随着运动员身体素质的提高,他们可以获得更高的薪水,但问题是,哪些身体素质对薪水的增长影响最大?
我们将在示例问题中使用的数据集是由视频游戏巨头 Electronic Arts 精心制作的,作为其多产的视频游戏 "Madden NFL" 系列的一部分,玩家可以模拟自己喜爱的美式职业橄榄球运动员的比赛。该数据集包含美式橄榄球职业球员的详细统计数据。我们将训练 4 个不同的模型,根据球员的年龄、冲刺速度和力量等特征来预测球员的薪水。从那里,我们将应用不同的模型解释技术,并观察我们可以从球员的属性和球员的薪水之间的关系中学到什么见解。我们将介绍每种解释技巧是如何被解释的,然后观察我们是否有任何矛盾的解释。我们将给分歧贴上标签,尝试破解可能导致分歧的原因,并讨论可能的解决方案。
图 1:电子艺界制作的 Madden NFL 视频游戏。
可解释性方法概述
广义上讲,可解释性方法可以按许多不同的方式进行分类。最简单的方法是将它们分为两类:白盒解释器和黑盒解释器。在上一篇文章中,我们讨论了玻璃盒模型和黑盒模型。今天我们的重点不是预测模型,所以我们所说的 "黑盒" 并不是复杂难解的机器学习模型。我们指的是帮助我们解释基础模型的可解释性算法。
黑盒解释技术可用于任何类型的模型;它们也被称为与模型无关的解释器。
白盒解释技术旨在利用特定类型底层模型的结构,提供可能更忠实于底层模型的解释。
正如人们所预料的那样,不同的可解释方法会产生不同的结果:
- 对基础数据的形式和结构持有不同的假设。
- 定义和评估不同的指标,以了解模型。
- 在不同条件下失灵。
可解释性算法也可以通过其方法论来区分。例如,有一些解释技术通过每次调整每个特征的输入并观察预测结果的后续变化来获得洞察力。这些技术可归类为基于扰动的技术。而另一方面,有一些可解释的算法试图了解模型对特征变化的敏感程度。这些技术可以归类为基于梯度的技术,因为它们采用模型输出相对于其特征的导数。
今天,我们只介绍几种不同类型的解释性算法。还有更多的解释技术超出了我们的讨论范围,因此这份清单绝非详尽无遗。
最后,管理好我们的期望值也很重要。建立一个能够以超过50%的准确率预测证券价格的模型并不一定能保证交易获利。
全局解释和局部解释
如果我们想更多地了解特征的重要性和模型的整体行为,就需要采用全局解释技术。相反,如果我们想详细了解我们的模型是如何得出某个特定预测的,那么我们就需要局部解释。
局部解释有助于我们在细微层面上理解特征和目标之间的关系,并帮助我们建立对模型预测的信任。此外,我们还可以更好地了解哪些特征会导致错误预测,并考虑对这些特征进行工程设计,以从中提取更多有用的信息。
因此,在我们的简单例子中,如果我们想知道哪些特征对预测运动员的工资很重要,我们需要应用排列重要性等全局解释技术。 此外,如果我们想更详细地了解某个运动员的预测,我们需要像 LIME 这样的局部解释技术。
让我们从我们的示例案例开始,了解我们模型的全局解释。
像往常一样,我们首先安装所需的依赖项。
在本例中,我们需要安装:
- Shap
- eli5
- Lime
- Interpret
- alibi
pip install alibi shap lime eli5 interpret
接下来,我们加载常用的依赖项。
import pandas as pd import numpy as np import matplotlib.pyplot as plt
现在我们可以读取游戏数据集了。
csv = pd.read_csv("/add/your/path/here/to/the/madden/csv")
让我们看看数据集中的几行和几列。
图 2:我们的示例数据集
为了简单起见,我们只保留一些数字特征。分类特征需要编码技术和细节,这与我们的讨论范围不符。
predictors = ["awareness_rating","throwPower_rating","kickReturn_rating", "leadBlock_rating","strength_rating","catchInTraffic_rating", "pursuit_rating", "catching_rating","acceleration_rating", "height","tackle_rating","yearsPro","throwUnderPressure_rating", "throwAccuracyDeep_rating","throwAccuracyShort_rating","speed_rating", "jumping_rating","toughness_rating","kickPower_rating", "kickAccuracy_rating","agility_rating","passBlock_rating","age" ] csv[predictors].dtypes
awareness_rating int64
throwPower_rating int64
kickReturn_rating int64
现在,我们将确定目标。
target = "totalSalary"
现在,我们将设置要使用的模型
from sklearn.linear_model import LinearRegression from sklearn.neighbors import KNeighborsRegressor from sklearn.ensemble import RandomForestRegressor from tensorflow import keras from tensorflow.keras import layers from tensorflow.keras.callbacks import EarlyStopping
在导入上述依赖关系之后,在初始化机器学习模型之前,我们应用了预处理步骤来缩放和标准化数据,并执行常规的训练/测试拆分,请注意,这些步骤被省略了,以使文章从头到尾易于阅读。训练/测试分离后,训练数据存储在变量 "x_train" 和 "y_train" 中,验证数据存储在变量 "x_valid" 和 "y_valid" 中,最后是测试数据 "x_test" 和 "y_test"。
接下来,我们将对今天要使用的每个模型进行设置。
lm = LinearRegression() rf = RandomForestRegressor() knn = KNeighborsRegressor(n_neighbors=10) dnn = keras.Sequential([ layers.Dense(units=30,activation="relu",input_shape=[scaled_data.shape[1]]), layers.Dense(units=20,activation="relu"), layers.Dropout(0.3), layers.Dense(units=10,activation="relu"), layers.Dense(units=1) ])
然后,我们将对每个模型进行拟合
#Fitting the linear regression lm.fit(x_train,y_train) #Fitting the k-nearest neighbor regressor knn.fit(x_train,y_train) #Fitting the random forest model rf.fit(x_train,y_train) #Fitting the deep neural network early_stopping = EarlyStopping( min_delta=0.001, patience=20, restore_best_weights=True ) dnn.compile(optimizer="adam",loss="mae") dnn.fit( x_train,y_train, validation_data=(x_validation,y_validation), batch_size = 60, epochs=100, verbose=0, callbacks=[early_stopping] )
全局解释
我们将从线性回归模型的整体解释入手。我们将使用的技术是排列重要性。这是一种黑盒全局解释技术。
import eli5 from sklearn.metrics import mean_squared_error from eli5.sklearn import PermutationImportance from eli5.permutation_importance import get_score_importances
线性回归模型的全局解释
permutation = PermutationImportance(lm).fit(x_test,y_test) eli5.show_weights(permutation,feature_names = predictors)
图 3: 线性回归模型的全局解释
让我们一起来解读结果。排列重要性假设数据集中的每一列都是独立的。因此,它认为它可以一次更改一列,以观察模型误差度量的变化,并利用这些变化来决定哪些特征是重要的。如果该特征很重要,那么调整其值会导致模型的误差指标增加;反之,如果该特征不重要,那么调整该列的值后,模型的误差指标实际上会减少。在独立性假设不成立的情况下,我们必须谨慎解释这些结果。
在上表中,重要特征的系数为正,不重要特征的系数为负。"yearsPro",即运动员的经验年数,系数为负,但我们知道事实上它很重要,这是我们的模型发现了一个我们没有注意到的现象,还是我们的解释器没有忠实地反映事实?
排列重要性可能会不切实际地改变运动员的经验,有可能使他们变得过于年轻,不符合他们的专业水平,或拥有比他们总年龄更多的经验。此外,这种方法忽略了数据集中可能存在的任何交互,并将模型误差的所有变化都归因于该特征。因此,其结果有可能不切实际,这就强调了谨慎和了解算法的必要性,而不是盲目应用。
不过,从长远来看,它可以帮助我们深入了解模型最重要的特征,在这种情况下,运动员的接球能力和敏捷性就是一些最重要的特征。这是有道理的,因为在橄榄球中,即使你承受着很大的压力,你也应该能够接到队友的远传。此外,在接球后,你必须在不丢球的情况下避开尽可能多的竞争对手,因此对这些特征的重视是有道理的。
局部解释
如前所述,局部解释有助于我们理解每个特征是如何影响模型所做的每个预测的。
我们将获得我们的深度神经网络对 Richard LeCounte 薪水的期望的局部解释。
图 4:Richard LeCounte III
首先,我们导入 LIME。
LIME 是 Locally Interpretable Model-Agnostic Explanations(局部可解释模型不可知解释) 的缩写。它将解释哪些特征对模型的输出有贡献,以及这些贡献是推动输出上升还是下降。
import lime from lime import lime_tabular
explainer = lime_tabular.LimeTabularExplainer(training_data = np.array(x_train),mode='regression',feature_names=predictors) exp = explainer.explain_instance(data_row=scaled_data.iloc[test],predict_fn=lm.predict) exp.show_in_notebook(show_table=True)
图 5:LIME 深度神经网络对 Richard LeCounte 薪资预期的解释
上面的图表为我们提供了很多信息,首先,我们可以看到 LeCounte 的哪些体能提高了他的预期工资,哪些降低了他的预期工资。我们的神经网络预计 LeCounte 的韧性评级会降低他的预期薪水,这是有道理的,因为 LeCounte 的韧性评级一般。 此外,深度神经网络还正确识别了 LeCounte 的优势,并预计他的薪水将因 LeCounte 的优势而增加。这就是我们所期望的模型运作方式。局部解释是验证我们的模型而不是盲目信任它的有力工具。
与模型无关的解释和特定模型的解释
在我们了解底层模型结构的情况下,我们可以使用专门为该模型结构设计的解释技术。这些通常被称为白盒解释器。实际上,白盒解释器比黑盒解释器的计算效率更高。
黑盒子解释器与模型无关,这意味着它们可以解释和说明向其展示的任何模型。我们刚刚介绍的 LIME 算法就是一个黑盒解释器的例子,它的名字就说明了这一点(局部可解释模型-诊断解释)。
当我们确定底层模型的结构时,使用白盒解释器可以帮助我们更忠实地解释模型的行为。而黑盒解释器在解释我们的模型时可能会引入一定程度的偏差。黑盒解释器对其处理的模型形式做出了一些简化假设,如果这些假设被违反,那么黑盒解释器就变得不可靠了。
根据经验,如果你确定模型的基本属性,那么从长远来看,使用白盒解释器可能会更好。
让我们来看看黑盒解释器的实际应用,SHAP 库中有 SHAP 黑盒解释器的实现。下面我们将演示一个例子。
导入 SHAP
import shap
计算 SHAP 值
explainer = shap.Explainer(xgb.predict,scaled_data.loc[test:test_end,predictors]) shap_values = explainer(scaled_data.loc[test:test_end,predictors])
绘制我们的黑盒解释
shap.plots.beeswarm(shap_values)
图 6:我们的 XGB 模型的 SHAP 黑箱解释
每一行的宽度是由异常值决定的,并不能传递任何有关特征重要性的信息。我们通过考虑每个特征在图中的垂直位置来评估特征的重要性。特征越靠前,SHAP 算法认为其越重要。
此外,请注意,在某些特征上,蓝色和粉红色的点是完全分开的,而在其他行上,蓝色和粉红色的点却混在一起。当点混杂在一起时,通常表明该特征与另一特征或一组特征之间存在交互效应。简而言之,SHAP 图可以帮助我们将大量信息归纳到一张图中。
每个点代表每个数据点的 Shapley 值。这样,我们就能快速了解每个数据点对模型预测的细微影响。每个点的颜色代表该数据点对我们模型预测的影响方向和影响程度。蓝色表示模型输出减少,粉红色表示输出增加。请记住,SHAP 算法假定特征是独立的,换句话说,它忽略了数据集中的任何交互作用。违反这一假设可能会使算法变得不可靠。
现在,我们将考虑一种专为树状模型(如我们的 XGB 模型)设计的白盒解释器。我们使用专门的 TreeExplainer() 方法来解释基于树的模型。
tree_explainer = shap.TreeExplainer(xgb) tree_shap_values = explainer(x_test)
然后,我们可以绘制白盒解释图
shap.plots.beeswarm(tree_shap_values)
图 7:XGB 模型的 SHAP 白盒解释
对比黑盒 SHAP 解释和白盒 SHAP 解释中最重要的 4 个特征,你能发现分歧问题的存在吗?
定义分歧问题及其影响因素
既然我们已经了解了什么是模型解释,那么现在我们就可以开始更直接地关注分歧问题了。让我们先回顾一下什么是分歧。让我们来剖析一下什么是特征重要性解释领域的分歧:
- 特征排列:第一种分歧形式是特征排列不同。如果特征重要性的具体顺序在您的工作流程中占有重要地位,那么在不同解释中出现不同的排列就会成为争论的关键点。当特征的细微排序对分析具有重要意义时,这一点就变得尤为重要。
- 最佳排名特征集:更深入地看,分歧还延伸到了排名最佳的特征集。如果放松对特征重要性精确排列的严格要求,注意力就会转移到最重要的特征集上。不同解释者为同一模型确定的前 3 个、前 5 个或任何 "n" 个特征的差异,将成为您分析过程中值得注意的分歧点。
- 波动系数:许多解释器的核心在于为特征分配系数,表明它们对预期结果的积极或消极贡献。如果同一模型的两个解释之间该系数的符号发生变化,就会成为工作流程中的一个标志。这种波动表明存在分歧,需要对模型的内部运作进行更仔细的检查。
- 感兴趣的特征之间的相对排序:放大到感兴趣的具体特征,分歧又呈现出另一个层面。如果您的工作重点是衡量某个特征相对于另一个特征的重要性,那么这两个特征在不同解释中的顺序变化就会成为工作流程中产生分歧的战场。
什么不是分歧?
同样重要的是要认识到,并非所有的差异都意味着分歧。让我们来探讨一下解释之间和谐共存的实例:
- 不同的重要性价值:当不同解释者计算出的特征重要性值不完全一致时,解释就会避免真正的分歧。关键在于承认不同的算法会产生不同的计算,这使得直接比较具有挑战性。这里强调的不是重要性数值的相等,而是从不同的解释中得出一致的见解。
- 一致的见解胜过精确的数值无分歧的本质在于追求一致的见解,而不是执着地追求精确的数字对等。我们的目标并不是要求每一个解释器都精确地反映出对应解释器的重要性。相反,重点转移到了更广泛的叙述上 - 确保解释虽然在数字上不同,但在引导我们获得一致可靠的模型见解方面是一致的。
- 模型复杂性和估算挑战:随着模型复杂性的不断增加,一个基本事实逐渐浮出水面:准确估计模型内部运作的难度成正比增长。没有分歧就是承认这种内在的复杂性。面对错综复杂的模型,解释值的差异成为一种预期的挑战,而不是引起恐慌的原因。这一观点与人工智能不断发展的本质相吻合,在人工智能领域,对透明度的追求遇到了越来越多的复杂问题。
为什么解释器之间会有分歧?
解释器之间意见分歧的原因有很多,让我们试着一起分析几个案例。
比较全局和局部解释器
我们之所以会看到分歧,一个原因是我们可能在比较全局和局部的解释。虽然这是一个很容易犯的错误,但我希望本文的读者都不会再犯这样的错误。全局解释和局部解释不能简单地直接比较。除非你详细了解算法是如何在内部实现的,否则比较全局和局部解释是不可取的,这样做很可能会产生不同的解释。黑盒和白盒解释器的比较
此外,比较黑盒和白盒解释器可能会产生另一个具有欺骗性的良性分歧。请记住,黑盒解释器试图呈现能够解释任何算法的效用,因此他们对模型或模型的行为做出了某些简化假设。黑盒解释器所作的这些假设使其在各种情况下都能保持稳健和良好的运行,但当这些假设不成立时,解释器就会出现偏差。这种偏差可能会导致黑盒和白盒解释的不同。
更改特征顺序
我们的麻烦还不止于此,导致分歧的一个狡猾原因可能是对特征呈现顺序的任何改变。有些解释技术对模型输入的顺序很敏感,有些则不然。例如,互信息(Mutual Information)对输入顺序并不敏感,但 SHAP 却很敏感!因此,SHAP 解释器在观察到相同的模型后,有可能与自己的解释不一致。
例如,下面我们输出了从两个相同的 XGBRegressor 得到的两个 SHAP 解释。这两个模型之间唯一的区别在于它们将特征作为输入的顺序,除此之外,它们从相同的数据集学习,并具有相同的目标。
图 8 :我们最初的 SHAP 值
图 9:更改特征顺序后的 SHAP 值
此外,同一解释技术的不同实施方案可能会因为计算结果的细微差别而无法达成一致。例如,eli5 实现的排列重要性对输入顺序很敏感,但 sklearn 实现的相同解释技术却不敏感。之所以能做到这一点,是因为相同的解释技术可以通过多种方式实现。
为了证明 eli5 的排列重要性实现对输入顺序的敏感性,下面我们输出两个使用 eli5 的排列重要性实现得到的解释。我们从两个相同的 XGBRegressor 中获得了结果。这两个模型之间唯一的区别在于它们将特征作为输入的顺序,除此之外,它们从相同的数据集学习,并具有相同的目标。
图 10: 初始排列重要性
图 11: 混洗排列重要性
请注意,"kickPower_rating" 最初的系数是正的,但在改变特征顺序后,它现在的系数是负的!这种波动被认为是明显的分歧。最重要的特征发生了变化,特征之间的相对顺序也发生了变化。
最后,房间里的 "大象" 之一很可能是方法论上的分歧 - 这是导致解释器在解释同一模型时却彼此意见不一这一有趣现象的关键因素。由于每个解释器对模型计算的指标不同,它们对模型的解释也可能不同。
幸运的是,XGB 有一个已实现的函数,可以让我们评估特征的重要性。让我们利用这一功能来观察一下,哪种解释技巧能让我们忠实地反映真相。为了帮助读者,我们把前面的解释结果也包括在这里,这样您就不必一直上下滚动了。
图 12:XGB 特征的重要性
图 13:XGB 排列重要性
图 14:SHAP 白盒解释
图 15:SHAP 黑盒解释
图 16:基于玻璃盒树模型的特征重要性
对我们的模型来说,认知度评级是最重要的特征,然而我们的一小套黑盒解释技术却无法解释这一点,完全忽略了前 3 个特征。这提醒我们,解释只是对特征重要性的估计,而不是绝对真理。此外,请注意,"nearsPro" 多次名列前茅,但实际上它并不那么重要,这提醒我们不要简单地认为在不同测试中多次名列前茅的特征本身就很重要。
我们应用于 XGB 模型的黑盒解释与我们应用的白盒解释相矛盾,但最终两种解释都错了。因此,在这样的情况下,找出哪种解释更忠实完全是在浪费时间,因为它们都没有让读者更接近真相。
我还在数据上安装了一个可解释的增强机器,看看玻璃盒模型是否可以用作评估特征重要性的代理,虽然它设法从数据集中正确识别出最重要的特征,但它可能并不总是一个可行的选择,特别是如果你试图解释的模型不是基于树的模型。然而,不幸的是,玻璃盒模型也认为 "yearsPro" 功能具有信息性,尽管它并不具有信息性,这意味着我们也应该意识到,我们的解释器可能会同意错误的信息!
案例研究
现在我们可以开始使用真实市场数据了,首先从 MetaTrader 5 终端提取数据。我们的策略包括开发一个 MetaQuotes Language 5 (MQL5) 脚本,专门用于高效的数据提取和转换。我们首先声明全局变量。//---Our handlers for our indicators int ma_handle; int rsi_handle; int cci_handle; int ao_handle; int bbands_handle; int atr_handle;
接下来,我们将创建数组来存储技术指标的值。这些数据结构在整个脚本执行过程中有条不紊地捕获、管理和利用指标的输出方面发挥着关键作用。
//---Data structures to store the readings from our indicators double ma_reading[]; double rsi_reading[]; double cci_reading[]; double ao_reading[]; double bb_high_reading[]; double bb_low_reading[]; double bb_mid_reading[]; double atr_reading[];
随后,我们需要为 CSV 文件创建一个名称。
//---File name string file_name = "The Dissagrement Problem Data.csv";
在此基础上,我们需要创建一个变量来存储我们想要请求的柱数。
//---Amount of data requested int size = 10000; int size_fetch = size + 50;
继续设置我们的技术指标处理函数。每次设置技术指标时,我们都需要指定要使用的交易品种和时间框架。根据指标的不同,您可能需要指定指标的周期、移动参数、平滑方法以及指标应应用于哪个价格
void OnStart() { //---Setup our technical indicators ma_handle = iMA(_Symbol,PERIOD_CURRENT,20,0,MODE_EMA,PRICE_CLOSE); rsi_handle = iRSI(_Symbol,PERIOD_CURRENT,60,PRICE_CLOSE); cci_handle = iCCI(_Symbol,PERIOD_CURRENT,10,PRICE_CLOSE); ao_handle = iAO(_Symbol,PERIOD_CURRENT); bbands_handle = iBands(_Symbol,PERIOD_CURRENT,120,0,0.2,PRICE_CLOSE); atr_handle = iATR(_Symbol,PERIOD_CURRENT,14);
在继续执行脚本的过程中,后续工作涉及将数值从我们的指标句柄转移到指定的数据结构中。
//---Set the values as series CopyBuffer(ma_handle,0,0,size_fetch,ma_reading); ArraySetAsSeries(ma_reading,true); CopyBuffer(rsi_handle,0,0,size_fetch,rsi_reading); ArraySetAsSeries(rsi_reading,true); CopyBuffer(cci_handle,0,0,size_fetch,cci_reading); ArraySetAsSeries(cci_reading,true); CopyBuffer(ao_handle,0,0,size_fetch,ao_reading); ArraySetAsSeries(ao_reading,true); CopyBuffer(bbands_handle,0,0,size_fetch,bb_mid_reading); ArraySetAsSeries(bb_mid_reading,true); CopyBuffer(bbands_handle,1,0,size_fetch,bb_high_reading); ArraySetAsSeries(bb_high_reading,true); CopyBuffer(bbands_handle,2,0,size_fetch,bb_low_reading); ArraySetAsSeries(bb_low_reading,true); CopyBuffer(atr_handle,0,0,size_fetch,atr_reading); ArraySetAsSeries(atr_reading,true);
在开始编写文件之前,一项重要的前期工作是在脚本中加入文件处理程序。这一基本步骤包括配置必要的参数和机制,以便顺利创建和操作输出文件。
//---Write to file int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");
随后,脚本中的一个关键阶段开始了,我们将系统地浏览历史价格和指标值数组,以便将数据编码到指定的 CSV 文件中。
for(int i=-1;i<=size;i++){ if(i == -1){ FileWrite(file_handle,"Time","Open","High","Low","Close","MA 20","RSI 60","CCI 10","AO","BBANDS 120 MID","BBANDS 120 HIGH","BBANDS 120 LOW","ATR 14"); } else{ FileWrite(file_handle,iTime(_Symbol,PERIOD_CURRENT,i), iOpen(_Symbol,PERIOD_CURRENT,i), iHigh(_Symbol,PERIOD_CURRENT,i), iLow(_Symbol,PERIOD_CURRENT,i), iClose(_Symbol,PERIOD_CURRENT,i), ma_reading[i], rsi_reading[i], cci_reading[i], ao_reading[i], bb_mid_reading[i], bb_high_reading[i], bb_low_reading[i], atr_reading[i]); } } FileClose(file_handle); }
图 17:来自 MetaTrader 5 终端的市场数据
现在我们可以处理数据,并应用从示例数据集中学到的新技能。
现在我们导入依赖项。
#Import Dependencies import pandas as pd import numpy as np import matplotlib.pyplot as plt import shap import lime from lime import lime_tabular import eli5 from eli5.sklearn import PermutationImportance from sklearn.feature_selection import mutual_info_regression
为 IntepretML 载入依赖项。
from interpret import set_visualize_provider from interpret.provider import InlineProvider set_visualize_provider(InlineProvider()) from interpret import show from interpret.blackbox import MorrisSensitivity
读入 csv。
csv = pd.read_csv("/enter/your/path/here")
让我们来设置目标。
csv["Target"] = csv["Close"].shift(-30)
删除所有缺失值的记录。
csv.dropna(axis=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 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
图 18: 缩放市场数据
设置黑盒算法
#Black box models from xgboost import XGBRegressor from sklearn.linear_model import LinearRegression from xgboost import plot_importance from tensorflow import keras from tensorflow.keras import layers from tensorflow.keras.callbacks import EarlyStopping
训练/测试分割
train = 0 train_end = 5000 test = train_end + 40
建立线性模型
lm = LinearRegression()
lm.fit(scaled_data.loc[train:train_end,predictors],csv.loc[train:train_end,"Target"])
设置我们的 XGB 模型
#XGBModel xgb = XGBRegressor() xgb.fit(scaled_data.loc[train:train_end,predictors],csv.loc[train:train_end,"Target"])
建立深度神经网络模型
dnn = keras.Sequential([ layers.Dense(units=30,activation="relu",input_shape=[scaled_data.shape[1]]), layers.Dense(units=20,activation="relu"), layers.Dropout(0.3), layers.Dense(units=10,activation="relu"), layers.Dense(units=1) ])
early_stopping = EarlyStopping( min_delta=0.001, patience=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 = 60, epochs=100, verbose=0, callbacks=[early_stopping] )
为我们的深度神经网络获取全局解释
我们将使用 alibi 软件包为我们的深度神经网络获取解释。alibi 软件包有用地实现了一种解释性技术,即累积局部效应(ALE)。ALE 是我们迄今为止考虑过的所有解释技术的延伸,因为它具有鲁棒性,可以处理强相关特征;它放宽了特征独立性假设;它可以直观地进行解释;它的计算效率高;与其他一些解释技术不同,ALE 不依赖线性假设,因此非常适合捕捉数据中的复杂关系。
首先,我们计算 ALE 值。
dnn_ale = ALE(dnn.predict, feature_names = predictors,target_names=["Close 30 Steps"])
然后,我们将输入数据转换成 NumPy 格式,然后就可以获得 ALE 图形了。
X = scaled_data.loc[train:train_end,predictors].to_numpy() dnn_alibi =dnn_ale.explain(X) plot_ale(dnn_alibi,n_cols=4, fig_kw={'figwidth': 20, 'figheight': 10}, sharey=None)
ALE 图可以帮助我们解释每个特征的变化对模型输出的影响,例如,下面是动量振荡指标的 ALE 图。正如我们所观察到的,动量振荡指标可以帮助我们的深度神经网络预测价格何时下跌,我们可以观察到随着 动量振荡指标值的增加,ALE 图中的斜率也在下降。
图 19:动量振荡指标的 ALE 图
此外,信息量不大的特征的 ALE 图将类似于一条水平线,这意味着该特征的变化对目标影响不大。在我们的案例研究中,相对强弱指数(RSI)并不能提供信息。
图 20 :ALE RSI
从与模型无关的 SHAP 解释器中获取全局解释
explainer = shap.Explainer(xgb.predict,scaled_data.loc[test:,predictors]) shap_values = explainer(scaled_data.loc[test:,predictors]) shap.plots.beeswarm(shap_values)
图 21: 我们的 XGB 模型的 SHAP 黑盒蜂群图
我们的蜂群图告诉我们,动量振荡指标是这个数据集中最重要的特征,请记住,图中每一行的宽度并不能传递任何信息,因为它可以简单地通过异常值来确定。此外,我们注意到蓝色和粉红色的点似乎并不杂乱,这意味着数据集中可能没有很强的交互项。
根据互信息确定特征重要性
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
BBANDS 120 MID 1.739039
BBANDS 120 HIGH 1.731220
BBANDS 120 LOW 1.716019
MA 20 1.525800
High 1.172096
Open 1.155583
Close 1.143642
Low 1.140613
ATR 14 0.421772
AO 0.232608
RSI 60 0.181932
CCI 10 0.016491
就前 4 项功能而言,相互信息与我们的 SHAP 解释器完全不一致。更糟糕的是,我们的 SHAP 解释器关于 AO 动量振荡指标是本案例中最重要功能的说法其实是正确的。因此,依赖多个解释器实际上是一把双刃剑,一方面它可以保护你免受所使用的任何解释所带来的偏见,但另一方面它又为分歧的出现创造了更大的概率空间。 很难说哪种情况更符合经验,因为这完全取决于特定的数据集、特定的模型和许多其他变量。
最后,我们将把莫里斯敏感性分析作为最后的 "全局黑盒解释器"。
msa = MorrisSensitivity(xgb, scaled_data.loc[train:train_end,predictors]) show(msa.explain_global())
图 22:莫里斯敏感性分析
以下是 XGB 模型的实际重要性指标。
图 23:XGB 市场数据的重要性
在我们的数据集中,移动平均线是最重要的特征,其次是AO 动量振荡指标。因此,在这个特殊案例中,莫里斯敏感性分析给出了相对最好的解释,但在大多数情况下,你可能无法获得基本真相,因此你会如何选择更重视哪种解释呢?您对自己的决定有多大信心?如果您无法访问真实的特征重要性表,又如何验证您关于特征重要性的任何决定?
结论
正如我们所看到的,这里没有简单的答案,有太多的变化部分。数据集的形式和结构必须考虑在内,底层模型也必须考虑在内,数据中存在的交互项必须考虑在内,最重要的是,机器学习从业者必须对每种解释的内部运作了如指掌。此外,在我们进行的对照试验中,我们发现所有的解释器都有可能是错的,因此在这种情况下,试图解决任何分歧都是在浪费时间。因此,我们从解释性技术中获得的回报并不总是能够证明它们可能带来的复杂性。不过,随着时间的推移,我们可能会认识到更好的解释技术,观察到更好的算法。
建议
因此,在考虑了所有这些因素之后,我相信,解决分歧问题的最佳方案可能是更多地依赖于可解释的机器学习模型,如广义相加模型(GAM)或可解释提升机(EBM),这些模型可以完美地替代对解释的需求。截至今天,还没有任何全球公认的解决方案来解决分歧问题的每一种可能情况,但随着对这一问题的认识不断提高,也许有一天我们能够自信地解释我们构建的任何机器学习模型。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/13729

