TowardsDataScience 博客中文翻译 2021(二百七十五)
可解释性是理解机器学习模型正在做什么的一种方式。有两种类型的解释。在局部可解释性中,我们问 ML 模型它是如何得出个体预测的结果的。为什么上面说这个租赁会持续 2000 秒?为什么会暗示这个交易是欺诈性的?在全局可解释性中,我们询问特性的重要性。在预测租赁期限时,时间有多重要?交易金额对预测交易是否欺诈有多重要?BigQuery ML 支持这两种类型的解释,但是根据我的经验,本地可解释性是最终用户
可解释的神经网络:最新进展,第 3 部分
回顾十年(2010–2020),四集系列
我们在哪里?
这个博客聚焦于神经网络可解释性的发展。我们将我们的演讲分为四个博客系列:
- 第一部分讲述了图像像素的可视化梯度对于解释 CNN 的 pre-softmax 类得分的有效性。
- 第 2 部分讲述了一些更先进的/改进的基于梯度的方法,如去卷积、导向反向传播来解释 CNN。
- 第 3 部分讲述了基于梯度的方法的一些缺点,并讨论了替代的公理化方法,如逐层相关性传播、泰勒分解、深度提升。
- 第 4 部分讲述了一些最近的发展,如集成渐变(上接第 3 部分)以及 CNN 架构中最近的创新,如类激活地图,开发这些地图是为了使特征地图更易理解。
公理方法
到目前为止,我们讨论了用于理解由神经网络做出的决策的基于梯度的方法。但是,这种方法有一个严重的缺点。由于像 ReLU 和 MaxPooling 这样的单元的存在,通常得分函数对于一些输入像素可以是局部“平坦的”,或者换句话说具有 0°梯度。基于梯度的方法通常对使 ReLU 或 MaxPool 饱和的像素贡献 0。这是反直觉的。为了解决这个问题,我们需要:
- 关于我们所指的可解释性或相关性的一些正式概念(超越普通梯度)。我们希望“相关性”遵循的属性是什么。希望相关性在线性层表现得像普通梯度一样,因为梯度擅长解释线性函数。
- 有哪些候选满足我们的“相关性”公理,这也很容易计算,理想情况下,我们希望在一次回传中计算它们。
泰勒分解和逐层相关性传播(2015)
Sebastian Bach、Alexander Binder、Grégoire Montavon、Frederick Klauschen、Klaus-Robert Müller 和 Wojciech Samek 首先探讨了公理相关性。他们在他们的工作“ 中引入了逐层相关性传播的概念,通过逐层相关性传播对非线性分类器决策进行逐像素解释(PLOS 2015) ”。
作者提出了关联性必须遵循的以下公理:
- 所有像素的相关性总和必须等于模型的类别分数。我们从现在开始称这个公理为“总关联守恒**”。这是一个流行的公理,其他作者也遵循。**

总关联守恒,来源:https://journals.plos.org/plosone/article?id = 10.1371/journal . pone . 0130140
作者提出了两种不同的方法来将总相关性分配给各个像素。
1。泰勒分解
在该方法中,作者建议选择参考图像 X₀,该参考图像将被解释为“基线图像”,相对于该基线图像来解释图像 x 的像素。希望该基线图像的类别分数尽可能小。
使用基线图像来比较输入图像以突出重要像素是许多公理相关性工作中反复出现的主题。基线图像的一些好例子是:
- 模糊输入图像:适用于彩色图像
- 空白(暗)图像:适用于灰度/黑白图像
给定基线图像 X₀,我们执行类得分函数的泰勒分解以获得各个像素的相关性。

泰勒分解,来源:https://journals.plos.org/plosone/article?id = 10.1371/journal . pone . 0130140
神经网络的泰勒分解的扩展版本是由作者在他们的另一项工作中提出的,称为 深度泰勒分解 。深度泰勒分解形成了下面描述的逐层相关性传播的理论基础。
2。逐层相关性传播
泰勒分解是一种通用方法,适用于任何类得分函数。对于神经网络,我们可以设计一种更简单的方法,称为分层相关性传播。
对于神经网络,作者建议将相关性从输出层向下传递到起作用的神经元。
- 每当相关性从一个神经元向下传递到下一层中的起作用的神经元时,我们遵循起作用的神经元与从中传递相关性的神经元的总相关性守恒。因此,在 LRP,总的相关性保存在每一层。

分层相关性传播中的相关性守恒,来源:https://journals.plos.org/plosone/article?id = 10.1371/journal . pone . 0130140
- 从上一层传入神经元的所有相关都被收集起来并相加,然后再向下传递。当我们从一层递归到下一层时,我们最终丰富了输入图像,给出了每个像素的相关性。

对神经元的输入相关性求和,来源:【https://journals.plos.org/plosone/article? id = 10.1371/journal . pone . 0130140
还有待于定义我们如何分配神经元与其贡献输入或输入神经元的相关性。这可以通过多种方案来实现。作者给出了这样一个简单的方案:

单个神经元的层相关性传播,来源:【https://journals.plos.org/plosone/article? id = 10.1371/journal . pone . 0130140
我们注意到,上述方案只是近似总相关性公理的守恒。为了精确地保持和,我们必须以某种方式将偏置项重新分配回输入/输入神经元。
以下是 LRP 在 ImageNet 数据集上的一些结果:

来源:https://arxiv.org/pdf/1604.00825.pdf
深度提升(2017 年)
继塞巴斯蒂安·巴赫等人关于 LRP/泰勒分解的工作之后,两代情·什里库马尔、佩顿·格林塞德、安舒尔·昆达杰在他们的工作 中提出了通过传播激活差异学习重要特征的深度提升方法(ICML 2017) 。DeepLiFT(深度学习重要功能)使用参考图像和输入图像来解释输入像素(类似于 LRP)。虽然 LRP 遵循守恒定律,但没有明确的方法来分配像素间的净相关性。DeepLiFT 通过强制执行一个关于如何向下传播相关性的附加公理来解决这个问题。
DeepLiFT 遵循的两个公理是:
公理 1。总相关性守恒:在每个神经元上,所有输入的相关性之和必须等于输入图像和基线图像的得分之差。这个公理和 LRP 的一样。

**总关联守恒,来源:【https://arxiv.org/pdf/1704.02685.pdf **
公理 2。反向传播/链式法则:每个输入的相关性遵循类似梯度的链式法则。这足以帮助我们反向传播每个输入的类似梯度的相关性。这个公理使得 DeepLiFT 更接近“香草”梯度反向传播。

反向传播/链式法则,来源:https://arxiv.org/pdf/1704.02685.pdf
作者证明了上述两个公理是一致的。
给定这些公理,DeepLiFT 有哪些好的候选解决方案?作者建议将相关性分为积极和消极两部分:

正面和负面贡献,资料来源:https://arxiv.org/pdf/1704.02685.pdf
根据手头的函数,作者为 C() 和 m() 建议了以下候选解:
- ****线性规则对于线性函数:这与对 m()使用渐变完全相同。LRP 也会这么做的。

线性法则,来源:https://arxiv.org/pdf/1704.02685.pdf
- 像 ReLU,Sigmoid 这样的非线性函数的重新标度规则:这和 LRP 完全一样。

重新标度法则,来源:https://arxiv.org/pdf/1704.02685.pdf
线性和重新标度规则非常接近 LRP。
- ****reveal cancel(Shapley)****规则对于像 MaxPool 这样的非线性函数:对 MaxPool 使用重新调整规则(参考输入为 0)将最终把所有相关性贡献归于最大输入。其他输入的变化对输出没有影响。RevealCancel 法则修正了这个反直觉的结论,利用了 Shapley 值 的思想。

沙普利法则,来源:https://arxiv.org/pdf/1704.02685.pdf
Shapley 值已经在博弈论中用于计算输入变量的属性。最近一些关于可解释人工智能的作品(如 、SHAP )使用了从 Shapley Values 得到的灵感。
作者展示了在 MNIST 数据集上训练的 CNN 上使用 DeepLiFT 的结果。

**深度提升 vs 梯度方法,来源:【https://arxiv.org/pdf/1704.02685.pdf **
接下来是什么?
继续研究相关性的公理方法,研究人员开发了一种完全基于梯度的方法,称为集成梯度**,它满足许多理想的公理。最近,研究人员还探索修改 CNN 架构,使其更容易窥视。围绕这一点的一些新颖之处包括使用全局平均池和类激活映射。我们将在下一部分讨论这些很酷的技术。**
要阅读更多关于神经网络可解释性的激动人心的作品,你可以点击这里阅读下一部分: 链接到第 4 部分
可解释的神经网络:最新进展,第 4 部分
回顾十年(2010–2020),四集系列
我们在哪里?
这个博客聚焦于神经网络可解释性的发展。我们将我们的演讲分为四个博客系列:
- 第一部分讲述了图像像素的可视化梯度对于解释 CNN 的 pre-softmax 类得分的有效性。
- 第 2 部分讲述了一些更先进的/改进的基于梯度的方法,如去卷积、导向反向传播来解释 CNN。
- 第 3 部分讲述了基于梯度的方法的一些缺点,并讨论了替代的公理化方法,如逐层相关性传播、泰勒分解、深度提升。
- 第 4 部分讲述了一些最近的发展,如集成渐变(上接第 3 部分)以及 CNN 架构中最近的创新,如类激活地图,开发这些地图是为了使特征地图更易理解。
公理方法
从上一节继续,我们将公理化方法和基于梯度的方法联系在一起。我们讨论一种基于梯度的方法,它遵循所有期望的公理。
集成渐变(2017)
在上一节中,我们看到了泰勒分解如何将像素值(和基线图像的像素)的梯度和差的乘积指定为单个像素的相关性。DeepLiFT 指定粗略梯度和输入图像与基线图像之间的像素值差的类似乘积。根据 RevealCancel 规则,我们可以观察到,单个像素的相关性是(粗略)梯度沿着δx 的正部分,后跟δx 的负部分的离散路径积分。这就引出了一个问题:
- 分数函数梯度上的路径积分对输入图像像素的特征属性有多有效。
Mukund Sundararajan、Ankur Taly 和 Qiqi Yan 在他们的工作“ 深度网络的公理化归属(2017) ”中研究了这种使用集成梯度的思想。作者沿着他们希望所有特征归因方案都满足的两个理想的公理批判了当时流行的归因方案:
公理 1。灵敏度:每当输入和基线在一个特征上完全不同时,不同的特征应该被赋予非零的属性。
可以看出,由于总相关性的守恒,LRP 和深度提升遵循灵敏度。但是基于梯度的方法不能保证灵敏度公理。发生这种情况是因为当分数函数相对于一些输入特征局部“平坦”时,在 ReLU 或 MaxPool 阶段饱和。通过饱和激活以适当的方式传递关联或归因是所有特征归因研究工作中反复出现的主题。
公理二。实现不变性:当两个模型在功能上等价时,它们必须对输入特性有相同的属性
“普通的”梯度在数学上保证了实现的不变性。但是像 LRP 和深度提升这样对梯度的粗略近似可能会打破这个假设。作者展示了 LRP 和 DeepLiFT 违反实现不变性公理的例子。
作者建议使用综合梯度进行特征归属:

综合渐变,来源:https://arxiv.org/pdf/1703.01365.pdf
作者进一步表明,上述定义遵循两个理想的假设:
- **灵敏度:**根据微积分的基本定理,积分梯度归结为特征分数的差异,就像 LRP 和 DeepLiFT 一样。因此,他们就像 LRP 和 DeepLiFT 一样追随敏感性。
- **实现不变性:**由于是完全按照梯度定义的,所以遵循实现不变性。
积分使用的默认路径是从基线到输入的特征的直线路径。对于上述公理,路径的选择并不重要。直线路径具有相对于基线和输入图像对称的附加属性。
以下是作者在 ImageNet 数据集上训练的 GoogleNet 模型上提供的一些结果:

综合渐变 Vs 渐变,来源:https://arxiv.org/pdf/1703.01365.pdf
新颖的建筑
随着神经网络在图像识别和定位方面的更好性能的发展,人们也对修改网络架构以使其更具可解释性感兴趣。在前面的章节中,我们已经讨论了一些可视化细胞神经网络特征图的方法。但是除了特征地图之外,CNN 还有一堆完全连接的层(在最后一个 CNN 层的顶部),这些层将过滤后的特征地图转换为 softmax 之前的分数,这不是很好解释。这一部分讲述了一些试图使 CNN 架构更具可解释性的作品。
类别激活图(2016)
由于完全连接的层不太容易解释,一些研究人员建议在(最后)CNN 层的每个特征图上用全局平均池(GAP) 替换完全连接的层,以在馈送到 softmax 之前将其减少为一维张量和其上的单个线性层。雷勃·周、阿迪蒂亚·科斯拉、阿加塔·拉普德里扎、奥德·奥利瓦、安东尼奥·托拉尔巴在他们的工作 中指出,这样的特征图(来自上一期 CNN)更具可解释性,学习深度特征用于鉴别性定位(CVPR 2016) 。


CNN 架构与全球平均池,来源:https://arxiv.org/pdf/1512.04150.pdf
GAP 如何让 CNN 更具可解读性?我们能从这样的架构中得到什么样的解释?
作者注意到,间隙层将每个特征图减少为单个标量,并且其后的权重可以被解释为每个特征图与特定类别的相关性。为了说明这一点,作者将每个类别的类别激活图(CAM) 定义为对应于该类别的各个特征图的加权和。

班级激活图,来源:https://arxiv.org/pdf/1512.04150.pdf
作者指出,尽管 CAMs 仅被训练用于图像识别,但它们在图像定位方面表现良好。这里是作者展示的单个图像的各种类的一些例子。

不同类别的 CAM 可视化,来源:https://arxiv.org/pdf/1512.04150.pdf
GradCAM 和制导 GradCAM (2019)
CAM 可视化的一个限制是,它只能应用于具有全局平均池(GAP)的架构。Ramprasaath Selvaraju、Michael Cogswell、Abhishek Das、Ramakrishna Vedantam、Devi Parikh、Dhruv Batra 将 CAM 方法扩展到其他架构,并在他们的工作“ Grad-CAM:通过基于梯度的定位从深度网络进行可视化解释(IJCV 2019、ICCV 2017) 中提出了 GradCAM 的想法并指导了 GradCAM。
作者注意到,在基于间隙的 CNN 中,在反向传播期间,最后一个 CNN 层中的特征图中的所有像素从上面的层接收相同的梯度。围绕这一思想,作者提出,在一般的 CNN 中,由最后一层 CNN 中的特征图接收的平均梯度可以用作定义类激活图的相应权重。作者将这种基于梯度的凸轮或梯度凸轮简称为。


完全连接层的局部线性近似以提取凸轮权重,来源:https://arxiv.org/pdf/1610.02391.pdf
可视化产生的 GradCAM 图像(向上采样到输入图像大小)提供了热图,描述了图像的哪些部分强烈影响输出。

GradCAM 狗 Vs 猫可视化,来源:https://arxiv.org/pdf/1610.02391.pdf
GradCAM 仅突出显示输入图像中负责特定类别激活的部分。对于更细粒度的细节,作者建议运行引导反向投影,并用 GradCAM 将结果信号逐元素相乘。作者称之为制导 GradCAM 。

制导 GradCAM,来源:https://arxiv.org/pdf/1610.02391.pdf
作者展示了制导 GradCAM 的许多有趣的应用,包括可视化由神经网络执行的各种各样的任务,如图像识别、视觉问题回答、检测性别偏见、在人工智能系统中建立更好的人类信任等。
前方的路
这篇博客重点介绍了过去十年(2010 年至 2020 年)在图像识别/定位领域对神经网络所做决策进行可视化的一些卓越工作。虽然本讨论仅限于计算机视觉主题,但这里讨论的大多数方法已经成功应用于自然语言处理、基因组学等许多领域。可解释的神经网络仍在发展,每天都有新的研究出现,以更好地解释神经网络做出的决定。
在过去的十年里,人们对人工智能的透明度、公平性、隐私性和信任度等问题越来越感兴趣。在这个可解释人工智能的更一般的领域中有许多有趣的作品,如石灰SHAP等。一些研究人员一直对探索新的机器学习模型感兴趣,如 软决策树 , 神经支持的决策树 ,这些模型可以隐式解释,并且足够强大,可以提取复杂的特征/表示。本博客中列出的大多数方法都不涉及重新训练网络,而只是窥视网络以提供可视化。最近一些名为 PatternNet 的工作挑战了这一假设,并探索了在生成解释方面更有效的神经网络架构。大量激动人心的工作正在进行中!
可解释人工智能的圣杯是当人工智能可以帮助人类从数据中做出新的发现,并指导我们的决策,而不仅仅是为我们提供“正确”的答案。
解释 BigQuery ML 模型
如何获得和解释对预测的解释
BigQuery ML 是一种简单易用的方法,只使用 SQL 就可以在结构化数据上调用机器学习模型。虽然它只是从线性回归开始,但通过将 BigQuery ML 与 TensorFlow 和 Vertex AI 连接起来作为后端,已经添加了更复杂的模型,如深度神经网络和 AutoML 表。换句话说,虽然我们写 SQL,但是执行的是 TensorFlow。

图片由 StartupStockPhotos 来自 Pixabay
BigQuery ML 正在从 Vertex AI 后端*获取更多功能。在早先的一篇文章中,我向你展示了超参数调谐。在这篇文章中,我将向你展示可解释性。
什么是可解释性?
可解释性是理解机器学习模型正在做什么的一种方式。有两种类型的解释。在局部可解释性中,我们问 ML 模型它是如何得出个体预测的结果的。为什么上面说这个租赁会持续 2000 秒?为什么会暗示这个交易是欺诈性的?
在全局可解释性中,我们询问特性的重要性。在预测租赁期限时,时间有多重要?交易金额对预测交易是否欺诈有多重要?
BigQuery ML 支持这两种类型的解释,但是根据我的经验,本地可解释性是最终用户想要的,所以我将重点讨论这一点。
回归模型
我将采用一个简单的模型来预测我在整本书中使用的自行车租赁期限(BigQuery:权威指南):
CREATE OR REPLACE MODEL ch09eu.bicycle_linear
TRANSFORM(
* EXCEPT(start_date),
CAST(EXTRACT(dayofweek from start_date) AS STRING)
as dayofweek,
CAST(EXTRACT(hour from start_date) AS STRING)
as hourofday
)
OPTIONS(model_type='linear_reg', input_label_cols=['duration'])
AS
SELECT
duration
, start_station_name
, start_date
FROM
`bigquery-public-data`.london_bicycles.cycle_hire
解释预测
而不是打电话给 ML。预测,我们称之为 ML。EXPLAIN_PREDICT 在预测的同时获得解释:
SELECT *
FROM **ML.EXPLAIN_PREDICT**(
MODEL `ai-analytics-solutions.ch09eu.bicycle_linear`,
(
SELECT 'Park Street, Bankside' AS start_station_name,
CURRENT_TIMESTAMP() AS start_date
)
)
结果是:

预计持续时间为 1929 秒。
该模型以 4598 秒的基线预测值开始,这是整个训练数据集的平均预测值。从第二个输入(时间戳)开始,模型提取出了一天中的小时和一周中的天。
星期几是星期三,模型将基线值增加了 247 秒。换句话说,人们在周三租自行车的时间比平均时间稍长。
但是,我们看到的是凌晨 4 点。这将导致租赁期限大幅下调 2800 秒!
最后,Parkside 是一个租金通常较低的车站。因此,模型应用了另一个修正,这次是 116 秒。
4598 + 247–2800–116 = 1929
分类模型
作为我们的第二个示例,我们来看一个欺诈预测模型:
CREATE OR REPLACE MODEL advdata.ulb_fraud_detection
TRANSFORM(
* EXCEPT(Amount),
SAFE.LOG(Amount) AS log_amount
)
OPTIONS(
INPUT_LABEL_COLS=['class'],
AUTO_CLASS_WEIGHTS = TRUE,
DATA_SPLIT_METHOD='seq',
DATA_SPLIT_COL='Time',
MODEL_TYPE='logistic_reg'
) AS
SELECT
*
FROM `bigquery-public-data.ml_datasets.ulb_fraud_detection`
同样,我们使用 ML。解释 _ 预测:
SELECT *
FROM ML.EXPLAIN_PREDICT(
MODEL `ai-analytics-solutions.advdata.ulb_fraud_detection`,
(
SELECT *
FROM `bigquery-public-data.ml_datasets.ulb_fraud_detection`
WHERE Amount BETWEEN 300 AND 305
LIMIT 1
)
)
结果的欺诈预测类为 0(无欺诈),概率为 0.946。这对应于-2.87 的 logits 值(概率是 logits 的 sigmoid)。
不幸的是,这个数据集没有告诉我们列是什么,但是基线是-2.70,表明数据值没有太多异常。
让我们尝试一个不同的预测,这次选择一个非常高价值的交易:
SELECT *
FROM ML.EXPLAIN_PREDICT(
MODEL `ai-analytics-solutions.advdata.ulb_fraud_detection`,
(
SELECT *
FROM `bigquery-public-data.ml_datasets.ulb_fraud_detection`
**WHERE Amount > 10000**
LIMIT 1
)
)
现在,该模型预测该交易很可能是欺诈性的。逻辑值是 12.665,而基线值是-2.7。这笔交易的概率大幅增加背后的关键因素是 V4(增加了 10.67)和 V14(减少了 4)。当然,如果我们知道 V4 和 V14 是……这将意味着更多,但公开发布的财务数据集没有这些信息。
如您所见,您可以使用实例解释来解释不同的输入是如何导致结果值发生变化的。这有助于建立模型正常工作的信心。
尽情享受吧!
*可解释性以最有效的方式为每个模型类型实现。在某些情况下,Vertex AI 被调用,而在其他情况下,它是在 BigQuery 内部完成的。但是这是一个实现细节——不管它是如何实现的,你都可以用同样的方式调用它。
向外行人解释人工智能
在聚会上教授人工智能概念的技巧和例子

图片来自 Unsplash 上的 Yutacar
作为人工智能爱好者,当朋友或熟人问我们整天都在做什么时,我们会回答一个笼统的说法,如“我从事人工智能应用/研究/等。”由于人工智能如今已成为一个家喻户晓的术语,他们可能会跟进并询问更多细节,这使得环境有些糟糕。这让你处境艰难。显然,人工智能非常复杂,如果你在一个聚会或晚宴上,在短时间内恰当地解释它是很困难的。你想用你的知识给人们留下深刻印象,并进行顺畅的交谈,但是他们的知识匮乏可能是一个重大障碍。
在一个月又一个月的家庭朋友、老师和同学问我人工智能是如何工作的以及我作为一名人工智能开发者做了什么之后,我想出了一些好的策略和类比来向完全的外行人解释人工智能。这样,每当你和问你 AI 的人说话时,你就不会慌张,也不会让人觉得你不知道自己在做什么。
一般提示
先处理任何误解。
如果你在聚会上遇到的人对人工智能到底是什么知之甚少,第一步是澄清他们以前的任何错误想法。在我教年轻人或不涉足科技的人的经验中,他们通常认为人工智能系统是一个可以自行操作的机器人意识。如果这些误解没有被消除,当你试图传达人工智能的基础知识时,他们会把他们不正确的想法和你告诉他们的混淆起来。这可能会导致你说话时被打断,让你的思路被转移。最简单的开始方式是让他们“用一句话给我解释一下 AI。”我总是从这个问题开始,因为它让我对他们的知识和他们可能有的任何错误想法有了一个基线。在此基础上,您就有了接下来对话的路线图。
还记得你是怎么学会 AI 的吗。
解释人工智能的最好方式是用你最初被教导的方式来教导它。对我来说,我第一次接触 AI 和 ML 是由我爸爸介绍的,他描述了机器使用数据进行学习。如果你像我一样,在第一次学习时没有背景知识,使用你所学的策略会非常有效。我爸爸解释说,机器可以获得大量数据来学习,当你进行逻辑思考时,这很有意义。询问人工智能的人很可能能够理解逻辑解释,所以只要想想是谁教你的,并遵循那些相同的步骤。
不要进入数学。
既然你很聪明,你的朋友可能也很聪明,但这并不意味着你应该开始向他们描述反向传播和梯度下降。奇怪的是,我看到很多人直接进入了如何优化模型参数的描述,这似乎是一种过于复杂的方式来教授一个完全外行的人工智能。在聚会场合,你只有很短的时间来表达你的观点。你可能认为证明你懂高级数学会让你看起来很聪明,但事实上,人们会更看重沟通技巧。如果你能用简单的方式将一个复杂的话题传达给门外汉,他们会认为你比你给他们讲微积分要聪明得多。
言简意赅,善于演讲。
这是一个向任何人解释任何事情的通用提示。但是,在解释 AI 这样的高级领域时,这一点尤为重要。你展示自己作品的大部分经历都是在小组会议或讨论会上,在这些会议上,每个人都很好地理解了你所解释的内容。因此,你可能更习惯于使用聚会上你的朋友不会理解的行话和术语。混淆视听是最糟糕的情况——它让你看起来好像不知道自己在做什么。确保在向外行观众演示时,使用外行术语和普通语言。做一个自信的演讲者,让他们经常提问。记住你只有很短的时间,因为你是在一个聚会上,所以你必须快速工作。他们问的问题越多,他们自己就越能把想法整合在一起,这样你的工作就容易多了。
简单的类比
AI/机器学习的基本前提是,计算机使用数据来学习底层模式,并根据这些数据进行预测。对于没有计算机科学背景的人来说,这种说法有点复杂。所以,让我们使用人工智能建立的原理——人类学习。几乎任何人都应该能够理解人类是如何学习执行新任务的,就像你的朋友们自己试图学习新事物一样。
数学考试
让我们举一个大学和高中的例子——准备数学考试。这是我过去几个月的类比。
当我准备数学考试时,我总是做练习题。为了在数学考试中取得成功,我需要理解或预先了解我正在练习的概念,我们可以忽略这一部分。试图解释参数如何以预训练的直觉的某些方式初始化不是一个好的第一步。我的目标是尽可能多地答对数学问题,以便在考试中获得 A。
打个比方,学生将是 AI 系统,教科书问题将是训练数据,答案关键将是标签,实际考试是测试案例。这是解释的设置。你必须确保他们明白从例子中练习是一种很好的学习方式。

安妮·斯普拉特来自 Unsplash 的照片
现在我们可以解释人工智能的训练是什么样的。训练的第一步是把所有的练习题做一遍。接下来,你要给你的答案打分,看看你错了多少。当你知道你错了什么,你会重新做同样的问题。然后再给他们打分,看看你错在哪里,再重新做一遍。坚持做下去,直到你觉得准备好了。正如我们所看到的,每次重复这些问题就像是在为另一个时代进行训练。与答案匹配并确定你做错了什么就是计算损失并更新你的参数。
这个例子是有效的,因为大多数人都做过类似的准备考试的程序。使用相关的类比可以减少你解释自己的次数,因为不管是谁在听你说话,他都可以自己把事情联系起来。
一旦你解释了每一步,就用更专业的人工智能术语来解释。所以如果你刚刚解释完如何纠正你的答案,快速描述一下什么是损失函数。确保遵循一般提示,并在更高的层次上解释它们,例如,仅仅将损失计算称为“惩罚”就足以让某人明白你在说什么。
儿童玩具
另一个很好的例子是为初学走路的孩子设计的玩具形状解算器,比如 this 或者下图中的那个。使用这种玩具,幼儿的任务是将特定的形状插入与形状匹配的孔中。对于没有什么先验知识的孩子来说,这个任务几乎是不可能的。

图片来自 Unsplash
我们可以用这个例子来说明,AI 模型在学习的时候往往没有先验知识,这也是为什么要花这么多数据和时间来训练的原因。蹒跚学步的孩子必须预测积木将适合哪种形状,一旦尝试,他可以从父母那里得到反馈,或者只是知道它不适合。这是损失计算过程,因为每次幼儿看到形状是否适合各自的洞,它将增加知识并再次尝试。下一次,他们更有可能答对。这个启发式的过程就是 AI 系统的学习方式,在给外行人解释的时候,应该能很快理解。每个人都明白试错的过程,他们只是不知道启发式是什么意思。这个例子更适合快速聊天,因为你不能深入聊天。
高级话题
这些例子甚至可以扩展到更高级的 AI 概念,如半监督学习或自监督学习。如果你想解释半监督学习,谈谈数据收集是一个大问题,为了类比,解释你有一些没有答案的数学问题。直觉上,不管怎样,做没有答案的问题是一个好主意,但是为了从中学习,你必须从有答案的问题中利用你的知识。为了解释自我监督学习,使用学生以前在课堂上做的热身问题的想法。你会把问题拿出来,自己修改,创造一个不同的解决方案,然后解决问题。即使没有解决方案,这也会让你理解问题的基本结构。
然后,您可以转而谈论现实世界的数据收集和标注是如何具有挑战性的,这促使使用高级策略来最大限度地利用可用数据。对于教科书,请注意,有些教科书的问题有限,只有奇数问题有答案,但做偶数问题仍然是有帮助的,不管你是否知道自己是否答对了。正如你所知道的,有许多很好的类比可以非常有效地表现人工智能的基本概念。
最后的想法
作为人工智能开发人员,我们有一项更复杂的工作要向外行人解释。但幸运的是,解释它真的很容易,因为人工智能是建立在人类学习的基础上的。你可能有聪明的朋友,所以当有人提问时,描述人工智能如何工作应该是一种令人兴奋的体验,而不是尴尬的体验,并且注意到他们何时开始明白你在说什么是非常有益的。如果顺利的话,对话会非常有趣。通过在新的技术革命领域工作,确保你可以向想学习的人恰当地传达人工智能,不仅给他们留下深刻印象,而且让其他人对人工智能有正确的看法。
使用电影宗旨解释开放人工智能环境的双 Q 学习
带着钦佩而不是剧透。如果你没看过这部电影,这不是问题。看看基于 CNN 的双 DQNs 如何解决你的OpenAI健身房 问题!

红色在左边前进,蓝色在右边后退图片作者
请阅读:
这个帖子绝不是为了吸引点击的人。我向你保证,在实际的主题上不会有任何妥协,我们会在任何需要的时候使用电影的类比来增加阅读的乐趣。
不是一种感觉,而是一种波动。
没有剧透
作为一个粉丝,我理解我们中的一些人因为疫情还没有看这部电影,因此在这篇文章中,绝对不会有剧透。我们只讨论一些让电影惊艳的概念,而不是电影,你不需要看电影来阅读这篇文章。
Tl;博士:【https://github.com/perseus784/Vehicle_Overtake_Double_DQN】https://github.com/perseus784/Vehicle_Overtake_Double_DQN
- 与解释机器学习所有概念的帖子不同,在这里,我们专注于项目的学习。每个元素如何影响我们的项目,以及如何设计这些将是主要的焦点。
- 这一点很重要,因为你可以通过原始论文以及互联网上不同帖子的其他精彩文章更好地了解概念和理解,但如果你打算自己实施一个,这种方法将有助于你了解实际障碍。
这个计划
- 基础知识(只是为了让它不碍事)
- 要求、环境和系统配置
- 深度强化学习
- 模型架构
- 钳形运动
- 该算法
- 后代
- 外卖食品
- 结果
- 原则
基础
强化学习 (RL)是机器学习的一个领域*关注的是智能代理应该如何在环境中采取行动,以最大化累积回报的概念。*
为什么?
在人工智能的其他方法中,我们的模型有一个特定的目标来学习并相应地调整参数。但是在这里,我们让模型选择它想学的东西,只是基于它从做一个特定的动作中得到的回报。
这很重要,因为我们人类不会为每项任务单独训练。我们只考虑回报,用解决任务的技能武装自己。我认为 RL 是迈向更通用的人工智能的第一步,而不是一个任务定制的人工智能。如果你想学习一个新的环境/任务,只要改变你得到的奖励。模特也应该学习你的新环境。
怎么会?
我们可以使用 RL 来训练一个代理来完成任何任务。这与我们人类的工作方式非常相似。
做点工作,吃点糖果。
做更多的工作,得到更多的糖果。
做不好/无效的工作得不到糖果。
够简单吧?在这里阅读更多信息:https://medium.com/@ipaar3/saturnmind-94586f0d0158
它像一个婴儿。把婴儿放在任何地方,它都会根据每一次行动所获得的回报来适应所处的环境。
这有用吗/我应该继续吗?
这篇文章的目标读者是想学习深度强化学习的人,这是一种基于深度学习的强化学习方法。如果你对 Deep RL 的任何方面感兴趣,如何创建一个可以自己学习玩游戏或处理任务的代理,你可以继续这篇文章。
希望获得一些关于深度 Q 学习、双 Q 学习及其实际实现的好见解的人是这篇帖子的目标受众。
也就是说,在互联网上学习一个新概念没有错,我会尽可能让你参与进来。
要求、环境和系统配置
- Python 3.7
- 张量流 2.3.0
- OpenCV 的 Numpy
- 你的开放式健身环境。
我们的目标是帮助用户汽车在道路环境中自动超越机器人汽车。我们在深度强化学习的帮助下训练用户汽车,奖励功能将在用户汽车每次减速时,每次撞上机器人汽车时以及如果它前面有任何机器人汽车时惩罚用户汽车。由于我们使用来自环境的原始像素作为我们的状态空间,我们可以容易地改变环境和奖励函数以适应新的问题。
- 这里使用的 OpenAI-Gym 环境是一个高速公路环境,它为我们的 RL 实验提供了一个干净的管道。如果你还没有注意到,由于在实现基于 RL 的环境中可能有很多版本和变化,OpenAI 将这个过程标准化了。
- 所以,任何你用 OpenAI 得到的环境,比如环境,将会有相同的获得奖励,变量和游戏统计的格式。对于我们的任务,我们将从到这里获取公路环境,如下所示。

作者拍摄的公路环境图片
主角:
在这个环境中,我们要训练我们的智能体(主角)通过直接摄取一个图像,输出一个动作,来学习超车我们环境中的车辆。但这并不意味着相同的管道对任何其他情况都没有用。正如我前面提到的,同样的代码可以应用于一个非常不同的问题,因为我们遵循的是 体育馆格式 。
请查看动物园的例子,通过修改几行代码就可以构建出什么。下面的格式对于所有的环境都是一样的,环境可以通过修改第 3 行的代码来替换。
***import** gym
**import** highway_envenv = gym.make("highway-v0")
observation = env.reset()
done =Falsewhilenot **done:**
env.render()
action = agent(observation) *# your agent*
observation, reward, done, info = env.step(action)
if done:
observation = env.reset()
env.close()*
***系统配置:*由于网络架构将涉及到几个 CNN,因此最好有一台配有像样的 GPU、超过 4GB 虚拟 RAM 和 4GB 系统 RAM 的机器。
代号:https://github.com/perseus784/Vehicle_Overtake_Double_DQN
深度强化学习
简单来说,深度强化学习只是强化学习,但我们将使用神经网络来学习我们环境的策略,而不是 Q 表。
为什么?
- 我们都知道神经网络在学习模式方面很有效。为了投入使用,我们用神经网络代替了 RL 算法的 Q 表。
- 这样做的好处是多方面的。首先,我们不需要为环境中的每个状态记忆单独的策略。这意味着,我们现在可以在更大的环境中进行 RL,而没有内存限制。为大型环境的每个状态制定一个策略将导致 Q 表的规模呈指数级增长。
- 其次,我们现在可以学习更复杂的政策,这些政策需要时间上的理解。旧的 RL 不能考虑环境以前的状态,而现在,dqn 可以学习采取一系列步骤,并在此基础上进行推广。
- 最后,我们可以有不同类型的网络输入,如图像、图像序列甚至数字序列。这可以是 CNN 和 rnn 的输入,它们分别在理解这些数据类型方面非常强大。
模型架构
该项目的最令人兴奋的部分是为给定的问题选择正确的神经网络。虽然我们可以使用一个普通的神经网络来学习状态并给出政策,但最好是建立一个更擅长处理和处理图像的 CNN。做出这个决定是为了让我们可以使用相同的网络来解决没有定义状态的问题,并给出我们看到的原始输入,从而节省一些数据工程工作。
最初,我用一个像模型一样的 MobileNet V2,但后来测试时意识到它对我没有帮助,最好建立我们自己的网络来学习任务。

作者的建筑形象模型
- 拍摄四幅连续的环境图像并作为输入。这包括我们的特工在环境中移动。
- 我们的网络具有如上图所示的架构,层块基本上由多个卷积层和一个 MaxPool 层组成。

作者图片
- 在架构中引入 Dropout 是为了避免学习更多的 about 图像,让网络学习我们的任务。这样,我们迫使网络了解任务和策略。
- 该网络从 200×100×4 尺寸(4 个堆叠灰度帧对应 4 个尺寸)到 9x3x512 特征地图。下图显示了使用的卷积块数、每个卷积块数的功能图以及池层。
- 然后,这些特征地图被展平并馈入两个密集层,每个层有 512 个隐藏单元。
- 这种架构有助于网络学习代理的复杂目标。我们的网络包含大约 9M 个参数。
代号:【https://github.com/perseus784/Vehicle_Overtake_Double_DQN】
钳形运动
到目前为止,我们已经看到了一个简单的神经网络如何取代 q 表方法,但经过实验,这种方法有很多问题,可以通过使用两个不同的网络来克服,这两个网络同时运行,使我们的代理更好。
钳形运动或双重包围是一种战术,在这种战术中,部队同时攻击敌人阵型的两侧。
怎么了?
深度 Q 学习在理论上很有意义,对吗?但是当尝试实现时,一旦复杂性增加一点,它的表现就不太好了。

图片来自【arxiv 的双 DQN 纸
上面的等式是通过取可用策略的最大值来计算州的新策略的贝尔曼等式。然而,这是因为它采用下一个状态的最大 Q 值的估计,并执行动作,如上面的等式所示。在网络学习期间进行的这种高估会引入最大化偏差。
- 简而言之,网络倾向于高估每个状态的具有较高值(最大值)的策略,这可能导致策略收敛波动,使得收敛非常慢。
- 这是有意义的,因为我们同时在评估新策略和更新它。这肯定会导致训练过程的中断。
- 更简单地说,我们不想同时学习和玩这个游戏(但它是理想的)。实际上更好的做法是玩游戏,从你的错误中学习,提高水平,犯新的错误并从中学习。
- 所以,这里我们不是单一的网络,我们要引入另一个网络。估计器中的一个将集中于最大化策略的 Q 值,另一个用于更新该值。
红蓝队:

作者图片
在电影《特尼特》中,他们做了一种叫做 太阳穴钳形运动 穿越时间来获取关于环境的知识,同时进行攻击。也就是说,一个小组将专注于收集事件发生时的环境知识,而另一个小组将利用这些知识攻击敌人。

作者的双 Q 学习图像
- 同样,我们将有两个网络在运行。一个将是我们的训练网络(红队),它用从游戏中获得的数据训练我们的代理,另一个将是预测网络(蓝队),它播放环境并为训练网络收集新的经验以保存在存储器中。
- 我们可以看到,随着时间的推移,代理获得的奖励会越来越高,因此这种操作会继续下去。您还可以看到使用我们的两个网络计算保单的最新数学公式。

arxiv 双 DQN 论文双 Q 学习图像的修正贝尔曼方程
- 这是他们介绍这项技术的原始论文,它是深度 RL 世界的主要助推器之一。我认为现在双 Q 学习这个名字以后用起来很好。
该算法
我们已经准备好了主角和环境,可以开始训练了。由于这是一个在线培训程序,数据是实时收集、处理和反馈的。
工作流程:

作者提供的培训管道图像
- 你可以在图片中看到我们训练程序的伪代码。我们将在下一节讨论ε和内存变量。
- 一旦环境启动,我们的预测网络将预测给定状态的行动,并保存从采取行动中获得的奖励。
- 这被重复几次,以收集更多的数据保存在存储器中,并避免过于频繁的训练。
- 每四次迭代,训练网络将对存储器中收集的数据进行训练。一旦网络被训练,该过程继续获得更多的数据。
**memory = [ [current_state, action, next_state, reward, done], ...]**
- 上面显示的存储器 数据结构存储当前状态、代理采取的行动、采取行动后到达的下一个状态、采取该行动获得的奖励以及情节是否结束。
- 对于每 10 集,我们用本地保存的目录中训练好的模型更新我们预测的网络。我认为这是一个拉平的过程。

作者的培训流程图
- 左图显示了我们的培训流程。训练网络基本上是在本地训练和存储模型。另一方面,每 10 集的预测网络通过加载本地存储的模型来更新自身。
- 这种拉平过程使得训练和收敛非常顺利,并且从直观的角度来看,随着时间的推移,代理学会犯比愚蠢的错误更好的错误。
培训:
培训持续数小时,最好使用 Tensorboard 记录所有日志,以便实时跟踪和可视化。我们在这里跟踪的两个主要指标是亏损和平均阶段性回报图。
损失:
- 损失图将显示网络在每个时期后学习得更好,因为它显示网络总体上做出更好的决策。

作者的损失图
- 我们在该系统中优化的损失是分别通过训练网络和预测网络的预测和地面真实值之间的均方误差。
- 请看一下这段代码,看看我们是如何分别从训练网络和预测网络得到预测和地面实况的。
**loss = tf.keras.losses.mean_squared_error(ground_truth, prediction)**
- 我们使用新的贝尔曼方程,我们必须找到预测的政策 Q 值和正确的政策之间的损失。
- 在我们将学习率降低到最小值后,来自训练网络的损失图更加平滑,并且我们还使用 Tensorboard 平滑了损失值。这个我们简单讨论一下。
平均情景奖励:
- 奖励图将显示随着纪元的增加,奖励也将增加,因为随着网络学习更多,它将执行给予它最大奖励的动作。

作者的奖励图
- 在他们开发双 DQN 技术的原始论文中,作者运行了 2.5 亿个时代的环境,与我们相比,我们只运行了 3K 时代的训练。
- 完美的政策不会因此而实现,但关键是要学习过程,而不是实现理想的结果。
- 特别是,在我们的实验中,我们给了更大的权重,让汽车保持在道路的右侧,以获得更好的回报。作为一个副作用,汽车在道路左侧行驶时会引发更多的事故。这有助于我们理解为强化学习任务设计奖励机制的复杂性。
- 一般来说,随着时间的推移获得越来越多的奖励是了解我们的代理正在学习我们打算学习的任务的一个好方法,甚至从视觉上来说,随着情节的增加,代理表现得很好,犯的错误也越来越少。
- 精确度指标并不能很好地衡量我们的网络在强化学习中的表现。因为可能是 Q 值死记硬背而不是学习任务的结果。
代号:T3【https://github.com/perseus784/Vehicle_Overtake_Double_DQN】T5
后代
老实说,这是这篇文章最重要的部分。我认为这些实验和决定更重要,因为这些是你在试图建立一个类似的项目时将面临的障碍或障碍。
后代——一个人的后代。
在我们构建管道时,添加/调整系统的一些关键元素以获得最大收益是非常重要的。这是通过创建多个版本的网络,并试验不同的超参数,甚至一些增加我们训练价值的机制来实现的。最后,选择合适的。
你看…特尼特不是在过去创立的,它将在未来创立。
体验回放
- 当代理学习和遍历情节时,它会忘记以前学习过的经验。因此,当我们训练时,递归地提醒网络它学到了什么是很重要的。
- 做到这一点的方法是使用体验重放机制。这种机制有助于网络在训练时通过随机选择来记住它的经历。

按作者划分的内存使用图像
- 该程序基本上存储了一个数据结构,其中包含当前观察结果、采取的行动、下一个状态、对该行动的奖励以及该集是否结束。
- 另外,我们不想存储整个历史,所以我们有一个 15000 个这样的数据结构的缓冲存储器。
- 缓冲存储器基本上是一个固定大小为 15000 的队列,当它积累更多时,就会弹出旧的存储器。网络使用该内存池来选择其批次并在其上进行训练。
- 假设我们的代理玩这个游戏,它学习通过一个特定的障碍。然后,当它继续前进到下一个障碍时,我们不希望它忘记从上一节中学到的东西。因此,该机制有助于深度 Q 网络根据经验进行训练,并在每个训练期间刷新其学习。
探索与开发
- 当代理从第一次训练开始就过度适应它在一种情况下的经验,并且不探索获得更多奖励的新选项(注意:不要将这与神经网络过度适应混淆)。**
- 也就是说,如果代理发现了一些次优路径或处理这种情况的方法,因为它没有任何负面影响,所以它会选择这个值。
- 这最终导致代理人满足于次优的解决方案,而报酬从未达到理想状态。

作者的勘探与开发图表
- 为了克服这个陷入刚性状态的问题,我们可以使用一个叫做ε的值。
- Epsilon 是一个情节衰减变量,从 1 开始衰减到 0,直到总情节的一半在该会话上运行。
- 对于环境的每个渲染,我们的代理决定随机探索动作空间或者从预测的网络动作中选择一个。这可以通过如下代码实现:
**if np.random.random() > epsilon:
action = np.argmax(get_prediction(state)) **#Exploitation**
else:
action = np.random.randint(0, no_of_actions) **#Exploration****
- ε值在代理的探索或开发模式之间决定,并且它随着时间而减小。这是在培训的前半部分完成的,也就是说,在我们的案例中,我们在 3000 集的 1500 集内将ε值从 1 降低到 0.1,如上图所示。
超参数调谐
- 通常的神经网络训练涉及大量的超参数调整,但由于深度学习社区的人们多年来对他们的发现所做的努力,这些年来变得更加精简和容易。
- 但是对于深度 RL,尤其是ε、学习速率和批量调整,该过程变得更加复杂。这是非常复杂的,因为每件事都很重要,即使这些参数的微小变化也会随着时间的推移造成巨大的差异。
- 但是对我们有用的是在训练的前半段慢慢降低ε。
- 学习率 ,另一方面,我们使用默认的 TensorFlow Adam 学习率,但它在训练期间给我们带来了更大的损失波动,因此我们的训练网络使用了降低的 1e-4 学习率。这有助于减少波动损失。
- 批量 为最终培训的 32 个。最初,我们使用较高的批量大小,如 256,512,但这些并没有显示出改进,而且偶尔会导致内存耗尽错误。
- 训练频率 也是这其中的一个主要因素。训练频率基本上是目标网络在一个时间间隔内应该被训练的次数。代理在环境中每走四步,我们就训练一次网络。这为网络提供了足够的训练间隔以及收集新数据。
- 拉平 是我们多长时间更新一次网上或预测网络来传递经验。这是每 10 集做一次,以便随着时间的推移网络预测变得更好,并且不会太多地更新在线网络而导致预测太不稳定。
代号:https://github.com/perseus784/Vehicle_Overtake_Double_DQN
外卖食品
- 先选择你想解决的问题,再选择解决方案。有一个解决方案,或者在这种情况下,一个特定的人工智能技术,并试图使它适合你的解决空间,只是糟糕的工程。
- OpenAI gym 是我们基于 RL 的实验的惊人资源。一旦了解了格式,我们就可以用同样的方式处理所有的环境。
- 要知道,无论我们的假设听起来多么正确,它都会出错。比如,我认为通过使用 MobileNet v2 作为特征提取器,我们永远不会出错,但它对我不起作用。
- 我还尝试了一个想法,比如结合我们 CNN 中的一些盗梦空间模块,以获得更好的效果,这些模块来自我之前的项目 。这个想法在那个项目中发挥了惊人的作用,但在这个项目中却没有任何改进。
- 在处理一个神经网络架构的时候,从小的开始,随着你的进展增加复杂性,而不是从一个巨大的模型开始,在那里我们将不能识别出哪里出错了。
- 设计奖励函数是 RL 最重要的方面。因为这将决定我们的代理必须学习的任务。例如,在我们的例子中,我们对停留在最喜欢的车道上的代理人给予了奖励,但这引起了不希望的效果,如在其他车道上制造更多的碰撞,因为代理人总是试图停留在最右边的车道上。
- 总是考虑我们设计的奖励功能的分支,这些分支大部分隐藏在明显的地方,但是直观上有意义。
- 我们可以制作一个复杂的奖励函数来覆盖所有的边缘情况,并运行它一百万次迭代,最终会使它变得完美。但重点是创建一个通用的方法,你可以只输入原始图像(而不是每个任务需要设计的状态)和训练代理的奖励。

来自way mo 网站的鸟瞰图
- 不是噱头。这个项目不仅仅是展示 RL 的力量。许多自动驾驶研究使用这种俯视方法来学习车辆代理的控制机制。
- 这包括 Waymo、特斯拉、优步等。该图显示了用于培训 SDCs 控制代理的输入内容,这有助于导航和巡航控制。
- 在今天,人工智能领域的教师比学生多。我只是作为一个学习者而不是一个大师来提出这一点。
结果
请查看下面的每张图片,了解我们的代理如何通过训练超越该领域的其他代理。这里的是一分多钟的连续录像。你可以看到它在左侧比右侧犯更多的错误,因为在我们的奖励函数中最喜欢的车道偏向。




****
由作者训练的代理演示图像
原则
我今天的学习,明天属于你。使用它,不要重复发明轮子。
宗旨:被一个团体或运动坚持的原则。

红色在左边前进,蓝色在右边后退图片作者
总的来说,该项目为我们提供了关于深度 RL 的原始像素输入、网络架构选择、超参数调整、奖励函数设计、体验回放、探索与开发机制以及双重深度 Q 学习的良好知识。有了这个,我们可以进一步将这个项目扩展到更先进的技术,如 A2C 或 DDPG 的连续空间学习。
这里看一下代码https://github.com/perseus784/Vehicle_Overtake_Double_DQN。****
完整演示:
其他职位:
!eraC ekaT
解释 vim-sensible 的每一行
学习机
因为并非所有明智的 Vim 对你来说都是明智的

以下是你点击这个故事的三个可能原因。首先,它作为一个推荐故事出现在你的屏幕上。你不是在用 Vim,而是对 Vim 和懂事的 Vim 很好奇。
第二个原因,你已经痴迷于 Vim 很多年了,你想,“我已经知道里面所有的行,但是这是一篇关于 Vim 的文章,所以我还是要读它。”
最后,你和我处于学习 Vim 的相同阶段,你已经搜索了 Sensible 的注释版本,但是还没有找到。
首先,我们需要理解 Vimrc 文件。Vimrc 文件是 Vim 的配置文件,允许您定制 Vim、安装插件等等。它通常在~/.vimrc中找到,但是如果它不在那里,你可以创建它。对于 Windows,您可以在这里找到关于查找 vimrc 文件的更多信息。
https://vim.fandom.com/wiki/Open_vimrc_file
我在寻找一个 vimrc 文件,其中充满了有用的缺省值,作为我未来的 vimrc 的基础。首先,我想分享我在多个帖子和讨论中发现的一个反复出现的答案。引用道格·布莱克的文章,
不要在你的 vimrc 中放任何你不理解的行。
我认为这是一个非常合理的建议。程序员不会在他们的代码中添加任何额外或不必要的行。vimrc 文件也应该如此。
在我的搜索过程中,我偶然发现了 Reddit 上的这篇文章,它指出 Sensible 是 vimrc 文件的一个很好的起点。
https://github.com/tpope/vim-sensible
与我得到的其他选择(范围从完全相同的 Sensible 到超过 300 行)相比,我决定更深入地研究这一点作为起点。
这是 Sensible Vim 的完整代码。
tpope 对 vim 敏感的代码
有些最难理解的台词是有注释的,但是大部分根本没有任何解释。尽管这是可以理解的,但对于那些更熟悉 Vim 的人来说,在这些行上添加注释是没有意义的。
也许对他们来说会是这个样子。
if speed > 80.0: # checks if speed is more than 80.0
speeding = True # set speeding flag to True
n_violation += 1 # add 1 to variable n_violation
无论如何,我不是 Vim 专家,所以我想用过度注释的版本来更好地理解这些行实际上做了什么。
这是我发现的,随意跳过一些琐碎的解释。
逐行解释
if exists(‘g:loaded_sensible’) || &compatible
finish
else
let g:loaded_sensible = ‘yes’
endif
第一点是将 vim 变量loaded_sensible设置为‘yes ’,大概是为了与其他插件兼容,这些插件检查是否使用了 vim-sensible。
if has(‘autocmd’)
filetype plugin indent on
endif
该行将检查autocmd是否启用。大多数 Vim 都会附带,但是一些发行版比如vim-small和vim-tiny会默认不带autocmd。
在这种情况下,需要检查autocmd,因为filetype检测系统需要它才能正常工作。
filetype plugin indent on是这些命令的简称。
filetype on
filetype plugin on
filetype indent on
第一个命令打开 Vim 的文件类型检测,以帮助设置语法高亮显示和其他选项。plugin部件将加载特定文件类型的插件,如果它们存在的话。最后一位将加载特定文件类型的缩进文件,如果它们也存在的话。
例如,如果你想只为 Python 语言激活某些插件,那么你可以创建一个文件~/.vim/ftplugin/python.vim。将所有专门针对 Python 的插件和命令放在该文件中。
一个好的做法是在另一个文件中分离缩进配置(~/.vim/indent/python.vim)。然而,我通常只是把缩进放在插件文件里。
if has(‘syntax’) && !exists(‘g:syntax_on’)
syntax enable
endif
与前面的if命令相似,这个命令检查 Vim 是否用syntax编译,以及全局变量syntax_on是否已经存在。目标是避免多次打开syntax(如果它已经启用)。
set autoindent
autoindent选项将在插入模式下创建新行时使用当前缩进,通过正常返回或o / O。
set backspace=indent,eol,start
如果您使用 Vim 已经有一段时间了,您会注意到有时退格键在插入模式下不能正常工作。当您尝试在自动插入的缩进、换行符和插入开始处退格时,会发生这种情况。
设置这些退格选项将使您能够以特定的顺序正常地对它们执行退格操作。
set complete-=i
Vim 中的-=操作符意味着如果已经设置了选项,它将删除这些选项。在这种情况下,它将从 autocomplete 中删除i选项。
i选项声明它将“扫描当前和包含的文件”,如果当前文件包含许多其他文件,这可能会影响自动完成的结果。因此,禁用该选项是有意义的。
set smarttab
smarttab将根据shiftwidth、tabstop或softtabstop插入n空白(如果它们被配置的话),并且还将删除行首的一个shiftwidth空白,而不是默认的一个空白。
set nrformats-=octal
nrformats是数字格式的缩写,有助于定义哪种格式将被视为 Vim 的数字。如果您键入13,然后在正常模式下悬停在它上面并按 Ctrl+A,那么它会将它递增到14。或者可以用 Ctrl+X 递减到12。
由于使用基数 8,octal选项将导致007增加到010。在正常使用中,这不是预期的行为,因为没有多少人在日常工作中使用 base 8。禁用后,007将增加到008。
if !has(‘nvim’) && &ttimeoutlen == -1
set ttimeout
set ttimeoutlen=100
endif
nvim变量用于检查您运行的是 NeoVim 还是普通 Vim。ttimeoutlen设置 Escape、Ctrl、Shift 等组合键的超时时间。默认情况下,ttimeoutlen的值是-1,它将被改为 100,但这一行将其显式设置。
ttimeoutlen有什么用?假设您为 Cmd+Shift+Up 设置了一个映射。该命令的关键代码是^[[1;6A。另一方面,Esc 的关键代码是^[。如果将ttimeoutlen设置为 5000,则每次按下 Esc 键,都会等待 5 秒钟,然后才会注册 Esc 命令。这是因为 Vim 认为您有可能在 5 秒钟窗口内按下其余的按键代码。
可能有一种情况是将ttimeoutlen设置得更长是有用的,但是就我个人而言,我没有发现它们对我的用例有用。
set incsearch
增量搜索或incsearch允许 Vim 在您键入搜索关键字时直接进入下一个匹配结果。如果没有这个设置,您需要按 Enter 键让 Vim 转到搜索结果。
“ Use <C-L> to clear the highlighting of :set hlsearch.if maparg(‘<C-L>’, ’n’) ==# ‘’
nnoremap <silent> <C-L>
:nohlsearch<C-R>=has(‘diff’)?’<Bar>diffupdate’:’’<CR><CR><C-L>
endif
谢天谢地,这段代码附有解释,因为我肯定要花相当长时间才能弄明白它的意思。如果您启用了hlsearch,那么按 Ctrl+L 将会清除高亮显示,因为它不会自己消失。
set laststatus=2
laststatus的默认值是 1,这意味着只有当有 2 个或更多的 Vim 窗口打开时,Vim 的状态栏才会显示。通过将值设置为 2,现在状态栏将一直存在,即使你只打开 1 个 Vim 窗口。
set ruler
显示光标位置的行号和列号。有时候这有点多余,因为大多数状态栏插件都是内置的。
set wildmenu
当在命令行中调用自动完成时,打开wildmenu会在命令行上方显示可能的完成。查看效果的最简单方法是在正常模式下键入:!,然后按 Tab 键触发自动完成。
if !&scrolloff
set scrolloff=1
endif
scrolloff值确保你的光标上方和/或下方始终至少有 n 行。这一行检查您是否设置了scrolloff值,如果没有,则将其设置为 1。
scrolloff的默认值是 5,所以这个设置实际上将它减少到 1。
有些人将该值设置为一个非常大的数字(999 ),以使光标保持在屏幕中间,但我发现每当我创建一个新行时,屏幕就会刷新并移动一行,这有点令人迷惑。也就是说,我也将我的设置为 1。
if !&sidescrolloff
set sidescrolloff=5
endif
如果scrolloff是上下两行,sidescrolloff是光标左右两边的字符数。默认值为 0。您也可以将此值设置为 999,使光标保持在线的中间。
set display+=lastline
如果你打开一个很长的文件,它不适合屏幕,Vim 通常会用一串“@”来替换它。通过将其设置为lastline,Vim 将显示尽可能多的字符,然后在最后一列加上“@@@”。
该行使用+=来避免覆盖已设置为truncate的设置,这将在第一列显示“@@@”。
if &encoding ==# ‘latin1’ && has(‘gui_running’)
set encoding=utf-8
endif
==#操作符表示区分大小写的比较,与==不同,因为它依赖于:set ignorecase。如果 Vim 运行 GUI,该行将把编码从latin1改为utf-8。
if &listchars ==# ‘eol:$’
set listchars=tab:>\ ,trail:-,extends:>,precedes:<,nbsp:+
endif
listchars用于指定使用:list命令时,特定字符的显示方式。默认值是'eol:$',所以如果您已经配置了自己的listchars,这一行不会覆盖您的设置。
制表符将被替换为>,后跟数字\,直到制表符结束。尾随空格将被替换为-。如果当前行不适合显示在屏幕上,扩展字符>将显示在最后一列,前面的字符<将显示在第一列。最后,nbsp代表不可破坏的空格字符,将被替换为+。
if v:version > 703 || v:version == 703 && has(“patch541”)
set formatoptions+=j “ Delete comment character when joining commented lines
endif
还好这也是评论的台词。如果 Vim 版本比 7.3 或带有补丁 541 的 7.3 版本新,则为formatoptions添加j选项。
当您连接注释行时,该行将删除注释字符(例如“#”、“//”)。Vim 中formatoptions的默认值是tcq,但是这有点误导,因为它可能会根据打开的文件类型而改变。
if has(‘path_extra’)
setglobal tags-=./tags tags-=./tags; tags^=./tags;
endif
setglobal命令与通常的set命令有点不同。通过使用set,我们设置了一个选项的局部和全局值,而setglobal只设置了全局值。
全局值是应用于所有 Vim 的默认值。局部值仅适用于当前窗口或缓冲区。
tags命令定义了 tag 命令的文件名。该行所做的是从当前的tags中删除./tags和./tags;。^=命令将值./tags;添加到当前的tags中。
例如,如果tags的当前值是./a, ./b并且我们执行了setglobal tags ^= ./tags;,那么它将变成./tags;, ./a, ./b。;表示如果已经在./tags中找到标记文件,Vim 将停止查找。
if &shell =~# ‘fish$’ && (v:version < 704 || v:version == 704 && !has(‘patch276’))
set shell=/usr/bin/env\ bash
endif
=~#操作符意味着与正则表达式匹配,所以在这种情况下,它检查您是否正在使用fish shell。Vim 在fish上运行时有一些已知的问题,主要是因为fish不完全兼容 POSIX。
https://stackoverflow.com/questions/48732986/why-how-fish-does-not-support-posix
如果您正在使用 Vim 的旧版本(比 7.4 版本更早)或者使用没有补丁 276 的 7.4 版本,这一行将把您的 shell 设置为bash,以避免在运行一些不能用fish shell 执行的命令时出错。
set autoread
这将使 Vim 在检测到文件在 Vim 之外被更改时自动读取文件。但是,如果文件被删除,它将不会被重新读取,以保留删除前的内容。
if &history < 1000
set history=1000
endif
将保留的历史数量设置为最小 1000。默认情况下,Vim 对于每种类型的历史只记住 50。在 Vim 中有五种类型的历史。
:命令- 搜索字符串
- 公式
- 输入行(
input()功能) - 调试模式命令
if &tabpagemax < 50
set tabpagemax=50
endif
使用-p命令行参数或:tab all命令将打开的最大选项卡页数从默认值 10 设置为至少 50。
if !empty(&viminfo)
set viminfo^=!
endif
Viminfo 是一个由 Vim 自动编写的文件,作为一种“缓存”来存储信息,比如命令行历史、搜索字符串历史、最后一次搜索模式、全局变量等。
这一行将在前面加上!值,该值将保存和恢复以大写字母开头且没有任何小写字母的全局变量。
或者,如果您想在某种私有模式下运行 Vim,您可以使用命令set viminfo=禁用 viminfo 功能。
set sessionoptions-=options
当您使用:mksession进行会话时,从sessionoptions中删除options值将禁止保存选项、映射和全局值。
Tim Pope 自己解释说,他禁用它是因为记住全局选项会覆盖对 vimrc 所做的更改,这是他不想要的。
https://github.com/tpope/vim-sensible/issues/117
set viewoptions-=options
与上一行类似,这一行在使用:mkview制作视图时会禁用相同的东西。
我对视图不太熟悉,但是这一页解释了为什么不使用这一行会导致 Vim 在加载保存的视图时跳转到不同的工作目录。
https://vim.fandom.com/wiki/Make_views_automatic
“ Allow color schemes to do bright colors without forcing bold.if &t_Co == 8 && $TERM !~# ‘^Eterm’
set t_Co=16
endif
t_Co是终端使用的颜色数量。当当前颜色数量设置为 8 且当前终端不固定时,这将使颜色增加到 16 种。
但是大多数现代终端不需要这个,因为通常默认设置为 256。您可以通过键入:echo &t_Co来检查这一点。
“ Load matchit.vim, but only if the user hasn’t installed a newer version.if !exists(‘g:loaded_matchit’) && findfile(‘plugin/matchit.vim’, &rtp) ==# ‘’
runtime! macros/matchit.vim
endif
matchit是一个 Vim 特性,它使我们能够在左括号和右括号、HTML 标签等之间跳转。您可以尝试在正常模式下按下左括号顶部的%,它会将您的光标移动到匹配的右括号。
如果matchit尚未启用或者现有的仍然是旧版本,该行将启用。
if empty(mapcheck(‘<C-U>’, ‘i’))
inoremap <C-U> <C-G>u<C-U>
endifif empty(mapcheck(‘<C-W>’, ‘i’))
inoremap <C-W> <C-G>u<C-W>
endif
mapcheck命令检查您是否为特定模式下的特定键设置了自定义映射。在这种情况下,它会在为 Ctrl+U 和 Ctrl+W 创建新映射之前,检查您是否已在插入模式下映射了它们。
这两行将防止意外删除,而不可能同时使用 Ctrl+U 和 Ctrl+W 进行撤销。通过在实际的 Ctrl+U 或 Ctrl+W 之前执行Ctrl+G u,我们可以使用撤销操作(正常模式下的u)恢复我们删除的文本,如果没有这些重新映射,这是不可能的。
https://vim.fandom.com/wiki/Recover_from_accidental_Ctrl-U
最后的想法
在理解,或者至少试图理解所有这些行之后,我决定不把 Sensible 作为插件安装,而是偷一些有用的行。
我最喜欢的一个是与set hlsearch配对的set incsearch,它支持渐进式搜索,也可以渐进式地突出显示搜索结果。
有一些我省略了,比如如果使用 fish shell,将 shell 设置为 bash,因为我自己也在使用 zsh shell。
首先,我开始寻找构建 vimrc 的最佳文件。在搜索过程中,我发现很多其他人也在搜索同样的东西。然而,在对 Vim 配置有了基本的了解之后,我决定从头开始构建一个,因为我使用 Vim 不仅是为了编码,也是为了写作。
如果任何解释是错误的或不够清楚,请随时发表评论给予纠正或要求澄清任何行。
最后一个提示,如果您不确定一行代码的作用,您可以通过键入:h 'commandname'来搜索 Vim 的手册,以显示特定命令的帮助。了解这些命令在 vimrc 文件中的作用和效果是一个很好的实践。
学习机是一系列关于我所学到的,并且认为足够有趣可以分享的事情的故事。有时也是关于机器学习的基础。 获得定期更新 上新故事和 成为中等会员 阅读无限故事。
https://chandraseta.medium.com/membership
解释我如何在以数据为中心的新竞争中名列前茅
实践教程
继吴君如本人代言(!)关于我的上一篇文章,很自然地分享了所有的技巧(带代码!)关于我如何应对 DeepLearning.ai 的新挑战。
10 月 21 日编辑:我以优秀奖的身份被选为 8 名获奖者之一(见下文)。

比赛解释…又来了!
如果你还不熟悉 DeepLearning.ai 几周前推出的以数据为中心的新挑战,你可以看看我几周前写的描述这一挑战的文章。
不确定这是否值得?只是听从吴恩达的建议😉:

截图自 Andrew NG 对我上一篇文章的背书—2021 年 8 月
https://www . LinkedIn . com/feed/update/urn:李:活动:6830591493435224064/
https://medium.com/geekculture/a-deep-dive-into-andrew-ng-data-centric-competition-eb2bc0886005
如果你赶时间,长话短说:比赛的目标是产生尽可能好的一组图片来训练预定义的模型(ResNet50)识别罗马数字。该竞赛提供了一个大约。3000 张图片,包括嘈杂和贴错标签的数字,如下图所示:

从训练数据集中提取-由作者在 DeepLearning.ai 的授权下编译
让游戏开始吧!
我将介绍不同的步骤,以平稳的方式达到良好的性能,但这显然是我为了找到最佳组合而进行的多次测试和试验的结果!
如果您想进一步探索我的解决方案,我还在 GitHub 上创建了一个专门的资源库(本文末尾有链接)。
1.图片评论
第一项任务可能是要求最高的:查看每张照片,检查一些标准。以下是我用过的一些:
- 这看起来像罗马数字吗?(如果没有,我们就应该去掉!)
- 图片标注正确吗?(例如。“II”在“III”文件夹中,反之亦然)
- 什么是数质?(评分从 1:好到 4:差)
- 背景质量如何?(等级同上)
- 什么是字体风格?(“Arial”或“Roman”)
- 数字的确切格式是什么?(“viii”或“VIII”)
- 我们能应用对称吗?(水平或垂直对称通常适用于“I、II、III 或 X”数字,但不适用于“I”、“II”或“VII”)
以下是我评估的三个例子(以表格形式存储):

图片分类示例—按作者分类的图片
在查看 3000 张照片时(这花了我大约 2 到 3 个小时😅),我有时会有“似曾相识”的感觉,我开始怀疑数据集中是否隐藏了一些重复项?这并不奇怪,所以我也必须考虑到这一点。

我还设计了一个简单的函数来自动检查文件夹的内容,以评估我将执行的不同操作的结果。像我后来在笔记本中使用的所有其他函数一样,我将它存储在一个专用的“dcc_functions.py”中(可以在 GitHub 存储库中找到)。
以下是初始数据集的输出:

folders_sumary 由 Pierre-Louis Bescond 输出
2.数据集清理
2.1 噪声消除
我首先删除所有我认为纯粹是噪音或者至少是噪音太大而无法正确训练模型的图片。这显然是个人的选择,每个参与者可能都有不同的选择。我确定大约。要删除的 260 张图片(相应的列表存储在 GitHub repo 上的 Excel 文件中)。
2.2 去重
如前所述,我感觉有些照片完全一样,但手动识别它们是不可能的。我知道有一些技术可以解决这个问题:
- 将大小相同的文件配对 …但是会出现很多误报
- 配对大小相同的文件&配置(如两个“II”或“viii”)
- 按文件统计配对文件(使用,例如。, PIL 的 ImageStat
- 利用结构相似度指标对文件进行配对(此处有说明)
- 根据文件的“哈希”号将文件配对
由于这不是一个“生死攸关”的问题,我决定使用第二种解决方案,这种方案既简单又容易实现。剧本(此处)确定了大约 200 对双胞胎照片,其中 53 张实际上是真正的复制品(下面是一些例子):

由DCC _ find _ duplicates . ipynb通过 Pierre-Louis Bescond 识别的双胞胎对的例子
2.3 移动右侧文件夹中的一些图片
没必要在这上面花太多时间:当一张照片被贴错标签时,我只是把它移回到它所属的文件夹。
2.4 前卫还是不前卫?
在我们继续之前,我想分享一个有趣的发现:我看了两次照片:一次是在我参加比赛的时候,另一次是在我对照片有了更好的想法的时候。
当我第一次查看原始图片时,我已经排除了许多看起来太模糊而无法训练模型的“边缘案例”。
但比赛开始几周后,我开始习惯这些尖锐的案例,并以不同的方式考虑它们,比如:“嗯,包括这个案例可能是好的,以教导模型这个案例可能会发生。”我最后添加了大约。80 张图片到数据集。
与直觉相反的是,这一新选择的性能正在下降,包括更尖锐的图片。怎么会这样
参与者之一 Mohamed Mohey 在专门的对话主题中强调,32x32 变换(在训练前应用于数据集)有时会完全改变图片的本质,如下例所示:

32x32 转换前后的相同图片-图片由作者提供-归功于 Mohamed Mohey
我们可以观察到,由于这种 32x32 的转换,一个明显的“III”正在变成一个似是而非的“II”,解释了为什么一些尖锐的案例不一定会给模型带来有价值的信息。
在 32x32 的转换后再看一遍照片可能是件好事,但我没有!
2.5 使用“标签簿”图片训练模型
DeepLearning.ai 的组织者提供了一组 52 张图片,这些图片不在“train”或“validation”文件夹中,以评估我们的模型在 ResNet50 训练结束时的表现。
这是一个很好的方式来了解模型在最终和隐藏数据集上将如何表现,但由于排行榜上显示的分数,我也“猜测”到对隐藏数据集的最终评估包括 2420 张图片 ( 请参见这里的相应笔记本)。所以 52 张图片无论如何都不是很有代表性!
所以我干脆把这些图片放在了我的训练文件夹里!越开心,越有趣😁
2.6 评估增强技术的影响
正如你可能知道的,在由图片组成的数据集上使用增强技术来帮助深度学习模型识别允许正确推断类别的特征是非常常见的。
我决定考虑其中的几个:
- 水平和垂直对称
- 顺时针和逆时针旋转(10°和 20°)
- 水平和垂直平移
- 裁剪图片中的白色区域
- 添加合成“盐和胡椒”噪声
- 将一些图片的噪声转移到另一些图片上
2.7 实现海关功能
第一个功能非常简单,很容易用 PIL、OpenCV,甚至像 ImgAug 这样的“打包解决方案”来实现。我认为分享一些关于我设计的自定义函数的技巧会更有趣😀
2.7.1 平方裁剪功能
裁剪操作是一个有趣的操作!由于图片最终将被转换为 32x32 的图片,放大数字所在的区域可能会更好。
但是,如果数字不是“平方”形状,则在转换为 32x32 时,结果可能会失真(如下所示)。我重新设计了该函数,以便裁剪后的输出将始终为方形,并避免这种失真效果:

原始和裁剪的可能输出—作者提供的图像
2.7.2【盐和胡椒】功能
由于最终评估数据集上的背景可能不总是纯白的,我试图通过添加合成背景来增加图片。
我使用了“salt & pepper”函数,它基本上是将“0”和“1”随机添加到描述图片的 NumPy 数组中:

原创和“经验丰富”的图片——作者提供的图片
2.7.3 背景噪声传递函数
我对“盐和胡椒”函数的结果不太满意,因为噪声总是均匀的,所以我想了另一种方法给图片添加噪声。
我回收了一些我原本认为不可读的图片,让它们成为一些“嘈杂的背景”的基础。还有一些“背景很重”的图片,我去掉了编号(如下所示)以获得更多的样本。
它为我提供了一个“嘈杂背景库”,里面有 10 张图片,我在应用水平或垂直对称后随机添加到一些图片中:

嘈杂的背景银行-作者图片

背景噪声转移示例—作者提供的图片
2.8 选择最佳增量
由于允许的图片数量不能超过 10.000 个元素,我必须知道哪些转换提供了最高的效果。
我决定通过比较基线(没有转换的干净数据集)和每种增强技术的单独性能来对它们进行基准测试(总结如下):

增强的影响,基于损失或准确性-图片由作者提供
我们可以观察到旋转、平移和裁剪带来了比其他方式更大的影响,所以我决定把重点放在这些方面。
和“瞧”!
由于该过程是随机的(以 50%的概率和一些随机参数应用变换),脚本的每次迭代将产生图片的唯一组合。
我的许多测试产生了大约 84%的性能,而最高的竞争对手达到了 86%(基线为 64%)。我猜是光荣的😅
将有一些额外的调整要考虑(如创建我自己的图片,并将其添加到数据集,但我选择只依赖于提供的初始图片)。其他人可能也尝试过!
竞争对手表现的全球概览
可能还值得一提的是,我分析了竞争对手在挑战的前几周(直到 2008 年 6 月)的表现,我们可以看到大多数参与者很快达到了可接受的表现,很快就达到了 75%及以上:

参赛作品的性能分析—作者提供的图片
最后的话
如前所述,在对数据进行适当的审查/清理,并在不到 30 秒的时间内执行一个脚本之后,您可以轻松地胜过最先进的模型在有噪声的数据下所能产生的结果!
我真的很喜欢参加这个挑战,根据我的说法,比“GPU 能力”更需要“新鲜的想法”,我真的很期待下一次挑战!
我们与其他参赛者和 Lynn(来自 DeepLearning.ai)进行了许多有趣而丰富的互动,分享我们对这场“同类第一”竞赛的看法。许多参与者更希望分享他们的观点和发现,而不是在排行榜上名列前茅。
我也知道生成“平均和嘈杂的数据”有多困难…所以祝贺组织者提供了这么好的材料来研究!
当然,我希望你喜欢 DeepLearning.ai 关于以数据为中心的挑战的第二部分!
等级
我的解决方案被 Andrew 和他的团队从 2000 多份提交的作品中选中,并获得了荣誉奖(共有 8 名获奖者/团队):

正如我所承诺的,这里是 GitHub 知识库的链接,欢迎在评论中分享你的经验和/或发现:
https://github.com/pierrelouisbescond/data-centric-challenge-public https://pl-bescond.medium.com/pierre-louis-besconds-articles-on-medium-f6632a6895ad
用简单的方式解释 Python 类
使用示例理解类的基础

作者插图
当我第一次学习 Python 类时,我发现它真的很复杂,我不明白为什么需要知道它们。在一堂大学课上,教授开始直接解释构建类的语法,而没有解释做这个特定主题的真正意义,并使用非常无聊的例子,使我迷失了方向。
在这篇文章中,我想用不同的方式解释 Python 类。我将开始解释为什么需要知道这些类,并展示一些已经构建好的类的例子。一旦明确了外部环境,下一步就是使用一些例子和插图一步步地展示如何定义类。
目录:
- 课程介绍
- 创建一个类
- 构造函数方法
- 神奇的方法
- 实例方法
1.课程简介
当你开始学习 Python 的时候,你肯定遇到过这样一句话:
Python 是一种面向对象的编程语言
意思是用 Python 编程导致处处被对象包围。在瞬间
我们给一个变量赋值,我们就创建了一个对象。这个对象属于一个已经预先构建好的特定类,比如数字、字符串、列表、字典等等。根据类的不同,对象会有不同的属性和方法
让我们来看一些对象和类的经典例子:
a = 28
print(type(a))
#<class 'int'>
我们定义了一个对象 a,它属于 Integer 类。
l = [1,2,3]
print(type(l))
#<class 'list'>
这一次,新对象属于 List 类。从对象中,我们可以调用已经在 Python 中预先构建的方法。要查看对象中允许的所有方法,我们使用 dir 函数:
dir(l)

从输出中,我们可以注意到该类可以提供三种类型的方法:
- 初始化器方法
__init__,这样调用是因为它是初始化对象属性的方法。此外,一旦创建了对象,就会自动调用它。 - 神奇的方法是两边有双下划线的特殊方法。例如,
__add__和__mul__分别用于对同一类的对象求和、相乘,而__repr__则以字符串的形式返回对象的表示。 - 实例方法是属于被创建对象的方法。例如,
l.append(4)在列表末尾添加一个元素。
2.创建一个类
现在,我们将创建一个空类,并在教程中逐步添加部分代码。
class Dog:
pass
我们创建了一个名为 Dog 的类,其中pass用来表示没有定义任何东西。
jack = Dog()
print(type(jack))
#<class '__main__.Dog'>
一旦我们定义了 Dog 类,我们就可以创建类的对象,它被分配给变量 jack。它是使用类似于我们调用函数的符号构建的:Dog()。
该对象也可以被称为实例。如果你发现“实例”或“对象”这两个词写在某个地方,不要感到困惑,它们总是指同一个东西。
像以前一样,我们检查对象的类型。输出清楚地指出该对象属于 Dog 类。
3.初始化器方法
除了前面显示的代码,初始化器方法__init__用于初始化 Dog 类的属性。
class Dog:
**def __init__(self,name,breed,age)**:
self.**Name** = name
self.**Breed** = breed
self.**Age** = age
print("Name: {}, Breed: {}, Age: {}".format(self.Name,
self.Breed,self.Age))
我们可以观察到该方法有不同的参数:
self是用作第一个参数的标准符号,指的是稍后将创建的对象。访问属于该类的属性也很有用。name、breed和age是剩余的自变量。每个参数用于存储对象的特定属性值。Name、Breed和Age是定义的属性。注意这些属性通常不大写。在本文中,它们是大写的,以突出属性和它们相应的值之间的区别。
jack = Dog('Jack','Husky',5)
#Name: Jack, Breed: Husky, Age: 5
print(jack)
#<__main__.Dog object at 0x000002551DCEFFD0>
print(jack.Age)
#5
我们再次创建了对象,但是我们也指定了对应于属性的值。如果您尝试运行代码,您将自动获得灰色窗口第二行中显示的文本行。这是检查所定义的类是否运行良好的好方法。
还值得注意的是,初始化器方法是在创建对象后自动调用的。这一方面可以通过打印属性“Age”的值来演示。您可以对其余的属性执行相同的操作。
4.神奇的方法
也有可能以更复杂的方式打印相同的信息。为此,我们使用神奇的方法__repr__:
class Dog:
def __init__(self,name,breed,age):
self.Name = name
self.Breed = breed
self.Age = age
**def __repr__(self):**
return "Name: {}, Breed: {}, Age: {}".format(self.Name,
self.Breed,self.Age)
方法__repr__采用一个唯一的参数self,它可以从这个参数访问对象的属性。
jack = Dog('Jack','Husky',5)
print(jack)
#Name: Jack, Breed: Husky, Age: 5
如果我们显示创建的新实例,我们可以查看属性及其各自的值。
5.实例方法


睡觉还是醒着?保罗·特里内肯斯在 Unsplash 上的照片。卡尔·安德森在 Unsplash 上拍摄的照片。
实例方法是属于类的方法。作为神奇的方法,它们接受一个输入参数self来访问类的属性。让我们看一个例子:
class Dog:
def __init__(self,name,breed,age,**tired**):
self.Name = name
self.Breed = breed
self.Age = age
self.**Tired** = tired
def __repr__(self):
return "Name: {}, Breed: {}, Age: {}".format(self.Name,
self.Breed,self.Age)
**def Sleep(self):**
if self.Tired==True:
return 'I will sleep'
else:
return "I don't want to sleep"
在初始化器方法中,我们添加了一个新的参数tired,并因此添加了一个新的属性Tired。之后,我们定义了一个叫做 Sleep 的新方法:如果属性的值等于 True,狗就要睡觉,否则,它就不睡觉。
jack = Dog('Jack','Husky',5,tired=False)
print(jack.Sleep())
#I don't want to sleep
狗不累,所以不睡觉。
最终想法:
在这篇文章中,我提供了 Python 类的快速总结。我希望你发现这有助于巩固你的课程基础。我没有解释其他类型的方法,静态方法和类方法,因为我想把重点放在最常见的方法上。而且,另一个有趣的话题是类继承,我写的另一篇帖子里有涉及。感谢阅读。祝您愉快!
你喜欢我的文章吗? 成为会员 每天无限获取数据科学新帖!这是一种间接的支持我的方式,不会给你带来任何额外的费用。如果您已经是会员, 订阅 每当我发布新的数据科学和 python 指南时,您都可以收到电子邮件!
解释 sci kit-与 SHAP 一起学习模型

照片由 Marek Piwn icki 在 Unsp lash 上拍摄
数据科学基础
走向可解释的人工智能
可解释的人工智能(XAI) 通过使机器学习模型更加透明,帮助建立对它们的信任和信心。 XAI 是一套工具和框架,可用于理解和解释机器学习模型如何做出决策。一个有用的 XAI 工具是 Python 中的 SHAP 库。此工具允许我们量化特征对单个预测以及整体预测的贡献。该库还带有美观易用的可视化功能。在这篇文章中,我们将学习 SHAP 库的基础知识,以理解来自 Scikit-learn 中内置的回归和分类模型的预测。

1.夏普价值观✨
形状值帮助我们量化特征对预测的贡献。Shap 值越接近零表示该要素对预测的贡献越小,而 shap 值远离零表示该要素的贡献越大。
让我们来学习如何为回归问题提取要素的 shap 值。我们将从加载库和样本数据开始,然后构建一个快速模型来预测糖尿病进展:
import numpy as np
np.set_printoptions(formatter={'float':lambda x:"{:.4f}".format(x)})
import pandas as pd
pd.options.display.float_format = "{:.3f}".formatimport seaborn as sns
import matplotlib.pyplot as plt
sns.set(style='darkgrid', context='talk', palette='rainbow')from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split
from sklearn.ensemble import (RandomForestRegressor,
RandomForestClassifier)import shap # v0.39.0
shap.initjs()# Import sample data
diabetes = load_diabetes(as_frame=True)
X = diabetes['data'].iloc[:, :4] # Select first 4 columns
y = diabetes['target']# Partition data
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size=0.2,
random_state=1)
print(f"Training features shape: {X_train.shape}")
print(f"Training target shape: {y_train.shape}\n")
print(f"Test features shape: {X_test.shape}")
print(f"Test target shape: {y_test.shape}")
display(X_train.head())# Train a simple model
model = RandomForestRegressor(random_state=42)
model.fit(X_train, y_train)

获取 shap 值的一个常见方法是使用解释器对象。让我们创建一个Explainer对象,并为测试数据提取shap_test:
explainer = shap.Explainer(model)
shap_test = explainer(X_test)
print(f"Shap values length: {len(shap_test)}\n")
print(f"Sample shap value:\n{shap_test[0]}")

shap_test的长度是 89,因为它包含了每个测试实例的记录。从第一个测试记录中,我们可以看到有三个属性:
◼ shap_test[0].base_values:目标的基值
◼ shap_test[0].data:每个特性的值
◼ shap_test[0].values:每个特性的形状值
让我们了解一下这些属性中的每一个向我们展示了什么。
📍1.1 基础值
基值( shap_test.base_values ) ,也称为期望值( explainer.expected_value),是训练数据中的平均目标值*。*我们可以用下面的代码来检查这一点:
print(f"Expected value: {explainer.expected_value[0]:.1f}")
print(f"Average target value (training data): {y_train.mean():.1f}")
print(f"Base value: {np.unique(shap_test.base_values)[0]:.1f}")

因此,我们将互换使用词语期望值和基础值。
📦 1.2.数据
接下来,shap_test.data包含与X_test相同的值:
(shap_test.data == X_test).describe()

让我们把它转换成数据帧来看看:
pd.DataFrame(shap_test.data, columns=shap_test.feature_names,
index=X_test.index)

这只是我们传递给它的数据集的副本。
✨ 1.3.价值观念
来自shap_test的最重要的属性是values属性。这是因为我们可以从中访问 shap 值。让我们将 shap 值转换成数据帧,以便于操作:
shap_df = pd.DataFrame(shap_test.values,
columns=shap_test.feature_names,
index=X_test.index)
shap_df

我们可以看到每个记录的 shap 值。如果我们把这些 shap 值加到期望值上,我们将得到预测:

我们来看看是不是这样:
np.isclose(model.predict(X_test),
explainer.expected_value[0] + shap_df.sum(axis=1))

厉害!这里我们用np.isclose()忽略浮点不准确。现在,我们有了 shap 值,我们可以像这样进行自定义可视化,以了解特性贡献:
columns = shap_df.apply(np.abs).mean()\
.sort_values(ascending=False).indexfig, ax = plt.subplots(1, 2, figsize=(11,4))
sns.barplot(data=shap_df[columns].apply(np.abs), orient='h',
ax=ax[0])
ax[0].set_title("Mean absolute shap value")
sns.boxplot(data=shap_df[columns], orient='h', ax=ax[1])
ax[1].set_title("Distribution of shap values");

左侧子图显示了每个特征的平均绝对形状值,而右侧子图显示了形状值按特征的分布。从这些图表中可以看出,bmi在使用的 4 个特性中贡献最大。
2.形状内置图📊
虽然我们可以使用 shap 值构建我们自己的可视化,但是shap 包带有内置的奇特的可视化。在这一节,我们将熟悉这些观想中的一些精选。我们将会看到两种主要的情节🌳**全局:显示特征总体贡献的图形。这种图显示了一个特征对整个数据的总体贡献。
🍀局部:**显示特定实例中特征贡献的图。这有助于我们深入了解单个预测。
🌳 2.1.全局|条形图
对于前面显示的左侧子图,有一个等效的内置函数,只需几次击键:
shap.plots.bar(shap_test)

这个简单但有用的图显示了特性贡献的强度。该图基于特征的平均绝对形状值:shap_df.apply(np.abs).mean()。要素从上到下排列,具有最高平均绝对形状值的要素显示在顶部。
🌳 2.2.全局|汇总图
另一个有用的情节是摘要情节:
shap.summary_plot(shap_test)

这里有一个替代语法:shap.plots.beeswarm(shap_test)用于这个具体的情节。与之前一样,要素按其平均绝对形状值排序。与之前的条形图相比,此图表更加复杂,包含更多信息。下面是解释该图的指南:
◼️ 图的横轴显示了特征的 shap 值分布。每个点代表数据集中的一条记录。例如,我们可以看到对于bmi,点非常分散,在 0 附近几乎没有任何点,而对于age,点更靠近 0 聚集。
◼️ 点的颜色显示特征值。这个额外的维度允许我们看到 shap 值如何随着特征值的改变而改变。换句话说,我们可以看到关系的方向。例如,我们可以看到,当bmi为高时,shap 值往往较高(由粉红色圆点表示),当bmi为低时,shap 值往往较低(由蓝色圆点表示)。还有一些紫色的点分散在光谱中。
如果我们发现默认颜色图不直观或不合适,我们可以将其更改为我们首选的 matplotlib 颜色图,如下所示:
shap.summary_plot(shap_test, cmap=plt.get_cmap("winter_r"))

此外,我们还可以使用不同的绘图类型。这里有一个例子:
shap.summary_plot(shap_test, plot_type='violin')

🌳 2.3.全球|热图
热图是可视化 shap 值的另一种方式。我们看到的是彩色编码的单个值,而不是将形状值聚合成一个平均值。特征标绘在 y 轴上,记录标绘在 x 轴上:
shap.plots.heatmap(shap_test)

该热图由顶部每个记录的预测值(即f(x))的线图补充。
我们可以使用cmap参数将颜色图更改为我们想要的颜色图:
shap.plots.heatmap(shap_test, cmap=plt.get_cmap("winter_r"))

🌳 2.4.全局|力图
这个交互图让我们可以看到由记录构成的 shap 值:
shap.force_plot(explainer.expected_value, shap_test.values,
X_test)

就像热图一样,x 轴显示每条记录。正的形状值显示为红色,负的形状值显示为蓝色。例如,由于第一条记录的红色成分比蓝色成分多,因此这条记录的预测值将高于预期值。
交互性允许我们改变两个轴。例如,y 轴显示预测,f(x),x 轴按上面快照中的输出(预测)值排序。
🍀 2.5.局部|条形图
现在,我们将通过图表来理解个别情况下的预测。让我们从柱状图开始:
shap.plots.bar(shap_test[0])

语法与第 2.1 节中的完全相同。Global | Bar plot’ 除了这次我们对单个记录的数据进行切片。
🍀 2.6.局部|瀑布图
这是条形图的另一种替代形式:
class WaterfallData():
def __init__ (self, shap_test, index):
self.values = shap_test[index].values
self.base_values = shap_test[index].base_values[0]
self.data = shap_test[index].data
self.feature_names = shap_test.feature_namesshap.plots.waterfall(WaterfallData(shap_test, 0))

瀑布图是信息密集的,有四位信息:
◼️在 y 轴上,我们看到记录的实际特征值。如果您不确定我的意思,请将X_test.head(1)与 y 轴上的值进行比较。
◼️在图表的右下角,我们看到E[f(X)],预期值。
◼️在左上角,我们看到f(x),预测值。
◼️ ️Just 像前面的柱状图一样,横条代表颜色编码的特征贡献。从底部的期望值开始,我们可以看到每个贡献是如何上下移动预测值以最终达到预测值的。
🍀 2.7.局部|力图
最后一个要熟悉的图是单个记录的力图。如果我们将该图旋转 90 度并绘制多个记录,我们将看到全局力图。
shap.plots.force(shap_test[0])

我们既可以看到基础值:153.4,也可以看到预测值:103.48。我们还可以看到特性贡献的分解。
3.调整分类🌓
到目前为止,我们已经关注了一个回归示例。在这一节中,我们将学习一种方法来调整我们所学的内容以适应二元分类。让我们导入 titanic 数据的子集并训练一个简单的模型:
# Import sample data
df = sns.load_dataset('titanic')
df['is_male'] = df['sex'].map({'male': 1, 'female': 0}) # Encode
# Keep numerical complete columns
df = df.select_dtypes('number').dropna()
X = df.drop(columns=['survived'])
y = df['survived']# Partition data
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size=0.2,
random_state=1)print(f"Training features shape: {X_train.shape}")
print(f"Training target shape: {y_train.shape}\n")
print(f"Test features shape: {X_test.shape}")
print(f"Test target shape: {y_test.shape}")
display(X_train.head())# Train a simple model
model = RandomForestClassifier(random_state=42)
model.fit(X_train, y_train)

✨ 3.1.形状值
我们将创建一个解释器对象,并像前面一样提取 shap 值:
explainer = shap.Explainer(model)
shap_test = explainer(X_test)
print(f"Length of shap_test: {len(shap_test)}\n")
print(f"Sample shap_test:\n{shap_test[0]}")

就像回归shap_test.data将包含与X_test相同数量的记录。但是,从这个示例记录中,我们可以看到values 和base_values的尺寸不同。
我们先来看看基值。基值现在告诉我们每个类的概率。我们将关注积极类(即y==1,生存):
print(f"Expected value: {explainer.expected_value[1]:.2f}")
print(f"Average target value (training data): {y_train.mean():.2f}")
print(f"Base value: {np.unique(shap_test.base_values)[0]:.2f}")

现在,让我们看看 shap 值。还为这两个类提供了 Shap 值。你也会注意到它们是彼此的反面。我们可以这样提取正类的 shap 值:
shap_df = pd.DataFrame(shap_test.values[:,:,1],
columns=shap_test.feature_names,
index=X_test.index)
shap_df

让我们仔细检查一下将 shap 值的总和加到预期概率上是否会得到预测概率:
np.isclose(model.predict_proba(X_test)[:,1],
explainer.expected_value[1] + shap_df.sum(axis=1))

太棒了,现在你知道如何得到概率单位的 shap 值了。
📊 3.2.形状内置图
是时候调整我们之前看到的情节了。为了避免重复,我将展示一个全局图的示例和一个局部图的示例,因为其他图可以使用相同的逻辑来复制。
**全局|条形图:**让我们检查特征对预测正类的总体贡献:
shap.plots.bar(shap_test[:,:,1])

**局部|瀑布图:**我们来看看第一个测试记录的瀑布图:
shap.plots.waterfall(shap_test[:,:,1][0])

从这个图中,我们可以看到每个特征是如何对属于正类的预测概率做出贡献的:对于这个记录是 0.98。
📍 3.3.使用案例
在我们结束之前,让我们看一个示例用例。我们将为幸存者找到最不正确的例子,并试图理解为什么模型做出不正确的预测:
test = pd.concat([X_test, y_test], axis=1)
test['probability'] = model.predict_proba(X_test)[:,1]
test['order'] = np.arange(len(test))
test.query('survived==1').nsmallest(5, 'probability')

第一次记录的存活概率是 0.03。让我们看看特性是如何促成这一预测的:
ind1 = test.query('survived==1')\
.nsmallest(1, 'probability')['order'].values[0]
shap.plots.waterfall(shap_test[:,:,1][ind1])

年轻的三等舱男性乘客……我们可以看到,性别、乘客级别和年龄已经大大降低了预测。让我们在训练数据中找到类似的例子:
pd.concat([X_train, y_train],
axis=1)[(X_train['is_male']==1) &
(X_train['pclass']==3) &
(X_train['age']==22) &
(X_train['fare'].between(7,8))]

所有类似的训练例子其实都没有存活下来。这就说得通了。这是一个小用例,说明 shap 如何有助于解释为什么模型会得出不正确的预测。

希望您喜欢学习如何使用 SHAP 库来理解 Scikit-learn 中内置的回归和分类模型。因为 SHAP 是模型不可知的,所以你也可以把它用于其他模型。我希望你能很快使用这个可解释的人工智能工具来理解为什么模型会做出预测,以便改进模型并更好地向他人解释它。
您想访问更多这样的内容吗?媒体会员可以无限制地访问媒体上的任何文章。如果你使用 我的推荐链接 ,成为会员,你的一部分会费会直接去支持我。
感谢您阅读这篇文章。如果你感兴趣,这里有我其他一些帖子的链接:
◼️️ K 近邻解释
◼️️ 逻辑回归解释
◼️️ 比较随机森林和梯度推进
◼️️ 决策树是如何构建的?
◼️️ 管道、ColumnTransformer 和 FeatureUnion 说明
◼️️ FeatureUnion、ColumnTransformer &管道用于预处理文本数据
再见🏃 💨
用反事实的例子解释 XGBoost 模型的决策
模型可解释性——故障检测、识别和诊断
反事实推理是可解释性的一般范式。它是关于确定我们需要对输入数据应用什么样的最小变化,以便通过分类模型将它分类到另一个类中。
典型的应用场景是故障检测和诊断。让我们想象一下,我们可以使用传感器精确地监控生产过程,这些传感器遍布整个生产链(通常在每个工作站)。利用这些数据,我们可以在制造过程的每个阶段跟踪产品。因此,可以记录与最终出现故障的产品相关联的数据,用于进一步分析,以试图追溯故障是从哪里引入的。特别是,我们有兴趣找到引入故障的工作站,并且如果可能的话,有兴趣诊断那里可能出现了什么问题(如果数据允许的话)。在这一点上,您可能已经猜到,主要目标是避免长时间停止生产链,同时试图发现哪里出了问题。拥有一个能够在眨眼之间提供这种答案的人工智能可能会证明非常有用,最重要的是,对商业有好处,这是一个足够好的借口,可以解决你在阅读本文其余部分时遇到的所有理论困难。

如果你不希望这种事发生在你身上,请进一步阅读:)。你不必再品尝生产线上生产的所有饼干,以确保它们符合祖母的标准。你将有一个预测它的模型,每次饼干不符合标准时,你将能够准确地说出生产线的哪个地方出了问题,以及为什么,甚至有什么最小的行动来使事情恢复正常。以及 100%人工智能驱动的解决方案。[图片由作者提供]
好消息是我们能够设计非常有效的故障检测模型(FDM)。后者能够实时分析来自不同性质(数值、分类……)的大量数据,并在生产的不同步骤进行测量,以预测给定的制造元件是否有缺陷。不太好的消息是,解释这种模型提供故障诊断的决定要困难得多。通常使用的 FDM 通常是非常复杂的黑盒模型,并且除了透明之外什么都不是。
在这篇文章中,我们展示了一类称为树集合模型的模型,属于流行的高性能模型,如 XGBoost ,LightGBM,random forests …,我们可以使用一种称为“反事实解释”的方法来解释这类模型的决定。为了简单起见,我们在此仅考虑将数据分为两类的二元分类模型:正常/故障。
对于被模型分类为故障的给定查询点,我们计算称为反事实示例的虚拟点(以下称为 CF 示例)。后者是输入空间中欧几里得距离最近的点,被模型分类为正态。这个点是虚拟的,因为它不一定存在于训练集中。大多数情况下不会,我们是基于 FDM 模型参数建立的。CF 示例背后的几何直觉如下图所示。

两类分类器的决策区域。模型预测类别 1 的区域标记为“C1”,模型预测类别 2 的区域标记为“C2”。点 P#i 的最接近的反事实示例由 CF#i 表示。它是最接近 P#i 的点,被模型分类为类别 2。在上图中,我们将所有点 P#i 放在类 1 中,所以我们在类 2 中寻找它们各自的反事实例子。[图片由作者提供]
对于一个错误的数据,我们可以使用其相关联的 CF 示例来说明需要在最小程度上改变什么,以便它返回到正常类。你看到它在几英里外,这是我们用来执行故障诊断。这种方法非常有效,因为它可以发现细微的变化,从而区分错误数据和正常数据。这些变化可以是多变量的,也就是说,与正常状态相比,几个输入特征可能已经发生了变化。(CF vs 特性重要性?)最重要的是,我们有与物理设备(工作站)和制造过程相关的输入特性。因此,如果故障输入数据与 CF 示例的比较指出,为了恢复正常,我需要将站 25 中的温度降低 0.1°C,并将站 31 中的压力增加 0.05 巴,我可以迅速在我的生产链上安排这种干预,以避免新的故障发生和浪费更多的时间/材料。这也是反事实解释特别有趣的地方,它们给你一个精确的想法,告诉你应该采取什么样的最小行动来纠正问题。好吧,我希望我说服了你,手头有一个与你的错误数据相关的 CF 示例是快速解决你的问题的关键,并且,潜在地,是一笔巨大的金钱节省(认为一切都是关于金钱和时间节省的陈词滥调……)。
在这一部分中,我们展开了一个有效的算法方法来计算在树集成模型的情况下与错误数据相关的最近的 CF 例子。我会尽量保持整体的直观性,但有时我不得不挖掘数学。敏感的读者可以跳过这些部分,这并不妨碍对整体的理解,而且你仍然可以把它留到以后,当你解决了生产链上的问题,有了足够的自由支配时间的时候。
首先,我们需要输入树集合模型的特殊性。CF 示例是基于模型的特殊性(即模型参数)计算的,因此理解它们如何工作似乎是相当重要的一步。
让我们从决策树开始,它是这种模型的基础组件。更准确地说是二叉决策树:在这种树的每个节点中,我们通过将输入特性的值与单个阈值进行比较来分析它。如果特征值高于阈值,我们转到右边的分支,否则,我们转到左边的分支。我们重复这个逻辑,直到我们在与分数(对一个类的投票)相关联的树的叶子中结束。现在让我们从几何学角度来分析这意味着什么。我试着在下图中表现出来。如果你认为这个数字没用,我会在另一个帖子里把它作为数字艺术回收,但是请相信我把我所有的才华都放在里面了。

输入特征空间中的决策区域对应于决策树的叶子。这些区域是盒子/多维间隔,在某些边上可能是开放的。输入空间是二维的。在节点 Ni 中,我们指示输入数据的哪个维度 dj 被分析,以及与其比较的阈值 ci 。节点 Ni 因此与对 (dj,ci) 相关联。可以检查到,即使在树根和树叶之间的路径上对一个特征测试了两次以上,只有两次测试对于表征与树叶相关联的判定区域是有效的,其他测试是多余的。无论输入维度的数量是多少,该属性都有效。 N1 和 F1 之间的路径包含一个对特征 d2 进行冗余测试的例子:在节点 N2 中,我们测试“D22.5”,在节点 N4 中,我们测试“D21.4”,相当于只测试“D21.4”。[图片由作者提供]
从几何学上讲,决策树的叶子似乎是多维的盒子(在某些面上可以是开放的)。为了在数学上更加正确,我们会说叶子是多维区间。那么,树的组合怎么样?你自己可能很容易得出结论,它只不过是多维区间/盒子的集合,以及它们相关的类投票/分数。要预测一个输入元素,我们只需要搞清楚它属于哪些盒子(盒子之间可能会有交集),把关联的分数加起来就可以了。在数学上,如果我们将一个树集成模型 F 表示为一对( B ,S )其中是一组盒子/叶子和 S 的关联分数的集合,那么与 F 关联的预测函数就是这样简单:

树集合模型预测函数。n 是模型的叶子数。Bn 表示第 n 片叶子,Sn 表示与该叶子相关联的分数。Sn 是 K 维向量,其中 K 是与分类问题相关联的类的数量。它通常是一个稀疏向量,只为单个类投票(即只有一个非空系数)。为了知道与输入点 X 相关联的模型的决策,我们计算“arg max(F(X))”。如果 X 属于叶 Bn,则δBn(X)=1,否则δBn(X)=0。[图片由作者提供]
您可能认为像 XGBoost 这样的模型可能会产生更复杂的决策函数,但事实上不是这样……对于二进制分类,它会产生一个分数(在 0 到 1 之间),一旦与阈值(默认为 0.5)进行比较,就会告诉您数据属于哪个类别。我将发表另一篇文章,介绍如何将这篇文章中描述的可解释性方法应用于多类分类和回归的树集合模型,因此,请遵循我的渠道(为了你好,这没有任何商业成分,科学是我唯一的燃料)。
在这一点上,我们将尝试基于模型的决策区域的几何分解来确定我们的 CF 示例。这就是事情变得棘手的地方,因为我们不能直接使用我上面提到的分解。下面的例子应该足以让你相信我们做不到,这个世界比它看起来更糟糕。

几个叶子/盒子的交叉使得出现新的区域,其分数是通过将交叉形成这些区域的各个盒子的分数相加来确定的。例如,绿色区域的分数通过计算 S1 + S2 + S3 来确定,红色区域的分数通过计算 S1+S3 来确定。[图片由作者提供]
在上图中,三个方框 B1、B2 和 B3 的交叉点出现了两个新的决策区域(以红色和绿色突出显示)。我们无法确定这些区域属于哪一类,除非我们将相交形成这些区域的盒子的分数相加。你猜怎么着?这就是我们的工作。我们对原始分解进行“超级分解”,确定所有相交区域以及其他区域。出于算法的原因,我们计算一个“超级分解”,它也是盒子的集合。所以,它看起来会像这样:

相互交叉的三个盒子的初始集合的超级分解。超级分解也采用盒子集合的形式。后者不一定是能找到的最简单的盒状分解(就盒数而言)。[图片由作者提供]
这个问题的所有困难是设计一种算法方法来计算一个盒状的超分解,它避开了问题的潜在组合。实际上,以强力方式表述,该问题相当于确定,对于任何一组“k”个盒子,这些“k”个盒子是否形成最大相交区域,即它们的相交区域是否不为空,以及在“k”个盒子之外是否没有其它盒子被认为与该区域相交。这个问题是组合性的,即使对于中等数量的盒子也是不容易解决的。我们提出了一种逐维构建相交盒的算法。我们使用边垂直于坐标轴的盒子的一个便利的特性:如果两个盒子没有根据一个特定的维度相交,它们根本没有相交。下图说明了这个想法。

如果两个盒子没有根据一个特定的维度相交,则它们根本没有相交。在左图中,两个框根据尺寸 d1 相交,但不根据尺寸 d2 相交。在右图中,两个框根据尺寸 d2 相交,但不根据尺寸 d1 相交。[图片由作者提供]
这个属性产生了一个树状的层次结构。每个级别对应于输入空间的一个维度。在每个节点中,我们根据对应于树状结构的当前级别的维度来计算存储在该节点中的盒子的最大交集。如果两个框没有根据该维度相交,则它们将不再一起出现在树的下一层的相同节点中,因为根据上述属性,它们没有机会一起形成相交区域。
将我作为数字艺术创作者的惊人天赋付诸行动,我在下面尝试以图形方式“运行”该算法,已经达到了文字所能提供的极限,并且不想用一个核心的算法证明来窒息你。该算法主要是关于前面提到的树状结构的构造。该结构在其叶子中包含由树集合模型的叶子形成的交叉区域的盒状超分解。

由盒分解算法构建的树状探索结构。我们在此考虑二维特征空间中具有两片叶子(F1,F2)的模型。该算法逐维进行。第一级对应于维度 d1,并且对应于根据维度 d1 分解成模型叶的最大相交区间。在这一级之后,我们以三个独立的叶子子集结束,我们对其应用相同的 1D 分解过程,但是,这一次,根据维度 d2。阴影区域表示由算法产生的结果最大交集框。[图片由作者提供]
在树状结构的每个节点中,我们总是解决相同的交集问题:给定一列盒子和与该节点相关联的输入空间的维度,我们寻找该维度中盒子的最大交集,即我们解决前面提到的组合问题,但是是在一个维度中。因此,我们不再处理盒子,而是处理 1D 区间,它是盒子在所考虑的维度中的投影。幸运的是,在 1D,问题不再是组合的,甚至在考虑区间的数量上变成线性的。这个想法是将所有的区间放在一个 1D 轴上,如下图所示。

从 1D 区间的集合中寻找 1D 最大交集的区域(区间)。如果一个区域对应于 k 个区间的交集的区域,则称该区域为最大交集,其中 k 是在该区域中相交的区间的最大数量。为了计算这些区域,我们将所有区间放置在 1D 轴上,并且每当区间开始或结束时,我们创建一个新的最大相交区域。在上面的例子中,从区间{I1,I2,I3}的集合中,我们提取了五个最大相交区域 Z1,Z2,Z3,Z4 和 Z5。[图片由作者提供]
然后,我们注意到,除了终止最后一个最大交集区域的最后一个区间结束之外,每次区间开始或结束时,都会开始一个新的最大交集 1D 区域(这是一个 1D 区间)。因此,如果我们考虑 N 个区间的初始集合,我们将最多有 2 个。N-1 个最大相交区域。从算法上来说,我们必须对所有区间的开始和结束进行排序,这相当于排序 2。n 分。实际上,在超分解算法中,通过在每个维度上分别预分类构成树集成模型的盒子,并且通过观察使用屏蔽操作从有序集合中提取子集产生有序子集,可以跳过该操作。因此,我们可以考虑节点中盒子的子集,而不必根据与节点相关联的维度再次对它们进行排序。因此,与问题的初始复杂性相比,我们最终在树状结构的每个节点中进行的处理惊人地便宜。
在这一点上,你可能已经预料到超级分解算法可能会产生如此多的决策区域,以至于它甚至不适合你的计算机内存。这就是残酷的现实。因此,我们现在将寻找优化,仍然允许找到确切的 CF 示例,但限制实际建立的区域的数量。主要思想是,我们不需要构建模型的整个决策区域,只需要围绕查询点构建一些东西,这样就可以从其他类中找到一个例子。因此,我们想要限制精确 CF 示例所在的搜索区域的大小。作为第一次尝试,我们可以使用训练数据来这样做:给定一个查询点,我们在训练集中寻找被模型分类为正常的最接近的数据(注意,我说的是“分类的”,而不是“标记的”)。这为搜索区域的大小提供了第一个相当可靠的上限。使用这个,我们将只建立位于这个上限内的搜索区域。这很方便,因为用于建立决策区域的算法是逐维进行的。因此,如果对于给定的维度,部分构建的决策区域已经比上限更远,我们可以停止在搜索树的相应节点中的探索。这种方法的通用名是“分支-绑定”,在需要沿着树状结构的分支进行探索的所有场景中特别有用(这显然是我们的情况)。
我希望你还在跟随我进行这一危险的分支定界的探索。还有许多其他实用的优化方法,其中最有效的是以深度优先的方式探索搜索结构。这很快为我们提供了一个比单独使用训练集计算的上界更好的上界。在实践中,我们使用多线程并行维护多个深度优先探索,以保持其有效性。每当我们发现一个比最后一个紧的上界更紧的上界时,我们就更新最紧的上界的值,并将新的上界传递给所有线程以供立即使用。这最终导致搜索树的快速修剪,并允许在眨眼之间彻底探索模型的决策区域(有时更多,取决于你眨眼的速度)。
最后一点,维度在某些时候对你有利(能够写这个很少见,所以我几乎可以称这个时刻为历史性的,如果我不打字,你会看到我的笔迹颤抖)。简单地说,添加的维度越多,部分构建的框到查询点的距离超过上限的几率就越高。这个简单的效果会使您通过搜索树创建的区域数量保持稳定,甚至有时会在达到一定数量的维度后(略微)减少。好吧,让我们不要对此过于狂热,最好添加好的优化,祈祷快速的结果。
所以,现在我展开了资金原则,让我们把我们的手放在代码中。出于我极大的善意,也为了你美丽的眼睛,我用一段很好的 C++程序编写了上面所有的优化(甚至更多),我用 R 封装了它(也许在不久的将来,我还会写一个 python 包装器,这样它就可以和你喜欢的高级编程语言一起使用了)。我将在另一篇博文中向您展示如何做好简单的 Rcpp 包装,以至于您可能会考虑放弃 python。
R 包可以在我的 github 上找到。它需要“ Boost 和“TBB”c++库。您必须检查这些库的路径是否在位于。/src "文件夹,或者它们可以在标准系统路径中找到。要将软件包安装到 INSTALL_PATH 文件夹中,请从终端使用以下命令(仅限 linux 用户):
*cd PACKAGE_SOURCE_FOLDER
R CMD INSTALL -l /INSTALL_PATH ./ --preclean*
一旦你(成功地)安装了 R 包,让我们从用例开始。我选择保持相对简短的描述,以避免 github 的可怕冗余。如果您感兴趣,请注意代码仍在开发中,等待外部贡献者将它制作成一个大尺寸树集合模型的 CF 可解释性的伟大包,有一天它可能会在 XGBoost 的代码中占有一席之地(是的,作者是一个梦想家……)。
作为第一个例子,让我们考虑一个用于消费信贷批准/拒绝的数据集。与我在博客文章开头宣传的工业故障检测场景完全不同,但是 CF 可解释性方法可以扩展到任何分类场景,包括正常类(信用批准)和“异常”类(信用拒绝)。此外,小数据集很好地展示了一些东西,学术界已经使用它们几十年了(这是一个可靠的论点吗?).该数据集允许学习 20 个输入特征之间的映射,例如正在进行的信贷数量、收入、年龄、信贷目的…以及授予或不授予信贷的决定。
我省略了 XGBoost 模型的所有特性格式和训练细节,您可以很容易地从演示脚本中对它们进行逆向工程。让我们直接跳到 CF 示例计算。我们首先需要选择测试数据中与信用拒绝相对应的一点。我们检查它是否被归类并标记为“信用拒绝”。为了保证以后,我们简单地看看地面真相。就两类准确率而言,经过训练的模型确实不会高于 75%,因此会有许多误报(意味着被归类为“信用拒绝”的点,而这在现实中是不存在的)。给定一个我们称之为“查询点”的选定点,我们计算其相关的最近 CF 示例。下面,我解释用于确定 CF 示例的函数中每个参数的含义。我觉得有必要在这里做,因为它在代码中缺乏解释。
负责计算 CF 示例的主方法的签名。
下表显示了三个人的一些结果。每次提到查询点(客户端状态),以及关联的 CF 例子(推荐+限制推荐)。当查询点和 CF 示例之间没有变化时,该单元格留空。对于每个人,我们计算两个 CF 示例。第一个(行“建议”)对应于“完全行动能力”情景,即个人可以控制/改变与自己有关的所有特征。第二种情况对应于“部分可行动性”情景,即个人只能控制/改变少数变量。例如,认为个人无法控制自己的年龄是更现实的,尽管对再生乳液的研究已经取得了很大进展。

对于三个不同的个人,我们诊断了银行拒绝他们所要求的信贷的决定,并且我们展示了 CF 方法所提供的建议,即他们需要改变一个最小值以使信贷被授予他们。对于每个个体,我们发出两个建议:第一个认为个体有控制/改变所有特征的可能性(“建议”线)。第二种认为个人只能控制有限数量的特征(“限制推荐”线),这通常更现实(例如,个人不能对自己的年龄或婚姻状况做出任何改变)。无论如何,我真诚地希望这将帮助你获得 100 美元的信贷,你需要购买布加迪赛车你的梦想。[图片由作者提供]
第二个用例涉及 MNIST 数据集的误分类数字。我们从十个类别中提取两个类别,它们本质上是不明确的(例如 1 和 7,或者 5 和 6)。然后,我们训练一个两类分类 XGBoost 模型,该模型学习区分这两个类。作为查询点,我们选择属于一个类的一个点,该点被模型误分类到另一个类中。我们还确保输入的数字在视觉上是模糊的(意味着人眼不能真正判断它属于哪个类别)。这最后一点特别重要,因为我们的目标不是检测分类模型的潜在弱点,而是检测输入数据中异常值的存在。因此,我们需要能够认为输入数据在某些方面是错误的,并且分类器只是做它的工作。只有在这些假设下,我们才能在误分类和错误数据之间画出一条平行线,并像对待错误数据一样对误分类的例子应用我们的 CF 方法。
在这个用例中,我们引入了另一个方面:合理性的概念。为了使 CF 示例更可信(即,视觉上更有说服力),我们可以在与模型对 CF 示例的预测相对应的决策得分上添加约束。通过迫使模型以更大的置信度预测正确的类,我们获得看起来越来越接近正确的类的元素的 CF 示例(“正确的类”,我是指与基本事实中的查询点相关联的类)。强制似然性以失真为代价,这意味着较高的似然性导致 CF 示例在欧几里德距离方面离查询点更远。下图说明了这个想法。

考虑两个二进制分类问题(5 对 6 和 4 对 9)。第一行:一个“5”失误的例子——被模型归类为“6”。初始查询图像显示在左侧。我们使判定阈值在 0.5 和 0.2 之间变化。获得被模型更有把握地分类到正确类别的 CF 示例是以引入到初始查询数据的失真为代价的,即,判决阈值“eps”越低,到初始查询“dCF”的距离越高。从视觉上看,我们看到 CF 方法提供了对初始查询数据的合理更改,使其看起来更像“5”。第二行:一个“4”失误的例子——被模型归类为“9”。[图片由作者提供]
也可以实施其他似真性标准:例如,我们可以检查找到的 CF 例子位于包含来自训练数据集的至少一个元素的最大交集区域中。这将避免挑选出在现实生活中不现实/不可及的非分布 CF 示例。
最后一点,在我让你吃你的没有缺陷的晶片之前,并不强制考虑一个查询点和它对应的 CF 例子之间的欧几里德距离。也可以考虑其他距离,而不需要改变分解算法,只要这些距离可以被公式化为坐标相加,即被计算为:“ d (X,Y) = g ( ∑ di (X[i],Y[i])”,其中“ di ”是应用于第 I 个坐标的特定运算,“ g ”是递增的单调函数。比如: di (X[i],Y[i]) = (Y[i]- X[i])对于平方的欧氏距离。
好吧,这篇很长的博客文章要记住什么(抱歉,我可能在第一次尝试与世界交流时过于啰嗦)。首先,树集成模型允许精确 CF 示例的计算,同时是用于故障检测的更好的模型(尤其是梯度增强树)。第二,除了定位故障/异常之外,CF 示例给出了纠正故障/异常的最小行动的精确概念。此外,这里提供的 CF 方法具有产生稀疏解释的巨大优势,即,在数量减少的输入特征上建议改变,这使得解释对于人类用户来说更容易理解。
如果你对这个话题感兴趣,并且想知道更多关于这个方法的可能性,你可以在这里阅读全文,我恰好是这篇文章的作者(但这纯粹是随机巧合)。如果你想在你的应用程序中使用这个包,请引用:一个精确的基于反事实例子的树集合模型可解释性方法
在下一篇文章中,我将向您展示 CF 解释对回归问题的扩展,并教您(如果您允许的话)如何部署 CF 推理以实现利润最大化,或者至少,如何仅仅通过改变厨房地毯的颜色就使您的房屋销售价格攀升 10 000 美元。好吧,我说得太多了(或者说还不够),但是如果这第一篇帖子成功了,我会把完整的配方交付给你:)。
为非数学家解释量子电路的数学
你是否因为所有的数学知识而难以理解量子电路?
量子机器学习要不要入门?看看 动手量子机器学习用 Python 。
在以前的帖子中,我声称你不需要成为数学家或物理学家才能理解量子计算。
然而,如果你既不是数学家也不是物理学家,学习量子算法有时会很难。它具有挑战性,因为它广泛使用符号。符号的问题在于,你不能轻易地谷歌它们。例如,考虑下面的电路。

作者图片
它代表了一种你在学习量子算法时会经常遇到的结构。但是如果你不知道这些术语的意思,你怎么知道呢?
以|0⟩和|1⟩为例。只是为了好玩:谷歌一下。
𝐻⊗𝑛怎么样?你知道如何在谷歌中输入这个词吗?(即使是中等文本编辑器也不支持正确书写此术语。)我的意思是,如果你得到的只是一张图片,那么从图片上复制文字都不容易。
如果你不是数学家,这些符号和象形文字一样好。

作者图片
当然,你不需要成为天才才能理解量子电路。只要你比克里特斯聪明一点,你就会没事的。

作者图片
那么,让我们来解释一下量子电路。量子计算不是偶然用数学符号来描述的。量子电路是一种数学构造。这是一个等式。而且,毫无疑问,数学是一种简洁而精确的语言。有了数学,你可以非常简洁地描述算法。
不幸的是,对我们大多数人来说,数学充其量是第二语言。
让我们从最左边开始。在那里,我们找到了电路的输入。|0⟩和|1⟩表示量子位元最初的状态。当我们在|0⟩态测量一个量子位时,它是 0。当我们在|1⟩态测量一个量子比特时,它是 1。这类似于传统的钻头。
那么,我们为什么不直接写 0 和 1 呢?为什么我们要把它们放在这么奇怪的框架里?
原因是一个量子位不仅仅是 0 或 1。它是 0 和 1 的线性组合,除非你测量它。一旦你测量它,它立刻变成 0 或 1。我们用向量来描述这种线性关系。所以|0⟩和|1⟩表示两个向量。它们是我们在量子计算中使用的标准基向量,用狄拉克符号书写。向量是有长度和方向的几何对象。狄拉克符号(也称为 bra-ket)在量子力学领域很流行。但这并不新奇。
我们可以简单地说

我不得不承认。这两个方程只是将向量从一种符号转换成另一种符号。所以,它仍然使用数学和符号。但这是大多数人在高中学习的符号。如果你对向量一点都不熟悉,我给你谷歌了足够多的关键词。
但是在你去 Google now 之前,我们为什么不图形化地看一下这两个向量呢?

作者图片
向量中的每个数字代表一个维度。所以,我们的向量是二维的。
在此表示中,两个维度都位于垂直轴上,但方向相反。因此,系统的顶部和底部分别对应于标准基矢量|0⟩和|1⟩。
在图中,有另一个称为“psi”的向量——|𝜓⟩.
|𝜓⟩代表任意量子位状态。我们上面提到过,一个量子位是 0 和 1 的线性关系。因此,

𝛼和𝛽是该矢量到标准基矢量|0⟩和|1⟩.的距离如果我们将它们平方,我们得到测量量子位为 0 (=𝛼)或为 1 (=𝛽)的概率。
举个例子,如果我们说𝛼=1 和𝛽=0,那么|𝜓⟩=1⋅|0⟩+0⋅|1⟩=|0⟩.对于𝛼=1 来说,测量量子位为 0 的概率是 1 =1=100%。
仅仅说我们测量一个量子位为 0,另一个量子位为 1,这就需要很多数学,不是吗?
但是,我们还没有完成初始化。你可能已经注意到在|0⟩^𝑛.旁边有一个小小的上标𝑛通常,这表示一个指数。这里,它说我们在|0⟩态没有一个量子比特,但是在这个态有𝑛量子比特。我们也可以把它写成|00⋯0⟩和𝑛0。
所以,我们的电路包含了 n 个量子位,它们被初始化为一个我们测量为 0 的状态,还有一个量子位被测量为 1。
在量子位的初始化旁边,我们看到了术语𝐻⊗𝑛和𝐻.先说后者。在量子计算中,大写字母通常代表矩阵。矩阵𝐻代表哈达玛矩阵,其定义为

重要的是要知道,如果你用一个向量乘一个矩阵会发生什么(假设矩阵的列数等于向量的行数)。然后,结果是另一个向量。

例如,如果我们将哈达玛矩阵乘以状态向量|0⟩或|1⟩,那么我们得到另一个向量。

和

两个矢量的𝛼和𝛽值相等,如果你平方它们。唯一的区别是𝐻乘以|1⟩.时𝛽的符号通常,标志也是这种状态的简称。
在量子计算中,|+⟩是

|−⟩是

当你计算这些值的平方来分别计算量子位的概率为 0 或 1 时,你会得到 1/2。
因此,𝐻矩阵有效地转换了量子位的状态。我们不再确定地测量量子位为 0 或 1。但是当我们测量的时候,可能是 0,也可能是 1——各有 50%的概率。
也许,你已经开始怀疑𝐻⊗𝑛的意思了。|0⟩^𝑛代表|0⟩.的𝑛量子位如果我们把这些量子位看作一个单一的向量,那么这个向量就不仅仅只有二维,而是 2⋅𝑛维。每个量子位有两个维度。
只有当一个矩阵和一个向量有相同的维数时,我们才能把它们相乘。这就是𝐻⊗𝑛所关心的。
让我们看看𝐻⊗2=𝐻⊗𝐻.这种两个矩阵相乘的方式叫做张量积。我们可以把它写成

它创造了一个更大的矩阵。更重要的是,它是递归工作的。

我饶你在这里扩展𝐻⊗2。总的来说,是的

这个公式被称为克罗内克积。这是数学上的说法:
对每个 𝑛 量子位应用相同的变换矩阵(这里是 𝐻 )。
电路的下一个术语叫做𝑈𝑓.它代表一个酉矩阵。酉矩阵是它自己的共轭转置矩阵。这听起来很复杂。但简单来说,矩阵翻转了对角线。自身是共轭转置的矩阵具有有趣的特征。例如,当你把这样一个矩阵本身相乘时,结果就是单位矩阵。𝑈是一个矩阵,𝑈†是它的共轭转置。然后,𝑈†𝑈=𝑈𝑈†=𝐼.
我们知道矩阵可以改变量子位的状态。但是,单位矩阵不会改变它。所以,我们可以说𝐼|𝜓⟩=|𝜓⟩.
所以,当你把你的量子位状态乘以同一个幺正态两次,它会再次得到原来的量子位状态。或者,换句话说,转换会自行恢复。
酉矩阵的另一个重要特征是它保留了测量概率。这是当前电路中的重要一点。
记住,所有的量子位在进入𝑈𝑓变换之前都处于 0 和 1 概率相等的状态。所以,如果一个酉矩阵不改变概率,它们仍然是相等的。
如果这个矩阵不会改变概率,我们为什么还要应用它呢?
答案就在|+⟩和|−⟩之间。如上所述,两种状态下的量子位具有相同的测量概率。
但是我们已经知道的哈达玛门有一个有趣的效应。它不仅把一个量子位从|0⟩变成了|+⟩,从|1⟩变成了|−⟩,还把|+⟩变回了|0⟩,把|−⟩变回了|1⟩.它会恢复原状。
这就是最终的𝐻⊗𝑛所做的。关键是𝑈𝑓是我们要解决的某个问题的占位符。
它只是将一些量子位从|+⟩翻转到|−⟩.它翻转了问题解中必须为 1 的那些。而且,它没有触及|+⟩那些需要在解决方案中为 0 的部分。
结论
在这篇文章中,我们揭示了量子电路背后的数学原理。我们学习了量子电路的数学以及它的含义。
原来小小的数学符号里有很多含义。如果你都知道,数学是精确描述电路的简洁方法。但是如果这些你都不知道,单单数学作为解释是不够的。它没有强调重要的事情,也没有提供你可以用来做自己研究的线索(比如关键词)。
手边的电路代表了著名量子算法的结构,例如 Deutsch-Jozsa 算法和 Bernstein-Vazirani 算法。
两种算法的区别在于𝑈𝑓.的实现
𝑈𝑓矩阵将这些量子位从|+⟩翻转到|−⟩,我们想测量为 1。并且这些形成了算法旨在解决的问题的解决方案。
在这篇帖子中,你可以了解更多关于用友和 Deutsch-Jozsa 算法的细节。
你对量子计算了解得越多,你对数学的理解也就越多。但是你也会看到量子计算不全是数学。然后,你会奇怪为什么没人费心用非数学的方式解释事情。
数学应该是解释的补充。这样,读者就更容易理解这些公式。如果她在看到一个公式时已经理解了这个概念,那么所有这些符号更有可能是有意义的。
量子机器学习要不要入门?看看 动手量子机器学习用 Python 。

免费获取前三章这里。
自我学习、进化的神经网络的解释
观察并理解算法是如何学习、变异和进化的

就像在塞伦盖蒂草原上,我们的算法是适者生存(图片由陈虎在 unsplash 上提供)
在这篇文章中,我们将通过一个自我学习,基于进化的遗传算法的应用来增强它自己的拓扑结构。迷茫?我可以想象;这是一些大词。不过,请继续关注我,我保证在本文的结尾,您会看到这个算法的美妙之处。
为了尽可能清楚地理解神经进化算法,我编写了一个名为 Dots 的小游戏,它将展示算法的工作方式。我试图用外行的术语描述发生的事情,而不是专注于编程或数学。目标是让你了解神经网络的一般工作原理,并看到它们的可能性和应用。
我们正在使用的算法被 Ken Stanley 称为 NEAT(扩充拓扑的神经进化)。读完这篇文章后,你会对它的工作原理和好处有一个大致的了解。我们将通过以下步骤来实现这一点:
- 游戏的描述和目标
- 圆点;输入和输出
- 演变的点
- 将所有这些放在一起—快速总结
- 其他应用
1.设置和游戏目标
前往mike-huls.github.io/neat_dots并按下右上角的绿色大播放按钮。

1.显示单个点的游戏概述(出于说明目的画出的传感器)
你首先会看到的是右下角的一堆圆圈,然后它们就像喝醉的飞机驾驶员一样向前移动。这些圆圈被称为点,它们产生了数百个。在游戏开始的时候,几百个小点产生并开始移动。如果他们撞上了黑色的边界,他们就死了。边缘的黑边是一个障碍。如果一个点碰到其中的一个,它就死了。您可以点击“添加障碍”按钮来绘制新的障碍。
一旦所有的点都死了,或者过了一段时间后,一个全新的新一代的点就会诞生。每个点的目标是达到左上角的目标。游戏的目标是进化这些点,使它们以最快、最优化的方式达到目标。让我们来看看这是如何工作的!
2.介绍我们的选手
玩几次这个游戏,尝试一些不同的障碍,看看这些点是如何反应的。现在是时候更好地了解他们了。首先我们将分析点本身;它如何解释环境,它能做什么,它如何决定这样做?

介绍我们的点(和一个漂亮的大脑;稍后将详细介绍)
行动、传感器和决策
圆点可以以两种方式移动:它们可以加速、减速和左右转向。dots 的目标是加速和转向,以达到目标;左上角的白色圆圈。
但是一个圆点如何决定何时加速或转向呢?这完全取决于点的周围环境。每个有 9 个传感器;它可以朝 8 个方向看(由图 1 中的线条表示)。此外,它可以检测是否看到目标。偏置传感器总是设置为 1;以防所有其他节点“关闭”。
所有传感器的值都在 0 和 1 之间。越靠近障碍物,该值越大,为 1。如果没有检测到障碍物,传感器为 0。seesGoal 传感器的工作方式略有不同;不是 0 就是 1。
动作的值介于-1 和 1 之间。负值将减速或转向左侧,正值将加速或转向右侧。
连接这些点;创造一个大脑
我们的目标是让 dot 作用于它的传感器;它为了生存和触及目标而解读它的环境。圆点必须知道,如果它在那里探测到一个障碍物,它不应该转向左边。每个点都有一个大脑,使得一个点完全有可能做到这一点。把大脑想象成传感器连接到行动的方式。这也被称为神经网络的拓扑结构(还记得这篇论文的名字吗?).您将在第一列中看到传感器节点,在第二列中看到操作节点。

2.一个点的大脑
在游戏的右上角,你可以看到最成功的圆点的大脑。你可以在图 2 中看到一个特写。由于游戏刚刚开始,这个大脑相当简单,但它可以复杂得多:

3.更复杂的大脑
dot 不仅创建了更多的连接,还创建了一些额外的节点。这样它可以一次解释多个传感器。例如,它可以检测传感器 1 和 2 中的障碍物,然后将该观察结果与传感器 0 中的障碍物相结合,并让该结果影响转向。
创建额外的节点和边是非常特殊的;在大多数神经算法中,你必须事先定义你的拓扑(大脑)。它假设您知道需要连接哪些节点。NEAT 的美妙之处在于它从最简单的拓扑开始;只有输入(传感器)和输出(动作),然后建立自己的拓扑结构。这保证了一个最小的网络结构,通常真的很酷。稍后我们将讨论算法如何演化拓扑。
在图 2 中,我们看到传感器 6 通过一条红色粗线连接到方向盘。红色表示连接是负的。厚度表示连接的重量高;传感器 6 对转向有很大的负面影响。换句话说:当这个点探测到它左边的东西时,它会转向左边,直接撞上障碍物。这有点傻,但没关系,我们的小点还在学习。
3.演变的点
我们使用的神经网络的美妙之处在于,我们没有预先定义任何东西。这个点并没有被设定远离墙壁,它甚至一点也不知道墙壁是如此致命。然而,这个点被编程为学习。在这一节中,我们将深入探讨 dot 是如何学习的,它是如何分享其知识的,以及这些知识是如何传递给下一代的。一般顺序如下:
- 计算点适合度
- 形成物种
- 交叉
- 使突变

我们正在进化我们的点(图片由尤金·日夫奇克在 Unsplash 上拍摄)
适者生存
当一个点产生时,它的传感器和动作之间没有多少联系。将创建一个随机连接,并赋予一个随机权重,由其厚度表示。在上面的图像中,这个点会自己撞到墙上。交通部不知道这对他来说是否是个好决定;它不知道自己是否在做正确的事情。为了学习,必须有某种对他有益的衡量标准。这就是健身的用武之地。
你可能认识达尔文的这个术语。健康反映了你对周围环境的适应程度。把它想象成一只狗,如果它做了好事,它会得到一份奖励;我们在强化“好”的行为。在游戏中,我们可以在模型信息部分看到最佳表现点的适合度。

点模型信息
我们在代码中定义一个点什么时候做得好。在这个特殊的例子中,我将适应性定义如下:
- 如果 dot 还没有看到目标,它需要进入探索模式:健身是 0 开始,并增加它覆盖的距离。
- 如果这个点已经看到了目标,这意味着这个点已经到达了它可以到达的位置。首先,dot 会因此获得奖金;那么它的适应度就由它死于目标的距离来定义;越短越好
- 如果小点达到了目标,它将再次获得奖励。健康的定义是达到目标所需的最短步数;确保我们尽快到达那里。
太好了!当一个点表现良好时,我们已经进行了编码。有了这些信息,我们就可以执行这个算法中最重要的部分:物种形成。
物种形成
现在我们有了比较点的方法,我们可以揭示为什么我们同时产生数百个点:它们将进行实验,分享知识和进化。它是这样工作的:在每个点完成它的工作后;四处狂奔,随意撞墙,是时候看看谁最厉害了。首先我们要根据他们的大脑来比较所有的点。非常相似的点将被归为一个物种。
你可以在游戏中识别物种;有时你会清楚地看到一组点在做着与其他点完全不同的事情。也许他们走向北的路线,而另一组绕过障碍物向南走。这是因为他们的大脑明显不同。
我们将根据每个物种的适合度排列它们内部的点,然后杀死每个物种的下半部分。这听起来可能有点残酷,但你是完全正确的。但是就像在自然界一样;只有适者生存。还要注意的是,圆点只需要在它们的物种内部竞争;这是为了保护创新。新发展的 dots 可能在开始时表现不佳,需要几代人才能真正发挥作用。如果它们在发挥出真正的潜力之前就灭绝了,那就太可悲了。
最后,是时候消灭过时的物种了。我们可以通过检查物种中表现最好的点的适合度分数来检测这一点。如果几代人都没有改进,它就变得陈旧了,是时候淘汰了。

交叉
在最后几段中,我们已经删除了许多坏点。让我们用我们最好的点来繁殖后代,把那些聪明的头脑传递下去。在每个物种中,随机选择成对的亲本。具有较高适合度分数的点具有较高的变化以被选择。每一对将结合他们最好的特点创造一个孩子。他们后代的大脑将是他们父母大脑的结合体。在这一步之后,我们的人口将与这一代人开始时一样多。
使突变
新一代开始前的最后一步是突变。为了保持创新,大脑中的每个点都会随机变异。这意味着每个点要么得到一个新的节点,要么得到一个新的连接。
这一步在所有的点上引入了一些随机性。在某些情况下,这将是有益的,其他点将遭受他们的新大脑。谢天谢地,多亏了物种形成,这个点将有一些时间来“调整”它的大脑。
再做一次
我们的新一代已经准备好迎接下一轮挑战。所有的点将再次产生,希望导航与多一点成功,并最终达到目标或死亡。然后他们的适应度被再次计算,所有的点都被特化、剔除、繁殖和变异,我们为下一个周期做好了准备。每一代都会比上一代好一点,最终目标会实现。
4.把所有的放在一起
我认为 NEAT 非常特别,因为它要求你事先定义很少的东西。在许多统计技术中,有许多要求。在许多遗传算法中,我们需要预先定义我们的拓扑结构(点脑)。NEAT 从一个最小的拓扑开始,只是连接输入和输出,然后慢慢增加复杂性以尽可能保持简单。此外,它通过使用物种来保护创新;圆点只需要在它们的物种内部竞争。此外,它培育出表现最好的点,结合了父母双方的最佳特征。
与其他统计技术相比,NEAT 的缺点是它生成的模型非常复杂。当我们使用线性回归时,我们可以提取一个简单的公式,一个整洁的大脑可能会非常复杂,很难用一个简单的公式来把握。当我们可以使用线性回归来确定变量之间是否存在线性关系时,这种关系可能很难看出。
5.其他应用
在点的情况下,我们将点传感器连接到它可以执行的一些动作。这在现实世界中没有太大的价值,但谢天谢地,NEAT 并不局限于通过障碍迷宫导航笨拙的圆圈。在这一节中,我们将研究一个假设的例子,如何在现实世界中应用 NEAT。

经纪人机器人将决定你的房价(图片由亚历山大·奈特在 Unsplash 上提供)
决定房价
您有一个包含房屋特征和销售价格的数据集。我们将应用 NEAT 来创建一个大脑,它可以根据特征最准确地确定销售价格。
一个点有 9 个传感器:8 个方向的视线和是否能看到目标。例如,我们的房价大脑将拥有房屋表面、土地表面、卧室数量、建筑年份、拥有车道和停车位数量。例如,我们必须在 0 到 1 之间调整所有这些值,其中 0 是最小建筑年份,1 是最大建筑年份。我们的输出将是房价。房价也会是 0 到 1 之间的数字;在我们的训练数据中,它映射在最低和最高房价之间。
就像我们产生了几百个点一样,现在我们将产生几百个“宅脑”,并从我们的训练集中给他们所有人一些数据记录。数据流经大脑计算房价。我们将把这个与实际房价进行比较。区别在于适合度;越小越好。然后,简洁的算法再次发挥作用。大脑被评分、分类、筛选、特化、繁殖和变异,之后我们会再次向它们输入数据。
大脑会改变它们的拓扑结构并调整它们的权重。经过多次迭代后,我们可以将表现最好的大脑输入一些新数据,以便预测房价。
结论
当我第一次读这篇整洁的论文时,我爱上了这个算法,因为它非常像自然。我们谈论的是物种、适应度、基因组、杂交、父母和后代。对我来说,我们能够对这种自然行为进行编程是非常令人着迷的。我真的很喜欢这个算法的灵活性,优雅和效率。
我希望这篇文章是对这个伟大算法的介绍,并且足够清晰地向您展示它的美妙之处。如果你有建议/澄清,请评论,以便我可以改进这篇文章。同时,查看我的其他关于各种编程相关主题的文章。编码快乐!
—迈克
页(page 的缩写)学生:比如我正在做的事情?跟我来!
更多推荐



所有评论(0)