通过可视化计算暴露错误的想法
Struck by Data (Jasper McChesney)
我们经常使用可视化来呈现数据。但是我们对数据 做的*的可视化呢?我指的是聚合、算法和流程。*****
通常这些计算非常简单。但是人们总是把简单的事情弄错——尤其是数学方面的事情。可视化可以帮助显示正在发生的事情,以及为什么一种方法可能行不通。你知道,同事的方法。
目标和“解决方案”
我在和某人一起写一份报告。我们需要向非技术观众解释如何根据行业趋势做出基本的预算预测。这些“趋势”实际上是逐年的百分比增长;以及在此期间的总体增长。它可能看起来像这样:
假设明年的增长将与过去几年的变化相似。但是我们不能规定任何花哨的建模:我们正在帮助人们用 Excel 或台式计算器进行粗略的猜测。
我的同事给人们提供了两种选择
- 取历史增长(%)的算术平均值。
- 求总体涨幅(%),除以年数。
我不确定这些是否有意义。但是说服我的同事?没那么容易。什么有帮助?会议议程背面潦草的图表。
每张图都是一组示例数据,加上您要进行投影的操作。每一个都说明了为什么上面的方法有问题。
错误
在数学层面上,这里的问题是根本性的:我的同事想要处理百分比增长,即比率*,就像他会处理美元增长一样,美元增长只是值。*
但是比率不是价值——事实上,它们将两个价值联系在一起。他们与自己互动的方式也不同。通常它们是通过乘法而不是加法结合在一起的;他们复合*。所以把每年的利率加在一起得到最终利率在概念上是没有意义的。*
(你可以用量纲分析来证实这一点。我们在这里的增长是无量纲的比例。所以把一堆加在一起,然后除以年,就产生了 1/年的单位。这对预算有什么用呢?)
但是这个论证依赖于数学。也许我是一个坚持理论严谨的混蛋,当我们只需要一些有用的东西。好吧,让我们看看是不是这样。
选项 2 拆卸(恒定百分比变化)
选项#2 是额外的攻击,值得首先反驳。但是为什么一开始会有人想这么做呢?
如果我们处理的是简单的数量,比如预算金额的变化(T1),选项 2(T2)就可以了(T3):你可以将总的变化除以年数,得到一种年平均值。它之所以有效,是因为它在数学上 等同于 选项 1,即每年数值的平均值。
这是我们的第一个例子,对两个选项都进行了计算:
处理简单数值时,变化总和与总体变化相同。这就是为什么选项 2 很诱人。**
百分比变化打破了这一点
现在,让我们看看当我们尝试这些相同的计算时,百分比会发生什么变化(正如我的同事提议的那样):
在这个整洁的例子中,每个百分比变化都是相同的,这使得我们的“预测”非常容易:我们允许的任何解决方案都应该肯定产生 50%。然而,选项 2 没有。它不仅在数学上没有意义,也不能产生合理的结果。我不仅仅是个混蛋!
选项 1 下降(振荡变化)
看到这个例子后,您可能会认为选项 1 是可行的。在这个简单的例子中,它确实是这样的,不断地变化。但那不是真正的测试。我们需要一个稍微现实一点的例子:年复一年的变化。
认识到一种模式,我们人类当然期待明年有+20 的变化。但是我们粗略估计的目标是简单地使用前几年的主要趋势;而不是去寻找不同的模式或体重年份。这些变化的中心趋势显然是相互平衡;以产生零净变化。
事实上,当使用原始的数字变化时,我们的两个选项都计算零平均变化。
百分比再次打破了事物
但我们任务的另一个假设是,任何真正的趋势都将是百分比变化的趋势,而不是原始值(正如我们在上面看到的,这些显然是不一样的)。在本例中,我们看到两种百分比变化:+100%和-50%。如果我们把这些输入算法,我们会得到什么?
这一次,选项 1 是明显的失败。变化的线性平均值为 25%。这意味着,如果我们假设所有年份都一样,那么每年都会有 25%的增长。但是如果我们算一下,从 20 岁开始,到 60 岁结束。差远了!
为什么不管用?
同样,这是因为我们误用了简单的汇总统计数据(矩),比如平均值。均值允许我们通过在所有项目之间分配总价值来假装所有项目都是相同的。通过加法,这种扩散是线性的。但是百分比不能把普通的数字相加。
让我们通过例子来说明这一点。数据中没有上升或下降的总趋势:上升抵消了下降,所以它们是无用的。这意味着**200%的变化必须被50%的变化抵消*。**(我已经将增加的百分比转换为原始百分比;200%和 50%当然相当于+100%和-50%)。*
但是在加法的土地上,这两项并不平衡:200% + 50% = 250%,平均变化 125%。我们应该得到的结果是 100%。
什么会起作用?
我们的变化如何平衡?与相乘。200%和 50%的乘积是 100%。没有变化。
乘法不仅仅是发生计算出来:它在概念上是有意义的,因为百分比是这样运作的:增加的大小取决于之前的值。(而加法与它们无关:如果你在银行账户上增加更多的钱,你就获得了总数;你现有的平衡不会改变它。不像按揭付款!)
另一种意思是
当然,我们不只是想要组合百分比,我们想要它们的平均值;我们想知道如果它们都一样的话,哪一年会是什么样。通俗说法的“平均”就是算术平均值。但是均值有很多种,包括几何均值,它的工作原理是等效的,但是用于乘法运算。你不是把值相加,而是把它们相乘;你不用除以数值的个数,而是取那个的根*。*
如果我们回到我们的第一个例子,恒定+150%的增长,我们可以看到它是如何工作的,使用选项 1。我们将再次使用基本百分比而不是增加额(即 250%):
*2.5 x 2.5 x 2.5 x 2.5 x 2.5 = 97.6697.66 ^(1/5) = **2.5***
在第二个示例中也是如此,没有任何变化:
*2 x 0.5 x 2 x 0.5 x 2 x 0.5 = 11 ^ (1/6) = **1***
所以我们至少可以在我们的(奇怪的)测试案例中确认合理的行为。
选项 1 和 2 再次变得等效
我们也可以放心使用几何平均,因为这两个选项再次产生相同的结果。也就是说,百分比增长的几何平均值与总体增长百分比的几何平均值相同。(因为将每一个连续的乘数相乘自然会产生总乘数。)
结论
这里的一个小教训是要小心利率!更一般地说,要小心看似基本的数学,尤其是当你关注更大的问题时。
但是另一个主题是使用视觉来谈论思维*,特别是关于数据的数学或统计思维——即使操作很简单。这可能会为你赢得一场争论。*
基于扩展聚类质心的多数欠采样技术
在我的上一篇文章“Python 中基于聚类质心的多数欠采样技术(CCMUT)的实现 ”中,我试图重温基于聚类质心的多数欠采样技术(CCMUT),并给出了它的 Python 实现。在这篇文章中,我将首次介绍 CCMUT 的扩展版本,并将其命名为 E-CCMUT 。
在 CCMUT 中,要求通过在属于多数类的特征空间中的数据点上平均所有特征的特征向量来找到聚类质心。但是,聚类质心是一个点,该点可能是也可能不是特征空间中的一个假想点,相对于该点,大多数类实例被划定其重要程度。聚类质心为虚的概率高得多,因为仅在一些非常特殊的情况下,聚类质心最初可能作为数据集中的样本作为数据点存在。
因此,由于聚类质心在大多数情况下仍然是特征空间中的一个假想点,直观上,它对于相对于真实存在的数据点(而不是假想的数据点)来划分多数实例的重要程度肯定是更有效和正确的,认为它是最重要的。
E-CCMUT 正是处理这种修改。这里,最接近群集质心的多数类实例,比如说 M (使用与 CCMUT 中相同的过程找到群集质心)被视为最重要的实例(如在 CCMUT 中),但是其他多数类实例的重要程度的划分是相对于该实例进行的,这是针对 CCMUT 中的群集质心进行的。同样,离 M 最远的多数类实例被认为是最不重要的,而离属于多数类的 M 、最近的数据点被认为是最重要的。
E-CCMUT 的伪代码如下:
- n:特征的数量。
- n:多数实例的原始数量。
- x:大多数实例存储在一个(N×N)矩阵中。
- f:欠采样的百分比。
- euclidean(x1,x2):返回特征空间中点 x1 和 x2 之间的欧氏距离。
- rev(l):它返回列表的反向排序形式
1\. under_sample(X,f)
2\. {
3\. d[N]
4\. cluster_centroid = sum(X[1], X[2], X[3],.., X[N])/N
5\. for i = 1 to N:
6\. {
7\. d[i] = euclidean(cluster_centroid,X[i])
8\. }
9\. cluster_centroid = X[index of the minimum element in d]
10\. for i=1 to N-1:
11\. {
12\. d[i] = euclidean(cluster_centroid,X[i])
13\. }
14\. indices = original indices list of elements in rev(d)
15\. x = delete(X, indices[:(f*N)])
16\. return x
17.}
使用函数 under_sample()构造伪代码,该函数将多数实例矩阵和欠采样百分比作为参数,并在欠采样后返回多数样本矩阵 x。
E-CCMUT 被认为比 CCMUT 直观地获得更好的结果。
在我即将发表的文章中,我将拿出实验证据,证明 E-CCMUT 比 CCMUT 表现得更好。
扩展卡尔曼滤波器:为什么我们需要一个扩展版本?
这篇文章是我上一篇关于卡尔曼滤波器的文章的延续。我的同事 Larry 对卡尔曼滤波器的工作原理非常感兴趣,但他能理解非线性和扩展卡尔曼滤波器的概念吗?让我们找出答案。
拉里:我知道卡尔曼滤波器,我现在可以预测和更新,我现在基本上知道一个重要的预测工具。
我:你能告诉我在阅读卡尔曼滤波器时我们做了什么假设吗?
拉里:假设…?你什么意思?你刚才说卡尔曼滤波器只对高斯函数有效。就是这样。不是吗?
我:嗯,说对了一半。上一篇文章中隐藏的另一个重要假设是线性函数。所以两个假设是-:
1。卡尔曼滤波器将始终与高斯分布一起工作。
2。卡尔曼滤波器将始终与线性函数一起工作。
拉里:哦,天哪!那么线性函数是从哪里出现的呢?
Me:对于线性函数,我的意思是预测和更新步骤都只包含线性函数。如果你仔细观察所有的方程,它们已经存在了。
线性函数看起来有点像这样:
Figure 1. Linear Function. (Source)
另一方面,非线性函数看起来像这样:
Figure 2. Non Linear Function. (Source)
从这些图中可以看出,直线方程是线性函数,而 cos 函数是非线性函数。
赖瑞:是的,没关系。我们的方程中没有任何角度,所以它们看起来只是线性的。那 KF 现在的问题是什么?我:大多数现实世界的问题都涉及到非线性函数。在大多数情况下,系统会观察某个方向,并在另一个方向进行测量。这涉及到角度和正弦、余弦函数,它们是非线性函数,然后会产生问题。
拉里:嗯,但是非线性函数是如何产生问题的呢?
我:如果你给高斯函数输入一个线性函数,那么输出也是高斯函数
Figure 3. Gaussian + Linear Function = Gaussian (Source)
如果用非线性函数输入高斯函数,则输出不是高斯函数。非线性函数导致非高斯分布。
Figure 4. Gaussian + Non Linear Function = Non Gaussian (Source)
因此,如果我们应用非线性函数,它将不会以高斯分布结束,我们不能再应用卡尔曼滤波器了。非线性破坏了高斯分布,计算平均值和方差是没有意义的。
拉里:哦,不,那样的话,我们的卡尔曼滤波器坏了。那么解决办法是什么呢?
我:你能想到的最琐碎的解决办法是什么?
拉里:我?嗯。我只能说使用线性函数:D:虽然这没有意义,但这确实是解决方案。
拉里:什么?你是说我是对的??怎么会这样 T2:是的,你是对的。我们将只研究线性函数。
拉里:但是你说成本、收益函数呢?它们仍然是非线性的,对吗?
我:绝对。它们是非线性的,但是我们将通过近似使它们线性。在这里,我们将借助一个叫做泰勒级数的强大工具,它将帮助我们得到非线性函数的线性近似。应用近似后,我们得到的是一个扩展卡尔曼滤波器。
拉里:新工具不断涌现!这个泰勒是怎么工作的?
Me:我们取一个点,对那个点进行一堆求导。在 EKF 的情况下,我们采用非线性曲线上的高斯平均值,并对其进行多次求导以逼近它。
Figure 5. Taylor Series
假设我们想在 x=0 时逼近 sin(x)。
假设我们要找一个多项式函数P(x)= c _ 0+c _ 1 * x+c _ 2 * x+c _ 3 * x来近似 sin(x)。所以我们需要找出 c0,C1,C2 和 C3 的值
在 x=0 时,sin(x) = 0,P(x) = c_0 + 0 + 0
如果我们的近似值必须更接近 sin(x),那么 sin(x)的值必须等于 x=0 时 P(x)的值。所以,0 = 0
如果我们的近似值与 x=0 处的 sin(x)具有相同的正切斜率也是很好的所以 1 = 1
发生…我们可以发现 sin(x)= x x/3 的近似值!+ x⁵/5!x⁷/7!+ x⁹/9!…
拉里:酷!那真的很酷。但是这又会给出一条非线性的曲线,我们不是只对线性化感兴趣吗?
我:正是,我们对线性化感兴趣,所以我们只是对泰勒级数的一阶导数感兴趣。对于每一个非线性函数,我们只需在平均值周围画一条切线,并尝试线性逼近该函数。
赖瑞:嗯。好的。KF 只研究线性函数,但在现实生活中,我们会遇到破坏高斯分布的非线性函数,所以我们尝试用泰勒级数来线性逼近这些函数,这属于扩展卡尔曼滤波。对吗?
我:绝对。完全正确!看看这个。
Figure 6. Scenario after applying Taylor’s Approximation to Linearize our function
拉里:我想知道这对我们为卡尔曼滤波器写的方程有什么影响,但在那之前,提供数据的传感器是什么?
我:假设我们有两个传感器激光雷达和雷达。激光雷达以笛卡尔坐标系的形式为我们提供距离。另一方面,雷达为我们提供了极坐标系统中的距离和速度。
Lidar => {px,py}
雷达= > { ρ,φ,ρ_dot}
px,py ->物体在笛卡尔坐标系中的坐标
ρ - > 是到物体的距离
*φ->*是 ρ 与 x 轴
ρ_dot - > 是 ρ
的变化 x 轴始终在汽车前进的方向。
Figure 7. Polar Coordinates as reported by Radar. ([Source](http://Taken from Udacity Nanodegree))
拉里:我们能从两个传感器都获取数据吗?
我:你当然要带。从不同的传感器获取数据并将它们组合在一起称为传感器融合。
拉里:好的,据我猜测,来自雷达的测量结果是非线性的,因为它们涉及到角度。现在我有兴趣知道扩展卡尔曼滤波器的方程!
我:对!当然可以。
预测步骤
x′= f . x+b .μ+ν p′=fpfᵀ+q 预测步骤与卡尔曼滤波完全相同。无论数据来自激光雷达还是雷达,预测步骤都是完全相同的。
更新步骤(仅在 EKF 情况下,即来自雷达的非线性测量)
等式 1:
y = z-h(x′)
z ->极坐标中的实际测量值
h - >指定我们的速度和位置如何映射到极坐标中的函数
x′->预测值
y - >测量值和实际值之间的差值
h(x’)
这是一个指定我们在笛卡尔坐标和极坐标中的预测值之间的映射的函数。此映射是必需的,因为我们在笛卡尔坐标中进行预测,但来自传感器的测量值(z)在极坐标中。
Figure 8. Mapping between Cartesian and Polar coordinates ([Source](http://Taken from Udacity Nanodegree))
等式 2:
s =hⱼp′hⱼᵀ+r
k =p′hⱼᵀs⁻R ->测量噪声
K - >卡尔曼增益
S- >总误差
s⁻->s 的逆
Hⱼ - >雅可比矩阵
Hⱼ
Hⱼ是雅可比矩阵。雅可比矩阵是我们刚刚在泰勒级数中讨论过的一阶导数。因为我们在这里处理矩阵,我们需要找到矩阵形式的微分。
J_kl = d F_k / dX_l
J_kl 是雅可比矩阵的 k,l 元素,F_k 是向量函数 F 的第 k 个元素,X_l 是向量变量 X 的第 l 个元素。
这里 F_k = { ρ,φ,ρ_dot} X_l = {px,py,vx,vy}
因为在雷达的情况下,我们有 4 个测量值,2 个用于距离,2 个用于速度。
Figure 9. Jacobian matrix ([Source](http://Taken from Udacity Nanodegree))
Figure 10. Jacobian Matrix after applying derivatives
等式 3:
x = x′+k . y
p =(I-khⱼ)p′
拉里:哦,明白了!因此,在激光雷达的情况下,我们将应用卡尔曼滤波器,因为来自传感器的测量是线性的。但是在雷达的情况下,我们需要应用扩展卡尔曼滤波器,因为它包括非线性的角度,因此我们使用泰勒级数的一阶导数来近似非线性函数,称为雅可比矩阵(Hⱼ)。然后我们用 h(x′)把笛卡尔空间转换到极空间,最后我们用 Hⱼ替换了 KF 的所有方程中的 h。
我:10/10。谢谢大家!
雅可比矩阵确实有点神奇,因为它将非线性空间转换成线性空间。但是相信我,这不是魔术,这都是数学。如果你发现任何错误,请务必与我联系。你可以在 LinkedIn 这里找到我。
你可以在这里阅读卡尔曼滤波器的基础知识。无迹卡尔曼滤波器这里。
扩展卡尔曼滤波器
在我以前的博客中,我已经介绍过卡尔曼滤波器。在这篇博客中,我将讨论扩展滤波,并看看它如何解决卡尔曼滤波的问题。
这篇博客是我之前关于卡尔曼滤波器的博客的延续,所以如果你还没有读过,请在这里阅读。
为什么要使用扩展卡尔曼滤波器?
为了解决卡尔曼滤波中的非线性问题,引入了扩展卡尔曼滤波。在现实生活中,可能有许多场景,系统可能会从一个方向观察,而从另一个方向进行测量。这涉及到解决这些问题的角度,导致非线性函数,当输入高斯函数时,导致非高斯分布。并且我们不能对非高斯分布应用卡尔曼滤波器,因为计算非高斯函数的均值和方差是没有意义的。
它是如何工作的?
扩展卡尔曼滤波器利用泰勒级数、将非线性函数转化为线性函数,有助于得到非线性函数的线性逼近。
泰勒级数:
在数学中,泰勒级数是一个函数的表示,它是由函数在一个点的导数的值计算出来的无穷多个项的总和。简单来说,我们可以说,在泰勒级数中,我们取一个点,对它进行一系列求导。
当我们对一个多项式执行泰勒级数时,结果也是一个多项式,因此 EKF 所做的是首先在一个平均值处评估非线性函数,该平均值是分布的最佳近似值,然后估计一条斜率在该平均值附近的直线。该斜率由泰勒展开的一阶导数确定,因为一阶导数给出了线性值。因此,这种从泰勒级数的一阶导数获得斜率的方法被称为一阶泰勒展开。
如何进行泰勒展开?
泰勒级数由下式给出
Taylor Series
非线性函数 f(x)在均值(μ)处的泰勒级数方程的一般公式由下式给出:
By appling first order taylor expansion method
得到泰勒展开式需要遵循的步骤:
- 用一个给定的等式代替 f(x)
- 求偏导数
- 插入值μ,并进行评估,以获得μ值处的泰勒展开式。
让我们通过一个例子来理解这一点:
假设我们必须找到方程 sin(x)在μ = 0 时的泰勒展开式
所以按照上面讨论的步骤:
- h(x) = sin(x)
- ∂h = cos(x)
- h(x)≈h(μ)+∂x∂h(μ)(xμ)
h(x)≈sin(μ)+cos(μ)(xμ)= 0+1 *(x-0)= x
EKF 对 KF 预测和更新方程的影响
当我们谈到 EKF 对 KF 方程的影响时,有必要知道数据的来源。也就是说,对于自主车辆,我们需要知道我们必须执行预测的数据是来自激光雷达还是雷达。
因为激光雷达以笛卡尔坐标系统的形式为我们提供距离,而雷达以极坐标系统为我们提供距离和速度。两者的测量向量如下:
激光雷达:{px,py}
雷达:{ ρ,φ,ρ_dot }
其中 px:物体在笛卡尔坐标系中的 x 坐标
py: 物体在笛卡尔坐标系中的 y 坐标
**:物体与车辆的距离
φ:x 和ρ之间的逆时针角度**
ρ_ dot:ρ的变化率**
x 轴决定汽车移动的位置。
Polar coordinate System
到目前为止,你可能已经理解了激光雷达提供的数据本质上是线性的,而雷达提供的数据是非线性的。因此,激光雷达数据的等式将保持与卡尔曼滤波器的等式相同,但是对于雷达测量,等式将如下:
预测步骤:
x′= f . x+b .μ+ν
p′=fpfᵀ+q
无论使用何种设备,它都将保持与 KF 相同的状态。
雷达测量更新步骤:
y = z—h(x’)
z: 极坐标下的实际测量
***h(x’):预测笛卡尔坐标系到极坐标的映射。这里的x′*是笛卡尔坐标系中的预测值。
Mapping of Cartesian to Polar Coordinate system [Taken from udacity Nano degree]
y: 实测值与实际测量值之差。
s =hⱼp′hⱼᵀ+r
k =p′hⱼᵀs⁻
R: 测量噪音
这些噪声决定了器件的校正参数。它由设备制造商提供,并在整个周期中保持不变。
Hⱼ: 雅可比矩阵
正如我们已经看到的,为了将非线性函数转换为线性函数,我们使用泰勒级数展开,并对其进行一阶导数。当我们在方程中处理矩阵时,这个一阶导数由雅可比矩阵给出。
Jacobian matrix
因为雷达的状态向量由 px、py、vx 和 vy 组成。
通过应用导数,我们将得到 Hj 的最终值,如下所示:
Jacobian matix after applying derivative
K: 卡尔曼增益
我们通过简单的公式计算卡尔曼增益,这个公式我已经在我的关于卡尔曼滤波的博客中展示过了。唯一的事情是,在矩阵中我们不能分割,所以这里我们采取 S⁻。
x = x′+k . y
p =(I-khⱼ)p′
最后,我们更新状态向量和协方差矩阵,并继续下一个预测步骤以获得其他值。
扩展卡尔曼滤波器的问题
在扩展卡尔曼滤波器中,近似是基于单点即均值来完成的。这种近似可能不是最佳的近似,并导致较差的性能。你可以通过下图来理解:
mean and variance [Left] approximation based on mean[center] best possible approximation vs EKF approximation.[right][Image Source]
使用无迹卡尔曼滤波器解决了扩展卡尔曼滤波器的这个问题
女子网球比赛的广泛分析
Photo by Ben Hershey on Unsplash
在这里找到的 Kaggle 数据集涵盖了在 WTA (女子网球协会)注册的球员的统计数据,每年每场巡回赛发生的比赛,结果,以及巡回赛的一些资格赛。
从 2000 年到 2017 年的数据中发现了一些有趣的分析(其中一部分)。分析是在 R Markdown 中完成的,在这里作为 Kaggle 内核托管
玩家统治
- 除了 2014 年和 2015 年由塞雷娜·威廉姆斯主导之外,2017-2003 年没有完全的优势
- 1984 年至 1996 年由三名球员主宰,他们是玛蒂娜·纳芙拉蒂洛娃、施特菲·格拉芙和莫妮卡·塞莱斯。1997 年到 2002 年左右由林德赛·达文波特和玛蒂娜·辛吉斯主导
- 在 2000 年至 2017 年(部分时间)的分析中,塞雷娜统治了大满贯赛事,赢得了 7 个澳大利亚公开赛、3 个法国公开赛、7 个温布尔登公开赛和 5 个美国公开赛
输掉第一盘或第二盘后获胜
- 在决赛输掉第一盘后赢得美网决赛的概率是零*。*****
- 在一场决赛输掉第一盘后赢得法网决赛的概率是 5 % 。这种情况只在 2001 年珍妮弗·卡普里亚蒂击败吉姆·克里斯特尔斯时发生过一次****
- 在一场决赛输掉第一盘后赢得澳网决赛的概率是 22% 。****
- 在一场决赛输掉第一盘后赢得温网决赛的概率是 17% 。****
- 在所有比赛中输掉第一盘后获胜的概率是 16%
- 在所有比赛中输掉第二盘后获胜的概率是 14%
表面分析
Photo by Aaina Sharma on Unsplash
在 2000 年至 2017 年期间(有 2017 年的部分数据),观察如下
- 威廉姆斯姐妹占据了中奖名单的第一和第二名
- 从各方面来看,俄罗斯人、美国人和法国人赢得了最多的比赛
- 在硬面上,俄罗斯人、美国人和法国人占据了前三名的位置
- 在红土表面,俄罗斯人、西班牙人和美国人占据了前三名
- 在草面上,美国人、俄罗斯人和澳大利亚人又占据了前三的位置**
- 在地毯表面,俄罗斯人、法国人和美国人占据了前三的位置**
锦标赛等级分析
我们调查了不同锦标赛级别的顶级赢家****
- 威廉姆斯姐妹和玛丽亚·莎拉波娃占据了大满贯的前三名
- 伊琳娜·德门蒂耶娃、贾斯汀·海宁和玛蒂娜·辛吉斯占据了 T1 锦标赛的前三名。比赛最多的 T1 锦标赛是迈阿密、印第安维尔斯和罗马
- 陈月娇·毛瑞斯莫、林德赛·达文波特和吉姆·克里斯特尔斯占据了 T2 锦标赛的前三名。比赛最多的 T2 锦标赛是阿米莉娅岛、洛杉机和悉尼
镦粗分析
Photo by Davon King on Unsplash
如果赢家排名比输家排名至少高 10 位,我们可以认为是一个冷门。我们绘制了大满贯冠军和失败者之间的排名差异。
- 我们注意到大满贯决赛中冷门很少。但是他们中的一些在那里,让我们找出他们是什么。维纳斯·大威廉姆斯在大满贯决赛中创造了两次爆冷。2017 年美国网球公开赛爆冷,半决赛斯隆·斯蒂芬斯击败维纳斯·大威廉姆斯决赛麦迪逊·凯斯*。***
- 吉姆·克里斯特尔斯和丹尼埃拉·汉图楚娃在 T1 锦标赛决赛中创造了两次冷门
- 查达·鲁宾在 T2 锦标赛决赛中创造了两次冷门
如果您对 代码感兴趣,请在下面查找详细信息。 分析是在 R Markdown 中完成的,在这里作为 Kaggle 内核托管。
加载库
我们需要加载库来进行数据分析。
****library(tidyverse)
library(stringr)
library(lubridate)
library(DT)****
读取数据
将数据放在项目的输入目录中,并读取文件。数据集托管在这里
****fillColor = “#FFA07A”
fillColor2 = “#F1C40F”players = read_csv(“../input/wta/players.csv”)
rankings = read_csv(“../input/wta/rankings.csv”)
matches = read_csv(“../input/wta/matches.csv”)matches$tourney_name = str_replace(matches$tourney_name,”Us Open”,”US Open”)****
国家和选手
该图显示了从 2000 年到 2017 年代表他们国家的国家和球员人数。我们观察到美国是最大的赢家,澳大利亚、俄国、法国、德国、日本和中国都有超过五十名玩家参加
****matches_country_winner =
matches %>%
select(winner_name,winner_ioc) %>%
rename ( name = winner_name,ioc = winner_ioc)matches_country_loser =
matches %>%
select(loser_name,loser_ioc) %>%
rename ( name = loser_name,ioc = loser_ioc)matches_country = unique(rbind(matches_country_winner,matches_country_loser))matches_country %>%
group_by(ioc) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
ungroup() %>%
mutate(ioc = reorder(ioc,Count)) %>%
head(20) %>%
ggplot(aes(x = ioc,y = Count)) +
geom_bar(stat=’identity’,colour=”white”, fill = fillColor) +
geom_text(aes(x = ioc, y = 1, label = paste0(“(“,Count,”)”,sep=””)),
hjust=0, vjust=.5, size = 4, colour = ‘black’,
fontface = ‘bold’) +
labs(x = ‘Country’,
y = ‘Count’,
title = ‘Country and Count’) +
coord_flip() +
theme_bw()****
排名和年份
我们显示球员和他们每年排名第一的位置。我们绘制了一个条形图,显示了他们至少获得 1 级排名的年数。同年,这些球员的排名可能下滑至第一名以下。**这显示了玩家的坚持和长寿。****
在过去的 20 年里,施特菲·格拉芙、小威廉姆斯、林德赛·达文波特、莫妮卡·塞莱斯和玛蒂娜·辛吉斯这些普通的名字主宰了女子网球。
Years in No 1 Rank
****players_rankings = inner_join(players,rankings)
players_rankings$year = str_sub(players_rankings$ranking_date,1,4)
players_rankings$month = str_sub(players_rankings$ranking_date,5,6)players_rankings_rank_one = players_rankings %>% filter(ranking == 1)players_rankings_rank_one_year =
players_rankings_rank_one %>%
group_by(first_name,last_name,year) %>%
tally()
players_rankings_rank_one_year = players_rankings_rank_one_year %>% select(-n)
players_rankings_rank_one_year = unique(players_rankings_rank_one_year)players_rankings_rank_one_year %>%
mutate(FullName = paste0(first_name,” “,last_name,””)) %>%
group_by(FullName) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
ungroup() %>%
mutate(FullName = reorder(FullName,Count)) %>%
head(10) %>%
ggplot(aes(x = FullName,y = Count)) +
geom_bar(stat=’identity’,colour=”white”, fill = fillColor) +
geom_text(aes(x = FullName, y = 1, label = paste0(“(“,Count,”)”,sep=””)),
hjust=0, vjust=.5, size = 4, colour = ‘black’,
fontface = ‘bold’) +
labs(x = ‘Name’,
y = ‘Count’,
title = ‘Name and Count’) +
coord_flip() +
theme_bw()****
每年排名第一的网球选手数量
我们绘制了一年中排名第一的网球运动员的数量。这显示了一年中有多少玩家排名第一。
2017 年至 2003 年排名第一的网球运动员人数
如柱状图 所示,2017 年至 2003 年没有完全的优势,除了 2014 年和 2015 年由小威廉姆斯 主导。
****players_rankings_rank_one_year %>%
group_by(year) %>%
summarise(Count = n()) %>%
arrange(desc(year)) %>%
ungroup() %>%
head(15) %>%
ggplot(aes(x = year,y = Count)) +
geom_bar(stat=’identity’,colour=”white”, fill = fillColor2) +
geom_text(aes(x = year, y = 1, label = paste0(“(“,Count,”)”,sep=””)),
hjust=0, vjust=.5, size = 4, colour = ‘black’,
fontface = ‘bold’) +
labs(x = ‘Year’,
y = ‘Count’,
title = ‘Year and Count’) +
coord_flip() +
theme_bw()****
排名数据如下所示
****datatable(head(players_rankings_rank_one_year %>% arrange(desc(year)),10), style=”bootstrap”, class=”table-condensed”, options = list(dom = ‘tp’,scrollX = TRUE))****
1984-2003 年世界排名第一的网球运动员人数
1984 年至 1996 年由三名球员主导 玛蒂娜·纳芙拉蒂洛娃、施特菲·格拉芙和莫妮卡·塞莱斯。1997 年至 2002 年左右由 林德赛·达文波特和玛蒂娜·辛吉斯主导。
****players_rankings_rank_one_year %>%
group_by(year) %>%
summarise(Count = n()) %>%
arrange(desc(year)) %>%
ungroup() %>%
tail(20) %>%
ggplot(aes(x = year,y = Count)) +
geom_bar(stat=’identity’,colour=”white”, fill = fillColor2) +
geom_text(aes(x = year, y = 1, label = paste0(“(“,Count,”)”,sep=””)),
hjust=0, vjust=.5, size = 4, colour = ‘black’,
fontface = ‘bold’) +
labs(x = ‘Year’,
y = ‘Count’,
title = ‘Year and Count’) +
coord_flip() +
theme_bw()****
排名数据如下所示
****datatable(tail(players_rankings_rank_one_year %>% arrange(desc(year)) ,34), style=”bootstrap”, class=”table-condensed”, options = list(dom = ‘tp’,scrollX = TRUE))****
小威廉姆斯排名趋势
****plotTrendsRankings = function(players_rankings,firstname,lastname,plottitle)
{
players_rankings %>%
filter(first_name == firstname) %>%
filter(last_name == lastname) %>%
mutate(YearMonth = make_date(year=year,month=month) ) %>%
ggplot(aes(x=YearMonth,y=ranking,group = 1)) +
geom_point(size=2, color=”red”) +
labs(x = ‘Time’, y = ‘ranking’,title = plottitle) +
theme_bw()
}plotTrendsRankings(players_rankings,”Serena”,”Williams”,”Trend of Ranking for Serena Williams”)****
排名在 100 名以上
****rankingsAboveThreshold = function(players_rankings,firstname,lastname,threshold)
{
players_rankings %>%
filter(first_name == firstname) %>%
filter(last_name == lastname) %>%
filter(ranking >=threshold)
}serena100 = rankingsAboveThreshold(players_rankings,”Serena”,”Williams”,100) %>%
select(year,month,ranking) %>%
arrange(desc(ranking))datatable(serena100 %>% arrange(desc(year)), style=”bootstrap”, class=”table-condensed”, options = list(dom = ‘tp’,scrollX = TRUE))****
很明显,小威在 2006 年和 2011 年的排名分别下降了 50 位和 100 位。
施特菲·格拉芙排名趋势
****plotTrendsRankings(players_rankings,”Steffi”,”Graf”,”Trend of Ranking for Steffi Graf”)****
西蒙娜·哈勒普排名趋势
****plotTrendsRankings(players_rankings,”Simona”,”Halep”,”Trend of Ranking for Simona Halep”)****
Sania Mirza 排名趋势
****plotTrendsRankings(players_rankings,”Sania”,”Mirza”,”Trend of Ranking for Sania Mirza”)****
澳网冠军
下面的柱状图展示了澳网冠军
****getTournamentWinners = function(tournamentname,roundname)
{
return(
matches %>%
filter(tourney_name == tournamentname) %>%
filter(round == roundname) %>%
mutate(agediff = winner_age — loser_age) %>%
mutate(rankingdiff = loser_rank — winner_rank) %>%
mutate(htdiff = winner_ht — loser_ht)
)
}getGrandSlamWinners = function(roundname)
{
return(
matches %>%
filter(tourney_level == “G”) %>%
filter(round == roundname) %>%
mutate(agediff = winner_age — loser_age) %>%
mutate(rankingdiff = loser_rank — winner_rank)
)
}plotTournamentWinners = function(tournament,titleName)
{
tournament %>%
group_by(winner_name) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
ungroup() %>%
mutate(winner_name = reorder(winner_name,Count)) %>%
ggplot(aes(x = winner_name,y = Count)) +
geom_bar(stat=’identity’,colour=”white”, fill = fillColor2) +
geom_text(aes(x = winner_name, y = 1, label = paste0(“(“,Count,”)”,sep=””)),
hjust=0, vjust=.5, size = 4, colour = ‘black’,
fontface = ‘bold’) +
labs(x = ‘Winner’,
y = ‘Count’,
title = titleName) +
coord_flip() +
theme_bw()
}ausopen = getTournamentWinners(“Australian Open”,”F”)
plotTournamentWinners(ausopen,’Aus Open Winners Count’)****
澳网冠军年龄分布
****summary(ausopen$winner_age)ausopen %>%
ggplot(aes(x = winner_age)) +
geom_histogram(binwidth = 1,fill = fillColor) +
labs(x= ‘Winner Age’,y = ‘Count’, title = paste(“Distribution of”, ‘ Winner Age ‘)) +
theme_bw()****
该图显示澳网冠军大多在 23 至 28 岁之间。
法网冠军
下面的柱状图展示了法国网球公开赛的获胜者
****french = getTournamentWinners(“French Open”,”F”)
plotTournamentWinners(french,’French Open Winners Count’)****
法网冠军年龄分布
****summary(french$winner_age)french %>%
ggplot(aes(x = winner_age)) +
geom_histogram(binwidth = 1,fill = fillColor) +
labs(x= ‘Winner Age’,y = ‘Count’, title = paste(“Distribution of”, ‘ Winner Age ‘)) +
theme_bw()****
该图显示法网冠军大多在 22 至 27 岁之间。
温布尔登冠军
下面的柱状图展示了温布尔登的获胜者
****wimbledon = getTournamentWinners(“Wimbledon”,”F”)
plotTournamentWinners(wimbledon,’Wimbledon Winners Count’)****
温布尔登冠军的年龄分布
****summary(wimbledon$winner_age)wimbledon %>%
ggplot(aes(x = winner_age)) +
geom_histogram(binwidth = 1,fill = fillColor) +
labs(x= ‘Winner Age’,y = ‘Count’, title = paste(“Distribution of”, ‘ Winner Age ‘)) +
theme_bw()****
该图显示温布尔登网球公开赛的获胜者大多在 21 岁至 29 岁之间。
美国公开赛冠军
下面的柱状图展示了美国公开赛的获胜者
****usopen = getTournamentWinners(“US Open”,”F”)
plotTournamentWinners(usopen,’US Open Winners Count’)****
美国公开赛冠军的年龄分布
****summary(usopen$winner_age)usopen %>%
ggplot(aes(x = winner_age)) +
geom_histogram(binwidth = 1,fill = fillColor) +
labs(x= ‘Winner Age’,y = ‘Count’, title = paste(“Distribution of”, ‘ Winner Age ‘)) +
theme_bw()****
该图显示,美国公开赛的获胜者大多在 21 岁至 28 岁之间。
成功者和失败者的身高差距
我们绘制了大满贯冠军和失败者之间的身高差。数据显示,大满贯冠军比失败者的身高更低,尽管数据没有 19 个条目。
****grandslam= rbind(ausopen,french,wimbledon,usopen)summary(grandslam$htdiff)****
直方图
****grandslam %>%
ggplot(aes(x = htdiff)) +
geom_histogram(binwidth = 1,fill = fillColor) +
labs(x= ‘Height Difference’,y = ‘Count’, title = paste(“Distribution of”, ‘ Height Difference ‘)) +
theme_bw()****
密度图
****grandslam %>%
ggplot(aes(x = htdiff)) +
geom_density(fill = fillColor2) +
labs(x= ‘Height Difference’,y = ‘Count’, title = paste(“Distribution of”, ‘ Height Difference ‘)) +
theme_bw()****
表列数据
****grandslamhtdiff = grandslam %>% select(winner_name,loser_name,htdiff)datatable(grandslamhtdiff, style=”bootstrap”, class=”table-condensed”, options = list(dom = ‘tp’,scrollX = TRUE))****
成功者和失败者之间的年龄差距
我们绘制了大满贯冠军和失败者之间的年龄差距。
****summary(grandslam$agediff)grandslam %>%
ggplot(aes(x = agediff)) +
geom_histogram(binwidth = 1,fill = fillColor) +
labs(x= ‘Age Difference’,y = ‘Count’, title = paste(“Distribution of”, ‘ Age Difference ‘)) +
theme_bw()grandslam %>%
ggplot(aes(x = agediff)) +
geom_density(fill = fillColor2) +
labs(x= ‘Age Difference’,y = ‘Count’, title = paste(“Distribution of”, ‘ Age Difference ‘)) +
theme_bw()****
从图中可以明显看出,年纪大的选手比年轻选手更容易赢得决赛。
特征工程:第一、第二、第三组赢家和输家
我们提取第一组、第二组和第三组获胜者的特征
****whowon = function(scores,setnumber)
{
scores2 = str_split(scores,” “)set = scores2[[1]][setnumber]set_score = str_split(set,”-”)winner_score = as.numeric(set_score[[1]][1])
loser_score =as.numeric(str_split(set_score[[1]][2],””)[[1]][1])
if( (is.na(winner_score)) ||
(is.na(loser_score))
)
{
setwinner = “”
}else
{
if(winner_score > loser_score)
{
setwinner = “winner”
}else
{
setwinner = “loser”
}
}
return(setwinner)
}matches$first_set = sapply(matches$score,whowon, setnumber = 1)
matches$second_set = sapply(matches$score,whowon, setnumber = 2)
matches$third_set = sapply(matches$score,whowon, setnumber = 3)****
输掉第一盘后赢家的百分比
****first_set_loser = matches %>%
filter(first_set == “loser”)nrow(first_set_loser)/nrow(matches) *100****
大多数人在输掉第一盘后获胜
条形图显示了第一盘失败后的获胜者
****first_set_loser %>%
group_by(winner_name) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
ungroup() %>%
mutate(winner_name = reorder(winner_name,Count)) %>%
head(10) %>%
ggplot(aes(x = winner_name,y = Count)) +
geom_bar(stat=’identity’,colour=”white”, fill = fillColor2) +
geom_text(aes(x = winner_name, y = 1, label = paste0(“(“,Count,”)”,sep=””)),
hjust=0, vjust=.5, size = 4, colour = ‘black’,
fontface = ‘bold’) +
labs(x = ‘Winner’,
y = ‘Count’,
title = ‘Winner’) +
coord_flip() +
theme_bw()****
输掉第二盘后赢家的百分比
****second_set_loser = matches %>%
filter(second_set == “loser”)nrow(second_set_loser)/nrow(matches) *100****
大多数人在输掉第二盘后获胜
条形图显示了输掉第二盘后的赢家
****second_set_loser = matches %>%
filter(second_set == “loser”)second_set_loser %>%
group_by(winner_name) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
ungroup() %>%
mutate(winner_name = reorder(winner_name,Count)) %>%
head(10) %>%
ggplot(aes(x = winner_name,y = Count)) +
geom_bar(stat=’identity’,colour=”white”, fill = fillColor) +
geom_text(aes(x = winner_name, y = 1, label = paste0(“(“,Count,”)”,sep=””)),
hjust=0, vjust=.5, size = 4, colour = ‘black’,
fontface = ‘bold’) +
labs(x = ‘Winner’,
y = ‘Count’,
title = ‘Winner’) +
coord_flip() +
theme_bw()****
在大满贯赛事中输掉第一盘后的赢家比例
我们在输掉第一盘后计算赢家的百分比
****gs_final_firstset_loser = matches %>%
filter(tourney_level == “G”) %>%
filter(round == “F”) %>%
filter(first_set == “loser”)gs_final_secondset_loser = matches %>%
filter(tourney_level == “G”) %>%
filter(round == “F”) %>%
filter(second_set == “loser”)gs_final_thirdset_loser = matches %>%
filter(tourney_level == “G”) %>%
filter(round == “F”) %>%
filter(third_set == “loser”)gs_final = matches %>%
filter(tourney_level == “G”) %>%
filter(round == “F”)nrow(gs_final_firstset_loser)/nrow(gs_final) *100****
在大满贯赛事中输掉第二盘后的赢家比例
我们在输掉第二盘后计算赢家的百分比
****nrow(gs_final_secondset_loser)/nrow(gs_final) *100****
澳大利亚网球公开赛输掉第一盘后的赢家比例
我们在输掉第一盘后计算赢家的百分比
****percentWinnersTourney = function(matches,tournamentName)
{
gs_final_firstset_loser = matches %>%
filter(tourney_name == tournamentName) %>%
filter(round == “F”) %>%
filter(first_set == “loser”)
gs_final_secondset_loser = matches %>%
filter(tourney_name == tournamentName) %>%
filter(round == “F”) %>%
filter(second_set == “loser”)gs_final = matches %>%
filter(tourney_name == tournamentName) %>%
filter(round == “F”)nrow(gs_final_firstset_loser)/nrow(gs_final) *100}displayGrandSlamWinnersAfterLosingFirstSet = function(matches,tournamentName)
{
gs_final_firstset_loser = matches %>%
filter(tourney_name == tournamentName) %>%
filter(round == “F”) %>%
filter(first_set == “loser”) %>%
select(winner_name,loser_name,year,score)
datatable(gs_final_firstset_loser, style=”bootstrap”, class=”table-condensed”, options = list(dom = ‘tp’,scrollX = TRUE))
}percentWinnersTourney(matches,”Australian Open”)
displayGrandSlamWinnersAfterLosingFirstSet(matches,”Australian Open”)****
在温布尔登输掉第一盘后的赢家比例
我们在输掉第一盘后计算赢家的百分比
****percentWinnersTourney(matches,”Wimbledon”)
displayGrandSlamWinnersAfterLosingFirstSet(matches,”Wimbledon”)****
法国网球公开赛输掉第一盘后的赢家比例
我们在输掉第一盘后计算赢家的百分比
****percentWinnersTourney(matches,”French Open”)
displayGrandSlamWinnersAfterLosingFirstSet(matches,”French Open”)****
美国网球公开赛输掉第一盘后的赢家比例
我们在输掉第一盘后计算赢家的百分比
****percentWinnersTourney(matches,”US Open”)
displayGrandSlamWinnersAfterLosingFirstSet(matches,”US Open”)****
很明显,输掉法美第一盘就意味着几乎输掉了决赛。零次,只有一次,一名选手在输掉第一盘后分别赢得了美国和法国。
表面
不同表面上的匹配数量显示在条形图中。
****matches %>%
filter(!is.na(surface)) %>%
filter(!str_detect(surface,”-”)) %>%
group_by(surface) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
ungroup() %>%
mutate(surface = reorder(surface,Count)) %>%
ggplot(aes(x = surface,y = Count)) +
geom_bar(stat=’identity’,colour=”white”,fill=fillColor2) +
geom_text(aes(x = surface, y = 1, label = paste0(“(“,Count,”)”,sep=””)),
hjust=0, vjust=.5, size = 4, colour = ‘black’,
fontface = ‘bold’) +
labs(x = ‘Surface’,
y = ‘Count’,
title = ‘Surface and Count’) +
coord_flip() +
theme_bw()****
在 2000 年至 2017 年期间(有 2017 年的部分数据),观察如下
- 威廉姆斯姐妹占据了中奖名单的第一和第二名
- 从各方面来看,俄罗斯人、美国人和法国人赢得了最多的比赛
- 在坚硬的地面上,俄罗斯人、美国人和法国人占据了前三名的位置
- 在红土表面上,俄罗斯人、西班牙人和美国人占据了前三名的位置
- 在草面上,美国人、俄罗斯人和澳大利亚人又占据了前三的位置
- 在地毯上,俄罗斯人、法国人和美国人占据了前三名的位置
所有表面
所有表面的前 10 名获奖者如下所示
****matches %>%
group_by(winner_name) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
ungroup() %>%
mutate(winner_name = reorder(winner_name,Count)) %>%
head(10) %>%
ggplot(aes(x = winner_name,y = Count)) +
geom_bar(stat=’identity’,colour=”white”,fill=fillColor) +
geom_text(aes(x = winner_name, y = 1, label = paste0(“(“,Count,”)”,sep=””)),
hjust=0, vjust=.5, size = 4, colour = ‘black’,
fontface = ‘bold’) +
labs(x = ‘Winner’,
y = ‘Count’,
title = ‘Winner and Count’) +
coord_flip() +
theme_bw()surfaceTitle = ‘Country Winning on All Surfaces’matches %>%
group_by(winner_ioc) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
ungroup() %>%
mutate(winner_ioc = reorder(winner_ioc,Count)) %>%
head(10) %>%
ggplot(aes(x = winner_ioc,y = Count)) +
geom_bar(stat=’identity’,colour=”white”,fill=fillColor) +
geom_text(aes(x = winner_ioc, y = 1, label = paste0(“(“,Count,”)”,sep=””)),
hjust=0, vjust=.5, size = 4, colour = ‘black’,
fontface = ‘bold’) +
labs(x = surfaceTitle,
y = ‘Count’,
title = surfaceTitle) +
coord_flip() +
theme_bw()****
壳体
在硬质表面上的前 10 名获胜者如下所示
********
****surfaceWinners = function(surfaceName)
{
matches %>%
filter(surface == surfaceName) %>%
group_by(winner_name) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
ungroup() %>%
mutate(winner_name = reorder(winner_name,Count)) %>%
head(10) %>%
ggplot(aes(x = winner_name,y = Count)) +
geom_bar(stat=’identity’,colour=”white”,fill=fillColor2) +
geom_text(aes(x = winner_name, y = 1, label = paste0(“(“,Count,”)”,sep=””)),
hjust=0, vjust=.5, size = 4, colour = ‘black’,
fontface = ‘bold’) +
labs(x = ‘Winner’,
y = ‘Count’,
title = ‘Winner and Count’) +
coord_flip() +
theme_bw()
}countriesSurface = function(surfaceName,surfaceTitle)
{
matches %>%
filter(surface == surfaceName) %>%
group_by(winner_ioc) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
ungroup() %>%
mutate(winner_ioc = reorder(winner_ioc,Count)) %>%
head(10) %>%
ggplot(aes(x = winner_ioc,y = Count)) +
geom_bar(stat=’identity’,colour=”white”,fill=fillColor) +
geom_text(aes(x = winner_ioc, y = 1, label = paste0(“(“,Count,”)”,sep=””)),
hjust=0, vjust=.5, size = 4, colour = ‘black’,
fontface = ‘bold’) +
labs(x = surfaceTitle,
y = ‘Count’,
title = surfaceTitle) +
coord_flip() +
theme_bw()
}surfaceWinners(‘Hard’)
countriesSurface(‘Hard’,’Country Winning on Hard Surface’)****
粘土表面
在粘土表面上的前 10 名获胜者如下所示
********
****surfaceWinners(‘Clay’)
countriesSurface(‘Clay’,’Country Winning on Clay Surface’)****
草地表面
草地表面的前 10 名获胜者如下所示
****surfaceWinners(‘Grass’)
countriesSurface(‘Grass’,’Country Winning on Grass Surface’)****
地毯表面
地毯表面的前 10 名获奖者如下所示
********
****surfaceWinners(‘Carpet’)
countriesSurface(‘Carpet’,’Country Winning on Carpet Surface’)****
锦标赛级别
下面的条形图显示了与每个级别相关的锦标赛数量。
****matches %>%
filter(!is.na(tourney_level)) %>%
group_by(tourney_level) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
ungroup() %>%
mutate(tourney_level = reorder(tourney_level,Count)) %>%
ggplot(aes(x = tourney_level,y = Count)) +
geom_bar(stat=’identity’,colour=”white”,fill=fillColor2) +
geom_text(aes(x = tourney_level, y = 1, label = paste0(“(“,Count,”)”,sep=””)),
hjust=0, vjust=.5, size = 4, colour = ‘black’,
fontface = ‘bold’) +
labs(x = ‘Tournament Levels’,
y = ‘Count’,
title = ‘Tournament Levels and Count’) +
coord_flip() +
theme_bw()tournamnentLevelTournaments = function(tournamnentLevel)
{
matches %>%
filter(tourney_level == tournamnentLevel) %>%
group_by(tourney_name) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
ungroup() %>%
mutate(tourney_name = reorder(tourney_name,Count)) %>%
head(10) %>%
ggplot(aes(x = tourney_name,y = Count)) +
geom_bar(stat=’identity’,colour=”white”,fill=fillColor) +
geom_text(aes(x = tourney_name, y = 1, label = paste0(“(“,Count,”)”,sep=””)),
hjust=0, vjust=.5, size = 4, colour = ‘black’,
fontface = ‘bold’) +
labs(x = ‘Tournament Name’,
y = ‘Count’,
title = ‘Tournament Name and Count’) +
coord_flip() +
theme_bw()
}tournamnentLevelWinners = function(tournamnentLevel)
{
matches %>%
filter(tourney_level == tournamnentLevel) %>%
group_by(winner_name) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
ungroup() %>%
mutate(winner_name = reorder(winner_name,Count)) %>%
head(10) %>%
ggplot(aes(x = winner_name,y = Count)) +
geom_bar(stat=’identity’,colour=”white”,fill=fillColor2) +
geom_text(aes(x = winner_name, y = 1, label = paste0(“(“,Count,”)”,sep=””)),
hjust=0, vjust=.5, size = 4, colour = ‘black’,
fontface = ‘bold’) +
labs(x = ‘Winner’,
y = ‘Count’,
title = ‘Winner and Count’) +
coord_flip() +
theme_bw()
}****
我们调查了不同锦标赛级别的顶级赢家
- 威廉姆斯姐妹和玛丽亚·莎拉波娃占据了大满贯的前三名
- 伊琳娜·德门蒂耶娃、贾斯汀·海宁和玛蒂娜·辛吉斯占据了 T1 锦标赛的前三名。比赛最多的 T1 锦标赛是迈阿密、印第安维尔斯和罗马
- 陈月娇·毛瑞斯莫、林德赛·达文波特和吉姆·克里斯特尔斯占据了 T2 锦标赛的前三名。比赛最多的 T2 锦标赛是阿米莉娅岛、洛杉机和悉尼
大满贯中获胜次数最多的
****tournamnentLevelWinners(‘G’)****
T1 锦标赛级别的大多数比赛
****tournamnentLevelTournaments(‘T1’)****
在 T1 锦标赛级别赢得最多
****tournamnentLevelWinners(‘T1’)****
T2 锦标赛级别的大多数比赛
****tournamnentLevelTournaments(‘T2’)****
在 T2 锦标赛中获胜次数最多
****tournamnentLevelWinners(‘T2’)****
T3 锦标赛级别的大多数比赛
****tournamnentLevelTournaments(‘T3’)****
在 T3 锦标赛级别赢得最多
****tournamnentLevelWinners(‘T3’)****
压折
如果赢家排名比输家排名至少高 10 位,我们可以认为是一个冷门。我们绘制了大满贯冠军和失败者之间的排名差异。
在大满贯决赛中造成冷门的球员
我们注意到大满贯决赛中冷门很少。但是他们中的一些在那里,让我们找出他们是什么。
维纳斯·大威廉姆斯在大满贯决赛中创造了两次冷门。2017 年,当斯隆·斯蒂芬斯在半决赛中击败维纳斯·大威廉姆斯,在决赛中击败麦迪逊·凯斯时,美国公开赛出现了逆转。
****grandslamupsets = grandslam %>%
filter(rankingdiff < -10) %>%
select(winner_name,loser_name,winner_rank,loser_rank,tourney_name,year)plotUpsets = function(upsetsData,titleName)
{
upsetsData %>%
group_by(winner_name) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
ungroup() %>%
mutate(winner_name = reorder(winner_name,Count)) %>%
head(10) %>%
ggplot(aes(x = winner_name,y = Count)) +
geom_bar(stat=’identity’,colour=”white”,fill=fillColor2) +
geom_text(aes(x = winner_name, y = 1, label = paste0(“(“,Count,”)”,sep=””)),
hjust=0, vjust=.5, size = 4, colour = ‘black’,
fontface = ‘bold’) +
labs(x = titleName,
y = ‘Count’,
title = titleName) +
coord_flip() +
theme_bw()
}
plotUpsets(grandslamupsets,’Upset Winner in Grand Slam Finals’)datatable(grandslamupsets, style=”bootstrap”, class=”table-condensed”, options = list(dom = ‘tp’,scrollX = TRUE))****
大满贯半决赛的冷门
威廉姆斯姐妹再次在大满贯半决赛中制造冷门。
****grandslamSF =getGrandSlamWinners(‘SF’)grandslamupsets = grandslamSF %>%
filter(rankingdiff < -10) %>%
select(winner_name,loser_name,winner_rank,loser_rank,tourney_name,year)plotUpsets(grandslamupsets,’Upset Winner in Grand Slam Semi Finals’)datatable(grandslamupsets, style=”bootstrap”, class=”table-condensed”, options = list(dom = ‘tp’,scrollX = TRUE))****
T1 锦标赛中的冷门
****getTournamentWinners = function(tourney_level_name,roundname)
{
return(
matches %>%
filter(tourney_level == tourney_level_name) %>%
filter(round == roundname) %>%
mutate(agediff = winner_age — loser_age) %>%
mutate(rankingdiff = loser_rank — winner_rank)
)
}T1Winners = getTournamentWinners(‘T1’,’F’)T1upsets = T1Winners %>%
filter(rankingdiff < -10) %>%
select(winner_name,loser_name,winner_rank,loser_rank,tourney_name,year)plotUpsets(T1upsets,’Upset Winner in T1 tournament Finals’)datatable(T1upsets, style=”bootstrap”, class=”table-condensed”, options = list(dom = ‘tp’,scrollX = TRUE))****
T2 锦标赛的冷门
****T2Winners = getTournamentWinners(‘T2’,’F’)T2upsets = T2Winners %>%
filter(rankingdiff < -10) %>%
select(winner_name,loser_name,winner_rank,loser_rank,tourney_name,year)plotUpsets(T2upsets,’Upset Winner in T2 tournament Finals’)datatable(T2upsets, style=”bootstrap”, class=”table-condensed”, options = list(dom = ‘tp’,scrollX = TRUE))****
你可能也喜欢访问我的网站 http://ambarishg.github.io,了解其他有趣的帖子。
Python 中的音乐特征提取
不同类型的音频特征以及如何提取它们。
MFCC feature extraction
特征提取是分析和发现不同事物之间关系的一个非常重要的部分。所提供的音频数据不能被模型直接理解以将它们转换成可理解的格式,使用特征提取。这是一个以可理解的方式解释大部分数据的过程。分类、预测和推荐算法都需要特征提取。
在这个博客中,我们将提取音乐文件的特征,这将有助于我们将音乐文件分类为不同的流派,或者根据您的喜好推荐音乐。我们将学习用于提取音乐特征的不同技术。
音频信号是三维信号,其中三个轴代表时间、振幅和频率。
Audio signal
要使用的包
我们将使用 librosa 来分析和提取音频信号的特征。为了播放音频,我们将使用 pyAudio ,这样我们就可以直接在 jupyter 上播放音乐。
加载音频
import librosa
audio_path = 'audio-path'
x , sr = librosa.load(audio_path)
print(type(x), type(sr))
.load
加载一个音频文件,解码成一维数组,一维数组是时间序列x
,sr
是采样率x
。默认sr
是 22kHz。我们可以通过以下方式覆盖sr
librosa.load(audio_path, sr=44100)
我们可以通过以下方式禁用采样:
librosa.load(audio_path, sr=none)
播放音频
import IPython.display as ipd
ipd.Audio(audio_path)
IPython.display
允许我们直接在 jupyter 笔记本上播放音频。它有一个非常简单的界面,有一些基本的按钮。
#display waveform
%matplotlib inline
import matplotlib.pyplot as plt
import librosa.display
plt.figure(figsize=(14, 5))
librosa.display.waveplot(x, sr=sr)
librosa.display
用于显示不同格式的音频文件,如波形图、声谱图或色彩图。波形图让我们知道在给定时间音频的响度。频谱图显示了在特定时间播放的不同频率及其振幅。振幅和频率是声音的重要参数,对于每个音频都是唯一的。librosa.display.waveplot
用于绘制振幅对时间的波形,其中第一轴为振幅,第二轴为时间。
频谱图
声谱图是声音的频率的频谱或其他信号随时间变化的直观表示。它代表了给定音乐信号的频率随时间的变化。
#display Spectrogram
X = librosa.stft(x)
Xdb = librosa.amplitude_to_db(abs(X))
plt.figure(figsize=(14, 5))
librosa.display.specshow(Xdb, sr=sr, x_axis='time', y_axis='hz')
#If to pring log of frequencies
#librosa.display.specshow(Xdb, sr=sr, x_axis='time', y_axis='log')
plt.colorbar()
.stft
将数据转换成短期傅立叶变换。 STFT 转换信号,这样我们就可以知道给定时间给定频率的幅度。使用 STFT,我们可以确定音频信号在给定时间播放的各种频率的幅度。.specshow
用于显示频谱图。
输出如下所示:
spectrum
特征提取
过零率
过零率是信号符号变化的速率,即信号从正变到负或反变的速率。这个特性已经在语音识别和音乐信息检索中大量使用。它通常具有较高的值,适用于像金属和摇滚这样的高敲击声。
x, sr = librosa.load(audio_path)
#Plot the signal:
plt.figure(figsize=(14, 5))
librosa.display.waveplot(x, sr=sr)
放大:
这里我们将只缩放或打印 100 个阵列列的光谱。
# Zooming in
n0 = 9000
n1 = 9100
plt.figure(figsize=(14, 5))
plt.plot(x[n0:n1])
plt.grid()
情节看起来像:
正如我们所见,在给定的图表中有三个过零点。
我们也可以使用给定代码计算过零事件:
zero_crossings = librosa.zero_crossings(x[n0:n1], pad=False)
print(sum(zero_crossings))
光谱质心
它表示声音的“质量中心”所在的位置,计算方法是声音中频率的加权平均值。如果音乐中的频率始终相同,那么频谱质心将围绕一个中心,如果在声音的末端有高频,那么质心将朝向其末端。
#spectral centroid -- centre of mass -- weighted mean of the frequencies present in the sound
import sklearn
spectral_centroids = librosa.feature.spectral_centroid(x, sr=sr)[0]
spectral_centroids.shape# Computing the time variable for visualization
frames = range(len(spectral_centroids))
t = librosa.frames_to_time(frames)
# Normalising the spectral centroid for visualisation
def normalize(x, axis=0):
return sklearn.preprocessing.minmax_scale(x, axis=axis)
#Plotting the Spectral Centroid along the waveform
librosa.display.waveplot(x, sr=sr, alpha=0.4)
plt.plot(t, normalize(spectral_centroids), color='r')
.spectral_centroid
用于计算每一帧的光谱质心。所以它将返回一个数组,其列数等于样本中的帧数。
.frames_to_time
将帧转换为时间。时间[i] ==帧[i]。
我们正在标准化,以便我们可以轻松地可视化数据。
spectral centroid
与过零率类似,在信号开始时,频谱质心会出现杂散上升。这是因为开始时的静音幅度很小,高频成分有机会占主导地位。
光谱衰减
频谱衰减是总频谱能量的指定百分比(例如 85%)低于的频率。
它也给出了每一帧的结果。
spectral_rolloff = librosa.feature.spectral_rolloff(x, sr=sr)[0]
librosa.display.waveplot(x, sr=sr, alpha=0.4)
plt.plot(t, normalize(spectral_rolloff), color='r')
.spectral_rolloff
用于计算给定帧的滚降。
Spectral Rolloff
MFCC —梅尔频率倒谱系数
该特征是提取音频信号特征的最重要的方法之一,并且主要在处理音频信号时使用。信号的 mel 频率倒谱系数(MFCCs)是一小组特征(通常约 10–20),它们简明地描述了频谱包络的整体形状。
mfccs = librosa.feature.mfcc(x, sr=sr)
print(mfccs.shape)#Displaying the MFCCs:
librosa.display.specshow(mfccs, sr=sr, x_axis='time')
.mfcc
用于计算信号的 mfccs。
通过打印 MFCC 的形状,你可以得到多少 MFCC 是根据多少帧计算的。第一个值表示计算的 mfccs 的数量,另一个值表示可用的帧数。
MFCC
现在,我们已经提取了音乐信号的特征。我们可以使用在各种用例中提取的这个特征,例如分类成不同的流派。我们将在我们的下一篇博客中实现这一点。
在 Python 中提取和转换数据
为了从数据中获得洞察力,你必须对它稍加处理…
重要的是能够从数据帧中提取、过滤和转换数据,以便深入到真正重要的数据中。熊猫图书馆有许多技术使这一过程高效而直观。在今天的文章中,我将列出这些技术,并附有代码示例和一些解释。让我们开始吧。
在这篇文章中,我用随机数创建了一个样本数据帧来玩它。在本文的解释过程中,我们将使用这些数据作为例子。
import pandas as pd
import numpy as np
cols = ['col0', 'col1', 'col2', 'col3', 'col4']
rows = ['row0', 'row1', 'row2', 'row3', 'row4']
data = np.random.randint(0, 100, size=(5, 5))
df = pd.DataFrame(data, columns=cols, index=rows)df.head()**Out[2]:
col0 col1 col2 col3 col4
row0 24 78 42 7 96
row1 40 4 80 12 84
row2 83 17 80 26 15
row3 92 68 58 93 33
row4 78 63 35 70 95**
索引数据帧
为了从 pandas 数据帧中提取数据,我们可以使用直接索引或访问器。我们可以使用标签选择必要的行和列:
df['col1']['row1']**Out[3]: 4**
请注意这种索引的顺序:首先指定列标签,然后指定行。但事实是,数据集很少,而且很小,而在现实生活中,我们使用的机器要重得多。使用访问器— 来选择数据要好得多。锁定和*。iloc* 。他们的区别在于*。loc* 接受标签,而接受标签。iloc —索引。同样,当我们使用访问器时,我们首先指定行,然后指定列。一开始我很难适应它——SQL 背景,你还能说什么…
因此,要使用访问器选择单个值,您需要执行以下操作:
df.loc['row4', 'col2']
**Out[4]: 35** df.iloc[4, 2]
**Out[5]: 35**
使用索引,我们可以从数据帧中选择单个值、系列或数据帧(抱歉,这是同义反复)。上面我已经展示了如何选择一个值。
要再选择几列,只需传递其标签的嵌套列表,DataFrame 将被返回:
df_new = df[['col1','col2']]
df_new.head(3)
**Out[6]:
col1 col2
row0 78 42
row1 4 80
row2 17 80**
如果您还想选择特定的行,添加它的索引,您将再次获得一个数据帧。这种技术被称为切片,下面会有更详细的介绍。
df_new = df[['col1','col2']][1:4]
df_new.head(3)
**Out[7]:
col1 col2
row1 4 80
row2 17 80
row3 68 58**
要选择一个系列,您必须选择包含所有行或一系列行的单个列。每一行代码都会产生相同的输出:
df['col0']
df.loc[:,'col0']
df.iloc[:, 0]
**Out[8]:
row0 24
row1 40
row2 83
row3 92
row4 78
Name: col0, dtype: int32**
冒号意味着我们要选择所有行或所有列— df.loc[:,:] 或 df.iloc[:,:] 将返回所有值。慢慢地,我们进入了切片阶段——从数据中选择特定的范围。要对一个系列进行切片,您只需添加要使用其索引选择的一系列行:
df['col3'][2:5]
**Out[12]:
row2 26
row3 93
row4 70
Name: col3, dtype: int32**
不要忘记 Python 中的范围——第一个元素被包含,第二个被排除。所以上面的代码将返回索引为 5、6、7、8 和 9 的行。索引从 0 开始。
切片数据帧的工作方式相同。只有一个细微的差别。使用时。loc (标签)包括两个边框。例如,选择从标签“行 1”到标签“行 4”的行或从行索引 1 到索引 4 的行以及所有列:
df.loc['row1':'row4', :]
**Out[20]:
col0 col1 col2 col3 col4
row1 40 4 80 12 84
row2 83 17 80 26 15
row3 92 68 58 93 33
row4 78 63 35 70 95** df.iloc[1:4, :]
**Out[21]:
col0 col1 col2 col3 col4
row1 40 4 80 12 84
row2 83 17 80 26 15
row3 92 68 58 93 33**
上面的第一行代码选择了 row1、row2、row3 和 row4。而第二个—仅行 1、行 2 和行 3。下面再举几个例子。
选择从标签“列 1”到标签“列 4”或从列索引 1 到索引 4 的列以及所有行:
df.loc[:, 'col1':'col4']
**Out[22]:
col1 col2 col3 col4
row0 78 42 7 96
row1 4 80 12 84
row2 17 80 26 15
row3 68 58 93 33
row4 63 35 70 95** df.iloc[:, 1:4]
**Out[23]:
col1 col2 col3
row0 78 42 7
row1 4 80 12
row2 17 80 26
row3 68 58 93
row4 63 35 70**
选择从标签“行 1”到标签“行 4”或从行索引 1 到索引 4 的行,以及从标签“列 1”到标签“列 4”或从列索引 1 到索引 4 的列:
df.loc['row1':'row4', 'col1':'col4']
**Out[24]:
col1 col2 col3 col4
row1 4 80 12 84
row2 17 80 26 15
row3 68 58 93 33
row4 63 35 70 95** df.iloc[1:4,1:4] **Out[25]:
col1 col2 col3
row1 4 80 12
row2 17 80 26
row3 68 58 93**
使用列表选择不在范围内的特定列或行。
df.loc['row2':'row4', ['col1','col3']]
**Out[28]:
col1 col3
row2 17 26
row3 68 93
row4 63 70** df.iloc[[2,4], 0:4]
**Out[30]:
col0 col1 col2 col3
row2 83 17 80 26
row4 78 63 35 70**
过滤数据帧
过滤是一种更通用的工具,它根据数据本身的感兴趣的属性而不是索引或标签来选择数据的一部分。数据帧有几种过滤方法。所有这些方法的基本思想是布尔级数。在这个条件为真的情况下, df[‘col1’] > 20 (我们假设 col1 的类型是 integer)将返回一个布尔序列。我将在这里输入。head()方法,所以不需要向上滚动来匹配数字。
**Out[2]:
col0 col1 col2 col3 col4
row0 24 78 42 7 96
row1 40 4 80 12 84
row2 83 17 80 26 15
row3 92 68 58 93 33
row4 78 63 35 70 95**
因此,要选择数据帧中 col1 的值大于 20 的部分,我们将使用以下代码:
df[df['col1'] > 20]
*# assigning variable also works*
condition = df['col1'] > 20
df[condition]
**Out[31]:
col0 col1 col2 col3 col4
row0 24 78 42 7 96
row3 92 68 58 93 33
row4 78 63 35 70 95**
我们可以使用标准的逻辑运算符(and — &,or — |,not — ~)来组合这些过滤器。请注意括号在这些操作中的用法。
df[(df['col1'] > 25) & (df['col3'] < 30)] # logical and
**Out[33]:
col0 col1 col2 col3 col4
row0 24 78 42 7 96** df[(df['col1'] > 25) | (df['col3'] < 30)] # logical or
**Out[34]:
col0 col1 col2 col3 col4
row0 24 78 42 7 96
row1 40 4 80 12 84
row2 83 17 80 26 15
row3 92 68 58 93 33
row4 78 63 35 70 95** df[~(df['col1'] > 25)] # logical not
**Out[35]:
col0 col1 col2 col3 col4
row1 40 4 80 12 84
row2 83 17 80 26 15**
处理 0 和 NaN 值
几乎所有的数据集都有零值或 NaN 值,我们肯定想知道它们在哪里。我们的比较特殊,所以我们会稍微修改一下:
df.iloc[3, 3] = 0
df.iloc[1, 2] = np.nan
df.iloc[4, 0] = np.nan
df['col5'] = 0
df['col6'] = np.NaN
df.head()
**Out[57]:
col0 col1 col2 col3 col4 col5 col6
row0 24.0 78 42.0 7 96 0 NaN
row1 40.0 4 NaN 12 84 0 NaN
row2 83.0 17 80.0 26 15 0 NaN
row3 92.0 68 58.0 0 33 0 NaN
row4 NaN 63 35.0 70 95 0 NaN**
要选择没有任何零值的列,我们可以使用*。all()* 方法(所有数据都存在):
df.loc[:, df.all()] **Out[43]:
col0 col1 col2 col4 col6
row0 24.0 78 42.0 96 NaN
row1 40.0 4 NaN 84 NaN
row2 83.0 17 80.0 15 NaN
row3 92.0 68 58.0 33 NaN
row4 NaN 63 35.0 95 NaN**
如果我们希望找到至少有一个非零(任意)值的列,这将有助于:
df.loc[:, df.any()]
**Out[47]:
col0 col1 col2 col3 col4
row0 24.0 78 42.0 7 96
row1 40.0 4 NaN 12 84
row2 83.0 17 80.0 26 15
row3 92.0 68 58.0 0 33
row4 NaN 63 35.0 70 95**
要选择具有任何 NaN 的列:
df.loc[:, df.isnull().any()]
**Out[48]:
col0 col2 col6
row0 24.0 42.0 NaN
row1 40.0 NaN NaN
row2 83.0 80.0 NaN
row3 92.0 58.0 NaN
row4 NaN 35.0 NaN**
选择不带 nan 的色谱柱:
df.loc[:, df.notnull().all()]
**Out[49]:
col1 col3 col4 col5
row0 78 7 96 0
row1 4 12 84 0
row2 17 26 15 0
row3 68 0 33 0
row4 63 70 95 0**
我们可以删除那些包含 nan 的行,但是这是一个危险的游戏——删除通常不是一个解决方案。您必须理解您的数据,并明智地处理这样的行。我警告过你。
你确定你想知道吗?好…😀
df.dropna(how='all', axis=1) # if all values in a column are NaN it will be dropped
**Out[69]:
col0 col1 col2 col3 col4 col5
row0 24.0 78 42.0 7 96 0
row1 40.0 4 NaN 12 84 0
row2 83.0 17 80.0 26 15 0
row3 92.0 68 58.0 0 33 0
row4 NaN 63 35.0 70 95 0** df.dropna(how='any', axis=1) # if any value in a row is NaN it will be dropped
**Out[71]:
col1 col3 col4 col5
row0 78 7 96 0
row1 4 12 84 0
row2 17 26 15 0
row3 68 0 33 0
row4 63 70 95 0**
这种方法不会修改原始数据帧,因此要继续处理过滤后的数据,必须将其分配给新的数据帧或重新分配给现有的数据帧
df = df.dropna(how='any', axis=1)
过滤的美妙之处在于,我们实际上可以根据一列的值来选择或修改另一列的值。例如,我们可以从 col1 中选择值,其中 col2 大于 35,并通过给每个值加 5 来更新这些值:
*# Find a column based on another*
df['col1'][df['col2'] > 35]
**Out[74]:
row0 78
row2 17
row3 68
Name: col1, dtype: int32**
*# Modify a column based on another*
df['col1'][df['col2'] > 35] += 5
df['col1']
**Out[77]:
row0 83
row1 4
row2 22
row3 73
row4 63
Name: col1, dtype: int32**
这让我们进入下一部分——
转换数据帧
一旦我们选择或过滤了我们的数据,我们想以某种方式转换它。最好的方法是使用继承到 DataFrames 或 numpy universal funcs 的方法,它们按元素方式转换整列数据。熊猫就是例子。floordiv() 函数(来自文档:
‘data frame 和其他元素的整数除法’)或 numpy 的*。floor_divide()* (doc:‘返回小于或等于输入的除法的最大整数。’).
如果这些函数不可用,我们可以编写自己的函数并与*一起使用。应用()*方法。
def some_func(x):
return x * 2df.apply(some_func) -- *# update each entry of a DataFrame without any loops**# lambda also works*
df.apply(lambda n: n*2) -- *# the same*
这些函数不返回转换,所以我们必须显式地存储它:
df['new_col'] = df['col4'].apply(lambda n: n*2)
df.head()
**Out[82]:
col0 col1 col2 col3 col4 col5 col6 new_col
row0 24.0 83 42.0 7 96 0 NaN 192
row1 40.0 4 NaN 12 84 0 NaN 168
row2 83.0 22 80.0 26 15 0 NaN 30
row3 92.0 73 58.0 0 33 0 NaN 66
row4 NaN 63 35.0 70 95 0 NaN 190**
如果 index 是一个字符串,它有一个*。允许我们一次修改整个索引的访问器:*
df.index.str.upper()
**Out[83]: Index(['ROW0', 'ROW1', 'ROW2', 'ROW3', 'ROW4'], dtype='object')**
同样,我们不能使用*。对索引应用()方法—它的替代方法是。map()*
df.index = df.index.map(str.lower)
**Out[85]: Index(['row0', 'row1', 'row2', 'row3', 'row4'], dtype='object')**
但是*。map()* 也可以用在列上。例如:
*# Create the dictionary: red_vs_blue*
red_vs_blue = {0:'blue', 12:'red'}
*# Use the dictionary to map the 'col3' column to the new column df['color']*
df['color'] = df['col3'].map(red_vs_blue)
df.head()
**Out[92]:
col0 col1 col2 col3 col4 col5 col6 new_col color
row0 24.0 83 42.0 7 96 0 NaN 192 NaN
row1 40.0 4 NaN 12 84 0 NaN 168 red
row2 83.0 22 80.0 26 15 0 NaN 30 NaN
row3 92.0 73 58.0 0 33 0 NaN 66 blue
row4 NaN 63 35.0 70 95 0 NaN 190 NaN**
序列和数据帧上的算术运算直接起作用。下面的表达式将创建一个新列,其中索引为 n 的每个值是来自*‘col 3’和‘col 7’的索引为 n 的值的总和。*
df['col7'] = df['col3'] + df['col4']
df.head()
**Out[94]:
col0 col1 col2 col3 col4 col5 col6 new_col color col7
row0 24.0 83 42.0 7 96 0 NaN 192 NaN 103
row1 40.0 4 NaN 12 84 0 NaN 168 red 96
row2 83.0 22 80.0 26 15 0 NaN 30 NaN 41
row3 92.0 73 58.0 0 33 0 NaN 66 blue 33
row4 NaN 63 35.0 70 95 0 NaN 190 NaN 165**
这是文章的第二个版本,因为第一个完全是一团糟——代码中有错误,没有例子,也没有其他东西。感谢反馈,我又看了一遍这篇文章,我觉得现在看起来好多了。我在这里用代码片段和例子讲述了用 Python 转换和提取数据的基础知识,希望对刚开始涉足这一领域的人有用。
同时,热爱数据科学,多笑笑。我们必须乐观,因为我们拥有 21 世纪最性感的工作😀
原载于sergilehkyi.com。
利用 k-均值聚类从图像中提取颜色
Photo by @shawnanggg on Unsplash
问题是
我想写一些软件,允许我从图像中提取一组颜色,并以一种似乎自然的方式完成,并考虑人类的感知。一个配色方案通常可以概括整个图像的“氛围”,所以我认为这将是一件有用的事情。
所以……我花了一些时间来思考我能做到这一点的一些方法。我设计了一些相当简单的算法,例如,将图像有规律地分割成块,并输出这些部分的平均颜色。也许可以添加额外的层,将这些块相互比较并组合成组,也许每种颜色可以递归地与另一种颜色组合,直到达到所需的颜色数量。不过,我很快意识到,这个问题在一般情况下已经得到了解决,而且以一种非常好的方式。
解决方案
K 均值聚类是一种方法,通过这种方法可以将一组数据点划分为几个不相交的子集,每个子集中的点被认为彼此“接近”(根据某种度量)。一个常见的度量标准是 bog 标准欧几里德距离函数,至少当这些点可以用几何方法表示时是这样。“k”只是指最终输出中所需的子集数量。事实证明,这种方法正是我们将图像分成一组颜色所需要的。
我们的案子
在我们的例子中,“数据点”是颜色,距离函数是两种颜色“不同程度”的度量。我们的任务是将这些颜色分成给定数量的集合,然后计算每个集合的平均颜色。使用平均值似乎是一个相当明智的选择,因为你可以想象当你看着不同的颜色集群时模糊你的眼睛,并看到每一个的平均颜色。然而,我们可以使用任何其他的统计方法(众数,中位数,或其他任何东西!)并可能得到更好的结果。
让我们用 JavaScript 写这个吧! ✨
数据点
每个数据点都是一种颜色,可以用一个 RGB 颜色空间中的一个点来表示。
在 JavaScript 中,单个数据点可能看起来像这样:
*// An array if we want to be general* let colour = [100,168,92];*// Or an object if we want to be more explicit* let colour = {red: 100, green: 168, blue: 92};
距离函数
因为我们希望能够计算两种颜色有多相似,所以我们需要一个函数。这是我们有许多选择的另一点,但一个简单的方法是使用每种颜色的分量值来计算欧几里德距离。
我们的距离函数可以是这样的:
*// Distance function* function euclideanDistance(a, b) {
let sum = 0;
for (let i = 0; i < a.length; i++) {
sum += Math.pow(b[i] — a[i], 2);
}
return Math.sqrt(sum);
}
由于我们没有在函数中指定固定数量的组件,因此它将适用于 n 维数据点(有 n 个组件)。如果我们以后想用不同的方式来表现颜色,这是很有用的。
该算法
k-means 聚类最常用的算法叫做劳氏算法(尽管它通常被简称为k-means 算法)。我们将在这里使用该算法。
我们将创建一组称为“质心”的对象,每个对象定义一个单独的、唯一的簇。
质心有两个相关的东西:
- 数据集范围内的点(质心位置)
- 数据集中的一组数据点(质心簇中的点)
该算法有三个主要步骤:
1.初始化。选择质心的初始值。在这种情况下,我们将为每一个随机选择一个点。
2.分配。将每个数据点分配给均值(质心)距离最小的聚类。
3.更新。将每个质心的新平均值设置为与其关联的所有数据点的平均值(在质心的聚类中)。
该算法将执行初始化一次,然后依次重复执行赋值和更新,直到算法收敛。
当赋值之间没有变化时,该算法被称为“收敛”。基本上,当这些点决定它们属于哪个集群时,我们可以停止循环。
助手功能
让我们定义一些辅助函数:一个用于计算给定数据集的范围,一个用于生成给定范围内的随机整数。n 维数据集的“范围”只是一组范围——每个维度一个范围。
**// Calculate range of a one-dimensional data set*function rangeOf(data) {
return {min: Math.min(data), max: Math.max(data)};
}*// Calculate range of an n-dimensional data set* function rangesOf(data) {
let ranges = []; for (let i = 0; i < data[0].length; i++) {
ranges.push(rangeOf(data.map(x => x[i])));
} return ranges;
}*// Generate random integer in a given closed interval* function randomIntBetween(a, b) {
return Math.floor(Math.random() * (b - a + 1)) + a;
}*
假设我们已经得到了这两个,我们现在可以为算法的三个步骤编写代码。
第一步—初始化
对于所需的质心数量(k),我们在所提供的数据集范围内生成一个随机整数值点,并将其附加到一个数组中。数组中的每个点代表一个质心的位置。当然,质心的维数与数据的维数相同。
*function initialiseCentroidsRandomly(data, k) {
let ranges = rangesOf(data);
let centroids = [];
for (let i = 0; i < k; i++) {
let centroid = [];
for (let r in ranges) {
centroid.push(
randomIntBetween(ranges[r].min, ranges[r].max));
} centroids.push(centroid);
} return centroids;
}*
第二步——分配
这是我们将数据点分配给集群的地方。对于每个点,我们选择距离最小的质心,并将该点附加到关联的聚类中。
我在这里使用了数组的 map 函数和一个 arrow 函数,所以如果你不确定发生了什么,可以快速浏览一下。
*function clusterDataPoints(data, centroids) {
let clusters = []; centroids.forEach(function() {
clusters.push([]);
}); data.forEach(function(point) { let nearestCentroid = Math.min(
centroids.map(x => euclideanDistance(point, x))); clusters[centroids.indexOf(nearestCentroid)].push(point);
}); return clusters;
}*
第三步—更新
对于每个聚类,我们计算其包围数据点的平均值,并将其设置为相关质心的位置。然后我们返回新的质心集。
这里实际上使用了另一个函数,叫做均值点*。我就不赘述了,因为实现自己是相当明显的。它返回一个点,该点的分量是每个传递点中相应分量值的平均值。*
*function getNewCentroids(clusters) {
let centroids = []; clusters.forEach(function(cluster) {
centroids.push(meanPoint(cluster));
});return centroids;
}*
哦,还有一件事!
这种算法有一个问题,就是有时聚类会变空。对于发生这种情况时应该做什么还没有达成共识,但是一些可能的方法是:
- 移除集群(有点傻)
- 将随机数据点分配给群集
- 将最近的数据点分配给聚类
- 重启算法,希望不会再发生
虽然最后一个选项看起来有点混乱,但是算法(带有随机初始化)是不确定的,所以简单地重启就能很好地工作。毕竟,这种方法很大程度上是一种试探法,而试探法从定义上来说是“一种杂烩”……至少与可靠的算法相比是这样。
齐心协力
现在我们已经定义了基本的功能,剩下的就是编写一些代码,在算法要求时调用每个函数。
我将把它留给你作为一个练习,但是,如果你真的想,你可以在这里看到我作为一个 web 应用程序的实现。🌈
Here’s what it looks like after being fed the cover of a Ryan Hemsworth song.
从短文本中提取关键词
专业提示:更多关于人工智能和机器学习的文章、故事、代码和研究,请访问我的网站:【www.theapemachine.com】
多年来,我多次遇到提取关键词的问题,尤其是从单句、标题或问题等短文本中提取关键词。
在 NLP 中处理短文本一直是一个众所周知的问题,而识别关键词尤其是很难做到。
这些年来,我尝试了许多方法,从我自己基于一个非常流行的文字游戏非常天真地实现了一个简单的评分算法,到快速自动关键词提取(RAKE ),如果没有大型文档要处理,没有一种方法表现得非常好。
最终,随着我对现代常见的机器学习模型越来越熟悉,我开始将关键词提取视为“简单!翻译问题,在这篇文章中,我们将回顾导致一个伟大的关键字提取器,我们现在可以在生产中使用的旅程。
我第一次需要找到一个短句中最重要的关键词时,我正在为一家心理学家公司开发一些软件,他们正在开发一个平台,其中有一个模块需要从语言挑战数据库中提取相关的句子。
这个想法是要将要检索的句子与前一个句子的关键词进行匹配,由于我当时对机器学习知之甚少,所以我用自己的天真解决方案来计算句子中每个单词的拼字得分,并从这些结果中取出得分最高的第 n 个单词,并假设它们是关键词。
如果不是因为一些在拼字游戏中得分很高的停用词,这实际上非常有效。
当然,你可以在这个方法中部署一个非索引列表,事情就会迎刃而解,这甚至是相当可扩展的,因为任何语言都只包含你想要过滤掉的单词,你只需要定义这个列表一次。
耙子
下一个实现是快速自动关键词提取,它实际上只比前一个方法好一点点,并且仍然使用非索引列表。
如今,RAKE 已经作为即插即用模块在许多编程语言中实现,您应该不用花太多时间来试验它,但是我警告您,不要让它在短文本上欺骗您。大多数时候,它实际上并没有提取任何关键词,只是删除了停用词。因为文本很短,所以没有那么多其他的词可以“搜索”。
发明一种新的语言!
最终,我明白了一些事情:如果像序列到序列神经网络这样的现代翻译方法可以翻译语言,或者给出从序列中学习到的响应,为什么我们不能将关键词视为其自身的“语言”。
一种没有语法的语言,可以翻译成(甚至可能翻译成?).
这似乎是一个很好的想法:简单地训练一个序列,对标有关键词的短句数据集进行神经网络排序,并让网络将新句子“翻译”成我们的关键词“语言”。
所以让我们开始吧!
1.生成数据集
现在我有了新方法,我面临的一个挑战是找到一个数据集进行训练,我相信你会认识到这是一个困难。可能会让你吃惊的是,真的没有任何现成的数据集有短句与文本中实际存在的关键词相匹配。当然,有许多关于关键词提取的数据集,但它们都有较大的文本,这些文本已经被手动标注了关键词。
幸运的是,我身边的一个人给了我一个绝妙的主意:使用博客圈真正的大玩家的博客标题,并使用这些页面上的关键字元标签来找到与标题相关的关键字。事实上,这些博客非常关注 SEO,因此我们可以假设这些数据是经过精心策划的。
剩下唯一要做的事情是确保删除所有实际上不在标题中的关键词,尽管你可能想考虑甚至保留这些关键词,因为一旦你的神经网络训练完毕,你不仅能够从一个句子中提取准确的关键词,甚至能够提取相关的关键词。
不过,我会把这留给未来的实验。
下面的 Ruby 脚本正是我用来在 Lifehacker 和 TechCrunch 上搜索关键词的脚本。
require 'mechanize'
**class** **Scrape**
**def** initialize
@mechanize = Mechanize.new
@visited = []
@v_titles = []
@csv = File.open('/home/theapemachine/data/keywords-web2.csv', 'a')
**end**
**def** run(urls)
scraped_urls = []
urls.each **do** |url|
**if** !@visited.include?(url)
**begin**
rawpage = @mechanize.get(url)
rawpage.links.uniq.each **do** |a|
scraped_urls << a.href
**end**
page_title = ''
rawpage.search('title').each **do** |title|
page_title = title.text.downcase.strip
**end**
keywords = rawpage.at('head meta[name="keywords"]')['content'].split(', ').delete_if{|k| page_title.scan(k.downcase).empty?}
**if** !page_title.empty? && !keywords.empty? && !@v_titles.include?(page_title)
puts
puts "TITLE: #{page_title}"
puts "KEYWORDS: #{keywords.join(',')}"
@csv << %("#{page_title}","#{keywords.join(',').downcase}"\n)
**end**
@v_titles << page_title
**rescue**
**end**
**end**
**end**
@visited += urls
run(scraped_urls.uniq)
**end**
**end**
@scrape = Scrape.new
@scrape.run([
'http://lifehacker.com/',
'http://www.techcrunch.com'
])
2.准备数据
我们现在需要准备收集的数据,这样我们就可以在 Pytorch 序列中使用它来排序我接下来将列出的模型。我使用了下面非常简单的 Ruby 脚本来确保训练数据中的每一行首先有文章标题,然后是制表位,然后是关键字。
require 'csv'
train = File.open('./keywords.train', 'w')
csv_text = File.read('/home/theapemachine/data/keywords-web.csv')
csv = CSV.parse(csv_text, headers: **false**)
csv.each **do** |row|
**begin**
train.puts row[0] + "\t" + row[1]
**rescue**
**end**
**end**
3.序列对序列模型
我们现在可以使用 Pytorch 编写我们的序列到序列神经网络,事实上,简单地使用他们的教程部分列出的代码就可以很好地完成这个任务。我在培训周期结束时添加了一个简单的提示,这样我就可以轻松地测试网络。
在以后的版本中,你会想要保存你的模型以备后用,因为在我的笔记本电脑上训练花了很长时间。
由肖恩·罗伯逊(Sean Robertson)的原 Pytorch 源代码翻译而来。
**import** unicodedata
**import** string
**import** re
**import** random
**import** time
**import** math
**import** torch
**import** torch.nn **as** nn
**from** torch.autograd **import** Variable
**from** torch **import** optim
**import** torch.nn.functional **as** F
USE_CUDA = True
SOS_token = 0
EOS_token = 1
**class** **Lang**:
**def** __init__(self, name):
self.name = name
self.word2index = {}
self.word2count = {}
self.index2word = {0: "SOS", 1: "EOS"}
self.n_words = 2
**def** index_words(self, sentence):
**for** word in sentence.split(' '):
self.index_word(word)
**def** index_word(self, word):
**if** word not in self.word2index:
self.word2index[word] = self.n_words
self.word2count[word] = 1
self.index2word[self.n_words] = word
self.n_words += 1
**else**:
self.word2count[word] += 1
**def** unicode_to_ascii(s):
**return** ''.join(
c **for** c in unicodedata.normalize('NFD', u'' + s)
**if** unicodedata.category(c) != 'Mn'
)
**def** normalize_string(s):
s = unicode_to_ascii(s.lower().strip())
s = re.sub(r"([.!?])", r" \1", s)
s = re.sub(r"[^a-zA-Z.!?]+", r" ", s)
**return** s
**def** read_langs(lang1, lang2, reverse=False):
**print**("Reading lines...")
lines = open('keywords.train').read().strip().split('\n')
pairs = [[normalize_string(s) **for** s in l.split('\t')] **for** l in lines]
**if** reverse:
pairs = [list(reversed(p)) **for** p in pairs]
input_lang = Lang(lang2)
output_lang = Lang(lang1)
**else**:
input_lang = Lang(lang1)
output_lang = Lang(lang2)
**return** input_lang, output_lang, pairs
MAX_LENGTH = 100
good_prefixes = (
"i am ", "i m ",
"he is", "he s ",
"she is", "she s",
"you are", "you re "
)
**def** filter_pair(p):
**return** len(p[0].split(' ')) < MAX_LENGTH and len(p[1].split(' ')) < MAX_LENGTH
**def** filter_pairs(pairs):
**return** [pair **for** pair in pairs **if** filter_pair(pair)]
**def** prepare_data(lang1_name, lang2_name, reverse=False):
input_lang, output_lang, pairs = read_langs(lang1_name, lang2_name, reverse)
**print**("Read %s sentence pairs" % len(pairs))
pairs = filter_pairs(pairs)
**print**("Trimmed to %s sentence pairs" % len(pairs))
**print**("Indexing words...")
**for** pair in pairs:
input_lang.index_words(pair[0])
output_lang.index_words(pair[1])
**return** input_lang, output_lang, pairs
input_lang, output_lang, pairs = prepare_data('eng', 'fra', False)
**print**(random.choice(pairs))
**def** indexes_from_sentence(lang, sentence):
**return** [lang.word2index[word] **for** word in sentence.split(' ')]
**def** variable_from_sentence(lang, sentence):
indexes = indexes_from_sentence(lang, sentence)
indexes.append(EOS_token)
var = Variable(torch.LongTensor(indexes).view(-1, 1))
**if** USE_CUDA:
var = var.cuda()
**return** var
**def** variables_from_pair(pair):
input_variable = variable_from_sentence(input_lang, pair[0])
target_variable = variable_from_sentence(output_lang, pair[1])
**return** (input_variable, target_variable)
**class** **EncoderRNN**(nn.Module):
**def** __init__(self, input_size, hidden_size, n_layers=1):
super(EncoderRNN, self).__init__()
self.input_size = input_size
self.hidden_size = hidden_size
self.n_layers = n_layers
self.embedding = nn.Embedding(input_size, hidden_size)
self.gru = nn.GRU(hidden_size, hidden_size, n_layers)
**def** forward(self, word_inputs, hidden):
seq_len = len(word_inputs)
embedded = self.embedding(word_inputs).view(seq_len, 1, -1)
output, hidden = self.gru(embedded, hidden)
**return** output, hidden
**def** init_hidden(self):
hidden = Variable(torch.zeros(self.n_layers, 1, self.hidden_size))
**if** USE_CUDA:
hidden = hidden.cuda()
**return** hidden
**class** **BahdanauAttnDecoderRNN**(nn.Module):
**def** __init__(self, hidden_size, output_size, n_layers=1, dropout_p=0.1):
super(AttnDecoderRNN, self).__init__()
self.hidden_size = hidden_size
self.output_size = output_size
self.n_layers = n_layers
self.dropout_p = dropout_p
self.max_length = max_length
self.embedding = nn.Embedding(output_size, hidden_size)
self.dropout = nn.Dropout(dropout_p)
self.attn = GeneralAttn(hidden_size)
self.gru = nn.GRU(hidden_size * 2, hidden_size, n_layers, dropout=dropout_p)
self.out = nn.Linear(hidden_size, output_size)
**def** forward(self, word_input, last_hidden, encoder_outputs):
word_embedded = self.embedding(word_input).view(1, 1, -1)
word_embedded = self.dropout(word_embedded)
attn_weights = self.attn(last_hidden[-1], encoder_outputs)
context = attn_weights.bmm(encoder_outputs.transpose(0, 1))
rnn_input = torch.cat((word_embedded, context), 2)
output, hidden = self.gru(rnn_input, last_hidden)
output = output.squeeze(0)
output = F.log_softmax(self.out(torch.cat((output, context), 1)))
**return** output, hidden, attn_weights
**class** **Attn**(nn.Module):
**def** __init__(self, method, hidden_size, max_length=MAX_LENGTH):
super(Attn, self).__init__()
self.method = method
self.hidden_size = hidden_size
**if** self.method == 'general':
self.attn = nn.Linear(self.hidden_size, hidden_size)
**elif** self.method == 'concat':
self.attn = nn.Linear(self.hidden_size * 2, hidden_size)
self.other = nn.Parameter(torch.FloatTensor(1, hidden_size))
**def** forward(self, hidden, encoder_outputs):
seq_len = len(encoder_outputs)
attn_energies = Variable(torch.zeros(seq_len))
**if** USE_CUDA:
attn_energies = attn_energies.cuda()
**for** i in range(seq_len):
attn_energies[i] = self.score(hidden, encoder_outputs[i])
**return** F.softmax(attn_energies).unsqueeze(0).unsqueeze(0)
**def** score(self, hidden, encoder_output):
**if** self.method == 'dot':
energy = hidden.dot(encoder_output)
**return** energy
**elif** self.method == 'general':
energy = self.attn(encoder_output)
energy = hidden.dot(energy)
**return** energy
**elif** self.method == 'concat':
energy = self.attn(torch.cat((hidden, encoder_output), 1))
energy = self.other.dot(energy)
**return** energy
**class** **AttnDecoderRNN**(nn.Module):
**def** __init__(self, attn_model, hidden_size, output_size, n_layers=1, dropout_p=0.1):
super(AttnDecoderRNN, self).__init__()
self.attn_model = attn_model
self.hidden_size = hidden_size
self.output_size = output_size
self.n_layers = n_layers
self.dropout_p = dropout_p
self.embedding = nn.Embedding(output_size, hidden_size)
self.gru = nn.GRU(hidden_size * 2, hidden_size, n_layers, dropout=dropout_p)
self.out = nn.Linear(hidden_size * 2, output_size)
**if** attn_model != 'none':
self.attn = Attn(attn_model, hidden_size)
**def** forward(self, word_input, last_context, last_hidden, encoder_outputs):
word_embedded = self.embedding(word_input).view(1, 1, -1)
rnn_input = torch.cat((word_embedded, last_context.unsqueeze(0)), 2)
rnn_output, hidden = self.gru(rnn_input, last_hidden)
attn_weights = self.attn(rnn_output.squeeze(0), encoder_outputs)
context = attn_weights.bmm(encoder_outputs.transpose(0, 1))
rnn_output = rnn_output.squeeze(0)
context = context.squeeze(1)
output = F.log_softmax(self.out(torch.cat((rnn_output, context), 1)))
**return** output, context, hidden, attn_weights
encoder_test = EncoderRNN(MAX_LENGTH, MAX_LENGTH, 2 )
decoder_test = AttnDecoderRNN('general', MAX_LENGTH, MAX_LENGTH, 2)
**print**(encoder_test)
**print**(decoder_test)
encoder_hidden = encoder_test.init_hidden()
word_input = Variable(torch.LongTensor([1, 2, 3]))
**if** USE_CUDA:
encoder_test.cuda()
word_input = word_input.cuda()
encoder_outputs, encoder_hidden = encoder_test(word_input, encoder_hidden)
word_inputs = Variable(torch.LongTensor([1, 2, 3]))
decoder_attns = torch.zeros(1, 3, 3)
decoder_hidden = encoder_hidden
decoder_context = Variable(torch.zeros(1, decoder_test.hidden_size))
**if** USE_CUDA:
decoder_test.cuda()
word_inputs = word_inputs.cuda()
decoder_context = decoder_context.cuda()
**for** i in range(3):
decoder_output, decoder_context, decoder_hidden, decoder_attn = decoder_test(word_inputs[i], decoder_context, decoder_hidden, encoder_outputs)
**print**(decoder_output.size(), decoder_hidden.size(), decoder_attn.size())
decoder_attns[0, i] = decoder_attn.squeeze(0).cpu().data
teacher_forcing_ratio = 0.5
clip = 5.0
**def** train(input_variable, target_variable, encoder, decoder, encoder_optimizer, decoder_optimizer, criterion, max_length=MAX_LENGTH):
encoder_optimizer.zero_grad()
decoder_optimizer.zero_grad()
loss = 0
input_length = input_variable.size()[0]
target_length = target_variable.size()[0]
encoder_hidden = encoder.init_hidden()
encoder_outputs, encoder_hidden = encoder(input_variable, encoder_hidden)
decoder_input = Variable(torch.LongTensor([[SOS_token]]))
decoder_context = Variable(torch.zeros(1, decoder.hidden_size))
decoder_hidden = encoder_hidden
**if** USE_CUDA:
decoder_input = decoder_input.cuda()
decoder_context = decoder_context.cuda()
use_teacher_forcing = random.random() < teacher_forcing_ratio
**if** use_teacher_forcing:
**for** di in range(target_length):
decoder_output, decoder_context, decoder_hidden, decoder_attention = decoder(decoder_input, decoder_context, decoder_hidden, encoder_outputs)
loss += criterion(decoder_output[0], target_variable[di])
decoder_input = target_variable[di] # Next target is next input
**else**:
**for** di in range(target_length):
decoder_output, decoder_context, decoder_hidden, decoder_attention = decoder(decoder_input, decoder_context, decoder_hidden, encoder_outputs)
loss += criterion(decoder_output[0], target_variable[di])
topv, topi = decoder_output.data.topk(1)
ni = topi[0][0]
decoder_input = Variable(torch.LongTensor([[ni]]))
**if** USE_CUDA:
decoder_input = decoder_input.cuda()
**if** ni == EOS_token:
**break**
loss.backward()
torch.nn.utils.clip_grad_norm(encoder.parameters(), clip)
torch.nn.utils.clip_grad_norm(decoder.parameters(), clip)
encoder_optimizer.step()
decoder_optimizer.step()
**return** loss.data[0] / target_length
**def** as_minutes(s):
m = math.floor(s / 60)
s -= m * 60
**return** '%dm %ds' % (m, s)
**def** time_since(since, percent):
now = time.time()
s = now - since
es = s / (percent)
rs = es - s
**return** '%s (- %s)' % (as_minutes(s), as_minutes(rs))
attn_model = 'general'
hidden_size = 500
n_layers = 2
dropout_p = 0.05
encoder = EncoderRNN(input_lang.n_words, hidden_size, n_layers)
decoder = AttnDecoderRNN(attn_model, hidden_size, output_lang.n_words, n_layers, dropout_p=dropout_p)
**if** USE_CUDA:
encoder.cuda()
decoder.cuda()
learning_rate = 0.0001
encoder_optimizer = optim.Adam(encoder.parameters(), lr=learning_rate)
decoder_optimizer = optim.Adam(decoder.parameters(), lr=learning_rate)
criterion = nn.NLLLoss()
n_epochs = 50000
plot_every = 200
print_every = 1000
start = time.time()
plot_losses = []
print_loss_total = 0
plot_loss_total = 0
# Begin!
**for** epoch in range(1, n_epochs + 1):
# Get training data for this cycle
training_pair = variables_from_pair(random.choice(pairs))
input_variable = training_pair[0]
target_variable = training_pair[1]
# Run the train function
loss = train(input_variable, target_variable, encoder, decoder, encoder_optimizer, decoder_optimizer, criterion)
# Keep track of loss
print_loss_total += loss
plot_loss_total += loss
**if** epoch == 0: **continue**
**if** epoch % print_every == 0:
print_loss_avg = print_loss_total / print_every
print_loss_total = 0
print_summary = '(%d %d%%) %.4f' % (epoch, epoch / n_epochs * 100, print_loss_avg)
**print**(print_summary)
**if** epoch % plot_every == 0:
plot_loss_avg = plot_loss_total / plot_every
plot_losses.append(plot_loss_avg)
plot_loss_total = 0
**import** matplotlib.pyplot **as** plt
**import** matplotlib.ticker **as** ticker
**import** numpy **as** np
**def** show_plot(points):
plt.figure()
fig, ax = plt.subplots()
loc = ticker.MultipleLocator(base=0.2) # put ticks at regular intervals
ax.yaxis.set_major_locator(loc)
plt.plot(points)
show_plot(plot_losses)
**def** evaluate(sentence, max_length=MAX_LENGTH):
input_variable = variable_from_sentence(input_lang, sentence)
input_length = input_variable.size()[0]
# Run through encoder
encoder_hidden = encoder.init_hidden()
encoder_outputs, encoder_hidden = encoder(input_variable, encoder_hidden)
# Create starting vectors for decoder
decoder_input = Variable(torch.LongTensor([[SOS_token]])) # SOS
decoder_context = Variable(torch.zeros(1, decoder.hidden_size))
**if** USE_CUDA:
decoder_input = decoder_input.cuda()
decoder_context = decoder_context.cuda()
decoder_hidden = encoder_hidden
decoded_words = []
decoder_attentions = torch.zeros(max_length, max_length)
# Run through decoder
**for** di in range(max_length):
decoder_output, decoder_context, decoder_hidden, decoder_attention = decoder(decoder_input, decoder_context, decoder_hidden, encoder_outputs)
decoder_attentions[di,:decoder_attention.size(2)] += decoder_attention.squeeze(0).squeeze(0).cpu().data
# Choose top word from output
topv, topi = decoder_output.data.topk(1)
ni = topi[0][0]
**if** ni == EOS_token:
decoded_words.append('<EOS>')
**break**
**else**:
decoded_words.append(output_lang.index2word[ni])
# Next input is chosen word
decoder_input = Variable(torch.LongTensor([[ni]]))
**if** USE_CUDA:
decoder_input = decoder_input.cuda()
**return** decoded_words, decoder_attentions[:di+1, :len(encoder_outputs)]
**def** evaluate_randomly():
pair = random.choice(pairs)
output_words, decoder_attn = evaluate(pair[0])
output_sentence = ' '.join(output_words)
**print**('>', pair[0])
**print**('=', pair[1])
**print**('<', output_sentence)
**print**('')
evaluate_randomly()
**while** True:
**try**:
raw = raw_input(">")
output_words, attentions = evaluate(raw)
**print** output_words
**except**:
**pass**
**def** show_attention(input_sentence, output_words, attentions):
# Set up figure with colorbar
fig = plt.figure()
ax = fig.add_subplot(111)
cax = ax.matshow(attentions.numpy(), cmap='bone')
fig.colorbar(cax)
# Set up axes
ax.set_xticklabels([''] + input_sentence.split(' ') + ['<EOS>'], rotation=90)
ax.set_yticklabels([''] + output_words)
# Show label at every tick
ax.xaxis.set_major_locator(ticker.MultipleLocator(1))
ax.yaxis.set_major_locator(ticker.MultipleLocator(1))
plt.show()
plt.close()
**def** evaluate_and_show_attention(input_sentence):
output_words, attentions = evaluate(input_sentence)
**print**('input =', input_sentence)
**print**('output =', ' '.join(output_words))
show_attention(input_sentence, output_words, attentions)
一旦这段代码完成训练,您将看到一个简单的提示,您可以在这里测试结果。在我的笔记本电脑上训练需要相当长的时间,因为我让我的 scraper 脚本运行了很长时间。你收集的训练数据越多,效果就越好,但是你必须自己试验,看看什么对你来说效果最好。
以下是我在笔记本电脑上进行了两个小时的训练后得出的句子结果:
我需要食物[u ’ food],‘【T4]’]
我在哪里可以找到食物[u ’ food],'< EOS > ‘]
我想要一些食物>我的胃需要食物
你好我需要食物[u ’ food],’< EOS > ‘]
我想要巴黎的免费食物[u ’ food],’< EOS > '] < EOS > ']
我要踢一场足球赛
足球赛【u’football】,< EOS > ‘]
我可以换酒店吗【u’change】,’】
换酒店我可以【u’change】,< EOS > ']
怎么可以
如果根据更多数据进行训练,结果会显著改善。最终,这不是你在笔记本电脑上做的事情,我使用的最终版本是在云中训练的,以便能够在合理的时间框架内完成训练。
幸运的是,那里有几乎无穷无尽的数据供应,您可以向 scraper 脚本添加更多的博客 URL。
我们来实验一下!
这将是一个非常有趣的实验,看看我们是否可以颠倒整个过程,看看我们是否可以得出一个完整的句子,只给关键字作为输入,这肯定是我会尝试的事情,我会很快报告。