本文要点
- 你可以使用模拟方法验证对于特定度量的估计;
- 当你的机器学习模型被用作会影响最终结果的措施时,你需要有一个用于反事实评估的系统;
- 你可以生成黑盒模型决策的“解释”,而这些解释有助于模型解释和调试(即使它们还不完善)。
长期以来,机器学习已经为许多我们每天都与之打交道的产品提供了支持——从类似 Apple Siri 和 Google Now 这样的“智能”助手,到推荐引擎,如 Amazon 的新品购买建议,再到 Google 和 Facebook 广告排名系统。特别是最近,随着“深度学习”的发展,机器学习已经进入了公众意识——其中包括 AlphaGo 击败了 Go 特级大师李世石,以及围绕图像识别和机器翻译的令人印象深刻的新产品。
在本系列文章中,我们将介绍一些功能强大而且普遍适用的机器学习技术。这包括深度学习,也包括更为传统的方法,而后者通常就能满足现代商业的所有需求。通过阅读这个系列的文章,你应该可以了解到机器学习的基本知识,开始独立地在各种领域进行实际的机器学习实验。
InfoQ 上的这篇文章是“机器学习入门”系列文章的一部分。读者可以通过 RSS 订阅来接收通知。
使用机器学习解决现实问题时,经常会面临一些最初开发机器学习方法时未考虑到的挑战,但是,遇到源于自己应用程序的挑战是身为实践者的乐趣的一部分。本文将举一些解决此类问题的例子,希望可以就如何通过对已有的数据进行简单的分析来克服这些挑战提供一些建议(和灵感)。
也许你想要量化其中一个业务指标的不确定性。遗憾的是,在任何一个指标的基础上增加“误差条(error bar)”都比计算平均值要困难。计算误差条宽度的合理公式通常是以数据点独立为前提(这在任何业务中几乎都永远不会成立——例如,在你的数据中,每个客户会有多个数据点,或者客户之间在社交网络上相互连接)。另外一个前提是,业务指标通常分散在用户中间,但这个假设经常会因为“超级用户”或一大部分不活跃用户的存在而不成立。但是别担心,摸拟方法和非参数方法几乎总是可以用于创建误差条,你所要做的只是写几行代码并准备一定的计算能力。
或者,你在产品中使用了一个二元分类器:例如,你可能需要确定是否向一名网站访问者展示广告,或者是否因为存在欺诈风险而拒绝信用卡交易。分类器导致的动作其实可能会成为自己的敌人,因为它让你观察不到其中一类观测结果:如果我们没有展示广告,那么我们就永远无法知道网站访问者是否会点击,同样,由于缺少评估数据,我们也永远无法知道信用卡消费是否真的会被欺诈,除非我们使用了它。幸运的是,有统计方法可以解决这个问题。
最后,你可能会使用一个“黑盒”模型:一个可以准确快速地作出预测的模型,计算机很容易理解,但其设计并未考虑让人类进行事后检验分析(随机森林就是一个典型的例子)。对于那个模型做出的决策,你的用户希望有可理解的解释吗?简单的建模技术可以解决那个问题。
作为一名侧重于统计分析的ML 实践者,我最喜欢的其中一件事情就是这个领域的乐观精神。在与数据分析相关的领域强调乐观精神似乎有点奇怪:统计分析师有一点不好的名声,他们是令人扫兴的人,他们会指出协作者在实验设计上的缺陷、违反模型假设或者因为缺少数据出现的问题。但是,ML 实践者一直竭尽全力地研究解决此类问题的技术,我就是从中看到了乐观精神。我们可以在事后纠正成本高昂但设计糟糕的生物实验。我们可以构建回归模型,即使我们的数据关系意外复杂或者无法量化,让标准的线性回归无法排上用场。我们可以根据经验估计如果我们缺少数据会出现什么情况。
我之所以举这些例子,是因为它们(还有无数其他类似的例子)让我相信,我们实际上可以解决大多数的数据问题,而且只需要使用相对简单的技术。我不喜欢放弃回答一个实证机器学习问题,而仅仅是因为乍看之下我们的数据不完全符合“教科书”。下面是几个机器学习的例子,从某一点上看似乎无法解决,但通过一些并不复杂的技术就可以解决。
问题1:模型成为自己的敌人
对抗性机器学习是机器学习的一个有趣的分支,它在一个数据因外部“敌人”而随时间变化的系统中处理模型构建,例如,有人尝试利用当前模型的漏洞,或者模型的受益者犯了一个错误。欺诈和安全是对抗性机器学习的两大应用领域。
我在 Stripe 从事机器学习方面的工作,这是一个构建互联网支付基础设施的公司。具体来说,我构建机器学习模型来自动检测和阻止我们平台上的欺诈付款。我的团队的目标是,在未经持卡人同意的情况下拒绝交易。我们使用投诉识别欺诈:当他们的信用卡在未经他们授权的情况下被使用时,持卡人会投诉相关企业。
在这个场景中,很明显,欺诈者是我们的敌人:那些人为了经济利益设法用偷来的信用卡号码交易。聪明的欺诈者通常知道,银行和支付处理商有阻止欺诈交易的模型,因此,他们会不断地寻找绕过这些模型的方法,所以我们要抢在那些坏家伙前面,努力更新我们的模型。
然而,更不易察觉的敌人是模型本身:一旦我们在产品中引入了一个模型,二元分类器的标准评价指标(如本系列文章第一篇中描述的准确率和召回率)就无法计算了。如果我们阻止了一笔信用卡交易,这次支付就永远不会发生,我们也就无法确定它是否是欺诈。也就是说,我们无法评估模型的性能。从理论上讲,任何观察到的欺诈率上升都会被归结为入站欺诈,而不是模型性能的退化;没有结果数据,我们就无法知道。非严格意义上说,模型是自己的“敌人”,因为它隐藏了性能指标,减少了训练数据供应,妨碍了模型改进。这也可以视为非常不幸的“缺失数据”问题:我们“缺少”了所有模型阻止的交易的结果。其他机器学习应用也面临着同样的问题:例如,在广告行业中,如果一则广告没有展示给访问者,那么就无法知道那个人是否会点击这则广告(基于模型针对那个用户预测的点击概率)。
具有标记过的训练数据和模型性能指标对于业务而言非常重要,所以我们研究出了一种相对简单的方法来变通地解决那个问题:我们随机让一小部分模型通常会阻止的支付完成,然后看看会发生什么(也就是说,观察支付是否是欺诈,从而填补我们缺失的部分数据。)。撤销模型“阻止”决策的概率取决于模型在多大程度上确定那笔支付是欺诈。如果模型不那么确定支付是欺诈,则这样的决策更可能被撤销;如果模型认为支付非常有可能是欺诈,则大概永远不会被撤销。撤销概率会被记录。
然后,我们可以使用名为“逆概率加权(inverse probability weighting)”的技术,借助标记过的结果数据,重构出一个经过完全标记的支付数据集。逆概率加权的思想是,对于一笔支付,因为我们撤销了模型的“阻止”决策,所以知道了其结果,如果撤销概率是5%,则表示有20 个这样的支付:它自己和19 笔其他与这笔支付类似而模型决策没有被撤销的支付。因此,我们实际创建的数据集中会把那笔支付复制20 次。由此,我们可以为我们的模型计算所有常见的二元分类器指标:准确率、召回率、误报率等等;我们还可以估计类似“入站欺诈量”这样的东西,为新的改进模型创建加权训练数据集。
在这里,我们首先利用我们的能力改变了底层系统的工作机制:在Stripe,我们不控制谁支付,但我们可以创造性地使用支付结果,获取改进欺诈检测方法所需的数据。我们的撤销概率会变化,因为模型的确定性反映了这一方案的业务需求:我们几乎总是阻止在我们看来是欺诈的支付,以便可以做到最有利于依赖我们进行支付的业务。对于数据分析而言,即使是这里最好的解决方案也不是理想的解决方案,我们使用经典的统计方法对其进行纠正。不断地对系统进行聪明的修改,并且我们经常可以调整事后分析,这是解决这类问题的两个要点。
问题2:误差条计算似乎是不可能的
确定任何估计的误差范围都(a)非常重要,因为估计的确定性会在很大程度上影响你稍后针对那个信息采取的行动,(b)相当有挑战性。标准误差公式只能达到这种程度;一旦你将误差条和任何不是平均值的数量放在一起,事情很快就会变得相当复杂。许多标准误差公式还需要相关估计或者协方差估计——对参与计算的数据点之间的关系进行量化——或者假设这些数据点是相互独立的。
我会使用上一部分的一个真实的例子来说明这种挑战:使用逆概率加权数据估计信用卡欺诈模型的召回率。我们希望知道,我们现在在生产环境中使用的模型所阻止的入站欺诈占了多大比例。假如我们有一个包含四列数据的数据框df:支付id、布尔值fraud(说明这笔支付是否真的是欺诈)、布尔值predicted_fraud(说明我们的模型是否将这笔支付识别为欺诈)、weight(我们观察到支付结果的概率)。于是,模型召回率(伪代码)公式如下:
recall = ((df.fraud & df.predicted_fraud).toint * df.weight) / (df.fraud * df.weight)
(注意,& 是 df.fraud 和 df.predicted_fraud 向量上面向元素的逻辑运算,* 是一个矢量点积。)对于那样一个估计量,并没有一个已知的封闭解用于计算置信区间(也就是说,计算误差条的宽)。所幸,有简单的技术可供我们用来绕过那个问题。
误差条估计计算方法几乎适用于任何场景,而且几乎不需要任何前提条件。我最喜欢,也是我现在将要讨论的是 bootstrap ,这是 Brad Efron 于 1979 发明的一项技术。相关论文已经证明,这样计算的置信区间具备你期望置信区间具备的所有数学特性。类似 bootstrap 这样的方法,其最大的缺点是计算密集,但现在是 2017 年了,我们生活在一个计算能力很便宜的世界里,那个在 1979 年有时会让它不可用的原因现如今已经不是问题了。
Bootstrapping 包括使用抽样估计观测数据集变化:我们从原始数据集抽取大量的样本作为替代,每个样本都有相同数量的观察值。然后,我们计算其中每个“bootstrap 样本”的估计指标(召回率)。然后,我们使用估计召回率中第 2.5 个百分位和第 97.5 个百分位作为 95% 置信区间的上下限。下面是 Python 实现的算法,df 是和上例相同的数据框:
from numpy import percentile from numpy.random import randint def recall(df): return ((df.fraud & df.predicted_fraud).toint * df.weight) / (df.fraud * df.weight) n = len(df) num_bootstrap_samples = 10000 bootstrapped_recalls = [] for _ in xrange(num_bootstrap_samples): sampled_data = df.iloc[randint(0, n, size=n)] est_recall = recall(sampled_data) boostrapped_recalls.append(est_recall) ci_lower = percentile(bootstrapped_results, 2.5) ci_upper = percentile(bootstrapped_results, 97.5)
借助类似 bootstrap 和 jackknife 这样的技术,误差条几乎总是可以估计。误差条的估计可能不得不批量进行,而不是实时,但是,我们基本上总是可以计算不确定性的准确度量!
问题 3:黑盒模型的决策需要由人来解释
人们常常将机器学习模型描述为“魔法”或者“黑盒”——重要的是输入什么、输出什么,而不一定是计算过程。有时候,这就是我们想要的:许多用户都不真的需要了解香肠工厂内部的情况,就像他们所说的那样——他们需要的只是美味的管状食物。但是有时候,一个来自魔法盒子的预测并不能让人满意。对于许多生产用机器学习系统而言,理解机器学习模型做出的各个决策至关重要:近日,在 Stripe,对于我们支持的在线业务,我们实现了机器学习模型决策的可视化,也就是说,企业主可以知道哪些因素致使模型做出拒绝或允许支付的决策。
正如我们在简介部分看到的那样,随机森林是黑盒模型的一个典型例子,它们是 Stripe 欺诈模型的核心。随机森林的基本思想是:一个“森林”由一系列多重决策树组成。这些树是通过找出那些可以优化某个分类标准(比如,总体分类精度)的划分或问题(比如,“信用卡的发行国家和使用信用卡的 IP 地址所在的国家是否一致?”)来构造。每个项都会经过所有树,每棵树都会做出决策,将它们“平均”(即树“投票”)就可以得到最终的预测。随机森林灵活、性能良好,在实时估计时速度相当快,所以,它们是生产用机器学习模型的常见选择。但是,对于最终的预测结果,要将多套划分以及树投票转换为直观的解释非常困难。
其实,对于这个问题,有人已经完成了相关的研究,因此,即使那不是机器学习入门课程的一部分,知识体系也已经存在。但是,也许更重要的是,这个问题说明,一个简单的解决方案也可以有很好的效果。再说一次,这是 2017 年;原始运算能力充足而廉价。对于一个黑盒模型,要获取直观的解释,一种方法是编写一个模拟程序:一次改变一个领域特性,看预测如何变化——或者可以改变两个协变量的值,看预测如何变化。另一种方法是(我们在 Stripe 用过一段时间)轮流将其中一个特性视为缺失特性(如果删除特性后出现了“划分”,则非确定性的遍历两条路径),然后重新计算预测结果;对预测结果概率影响最大的特性可以视为最重要的特性。这会产生一些让人困惑的解释,但在我们能够实现一个更正规的解决方案之前,这种方法的效果已经相当不错了,我们现在就使用这种方法来解释通过我们完成的企业支付的处理结果。
据观察,借助我们实现的每一种解决方案,总体上,我们对于做出特定决策的原因的解释有了显著改善。解释也是一个很不错的调试工具,用于发现我们的模型在什么地方犯了系统性错误。能够解决这种问题再一次说明了我们如何利用工具(简单的数学和几台计算机)解决关键业务的机器学习问题,有解决方案即使简单也比没有好。认为“这可以解决”有利于我们实现一个有用的系统,这让我乐观地认为,我们有能力从我们所拥有的数据中获取我们想要的东西。
我很乐观的其他原因
我对使用数据解决重要的问题充满希望。除了简介中的示例以及上面提到的三个问题,我还想到了其他几种简洁、简单、聪明的问题解决方法:
- 难处理的数据集:如果数据集太大,无法装入内存,或者计算因为数据量慢得过分,则可以降低采样率。许多问题都可以使用数据集的样本得到合理的解释(而且,有非常多的统计技术是开发用来获得随机样本的);
- 样本数据集中缺少小概率事件数据:例如,我经常获得不包含任何欺诈的支付数据集随机样本,因为欺诈是小概率事件。这里有一个策略,就是取得原始数据集中的所有欺诈,降低非欺诈数据的采样率,在最终分析时使用样本权重(类似上面讨论过的逆概率权重);
- 使用漂亮、聪明的计算技巧计算计算密集型变量:关于这一点,指数加权移动平均就是一个很好的例子。众所周知,移动平均计算非常困难(因为你必须保留相关窗口中的所有数据点),但是,虽然指数加权移动平均的思想一样,但使用了聚合,所以要快许多。 HyperLogLogs 是全时数量的一个很好的近似,并且不需要扫描整个数据集,相应的,HLLSeries 在特定的窗口中发挥了类似的作用。这些策略全都是近似,但机器学习反正就是近似。
这些数据驱动的方法可以克服我们每天在机器学习实践中面临的挑战。
关于作者
Alyssa Frazee是 Stripe 的一名机器学习工程师,她负责构建模型检测信用卡在线交易中的欺诈。在加入 Stripe 之前,她攻读了生物统计学博士,并在 Recurse Center 喜欢上了编程。她的 Twitter 账号为 @acfrazee。
评论