GA2Ms 的温和介绍,白盒模型
这篇文章温和地介绍了一个叫做 GA2M 的白盒机器学习模型。
我们将走过:
- 什么是白盒模型,您为什么想要白盒模型?
- 一个经典的白盒模型:逻辑回归
- 什么是 GAM,你为什么想要 GAM?
- GA2M 是什么,您为什么想要它?
- 什么时候应该选择 GAM,GA2M,还是别的?
所有这些机器学习模型的目的是对人类指定的目标做出预测。想一个模型,可以预测贷款违约,或在照片中出现某人的脸。
简而言之:广义加性模型(GAM)是一个白盒模型,比逻辑回归更灵活,但仍然可以解释。GA2M 是一个带有交互项的 GAM,这使得它更加灵活,但是解释更加复杂。gam 和 GA2Ms 是工具箱中一个有趣的附加物,以不适合每种数据为代价进行解释。一张图:
关于这一切意味着什么的更多信息,请继续阅读。
白盒模型
术语“白盒来自软件工程。它意味着你可以看到软件的内部结构,相比之下,它是一个“黑匣子”,你不能看到它的内部结构。根据这个定义,如果你能看到权重(图片来源),一个神经网络可以是一个白盒模型:
然而,人们所说的“白盒”实际上是指他们能够理解的东西。白盒模型是一种人们可以看到其内部并对其进行推理的模型。这是主观的,但大多数人会同意上面显示的权重没有给我们提供关于模型如何工作的信息,因为我们可以有效地描述它,或预测模型在未来会做什么。
将上面的图片与这张关于肺炎死亡风险的图片进行比较[1]:
这不是一个完整的模型。相反,它是一个特征(年龄)对风险分值的影响。绿线为误差线(100 轮装袋中的 1 个标准差)。它们中间的红线是最好的估计。在论文中,他们观察到:
- 直到 50 岁左右,风险评分一直持平。这里的风险评分为负,意味着死亡风险低于数据集中的平均值。
- 风险评分在 65 分急剧上升。这可能是因为退休。将来,收集关于退休的数据可能会很有趣。
- 误差线在 66-85 岁之间最窄。也许这是数据最多的地方。
- 风险评分再次上升至 85。误差线也再次变宽。也许这一跳不是真的。
- 风险分值降至 100 以上。这可能是因为缺乏数据,或者其他原因。在论文中,他们提出,人们可能希望“修复”模型的这一区域,通过改变它来预测 85-100 岁的水平,而不是下降。这个修正是使用领域知识(“85 岁以后肺炎的风险可能不会下降”)来解决可能的模型工件。
- 66 到 85 之间的风险得分相对较低。
所有这些都来自一个模型特征的一个图表。有一些事实,比如图表的形状,以及对图表为什么会那样的推测。这些事实有助于理解这些数据。这种推测无法通过任何工具来回答,但可能有助于建议进一步的行动,如收集新的特征(比如退休)或新的实例(比如年龄低于 50 岁或高于 100 岁的点),或新的分析(比如仔细查看年龄在 85-86 岁左右的数据实例以找出差异)。
这些并不是模型会做的模拟。这些是模型本身的内部因素,所以图表准确地描述了年龄对风险评分的确切影响。这个模型还有 55 个其他组件,但是每个组件都可以被检查和推理。
这就是白盒模型的力量。
这个例子也说明了危险。通过看到一切,我们可能认为我们理解了一切,并胡乱猜测或不恰当地“修复”。一如既往,我们必须运用判断力来正确使用数据。
总之:制作一个白盒模型来
- 了解你的模型,不是从模拟或近似,而是实际的内部
- 改善你的模型,给你方向追求的想法
- “修复”你的模型,即,让它与你的直觉或领域知识保持一致
最后一种可能性:法规规定您需要完整地描述您的模型。在这种情况下,有人类可读的内部参考可能是有用的。
以下是一些白盒和黑盒模型的示例:
白盒模型
- 逻辑回归
- GAMs
- GA2Ms
- 决策树(短树和少树)
黑盒模型
- 神经网络(包括深度学习)
- 助推树木和随机森林(许多树木)
- 支持向量机
现在,我们来看三种特定的白盒模型。
经典:逻辑回归
逻辑回归发展于 19 世纪初,20 世纪初重新普及。它已经存在很长时间了,原因有很多。它解决了一个常见的问题(预测事件的概率),并且是可解释的。让我们探索一下这意味着什么。下面是定义该模型的逻辑方程式:
在这个模型方程中有三种类型的变量:
- p 是我们预测的事件的概率。例如,拖欠贷款
- x 是特征。例如,贷款金额。
- 𝛽’s(贝塔)是我们用计算机拟合的系数。
betas 适合整个数据集一次。对于数据集中的每个实例,x 是不同的。p 表示数据集行为的集合:任何数据集实例要么发生了(1),要么没有发生(0),但是在集合中,我们希望右侧和左侧尽可能接近。
“log(p/(1-p))”是对数几率,也称为“概率的 logit”。几率是(事件发生的概率)/(事件不会发生的概率),或者 p/(1-p)。然后,我们应用自然对数将取值范围为 0 到 1 的 p 转换为适用于线性模型的范围从-∞到+∞的量。
这个模型是线性的,但是对于对数概率来说。也就是说,右边是一个线性方程,但它适合于对数赔率,而不是事件的概率。
该模型可解释如下:在𝛽i,xi 的单位增长是对数优势增长。
例如,假设我们正在预测贷款违约的概率,我们的模型有一个贷款金额特征 x1 的特征系数𝛽1=0.15。这意味着在默认情况下,该特征的单位增加对应于 0.15 的对数赔率增加。我们可以取自然指数得到比值比,exp(0.15)=1.1618。这意味着:
在这个模型中,在其他因素不变的情况下,贷款金额每增加一个单位(比如说 1000 美元),贷款违约的几率就会增加 16%。
当人们说逻辑回归是可解释的时,这种说法就是他们的意思。
总结一下为什么逻辑回归是一个白盒模型:
- 输入响应项(𝛽i*xi 项)可以相互独立地解释
- 术语以可解释的单位表示:系数(β)以对数概率为单位。
那么,除了友好、古老的逻辑回归模型,我们为什么还要使用其他模型呢?
嗯,如果特征和对数概率没有线性关系,这个模型就不太适合。我总是想尝试用一条线来拟合一条抛物线:
No line fits a curve.
如果你有非线性数据(黑色抛物线),线性拟合(蓝色虚线)永远不会很好。没有一条线符合这条曲线。
广义可加模型
广义加性模型 (GAMs)由 Hastie 和 Tibshirani 于 20 世纪 90 年代开发。(参见他们的书第九章“统计学习的要素”。)下面是定义该模型的等式:
这个方程非常类似于逻辑回归。它具有相同的三种类型的元素:
- E(Y)是数据集行为的集合,就像上面等式中的“p”。其实很可能是一个事件的概率,同 p。
- g(。)是一个链接函数,就像上面的逻辑方程式中的 logit(或 log odds)。
- fi(xi)是每个数据集实例特征 x1,…,xm 的术语。
最大的不同是,现在我们有一个函数 fi(xi ),而不是一个特征的线性项𝛽i*xi。在他们的书中,Hastie 和 Tibshirani 指定了一个像三次样条一样的“平滑”函数。Lou 等人[2]研究了 fi 的其他功能,他们称之为“形状功能”
GAM 还具有白盒特性:
- 输入响应项(f(xi)项)可以相互独立地解释
- 这些术语是以可解释的单位表示的。对于 logit 链接函数,这些是对数概率。
现在,一个项,而不是一个常数(β),是一个函数,所以不是用数字来报告对数几率,我们用一个图表来形象化它。事实上,上图中按年龄划分的肺炎死亡风险是 GAM 中的一项(形状函数)。
那我们为什么要用 GAM 以外的东西呢?它已经是灵活的和可解释的了。和之前一样的原因:可能不够准确。特别是,我们已经假设每个特征响应可以用它自己的函数建模,与其他的无关。
但是如果特征之间有交互作用呢?一些黑盒模型(提升树、神经网络)可以对交互项进行建模。让我们通过一个白盒模型来看看:GA2Ms。
具有交互条款的 gam(GA2Ms)
Lou 等人在 2013 年对 GA2Ms 进行了研究[3]。作者用字母“gee ay two em”来发音,但在室内我们称它们为“互动游戏”,因为它更容易发音。下面是模型方程:
这个方程与上一节的 GAM 方程非常相似,只是它增加了可以同时考虑两个特征变量的函数,即相互作用项。
微软刚刚发布了一个用 python 实现 GA 2Ms 的库 InterpretML 。在图书馆里,他们称之为“可解释的助推机器”
Lou 等人说,这些仍然是白盒模型,因为相互作用项的“形状函数”是热图。这两个特征是沿 X 和 Y 轴的,中间的颜色表示函数响应。下面是一个来自微软图书馆的例子,适合预测的贷款违约,这个数据集来自 lending club 的贷款表现:
对于此示例图:
- 右上角是最红的。这意味着,当 dti(债务收入比)和 FICO _ range _ midpoint(FICO 信用评分)都很高时,违约概率上升最多。
- 左边的条纹也是红色的,但在底部附近变成蓝色。这意味着非常低的 dti 通常是不好的,除非 fico_range_midpoint 也很低。
这个特殊的热图很难解释。这可能只是没有单一特征项的交互作用效应。因此,在高 dti 和高 fico 的情况下,整体违约概率可能并不更高,而只是高于他们自己预测的任何一种主要影响。为了进一步研究,我们可以看看边界附近的一些例子。但是,对于这篇博文,我们将跳过深潜。
在实践中,这个库适合所有的单特征函数,然后是 N 个交互项,其中你选择 N 个。选择 N 个并不容易。如果交互项增加了足够的准确性,值得花额外的复杂性盯着热图来解释它们,那么交互项是值得的。这是根据你的业务情况做出的判断。
什么时候应该用 GAMs 或者 GA2Ms?
要执行机器学习,首先挑选一个目标。然后选择一种最能利用您的数据来实现目标的技术。有成千上万本书和数百万篇论文是关于那个主题的。但是,考虑 GA2Ms 如何适应可能的模型技术,这里有一个大大简化的方法:它们在从可解释性到建模特性交互的范围内。
- 如果游戏足够精确,就使用游戏。它给出了白盒模型的优点:带有可解释单元的可分离项。
- 如果 GA2Ms 明显比 gam 更准确,就使用 GA2Ms,特别是如果您从您的领域知识中相信存在真实的功能交互,但它们并不太复杂。这也给出了白盒模型的优势,用更多的精力去解读。
- 如果您不太了解数据,可以尝试 boosted trees (xgboost 或 lightgbm ),因为它对数据中的异常非常健壮。这些是黑盒模型。
- 当特征彼此高度交互时,比如图像中的像素或音频中的上下文,你可能很需要神经网络或其他可以捕捉复杂交互的东西。这些都是深深的黑匣子。
在所有情况下,您可能都需要特定领域的数据预处理,比如对图像求平方,或者标准化特征(减去平均值并除以标准偏差)。那是另一天的话题。
现在希望我们开始的图表更有意义。
在 Fiddler Labs ,我们帮助你解释你的人工智能。请发邮件至 info@fiddler.ai 联系我们。
参考
- 卡鲁阿纳、里奇、尹露、约翰内斯·盖尔克、保罗·科赫、马克·斯特姆和小糯米·艾尔哈达德。"医疗保健的可理解模型:预测肺炎风险和住院 30 天再入院."第 21 届 ACM SIGKDD 知识发现和数据挖掘国际会议论文集,1721–1730。KDD 15 年的。美国纽约州纽约市:美国计算机学会,2015 年。【https://doi.org/10.1145/2783258.2788613】T4。
- 卢、尹、里奇·卡鲁阿纳和约翰内斯·戈尔克。"可理解的分类和回归模型."第 18 届 ACM SIGKDD 知识发现和数据挖掘国际会议论文集,150–158。12 年的 KDD。美国纽约州纽约市:美国计算机学会,2012 年。https://doi.org/10.1145/2339530.2339556。
- 娄、尹、里奇·卡鲁阿纳、贾尔斯·胡克和约翰内斯·戈尔克。具有成对交互的精确可理解模型,2017。https://www . Microsoft . com/en-us/research/publication/accurate-understand-models-pairwise-interactions/。
原载于 2019 年 6 月 3 日https://blog . fiddler . ai。
通过线性回归对梯度下降的温和介绍
Génesis | © Sebastião Salgado
为了对我们周围的现实的错综复杂的本质有所了解,为了理解事件或主题之间的潜在关系,或者甚至为了评估特定现象对任意事件的影响,我们必须将现实旋绕到信息维度中,试图利用我们人类的抽象来以某种方式把握我们周围实际事物的本质。
在本文中,我们将简要分析一个简单的统计工具,该工具允许我们根据一组观察值、评估我们如何利用一组变量来适当地生成一个模型,该模型将通过推断变量关系和相互影响来预测或预报行为。这个统计工具叫做线性回归。
为了帮助最小化与预测模型相关的误差,优化它以更好地代表现实,我们还将简要展示梯度下降优化算法的简单应用。
本文假设只有基本的代数和微积分知识,因为这两个主题都很简单,但却代表了现代统计学和机器学习的两个基础学科。
方案
让我们考虑一个简单的例子,从 StackOverflow 的“2018 年开发者工资”文章中抽取一些值。下面我们可以看到一个代表平均工资点的小数字,以及随着开发人员经验的增加,德国的平均工资点是如何变化的:
Figure 1) Median yearly salaries for developers, in thousands of euros, by experience in Germany (2018)
基于上述数据点,我们希望开发一个简单的模型函数,使我们能够预测在任何给定的经验时间点工资如何演变。
线性回归
然后,线性函数可以由简单的表达式定义:
常数 m 代表函数线的斜率,常数 b 通常称为截距。下面的一些例子展示了 (m,b) 的不同值:
Figure 2) Three examples of slopes and intercepts for a linear function
在上面的例子中,我们可以看到,改变 m 会影响结果值的斜率,也会影响截距 b ,它会在穿过 x = 0 时修改函数值。
在我们当前的方案中,由于工资的变化实际上并不是由线性级数表示的,然而,查看图 1 中的数据形状,通过在积分级数中拟合一条线来近似预测的最终工资是可以接受的,其方式为:
最后,我们的模型将试图通过某种方式调整 m 和 b ,将自己表示为逼近我们的参数薪水和经验的演变的线,例如,它将允许我们获得类似如下的结果:
Figure 3) Possible linear model used to predict the median salaries
对于代表我们参数之间关系的基本模型的“线性”表示,我们可以称之为线性回归。
我们错误的代价
既然我们已经确定了我们将使用什么形状来生成我们的模型,那么我们必须试着弄清楚***【m,b】***的值是什么,它们将更好地描述我们的数据预测模型的发展。
但是我们如何选择 m 和 b 的值来生成我们要寻找的线呢?一种方法是计算我们的模型生成的值和我们现有的实际数据之间的误差。
仅用于表示目的的简单方法可以是:
- 我们知道,一个拥有大约10 年左右经验*的开发人员挣得大约72K欧元的年薪* (1)****
- 从示例斜率和截距 (m,b) = (3,35) 开始
一个样本误差函数,对于这个 10 年经验的特定数据点 (x=10) ,我们的误差 E 为:
对于我们现有的所有样本数据点,我们可以计算误差,该误差考虑了我们的预测和真实值之间的所有误差之和,从而得到如下函数:
使用:
- n 是我们数据集的总样本
- y 为具体观察的实际薪资值
- x 是我们要为 y 预测的经验年数
对于每个观察值与其组均值之间的平方差之和,这将代表我们的误差函数*(或成本函数,我们将每个观察值与其组均值之间的平方差之和命名为 : 平方差之和(SSE)。在统计学中,这个均方差对于评估我们的预测值相对于真实观测值的“质量”非常有用。***
但是我们如何继续寻找 m 和 b 的合适值呢?一种直观的方法可能是,因为我们现在能够计算一个误差函数*,找到最小化该函数的一对 (m,b) 。如果是这样的话,我们就可以清楚地表明,我们的预测产生的误差最小,因此更接近现实。***
然后,让我们为 (m,b) 选择两个随机值,计算成本函数,然后改变这些值,以试图找到我们的误差函数的最小值。让我们首先考虑(m,b) = (3,0),我们的数据点来自图 1,我们得到下面的图形结果:
Figure 4) Initial prediction and errors for (m, b) = (3, 0)
从上图我们可以看出:
- 绿点代表我们的工资观察数据值****
- 蓝线是我们的预测模型* ( y = 3 年经验+ 0 )**
- 红色虚线表示当前参数 (m,b) 的错误****
对于这组特定的截距和斜率,现在让我们计算现有观测值的累积成本:
现在让我们将斜率值固定为 3,但是将截距增加到 20, (m,b) = (3,20) 。我们将获得以下表示:
Figure 5) Prediction iteration 1, and errors for (m, b) = (3, 20)
与 相关的成本 E = 232.5 。我们可以清楚地看到,通过更新我们的截距,我们已经改善了我们的预测,因为误差大幅下降。现在让我们针对不同的截距值绘制多种场景:
Figure 6) Computing the errors by varying the value of the intercept
从上图可以看出,随着截距值 b 的增加,我们还可以观察到成本函数的变化。在这个具体的例子中,用 (m,b) = (3,30) 确定粉红色线是我们观察值的更准确预测,因为它也具有更低的成本值。
通过绘制通过改变截距值获得的误差成本函数的变化,我们得到了下图:**
Figure 7) Evolution of the cost function when changing the intercept value
我们可以清楚地看到,当改变 b 的值时,考虑到我们的误差函数是凸的,我们能够找到一个局部最小值,它将代表我们的预测模型的最小误差。在上面这个简单的演示中,可以清楚地说明使我们的误差最小的截距 b 在【30,40】之间。不幸的是,用预先定义的步骤简单地迭代,以找到这个最小值是非常昂贵和耗时的。
但是我们怎样才能更巧妙的计算出我们的成本函数的最小值呢?然后我们将使用梯度下降算法。**
梯度下降
梯度下降是一种迭代优化算法,允许我们找到特定函数的局部最小值。
解释这种算法背后的逻辑的一个很好的例子,也是文献中反复出现的,是盲人登山家的例子。让我们想象一下,一个盲人登山运动员想用最少的步骤爬到山顶:**
Figure 9) Sequence of steps a smart blind alpinist would take to climb a mountain
由于登山运动员是盲人,他将评估他当前位置的倾向,以便选择他下一步应该采取的幅度:
- 如果他当前位置的山(坡)的倾斜度很高,他可以安全地迈出一大步(例如我们可以注意到从第一步到第二步的过渡)**
- 当坡度越来越小时,当他到达山顶时,他知道他需要迈更小的步子,以便接近精确的最高点(当登山运动员越来越接近山顶时,从第 6 步到第 7 步,他更加小心如何增加他的位置)
- 对于正坡,他需要继续向上到达顶部
- 对于负斜率,他正在下降,所以他需要回到顶部
给定点处“山”的斜率则由该函数在特定点处的导数给出:
Figure 10) Slope of the function at a given point
因此,通过计算我们的“山函数”在某一点的导数,我们就可以推断出为了适当地达到我们的局部最小值,我们将需要的步骤的性质。通过达到接近 0 的斜率(顶部的黄色斜率,与山起点的蓝色斜率值相比)。
通过将登山挑战从到达山顶切换到实际到达谷底,理解对于该函数的凸形版本同样有效也是微不足道的:
Figure 11) Iterative finding of our local minimum for a convex function (or a valley in this alpine example)
回到我们的研究案例,考虑到我们想要正确估计使成本最小化的斜率和截距的值,我们可以使用这个概念来最小化凸函数,它实际上是我们的成本函数。
为了简单起见,让我们首先通过保持斜率固定在 m = 3 来预测截距的实际值。我们的成本函数是:
现在我们知道了这条曲线的方程,我们可以对它求导,并确定它在截距的任意值处的斜率。**
现在让我们使用链式法则,根据截距计算成本函数的导数:
现在我们已经正确地计算了导数,我们现在可以使用梯度下降来寻找我们的成本函数在哪里有它的局部最小值。
通过找到导数(斜率)为 dE(b)/db = 0 的位置来计算这个特定的最小值确实是微不足道的。然而,这在许多计算问题中是不可能的。因此,我们将应用梯度下降,从最初的猜测开始,了解这个最小值的性质。当我们无法计算导数时,这种多功能性实际上使得这种优化算法在许多情况下如此有用,例如现代机器学习问题。
学习正确的价值观
现在我们有了导数函数,让我们首先计算截距 b 的随机值的斜率,例如:
由此我们知道,当截距为 0 时,在我们的成本函数上,该点切线的斜率为 -69 。一旦我们接近函数的最小值,这个斜率也将接近 0。
从我们在阿尔卑斯山的例子中,我们明白了我们应该采取的步骤的大小应该在某种程度上与给定点的斜率有关。这具有的目标,即当斜率较高且我们远离最小值时给出“较大”的步长,而当我们越来越接近零斜率时给出“较小”的步长*。***
当我们迭代地进行这个过程时,只需要我们采用上面描述的具有实际所需步长的图像,并在每次迭代中调整它们。我们将使用这个常数来实际调整步长,我们称之为学习速率*。考虑到这一点,我们可以定义以下表达式,以在每次迭代中生成和调整我们的步长:***
假设学习率为 0.2,我们将获得以下步长:**
考虑到我们的新步长,我们可以安全地将下一次迭代截距计算为实际步长:**
因此,对于我们的第一次迭代,我们有:
对于这个新的截距值,我们可以看到误差函数的斜率由下式给出:
随着斜率越来越接近 0,我们就可以理解,仅仅通过进行第一次迭代,我们实际上就越来越接近最佳值。通过重新查看图 6,我们确实可以推断,通过将截距从 0 增加到更大的值,我们确实可以减少估计值和实际观测值之间的残余误差。
通过几次迭代,我们得到:
*****Step Size(2)** = -41.4 * 0.2 = -8.28
**b(2)** = 13.8 - (-8.28) = 22.08
**dE(22.08)/db** = -24.8**Step Size(3)** = -24.8 * 0.2 = -4.96
**b(3)** = 22.08 - (-4.96) = 27.04
**dE(27.04)/db** = -14.8**Step Size(4)** = -14.8 * 0.2 = -2.96
**b(4)/db** = 27.04 - (-2.96)= 30
**dE(30)/db** = -9***
我们可以从这 3 次迭代中验证以下内容:
- 每走一步,我们都在接近一个更小的绝对斜率
- 当我们接近 0°斜率时,我们通过保持相同的学习速率来做更小的步骤
从视觉上我们可以看到,每一次迭代,我们都离数据集的预测线越来越近,实际上步长越来越小:
Figure 13) Applying the iteration values from the gradient descent for the intercept
为了适当地停止迭代,对于某个可接受的值,应该:
- 决定每次迭代的最小步长,例如,如果步长小于 0.001,则停止
- 当我们达到一定的迭代次数时停止
通过应用这些规则,我们可以验证算法在几次迭代后停止:
*****[+] Iteration 5:****Step Size** = -0.592
**b =** 30.592
**dE/db** = -7.8160000000000025**[+] Iteration 6:****Step Size** = -0.1184
**b = ** 30.7104
**dE/db** = -7.579200000000007*(...)***[+] Iteration 9:****Step Size** = -0.0009472000000000001
**b =** 30.7397632
**dE/db** = -7.5204736000000025***
以截距 b = 30.7397632 稳定。当绘制时,我们得到:
Figure 14) Using the stabilized predicted value given for a fixed slope to our intercept
通过这种方法,我们可以验证,通过渐进迭代(以基于学习洞穴的适应步长的速度),我们确实可以接近误差成本函数的最小化,如所绘制的,获得进化的非常接近的模型表示。这是通过简单地预测一个参数,截距来实现的。在接下来的部分,我们将试图理解这两个变量的模型的演变。
进入新的维度
既然我们已经了解了如何为我们的模型估计截距值,现在让我们向一维之外移动一步,并将梯度下降应用于截距和斜率*。***
首先,如前一节所述,我们将使用链式法则,根据截距*,计算成本函数的导数:***
现在,我们可以根据斜率继续寻找误差函数的偏导数:
对于偏导数的集合,对于这个函数的所有维度,我们称之为梯度:****
然后,我们将使用这个梯度,如在前面的部分,然后找到我们的误差函数的局部最小值。这就是称这个算法为梯度下降的原因。**
为了做到这一点,我们需要推断我们在上一节中为 intersect 所做的事情,以实际预测这两个值,并调整它们自己的相互依赖性。就这样暴露出来:
- 当我们现在接近两个变量时,这个问题可能再次类似于爬山,但是具有额外的复杂性。你需要调整双脚在墙上运动的速度,但也需要调整一个独特的手握动作的速度。因此,保持相同的学习速率*,我们需要调整两个步长,一个用于斜率,另一个用于交点:***
- 使用新的步长,我们可以在每次迭代 n 中获得两个变量的当前预测:
- 然后,我们将再次计算梯度(即两个变量的导数,带有更新的值):
- 重复整个过程,直到我们达到迭代过程的选定极限。我们将继续使用步长限制。
为了实现这个小算法,我们还需要调整所有的初始值。还应该确定一个适当的停止值限制。以我们的例子为例,让我们决定:
- 初始相交为*b = 30*******
- 初始斜率为 m = 3
- 我们的学习率将会是 0.001
- 当学习率达到 0.00001 时,我们将停止
这可以用这个简单的 python 脚本来表示:
运行这个简单的脚本,我们获得以下输出:
然后获得我们两个变量的下列预测值:
- 坡度(米)= 2.5101
- 相交(b) = 40.5478
将这些值应用于包含误差传播的先前图,我们得到以下结果:
Figure 15) Using the stabilized predicted value, based on the computed slope and intercept, compared with the initial value
从上图中我们可以看到,我们的(线性)预测现在更接近于预测模型数据,然后我们可以使用我们的新模型来实际推断这两个参数之间的关系。
结论
我们可以通过使用线性回归来尝试对一组参数的自然关系做出初步预测,甚至获得一个简单的进化模型。在本文中,我们尝试使用这种简单的数值方法来清楚地展示梯度下降算法的基本功能,以及我们如何通过尝试最小化收敛误差函数来实现预测的迭代优化。
尽管它所拥有的概念可能会在概念和数学上表现得非常简单,但它们是深度学习和神经网络的基础之一。
图形神经网络的简单介绍(基础、深走和 GraphSage)
Image from Pexels
最近,图神经网络(GNN)在各个领域越来越受欢迎,包括社会网络、知识图、推荐系统,甚至生命科学。GNN 在建模图中节点之间的依赖性方面的能力使得与图分析相关的研究领域取得突破。本文旨在介绍图神经网络的基础知识和两个更高级的算法,DeepWalk 和 GraphSage。
图表
在我们进入 GNN 之前,让我们先了解一下什么是图。在计算机科学中,图是由顶点和边两部分组成的数据结构。一个图 G 可以由它包含的顶点 V 和边 E 的集合很好地描述。
边可以是有向的,也可以是无向的,这取决于顶点之间是否存在方向依赖。
A Directed Graph (wiki)
顶点通常被称为节点。在本文中,这两个术语可以互换。
图形神经网络
图形神经网络是一种直接作用于图形结构的神经网络。GNN 的一个典型应用是节点分类。本质上,图中的每个节点都与一个标签相关联,我们希望在没有事实的情况下预测节点的标签。本节将举例说明论文中所描述的算法,它是 GNN 的第一个提议,因而通常被视为 GNN 的原创。
在节点分类问题设置中,每个节点 v 通过其特征 x_v 来表征,并且与地面事实标签 t_v. 相关联。给定部分标记的图 G ,目标是利用这些标记的节点来预测未标记的标签。它学习用包含其邻域信息的 d 维向量(状态) h_v 来表示每个节点。具体来说,
https://arxiv.org/pdf/1812.08434
其中 x_co[v] 表示与 v 连接的边的特征,h_ne[v] 表示 v 的相邻节点的嵌入, x_ne[v] 表示v的相邻节点的特征,函数 f 是将这些输入投影到 d 维空间的转移函数。由于我们正在寻找 h_v 的唯一解,我们可以应用 Banach 不动点定理并将上述方程重写为迭代更新过程。这种操作通常被称为消息传递或邻域聚合。
https://arxiv.org/pdf/1812.08434
h 和 X 分别表示所有 h 和 X 的连接。
通过将状态 h_v 以及特征 x_v 传递给输出函数 g 来计算 GNN 的输出
https://arxiv.org/pdf/1812.08434
这里的 f 和 g 都可以解释为前馈全连接神经网络。L1 损耗可以直接用公式表示如下:
https://arxiv.org/pdf/1812.08434
这可以通过梯度下降来优化。
然而,本文指出 GNN 的这一原始提案有三个主要局限性:
- 如果放松“不动点”的假设,就有可能利用多层感知器来学习更稳定的表示,并消除迭代更新过程。这是因为,在最初的提议中,不同的迭代使用转移函数 f 的相同参数,而 MLP 的不同层中的不同参数允许分层特征提取。
- 它不能处理边信息(例如,知识图中的不同边可以指示节点之间的不同关系)
- 不动点会阻碍节点分布的多样化,因此可能不适合学习表示节点。
已经提出了 GNN 的几种变体来解决上述问题。然而,他们没有被涵盖,因为他们不是这篇文章的重点。
深度行走
DeepWalk 是第一个提出以无监督方式学习的节点嵌入的算法。就训练过程而言,它非常类似于单词嵌入。其动机是图中节点和语料库中单词的分布都遵循如下图所示的幂定律:
http://www.perozzi.net/publications/14_kdd_deepwalk.pdf
该算法包含两个步骤:
- 对图中的节点执行随机行走以生成节点序列
- 运行 skip-gram,根据步骤 1 中生成的节点序列学习每个节点的嵌入
在随机游走的每个时间步,从前一个节点的邻居均匀地采样下一个节点。然后,每个序列被截断成长度为 2|w| + 1 的子序列,其中 w 表示跳跃图中的窗口大小。如果你不熟悉 skip-gram,我之前的博客文章将向你简要介绍它是如何工作的。
本文采用层次化的 softmax 来解决 softmax 由于节点数量巨大而导致的计算量大的问题。为了计算每个单独输出元素的 softmax 值,我们必须计算所有元素 k 的所有 e^xk
The definition of Softmax
因此,对于原始的 softmax,计算时间是 O(|V|) ,其中 V 表示图中的顶点集。
分层 softmax 利用二叉树来处理这个问题。在这个二叉树中,所有的叶子(上图中的 v1,v2,… v8)都是图中的顶点。在每个内部节点中,有一个二元分类器来决定选择哪条路径。为了计算给定顶点 v_k 的概率,简单地计算沿着从根节点到叶子 v_k 的路径的每个子路径的概率。由于每个节点的子节点的概率总和为 1,所以所有顶点的概率总和等于 1 的性质在分层 softmax 中仍然成立。元素的计算时间现在减少到了 O(log|V|) ,因为二叉树的最长路径由 O(log(n)) 限定,其中 n 是叶子的数量。
Hierarchical Softmax (http://www.perozzi.net/publications/14_kdd_deepwalk.pdf)
在对 DeepWalk GNN 进行训练之后,该模型已经学习了每个节点的良好表示,如下图所示。不同的颜色表示输入图中不同的标签。我们可以看到,在输出图(二维嵌入)中,具有相同标签的节点聚集在一起,而大多数具有不同标签的节点被适当分离。
http://www.perozzi.net/publications/14_kdd_deepwalk.pdf
然而,DeepWalk 的主要问题是它缺乏泛化能力。每当一个新的节点进来,它必须重新训练模型,以便表示这个节点(转导)。因此,这种 GNN 不适用于图中节点不断变化的动态图。
图表法
GraphSage 提供了一个解决上述问题的方案,以一种归纳的方式学习每个节点的嵌入。具体来说,每个节点由其邻域的聚合来表示。因此,即使一个在训练期间看不见的新节点出现在图中,它仍然可以由它的相邻节点适当地表示。下面显示了 GraphSage 的算法。
https://www-cs-faculty.stanford.edu/people/jure/pubs/graphsage-nips17.pdf
外环表示更新迭代的次数,而 h^k_v 表示在更新迭代 k 时节点 v 的潜在向量。在每次更新迭代中, h^k_v 基于聚合函数、前一次迭代中 v 和 v 的邻域的潜在向量以及权重矩阵 W^k 进行更新。论文提出了三个聚合函数:
1。平均聚合器:
均值聚合器取一个节点及其所有邻域的潜在向量的平均值。
https://www-cs-faculty.stanford.edu/people/jure/pubs/graphsage-nips17.pdf
与原来的等式相比,它删除了上面伪代码中第 5 行的串联操作。这种操作可以被看作是一种“跳过连接”,这在本文的后面部分被证明可以极大地提高模型的性能。
2。LSTM 聚合器:
由于图中的节点没有任何顺序,它们通过置换这些节点来随机分配顺序。
3。汇集聚合器:
该操作符对相邻集执行元素池功能。下面显示了最大池的一个示例:
https://www-cs-faculty.stanford.edu/people/jure/pubs/graphsage-nips17.pdf
,可以用均值池或任何其他对称池函数来代替。指出池式聚合器性能最好,而平均池式和最大池式聚合器性能相近。本文使用 max-pooling 作为默认的聚合函数。
损失函数定义如下:
https://www-cs-faculty.stanford.edu/people/jure/pubs/graphsage-nips17.pdf
其中 u 和 v 在定长随机游走中同现,而 v_n 为不与 u 同现的负样本。这种损失函数鼓励距离较近的节点具有相似的嵌入,而距离较远的节点在投影空间中被分离。通过这种方法,节点将获得越来越多的关于其邻居的信息。
GraphSage 允许通过聚集其附近的节点来为看不见的节点生成可表示的嵌入。它允许将节点嵌入应用于涉及动态图的领域,其中图的结构是不断变化的。例如,Pinterest 采用了 GraphSage 的扩展版本 PinSage ,作为其内容发现系统的核心。
结论
您已经学习了图形神经网络、DeepWalk 和 GraphSage 的基础知识。GNN 在建模复杂图形结构方面的能力确实令人惊讶。鉴于其有效性,我相信,在不久的将来,GNN 将在人工智能的发展中发挥重要作用。如果你喜欢我的文章,别忘了在 Medium 和 Twitter 上关注我,在那里我经常分享 AI、ML 和 DL 的最先进的发展。
物联网/GPS 轨迹聚类和地理空间聚类简介
当谈到开发增值主张时,物联网、互联设备和地理空间数据都是热门话题。在这篇文章中,我将温和地介绍应用于 GPS 轨迹的无监督学习技术。
数据来自微软亚洲研究院,包含 2007 年 4 月至 2012 年 8 月间 182 名用户和 17621 次旅行的数据。数据大多是密集的,每 1 到 5 秒记录一次经度和纬度。
数据准备和清理
现在让我们来看看数据文件本身。嗯。plt '格式,一个我没见过的格式,不过还是用文本编辑打开吧。
好极了,看起来熊猫 read_csv 函数可以很好地与。plt 文件格式,附带的关于数据结构的文档看起来很准确。
import pandas as pdcolnames = ['lat','long', 'null', 'alt' ,'DateTime','Date','Time']df = pd.read_csv('20090724005608.plt', skiprows=6, names = colnames)
将时间序列放在一个数据框中很好,但是我们需要将每个数据框转换成具有多个特征的单个观察值。这样做将格式化数据,以便加载到聚类分析算法中。让我们合计速度、加速度和加加速度的第 10、25、50、75、90 和平均值。为此,我们将编写几个 python 函数来应用于每个文件。
def add_feat(df_input):
"""
This function takes in raw lat long time series from the microsoft geolife data.Preprocessing notes: skip the first six lines before doing pandas read csv , expecting columns in ['lat','long', 'null', 'alt' ,'DateTime','Date','Time']Requres: pandas imported as pd
from vincenty import vincenty
Adds:
speed
acceleration
jerk
bearing rate
distance travelled
"""
df = df_input
# add some initial shifts
df['lat_shift'] = df.lat.shift(-1)
df['long_shift'] = df.long.shift(-1)
df['time_shift'] = df.DateTime.shift(-1)
# add speed
def speed(x):
try:
s = vincenty((x[-3],x[-2]),(x[0],x[1]), miles = True) / ((x[-1]-x[4]) * 24)
except:
s= np.nan
return s
df['speed_mph'] = df.apply(speed,axis =1)
df['speed_shift'] = df.speed_mph.shift(-1)
# add acceleration
def accel(x):
try:
a = (x[-1] - x[-2]) / ((x[9] - x[4]) *24*60*60)
except:
a = np.nan
return a
df['acceleration'] = df.apply(accel, axis =1)
df['acceleration_shift'] = df.acceleration.shift(-1)
# add jerk
def jerk(x):
try:
j = (x[-1] - x[-2]) / ((x[9] - x[4]) *24*60*60)
except:
j = np.nan
return a
df['jerk'] = df.apply(accel, axis =1)
df['jerk_shift'] = df.jerk.shift(-1)
# add y for bearing calculator
def y(x):
try:
yy = np.sin((x[8] - x[0]) * np.pi/180) *np.cos( x[7]* np.pi/180)
except:
yy= np.nan
return yy
df['y'] = df.apply(y, axis =1)
# add x for bearing calculator
def x(x):
try:
xx = np.cos(x[0] * np.pi/180) *np.sin(x[7]* np.pi/180) - np.sin(x[0]* np.pi/180) * np.cos(x[7]* np.pi/180)*np.cos((x[8]-x[1])* np.pi/180)
except:
xx = np.nan
return xx
df['x'] = df.apply(x,axis =1)
# calculate bearing
def bearing(x):
try:
b = np.arctan2(x[-2],x[-1])*180/np.pi
except:
b = np.nan
return b
df['bearing'] = df.apply(bearing,axis=1)df['brearing_shift'] = df.bearing.shift(-1)
# calculate bearing rate (rate of change of direction)
def bearing_rate(x):
try:
br = abs(x[-1]-x[-2])
except:
br = np.nan
return br
df['bearing_rate'] = df.apply(bearing_rate,axis=1)
# calculate distance travelled
def distance(x):
try:
dist = vincenty((x[7],x[8]),(x[0],x[1]), miles = True)
except:
dist= np.nan
return dist
df['distance'] = df.apply(distance,axis = 1)
df.drop(df.tail(4).index,inplace=True)
return dfdef list_df_summary(input_df):
'''
Converts output from add_feat function into an observation for machine learning
'''
names = ['speed_10','speed_25','speed_50','speed_75','speed_90','speed_ave',
'accel_10','accel_25','accel_50','accel_75','accel_90','accel_ave',
'jerk_10','jerk_25','jerk_50','jerk_75','jerk_90','jerk_ave',
'bearingRate_10','bearingRate_25','bearingRate_50','bearingRate_75','bearingRate_90','bearingRate_ave',
'distance_traveled','time_traveled' ,'last_lat','last_long','start_lat','start_long'
]
values = list()
#speed
values.append(input_df.quantile(.10)[8])
values.append(input_df.quantile(.25)[8])
values.append(input_df.quantile(.50)[8])
values.append(input_df.quantile(.75)[8])
values.append(input_df.quantile(.90)[8])
values.append(input_df.mean()[8])
#accel
values.append(input_df.quantile(.10)[10])
values.append(input_df.quantile(.25)[10])
values.append(input_df.quantile(.50)[10])
values.append(input_df.quantile(.75)[10])
values.append(input_df.quantile(.90)[10])
values.append(input_df.mean()[10])
#jerk
values.append(input_df.quantile(.10)[12])
values.append(input_df.quantile(.25)[12])
values.append(input_df.quantile(.50)[12])
values.append(input_df.quantile(.75)[12])
values.append(input_df.quantile(.90)[12])
values.append(input_df.mean()[12])
#bearing
values.append(input_df.quantile(.10)[18])
values.append(input_df.quantile(.25)[18])
values.append(input_df.quantile(.50)[18])
values.append(input_df.quantile(.75)[18])
values.append(input_df.quantile(.90)[18])
values.append(input_df.mean()[18])
#distance travelled
values.append(input_df.distance.sum())
#time travelled
values.append((input_df.iloc[-1,4] - input_df.iloc[0,4])*24*60)
#lat long
values.append(input_df.iloc[-1,0])
values.append(input_df.iloc[-1,1])
values.append(input_df.iloc[0,0])
values.append(input_df.iloc[0,1])
return pd.DataFrame([values],columns=names)
为了遍历文件目录,我使用了一个名为 glob 的包。下面的代码完成了为每个。GeoLife 目录中的 plt 文件。然后通过运行 for 循环。plt 列出并应用前面的函数让我们为机器学习做好准备!
plts = []
for folder in glob('Geolife Trajectories 1.3/Data/*/Trajectory'):
for file in glob(folder +'/*.plt'):
plts.append(file)large_df = []
for i, file in enumerate(plts):
print( int(i*100 / len(plts)))
try:
large_df.append(list_df_summary(add_feat(pd.read_csv(file, skiprows=6,names=colnames))))
except:
print('error at: ' + file)
df = pd.concat(large_df)
对于我的例子,我将使用 modeling_df 特性的一个子集来创建集群。不是每个特征都有有用的信息(阅读更多关于维度的诅咒)。这是一个迭代的过程,你可以尝试不同的子集来开发不同的集群。最初,我查看每个特征的分布,猜测它是否对无监督学习算法有价值。
GPS 轨迹聚类
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
import seaborn as sns
from collections import Counter## Remove longer trips
df_local_all_feat=df[(df.time_traveled < 300) & (df.distance_traveled < 100)& (df.speed_90<75)]## Select Features with unique distributions
df_local = df_local_all_feat[['speed_90','speed_ave','accel_75','speed_50','accel_ave','distance_traveled','time_traveled']]## Scale Data for KMeans
X = StandardScaler()
X = X.fit_transform(df_local)#################################
### PERFORM KMeans CLUSTERING ###
#################################Sum_of_squared_distances = []
scores = []
cluster_count = []
K= range(2,15,1)
for i in K:
km = KMeans(n_clusters=i)
km.fit(X)
pred =km.predict(X)
Sum_of_squared_distances.append(km.inertia_)
scores.append(silhouette_score(X, pred))
cluster_count.append(i)
print(Counter(pred))
print(str(silhouette_score(X, pred)) + ' clusters:' +str(i))
print('------')########################################
### Elbow plot with silhouette score ###
########################################sns.set_context('talk')
sns.lineplot(cluster_count,scores)
plt.xlabel('Cluster Count')
plt.ylabel('Silhouette Score')
plt.title('Elbow Method For Optimal Cluster Count')
plt.show()
The elbow plot is telling us to investigate either 6 or 7 clusters.
df_local_all_feat['cluster'] = KMeans(n_clusters=7).fit_predict(X)
Trajectory Clusters
聚类 4,5 包含很少数量的观察值,因此不包括在此分析中。
让我们看看最终目的地和轨迹簇。
Tsinghua University, Trajectory Cluster
看看清华大学,我们可以看到大量的集群 0 和 1。
Tencent Office, Trajectory Cluster
看看腾讯的办公室,我们可以开始看到更多的地铁乘坐,集群 5 和 6。
看一下当地的购物区,地铁/火车出行较少,但火车出行通常距离更长(大小是行驶的总距离)。
目标地理空间聚类
上面部分的放大图可以在下面找到。我们已经确定了 3 个一般的兴趣点,现在是时候将它们聚集在一起了。
Tsinghua University on top, shopping center on lower left, and Tencent on lower right
看起来我们需要一种算法,既能结合距离和接近度,又能排除噪音。听起来 DB 扫描将是这个应用程序的算法。
from sklearn.cluster import DBSCAN# pull out the only the location metrics
df_cluster = df_local_all_feat[['Last Lat', 'Last Long']]X = StandardScaler()
X = X.fit_transform(df_cluster)scores = []
EP =[]
number =[]
eps= [.08,.10,.15,.20,.25,.30,.40,.50,.60]
size= [200,250,300]
for ep in eps:
for siz in size:
db = DBSCAN(eps=ep, min_samples=siz)
pred = db.fit_predict(X)
print('ep:'+ str(ep) + ' size:'+ str(siz))
print(Counter(pred))
try:
scores.append(silhouette_score(X, pred))
except:
scores.append(np.nan)
EP.append(ep)
number.append(siz)
变量 eps 和 size 将允许您为您的应用网格搜索适当的超参数。最后,我选择了下面的价值观。
db = DBSCAN(eps= 0.37, min_samples = 250)
结合两种聚类算法
通过结合两种聚类算法,我们可以基于兴趣点过滤特定的行程,以确定人们来自哪里以及到达所述兴趣点所使用的交通方式。
对于清华大学,我们可以看到人们通常从附近短途旅行。
对于购物区,我们可以看到人们的出行距离更远,并且大多使用交通模式 0。
对于腾讯办公室来说,人们从最远的地方出发。然而,从附近通勤的人通常选择交通模式 0,而从更远通勤的人选择交通模式 6。
结论
使用各种无监督学习算法,我们能够确定人们如何前往特定位置,如何限制兴趣点,以及人们来自哪里。
通过调整,在数据收集架构中,根据出行距离和交通方式等因素来确定人们在一个地点的居住方式将会很有意义。
机器学习的简明介绍
内部 AI
任何足够先进的技术都和魔法没什么区别
欢迎光临!这篇文章是我第一次尝试稍微深入一点像机器学习这样的高级主题。我写这篇文章有两个原因。首先,我发现这个主题非常有趣,如果你对机器学习感兴趣,但不知道从哪里开始,那么这篇文章是给你的。其次,这篇文章是我综合我的学习并与机器学习爱好者分享我的知识的一种方式。
我希望你有一些“概率论”的基础知识,以便跟上。由于机器学习是一个非常广泛的话题,我希望您喜欢阅读,因此本文将主要关注:
- 揭秘什么是机器学习?
- 学习方法
- 监督和非监督学习
- 回归、分类和聚类概述
- 数据集的两个实例。
- 免费资源,了解更多信息
So、什么是机器学习&我们为什么需要它?
任何足够先进的技术都和魔法没什么区别
—亚瑟·C·克拉克
我们知道人类从过去的经验中学习,机器遵循人类给出的指令。
W 如果人类可以从过去的数据中训练机器,做人类能做的事情,会怎么样?
好吧,那就是机器学习,我们来深究一下。
这个世界充满了数据,大量的数据——图片、音乐、文本、视频、电子表格。机器学习是人工智能的科学和分支,其中计算机根据数据进行训练以执行特定任务,而不是通过显式编程。
传统软件开发 VS 机器学习
在传统的软件开发方法中,输入和算法是已知的,你编写一个函数来产生一个输出。
- 输入数据
- 通过对算法应用逻辑来设计算法
- 产生输出
然而,在机器学习方法中,你知道输入和期望输出,但不知道给出输出的算法。
- 给出一组输入数据
- 给出一组期望的输出数据
- 通过神经网络 *(将在另一篇文章中介绍)*你得到预测期望输出的算法。
机器学习的一些应用
公司使用机器学习算法来优化他们产品的结果。
- 当你在网飞的时候,你怎么知道你想看哪部电影?网飞使用机器学习提出建议。
- 当你使用信用卡进行购买时,上面有欺诈保护,这就是机器学习。
- 机器学习的其他应用包括语音识别、语言翻译、自动驾驶汽车和许多其他应用。
学习是如何发生的?
在机器学习的大多数应用:重点不在于建立模型(“理解底层过程”),而在于预测。
就像人类从经验中学习一样,机器学习也是一样,你给他们大量的例子(数据),他们就开始弄明白发生了什么。我们人类进行归纳推理而不是演绎推理。让我们回顾一下这两个概念,因为它们在机器学习中非常重要。
**归纳(具体→一般)**是根据我们有限的经验,从具体的例子中归纳出结论,这里的结论可能并不总是 100%准确。
- 我们每天都观察日出。
- 所以我们预测太阳明天也会升起。
- 因此,我们预测明天太阳还会升起。
**演绎(一般→具体)**是从一般陈述中得出具体结论的过程。这里的结论是事实。
- 所有的人都会死。
- 威廉是个男人。
- 因此,威廉是凡人。
重要的是要记住,任何学习的系统都有“归纳偏差”或先验知识,机器学习旨在自动化“归纳推理”的过程。机器学习这种多样性的一个显著例子就是监督学习和**非监督学习的分离,**这里我就不说强化学习了。
监督学习
在监督学习中,考虑到输入和输出之间存在某种关系,我们得到了一个带标签的数据集,并且已经知道我们的正确输出应该是什么样子。自动驾驶汽车就是监督学习的一个例子。监督问题分为“回归”和“分类”问题。
在一个“回归”问题中,我们试图预测具有连续结果的事物。例如,给定一个人的照片,我们必须根据给定的照片来预测他们的年龄。
在“分类问题中,我们试图预测具有离散输出的结果。例如,给定一个患有肿瘤的患者,我们必须预测肿瘤是恶性的还是良性的。
机器学习的 5 个步骤
为了设计机器学习算法,需要遵循以下 5 个主要步骤:
- 输入,又称功能 : x
- 输出,也称为标签 : y
- 目标函数: f: x → y (未知)
- 采集数据 (x1,y1),(x2,y2),…。,(xn,yn)
- 假设: h: x → y
让我们看两个既有分类又有回归的监督学习的例子,以便更深入地理解。
例 1:分类
假设我们在大学的招生办公室工作,我们的工作是接受或拒绝一个学生。为了做出决定,我们需要一些学生的意见,对吗?让我们看看:他们的成绩和最后一年的考试分数。
我们在尝试根据以往收集的数据来预测,学生 C 是否会被录取?为了形象化,我们可以把它画在图上。
Udacity’s course “Intro to Deep Learning with PyTorch”
在上图中,我们首先用一条线将好的和坏的数据分开。这条线将成为我们的模型。蓝点代表被录取的学生的数据点,红点代表根据他们的考试分数和成绩被拒绝的学生的数据点。看这个图表,我们可以看到,线上的学生被录取,而线下的学生被拒绝。你可能会想,线上有几个红点,线下有几个蓝点,是的,你是对的,模型犯了几个错误,这就是为什么它是概率而不是 100%确定的。所以可以肯定的是,如果一个分数超过了分数线,那么这个学生就被录取了。成绩(6)和考试分数(7)的学生 C 在分数线以上,因此我们可以有把握地认为学生 C 最有可能被录取。这是一个分类问题,因为输出是离散的(接受,拒绝)。
示例 2:回归(小高级)
我们举一个 房价预测的例子。 你的朋友拥有一栋 750 平方英尺的房子,他想卖掉房子,想知道能卖多少钱。你知道机器学习,因此你可以用你的机器学习知识和技能帮助他预测价格。我们来看看怎么做。
我们的目标是,给定一个训练集,学习一个函数 h: X → Y ,使得 h(x)是 Y 的相应值的“良好”预测器。出于历史原因,这个函数 h 被称为假设。假设接受输入并给出一个估计值,所以它从 x → y 映射。
我们可以使用一条“线性线(下方图片中的粉色线)来拟合数据集,基于此,看起来他可能可以以 15 万美元的价格出售它。然而,可能有比线性更好的学习算法。除了将直线拟合到数据中,我们也许可以尝试将“二次”拟合到这个函数中(下图中的蓝线)。然后,通过对它进行预测,看起来他可能可以以 20 万美元的价格出售它。现在的问题是选择我们应该使用哪一个?挑选中没有公平可言,哪个给我们的朋友的价格最好。这是一个回归问题,因为答案可以是连续的。**
Andrew Ng’s Machine Learning course on Coursera
无监督学习
在无监督学习中,我们很少或根本不知道我们的结果应该是什么样的。因此,目的是发现数据中隐藏的结构。理解无监督学习的核心思想是“聚类”。
聚类:取 50 个不同的人集合,想办法自动把这些人分组。也许你想把他们分成男性和女性,因此你可以决定如何去做。你也许可以根据面部毛发、肤色、衣着等来划分他们。最后,你可以说你是 27 个男性和 23 个女性。
关键术语
- **特性:**模型的输入
- **示例:**用于训练的输入/输出对
- **标签:**模型的输出
- **层:**神经网络中连接在一起的节点的集合
- **模型:**你的神经网络的表示
总结
- 任何学习的系统都有“归纳偏差”或“先验知识”。
- 机器学习旨在自动化“归纳推理”的过程
- 我们需要知道我们在寻找什么(归纳偏差)
- 由于机器学习是一种归纳参考的形式,我们永远无法对结果充满信心。
- 在机器学习的大部分应用中,重点与其说是建立模型,不如说是预测。
这篇文章只是对机器学习领域的浅尝辄止,还有更多的内容,并且有大量的在线资源可以用来增强你的知识。下面我提供了 5 个我个人认为非常有用的资源。
一些免费的有用资源,了解更多:
这篇文章是我第一次尝试深入像机器学习这样的复杂话题。我会写更多的文章来讨论机器学习背后的数学。我希望我做了一个体面的解释工作,你觉得这篇文章很有见地。
保持好奇和不断学习:)
最大似然估计和最大后验估计简介
以足球为例获得最大似然法和地图的直觉
Photo by Mitch Rosen on Unsplash
最大似然估计(MLE)和最大后验估计(MAP)是估计统计模型参数的方法。
尽管这些方法背后有一点高等数学,但 MLE 和 MAP 的思想非常简单,直观易懂。在这篇文章中,我将解释什么是 MLE 和 MAP,重点是方法的直觉以及背后的数学。
例子:利物浦足球俱乐部在下个赛季赢得比赛的概率
2018-19 赛季,利物浦 FC 在英超 38 场比赛中赢了 30 场。有了这些数据,我们想猜测下赛季利物浦赢得比赛的概率。
这里最简单的猜测是 30/38 = 79% ,这是基于数据的最佳猜测。这实际上是用 MLE 方法估计的。
然后,假设我们知道利物浦在过去几个赛季的胜率在 50%左右。你认为我们最好的猜测还是 79%吗?考虑到先前的知识以及本赛季的数据,我认为 50%到 79%之间的某个值会更现实。这是用图法估算的。
我相信上面的想法很简单。但是为了更精确的理解,我将在下面的部分中阐述 MLE 和 MAP 的数学细节。
模型和参数
在讨论每种方法之前,让我先澄清一下这个例子中的模型和参数,因为 MLE 和 MAP 是估计统计模型的参数的方法。
在这个例子中,我们简化为利物浦在所有赛季的所有比赛中只有一个获胜概率(姑且称之为 θ ),而不考虑每场比赛的独特性和真实足球比赛的任何复杂因素。换句话说,我们假设利物浦的每场比赛都是伯努利试验,获胜概率为 θ 。
有了这个假设,我们就可以描述对于任意给定数量的 k 和 n ( k≤n ),利物浦在 n 场比赛中赢 k 次的概率。更准确的说,我们假设利物浦的胜数服从参数为 θ 的二项分布。给定获胜概率 θ ,利物浦在 n 场比赛中赢 k 次的概率公式如下。
这种简化(仅使用单个参数 θ 来描述概率,而不考虑现实世界的复杂性)是该示例的统计建模,并且 θ 是待估计的参数。
从下一节开始,我们用 MLE 和 MAP 来估计这个 θ 。
最大似然估计
在上一节中,我们得到了对于给定的 θ ,利物浦在 n 场比赛中赢 k 次的概率公式。
由于我们有本赛季的观测数据,即38 场比赛中的 30 场胜利(姑且称此数据为 D ),我们可以计算出*P(D |θ)——*对于给定的 θ ,观测到此数据 D 的概率。让我们以 θ=0.1 和 θ=0.7 为例计算 P(D|θ) 。
当利物浦获胜概率 θ = 0.1 时,观察到这个数据D(38 场比赛 30 胜)的概率如下。
P(D |θ)= 0.000000000000000000211。所以,如果利物浦的获胜概率 θ 实际上是 0.1 ,这个数据D(38 场比赛 30 胜)是极不可能被观测到的。那如果 θ = 0.7 呢?
远高于之前的。所以如果利物浦的获胜概率 θ 为 0.7,这个数据 D 比 θ = 0.1 时更容易被观察到。
基于这种比较,考虑到实际观测数据 D ,我们可以说 θ 更有可能是 0.7 而不是 0.1 。
这里,我们一直在计算对于每个 θ 观察到 D 的概率,但同时,我们也可以说,我们一直在根据观察到的数据检查 θ 的每个值的可能性。正因为如此, P(D|θ) 也被认为是 θ 的可能性。这里的下一个问题是,最大化可能性 P(D|θ) 的 θ 的精确值是什么?没错,这就是最大似然估计!
最大化似然性的 θ 的值可以通过使似然函数对 θ 求导并将其设置为零来获得。
解决这个, θ = 0,1 或者 k/n 。由于当 θ= 0 或 1 时似然性为零,因此 θ 的值使似然性最大化为 k/n 。
本例中,用 MLE 估计时, θ 的估计值为 30/38 = 78.9% 。
最大后验估计
当你有足够的数据时,MLE 是强大的。然而,当观察到的数据量很小时,这种方法效果不好。例如,如果利物浦只有 2 场比赛,他们赢了这 2 场比赛,那么 MLE 估计的 θ 的值是 2/2 = 1 。意思是估计说利物浦赢 100% ,这是不切实际的估计。地图可以帮助处理这个问题。
假设我们事先知道利物浦过去几个赛季的胜率在 50%左右。
然后,没有这个赛季的数据,我们已经对 θ 的潜在价值有了一些概念。(仅)基于先验知识, θ 的值最有可能是 0.5 ,不太可能是 0 或 1 。换句话说, θ=0.5 的概率高于 θ=0 或 1 。称之为先验概率 *P(θ),*如果我们将它形象化,它将如下。
Figure 1. A visualisation of P(θ) expressing the example prior knowledge
然后,有了本赛季的观察数据D(38 场比赛中 30 胜),我们可以更新这个仅基于先验知识的 P(θ) 。给定 D 的 θ 的更新概率表示为 P(θ|D) ,称为后验概率。
现在,考虑到我们的先验知识和观测数据,我们想知道 θ 的最佳猜测。这意味着最大化 P(θ|D) 并且这是 MAP 估计。
这里的问题是,如何计算 P(θ|D) ?到目前为止,在本文中,我们检查了计算 P(D|θ) 的方法,但还没有看到计算 P(θ|D) 的方法。为此,我们需要使用下面的贝叶斯定理。
本文不深入讨论贝叶斯定理,但有了这个定理,我们可以利用似然 P(D|θ) 和先验概率 P(θ) 计算后验概率 P(θ|D) 。
等式中有 P(D) ,但 P(D) 与 θ 的值无关。由于我们只对寻找最大化 P(θ|D) 的 θ 感兴趣,我们可以在最大化中忽略 P(D) 。
上面的等式意味着关于 θ 的后验概率 P(θ|D) 的最大化等于关于 θ 的似然 P(D|θ) 和先验概率 P(θ) 的乘积的最大化。
我们在本节的前面讨论了 P(θ) 的含义,但是我们还没有进入公式。本质上,我们可以用任何一个将概率分布描述为 P(θ) 的公式来很好地表达我们的先验知识。然而,为了计算的简单性,使用对应于可能性的概率分布的特定概率分布。叫做共轭先验分布。
在这个例子中,可能性 P(D|θ) 遵循二项式分布。由于二项分布的共轭先验是贝塔分布,所以我们这里用贝塔分布来表示 P(θ) 。贝塔分布描述如下。
其中, α 和 β 称为超参数,无法通过数据确定。相反,我们主观地设置它们是为了更好地表达我们的先验知识。例如,下图是不同值的 α 和 β 的贝塔分布的可视化。你可以看到左上角的图是我们在上面的例子中使用的图(表示 θ=0.5 是基于先验知识的最可能的值),右上角的图也表示相同的先验知识,但这是为相信过去几个赛季的结果很好地反映了利物浦的真实能力的人准备的。
Figure 2. Visualisations of Beta distribution with different values of α and β
这里有一个关于右下图的说明:当 α=1 和 β=1 时,意味着我们对 θ 没有任何先验知识。在这种情况下,估计将与 MLE 的估计完全相同。
所以,现在我们有了所有的组件来计算 P(D|θ)P(θ) 以使其最大化。
与 MLE 一样,我们可以通过对这个函数求关于 θ 的导数,并将其设置为零,从而使 θ 最大化。
通过解决这个问题,我们得到以下结果。
在这个例子中,假设我们用 α=10 和 β=10 ,那么θ=(30+10–1)/(38+10+10–2)= 39/56 = 69.6%
结论
从上面的例子可以看出,MLE 和 MAP 复杂的数学方程背后的思想出奇的简单。我在本文中使用了二项分布作为例子,但是 MLE 和 MAP 也适用于其他统计模型。希望这篇文章能帮助你理解 MLE 和 MAP。
自然语言处理入门
介绍自然语言处理和对文本数据的情感分析。
Image Source
人类通过某种形式的语言进行交流,无论是文本还是语音。现在要让计算机和人类互动,计算机需要理解人类使用的自然语言。自然语言处理就是让计算机学习、处理和操作自然语言。
在这篇博客中,我们将看看在自然语言处理任务中使用的一些常见做法。并且在电影评论上建立简单的情感分析模型,以预测给定评论是正面的还是负面的。
什么是自然语言处理(NLP)?
NLP 是人工智能的一个分支,它处理分析、理解和生成人类自然使用的语言,以便使用自然人类语言而不是计算机语言在书面和口头上下文中与计算机交互。
自然语言处理的应用
- 机器翻译(谷歌翻译)
- 自然语言生成
- 网络搜索
- 垃圾邮件过滤器
- 情感分析
- 聊天机器人
…以及更多
数据清理:
在数据清理过程中,我们从原始数据中移除特殊字符、符号、标点符号、HTML 标签<>等,这些数据不包含模型要学习的信息,这些只是我们数据中的噪声。
这个过程还取决于问题陈述,比如从原始文本中删除什么。例如,如果问题包含来自经济或商业世界的文本,那么像$或其他货币符号这样的符号可能包含一些我们不想丢失的隐藏信息。但大多数情况下我们会移除它们。
数据预处理:
数据预处理是一种数据挖掘技术,包括将原始数据转换成可理解的格式。
小写:
让所有文本都变成小写是最简单也是最有效的文本预处理形式之一。
Image Source
符号化:
记号化是将文本文档分解成称为记号的单个单词的过程。
如上所述,句子被分解成单词(记号)。自然语言工具包(NLTK)是一个流行的开源库,广泛用于 NLP 任务。对于这个博客,我们将使用 nltk 进行所有的文本预处理步骤。
您可以使用 pip 下载 nltk 库:
*!pip install nltk*
停止单词删除:
停用词是在文本文档中不提供太多信息的常用词。像‘the’,‘is’,‘a’这样的词价值较小,会给文本数据增加干扰。
NLTK 中有一个内置的停用词列表,我们可以用它从文本文档中删除停用词。然而,这不是每个问题的标准停用词表,我们也可以根据领域定义自己的停用词表。
NLTK 有一个预定义的停用词列表。我们可以从这个列表中添加或删除停用词,或者根据具体任务来使用它们。
词干:
词干化是将一个单词缩减为其词干/词根的过程。它将单词(如“help”、“helping”、“helped”、“helped”)的词形变化减少到词根形式(如“help”)。它从单词中去掉词缀,只留下词干。
Image Source
词干可能是也可能不是语言中的有效词。例如,movi 是 movie 的词根,emot 是 emotion 的词根。
词汇化:
词汇化与词干化的作用相同,将单词转换为其词根形式,但有一点不同,即在这种情况下,词根属于语言中的有效单词。例如,在词干的情况下,单词 caring 将映射到“care”而不是“car”。
WordNet 是英语中有效单词的数据库。NLTK 的 WordNetLemmatizer()使用来自 WordNet 的有效单词。
N-grams:
Image Source
N-grams 是多个单词一起使用的组合,N=1 的 N-grams 称为 unigrams。类似地,也可以使用二元模型(N=2)、三元模型(N=3)等等。
当我们希望保留文档中的序列信息时,可以使用 n 元语法,比如给定的单词后面可能跟什么单词。单字不包含任何序列信息,因为每个单词都是独立的。
文本数据矢量化:
将文本转换为数字的过程称为文本数据矢量化。现在,在文本预处理之后,我们需要用数字表示文本数据,也就是说,用数字对数据进行编码,以便算法进一步使用。
包话(鞠躬):
这是最简单的文本矢量化技术之一。BOW 背后的直觉是,如果两个句子包含一组相似的单词,就说它们是相似的。
考虑这两句话:
Image Source
在 NLP 任务中,每个文本句子被称为一个文档,这些文档的集合被称为文本语料库。
BOW 在语料库(数据中所有标记的集合)中构建了一个包含 d 个唯一单词的字典。例如,上图中的语料库由 S1 和 S2 的单词组合而成。
现在,我们可以想象创建一个表,其中的列是语料库中唯一的单词集,每行对应一个句子(文档)。如果这个单词出现在句子中,我们把它的值设置为 1,否则我们把它设置为 0。
Image Source
这将创建一个矩阵 dxn ,其中 d 是语料库中唯一标记的总数,而 n 等于文档的数量。在上面的例子中,矩阵的形状为 11x2。
TF-IDF:
Image Source
它代表词频(TF)-逆文档频率。
词频:
词频定义了在文档中找到某个单词的概率。现在假设我们想找出在文档 dj 中找到单词 wi 的概率是多少。
词频( wi , dj ) =
wi 出现在 dj 中的次数/ 在 dj 中的总字数
逆文档频率:
IDF 背后的直觉是,如果一个单词出现在所有文档中,它就没有多大用处。它定义了单词在整个语料库中的独特性。
IDF(wi,Dc) = log(N/ni)
这里, Dc =语料库中的所有文档,
N =文件总数,
ni =包含 word ( wi )的文档。
如果 wi 在语料库中更频繁,则 IDF 值减少。
如果 wi 不频繁,这意味着 ni 减少,因此 IDF 值增加。
TF( 作业指导书、DJ)*****IDF(作业指导书、 Dc )
TF-IDF 是 TF 和 IDF 值的乘积。它给予在文档中出现较多而在语料库中出现较少的单词更大的权重。
情感分析:IMDB 电影评论
Image Source
关于
该数据集包含来自 IMDB 网站的 50,000 条评论,正面和负面评论的数量相等。任务是预测给定评论(文本)的极性(积极或消极)。
我使用 Deepnote 在 IMDB 数据集上做数据分析,它的设置简单快捷,并且提供了很好的协作工具。我最喜欢的是从多个数据源即插即用的能力。如果你是新手,正在开始你的数据科学之旅,我强烈建议你去看看。这里是这个项目的笔记本。
1.数据的加载和浏览
IMDB 数据集可以从这里下载。
数据集概述:
正面评价标为 1,负面标为 0。
样本正面评论:
样品差评:
2.数据预处理
这里,我们在一个方法中完成了数据清理和预处理的所有步骤,如上所述。我们使用词干化而不是词干化,因为在测试两者的结果时,词干化给出的结果比词干化稍好。
词干化或词干化或两者的使用取决于问题,所以我们应该尝试看看哪种方法对给定的任务最有效。
通过对所有评论应用 data_preprocessing(),在 dataframe 中添加一个新列 preprocessed _ review。
3.向量化文本(评论)
将数据集分为训练和测试(70–30):
我们正在使用 sklearn 的 train_test_split 将数据拆分为训练和测试。这里我们使用参数分层,在训练和测试中有相等比例的类。
低头
这里我们使用了 min _ df = 10T5,因为我们只需要那些在整个语料库中出现至少 10 次的单词。
TF-IDF
4.构建 ML 分类器
带评论 BOW 编码的朴素贝叶斯
带 BOW 的朴素贝叶斯给出了 84.6%的准确率。用 TF-IDF 试试吧。
带有 TF-IDF 编码评论的朴素贝叶斯
TF-IDF 给出了比 BOW 稍好的结果(85.3%)。现在让我们用一个简单的线性模型,逻辑回归,来试试 TF-IDF。
对 TF-IDF 编码的评论进行逻辑回归
使用 TFIDF 编码的评论的逻辑回归给出了比朴素贝叶斯更好的结果,准确率为 88.0%。
绘制混淆矩阵为我们提供了关于有多少数据点被模型正确和错误分类的信息。
在 7500 个负面评论中,6515 个被正确分类为负面,985 个被错误分类为正面。在 7500 个正面评论中,6696 个被正确分类为正面,804 个被错误分类为负面。
摘要
我们已经学习了一些基本的 NLP 任务,并为电影评论的情感分析建立了简单的 ML 模型。通过深度学习模型尝试单词嵌入,可以实现进一步的改进。
感谢您的阅读。完整的代码可以在这里找到。
参考资料:
[## 处理文本数据(使用 Python)的终极指南——面向数据科学家和工程师
引言实现任何水平的人工智能所需的最大突破之一是拥有…
www.analyticsvidhya.com](https://www.analyticsvidhya.com/blog/2018/02/the-different-methods-deal-text-data-predictive-python/) [## 关于自然语言处理和机器学习的文本预处理
数据科学家卡维塔·加内桑。根据最近的一些谈话,我意识到文本预处理是一个严重的…
www.kdnuggets.com](https://www.kdnuggets.com/2019/04/text-preprocessing-nlp-machine-learning.html)
推荐系统简介
基于内容的过滤、协同过滤及实际应用
推荐系统简介
如果你正在阅读关于推荐系统的文章,你肯定已经知道我们将要讨论的内容,所以也许你可以跳过这一章。但是如果你是被封面图片所吸引,或者如果你想知道更多关于推荐系统在过去几年是如何出现和成长的,那么请继续关注这篇文章的这一部分。
让我们回到过去,试着想象一下:这是周五晚上,你想租一盘你最近的大片的录像带。你和你的女朋友去那里,已经在讨论你要租哪部电影了。也许是喜剧?或者一部动作片?一场雷雨即将来临,这给恐怖电影或惊悚片带来了完美的气氛。
当你在思考的时候,你的女朋友告诉你:“你在网上搜索过什么好的推荐吗?”。不幸的是,在你工作的时候,互联网一整天都是关闭的,不管怎样,你仍然试图让自己在万维网上到处都是那些晦涩难懂的东西。
你终于到了,但是经过近一个小时的考虑,你还是不知道该选什么。你已经看过所有受欢迎的电影,商店的首要部分全是废话。你不想吹毛求疵,但就是没什么好的。你的女朋友问桌子后面的大片伙伴,但他对电影的了解似乎比你爷爷少,而且他真的不喜欢孩子。天色已晚,你筋疲力尽,长话短说,你决定租一部你已经看过的老电影。保持它的健全和安全比你周五晚上的计划要好得多。
这个例子似乎很老了,但是在生活的很多方面,人们都在以相似的方式做决定。我可以向你保证,我爷爷想给自己买书的时候不会用谷歌。然而,对年轻人来说幸运的是,今天的决策在许多情况下更安全(我不知道我是否敢说更容易,或更快),因为在谷歌上简单的搜索会给我们几十甚至几百条评论。然而,在线零售商想让你呆在他们的网站里。他们不希望你走出他们的网页,搜索一些评论,这些评论可能会引导你去另一家零售商那里购买你正在寻找的小玩意。甚至,因为无法解决或找到您想要的东西而暂停购买。
在这种背景下,推荐系统作为一种维护网站或应用程序内部受众的方式出现了。因此,首先,重要的是要明白,它是厌倦了失去客户的商家的工具。当然,这样做的结果是,商家试图用好的推荐来让他们的客户满意,否则,这个工具根本不会起作用。所以最终,对零售商和顾客来说,这是一个双赢的局面。
通常,推荐系统会利用我们之前的活动为我们提出具体的建议。现在,如果我们是第一次访问电子商务,它不会知道我们的任何事情,那么它怎么能给出合理的建议呢?最基本的解决方案是推荐最畅销的产品,一些最新版本,如果我们谈论的是电影或书籍,可能是经典系列,或者我们甚至可以推荐能给企业带来最大利润的产品。然而,构建智能推荐系统有可能提高销售和业务绩效,因此公司正在超越这些经典技术,构建更好、更强大的推荐系统。
构建推荐系统时的挑战
当我们试图向用户推荐商品时,我们面临着一些根本性的挑战:
- 数据稀疏性:有很多产品可以推荐给很多用户,而且一个用户不太可能试用很大一部分产品。相反,许多用户可能只需要几样东西,但很多人只需要几样。
- 冷启动:我们需要能够向那些我们只有很少数据(如果有的话)的用户提供建议。
- 准确但多样的预测:我们希望给出有用的推荐,即它们符合用户的偏好,但也希望推荐对用户来说包含一些新奇的东西。
- **评估:**评估很困难,可能因算法而异。
- 可扩展性:即使有数百万的用户和项目需要我们仔细分析,我们也需要能够当场给出建议。
- **用户界面:**用户想知道为什么会收到特别的推荐。
- 易受攻击性:我们不希望我们的推荐系统被滥用于推广或禁止特定项目。
- 时间分辨力:品味和喜好不会随时间保持不变。
面对这一切,我们会定期谈论用户和物品。在大多数情况下,我们会为每一对可能的用户和项目预测一个特定的评级。如果用户已经给出了一些评分,我们可以将其与我们的预测进行比较:
设计我们的推荐系统
基本上有两种方法:
- 基于内容的过滤
这个替代方案推荐类似于每个特定用户过去已经喜欢的产品。就拿推荐一本书来说,如果用户喜欢《哈利·波特与魔法石》这本书,但没有给《哈利·波特与被诅咒的孩子》排名,我们可以推荐后者。
基于内容的过滤使用特定的相似性度量。相似性度量仅仅是获得用户或项目的向量之间的距离的度量。它本身是一个非常广泛的数学概念,所以我们不打算在本文中更多地讨论它(不过我可能会在将来写更多关于它的内容,所以请保持关注)。但是,例如,我们可以使用余弦相似性来度量项目向量之间的距离,按降序排列它们,然后使用以下方法之一向任何给定用户推荐项目:
- **Top-n 方法:**推荐前 n 部电影的地方。在这种情况下,该数字通常由企业定义
- **分级方法:**设定一个阈值,将高于该阈值的所有电影推荐给用户
这种技术的最大问题是,它将总是限于用户过去购买或排序的相同类型的项目。因此,以我们的书籍为例,如果用户以前从未购买过与商业相关的书籍,它就不会推荐这一类别的书籍。
- 协同过滤
文章的这一部分主要基于来自 Analytics Vidhya 的内容,因为那里几乎所有的内容都得到了完美的解释,我不想重新发明轮子:)
说到这里,为了理解这个算法,让我们继续我们关于书籍的例子:假设我喜欢以下书籍:《盲人刺客》和《莫斯科的绅士》。我的朋友马蒂亚斯也喜欢《盲人刺客》和《莫斯科绅士》,但也喜欢《小龙虾歌唱的地方》。似乎马蒂亚斯和我有相同的兴趣。所以你可能会肯定我也喜欢《小龙虾歌唱的地方》,尽管我没有读过。这正是协同过滤背后的逻辑,唯一的例外是你可以在他们之间比较用户,或者比较项目。让我们看看每种方法是如何工作的:
- 用户-用户协同过滤
这个例子和我们刚才提到的例子一模一样。如果用户 A 喜欢或购买与用户 B 相同的物品,那么我们可以推荐用户 A 喜欢但用户 B 还不喜欢的物品。反之亦然。我们将通过使用相似性度量来比较两个用户对所有排序/购买/喜欢的项目的向量,从而找到它们之间的相似性:
预测 Pu,I 由下式给出:
其中:
- Pu,I 是一个项的预测
- Rv,I 是用户 v 对电影 I 的评价
- 苏,v 是用户之间的相似度
在我们的图书例子中,这似乎很容易,但是现在想象一下,你和你的亚马逊有成千上万的用户和评级。思考计算问题?嗯,你说得对:这种情况下这个算法很重。为了解决这个问题,公司使用邻居逻辑,只选择固定数量的用户进行预测,而不是使用所有的用户。我们可以通过几种方式做到这一点:
然而,该算法相当耗时,因为它涉及计算每个用户的相似性,然后计算每个相似性得分的预测。处理这个问题的一种方式是仅选择几个用户(邻居)而不是所有用户来进行预测,即,不是对所有相似性值进行预测,而是仅选择几个相似性值。有几种选择邻居的方法:
- 选择一个阈值相似性,并选择高于该值的所有用户
- 随机选择用户
- 安排 desc 的邻居。排序他们的相似性值并选择前 N 个用户
- 使用聚类选择邻居
在选择查看 k 个最相似用户的情况下,预测等式将被转换成如下:
这里𝑁𝑘𝑖(𝑢)表示与评价项目𝑖.的用户𝑢最相似的𝑘用户
作为一个例子,让我们以用户相似性(例如相关相似性)为例,我们将考虑用户 1 的两个最近邻居:
- 项目-项目协同过滤
在该算法中,我们计算每对项目之间的相似性:
在这种情况下,算法会再次向我推荐《小龙虾歌唱的地方》,但这只是因为马蒂亚斯、维多利亚和我,我们三个人都喜欢《盲刺客》,而《小龙虾歌唱的地方》是马蒂亚斯和维多利亚都喜欢的唯一一本书。因此,在这种情况下,我们不采用“用户-邻居”评级的加权和,而是采用“项目-邻居”评级的加权和。预测结果由下式给出:
使用协作过滤器
最后,为了应用我们刚刚看到的一些概念,我们将重新访问非常受欢迎的 movielens 数据集(在我的 GitHub 帐户中可用),应用来自 Analytics Vidhya 的技术和来自我的大会沉浸式课程的一些其他技术,使用相同的数据集:
movielens 数据集包含不同的表:
- 用户表:包含年龄、性别、职业和邮政编码等信息
- 项目表:包含电影标题、上映日期、类型、IMBd URL 等等
- 评级表:这是我们将要使用的。
让我们看看最后一个:
现在我们知道了所有这些,让我们一步一步来得到我们的预测:
首先,我们需要创建一个用户-项目矩阵,用于计算用户和项目之间的相似性:
n_users = ratings.user_id.unique().shape[0]
n_items = ratings.movie_id.unique().shape[0]
现在,我们将创建一个充满零的矩阵,然后我们将遍历评级中的每一行,用评级填充用户行和电影列:
data_matrix = np.zeros((n_users, n_items))
**for** line **in** ratings.itertuples():
data_matrix[line[1]-1, line[2]-1] = line[3]
现在,我们将计算相似度。根据定义,规则余弦相似性反映了方向的差异,而不是位置的差异。因此,它没有考虑用户评分的差异。经调整的余弦相似性通过减去平均评级来抵消这一缺点。因此,我们将创建一个函数来查找调整后的余弦相似度:
**from** **scipy** **import** spatial**def** adjusted_cos_distance_matrix(size, matrix, row_column):
distances = np.zeros((size,size))
**if** row_column == 0:
M_u = matrix.mean(axis=1)
m_sub = matrix - M_u[:,**None**]
**if** row_column == 1:
M_u = matrix.T.mean(axis=1)
m_sub = matrix.T - M_u[:,**None**]
**for** first **in** range(0,size):
**for** sec **in** range(0,size):
distance = spatial.distance.cosine(m_sub[first],m_sub[sec])
distances[first,sec] = distance
**return** distances
现在,让我们找出用户和项目的相似性:
user_similarity = adjusted_cos_distance_matrix(n_users,data_matrix,0)
item_similarity = adjusted_cos_distance_matrix(n_items,data_matrix,1)
最后,让我们做个预测:
def predict(ratings, similarity, type='user'):
if type == 'user':
mean_user_rating = ratings.mean(axis=1)
ratings_diff = (ratings - mean_user_rating[:, np.newaxis])
pred = mean_user_rating[:, np.newaxis] + similarity.dot(ratings_diff) / np.array([np.abs(similarity).sum(axis=1)]).T
elif type == 'item':
pred = ratings.dot(similarity) / np.array([np.abs(similarity).sum(axis=1)])
return pred
如您所见,我们使用了 np.newaxis,因此 mean_user_rating 与 ratings 具有相同的格式。点击了解更多关于 np.newaxis 的信息。
user_prediction = predict(data_matrix, user_similarity, type='user')
item_prediction = predict(data_matrix, item_similarity, type='item')
让我们检查一下预测矩阵的形状:
最后,除了创建推荐系统之外,让我们使用之前创建的预测创建一个函数。它会找到用户没有评级的电影(因此没有看过),并根据我们的预测给我们排名前 5 的电影。
**def** finding_movies_for_user(user, ratings, preds):
recos = {'movie_id': [],'rating': []}
user_array = preds[user]
orig_rat = ratings[user]
**for** each **in** range(0,len(orig_rat)):
**if** orig_rat[each] == 0:
recos['movie_id'].append(each+1)
recos['rating'].append(user_array[each])
recos_df = pd.DataFrame(recos)
final_recos_df = pd.merge(recos_df, items[['movie_id','movie_title']],
how='left', on='movie_id')
**return** final_recos_df.sort_values(by='rating',ascending=**False**).head(5)
让我们用一个随机用户来测试一下。
哇,太神奇了!我们已经设法为任何给定的用户获得了我们的第一批推荐。当然,推荐系统是一个非常广泛的话题,我们还可以谈论更多:相似性度量、性能评估、更复杂的获取推荐的技术等等!我可能会在未来写更多关于这方面的内容,但同时,请随意查看我的 GitHub 账户,那里有一些不错的项目,包括带有 movielens 数据集的完整推荐系统项目:)
顺便说一句,如果你对推荐系统感兴趣,可以看看我上一篇关于使用奇异值分解改进你的推荐系统的文章,以及我的作者简介中的更多内容。如果你喜欢这篇文章,别忘了关注我,如果你想直接在你的电子邮件上收到我的最新文章,只需订阅我的时事通讯:)
干杯。
写委婉介绍的委婉介绍
如果你给你的文章起了别的标题,人们怎么知道你写的是关于数学的呢?
你是谁?你,对一些听起来很可怕的事情很了解,却想让人们知道这个事情!你的动机当然不是想让人们知道你知道这件事——当然不是。你的动机肯定不是展示那些软技能,那些你在 LinkedIn 网络上认可的沟通技能。不。你希望世界有一个容易获得的资源来学习中心极限定理,或者稀疏矩阵,或者神经网络,或者神经网络,或者神经网络,甚至神经网络!
你必须强调这个概念是如何可接近的,不要介意这个概念的完整推导是某人花了五年时间写博士论文的主题。不,作为一个受人尊敬的媒介传播者,你可以将这个想法提炼为一段估计为“6 分钟阅读”的文字。亲爱的作家,不要担心,如果你的主题对于一篇六分钟的中型文章来说确实太复杂了。这就是范围和背景假设的辉煌写作手段发挥作用的地方。例如,如果你试图通过梯度下降来解释反向传播,这是一个完全公平的假设,即阅读你的帖子的机器学习爱好者具有足够好的多变量微积分背景,能够通过将链规则应用于你的网络的权重、偏差和激活的梯度来理解你在做什么,因为它们与你的通用示例损失函数相关。如果他们没有这方面的背景,那么这就“超出了”你的文章的范围——不需要再想了!
哦,有帮助的、知识渊博的作家,不要担心在这一点上你的数学密集、充满隐喻的文章不再是“温和的介绍”。更改你的标题不符合任何人的利益,所以你对支持向量机松弛变量的温和介绍将经受住中等出版物编辑的严格编辑。想想看:你的标题对技术和非技术读者有广泛的吸引力,你的材料符合出版物的使命,增加的流量同样遵循出版物的使命。“流量增加了!”你暗自思忖,对宣传你的作品的潜在点击诱饵的指控感到震惊,“这个骗子如何证明这是真正的点击诱饵?”亲爱的有用的作者,使用下面关于机器学习和数据科学的中型文章的调查,我们可以看到明确的证据,一旦短语“温和的介绍”出现在你的标题中,鼓掌的中位数和平均数都会飙升。
Some Examination of Clap Totals in “Gentle Introductions” vs All Other Medium Data Science Posts DATA
记住所有这些,不要考虑改变你的标题,当然不要偏离成功的媒体文章公式,无论你做什么,不要试图将你自己从众多知识渊博的数据科学作家中区分出来,旨在帮助所有“有抱负的数据科学家”。你的工作太重要了,以前肯定没有人写过。
可视化的另一个阶段:对 Dash 做出反应
一个温柔的邀请
Dash 是一个开源的 python 库,它使我们能够用 Plotly 创建 web 应用程序。使用简单的反应式装饰器,如下拉菜单、滑动条和降价文本数据,可以很容易地构建交互式可视化。我们甚至可以使用回调函数根据输入数据更新图表,所有这些功能都可以直接使用,不需要 Javascript 或 HTML。Plotly 是一个非常强大的工具,它使我们能够以一种方便的方式制作一个信息丰富且有效的情节,Dash 可以被视为展示令人敬畏的可视化的舞台。
今天我将向你展示如何用 Plotly 创建一个仪表板。我们将制作一个时间序列线图,并添加一个下拉菜单和一个与该图交互的滑动条。有关于如何使用这个库的很好的文档,但是我发现一开始可能很难阅读示例代码并使用它们。因此,这可能是一个善良的桥梁,让你了解如何使用破折号与 Plotly。
装置
要构建一个仪表板,我们需要安装如下一些包。
pip install plotly==2.5.1
pip install dash==0.21.0
pip install dash-core-components==0.22.1
pip install dash-html-components==0.10.0
pip install dash-renderer==0.12.1
正如我上面所说,dash-core-components
不仅允许我们构建图形,还允许我们构建下拉列表和文本框,这样我们就可以相应地更新组件。dash-html-components
使我们能够在 Python 中使用 HTML & CSS。它帮助我们在仪表板上放置 HTML 组件,如 Div、H1 和 H2。
如果这是你第一次使用破折号和 HTML 语法,它可能有点复杂和难以阅读。因此,我建议您将以下脚本作为 Dash 的基本指南。从现在开始,我们要做的是一步一步地填写这个。
首先,我们导入库。您可以根据需要添加其他库。然后我们通过调用 Dash 类来初始化 Dash。这就像在我们的桌子上放一块空白的白板,我们所做的就是在这块板上构建更多的应用程序。
**# Step 1\. Launch the application**
app = dash.Dash()**# Step 2\. Import the dataset**
df = pd.read_csv('finance-charts-apple.csv')
现在让我们从熊猫的 CSV 文件中提取一些材料。我们要使用的数据集是苹果公司的股票价格数据,这里的。
仪表板上的简单散点图
关于 Plotly 本身的细节我就不多说了,但是如果有必要的话你可以从这个视频中找到一个关于 Plotly 的很好的教程。在这一系列的视频中,你还可以学习如何制作 3D 绘图或 Choropleth 地图。这里我们将绘制一个显示股票价格波动的线图。
******# Step 3\. Create a plotly figure**
trace_1 = go.Scatter(x = st.Date, y = st['AAPL.High'],
name = 'AAPL HIGH',
line = dict(width = 2,
color = 'rgb(229, 151, 50)'))layout = go.Layout(title = 'Time Series Plot',
hovermode = 'closest')fig = go.Figure(data = [trace_1], layout = layout)****
现在,该是dash-html-components
出场的时候了。我们先放一个除法,然后把图形放进去。id
正在给这个组件命名,这样我们就可以用它的名字来称呼它。以后你就明白这是干什么用的了。然后,我们在步骤 6 中创建一个运行的服务器。如果我们设置调试模式等于真,我们可以很容易地改变和更新应用程序,而服务器正在运行。
******# Step 4\. Create a Dash layout**
app.layout = html.Div([
** dcc.Graph(id = 'plot', figure = fig)**
])**# Step 6\. Add the server clause**
if __name__ == '__main__':
app.run_server(debug = True)****
我们用 app.py 的名字保存这个脚本,在终端上导入(或者 anaconda 提示)。请注意,工作目录应该与您保存文件的位置相同。
**C:\Users\jjone\Dash> **python app.py****
如果没有打字错误或语法错误,您将看到本地主机地址。你可以复制并粘贴它,也可以在一个新的网页标签上输入 localhost:8050 。
添加页眉和段落
我们也可以有额外的组件,如把文本数据就像 HTML。你可以在 这里找到 有哪些元素。让我们在页面上放一个简单的标题和一段文字。
****# Step 4\. Create a Dash layout**
app.layout = html.Div([
** # adding a header and a paragraph
html.Div([
html.H1("This is my first dashboard"),
html.P("Learning Dash is so interesting!!")
],
** style = {'padding' : '50px' ,
'backgroundColor' : '#3aaab2'}),# adding a plot
dcc.Graph(id = 'plot', figure = fig)
])**
在图表的顶部,我将再添加一个部分,并在其中添加一个标题和一个段落。我们有两个主要组件,html.Div
里面的html.Div
和dcc.Graph.
,还有两个附加组件,头(html.H1
)和段(html.P
)。我们可以用style
属性改变组件的边距或背景色。它应该以支持 CSS 属性的字典格式来指定。
请特别注意括号的开始和结束位置。理解每个部分的段落范围是很重要的。由于有太多的括号和方括号,一开始可能会令人困惑。而且很容易犯语法错误。
将步骤 4 中的代码放入我们的模板中,看看结果。如果服务器没有关闭,我们可以通过按 F5 来检查结果。
现在,我希望您了解什么是 dash 组件和 HTML 组件。如何将多个组件放在一起,以及如何将它们放在一起。
下拉式
这次让我们试着在仪表板上做一个下拉菜单。我们要做另一个图,根据给定的选项改变它的 y 轴。通过 仪表板组件 的文档,我们可以把Dropdown
整理如下。
如您所见,这些选项应该是字典格式的。在我们的例子中,选项将是只有连续变量的列,从第 2 列到第 10 列。有了列表理解,我们可以只用一行就做出选项字典。
**features = st.columns[1:-1]
opts = [{'label' : i, 'value' : i} for i in features]**
现在,我们将下拉组件放在绘图的底部,而不是 HTML 组件。先看一下粗体字,因为其他的只是为了修饰两大应用。value
是下拉列表的默认值。
****# Step 4\. Create a Dash layout**
app.layout = html.Div([
# adding a plot
dcc.Graph(id = 'plot', figure = fig), **# dropdown**
**html.P([
html.Label("Choose a feature"),
dcc.Dropdown(id = 'opt',
options = opts,
value = opts[0])
],** style = {'width': '400px',
'fontSize' : '20px',
'padding-left' : '100px',
'display': 'inline-block'}**)**
])**
正如我们之前所做的,我们可以在模板的第 4 步替换这段代码,并检查结果。
使用回调连接到图形
为了根据下拉列表的选择更新图表,我们需要在输入数据(下拉列表)和输出数据(图表)之间建立一个连接。这将通过在步骤 5 中添加一个回调函数来完成。
****# Step 5\. Add callback functions**
[**@app**](http://twitter.com/app)**.callback(**Output('plot', 'figure'),
[Input('opt', 'value')]**)****
你记得我们给每个组件赋予了什么id
吗? 剧情 和 opt。 因此我们可以如上图用它们的名字来称呼它们。我们从名为 opt 的选项中获取输入数据,并将输出给名为 plot 的线图。
****def** update_figure(**X**):
trace_2 = go.Scatter(x = st.Date, **y = st[X],**
** name = X,**
line = dict(width = 2,
color = 'rgb(106, 181, 135)'))
fig = go.Figure(data = **[trace_1, trace_2]**, layout = layout)
**return** fig**
因此,我们获取输入数据,然后通过创建更新函数返回我们想要的输出。这里我们的输入数据是什么?这是从下拉列表中选择的特征变量的名称,我们将它作为第二个线图的 y 轴。由于trace_1
没有什么可改变的,我们可以简单地将trace_2
添加到数据表中。现在,如果我们将这段代码放到第 5 步,并重新运行 app.py ,结果将如下所示。
在我们进入下一部分之前,我想简单谈一下回调。因为这是编写任何应用程序时最常用的函数之一。简单来说, 一个回调是一个在得到真正的执行命令之后再执行的函数,通常是为了更新。回调函数在得到命令后不能正常工作。这些人都是那种。它们将在其他命令行被执行后执行它们的工作,我们 调用 它们 返回 再次执行真正的命令。**
看看我们的update_figure()
是怎么运作的。这个函数不是在我们导入这个应用程序之后,而是当我们通过“回调”给它实际的输入信号时,才开始工作这就是回调函数。你在不理解的情况下使用它是完全没问题的,但是我希望你在这里得到粗略的想法。你还可以从 这篇文章 中找到更多细节。
范围滑块
最后,让我们尝试一个范围滑块。我们将在图中添加一个年范围滑块,它非常类似于下拉菜单。让我们先查看一下 dash 组件的文档。
我想这时候你很容易就明白了。这里的重点是如何做一个标记字典。由于日期周期从(2015 年 2 月 17 日 ) 开始到(2017 年 2 月 17 日),我想在周期之间添加 7 个标记,如下所示。
****st['Date'] = pd.to_datetime(st.Date)
dates = ['2015-02-17', '2015-05-17', '2015-08-17', '2015-11-17',
'2016-02-17', '2016-05-17', '2016-08-17', '2016-11-17',
'2017-02-17']date_mark = {i : dates[i] for i in range(0, 9)}****
现在,我们可以简单地将范围滑块放在下拉位置。这次我把它命名为 滑块 。min
和max
是滑块的最小值和最大值,value
是滑块的默认设置。同样,所有其他部分都在设计 CSS 样式的 HTML 组件。
******# Step 4\. Create a Dash layout**
app.layout = html.Div([
# adding a plot
dcc.Graph(id = 'plot', figure = fig), **# range slider
html.P([
html.Label("Time Period"),
dcc.RangeSlider(id = 'slider',
marks = date_mark,
min = 0,
max = 8,
value = [3, 4])
],** style = {'width' : '80%',
'fontSize' : '20px',
'padding-left' : '100px',
'display': 'inline-block'}**)**
])****
做好滑块后,下一步会做什么?把它和剧情联系起来!我们将再次从 滑块 获取输入数据,并将输出返回到 图 。但是这一次,这里有个小技巧。您是否注意到滑块的默认值是一个包含两个值的列表?请再次检查上面的代码框。**value = [3, 4]**
与下拉组件不同,范围滑块在一个列表中接受两个值。因此,当我们从滑块中获取输入数据时,会有两个值,即起始值和结束值(**X[0]**
、**X[1]**
)
******# Step 5\. Add callback functions**
[@app](http://twitter.com/app).callback(Output('plot', 'figure'),
[Input('slider', 'value')])
**def** update_figure**(X):**
**st2 = st[(st.Date > dates[X[0]]) & (st.Date < dates[X[1]])]**
trace_1 = go.Scatter(x = st2.Date, y = st2['AAPL.High'],
name = 'AAPL HIGH',
line = dict(width = 2,
color = 'rgb(229, 151, 50)'))
fig = go.Figure(data = [trace_1], layout = layout)
** return** fig****
现在我们将过滤数据,并用给定的周期更新图形。如果你再次重新加载网页,你会看到与我相同的结果。
把这些都集中起来!
我想到目前为止我们已经谈了很多了。从放图到添加回调。现在我们为什么不把它们都放在一起?我建议你一步一步地阅读下面的脚本。您可以在第 4 步中组合图形、下拉菜单和滑块的所有组件,就像替换和更改模块一样。
你准备好看看我们的第一个仪表板是什么样子了吗?😃😃🙌🙌嘣!!
干得好!你喜欢制作仪表板吗?我希望现在您能够轻松阅读和理解其他 Dash 代码示例。Dash 提供了这样一个不错的指南 所以你可以随意探索自己的网站。你也可以通过 Plotly 查看 Dash 中的其他内容。
用纯 Python 创建反应式 Web 应用程序
medium.com](https://medium.com/@plotlygraphs/introducing-dash-5ecf7191b503)
部署
到目前为止,我们的应用程序本地托管在您的机器上。为了让其他人也能使用它,我们需要将它部署在 web 上。有两个选择可以选择,App 授权和部署到 Heroku。第一种要求我们安装[dash-auth](https://github.com/plotly/dash-auth)
包,有两种不同类型的认证,HTTP Basic Auth 或 Plotly OAuth。可以按照 这个教程 。
另一种方式是通过使用 Heroku、AWS、Google 云平台等托管平台。Heroku 是最简单的部署方式,你可以按照这个教程。其他平台和 Heroku 一样。
除了下拉菜单,我们还可以添加其他组件,如文本区或上传数据。我建议你去了解一下有什么样的仪表板部件。您还可以添加任意数量的 HTML 组件,并使用 CSS 属性以更奇特的方式设计您的电路板。如果你需要学习一些 HTML & CSS 的基础知识,可以从 这个视频 中找到很棒的教程。
感谢您的阅读,希望您对这篇文章感兴趣。如果有什么需要改正的地方,请和我们分享你的见解。我总是乐于交谈,所以请在下面留下评论,分享你的想法。我还在 LinkedIn 上分享有趣和有用的资源,所以请随时关注并联系我。下次我会带着另一个有趣的故事回来的!******
竞争数据科学一瞥:计算机视觉的最佳实践
几种技术如何显著提高计算机视觉模型的性能…
简介
S 由于 2019 年 Freesound Audio Tagging 比赛即将结束,我决定写一篇关于构建健壮的计算机视觉模型的最佳实践和技术的文章,这是我在第一次研究比赛中学到的。此外,这是我把我的想法写在纸上的一种方式,以便对我所使用的技术有一个结构化和清晰的概述:那些工作良好的技术,以及那些没有在我的测试分数中产生显著提高的技术。
作为 Fast.ai 课程的学生,我仍然处于学习旅程的最开始,因此一些评论或技术对于最有经验的人来说可能是显而易见的。
像往常一样,我强烈鼓励你纠正我,如果你不明白或者不清楚,可以问我关于某一点的精确信息,并在评论区添加一些评论!
我从这场比赛中得到的最重要的教训是,建立稳健的模型是一个经验过程。竞争有助于培养直觉,知道什么可行,什么不可行。
给你一点背景,比赛是关于标记(分类)声音(猫,瀑布,乐器…)。我们得到了两个数据集:一个是有清晰可闻声音的精选数据集(4970 个条目),另一个是有声音和一些背景噪音的嘈杂数据集(20000 多个条目)。请注意,每个数据条目都有多个标签。两个数据集都被标记了。竞赛的主要挑战(由绝大多数参与者确定)是正确使用有噪声的数据集来提高精选数据的性能。
我们根据一个定制的指标进行评估:标签加权的标签排名平均精度也缩写为 lwlrap 。为了给出一个尺度,前 1%的人获得了 0.76 的 lwlrap 分数,而铜牌(大约前 12%)以 0.695 或更高的分数被授予。我在第一阶段的最终分数是 0.686,这使我进入了前 15%。
快速傅立叶变换和 melspectrograms:如何将声音输入 CNN?
从文章开始,你可能会觉得我同时谈论计算机视觉和声音很奇怪。事实上,声音是由多个样本组成的基于时间的信号。起初,我们可能认为可以通过应用 1D 卷积将声音输入到 CNN 或 LSTM 的模型中。然而,这两种技术都没有产生令人信服的结果,可能是由于将原始信号输入到模型中导致了信息的丢失。馈送原始信号会导致信息(即频率、相位、幅度)的显著损失。经过一些实验,两个模型都没有产生超过 0.5 的 lwlrap。相当失望…
作为音频分类的经验法则,我们通常将声音转换成更全面的 2D 表示(图像),然后输入 CNN。这种表示可以是 MFCC、色度、CQT 变换,但通常是梅尔频谱图。
Mel-spectogram of a signal plotted with Matplotlib
为了理解 mel 谱图是如何产生的,我们首先需要理解傅立叶变换对信号的影响。快速傅立叶变换(具有比原始版本 o(n2) 更低复杂度 o(nlogn) 的修改算法)将声音信号从其原始时域转换成频域中的表示。
然后我们应用一系列变换,最终得到 mel 谱图表示。
为了执行这一系列的计算,你可以直接使用 librosa 库,该库具有各种功能来执行音频特征分析。
附加阅读:
- 如何在 Python 中把声音转换成图像(Daisukelab 的一个内核):https://www . ka ggle . com/Daisukelab/creating-fat 2019-预处理-数据
- 一套用于音频特征分析的笔记本:https://musicinformationretrieval.com/
- Python 中的 librosa 库介绍:https://towardsdatascience . com/audio-classification-using-fastai-and-on-the-fly-frequency-transforms-4 dbe1 b 540 f 89
Mixup:在计算机视觉中实现艺术效果的必备工具
简单地说,Mixup 是一种数据扩充技术,它根据两个现有数据条目之间的线性关系创建新的数据条目。我们可以用这个公式来总结。γ是代表图像的张量,而λ是权重。
相同的变换应用于目标:
它只不过是两个现有数据样本的加权平均值。例如,我们可以选择λ = 0.7。如果γ1 代表狗,γ2 代表猫,new 将代表介于狗和猫之间的东西,比猫离狗更近。如果你查看与 new 相关的图像,它可能对你没有太大意义,但对计算机来说,它显然看到了一只狗,因此增加了狗的图像数据集。
Representation of a new data sample using Mixup (credits: Fast.ai)
如您所见,通过从其他现有数据创建新数据,它可以作为一种强大的数据扩充技术。当你不得不处理阶级不平衡时,这是特别有用的。在这次比赛中就是这种情况(有些品牌出现的次数很少,而其他品牌出现的次数很多)。这个技巧帮助我打破了 0.6 lwlrap 的障碍,在排行榜上从 0.57 上升到 0.65(结合下面列出的其他技巧)。
附加阅读:
- Mixup 原创研究论文:【https://arxiv.org/abs/1710.09412
- 关于 Mixup 的 Fast.ai 文档:【https://docs.fast.ai/callbacks.mixup.html
训练时间增加(TTA):在排行榜中排名
训练时间增强(TTA)是一种在推理过程中使用的数据增强技术。TTA 的结果是通过计算标准预测和应用数据扩充技术后对数据集所做预测的加权平均值而获得的。不出所料,这个等式看起来与 Mixup 非常相似:
默认情况下,在 fast.ai 中,beta 系数设置为 0.4。
注意,在 Fast.ai 中,TTA 计算对数概率(因此是负数)。要从对数概率转换到“标准”概率(介于 0 和 1 之间),只需计算概率的指数。
您可能面临的唯一问题是模型计算预测需要多长时间。特别是如果您的模型的执行有严格的时间限制,TTA 可能不是一个好的选择。
至于比赛,它让我从排行榜上的 0.57 跳到了 0.65。
迁移、半监督学习和伪标记
迁移学习包括用数据集预先训练你的模型,然后用最接近你想要预测的数据集训练它。这是一种允许更快训练和更稳定地收敛到损失最优值的技术。无论是计算机视觉还是基于 ULMFiT 模型和 BERT 的 NLP,它现在都是业界的标准。
对于这场比赛,我选择首先在嘈杂的数据集上预先训练我的模型,最终在精选的数据集上训练我的模型。在我的本地机器上,我的 lwlrap 从 0.81 跳到 0.83。让我们回想一下,比赛不允许预先训练的模型。
我尝试的第二种技术是半监督学习。如果您有大量未标记的数据,这将非常有用。它有助于模型映射数据分布,并允许更快的收敛。
你最好花一周时间来标记额外的数据,而不是试图创建一个模型来完美地利用未标记的数据。
我选择了一种特殊类型的半监督学习:伪标记噪声数据。在这个比赛中,我们有大量的噪音数据。我没有使用现有的标签,而是在精选的数据上训练了一个模型,然后使用这个模型来标记我的噪音数据。最终,通过将筛选的数据与新标记的噪声数据相结合,我重新训练了我的模型。尽管由于精确的数据分布和有噪声的数据分布之间的太大差异,它没有显著地提高性能,但是在集合模型中,性能提高了。
我可能会总结说,你最好花一周的时间来标记额外的数据,而不是试图创建一个模型来完美地利用未标记的数据。当你有大量的备用数据时,半监督学习技术通常是合适的。
补充阅读:
- 伪标签综合讲解:https://www . analyticsvidhya . com/blog/2017/09/pseudo-labeling-semi-supervised-learning-technique/
K-Fold 交叉验证和 bootstrap 聚集:在推理过程中获得更稳定的结果
由于 K-Fold CV 在 fast.ai 课程中并没有特别突出,虽然是业界标配,但我并没有真正用过。让我快速回忆一下 K 折叠验证的定义:这是一种技术,通过这种技术,数据集被分成 K 个折叠,模型在 K-1 个折叠上被训练,并在剩余的折叠上被验证。显然,由于每个模型都有不同的验证集,所以会生成 K 个模型。这导致更准确和一致的性能评估。
A 5-fold cross-validation scheme
但是除了作为一种验证技术之外,K-Fold CV 还可以在一个集合模型中使用(对它的花哨说法是 bootstrap aggregating )。这个词隐藏了相当直接和简单的技术,例如,你计算你的 K 倍所有预测的平均值。这种集成技术通常会产生更好的性能,因为整个模型的方差大大减少了。
集合技术:进入前 5%的关键
我的上一段介绍了竞争数据科学的一个关键概念,广义地说是创建更精确的模型的概念:集成的概念。简而言之,集成是一种将几个模型放在一起以获得预测稳定性和精确度的技术。
为了实现一个高性能的集合模型,你需要检查所有模型之间的相关性。相关性越低越好。为此,您需要计算皮尔逊相关系数:
其中 σ 是统计数列的标准差, cov(X,Y) 是统计数列 X 和 Y 的协方差。
通过结合一个简单的基线模型和一个迁移学习模型以及一个伪标签模型,我获得了排行榜上的最高分:0.686。
说清楚一点,统计数列 X 和 Y 是你的模型的预测。对于您创建的每个模型,您可以计算彼此之间的皮尔逊相关系数,并丢弃高度相关的模型。
在这个竞赛中,X 和 Y 将是包含属于每个数据样本的 80 个类别之一的概率的矩阵。
至于比赛,组合技术显然给了我接近铜牌的机会。通过结合一个简单的基线模型和一个迁移学习模型以及一个伪标签模型,我获得了排行榜上的最高分:0.686。
补充阅读:
- 集成技术综合指南,从平均到叠加概括:https://mlwave.com/kaggle-ensembling-guide/
从这里去哪里?
现在是时候对我的表现采取批评的观点了。因为我没有赢得奖牌,所以有很多事情我可以改进。我会强迫自己去思考那些我本可以用不同的方式去做的事情,或者除了我已经做的事情之外。
了解你的数据
可能我最严重的错误在于太急于建立一个模型,而不是首先试图理解数据分布和噪声数据集中的噪声种类。它会让我更好地理解预测标签的难度,并阅读更多相关的研究论文,以消除或减弱轨道上的噪声。
阅读研究论文
这是一个提醒,而不是一个真正的批评家。我发现资本阅读研究论文是如何应用技术,导致最先进的结果。随着人工智能的民主化,得益于 fast.ai 课程等 MOOC(再次感谢杰瑞米·霍华德、雷切尔·托马斯和项目背后的出色团队),越来越多的人可以获得强大的机器学习工具,使他们更接近最先进的结果。阅读研究论文并能够实现所描述的一些技术是一种无价的额外津贴,因为它使你从竞争中脱颖而出。很少有人愿意花时间去阅读它,更少有人去实现一种技术。
我为自己设定的一个主要目标是对 Fast.ai 及其底层框架 PyTorch 足够熟悉,以实现并将我自己的代码片段集成到库中。例如,我偶然发现了这篇研究论文https://arxiv.org/abs/1905.02249,它看起来非常有效,因为据说 MixMatch 可以将错误率降低 4 倍。然而,经过多次尝试,我还不能实现它。
使用更多数据通道?
我们之前已经讨论过 mel 光谱图以及它们是如何产生的。但是可以创建其他音频表示,即色度、梅尔频率倒谱系数(MFCC)、常数 Q 变换等等。所有这些表示强调音频信号的不同方面:一个可能探索振幅,而另一个可能探索相位或频率。例如,我无法建立一个足够准确的模型来从阶段中学习。我坚信这是比赛的关键,因为它给光谱模型增加了更多的信息。
更复杂的集成技术:加权平均、堆叠概括?
当使用集合技术时,我没有以复杂的方式组合我的模型。我只是计算了我所有预测的平均值。我本可以通过选择适当的权重来微调我的模型,从而为我的预测创建一个加权平均值。
我甚至可以尝试使用一个神经网络,它将来自不同模型的所有预测作为输入,并试图检测不同预测之间的非线性模式。一个挑战是数据限制。我们在精选的数据集中只有 4970 个数据样本,删除 1000 个数据样本会适得其反,因为 4970 已经是一个很低的数字了。
我没有尝试在嘈杂的数据集上训练我的模型,因为这个分布离测试集太远了。
结论
因为文章已经很长了,所以我的结论会很简短(如果你还在的话,恭喜你,谢谢你)。我从这场比赛中得到的最重要的教训是,建立稳健的模型是一个经验过程。你尝试,你失败,你思考,你改正。为了变得擅长,你必须尝试很多事情。竞争有助于培养直觉,知道什么可行,什么不可行。
我希望你喜欢这篇文章。同样,如果我在某些方面有错,请不要犹豫地纠正我,或者在评论区分享你对比赛的想法。
https://www.kaggle.com/rftexas
一瞥新披露的亚马逊欺诈探测器
几天前,在亚马逊网络服务(AWS) re:Invent 2019 上,亚马逊宣布了其检测交易异常的完全托管服务亚马逊欺诈检测器(Amazon Fraud Detector)。这项服务目前处于预览阶段,当我的预览应用程序被批准后,我迫不及待地尝试了一些欺诈检测样本。
亚马逊欺诈检测器最棒的一点是,你不需要任何人工智能专业知识来构建欺诈检测模型。只需获取一些历史数据,亚马逊欺诈检测器就会使用这些数据来自动训练、测试和部署定制的欺诈检测模型。
要开始,我总是推荐官方文档,这是一个很好的起点。您可以通过本文末尾提供的参考链接来了解更多关于 Amazon Fraud Detector 的信息。
对于那些仍在急切等待预览的人来说,本文将简要介绍该服务,并介绍构建欺诈检测模型和运行欺诈预测测试的步骤。
我使用了一些从 https://mockaroo.com/生成的假数据,这是一个很好的生成真实数据的工具。还可以使用开源的 faker . js(https://github.com/marak/Faker.js/)库,在 Node.js 和浏览器中生成海量的虚假数据。
准备好数据集后,将 csv 文件上传到 S3 存储桶,控制台将为您提供创建或选择现有 IAM 角色来访问培训数据集的选项。
Defining data location
亚马逊欺诈检测器模型模板提供了三种模板选项,其中“在线欺诈洞察”模型目前可用。其他两个模型模板,即用于识别受损账户的账户接管和用于捕获欺诈交易的交易欺诈,正在未来发布的路线图中。
在线欺诈洞察模型模板需要下图所示的输入来创建模型。我们需要确保我们的数据集包括电子邮件、时间戳、IP 和欺诈标签字段。
下一步,我们需要配置我们的模型,以确保输入正确地映射到选定的变量。
第三步,也是最后一步,允许我们查看模型细节,最后单击以创建和训练我们的模型。该模型在部署之前需要一些时间来训练。在这种情况下,为具有 20,000 行的数据集定型大约需要 20 分钟。
一旦培训成功,我们就可以部署这一特定版本的模型来配合我们的检测机使用。在下图中,我们看到了模型性能和一个表格表示,以帮助我们确定在编写评估事件的规则时应该使用哪个模型阈值。
一旦我们成功部署了欺诈检测模型,我们将在本次动手演示的第二部分创建一个检测器。创建检测器有五个简单的步骤,首先是为我们的检测器定义一个名称。
在第二步,添加我们在本演示的第一部分中创建的模型。
在此之后,我们可以添加规则来解释我们的亚马逊欺诈检测器模型的分数。这里,我们将需要提供规则表达式和一个结果,该结果将是欺诈预测的结果,如果在评估期间规则匹配,则返回该结果。
我们可以在下图中看到一个样本规则表达式。
一旦我们创建了规则,我们就可以定义执行规则的顺序,这些规则将影响最终的欺诈预测结果。在这种情况下,我们将“高欺诈风险”规则移到了第一个位置。
在最后一步,我们可以查看规则,最后,继续创建检测器。
一旦我们的检测器被创建,我们可以使用一些样本事件数据来测试我们的检测器的逻辑。正如我们在下图中看到的,测试的结果是“审查”,模型得分为 688。因此,我们的模型现在已经准备好了,我们可以将其用于实时欺诈检测。
结论
凭借 AWS 和 Amazon.com 20 年的欺诈检测专业知识,在数毫秒内自动识别潜在的欺诈活动,亚马逊欺诈检测器抽象出了复杂的机器学习,以提供一个简单易用的界面,供每个人使用。目前,虽然此服务仅限于少数模型模板,并且仅在 AWS 美国东部(N. Virginia)地区提供,但我们可以预计,随着欺诈管理需求的不断增长,此服务将会增长。
参考
https://aws.amazon.com/fraud-detector/
https://docs . AWS . Amazon . com/fraud detector/latest/ug/what-is-fraud detector . html
张量流一瞥
TensorFlow 是 Google 的一个流行的开源软件库。最初它是由谷歌大脑团队开发的,供谷歌内部使用。随着 AI 研究社区变得越来越协作,TensorFlow 在 Apache 2.0 开源许可下发布。
张量流的详细研究可能需要几个月的时间。但是对它的力量的一瞥提供了一个很好的动力去钻研它。考虑到这一点,这篇博客着眼于分类模型的实现。
分类是我们在人工智能工作中经常遇到的问题之一。通常,我们有一组输入,这些输入必须分类到不同的类别中。我们可以使用 TensorFlow 为这项任务训练一个模型。下面,我们将逐步介绍一个这样的实现的每个步骤。
导入模块
重要的事情先来!TensorFlow 是一个外部库,需要导入到脚本中,我们才能使用它。
除了 TensorFlow,我们通常还会导入一些其他的库,让我们的生活更加简单。Keras 是 TensorFlow 的一部分。它帮助我们非常容易地开发高阶模型。我们可以单独使用 TensorFlow 来创建模型。然而,Keras 简化了我们的工作。
NumPy 是任何机器学习任务中的默认导入。没有它我们无法生存。几乎所有的数据操作都需要 NumPy。
另一个重要的模块是 matplotlib。这是非常重要的,我们可视化的可用数据,以获得隐藏在其中的感觉。任何数量的算法分析都不能给我们仅仅通过图形形式查看数据所得到的东西。
TensorFlow 在版本上经历了一些变化。概念没有变,但是一些方法变了。检查我们使用的版本是一个很好的做法。如果我们有问题,我们可以查看特定版本的帮助。由于版本冲突,许多开发人员都面临这个问题,所以在论坛上搜索特定版本的问题要简单得多。
以下代码基于版本 1.12.0
MNIST 数据集
为了分享基本思想的简要介绍,我们可以研究一个简单问题的实现。MINST(改进的国家标准和技术研究所)给出了从 0 到 9 的手写数字的良好数据集。我们可以利用这一点来训练神经网络,并建立一个可以读取和解码手写数字的模型。
这个问题通常被称为深度学习的“Hello World”。当然,我们需要更多来开发“真正的”应用程序。这很好的给你介绍了题目。当然,TensorFlow 还有更多的功能。如果你对详细的研究感兴趣,你可以参加在线课程。
加载数据
第一步是加载可用数据。TensorFlow 为我们提供了一套很好的测试数据集,可以用来学习和测试。MINST 数据集也在这些数据库中提供。因此,在这种情况下,获取训练和测试数据的工作非常简单。在现实生活问题中,积累、清理和加载这样的数据是工作的主要部分。这里我们只用一行代码就完成了。
这给了我们四个张量——train _ images、train_labels、test_images 和 test_labels。load_data()方法本身负责将可用数据分成训练集和测试集。在现实生活问题中,我们必须自己解决这个问题。但是,对于示例代码,我们可以使用 TensorFlow 数据集的可用方法。
检查数据
作为一种最佳实践,应该总是看一眼可用的数据。
我们知道我们正在解决一个图像分类问题。所以让我们试着看看这些图像是什么样子的。
在这个阶段,我们可以做很多事情。但是对于像这样已经清理过的数据来说,这没有什么意义。
数据净化
下一步是改变可用数据,使其更适合训练模型。
图像数据自然是二维的。这对于查看图形来说可能非常好。但是,为了训练神经网络,我们需要一维记录。这需要“扁平化”数据。Keras 为我们提供了简化模型中数据的简单方法。但是对于一般的预处理,最好是立即将其展平。TensorFlow 也提供了这一功能。
我们的标签是用数字 0-9 来表示的。但是,作为神经网络的输出,这些值没有数字序列。也就是 1 小于 9。但是当我们阅读图像时,这种关系一点也不重要。这些输出之间的数字关系只是偶然的。对于应用程序,它们只是 10 个不同的标签。它们是分类输出。
为此,我们需要将这些标签映射到 10 个不同的由 1 和 0 组成的独立数组中。我们需要将每个标签映射到一个由 10 个二进制数组成的数组中——0 代表所有的值,1 代表特定的值。例如,1 将被映射到[0,1,0,0,0,0,0,0,0,0];8 会映射到[0,0,0,0,0,0,0,0,1,0]等等。这就是所谓的“分类”输出。
另一个非常重要的任务是将数据标准化。激活函数— relu 或 sigmoid 或 tanh…当这些数字小于 1 时,它们都可以最佳地工作。这在任何神经网络中都是重要的一步。错过这一步对模型效率有非常不好的影响。
这是训练神经网络中非常简单但重要的一步。同样,如果是一些原始数据,我们将需要更多的清理工作。但是这个已经被 Keras 消毒了。
数据扩充
我们拥有的资源总是少于我们所需要的。数据也不例外。为了取得越来越好的结果,我们需要比我们现有的更多的东西。我们可以从现有的资源中生成更多的数据——利用我们对数据的了解。例如,我们知道,如果整个图像向两边移动一个像素,数字不会改变。
因此,输入集合中的每个图像可以通过在每一侧移动一个像素来生成另外四个图像。这些图像在我们看来几乎没有变化。但是对于神经网络模型来说,它是一个新的输入集。这个简单的信息可以给我们 5 倍的数据。就这么办吧。
如果你不能理解上面的代码,不要担心。有关使用 NumPy 数组的详细信息,请参考 NumPy 博客。
本质上,这段代码只是删除了图像一侧的单元格,并在另一侧插入了 0。它从训练数据中每个图像的所有四个边进行计算,然后将其附加到名为 augmented_images 的新数组中。除此之外,它还构建了 augmented_lables 数组。
现在,我们可以使用 augmented_images 和 augmented_labels 代替 train_images 和 train_labels 来训练我们的模型。但是,等一下。如果我们仔细想想,数据就不再是随机的了。我们有一个巨大的数据块,图像在中心,后面是一个巨大的数据块,图像在每个方向上移动。这样的数据并不能创造出好的模型。我们需要通过很好地重组数据来改善这一点。
但这并不那么简单。现在,我们有一组图像和一组标签。我们必须洗牌。但是,通信不应该丢失。洗牌后,5 的图像应该指向标签 5!
NumPy 确实为我们提供了一种优雅的方式。
本质上,我们将两者结合成一个单一的实体,然后以图像和标签一起移动的方式对其进行洗牌。
训练模型
一切就绪后,我们现在可以开始训练模型了。我们从创建一个 Keras 序列模型开始。我们可以在下面的博客中查看不同类型模型的详细信息。让我们给这个模型添加三层。Keras 允许我们添加许多层。但是对于这样的问题,3 层就足够了。
784 的输入形状由输入数据中每个实体的大小定义。我们每个人有 784 张照片。这就是我们开始的地方。输出大小必须是 10,因为我们有 10 种可能的结果。典型的网络在最后一层具有 softmax 激活,在内部隐藏层具有 relu。
每层的大小只是基于判断的估计。我们可以通过实践、经验和对神经网络工作原理的理解来发展这种判断。您可以尝试使用这些工具,看看它们对模型效率的影响。
接下来,我们使用可用的数据来编译和训练模型。
编译器的参数——loss = " categorial _ cross entropy "和 optimizer = TF . train . adamoptimizer()可能看起来模糊不清。你可以查看一下深度学习上的博客,以便更好地理解它们。
方法 model.fit()使用我们拥有的数据完成训练模型的实际工作。
这会生成一个输出:
我们可以看到,随着每次迭代,损耗减少,精度提高。请注意,输出可能不总是精确匹配,这是因为随机播放和训练的随机性。但趋势应该差不多。
评估模型
现在我们有了一个训练好的模型,我们需要评估它有多好。第一个简单的步骤是使用 TensorFlow 自己的评估方法进行检查。
那很好。考虑到我们拥有的数据量,这是一个很好的精确度。但是,这个测试是不够的。非常高的精度也意味着过度拟合。所以一定要用测试数据来核对。
人们可以注意到,测试数据的准确性稍低。这意味着轻微的过度拟合。但这还不算太糟。所以我们暂时可以接受。在真实的例子中,根据需要,可以尝试调整模型形状和其他超参数以获得更好的结果。
抽样
这可能不会给我们所需要的所有信心。我们可以手动检查一些样本,看看我们的模型表现如何。
首先,为所有测试图像创建预测。现在,预测是一个包含测试集中所有图像输出的数组。
我们可以看到,这个集合中的每个元素都是一个 10 个元素的数组,它显示了输入图像属于给定标签的概率。当我们检查预测集中的第 0 个元素时,我们可以看到除了元素 7 之外,所有元素的概率都很低,几乎为 1。因此,对于图像 0,人们会预测 7。
现在让我们看看测试图像是什么样子的。在此之前,我们需要重塑图像数组(还记得我们为构建模型制作了一个一维数组吗?现在让我们检查第 0 个元素
这确实是 7!
只有一个正确答案肯定不足以验证模型的准确性。但是,此时我们可以检查的一点是预测[0]数组中的值。元素 7 中的值远远大于其他值。这意味着,模型对结果是绝对确定的。毫无疑问是因为两个类似的结果。这是一个好模型的重要特征。
Kaggle 座头鲸识别挑战的金牌解决方案综述
对最引人注目的方法的广泛而简单的回顾
Photo by Sandra Seitamaa on Unsplash
最近,我的团队参加了在 Kaggle 举办的座头鲸识别挑战赛。我们赢得了一枚金牌,并在排行榜上(在 2131 支队伍中)名列第十。
在这篇博文中,我将总结我们解决方案的主要思想,并简要概述其他团队使用的有趣且吸引人的方法。
问题描述
主要目标是确定,给定的这张鲸侥幸的照片是属于 5004 种已知鲸鱼个体中的一种,还是一种以前从未观察到的新鲸鱼。
Example of 9 photos of the same whale from the training data
这场竞赛令人困惑的一面是巨大的阶级不平衡。对于 2000 多个班级,只有一个训练样本**,这使得很难使用开箱即用的分类方法。更重要的是,对鲸鱼是否是新品种进行分类是比赛的重要组成部分,这被证明是相当重要的。**
Class Imbalance, from kernel by
Tomasz Bartczak
比赛的衡量标准是 mAP@5 (平均精度为 5),这允许我们为每张测试图像提交多达 5 个预测。我们在私有测试集上的最高成绩是 0.959 mAP@5。
萨纳科耶乌,普莱斯科夫,沙克雷💪
该团队由海德堡大学博士生弗拉迪斯拉夫·沙克雷(我)阿奇奥姆·萨纳科耶乌和 Kaggle Top-5 特级大师帕维尔·普莱斯科夫组成。
为了加快实验速度,我们在比赛中途加入了 Artsiom,Pavel 在团队合并截止日期前一周加入了我们。
验证和初始设置
在这场比赛的几个月前,同一场比赛的游乐场版本在 Kaggle 上举办,但是,正如比赛主办方提到的,真实(非游乐场)版本的特点是更多的数据和更干净的标签。我们决定以多种方式利用之前比赛的知识和数据:
- 利用之前比赛的数据,我们使用图像哈希收集了【2000 多个验证样本。后来,当我们验证我们的套装时,这被证明是至关重要的。
- 我们从训练数据集中删除了 new_whale 类,因为它的元素之间不共享任何逻辑图像特性。
- 有些图像根本没有对齐。幸运的是,有一个公开可用的预先训练的边界框模型,用于操场竞赛的获胜解决方案。我们用它来检测鲸鱼爪周围的精确边界框,并相应地裁剪图像。
- 由于图像的颜色不同,所有数据在训练前都被转换为灰度。
方法 1:连体网络(Vladislav)
我们的第一个架构是一个暹罗网络,有许多分支架构和客户损耗,由许多卷积层和密集层组成。我们使用的分支架构包括:
- ResNet-18,ResNet-34,Resnet-50
- SE-ResNeXt-50
- 马丁·皮奥特公开分享的类 ResNet自定义分支
我们使用硬负以及硬正挖掘,通过每 4 个时期对分数矩阵求解线性分配问题。为了简化训练过程,在矩阵中加入了少量的随机化。
采用渐进式学习,分辨率策略 229 x229->384 x384->512 x512。也就是说,我们首先在 229x229 图像上训练我们的网络,几乎没有正则化,学习率更大。收敛后,我们重置学习率并增加正则化,从而在更高分辨率(例如 384x484)的图像上再次训练网络。
此外,由于数据的性质,使用了大量的增强,包括随机亮度、高斯噪声、随机裁剪和随机模糊。
此外,我们追求智能翻转增强策略,这大大有助于创建更多的训练数据。具体来说,对于属于同一鲸鱼X, Y
的每对训练图像,我们多创建了一对训练图像flip(X), flip(Y)
。另一方面,对于每一对不同的鲸鱼,我们又创造了三个例子flip(X), Y
、Y, flip(X)
和flip(X), flip(Y)
。
An example showing that random flips strategy does not work with a pair of same-whale photos. Notice how the lower photos become different when we flip one of the images since we care about fluke orientation.
使用 Adam optimizer 对模型进行优化,初始学习率为 1e-4,在平稳状态下减少了 5 倍。批量大小被设置为 64。
模型的来源写在 Keras 上。在单个 2080Ti 上训练约 400-600 个历元的模型需要 2-3 天(取决于图像分辨率)。
拥有 ResNet-50 的表现最好的单一模型得分 0.929 磅。
方法 2:度量学习(Artsiom)
我们使用的另一种方法是利用利润损失的度量学习。我们使用了许多 ImageNet 预训练的主干架构,包括:
- ResNet-50,ResNet-101,ResNet-152
- DenseNet-121、DenseNet-169
网络主要使用 448×448-> 672×672 策略逐步训练。
我们使用 Adam 优化器,在 100 个时期后将学习率降低 10 倍。我们还在整个培训中使用了 96 的批量大小。
最有趣的部分是什么让我们立即获得了 2%的提升。这是一种度量学习方法,由 Sanakoyeu,Tschernezki 等人开发,并在 2019 年 CVPR 上发表。它所做的是每隔n
个时期就将训练数据和嵌入层分成k
个簇。在建立了训练组块和学习者之间的双射之后,该模型在累积分支网络的梯度的同时单独训练它们。你可以在这篇论文发表的时候在这里查看它和代码。
“分而治之,嵌入度量学习空间”,Artsiom Sanakoyeu,Vadim Tschernezki,Uta Büchler,bjrn Ommer,CVPR,2019 年
由于巨大的阶级不平衡,大量的增强被使用,其中包括随机翻转,旋转,缩放,模糊,照明,对比度,饱和度变化。在推断期间,计算查询特征向量和训练图库特征向量之间的点积,并且选择具有最高点积值的类作为前 1 个预测。另一个有助于解决类别不平衡的技巧是对属于相同鲸鱼 id 的训练图像的特征向量进行平均。
这些模型是使用 PyTorch 实现的,在一台 Titan Xp 上训练需要 2-4 天(取决于图像分辨率)。值得一提的是,采用 DenseNet-169 主干的性能最佳的单一型号的得分为 0.931 磅。
方法 3:特征分类(Artsiom)
当我和 Artsiom 联手时,我们做的第一件事就是使用从我们所有模型中提取的特征训练分类模型,并连接在一起(当然是在应用 PCA 之后)。
分类头由两个致密层组成,中间有脱落物。该模型训练非常快,因为我们使用预先计算的特征。
这种方法让我们获得了 0.924 磅,并带来了更多的整体多样性。
方法 4:新的鲸鱼分类(Pavel)
这场比赛最复杂的部分之一是正确地对新鲸鱼进行分类(因为大约 30%的图像属于新鲸鱼类)。
处理这个问题的流行策略是使用一个简单的阈值。也就是说,如果给定图像 X 属于某个已知的鲸类的最大概率小于阈值,则它被分类为新鲸类。然而,我们认为可能有更好的办法来解决这个问题。
对于每个表现最好的模型和集合,我们取其前 4 个预测,按降序排列。然后,对于每隔一个模型,我们取它们在所选的 4 个类别中的概率。目的是根据这些特征来预测鲸鱼是否是新的。
Pavel 创建了一个非常强大的对数回归、SVM、几个 k-NN 模型和 LightGBM 的混合体。在交叉验证中,所有数据的组合为我们提供了 0.9655 的 ROC-AUC,并增加了 2%的 LB 分数。
组装
从我们的模型中构建整体绝对不是一件容易的事。事实是,我的模型的输出是一个非标准化概率矩阵(从 0 到 1),而 Artsiom 提供的输出矩阵由欧几里德距离组成(因此范围从 0 到无穷大)。
我们尝试了多种方法将 Artsiom 的矩阵转换为概率,其中包括:
- 类 t-SNE 变换:
- Softmax
- 通过应用功能
1 / (1 + distances)
简单地反转范围 - 许多其他函数来反转矩阵的范围
不幸的是,前两种方法根本不起作用,虽然大多数情况下使用任意函数将范围裁剪为[0,1],但结果大致相同。我们最终选择了这个函数,在验证集上选择了一个具有最高 mAP@5 的函数。
令人惊讶的是,最好的一个是1 / (1 + log(1 + log(1 + distances)))
。
其他团队使用的方法
基于 SIFT 的
我想概述一个解决方案,在我看来,它是最漂亮的解决方案之一,同时也是不寻常的。
现在是 Kaggle 特级大师(排名 12)的 David 在私人 LB 排名第四,并在 Kaggle 讨论论坛上以帖子的形式分享了他的解决方案。
他处理全分辨率图像,使用传统的关键点匹配技术,利用 SIFT 和 ROOTSIFT。为了处理假阳性,大卫训练了一个 U-Net 从背景中分割出鲸鱼。有趣的是,他使用 smart 后处理给只有一个训练示例的类更多的机会进入前 1 预测。
我们也想过尝试基于 SIFT 的方法,但我们确信它的性能肯定比顶级的神经网络差。
在我看来,我们永远不应该被深度学习的力量所蒙蔽,低估传统方法的能力。
纯分类
由 Dmytro Mishkin 、 Anastasiia Mishchuk 和 Igor Krashenyi 组成的团队 Pure Magic thanks radek (第 7 名)采用了一种结合了度量学习(三重缺失)和分类的方法,正如 Dmytro 在他的帖子中所描述的那样。
在长时间训练分类模型时,他们尝试使用中心损失来减少过拟合,并在应用 softmax 之前使用温度缩放。在使用的众多主干架构中,最好的是 SE-ResNeXt-50,它能够达到 0.955 LB。
他们的解决方案远不止这些,我强烈建议你参考最初的帖子。
圆脸,圆脸
正如 Ivan Sosin 在帖子中提到的(他的团队 BratanNet 在这次比赛中获得第 9 名),他们使用了 CosFace 和 ArcFace 方法。来自原帖:
其中,Cosface 和 Arcface 作为新发现的人脸识别任务的 SOTA 脱颖而出。主要思想是在余弦相似性空间中使相同类的例子彼此靠近,并拉开不同的类。用 cosface 或 arcface 训练一般是分类,所以最后损失的是交叉熵。
当使用像 InceptionV3 或 SE-ResNeXt-50 这样的较大主干时,他们注意到过度拟合,因此他们转向像 ResNet-34、BN-Inception 和 DenseNet-121 这样的较轻网络。
该团队还使用了精心挑选的增强功能和众多网络修改技术,如 CoordConv 和 GapNet 。
他们的方法中特别有趣的是他们处理new _ whales . From the original post 的方式:
从一开始,我们就意识到有必要对新的鲸鱼做些什么,以便将它们纳入训练过程。简单的解决方法是给每条新鲸鱼分配一个概率,每类概率等于 1/5004。在加权抽样技术的帮助下,它给了我们一些帮助。但后来我们意识到,我们可以使用 softmax 预测来自训练集合的新鲸鱼。所以我们想出了蒸馏。我们选择蒸馏而不是伪标签,因为新鲸被认为具有与火车标签不同的标签。尽管这可能不是真的。
为了进一步提高模型能力,我们将带有伪标签的测试图像添加到训练数据集中。最终,我们的单个模型可以通过快照组装达到 0.958 。不幸的是,以这种方式训练的 ensembling 并没有带来任何分数的提高。也许是因为伪标签和蒸馏导致的品种减少。
最后的想法
Final Standings
令人惊讶的是,尽管私有测试集贡献了所有测试数据集的近 80%,但最终几乎没有任何改变。我相信竞赛主持人很好地提供了一个非常有趣的问题,以及干净和经过处理的数据。
这是我参加的第一次 Kaggle 比赛,它确实证明了 Kaggle 比赛是多么有趣、吸引人、激励和教育人。我想祝贺那些由于这次比赛而成为专家、大师和特级大师的人们。我还要感谢 ODS.ai 社区令人惊叹的讨论和支持。
最后,我想再次特别感谢我的团队成员 Artsiom Sanakoyeu 和 Pavel Pleskov 给了我一次难忘的 Kaggle 比赛经历。