原文:
zh.annas-archive.org/md5/92E2CBA50423C2D275EEE8125598FF8B
译者:飞龙
第四章:预测模型
在本章中,我们将探讨预测建模是什么,以及它如何使用统计数据来预测现有数据的结果。我们将涵盖现实世界的例子,以更好地理解这些概念。我们将了解回归分析的含义,并详细分析其中的一些形式。我们还将看一个预测汽车价格的例子。
这些是本章中我们将涵盖的主题:
-
线性回归及其在 Python 中的实现方式
-
多项式回归,其应用和示例
-
多元回归及其在 Python 中的实现方式
-
我们将构建一个使用 Python 预测汽车价格的示例
-
多层模型的概念和一些需要了解的内容
线性回归
让我们谈谈回归分析,这是数据科学和统计学中非常流行的话题。它的核心是试图将曲线或某种函数拟合到一组观察结果中,然后使用该函数来预测你尚未见过的新值。这就是线性回归的全部内容!
因此,线性回归是将一条直线拟合到一组观察结果中。例如,假设我测量了一群人,我测量的两个特征是他们的体重和身高:
我在x轴上显示了体重,y轴上显示了身高,我可以绘制所有这些数据点,就像人们的体重与身高一样,我可以说,“嗯,这看起来像是一个线性关系,不是吗?也许我可以拟合一条直线并用它来预测新值”,这就是线性回归的作用。在这个例子中,我得到了斜率为 0.6 和y截距为 130.2,这定义了一条直线(一条直线的方程是y=mx+b,其中 m 是斜率,b 是y截距)。给定一个斜率和一个y截距,最能适应我拥有的数据,我可以使用这条线来预测新值。
你可以看到我观察到的重量只涵盖了重 100 公斤的人。如果我有一个重 120 公斤的人怎么办?嗯,我可以使用这条线来计算基于先前数据的 120 公斤的人的身高。
我不知道为什么他们称之为回归。回归有点意味着你在做一些事情。我猜你可以把它看作是在根据你过去的观察结果创建一条线来预测新值,时间上倒退,但这似乎有点牵强。说实话,这只是一个令人困惑的术语,我们用非常花哨的术语来掩盖我们用非常简单的概念做的事情的一种方式。它只是将一条直线拟合到一组数据点。
普通最小二乘法技术
线性回归是如何工作的?在内部,它使用一种称为普通最小二乘法的技术;它也被称为 OLS。你可能也会看到这个术语被提及。它的工作方式是试图最小化每个点与直线之间的平方误差,其中误差只是每个点与你所拥有的直线之间的距离。
因此,我们总结了所有这些错误的平方和,这听起来很像我们计算方差时的情况,对吧,只是不是相对于均值,而是相对于我们定义的直线。我们可以测量数据点相对于该直线的方差,并通过最小化该方差,我们可以找到最适合的直线:
现在你永远不必自己费力去做这件事,但如果你因某种原因不得不这样做,或者如果你只是好奇发生了什么,我现在会为你描述整体算法,以及如果有一天你需要自己费力计算斜率和y截距,你将如何去做。这真的并不复杂。
还记得线的斜率截距方程吗?它是y=mx+c。斜率实际上就是两个变量之间的相关性乘以Y的标准差除以X的标准差。标准差在数学中自然地出现可能看起来有点奇怪,但是记住相关性也包含了标准差,所以不太奇怪你必须重新引入这个术语。
然后,截距可以计算为Y的平均值减去斜率乘以X的平均值。再次强调,尽管这并不是非常困难,Python 会为你完成所有计算,但重点是这些并不是难以运行的复杂事情。它们实际上可以非常高效地完成。
记住,最小二乘法最小化了每个点到线的平方误差的总和。另一种思考线性回归的方式是,你正在定义一条代表观察线的最大可能性的线;也就是说,y值在给定x值时的最大概率。
有时人们称线性回归为最大似然估计,这只是人们给一个非常简单的东西起了一个花哨的名字的又一个例子,所以如果你听到有人谈论最大似然估计,他们实际上是在谈论回归。他们只是试图显得很聪明。但现在你也知道了这个术语,所以你也可以显得很聪明。
梯度下降技术
进行线性回归有多种方法。我们已经谈到了普通最小二乘法是拟合一组数据的简单方法,但也有其他技术,梯度下降就是其中之一,它在三维数据中效果最好。因此,它试图为你跟随数据的轮廓。这非常高级,显然计算成本更高一些,但是 Python 确实让你很容易地尝试它,如果你想将其与普通最小二乘法进行比较。
使用梯度下降技术在处理三维数据时是有意义的。
通常情况下,最小二乘法是进行线性回归的一个完全合理的选择,它总是一个合法的事情,但是如果你遇到梯度下降,你会知道那只是进行线性回归的另一种方式,通常在更高维度的数据中看到。
确定系数或 R 平方
那么,我如何知道我的回归有多好?我的线对数据的拟合程度如何?这就是 R 平方的作用,R 平方也被称为确定系数。虽然有人可能会试图显得聪明一点,称其为确定系数,但通常被称为 R 平方。
它是你的模型捕捉到的 Y 的总变化的分数。你的线有多好地跟随了发生的变化?我们在你的线的两侧是否得到了相等数量的变化?这就是 R 平方在衡量的。
计算 R 平方
要实际计算该值,取 1 减去平方误差的总和除以平方变化的总和:
因此,计算起来并不是很困难,但是 Python 会为你提供函数,可以帮你计算,所以你实际上不需要自己进行数学计算。
解释 R 平方
对于 R 平方,你将得到一个从 0 到 1 的值。0 意味着你的拟合很糟糕。它没有捕捉到数据的任何变化。而 1 是完美的拟合,数据的所有变化都被这条线捕捉到,你在线的两侧看到的所有变化应该是相同的。所以 0 是糟糕的,1 是好的。这就是你真正需要知道的。介于两者之间的值就是介于两者之间的值。低 R 平方值意味着拟合很差,高 R 平方值意味着拟合很好。
正如你将在接下来的部分中看到的,有多种方法可以进行回归。线性回归是其中之一。这是一种非常简单的技术,但也有其他技术,你可以使用 R 平方作为一个定量的度量来衡量给定回归对一组数据点的拟合程度,然后使用它来选择最适合你的数据的模型。
使用 Python 计算线性回归和 R 平方
现在让我们来玩一下线性回归,实际计算一些线性回归和 R 平方。我们可以从这里创建一些 Python 代码,生成一些随机的数据,实际上是线性相关的。
在这个例子中,我将捏造一些关于页面渲染速度和人们购买金额的数据,就像之前的例子一样。我们将捏造网站加载所需时间和人们在该网站上花费的金额之间的线性关系:
%matplotlib inline
import numpy as np
from pylab import *
pageSpeeds = np.random.normal(3.0, 1.0, 1000)
purchaseAmount = 100 - (pageSpeeds + np.random.normal(0, 0.1,
1000)) * 3
scatter(pageSpeeds, purchaseAmount)
我在这里所做的只是制作了一个随机的、以 3 秒为中心的页面速度的正态分布,标准差为 1 秒。我将购买金额设为它的线性函数。因此,我将它设为 100 减去页面速度加上一些围绕它的正态随机分布,乘以 3。如果我们散点图,我们可以看到数据最终看起来是这样的:
你可以通过肉眼观察到确实存在线性关系,这是因为我们在源数据中硬编码了一个真正的线性关系。
现在让我们看看是否可以通过最小二乘法找出最佳拟合线。我们讨论了如何进行最小二乘法和线性回归,但你不必自己进行任何数学计算,因为 SciPy 包有一个stats
包,你可以导入:
from scipy import stats
slope, intercept, r_value, p_value, std_err =
stats.linregress(pageSpeeds, purchaseAmount)
你可以从scipy
中导入stats
,然后你可以在你的两个特征上调用stats.linregress()
。因此,我们有一个页面速度(pageSpeeds
)的列表和一个相应的购买金额(purchaseAmount
)的列表。linregress()
函数将给我们一堆东西,包括斜率、截距,这是我需要定义最佳拟合线的东西。它还给我们r_value
,从中我们可以得到 R 平方来衡量拟合的质量,以及一些我们稍后会讨论的东西。现在,我们只需要斜率、截距和r_value
,所以让我们继续运行这些。我们将从找到线性回归的最佳拟合开始:
r_value ** 2
你的输出应该是这样的:
现在我们得到的线的 R 平方值是 0.99,几乎是 1.0。这意味着我们有一个非常好的拟合,这并不太令人惊讶,因为我们确保这些数据之间存在真正的线性关系。即使在该线周围存在一些方差,我们的线也捕捉到了这些方差。我们在线的两侧大致有相同数量的方差,这是一件好事。这告诉我们我们确实有线性关系,我们的模型很适合我们的数据。
让我们画出那条线:
import matplotlib.pyplot as plt
def predict(x):
return slope * x + intercept
fitLine = predict(pageSpeeds)
plt.scatter(pageSpeeds, purchaseAmount)
plt.plot(pageSpeeds, fitLine, c='r')
plt.show()
以下是前面代码的输出:
这段代码将创建一个函数来绘制最佳拟合线与数据一起。这里有一些 Matplotlib 的魔法。我们将创建一个fitLine
列表,并使用我们编写的predict()
函数来获取pageSpeeds
(我们的x轴),并从中创建 Y 函数。因此,我们不是使用花费金额的观察值,而是使用linregress()
调用返回的斜率
乘以x
加上截距
。基本上在这里,我们将做一个散点图,就像我们以前做的那样,来显示原始数据点,即观察值。
然后我们还将在同一个pyplot
实例上调用plot
,使用我们得到的线方程创建的fitLine
,并将它们一起显示出来。当我们这样做时,图表看起来像下面这样:
你可以看到我们的直线实际上非常适合我们的数据!它正好位于中间,你只需要使用这个预测函数来预测新值。给定一个新的之前未见过的页面速度,我们可以使用斜率乘以页面速度加上截距来预测花费的金额。就是这么简单,我觉得很棒!
线性回归的活动
现在是时候动手了。尝试增加测试数据中的随机变化,并查看是否会产生影响。记住,R 平方是拟合程度的一个度量,我们捕捉了多少方差,所以方差的数量,嗯…你看看它是否真的有影响。
这就是线性回归,一个非常简单的概念。我们所做的就是将一条直线拟合到一组观察结果,然后我们可以使用这条直线来预测新值。就是这么简单。但是为什么要限制自己只使用一条直线呢?我们可以做其他更复杂的回归。我们接下来会探讨这些。
多项式回归
我们已经讨论了线性回归,其中我们将一条直线拟合到一组观察结果。多项式回归是我们接下来要讨论的话题,它使用更高阶的多项式来拟合你的数据。有时候你的数据可能并不适合一条直线。这就是多项式回归的用武之地。
多项式回归是回归的更一般情况。那么为什么要限制自己只使用一条直线呢?也许你的数据实际上并没有线性关系,或者可能有某种曲线关系,对吧?这种情况经常发生。
并非所有的关系都是线性的,但线性回归只是我们可以做的整个回归类别中的一个例子。如果你还记得我们最终得到的线性回归线的形式是y = mx + b,其中 m 和 b 是我们从普通最小二乘法线性回归分析中得到的值,或者你选择的任何方法。现在这只是一个一次多项式。阶数或度数就是你看到的 x 的幂。所以这是一个一次多项式。
现在如果我们想的话,我们也可以使用二次多项式,它看起来像y = ax² + bx + c。如果我们使用二次多项式进行回归,我们会得到 a、b 和 c 的值。或者我们可以使用三次多项式,它的形式是ax³ + bx² + cx + d。阶数越高,你可以表示的曲线就越复杂。所以,你将 x 的更多次幂混合在一起,你就可以得到更复杂的形状和关系。
但并不是阶数越高越好。通常你的数据中有一些自然关系并不是那么复杂,如果你发现自己在拟合数据时使用了非常大的阶数,你可能是在过度拟合!
注意过度拟合!
-
不要使用比你需要的更多的度数
-
首先可视化你的数据,看看可能存在多复杂的曲线
-
可视化拟合并检查你的曲线是否在努力适应异常值
-
高 R 平方仅意味着你的曲线很好地拟合了训练数据;它可能是一个好的预测器,也可能不是
如果你的数据有点乱七八糟,方差很大,你可以疯狂地创建一条上下波动的直线,试图尽可能地拟合数据,但实际上这并不代表数据的内在关系。它不能很好地预测新值。
所以,始终从可视化你的数据开始,考虑曲线实际上需要多复杂。现在你可以使用 R 平方来衡量你的拟合有多好,但要记住,这只是衡量这条曲线有多好地拟合了你的训练数据——也就是说,你用来实际进行预测的数据。它并不衡量你准确预测未来的能力。
稍后,我们将讨论一些防止过拟合的技术,称为训练/测试,但现在你只需要用眼睛来确保你没有过拟合,并且不要给函数添加比你需要的更多的度数。当我们探索一个例子时,这将更有意义,所以让我们接着做。
使用 NumPy 实现多项式回归
幸运的是,NumPy 有一个polyfit
函数,可以让你轻松地玩弄这个并尝试不同的结果,所以让我们去看看。多项式回归的乐趣时刻到了。顺便说一下,我真的觉得这很有趣。实际上看到所有那些高中数学实际上应用到一些实际的场景中,这有点酷。打开PolynomialRegression.ipynb
,让我们玩得开心一点。
让我们在页面速度和我们的购买金额虚假数据之间创建一个新的关系,这一次我们将创建一个不是线性的更复杂的关系。我们将把页面速度作为购买金额的除法函数的一部分:
%matplotlib inline
from pylab import *
np.random.seed(2)
pageSpeeds = np.random.normal(3.0, 1.0, 1000)
purchaseAmount = np.random.normal(50.0, 10.0, 1000) / pageSpeeds
scatter(pageSpeeds, purchaseAmount)
如果我们做一个散点图,我们得到以下结果:
顺便说一下,如果你想知道np.random.seed
这一行是做什么的,它创建一个随机种子值,这意味着当我们进行后续的随机操作时,它们将是确定性的。通过这样做,我们可以确保每次运行这段代码时,我们都得到完全相同的结果。这将在以后变得重要,因为我将建议你回来实际尝试不同的拟合来比较你得到的拟合。所以,重要的是你从相同的初始点开始。
你可以看到这并不是一个线性关系。我们可以尝试对其进行拟合,对于大部分数据来说可能还可以,也许在图表右侧的下方,但在左侧就不太行了。我们实际上更多的是一个指数曲线。
现在碰巧 NumPy 有一个polyfit()
函数,允许你对这些数据进行任意次数的多项式拟合。所以,例如,我们可以说我们的x轴是我们拥有的页面速度(pageSpeeds
)的数组,我们的y轴是我们拥有的购买金额(purchaseAmount
)的数组。然后我们只需要调用np.polyfit(x, y, 4)
,意思是我们想要一个四次多项式拟合这些数据。
x = np.array(pageSpeeds)
y = np.array(purchaseAmount)
p4 = np.poly1d(np.polyfit(x, y, 4))
让我们继续运行。它运行得相当快,然后我们可以绘制出来。所以,我们将在这里创建一个小图表,绘制我们原始点与预测点的散点图。
import matplotlib.pyplot as plt
xp = np.linspace(0, 7, 100)
plt.scatter(x, y)
plt.plot(xp, p4(xp), c='r')
plt.show()
输出看起来像下面的图表:
目前看起来是一个相当好的拟合。不过你要问自己的是,“我是不是过度拟合了?我的曲线看起来是不是真的在努力适应异常值?”我发现实际上并没有发生这种情况。我并没有看到太多疯狂的事情发生。
如果我有一个非常高阶的多项式,它可能会在顶部上升以捕捉那个异常值,然后向下下降以捕捉那里的异常值,并且在我们有很多密度的地方会变得更加稳定,也许它最终可能会到处尝试适应最后一组异常值。如果你看到这种无稽之谈,你就知道你的多项式阶数太多了,你应该把它降下来,因为虽然它适合你观察到的数据,但对于预测你没有看到的数据是没有用的。
想象一下,我有一条曲线,它向上飞起,然后又回到原点以适应异常值。我对中间的某些值的预测不会准确。曲线实际上应该在中间。在本书的后面,我们将讨论检测这种过拟合的主要方法,但现在,请只是观察它,并知道我们以后会更深入地讨论。
计算 r 平方误差
现在我们可以测量 r 平方误差。通过在sklearn.metrics
中的r2_score()
函数中取y
和预测值(p4(x)
),我们可以计算出来。
from sklearn.metrics import r2_score
r2 = r2_score(y, p4(x))
print r2
输出如下:
我们的代码将一组观察结果与一组预测进行比较,并为你计算 r 平方,只需一行代码!我们的 r 平方结果为 0.829,这还不错。记住,零是不好的,一是好的。0.82 接近一,不完美,直观上是有道理的。你可以看到我们的线在数据的中间部分非常好,但在极端左侧和极端右侧并不那么好。所以,0.82 听起来是合理的。
多项式回归的活动
我建议你深入研究这些东西。尝试不同阶数的多项式。回到我们运行polyfit()
函数的地方,尝试除了 4 之外的不同值。你可以使用 1,那就会回到线性回归,或者你可以尝试一些非常高的值,比如 8,也许你会开始看到过拟合。看看它的影响。你会想要改变它。例如,让我们来看一个三次多项式。
x = np.array(pageSpeeds)
y = np.array(purchaseAmount)
p4 = np.poly1d(np.polyfit(x, y, 3))
只需不断运行每一步,你就可以看到它的影响…
我们的三次多项式显然不如四次多项式拟合得好。如果你实际测量 r 平方误差,定量上会更糟,但如果我太高,你可能会开始看到过拟合。所以,只是玩一下,尝试不同的值,了解不同阶数的多项式对回归的影响。去动手尝试学习一些东西。
这就是多项式回归。再次强调,你需要确保你不会给问题增加比你需要的更多的度数。使用恰到好处的数量来找到看起来符合你的数据的直观拟合。太多可能导致过拟合,而太少可能导致拟合不足…所以你现在可以同时使用你的眼睛和 r 平方指标来找出你的数据的正确度数。让我们继续。
多元回归和预测汽车价格
那么,如果我们试图预测基于多于一个其他属性的值会发生什么?假设人的身高不仅取决于他们的体重,还取决于他们的遗传或其他一些可能影响它的因素。那么,多元分析就派上用场了。你实际上可以构建同时考虑多个因素的回归模型。用 Python 做起来实际上非常容易。
让我们谈谈多元回归,这有点复杂。多元回归的想法是:如果有多个因素影响你要预测的事物会怎么样?
在我们之前的例子中,我们看了线性回归。例如,我们讨论了基于体重预测人的身高。我们假设体重是影响身高的唯一因素,但也许还有其他因素。我们还研究了页面速度对购买金额的影响。也许影响购买金额的因素不仅仅是页面速度,我们想要找出这些不同因素如何结合在一起影响价值。这就是多元回归的作用。
我们现在要看的示例是这样的。假设您试图预测汽车的售价。它可能基于该汽车的许多不同特征,例如车身风格、品牌、里程数;谁知道,甚至还取决于轮胎的好坏。其中一些特征对于预测汽车价格更为重要,但您希望一次考虑所有这些特征。
因此,我们在这里前进的方式仍然是使用最小二乘法来拟合模型到您的一组观察结果。不同之处在于,我们将为您拥有的每个不同特征有一堆系数。
因此,例如,我们最终得到的价格模型可能是 alpha 的线性关系,一些常数,有点像您的 y 截距,再加上里程的一些系数,再加上年龄的一些系数,再加上它有多少个门的一些系数:
一旦您得到了那些最小二乘分析的系数,我们可以利用这些信息来弄清楚,每个特征对我的模型有多重要。因此,如果我得到了某些东西的系数非常小,比如车门数量,那就意味着车门数量并不重要,也许我应该完全将其从我的模型中移除,以使其更简单。
这是我在这本书中应该更经常说的一件事。在数据科学中,您总是希望做最简单有效的事情。不要过于复杂化事情,因为通常简单的模型效果最好。如果您能找到恰到好处的复杂度,但不要过多,那通常就是正确的模型。无论如何,这些系数给了您一种实际的方式,“嘿,有些因素比其他因素更重要。也许我可以丢弃其中一些因素。”
现在我们仍然可以使用 r-squared 来衡量多元回归的拟合质量。它的工作方式相同,尽管在进行多元回归时,您需要假设因素本身不相互依赖…而这并不总是正确的。因此,有时您需要将这个小小的警告放在脑后。例如,在这个模型中,我们将假设汽车的里程和年龄不相关;但实际上,它们可能非常紧密相关!这是这种技术的局限性,它可能根本没有捕捉到某种效应。
使用 Python 进行多元回归
幸运的是,Python 有一个名为statsmodel
的包,可以很容易地进行多元回归。让我们深入了解一下它的工作原理。让我们使用 Python 进行一些多元回归。我们将使用一些关于凯利蓝皮书中汽车价值的真实数据。
import pandas as pd
df = pd.read_excel('http://cdn.sundog-soft.com/Udemy/DataScience/cars.xls')
我们要在这里介绍一个名为pandas
的新包,它让我们非常容易地处理表格数据。它让我们能够轻松读取表格数据,并以不同的方式重新排列、修改、切片和切块它们。我们将在未来经常使用它。
我们将导入pandas
作为pd
,pd
有一个read_Excel()
函数,我们可以使用它来从 Web 通过 HTTP 读取 Microsoft Excel 电子表格。因此,pandas 有非常棒的功能。
我已经提前为您在我的域上托管了该文件,如果我们运行它,它将加载到我们称之为df
的DataFrame
对象中。现在我可以在这个DataFrame
上调用head()
,只显示它的前几行:
df.head()
以下是前面代码的输出:
实际数据集要大得多。这只是前几个样本。因此,这是关于里程、制造商、型号、修剪、类型、车门、巡航、音响和皮革的真实数据。
好的,现在我们要使用pandas
将其拆分为我们关心的特征。我们将创建一个模型,试图仅基于里程、型号和车门数量来预测价格,没有其他因素。
import statsmodels.api as sm
df['Model_ord'] = pd.Categorical(df.Model).codes
X = df[['Mileage', 'Model_ord', 'Doors']]
y = df[['Price']]
X1 = sm.add_constant(X)
est = sm.OLS(y, X1).fit()
est.summary()
现在我遇到的问题是,模型是一个文本,比如 Buick 的世纪,正如您所记得的,当我进行这种分析时,一切都需要是数字。在代码中,我使用pandas
中的Categorical()函数
来将DataFrame
中看到的模型名称转换为一组数字;也就是一组代码。我将说我的模型的 x 轴输入是里程(Mileage
),转换为序数值的模型(Model_ord
),和车门数量(Doors
)。我试图在 y 轴上预测的是价格(Price
)。
您可以看到这里的 R 平方值非常低。实际上,这不是一个很好的模型,但我们可以了解各种错误的一些见解,有趣的是,最低的标准误差与里程相关联。
现在我之前说过,系数是一种确定哪些项目重要的方法,但前提是您的输入数据已经标准化。也就是说,如果所有数据都在 0 到 1 的相同尺度上。如果不是,那么这些系数在一定程度上是在补偿它所看到的数据的尺度。如果您处理的不是标准化数据,就像在这种情况下一样,查看标准误差更有用。在这种情况下,我们可以看到里程实际上是这个特定模型的最大因素。我们早些时候能否已经想到这一点呢?嗯,我们只需稍微切片和切块就能发现车门数量实际上并不会对价格产生太大影响。让我们运行以下小行:
y.groupby(df.Doors).mean()
这里有一点pandas
的语法。在 Python 中只需一行代码就能做到这一点,这很酷!这将打印出一个新的DataFrame
,显示给定车门数量的平均价格:
接下来的两行代码只是创建了一个我称之为est
的模型,它使用普通最小二乘法(OLS),并使用我给它的列Mileage
,Model_ord
和Doors
进行拟合。然后我可以使用 summary 调用来打印出我的模型是什么样子的:
我可以看到平均两门车的售价实际上比平均四门车的售价更高。如果有的话,车门数量和价格之间存在负相关,这有点令人惊讶。不过,这是一个小数据集,所以我们当然不能从中得出太多意义。
多元回归的活动
作为一个活动,请随意修改假输入数据。您可以下载数据并在电子表格中进行修改。从本地硬盘读取数据,而不是从 HTTP 读取,看看您可以有什么样的不同。也许您可以制作一个行为不同且拥有更好适合的模型的数据集。也许您可以更明智地选择特征来构建您的模型。所以,请随意尝试一下,然后我们继续。
这就是多元分析和其运行示例。和我们探索的多元分析概念一样重要的是我们在 Python 笔记本中所做的一些事情。所以,您可能需要回到那里,确切地研究发生了什么。
我们介绍了 pandas 和处理 pandas 和 DataFrame 对象的方法。pandas 是一个非常强大的工具。我们将在未来的章节中更多地使用它,但请确保您开始注意这些事情,因为这些将是您在 Python 技能中处理大量数据和组织数据的重要技术。
多级模型
现在谈论多层次模型是有意义的。这绝对是一个高级话题,我不会在这里详细讨论。我现在的目标是向你介绍多层次模型的概念,并让你了解一些挑战以及在组合它们时如何思考。就是这样。
多层次模型的概念是,一些影响发生在层次结构中的各个层次。例如,你的健康。你的健康可能取决于你的个体细胞的健康程度,而这些细胞可能取决于它们所在的器官的健康程度,而你的器官的健康可能取决于你整体的健康。你的健康可能部分取决于你家庭的健康和你家庭给予你的环境。而你家庭的健康反过来可能取决于你所在城市的一些因素,比如犯罪率、压力和污染程度。甚至超越这些,它可能取决于我们所生活的整个世界的因素。也许世界上医疗技术的状况是一个因素,对吧?
另一个例子:你的财富。你赚多少钱?这取决于你个人的努力,但也取决于你父母的努力,他们能够为你的教育投入多少钱以及你成长的环境,反过来,你的祖父母呢?他们能够创造什么样的环境,能够为你的父母提供什么样的教育,进而影响他们为你的教育和成长提供的资源。
这些都是多层次模型的例子,其中存在一个影响彼此的层次结构。现在多层次模型的挑战是要尝试弄清楚,“我该如何建模这些相互依赖关系?我该如何建模所有这些不同的影响以及它们如何相互影响?”
这里的挑战是识别每个层次中实际影响你试图预测的事物的因素。例如,如果我试图预测整体 SAT 成绩,我知道这部分取决于参加考试的个体孩子,但是孩子的哪些方面很重要呢?可能是基因,可能是他们的个体健康,他们的个体大脑大小。你可以想到任何可能影响个体的因素,可能会影响他们的 SAT 成绩。然后,如果你再往上看,看看他们的家庭环境,看看他们的家庭。家庭的哪些方面可能会影响他们的 SAT 成绩?他们能提供多少教育?父母是否能够辅导孩子学习 SAT 考试中的主题?这些都是第二层次的重要因素。那么他们的社区呢?社区的犯罪率可能很重要。他们为青少年提供的设施以及让他们远离街头的措施等等。
关键是你想要继续关注这些更高的层次,但在每个层次上识别影响你试图预测的事物的因素。我可以继续上升到学校老师的素质、学区的资金、州级的教育政策。你可以看到不同层次的不同因素都会影响你试图预测的事物,而其中一些因素可能存在于多个层次。例如,犯罪率存在于地方和州级。在进行多层次建模时,你需要弄清楚它们如何相互作用。
正如你可以想象的那样,这很快变得非常困难和复杂。这确实远远超出了本书的范围,也超出了任何数据科学入门书籍的范围。这是困难的东西。有整整厚厚的书籍讨论它,你可以写一本完整的书籍,这将是一个非常高级的话题。
那么为什么我要提到多层模型呢?因为我在一些工作描述中看到它被提到,在一些情况下,作为他们希望你了解的内容。我在实践中从未使用过它,但我认为在数据科学职业中重要的是,你至少要熟悉这个概念,知道它的含义以及创建多层模型所涉及的一些挑战。我希望我已经向你介绍了这些概念。有了这些,我们可以继续下一节了。
这就是多层模型的概念。这是一个非常高级的话题,但你至少需要了解这个概念,而这个概念本身是相当简单的。当你试图做出预测时,你只是在不同层次、不同层次之间寻找影响。所以也许有不同层次的影响相互影响,而这些不同层次可能有相互关联的因素。多层建模试图考虑所有这些不同的层次和因素以及它们如何相互作用。放心,这就是你现在需要知道的全部。
总结
在本章中,我们谈到了回归分析,即试图将曲线拟合到一组训练数据,然后使用它来预测新值。我们看到了它的不同形式。我们看了线性回归的概念及其在 Python 中的实现。
我们学习了多项式回归是什么,也就是使用更高次的多项式来为多维数据创建更好、更复杂的曲线。我们还看到了它在 Python 中的实现。
然后我们谈到了多元回归,这是一个稍微复杂一点的概念。我们看到了当有多个因素影响我们要预测的数据时,多元回归是如何使用的。我们看了一个有趣的例子,使用 Python 和一个非常强大的工具 pandas 来预测汽车的价格。
最后,我们看了多层模型的概念。我们了解了一些挑战,以及在将它们组合在一起时如何考虑它们。在下一章中,我们将学习一些使用 Python 的机器学习技术。
第五章:使用 Python 进行机器学习
在本章中,我们将介绍机器学习以及如何在 Python 中实际实现机器学习模型。
我们将研究监督学习和无监督学习的含义,以及它们之间的区别。我们将看到防止过拟合的技术,然后看一个有趣的示例,我们在其中实现了一个垃圾邮件分类器。我们将分析 K 均值聚类是什么,并使用 scikit-learn 对基于收入和年龄的人群进行聚类的工作示例!
我们还将介绍一种非常有趣的机器学习应用,称为决策树,并且我们将在 Python 中构建一个工作示例,用于预测公司的招聘决策。最后,我们将深入探讨集成学习和 SVM 的迷人概念,这些是我最喜欢的机器学习领域之一!
更具体地说,我们将涵盖以下主题:
-
监督和无监督学习
-
通过使用训练/测试来避免过拟合
-
贝叶斯方法
-
使用朴素贝叶斯实现电子邮件垃圾邮件分类器
-
K 均值聚类的概念
-
Python 中聚类的示例
-
熵及其测量方法
-
决策树的概念及其在 Python 中的示例
-
什么是集成学习
-
支持向量机(SVM)及其在 scikit-learn 中的示例
机器学习和训练/测试
那么什么是机器学习?如果您在维基百科或其他地方查找,它会说这是一种可以从观测数据中学习并基于此进行预测的算法。听起来很花哨,对吧?就像人工智能一样,就像您的计算机内部有一个跳动的大脑。但实际上,这些技术通常非常简单。
我们已经看过回归,我们从中获取了一组观测数据,我们对其进行了拟合,并使用该线进行预测。所以按照我们的新定义,那就是机器学习!您的大脑也是这样工作的。
机器学习中的另一个基本概念是称为训练/测试的东西,它让我们非常聪明地评估我们制作的机器学习模型有多好。当我们现在看无监督和监督学习时,您将看到为什么训练/测试对机器学习如此重要。
无监督学习
现在让我们详细讨论两种不同类型的机器学习:监督学习和无监督学习。有时两者之间可能存在一种模糊的界限,但无监督学习的基本定义是,您不会给模型任何答案来学习。您只是向其提供一组数据,您的机器学习算法会尝试在没有额外信息的情况下理解它:
假设我给它一堆不同的对象,比如这些球和立方体以及一些骰子之类的东西。然后让我们假设有一些算法,它将根据某种相似性度量将这些对象聚类成相互相似的东西。
现在我没有提前告诉机器学习算法,某些对象属于哪些类别。我没有一个可以从中学习的作弊表,其中有一组现有对象和我对其正确分类的信息。机器学习算法必须自行推断这些类别。这是无监督学习的一个例子,我没有一组答案可以让它学习。我只是试图让算法根据所呈现给它的数据自行收集答案。
问题在于我们不一定知道算法会得出什么结果!如果我给它前面图像中显示的一堆物体,它会将物品分成圆形的,大的与小的,红色的与蓝色的吗,我不知道。这将取决于我为物品之间的相似性给出的度量标准。但有时您会发现令人惊讶的聚类,并且会出现您没有预料到的结果。
所以这确实是无监督学习的重点:如果你不知道你在寻找什么,它可以成为一个强大的工具,用来发现你甚至不知道存在的分类。我们称之为潜在变量。你最初甚至不知道的数据属性,可以通过无监督学习来挖掘出来。
让我们来看一个无监督学习的例子。假设我是在对人群进行聚类而不是对球和骰子进行聚类。我正在写一个约会网站,我想看看哪些类型的人倾向于聚集在一起。人们倾向于围绕一些属性进行聚类,这些属性决定了他们是否倾向于彼此喜欢和约会。现在你可能会发现出现的聚类并不符合你的先入为主的刻板印象。也许这与大学生与中年人、离婚者等无关,或者他们的宗教信仰。也许如果你看看实际从分析中出现的聚类,你会对你的用户学到一些新东西,并且真正发现有些东西比你的人群的任何现有特征更重要,以决定他们是否喜欢彼此。这就是监督学习提供有用结果的一个例子。
另一个例子可能是根据电影的属性对电影进行聚类。如果你对像 IMDb 这样的一组电影运行聚类,也许结果会让你惊讶。也许这不仅仅是关于电影的类型。也许还有其他属性,比如电影的年龄或播放时长或上映国家,更重要。你永远不知道。或者我们可以分析产品描述的文本,尝试找出对某个类别具有最重要意义的术语。同样,我们可能并不一定知道哪些术语或词语最能表明产品属于某个类别;但通过无监督学习,我们可以挖掘出这些潜在信息。
监督学习
相比之下,监督学习是一种模型可以从一组答案中学习的情况。我们给它一组训练数据,模型从中学习。然后它可以推断我们想要的特征和类别之间的关系,并将其应用于未见过的新值,并预测有关它们的信息。
回到我们之前的例子,我们试图根据这些汽车的属性来预测汽车价格。这是一个我们使用实际答案来训练模型的例子。所以我有一组已知的汽车及其实际售价。我在这组完整答案上训练模型,然后我可以创建一个能够预测我以前没有见过的新车价格的模型。这是一个监督学习的例子,你给它一组答案来学习。你已经给一组数据分配了类别或一些组织标准,然后你的算法使用这些标准来建立一个模型,从中可以预测新的数值。
评估监督学习
那么如何评估监督学习呢?监督学习的美妙之处在于我们可以使用一个称为训练/测试的技巧。这里的想法是将我希望我的模型学习的观察数据分成两组,一个训练集和一个测试集。所以当我基于我拥有的数据来训练/构建我的模型时,我只使用我称之为训练集的部分数据,然后我保留另一部分数据,我将用于测试目的。
我可以使用我的数据子集来构建我的模型作为训练数据,然后我可以评估从中得出的模型,并看看它是否能成功地预测我的测试数据的正确答案。
所以你看到我在这里做了什么?我有一组数据,我已经有了可以训练模型的答案,但我将保留一部分数据,实际上用它来测试使用训练集生成的模型!这让我有一个非常具体的方法来测试我的模型在未知数据上的表现,因为我实际上有一些数据被我留出来可以用来测试。
然后你可以定量地测量它的表现如何,使用 R 平方或其他指标,比如均方根误差。你可以用它来测试一个模型与另一个模型,看看对于给定问题哪个是最好的模型。你可以调整该模型的参数,并使用训练/测试来最大化该模型在测试数据上的准确性。这是防止过拟合的一个很好的方法。
有一些监督学习的注意事项。需要确保你的训练和测试数据集足够大,能够真正代表你的数据。你还需要确保你在训练和测试中捕捉到所有你关心的不同类别和异常值,以便对其成功进行良好的衡量和建立一个好的模型。
你必须确保你从这些数据集中随机选择,并且不是只将你的数据集分成两部分,说这里左边的是训练,右边的是测试。你想要随机抽样,因为你的数据中可能有一些你不知道的顺序模式。
现在,如果你的模型过拟合,并且只是竭尽全力接受训练数据中的异常值,那么当你将其放在未见过的测试数据中时,这将会显露出来。这是因为所有为了异常值而进行的旋转对于它以前没有见过的异常值是没有帮助的。
让我们清楚地说一下,训练/测试并不完美,有可能会得到误导性的结果。也许你的样本量太小,就像我们已经讨论过的那样,或者可能仅仅由于随机机会,你的训练数据和测试数据看起来非常相似,它们实际上确实有一组相似的异常值-你仍然可能会过拟合。正如你在下面的例子中所看到的,这确实可能发生:
K-fold 交叉验证
现在有一种解决这个问题的方法,叫做 k-fold 交叉验证,我们将在本书的后面看一个例子,但基本概念是你要多次进行训练/测试。所以你实际上不是将数据分成一个训练集和一个测试集,而是分成多个随机分配的段,k 个段。这就是 k 的含义。然后你保留其中一个段作为测试数据,然后开始在剩余的段上训练模型,并测量它们对测试数据集的表现。然后你取每个训练集模型结果的平均表现,并取它们的 R 平方平均分数。
这样,你实际上是在不同的数据片段上进行训练,将它们与相同的测试集进行比较,如果你的模型对训练数据的特定部分过拟合,那么它将被 k-fold 交叉验证中其他部分的平均值抵消。
以下是 K-fold 交叉验证的步骤:
-
将数据分成 K 个随机分配的段
-
将一个段保留为测试数据
-
在剩余的 K-1 个段上进行训练,并测量它们对测试集的表现
-
计算 K-1 个 R 平方分数的平均值
这在本书的后面会更有意义,现在我只是想让你知道这个工具实际上可以使训练/测试比它已经是更加健壮。所以让我们去实际玩一些数据,并使用训练/测试来评估它。
使用训练/测试来防止多项式回归的过拟合
让我们把训练/测试付诸实践。你可能记得回归可以被看作是一种监督式机器学习。让我们尝试一下多项式回归,我们之前介绍过,使用训练/测试来尝试找到适合给定数据集的正确次数的多项式。
就像我们之前的例子一样,我们将建立一个小的虚拟数据集,其中包括随机生成的页面速度和购买金额,我将在它们之间创建一个怪异的指数关系。
%matplotlib inline
import numpy as np
from pylab import *
np.random.seed(2)
pageSpeeds = np.random.normal(3.0, 1.0, 100)
purchaseAmount = np.random.normal(50.0, 30.0, 100) / pageSpeeds
scatter(pageSpeeds, purchaseAmount)
让我们继续生成这些数据。我们将使用页面速度和购买金额的正态分布的随机数据,使用如下截图中所示的关系:
接下来,我们将拆分数据。我们将取 80%的数据,保留给训练数据。所以这些点中只有 80%会用于训练模型,然后我们将保留另外的 20%用于测试模型对未知数据的预测。
我们将使用 Python 的语法来拆分列表。前 80 个点将用于训练集,最后的 20 个点将用于测试集。你可能还记得我们在之前的 Python 基础章节中介绍过这个语法,我们将在购买金额中也做同样的事情:
trainX = pageSpeeds[:80]
testX = pageSpeeds[80:]
trainY = purchaseAmount[:80]
testY = purchaseAmount[80:]
在我们之前的章节中,我说过你不应该像这样简单地将数据集分成两部分,而是应该随机抽样进行训练和测试。但在这种情况下,它是有效的,因为我的原始数据本来就是随机生成的,所以没有任何规律可循。但在现实世界的数据中,你会希望在拆分之前对数据进行洗牌。
现在我们将看一下一个方便的方法,你可以用它来洗牌你的数据。另外,如果你使用 pandas 包,那里有一些方便的函数可以自动为你创建训练和测试数据集。但我们将在这里使用 Python 列表来做。所以让我们来可视化我们最终得到的训练数据集。我们将绘制我们的训练页面速度和购买金额的散点图。
scatter(trainX, trainY)
这是你的输出现在应该看起来的样子:
基本上,从原始完整数据集中随机选择的 80 个点已经被绘制出来。它基本上具有相同的形状,这是一个好事。它代表了我们的数据。这很重要!
现在让我们绘制剩下的 20 个点,作为测试数据。
scatter(testX, testY)
在这里,我们看到我们剩下的 20 个测试点也与我们原始数据的形状相同。所以我认为这也是一个代表性的测试集。当然,它比你在现实世界中想看到的要小一点。例如,如果你有 1000 个点而不是 100 个点可以选择,并且保留 200 个点而不是 20 个点,你可能会得到更好的结果。
现在我们将尝试对这些数据拟合一个 8 次多项式,我们只是随机选择了数字8
,因为我知道这是一个非常高的阶数,可能会导致过拟合。
让我们继续使用np.poly1d(np.polyfit(x, y, 8)
来拟合我们的 8 次多项式,其中x是仅包含训练数据的数组,y也是仅包含训练数据的数组。我们只使用了那 80 个保留用于训练的点来找到我们的模型。现在我们有了这个p4
函数,可以用它来预测新的值:
x = np.array(trainX)
y = np.array(trainY)
p4 = np.poly1d(np.polyfit(x, y, 8))
现在我们将绘制这个多项式与训练数据的关系。我们可以散点绘制我们的原始训练数据,然后我们可以绘制我们的预测值:
import matplotlib.pyplot as plt
xp = np.linspace(0, 7, 100)
axes = plt.axes()
axes.set_xlim([0,7])
axes.set_ylim([0, 200])
plt.scatter(x, y)
plt.plot(xp, p4(xp), c='r')
plt.show()
你可以在下图中看到,它看起来非常匹配,但你知道显然它有一些过拟合:
右边的这种疯狂是什么?我非常确定,如果我们真的有真实数据,它不会像这个函数所暗示的那样疯狂高。所以这是一个很好的过拟合数据的例子。它非常适合你提供的数据,但是在图表右侧疯狂高的点之后,它会对预测新值做出糟糕的预测。所以让我们试着揭示这一点。让我们给它我们的测试数据集:
testx = np.array(testX)
testy = np.array(testY)
axes = plt.axes()
axes.set_xlim([0,7])
axes.set_ylim([0, 200])
plt.scatter(testx, testy)
plt.plot(xp, p4(xp), c='r')
plt.show()
实际上,如果我们将我们的测试数据绘制到同样的函数上,嗯,它看起来并不那么糟糕。
我们很幸运,我们的测试数据实际上并不在这里开始,但你可以看到这是一个合理的拟合,但远非完美。事实上,如果你实际测量 R 平方分数,它比你想象的要糟糕。我们可以使用sklearn.metrics
中的r2_score()
函数来测量。我们只需给它我们的原始数据和我们预测的值,它就会测量所有预测值的方差并为你平方:
from sklearn.metrics import r2_score
r2 = r2_score(testy, p4(testx))
print r2
我们最终得到的 R 平方分数只有0.3
。所以并不是很高!你可以看到它更适合训练数据:
from sklearn.metrics import r2_score
r2 = r2_score(np.array(trainY), p4(np.array(trainX)))
print r2
R 平方值结果为0.6
,这并不令人意外,因为它是在训练数据上训练的。测试数据是它的未知,它的测试,而它确实没有通过测试。30%,这是 F!
这是一个例子,我们使用训练/测试来评估监督学习算法,就像我之前说的那样,pandas 有一些方法可以使这变得更容易。我们稍后会看一下,我们还将在本书的后面看到更多关于训练/测试的例子,包括 k 折交叉验证。
活动
你可能能猜到你的作业是什么。所以我们知道 8 次多项式并不是很有用。你能做得更好吗?所以我希望你回到我们的例子中,使用不同的多项式阶数来拟合。将 8 更改为不同的值,看看你能否找出使用训练/测试作为度量标准的最佳多项式阶数。你的测试数据在哪里得到最好的 R 平方分数?哪个阶数更适合?去尝试一下。这应该是一个相当简单的练习,对你来说也是一个非常有启发性的练习。
所以这就是训练/测试的实际应用,这是一个非常重要的技术,你将一遍又一遍地使用它,以确保你的结果与你拥有的模型非常匹配,并且你的结果对未知值有很好的预测能力。这是在进行建模时防止过拟合的好方法。
贝叶斯方法-概念
你是否曾经想过你的电子邮件中的垃圾邮件分类器是如何工作的?它是如何知道一封电子邮件可能是垃圾邮件还是不是?嗯,一个流行的技术是一种称为朴素贝叶斯的技术,这是贝叶斯方法的一个例子。让我们更多地了解它是如何工作的。让我们讨论贝叶斯方法。
我们在本书的早些时候讨论了贝叶斯定理,讨论了像药物测试这样的事情在结果上可能非常误导。但你实际上可以将相同的贝叶斯定理应用到更大的问题,比如垃圾邮件分类器。所以让我们深入了解一下它可能是如何工作的,这就是所谓的贝叶斯方法。
所以对贝叶斯定理的一个复习-记住,给定 B 的 A 的概率等于 A 的整体概率乘以给定 A 的 B 的概率除以 B 的整体概率:
我们如何在机器学习中使用它?我实际上可以为此构建一个垃圾邮件分类器:一个可以分析一组已知的垃圾邮件和一组已知的非垃圾邮件,并训练一个模型来预测新邮件是否为垃圾邮件的算法。这是实际世界中实际使用的垃圾邮件分类器的真正技术。
作为一个例子,让我们来计算一下包含单词“free”的电子邮件被认为是垃圾邮件的概率。如果有人向你承诺免费的东西,那很可能是垃圾邮件!所以让我们来计算一下。在电子邮件中包含单词“free”时,电子邮件被认为是垃圾邮件的概率等于它是垃圾邮件的总体概率乘以包含单词“free”的概率,假设它是垃圾邮件,除以总体概率是免费的概率:
分子可以被认为是消息是“垃圾邮件”并包含单词“免费”的概率。但这与我们要寻找的有点不同,因为这是完整数据集中的几率,而不仅仅是包含单词“免费”的几率。分母只是包含单词“免费”的总体概率。有时,这可能不会立即从您拥有的数据中获得。如果没有,如果需要推导出来,可以将其扩展为以下表达式:
这给出了包含单词“free”的电子邮件中是垃圾邮件的百分比,这在您试图确定它是否是垃圾邮件时是一个有用的信息。
但是英语中的所有其他单词呢?所以我们的垃圾邮件分类器应该知道的不仅仅是单词“free”。理想情况下,它应该自动选择消息中的每个单词,并弄清楚每个单词对特定电子邮件被认为是垃圾邮件的可能性有多大的贡献。所以我们可以在训练时对我们遇到的每个单词进行训练,丢弃像“a”、“the”和“and”这样的东西以及无意义的单词。然后当我们浏览新电子邮件中的所有单词时,我们可以将每个单词的垃圾邮件概率相乘在一起,然后得到该电子邮件是垃圾邮件的总体概率。
现在它被称为朴素贝叶斯是有原因的。它是朴素的,因为我们假设单词之间没有关系。我们只是独立地查看消息中的每个单词,并基本上结合每个单词对其是否是垃圾邮件的概率。我们不考虑单词之间的关系。因此,更好的垃圾邮件分类器会这样做,但显然这要困难得多。
听起来好像是很多工作。但总体想法并不难,而且 Python 中的 scikit-learn 使得实际上很容易做到。它提供了一个名为 CountVectorizer 的功能,可以非常简单地将电子邮件拆分为其所有组成单词,并逐个处理这些单词。然后它有一个 MultinomialNB 函数,其中 NB 代表朴素贝叶斯,它将为我们完成所有朴素贝叶斯的繁重工作。
使用朴素贝叶斯实现垃圾邮件分类器
让我们使用朴素贝叶斯编写一个垃圾邮件分类器。你会惊讶地发现这是多么容易。实际上,大部分工作最终都是读取我们将要训练的所有输入数据,并实际解析这些数据。实际的垃圾邮件分类部分,机器学习部分,本身只是几行代码。所以通常情况下是这样的:当你在做数据科学时,读取和整理数据通常是大部分工作,所以要习惯这个想法!
import os
import io
import numpy
from pandas import DataFrame
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
def readFiles(path):
for root, dirnames, filenames in os.walk(path):
for filename in filenames:
path = os.path.join(root, filename)
inBody = False
lines = []
f = io.open(path, 'r', encoding='latin1')
for line in f:
if inBody:
lines.append(line)
elif line == '\n':
inBody = True
f.close()
message = '\n'.join(lines)
yield path, message
def dataFrameFromDirectory(path, classification):
rows = []
index = []
for filename, message in readFiles(path):
rows.append({'message': message, 'class': classification})
index.append(filename)
return DataFrame(rows, index=index)
data = DataFrame({'message': [], 'class': []})
data = data.append(dataFrameFromDirectory(
'e:/sundog-consult/Udemy/DataScience/emails/spam',
'spam'))
data = data.append(dataFrameFromDirectory(
'e:/sundog-consult/Udemy/DataScience/emails/ham',
'ham'))
所以我们需要做的第一件事是以某种方式读取所有这些电子邮件,我们将再次使用 pandas 使这变得更容易一些。再次,pandas 是处理表格数据的有用工具。我们在这里的示例中导入了我们将在其中使用的所有不同包,包括 os 库、io 库、numpy、pandas,以及 scikit-learn 中的 CountVectorizer 和 MultinomialNB。
现在让我们详细地看一下这段代码。我们现在可以跳过readFiles()
和dataFrameFromDirectory()
函数的定义,然后继续到我们的代码实际上要做的第一件事,那就是创建一个 pandas DataFrame 对象。
我们将从一个最初包含消息的空列表和一个空类列表的字典中构建这个 DataFrame。这个语法是在说:“我想要一个 DataFrame,它有两列:一个包含消息,即每封电子邮件的实际文本;另一个包含每封电子邮件的类别,也就是它是垃圾邮件还是正常邮件”。所以它是在说我想要创建一个电子邮件的小数据库,这个数据库有两列:电子邮件的实际文本和它是否是垃圾邮件。
现在我们需要向数据库中添加一些内容,也就是说,向那个 DataFrame 中添加内容,使用 Python 语法。所以我们调用了append()
和dataFrameFromDirectory()
两个方法,实际上将来自我的spam
文件夹的所有垃圾邮件和来自ham
文件夹的所有正常邮件都放入了 DataFrame 中。
如果你在这里跟着做,请确保修改传递给dataFrameFromDirectory()
函数的路径,以匹配你在系统中安装书籍材料的位置!再次强调,如果你使用的是 Mac 或 Linux,请注意反斜杠和正斜杠等等。在这种情况下,这并不重要,但如果你不是在 Windows 上,你就不会有一个驱动器号。所以请确保这些路径实际上指向你的spam
和ham
文件夹,以便进行本示例。
接下来,dataFrameFromDirectory()
是我写的一个函数,基本上它说我有一个目录的路径,并且我知道它给定了分类,垃圾邮件或正常邮件,然后它使用了我也写的readFiles()
函数,它会遍历目录中的每一个文件。所以readFiles()
使用os.walk()
函数来查找目录中的所有文件。然后它会为该目录中的每个单独的文件构建完整的路径名,然后读取它。在读取时,它实际上会跳过每封电子邮件的标题,直接进入文本,它是通过查找第一个空行来实现的。
它知道第一个空行之后的所有内容实际上是消息正文,而在第一个空行之前的所有内容只是一堆我实际上不想让我的垃圾邮件分类器进行训练的头部信息。所以它会把每个文件的完整路径和消息正文都返回给我。这就是我们读取所有数据的方式,也是代码的大部分内容!
所以,最终我得到的是一个 DataFrame 对象,基本上是一个有两列的数据库,包含了消息正文,以及是否是垃圾邮件。我们可以继续运行,并且可以使用 DataFrame 的head
命令来预览一下它的样子:
data.head()
我们 DataFrame 中的前几个条目看起来是这样的:对于给定文件中的每个电子邮件的路径,我们有一个分类和消息正文:
好了,现在到了有趣的部分,我们将使用 scikit-learn 中的MultinomialNB()
函数来对我们的数据执行朴素贝叶斯。
vectorizer = CountVectorizer()
counts = vectorizer.fit_transform(data['message'].values)
classifier = MultinomialNB()
targets = data['class'].values
classifier.fit(counts, targets)
现在你的输出应该是这样的:
一旦我们构建了MultinomialNB
分类器,它需要两个输入。它需要我们正在训练的实际数据(counts
),以及每个数据的目标(targets
)。所以counts
基本上是每封电子邮件中所有单词的列表,以及该单词出现的次数。
所以CountVectorizer()
的作用是:它从 DataFrame 中取出message
列并获取其中的所有值。我将调用vectorizer.fit_transform
,它基本上是将我数据中出现的所有单词进行标记或转换为数字,为其赋予数值。然后它会计算每个单词出现的次数。
这是一种更紧凑的方式来表示每个单词在电子邮件中出现的次数。我不是保留单词本身,而是将这些单词表示为稀疏矩阵中的不同值,这基本上是说我将每个单词视为一个数字,作为一个数值索引,进入一个数组。它所做的是,用简单的英语说,将每个消息拆分成其中包含的单词列表,并计算每个单词出现的次数。所以我们称之为counts
。它基本上是每个单词在每个单独消息中出现的次数的信息。同时,targets
是我遇到的每封电子邮件的实际分类数据。所以我可以使用我的 MultinomialNB()函数调用 classifier.fit()来实际使用朴素贝叶斯创建一个模型,该模型将根据我们提供的信息预测新的电子邮件是否是垃圾邮件。
让我们继续运行。它运行得相当快!我将在这里使用几个例子。让我们尝试一个只说“现在免费赚钱!”的消息正文,这显然是垃圾邮件,还有一个更无辜的消息,只是说“嗨鲍勃,明天打一场高尔夫怎么样?”所以我们要传递这些消息。
examples = ['Free Money now!!!', "Hi Bob, how about a game of golf tomorrow?"]
example_counts = vectorizer.transform(examples)
predictions = classifier.predict(example_counts)
predictions
我们要做的第一件事是将消息转换为我训练模型的相同格式。所以我使用了创建模型时创建的相同的向量化器,将每条消息转换为一个单词和它们的频率的列表,其中单词由数组中的位置表示。一旦我完成了这个转换,我实际上可以在我的分类器上使用 predict()函数,对已经转换成单词列表的示例数组进行预测,看看我们得到了什么:
array(['spam', 'ham'], dtype='|S4')
当然,它有效!所以,给定这两个输入消息的数组,“现在免费赚钱!”和“嗨鲍勃”,它告诉我第一个结果是垃圾邮件,第二个结果是正常邮件,这正是我所期望的。这很酷。就是这样。
活动
我们这里有一个相当小的数据集,所以如果你愿意,你可以尝试通过一些不同的电子邮件,并查看是否会得到不同的结果。如果你真的想挑战自己,尝试将训练/测试应用到这个例子中。所以是否我的垃圾邮件分类器好不好的真正衡量标准不仅仅是它是否能直观地判断“现在免费赚钱!”是垃圾邮件。你想要定量地衡量它。
所以如果你想挑战一下,尝试将这些数据分成一个训练集和一个测试数据集。你实际上可以在网上查找如何使用 pandas 很容易地将数据分成训练集和测试集,或者你可以手动操作。无论哪种方式都可以。看看你是否能够将你的 MultinomialNB 分类器应用到一个测试数据集上,并衡量其性能。所以,如果你想要一点挑战,一点挑战,那就试试看吧。
这有多酷?我们只是用几行 Python 代码编写了自己的垃圾邮件分类器。使用 scikit-learn 和 Python 非常容易。这就是朴素贝叶斯的实际应用,现在你可以去分类一些垃圾邮件或正常邮件了。非常酷。接下来让我们谈谈聚类。
K-Means 聚类
接下来,我们将讨论 K 均值聚类,这是一种无监督学习技术,你有一堆东西想要分成各种不同的簇。也许是电影类型或人口统计学,谁知道呢?但这实际上是一个相当简单的想法,所以让我们看看它是如何工作的。
K-means 聚类是机器学习中非常常见的技术,您只需尝试获取一堆数据,并根据数据本身的属性找到有趣的集群。听起来很花哨,但实际上非常简单。在 k-means 聚类中,我们所做的就是尝试将我们的数据分成 K 组-这就是 K 的含义,它是您尝试将数据分成多少不同组的数量-它通过找到 K 个质心来实现这一点。
所以,基本上,给定数据点属于哪个组是由散点图中它最接近的质心点来定义的。您可以在以下图像中可视化这一点:
这是一个 K 为三的 k-means 聚类的示例,方块代表散点图中的数据点。圆圈代表 k-means 聚类算法得出的质心,并且每个点根据它最接近的质心被分配到一个集群中。所以,这就是全部内容,真的。这是无监督学习的一个例子。这不是一个情况,我们有一堆数据,我们已经知道给定一组训练数据的正确集群;相反,你只是给出了数据本身,它试图仅基于数据的属性自然地收敛到这些集群。这也是一个例子,您正在尝试找到甚至您自己都不知道存在的集群或分类。与大多数无监督学习技术一样,重点是找到潜在价值,直到算法向您展示它们之前,您并没有真正意识到它们的存在。
例如,百万富翁住在哪里?我不知道,也许有一些有趣的地理集群,富人倾向于居住在那里,k-means 聚类可以帮助您找出答案。也许我真的不知道今天的音乐流派是否有意义。现在成为另类是什么意思?不多,对吧?但是通过对歌曲属性进行 k-means 聚类,也许我可以找到相关的歌曲集群,并为这些集群代表的内容想出新的名称。或者我可以查看人口统计数据,也许现有的刻板印象已经不再有用。也许西班牙裔已经失去了意义,实际上有其他属性可以定义人群,例如,我可以通过聚类发现。听起来很花哨,不是吗?真的很复杂。具有 K 个集群的无监督机器学习,听起来很花哨,但与数据科学中的大多数技术一样,实际上是一个非常简单的想法。
以下是我们用简单英语的算法:
-
**随机选择 K 个质心(k-means):**我们从一组随机选择的质心开始。所以如果我们有三个 K,我们将在我们的组中寻找三个集群,并且我们将在我们的散点图中分配三个随机位置的质心。
-
**将每个数据点分配给最接近的质心点:**然后我们将每个数据点分配给它最接近的随机分配的质心点。
-
**根据每个质心点的平均位置重新计算质心:**然后重新计算我们得出的每个集群的质心。也就是说,对于我们最终得到的给定集群,我们将移动该质心以成为所有这些点的实际中心。
-
**迭代直到点停止改变分配到质心:**我们将一直重复这个过程,直到这些质心停止移动,我们达到了一些阈值值,表示我们已经收敛到了某些东西。
-
**预测新点的集群:**要预测我以前没有见过的新点的集群,我们只需通过我们的质心位置并找出它最接近的质心来预测其集群。
让我们看一个图形示例,以便更容易理解。我们将以下图像中的第一个图形称为 A,第二个称为 B,第三个称为 C,第四个称为 D。
图 A 中的灰色方块代表我们散点图中的数据点。坐标轴代表某些不同特征。也许是年龄和收入;这是我一直在使用的一个例子,但它可以是任何东西。灰色方块可能代表个体人员、个体歌曲或我想要找到它们之间关系的任何东西。
因此,我首先随机在我的散点图上选择了三个点。可以是任何地方。总得从某个地方开始,对吧?我选择的三个点(质心)在图 A 中被表示为圆圈。接下来,我要做的是对于每个质心,计算它最接近的灰色点是哪一个。通过这样做,图中蓝色阴影的点与蓝色质心相关联。绿色点最接近绿色质心,而这个单独的红点最接近我选择的那个红色随机点。
当然,你可以看到这并不真正反映实际聚类的情况。因此,我要做的是取出每个聚类中的点,并计算这些点的实际中心。例如,在绿色聚类中,所有数据的实际中心实际上要低一点。我们会将质心向下移动一点。红色聚类只有一个点,所以它的中心移动到那个单个点的位置。而蓝色点实际上离中心相当近,所以只是移动了一点。在下一次迭代中,我们得到了类似图 D 的结果。现在你可以看到我们红色聚类的范围有所增加,事物也有所移动,也就是说,它们被从绿色聚类中取走了。
如果我们再次这样做,你可能可以预测接下来会发生什么。绿色的质心会移动一点,蓝色的质心仍然会保持在原来的位置。但最终你会得到你可能期望看到的聚类。这就是 k-means 的工作原理。因此,它会不断迭代,试图找到正确的质心,直到事物开始移动并收敛于一个解决方案。
k-means 聚类的局限性
因此,k-means 聚类存在一些局限性。以下是其中一些:
-
**选择 K:**首先,我们需要选择正确的 K 值,这并不是一件简单的事情。选择 K 的主要方法是从较低的值开始,根据你想要的群组数量不断增加 K 的值,直到停止获得平方误差的大幅减少。如果你观察每个点到它们的质心的距离,你可以将其视为一个误差度量。当你停止减少这个误差度量时,你就知道你可能有太多的聚类。因此,在那一点上,通过添加额外的聚类,你实际上并没有获得更多的信息。
-
**避免局部最小值:**此外,还存在局部最小值的问题。你可能会因为初始质心的选择而非常不幸,它们最终可能只会收敛于局部现象,而不是更全局的聚类,因此通常情况下,你需要多次运行这个过程,然后将结果进行平均。我们称之为集成学习。我们稍后会更详细地讨论这个问题,但多次运行 k-means 并使用不同的随机初始值是一个很好的主意,看看你最终是否得到了相同的结果。
-
**标记聚类:**最后,k-means 聚类的主要问题是得到的聚类没有标签。它只会告诉你这组数据点在某种程度上是相关的,但你无法给它们贴上名字。它无法告诉你该聚类的实际含义。假设我有一堆电影,我正在观看,k-means 聚类告诉我一堆科幻电影在这里,但它不会为我称它们为“科幻”电影。我需要深入数据并弄清楚,这些东西真正有什么共同点?我如何用英语描述它们?这是困难的部分,k-means 不会帮助你。所以再次,scikit-learn 使这变得非常容易。
现在,让我们举个例子,让 k-means 聚类付诸实践。
基于收入和年龄对人进行聚类
让我们看看使用 scikit-learn 和 Python 进行 k-means 聚类有多容易。
我们要做的第一件事是创建一些我们想要尝试进行聚类的随机数据。为了简化,我们实际上会在我们的假测试数据中构建一些聚类。所以假设这些数据之间存在一些真实的基本关系,并且其中存在一些真实的自然聚类。
为了做到这一点,我们可以使用 Python 中的createClusteredData()
函数:
from numpy import random, array
#Create fake income/age clusters for N people in k clusters
def createClusteredData(N, k):
random.seed(10)
pointsPerCluster = float(N)/k
X = []
for i in range (k):
incomeCentroid = random.uniform(20000.0, 200000.0)
ageCentroid = random.uniform(20.0, 70.0)
for j in range(int(pointsPerCluster)):
X.append([random.normal(incomeCentroid, 10000.0),
random.normal(ageCentroid, 2.0)])
X = array(X)
return X
该函数从一致的随机种子开始,因此每次都会得到相同的结果。我们想要在 k 个聚类中创建 N 个人的聚类。所以我们将N
和k
传递给createClusteredData()
。
我们的代码首先计算出每个聚类的点数,并将其存储在pointsPerCluster
中。然后,它构建了一个起始为空的列表X
。对于每个聚类,我们将创建一些收入的随机中心(incomeCentroid
),介于 20,000 到 200,000 美元之间,以及一些年龄的随机中心(ageCentroid
),介于 20 到 70 岁之间。
我们在这里所做的是创建一个假的散点图,显示了N
个人和k
个聚类的收入与年龄。所以对于我们创建的每个随机中心,我将创建一组正态分布的随机数据,收入的标准差为 10,000,年龄的标准差为 2。这将给我们一堆年龄收入数据,它们被聚类到一些我们可以随机选择的预先存在的聚类中。好的,让我们继续运行。
现在,要实际进行 k-means,你会看到它有多容易。
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
from sklearn.preprocessing import scale
from numpy import random, float
data = createClusteredData(100, 5)
model = KMeans(n_clusters=5)
# Note I'm scaling the data to normalize it! Important for good results.
model = model.fit(scale(data))
# We can look at the clusters each data point was assigned to
print model.labels_
# And we'll visualize it:
plt.figure(figsize=(8, 6))
plt.scatter(data[:,0], data[:,1], c=model.labels_.astype(float))
plt.show()
你所需要做的就是从 scikit-learn 的 cluster 包中导入KMeans
。我们还要导入matplotlib
,这样我们就可以可视化数据,还要导入scale
,这样我们就可以看看它是如何工作的。
所以我们使用我们的createClusteredData()
函数来说有 100 个随机人分布在 5 个聚类中。所以对于我创建的数据,有 5 个自然的聚类。然后我们创建一个模型,一个 k 为 5 的 KMeans 模型,所以我们选择 5 个聚类,因为我们知道这是正确的答案。但是在无监督学习中,你不一定知道k
的真实值。你需要自己迭代和收敛到它。然后我们只需使用我们的 KMeans 模型使用我们的数据来调用model.fit
。
现在我之前提到的规模,那是对数据进行归一化。k-means 的一个重要问题是,如果你的数据都归一化,它的效果会更好。这意味着一切都在相同的尺度上。所以我在这里遇到的问题是,我的年龄范围是 20 到 70 岁,但我的收入范围高达 20 万美元。所以这些值并不真正可比。收入远远大于年龄值。Scale
将把所有数据一起缩放到一个一致的尺度,这样我就可以将这些数据进行比较,这将有助于你的 k-means 结果。
所以,一旦我们在我们的模型上调用了fit
,我们实际上可以查看我们得到的结果标签。然后我们可以使用一点matplotlib
的魔法来可视化它。你可以在代码中看到我们有一个小技巧,我们将颜色分配给了我们最终得到的标签,转换为一些浮点数。这只是一个小技巧,你可以用来为给定的值分配任意颜色。所以让我们看看我们最终得到了什么:
这并没有花太长时间。你可以看到结果基本上是我分配给每个东西的聚类。我们知道我们的假数据已经被预先聚类,所以它似乎很容易地识别了第一和第二个聚类。然而,在那之后它有点困惑了,因为我们中间的聚类实际上有点混在一起。它们并不是真的那么明显,所以这对 k 均值来说是一个挑战。但无论如何,它确实对聚类提出了一些合理的猜测。这可能是四个聚类更自然地适应数据的一个例子。
活动
所以我想让你做的一个活动是尝试不同的 k 值,看看你最终得到了什么。仅仅凭眼前的图表,看起来四个可能效果很好。真的吗?如果我把 k 增加得太大会发生什么?我的结果会怎样?它会尝试将事物分成什么,这甚至有意义吗?所以,玩一下,尝试不同的k
值。所以在n_clusters()
函数中,将 5 改为其他值。再次运行一遍,看看你最终得到了什么。
这就是 k 均值聚类的全部内容。就是这么简单。你可以使用 scikit-learn 的KMeans
从cluster
中的东西。唯一真正需要注意的是:确保你对数据进行缩放,归一化。你希望确保你用 k 均值进行处理的东西是可比较的,scale()
函数会为你做到这一点。所以这些是 k 均值聚类的主要内容。非常简单的概念,使用 scikit-learn 更简单。
就是这样。这就是 k 均值聚类。所以如果你有一堆未分类的数据,而且你事先并没有正确的答案,这是一个很好的方法来自然地找到数据的有趣分组,也许这可以让你对数据有一些见解。这是一个很好的工具。我以前在现实世界中使用过它,而且真的并不难使用,所以记住它。
测量熵
很快我们就要进入机器学习中更酷的部分之一,至少我认为是,叫做决策树。但在我们谈论那之前,理解数据科学中熵的概念是必要的。
所以熵,就像在物理学和热力学中一样,是数据集的混乱程度的度量,数据集的相同或不同程度。所以想象一下,我们有一个不同分类的数据集,例如动物。比如说我有一堆我已经按物种分类的动物。现在,如果我的数据集中的所有动物都是鬣蜥,我就有很低的熵,因为它们都是一样的。但如果我的数据集中的每个动物都是不同的动物,我有鬣蜥和猪和树懒和谁知道还有什么,那么我就会有更高的熵,因为我的数据集中有更多的混乱。事物之间的不同性大于相同性。
熵只是一种量化我的数据中相同或不同的方式。所以,熵为 0 意味着数据中的所有类别都是相同的,而如果一切都不同,我就会有很高的熵,而介于两者之间的情况将是介于两者之间的数字。熵只是描述数据集中的事物是相同还是不同的方式。
现在从数学上讲,它比那复杂一点,所以当我实际计算熵的数值时,它是使用以下表达式计算的:
所以对于我数据中的每个不同类,我将有一个这样的 p 项,p[1],p[2],等等,直到 p[n],对于我可能有的 n 个不同类。p 只是表示数据中是那个类的比例。如果你实际上绘制出每个项的样子- pi* ln * pi
,它看起来会有点像下面的图表:
你为每个单独的类加起来。例如,如果数据的比例,也就是说,对于给定的类是 0,那么对总熵的贡献就是 0。如果一切都是这个类,那么对总熵的贡献也是 0,因为在任何一种情况下,如果没有任何东西是这个类或者一切都是这个类,那实际上并没有对总熵做出任何贡献。
中间的事物会为类的熵做出贡献,那里有一些这种分类和其他东西的混合。当你把所有这些项加在一起时,你就得到了整个数据集的总熵。所以从数学上讲,就是这样运作的,但是,这个概念非常简单。它只是衡量你的数据集有多无序,你的数据中的事物有多相同或不同。
决策树-概念
信不信由你,给定一组训练数据,你实际上可以让 Python 为你生成一个流程图来做出决定。所以如果你有一些你想要在某些分类上进行预测的东西,你可以使用决策树来实际查看流程图中每个级别上可以决定的多个属性。你可以打印出一个实际的流程图供你使用,以便基于实际的机器学习做出决定。这有多酷?让我们看看它是如何运作的。
我个人认为决策树是机器学习中最有趣的应用之一。决策树基本上给出了如何做出某些决定的流程图。你有一些依赖变量,比如今天是否应该根据天气出去玩。当你有一个依赖于多个属性或多个变量的决定时,决策树可能是一个不错的选择。
天气的许多不同方面可能会影响我是否应该出去玩的决定。这可能与湿度、温度、是否晴天等有关。决策树可以查看天气的所有这些不同属性,或者其他任何东西,并决定什么是阈值?在每个属性上我需要做出什么决定,然后才能决定我是否应该出去玩?这就是决策树的全部内容。所以它是一种监督学习。
在这个例子中,它的工作方式如下。我会有一些关于历史天气的数据集,以及关于人们在特定一天是否出去玩的数据。我会向模型提供这些数据,比如每天是否晴天,湿度是多少,是否刮风,以及那天是否适合出去玩。在给定这些训练数据的情况下,决策树算法可以得出一棵树,给我们一个流程图,我们可以打印出来。它看起来就像下面的流程图。你可以浏览并根据当前属性来判断是否适合出去玩。你可以用它来预测新一组值的决定:
这有多酷?我们有一个算法,可以根据观测数据自动生成流程图。更酷的是,一旦你学会了它的工作原理,它的一切都是如此简单。
决策树示例
假设我想建立一个系统,根据其中的信息自动筛选简历。技术公司面临的一个大问题是,我们为我们的职位收到了大量的简历。我们必须决定我们实际上要邀请谁来面试,因为飞某人出来并且实际上花时间进行面试可能是很昂贵的。那么,如果有一种方法可以将实际上被雇佣的人的历史数据与他们简历中的信息相匹配,那会怎么样呢?
我们可以构建一个决策树,让我们可以浏览个人简历,并说,“好的,这个人实际上有很高的被雇佣可能性,或者没有”。我们可以根据历史数据训练一个决策树,并为未来的候选人走过这个流程。那不是一件很美好的事情吗?
所以让我们制作一些完全捏造的雇佣数据,我们将在这个例子中使用。
在前面的表中,我们有一些只用数字标识的候选人。我将挑选一些我认为可能有趣或有助于预测他们是否是一个好的雇佣者的属性。他们有多少年的工作经验?他们目前有工作吗?他们之前有多少雇主?他们的教育水平是多少?他们有什么学位?他们是否上过我们分类为顶尖学校?他们在大学期间是否做过实习?我们可以看一下这些历史数据,这里的因变量是“被雇佣”。这个人实际上是否根据这些信息得到了工作机会?
现在,显然这个模型中没有的很多信息可能非常重要,但是我们从这些数据中训练出来的决策树实际上可能有助于在初步筛选一些候选人时使用。我们最终得到的可能是一个看起来像下面这样的树:
-
所以事实证明,在我完全捏造的数据中,任何在大学实习的人最终都得到了工作机会。所以我的第一个决策点是“这个人是否做过实习?”如果是,那就让他们进来吧。根据我的经验,实习实际上是一个相当好的人才预测指标。如果他们有主动性去实习,并且在实习中真正学到了东西,那是一个好迹象。
-
他们目前有工作吗?嗯,如果他们目前有工作,那么在我的非常小的虚拟数据集中,结果表明他们值得雇佣,只是因为其他人也认为他们值得雇佣。显然,在现实世界中,这将是一个更微妙的决定。
-
如果他们目前没有工作,他们之前的雇主少于一个吗?如果是,这个人以前从未工作过,他们也没有做过实习。可能不是一个好的雇佣决定。不要雇佣这个人。
-
但是如果他们之前有过雇主,他们是否至少上过一所顶尖学校?如果没有,那就有点靠不住。如果是,那么是的,我们应该根据我们训练的数据来雇佣这个人。
浏览决策树
这就是你如何浏览决策树的结果。就像浏览流程图一样,算法可以为你产生这样的结果,这真是太棒了。算法本身实际上非常简单。让我解释一下算法是如何工作的。
在决策树流程图的每一步,我们找到可以将我们的数据分区的属性,以最小化下一步数据的熵。所以我们得到了一组分类:在这种情况下是雇佣或不雇佣,我们希望选择在下一步最小化熵的属性决策。
在每一步,我们希望所有剩下的选择都导致尽可能多的不录用或尽可能多的录用决定。我们希望使数据变得越来越统一,因此当我们沿着流程图向下工作时,最终我们最终得到一组候选人,要么全部录用,要么全部不录用,这样我们就可以在决策树上对是/否做出分类。因此,我们只需沿着树走,通过选择正确的属性来最小化每一步的熵,直到我们用完为止。
这种算法有一个花哨的名字。它被称为ID3(迭代二分器 3)。这是一个贪婪算法。因此,当它沿着树走时,它只选择在那一点上最小化熵的属性。现在,这可能实际上不会导致最小化你必须做出的选择的最佳树,但它将会得到一个树,鉴于你给它的数据。
随机森林技术
现在决策树的一个问题是它们很容易过拟合,所以你可能会得到一个对训练数据非常有效的决策树,但对于那些它以前没有见过的新人的正确分类预测可能并不那么好。决策树的核心是为你提供的训练数据做出正确的决策,但也许你并没有真正考虑到正确的属性,也许你没有给它足够代表性的人员样本来学习。这可能会导致真正的问题。
为了解决这个问题,我们使用一种称为随机森林的技术,其思想是我们以不同的方式对我们进行训练的数据进行采样,用于多个不同的决策树。每棵决策树从我们的训练数据集中随机选择不同的样本,并从中构建一棵树。然后每棵树都可以对正确的结果进行投票。
现在我们使用相同模型对我们的数据进行随机重采样的技术被称为自举聚合,或者称为装袋。这是一种我们称之为集成学习的形式,我们很快会更详细地介绍。但基本思想是我们有多个树,如果你愿意的话,可以称之为树的森林,每个树都使用我们要训练的数据的随机子样本。然后每棵树都可以对最终结果进行投票,这将帮助我们对给定的训练数据进行过拟合。
随机森林可以做的另一件事是在它尝试最小化熵的同时,实际上限制它可以在每个阶段选择的属性数量。我们可以在每个级别随机选择它可以选择的属性。因此,这也使我们的树与树之间更加多样化,因此我们得到了更多可以相互竞争的算法的变化。它们可以使用略有不同的方法对最终结果进行投票,以达到相同的答案。
这就是随机森林的工作原理。基本上,它是一组决策树的森林,它们从不同的样本和不同的属性集中进行选择。
因此,有了这一切,让我们去做一些决策树。当我们完成后,我们也将使用随机森林,因为 scikit-learn 使得这变得非常容易,很快你就会看到。
决策树 - 使用 Python 预测招聘决策
事实证明,制作决策树很容易;事实上,只需几行 Python 代码就可以做到这一点。所以让我们试一试。
我已经在你的书材料中包含了一个PastHires.csv
文件,里面只包含了一些虚构的数据,是我根据候选人的属性编造的,这些候选人要么得到了工作机会,要么没有。
import numpy as np
import pandas as pd
from sklearn import tree
input_file = "c:/spark/DataScience/PastHires.csv"
df = pd.read_csv(input_file, header = 0)
请立即更改我在这里使用的路径,用于我的系统(c:/spark/DataScience/PastHires.csv
),改为你安装本书材料的位置。我不确定你把它放在哪里,但几乎肯定不是那里。
我们将使用pandas
读取我们的 CSV 文件,并将其创建为一个 DataFrame 对象。让我们继续运行我们的代码,并可以使用 DataFrame 的head()
函数打印出前几行,确保它看起来是有意义的。
df.head()
确实,我们在输出中有一些有效的数据:
因此,对于每个候选人 ID,我们有他们的过去工作经验年限,是否就业,以前的雇主数量,他们的最高教育水平,是否就读顶级学校,是否做过实习;最后,在 Hired 列中,答案-我们知道我们是否向这个人提供了工作机会。
通常情况下,大部分工作只是在处理数据,准备数据,然后才实际运行算法,这就是我们需要在这里做的。现在 scikit-learn 要求一切都是数字,所以我们不能有 Y 和 N 和 BS 和 MS 和 PhD。我们必须将所有这些东西转换为数字,以便决策树模型能够工作。在 pandas 中使用一些简写可以使这些事情变得容易。例如:
d = {'Y': 1, 'N': 0}
df['Hired'] = df['Hired'].map(d)
df['Employed?'] = df['Employed?'].map(d)
df['Top-tier school'] = df['Top-tier school'].map(d)
df['Interned'] = df['Interned'].map(d)
d = {'BS': 0, 'MS': 1, 'PhD': 2}
df['Level of Education'] = df['Level of Education'].map(d)
df.head()
基本上,我们在 Python 中创建一个字典,将字母 Y 映射为数字 1,将字母 N 映射为值 0。因此,我们想将所有的 Y 转换为 1,将所有的 N 转换为 0。因此 1 表示是,0 表示否。我们只需从 DataFrame 中取出 Hired 列,并在其上调用map()
,使用一个字典。这将遍历整个 DataFrame 中的 Hired 列,并使用该字典查找来转换该列中的所有条目。它返回一个新的 DataFrame 列,我将其放回到 Hired 列中。这将用 1 和 0 映射的列替换 Hired 列。
我们对就业,顶级学校和实习做同样的处理,所以所有这些都使用是/否字典进行映射。因此,Y 和 N 变成了 1 和 0。对于教育水平,我们也使用同样的技巧,创建一个将 BS 分配为 0,MS 分配为 1,PhD 分配为 2 的字典,并使用它来重新映射这些学位名称为实际的数值。所以如果我继续运行并再次使用head()
,你会看到它起作用了:
我的所有是 1,我的所有否是 0,我的教育水平现在由具有实际含义的数值表示。
接下来,我们需要准备一切以实际进入我们的决策树分类器,这并不难。为此,我们需要分离我们的特征信息,即我们试图预测的属性,以及我们的目标列,其中包含我们试图预测的东西。为了提取特征名称列的列表,我们只需创建一个列的列表,直到第 6 列。我们继续打印出来。
features = list(df.columns[:6])
features
我们得到以下输出:
上面是包含我们特征信息的列名:工作经验年限,是否就业,以前的雇主,教育水平,顶级学校和实习。这些是我们想要预测雇佣的候选人的属性。
接下来,我们构建我们的y向量,它被分配为我们要预测的内容,也就是我们的 Hired 列:
y = df["Hired"]
X = df[features]
clf = tree.DecisionTreeClassifier()
clf = clf.fit(X,y)
这段代码提取整个 Hired 列并将其命名为y
。然后它将所有特征数据的列放入一个名为X
的对象中。这是所有数据和所有特征列的集合,X
和y
是我们的决策树分类器需要的两个东西。
要实际创建分类器本身,只需两行代码:我们调用tree.DecisionTreeClassifier()
来创建我们的分类器,然后将其拟合到我们的特征数据(X
)和答案(y
) - 是否雇佣了人。所以,让我们继续运行。
显示图形数据有点棘手,我不想在这里分散我们太多的注意力,所以请只考虑以下样板代码。你不需要深入了解 Graph viz 在这里的工作方式 - 以及 dot 文件和所有这些东西:这对我们的旅程现在不重要。你需要实际显示决策树最终结果的代码只是:
from IPython.display import Image
from sklearn.externals.six import StringIO
import pydot
dot_data = StringIO()
tree.export_graphviz(clf, out_file=dot_data,
feature_names=features)
graph = pydot.graph_from_dot_data(dot_data.getvalue())
Image(graph.create_png())
所以让我们继续运行这个。
你的输出现在应该是这样的:
我们有了!这有多酷!我们这里有一个实际的流程图。
现在,让我告诉你如何阅读它。在每个阶段,我们都有一个决定。记住,我们的大多数数据是是或否,将是 0 或 1。所以,第一个决定点是:就业?小于 0.5 吗?这意味着如果我们有一个就业价值为 0,那就是不,我们将向左走。如果就业是 1,也就是是,我们将向右走。
那么,他们以前受雇吗?如果没有,向左走,如果是,向右走。结果是,在我的样本数据中,每个目前受雇的人实际上都得到了一个工作机会,所以我可以非常快速地说,如果你目前受雇,是的,你值得被带进来,我们将继续到第二个层级。
那么,你如何解释这个呢?基尼分数基本上是在每一步使用的熵的度量。记住,当我们进行算法时,它试图最小化熵的量。样本是之前的决定没有分割的剩余样本数量。
所以说这个人以前是受雇的。阅读右叶节点的方法是值列,告诉你在这一点上,我们有 0 个候选人是不被雇佣的,有 5 个是被雇佣的。所以,解释第一个决定点的方法是,如果就业?是 1,我会向右走,这意味着他们目前是受雇的,这让我进入了一个每个人都得到了工作机会的世界。所以,这意味着我应该雇佣这个人。
现在假设这个人目前没有工作。我接下来要看的是,他们有实习吗。如果是,那么在我们的训练数据中,每个人都得到了一个工作机会。所以,在那一点上,我们可以说我们的熵现在是 0(gini=0.0000
),因为每个人都一样,在那一点上他们都得到了一个工作机会。然而,你知道,如果我们继续下去(在这个人没有做实习的情况下),我们将到达一个熵为 0.32 的点。它越来越低,这是一件好事。
接下来我们要看的是他们有多少经验,他们有不到一年的经验吗?如果情况是他们确实有一些经验,并且他们已经走到了这一步,他们是一个相当好的不被雇佣的决定。我们最终到达了熵为零的点,但是,在我们的训练集中剩下的三个样本都是不被雇佣的。我们有 3 个不被雇佣和 0 个被雇佣。但是,如果他们经验较少,那么他们可能刚刚从大学毕业,他们仍然值得一看。
我们要看的最后一件事是他们是否上了一所顶尖学校,如果是的话,他们最终会成为一个好的预测被雇佣。如果不是,他们最终会成为一个不被雇佣。我们最终有一个候选人属于这个类别,是一个不被雇佣,还有 0 个被雇佣。而在候选人确实上了一所顶尖学校的情况下,我们有 0 个不被雇佣和 1 个被雇佣。
所以,你可以看到,我们一直走下去,直到我们达到熵为 0,如果可能的话,对于每种情况。
集成学习 - 使用随机森林
现在,假设我们想使用随机森林,你知道,我们担心我们可能过度拟合我们的训练数据。实际上,很容易创建一个多个决策树的随机森林分类器。
所以,为了做到这一点,我们可以使用之前创建的相同数据。你只需要你的*X*
和*y*
向量,也就是特征集和你试图预测的列:
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier(n_estimators=10)
clf = clf.fit(X, y)
#Predict employment of an employed 10-year veteran
print clf.predict([[10, 1, 4, 0, 0, 0]])
#...and an unemployed 10-year veteran
print clf.predict([[10, 0, 4, 0, 0, 0]])
我们制作了一个随机森林分类器,也可以从 scikit-learn 中获得,并传递给它我们想要在我们的森林中的树的数量。所以,在上面的代码中,我们的随机森林中有十棵树。然后我们将其适配到模型上。
你不必手动遍历树,而且当你处理随机森林时,你也不能真正这样做。所以,我们在模型上使用predict()
函数,也就是我们制作的分类器上。我们传入一个给定候选人的所有不同特征的列表,我们想要预测他们的就业情况。
如果你记得,这些映射到这些列:工作经验,就业情况,以前的雇主,教育水平,顶级学校和实习;被解释为数值。我们预测一个有工作的 10 年经验的老手的就业情况。我们还预测一个失业的 10 年经验的老手的就业情况。果然,我们得到了一个结果:
在这种情况下,我们最终都做出了雇佣决定。但有趣的是,这其中有一个随机因素。你实际上并不会每次都得到相同的结果!往往情况下,失业者并不会得到工作机会,如果你继续运行这个过程,你会发现通常情况下都是这样。但是,bagging 的随机性,每棵树的自助聚合的随机性,意味着你不会每次都得到相同的结果。所以,也许 10 棵树还不够。总之,这是一个很好的教训!
活动
作为一个活动,如果你想回去玩一下,可以玩弄我的输入数据。随意编辑我们一直在探索的代码,并创建一个颠倒世界的替代宇宙;例如,我给工作机会的每个人现在都不再得到工作机会,反之亦然。看看这对你的决策树有什么影响。只是随意玩弄一下,看看你能做什么,并尝试解释结果。
所以,那就是决策树和随机森林,我认为这是机器学习中更有趣的部分之一。我总是觉得能够从空中生成一个流程图非常酷。所以,希望你会觉得这很有用。
集成学习
当我们谈论随机森林时,那是集成学习的一个例子,我们实际上将多个模型组合在一起,以得到比任何单个模型更好的结果。所以,让我们更深入地了解一下。让我们更多地谈谈集成学习。
所以,还记得随机森林吗?我们有一堆使用输入数据的不同子样本和不同属性集的决策树,当你试图在最后对某些东西进行分类时,它们都对最终结果进行投票。这就是集成学习的一个例子。另一个例子:当我们谈论 k 均值聚类时,我们有一个想法,也许使用不同的 k 均值模型和不同的初始随机质心,让它们都对最终结果进行投票。这也是集成学习的一个例子。
基本上,这个想法是你有不止一个模型,它们可能是相同类型的模型,也可能是不同类型的模型,但你在你的训练数据集上运行它们所有,并且它们都对你试图预测的最终结果进行投票。往往情况下,你会发现这个不同模型的集合产生比任何单个模型本身更好的结果。
几年前的一个很好的例子是 Netflix 奖。Netflix 举办了一项比赛,他们提供了我认为是一百万美元的奖金,给任何研究人员,如果他们能够超越现有的电影推荐算法。获胜的方法是集成方法,他们实际上同时运行多个推荐算法,并让它们都对最终结果进行投票。因此,集成学习可以是一种非常强大而简单的工具,用于提高机器学习中最终结果的质量。现在让我们尝试探索各种类型的集成学习:
-
**自举聚合或装袋:**现在,随机森林使用一种称为装袋的技术,简称自举聚合。这意味着我们从训练数据中随机抽取子样本,并将它们馈送到同一模型的不同版本中,让它们都对最终结果进行投票。如果你还记得,随机森林采用许多不同的决策树,这些决策树使用训练数据的不同随机样本进行训练,然后它们最终汇聚在一起对最终结果进行投票。这就是装袋。
-
**提升:**提升是一种替代模型,其思想是你从一个模型开始,但每个后续模型都增强了前一个模型误分类的属性。因此,你在一个模型上进行训练/测试,找出它基本上搞错了什么属性,然后在后续模型中增强这些属性 - 希望后续模型会更加关注它们,并且把它们搞对。这就是提升的一般思想。你运行一个模型,找出它的弱点,随着你的进行,增强对这些弱点的关注,并且不断构建更多的模型,这些模型根据前一个模型的弱点进行改进。
-
**模型桶:**另一种技术,这就是 Netflix 奖的获奖者所做的,称为模型桶,你可能有完全不同的模型来尝试预测某些东西。也许我正在使用 k-means、决策树和回归。我可以同时在一组训练数据上运行这三个模型,并让它们都对在预测时的最终分类结果进行投票。也许这比单独使用其中任何一个模型要好。
-
**堆叠:**堆叠有相同的思想。因此,你在数据上运行多个模型,以某种方式将结果组合在一起。桶模型和堆叠之间的微妙差异在于你选择获胜的模型。因此,你运行训练/测试,找出最适合你的数据的模型,并使用该模型。相比之下,堆叠将所有这些模型的结果组合在一起,得出最终结果。
现在,有一个关于集成学习的研究领域,试图找到最佳的集成学习方法,如果你想显得聪明,通常这涉及大量使用贝叶斯这个词。因此,有一些非常先进的集成学习方法,但它们都有弱点,我认为这又是一个教训,即我们应该始终使用对我们有效的最简单的技术。
现在,这些都是非常复杂的技术,在本书的范围内我无法深入讨论,但归根结底,很难超越我们已经讨论过的简单技术。以下列出了一些复杂的技术:
-
**贝叶斯最优分类器:**理论上,有一种称为贝叶斯最优分类器,它将始终是最好的,但这是不切实际的,因为计算上是禁止的。
-
贝叶斯参数平均化:许多人尝试对贝叶斯最优分类器进行变化,使其更实用,比如贝叶斯参数平均化变化。但它仍然容易过拟合,通常被随机森林背包法超越;你只需多次重新采样数据,运行不同模型,让它们投票决定最终结果。结果表明这样做同样有效,而且简单得多!
-
贝叶斯模型组合:最后,有一种称为贝叶斯模型组合的东西,试图解决贝叶斯最优分类器和贝叶斯参数平均化的所有缺点。但是,归根结底,它并没有比只是交叉验证组合模型做得更好。
再次强调,这些都是非常复杂的技术,非常难以使用。在实践中,我们最好使用我们更详细讨论过的更简单的技术。但是,如果你想显得聪明并经常使用贝叶斯这个词,熟悉这些技术并知道它们是什么是很好的。
因此,这就是集成学习。再次强调的是,像自助聚合、背包法、提升法、堆叠法或模型桶之类的简单技术通常是正确的选择。还有一些更花哨的技术,但它们在很大程度上是理论性的。但是,至少现在你知道它们了。
尝试集成学习总是一个好主意。一次又一次地证明,它将产生比任何单一模型更好的结果,因此一定要考虑它!
支持向量机概述
最后,我们将讨论支持向量机(SVM),这是一种非常先进的方法,用于聚类或分类高维数据。
那么,如果你有多个要预测的特征呢?支持向量机可能是一个非常强大的工具,结果可能非常好!它在内部非常复杂,但重要的是要理解何时使用它,以及它在更高层次上是如何工作的。所以,现在让我们来讨论支持向量机。
支持向量机是一个花哨的名字,实际上是一个花哨的概念。但幸运的是,它非常容易使用。重要的是要知道它的作用和用途。因此,支持向量机对于分类高维数据效果很好,我指的是许多不同的特征。因此,使用 k 均值聚类之类的东西很容易对具有两个维度的数据进行聚类,也许一个维度是年龄,另一个维度是收入。但是,如果我有许多不同的特征要预测,那么支持向量机可能是一个不错的选择。
支持向量机找到高维支持向量,用于划分数据(数学上,这些支持向量定义超平面)。也就是说,数学上,支持向量机可以找到高维支持向量(这就是它得名的地方),这些支持向量定义了将数据分成不同簇的高维平面。
显然,所有这些都很快变得非常奇怪。幸运的是,scikit-learn 软件包将为您完成所有这些,而无需您实际参与其中。在内部,您需要理解它使用一种称为核技巧的东西来实际找到那些在较低维度中可能不明显的支持向量或超平面。您可以使用不同的核来以不同的方式执行此操作。主要问题是,如果您有具有许多不同特征的高维数据,支持向量机是一个不错的选择,您可以使用不同的核,其计算成本不同,并且可能更适合手头的问题。
重要的一点是 SVM 使用一些高级数学技巧来对数据进行聚类,并且可以处理具有许多特征的数据集。它也相当昂贵 - "核技巧"是唯一使其可能的东西。
我想指出 SVM 是一种监督学习技术。因此,我们实际上要在一组训练数据上对其进行训练,并且我们可以使用它来对未来未见数据或测试数据进行预测。这与 k 均值聚类有点不同,k 均值是完全无监督的;相比之下,支持向量机是基于实际训练数据进行训练的,其中你有一些数据集的正确分类答案可以学习。因此,如果你愿意的话,SVM 对于分类和聚类是有用的 - 但这是一种监督技术!
SVM 经常使用的一个例子是使用称为支持向量分类的东西。典型的例子使用了 Iris 数据集,这是 scikit-learn 附带的样本数据集之一。这个数据集是对不同的鸢尾花进行分类,不同的鸢尾花的不同观察和它们的种类。这个想法是使用关于每朵花瓣的长度和宽度以及每朵花萼的长度和宽度的信息来对这些进行分类。 (萼片显然是花瓣下面的一个小支撑结构。我直到现在也不知道。)你有四个属性的维度;你有花瓣的长度和宽度,以及萼片的长度和宽度。你可以使用这些信息来预测给定信息的鸢尾花的种类。
这是使用 SVC 进行的一个例子:基本上,我们将萼片宽度和萼片长度投影到二维,这样我们就可以实际可视化它:
使用不同的核心可能会得到不同的结果。具有线性核心的 SVC 将产生与前面图像中看到的非常相似的东西。你可以使用多项式核心或更复杂的核心,可能会在图像中显示为二维曲线。你可以通过这种方式进行一些相当花哨的分类。
这些都会增加计算成本,并且可以产生更复杂的关系。但同样,这是一种情况,过于复杂可能会产生误导性的结果,因此你需要小心,并在适当的时候使用训练/测试。由于我们正在进行监督学习,你实际上可以进行训练/测试,并找到适合的模型,或者使用集成方法。
你需要找到适合当前任务的正确核心。对于多项式 SVC 之类的东西,使用什么程度的多项式才是正确的?即使是线性 SVC 也会有与之相关的不同参数,你可能需要进行优化。这将在一个真实的例子中更有意义,所以让我们深入一些实际的 Python 代码,看看它是如何工作的!
使用 SVM 通过 scikit-learn 对人进行聚类
让我们在这里尝试一些支持向量机。幸运的是,使用起来比理解起来要容易得多。我们将回到我用于 k 均值聚类的相同示例,我将创建一些关于一百个随机人的年龄和收入的虚构集群数据。
如果你想回到 k 均值聚类部分,你可以了解更多关于生成虚假数据的代码背后的想法。如果你准备好了,请考虑以下代码:
import numpy as np
#Create fake income/age clusters for N people in k clusters
def createClusteredData(N, k):
pointsPerCluster = float(N)/k
X = []
y = []
for i in range (k):
incomeCentroid = np.random.uniform(20000.0, 200000.0)
ageCentroid = np.random.uniform(20.0, 70.0)
for j in range(int(pointsPerCluster)):
X.append([np.random.normal(incomeCentroid, 10000.0),
np.random.normal(ageCentroid, 2.0)])
y.append(i)
X = np.array(X)
y = np.array(y)
return X, y
请注意,因为我们在这里使用的是监督学习,我们不仅需要再次使用特征数据,还需要我们训练数据集的实际答案。
这里的createClusteredData()
函数的作用是根据年龄和收入创建一堆围绕k
点聚集的随机数据,并返回两个数组。第一个数组是我们称之为X
的特征数组,然后我们有我们试图预测的东西的数组,我们称之为y
。在 scikit-learn 中,当你创建一个可以进行预测的模型时,这些是它将接受的两个输入,特征向量的列表和你试图预测的东西,它可以从中学习。所以,我们将继续运行。
所以现在我们将使用createClusteredData()
函数创建 100 个随机人,分为 5 个不同的集群。我们将创建一个散点图来说明这些人的情况,并看看他们最终落在哪里:
%matplotlib inline
from pylab import *
(X, y) = createClusteredData(100, 5)
plt.figure(figsize=(8, 6))
plt.scatter(X[:,0], X[:,1], c=y.astype(np.float))
plt.show()
下图显示了我们正在处理的数据。每次运行这个程序,你都会得到一组不同的集群。所以,你知道,我实际上没有使用随机种子…让生活变得有趣。
这里有几个新东西 - 我在plt.figure()
上使用了figsize
参数来实际上制作一个更大的图。所以,如果你需要在matplotlib
中调整大小,就是这样做的。我使用了相同的技巧,将颜色作为我最终得到的分类号。所以我开始的集群号被绘制为这些数据点的颜色。你可以看到,这是一个相当具有挑战性的问题,这里肯定有一些集群的交错:
现在我们可以使用线性 SVC(SVC 是 SVM 的一种形式)来将其分成集群。我们将使用具有线性核的 SVM,并且 C 值为1.0
。C 只是一个可以调整的错误惩罚项;默认为1
。通常情况下,你不会想去改变它,但如果你正在使用集成学习或训练/测试对正确模型进行一些收敛,那就是你可以玩耍的东西之一。然后,我们将将该模型拟合到我们的特征数据和我们的训练数据集的实际分类。
from sklearn import svm, datasets
C = 1.0
svc = svm.SVC(kernel='linear', C=C).fit(X, y)
所以,让我们继续运行。我不想过多地讨论我们实际上将如何可视化结果,只是相信plotPredictions()
是一个可以绘制分类范围和 SVC 的函数。
它帮助我们可视化不同分类的位置。基本上,它在整个网格上创建一个网格,并且会将来自 SVC 模型的不同分类作为网格上的不同颜色进行绘制,然后我们将在其上绘制我们的原始数据:
def plotPredictions(clf):
xx, yy = np.meshgrid(np.arange(0, 250000, 10),
np.arange(10, 70, 0.5))
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
plt.figure(figsize=(8, 6))
Z = Z.reshape(xx.shape)
plt.contourf(xx, yy, Z, cmap=plt.cm.Paired, alpha=0.8)
plt.scatter(X[:,0], X[:,1], c=y.astype(np.float))
plt.show()
plotPredictions(svc)
所以,让我们看看它是如何工作的。SVC 的计算成本很高,所以运行时间很长:
你可以在这里看到它尽了最大努力。考虑到它必须绘制直线和多边形形状,它做了不错的工作来适应我们的数据。所以,你知道,它错过了一些 - 但总体上,结果还是相当不错的。
SVC 实际上是一种非常强大的技术;它的真正优势在于更高维度的特征数据。继续玩耍吧。顺便说一句,如果你不仅想可视化结果,你可以像在 scikit-learn 中的几乎任何模型一样使用predict()
函数在 SVC 模型上,传入你感兴趣的特征数组。如果我想预测一个年收入为 20 万美元,年龄为 40 岁的人的分类,我将使用以下代码:
svc.predict([[200000, 40]])
这将把这个人放在我们的情况下,集群编号 1 中:
如果我在这里有一个年收入为 50,000 美元,年龄为 65 岁的人,我将使用以下代码:
svc.predict([[50000, 65]])
这是你的输出现在应该看起来的样子:
这个人最终会进入集群编号 2,无论在这个例子中代表什么。所以,继续玩耍吧。
活动
现在,线性只是你可以使用的许多内核之一,就像我说的,你可以使用许多不同的内核。其中之一是多项式模型,所以你可能想试试。请继续查阅文档。查看文档对你来说是一个很好的练习。如果你要深入使用 scikit-learn,你有很多不同的功能和选项可供选择。所以,去在线查找 scikit-learn,找出 SVC 方法的其他内核是什么,然后尝试它们,看看你是否真的得到了更好的结果。
这不仅是一个关于玩 SVM 和不同类型的 SVC 的练习,还是一个让你熟悉如何自己学习更多关于 SVC 的内容的练习。而且,老实说,任何数据科学家或工程师的一个非常重要的特质将是在你不知道答案时自己去查找信息的能力。
所以,你知道,我没有懒惰,没有告诉你那些其他内核是什么,我希望你习惯于自己查找这些东西的想法,因为如果你总是不得不问别人这些事情,你在工作中会变得非常烦人,非常快。所以,去查一下,玩一下,看看你能得到什么。
所以,这就是 SVM/SVC,一种非常强大的技术,你可以用它来对数据进行分类,在监督学习中。现在你知道它是如何工作的,以及如何使用它,所以记住它吧!
总结
在本章中,我们看到了一些有趣的机器学习技术。我们涵盖了机器学习背后的一个基本概念,称为训练/测试。我们看到如何使用训练/测试来尝试找到合适的多项式度数来适应给定的数据集。然后我们分析了监督学习和无监督学习之间的区别。
我们看到了如何实现一个垃圾邮件分类器,并使其能够使用朴素贝叶斯技术确定一封电子邮件是否是垃圾邮件。我们讨论了 k 均值聚类,一种无监督学习技术,它有助于将数据分组成簇。我们还看了一个使用 scikit-learn 的例子,根据他们的收入和年龄对人进行了聚类。
然后我们继续讨论了熵的概念以及如何衡量它。我们深入讨论了决策树的概念,以及如何在给定一组训练数据的情况下,实际上可以让 Python 为您生成一个流程图来做出决策。我们还建立了一个系统,根据简历中的信息自动过滤简历,并预测一个人的招聘决定。
我们沿途学到了集成学习的概念,并最后谈到了支持向量机,这是一种非常先进的聚类或分类高维数据的方法。然后我们继续使用 SVM 来使用 scikit-learn 对人进行聚类。在下一章中,我们将讨论推荐系统。
第六章:推荐系统
让我们谈谈我个人的专业领域——推荐系统,即可以根据其他人的行为向人们推荐东西的系统。我们将看一些例子以及几种方法。具体来说,两种叫做基于用户和基于物品的协同过滤技术。所以,让我们深入了解一下。
我在amazon.com和imdb.com大部分职业生涯都在那里度过,我在那里做的很多工作都是开发推荐系统;比如购买这个的人也购买了,或者为你推荐,以及为人们推荐电影的东西。所以,这是我个人非常了解的东西,我希望能与你们分享一些这方面的知识。我们将逐步讲解以下主题:
-
什么是推荐系统?
-
基于用户的协同过滤
-
基于物品的协同过滤
-
寻找电影的相似之处
-
向人们推荐电影
-
改进推荐系统的结果
什么是推荐系统?
嗯,就像我说的,亚马逊是一个很好的例子,我对此非常熟悉。所以,如果你去他们的推荐部分,就像下面的图片所示,你会看到它会根据你在网站上的过去行为推荐你可能感兴趣的购买物品。
推荐系统可能包括你评价过的东西,或者你购买过的东西,以及其他数据。我不能详细说明,因为他们会追捕我,你知道,对我做坏事。但是,这很酷。你也可以把亚马逊上的购买这个的人也购买了功能看作是一种推荐系统。
不同之处在于你在亚马逊推荐页面上看到的推荐是基于你的所有过去行为,而购买这个的人也购买了或浏览这个的人也浏览了之类的东西,只是基于你现在正在看的东西,向你展示与之相似的东西,你可能也会感兴趣。而且,结果表明,你现在正在做的事情可能是你兴趣最强烈的信号。
另一个例子来自 Netflix,就像下面的图片所示(下面的图片是 Netflix 的截图):
他们有各种功能,试图根据你以前喜欢或观看的电影来推荐新电影或其他你还没有看过的电影,并且他们会按类型进行分类。他们有一种不同的方式,他们试图确定你最喜欢的电影类型,然后向你展示更多来自这些类型的结果。所以,这是推荐系统在行动中的另一个例子。
它的整个目的是帮助你发现以前可能不知道的东西,所以这很酷。你知道,它给了个别电影、书籍、音乐或其他东西一个被那些以前可能没有听说过的人发现的机会。所以,你知道,它不仅是很酷的技术,它也在某种程度上平衡了竞争,帮助新物品被大众发现。所以,它在当今社会扮演着非常重要的角色,至少我是这么认为的!有几种方法可以做到这一点,我们将在本章中看到主要的方法。
基于用户的协同过滤
首先,让我们谈谈基于你过去行为推荐东西的方法。一种技术叫做基于用户的协同过滤,它是这样工作的:
顺便说一句,协同过滤只是一个花哨的说法,意思是根据你的行为和其他人的行为的组合来推荐东西,好吗?所以,它是在研究你的行为并将其与其他人的行为进行比较,以得出可能对你感兴趣但你还没有听说过的东西。
-
这里的想法是我们建立一个矩阵,记录每个用户曾经购买、查看、评分或者其他你想要基于的兴趣信号的一切。所以基本上,我们的系统中有一个用户的行,该行包含了他们可能对某个产品感兴趣的所有事情。所以,想象一张表,我有用户的行,每一列是一个项目,好吗?这可能是一部电影,一个产品,一个网页,无论什么;你可以用这个做很多不同的事情。
-
然后我使用该矩阵来计算不同用户之间的相似性。所以,我基本上将这个矩阵的每一行都视为一个向量,我可以根据他们的行为计算用户之间的相似性。
-
大部分喜欢相同东西的两个用户会非常相似,然后我可以根据这些相似性分数进行排序。如果我可以找到所有与你相似的用户,基于他们的过去行为,我就可以找到与我最相似的用户,并推荐他们喜欢但我还没看过的东西。
让我们看一个真实的例子,这样可能会更有意义:
假设在前面的例子中,这位可爱的女士看了《星球大战》和《帝国反击战》,她都很喜欢。所以,我们有一个用户向量,这位女士给《星球大战》和《帝国反击战》都打了 5 星的评分。
假设 Edgy Mohawk 先生来了,他只看了《星球大战》。这是他唯一看过的东西,他不知道《帝国反击战》还没有看过,不知何故,他生活在一个奇怪的宇宙里,他不知道实际上有很多很多《星球大战》电影,事实上每年都在增加。
当然,我们可以说这个家伙实际上与另一个女士相似,因为他们都非常喜欢《星球大战》,所以他们的相似性分数可能相当高,我们可以说,好吧,这位女士喜欢的他还没看过什么?《帝国反击战》就是其中之一,所以我们可以根据他们对《星球大战》的喜爱找到这位女士也喜欢《帝国反击战》,然后将其作为对 Edgy Mohawk 先生的良好推荐。
然后我们可以向他推荐《帝国反击战》,他可能会喜欢,因为在我看来,这实际上是一部更好的电影!但我不打算在这里和你进行极客之争。
基于用户的协同过滤的限制
不幸的是,基于用户的协同过滤有一些限制。当我们考虑基于物品和人之间的关系来推荐东西时,我们的思维往往会转向人与人之间的关系。所以,我们想要找到与你相似的人,并推荐他们喜欢的东西。这似乎是直观的做法,但并不是最好的做法!以下是基于用户的协同过滤的一些限制:
-
一个问题是人们喜新厌旧;他们的口味总是在变化。所以,也许在前面的例子中,这位可爱的女士经历了一段短暂的科幻动作电影阶段,然后她克服了这一阶段,也许后来她开始更喜欢戏剧或者浪漫电影或者爱情喜剧。所以,如果我的 Edgy Mohawk 先生基于她早期的科幻时期与她有很高的相似性,然后我们因此向他推荐了浪漫喜剧,那将是糟糕的。我的意思是,在我们计算相似性分数的方式上,对此有一些保护,但人们的口味随时间变化仍然会污染我们的数据。所以,比较人与人之间并不总是一件简单的事情,因为人们会改变。
-
另一个问题是在你的系统中通常会有比物品更多的人,全球有 70 亿人口,而且还在增加,世界上可能并不会有 70 亿部电影,或者你的目录中可能不会有 70 亿个物品需要推荐。在你的系统中找到所有用户之间的相似性可能比找到物品之间的相似性更困难。因此,通过将系统重点放在用户上,你让计算问题变得更加困难,因为你有很多用户,至少希望如此,如果你在一家成功的公司工作的话。
-
最后一个问题是人们会做坏事。确保你的产品、电影或其他任何东西被推荐给人们有着非常现实的经济激励,有些人会试图操纵系统,让他们的新电影、新产品或新书等被推荐。
在系统中制造假身份非常容易,只需创建一个新用户,让他们执行一系列喜欢很多流行物品的事件,然后也喜欢你的物品。这被称为炒作攻击,我们希望能够拥有一个能够处理这种情况的系统。
关于如何检测和避免基于用户的协同过滤中的炒作攻击有研究,但更好的方法是使用一种完全不容易被操纵系统的全新方法。
这就是基于用户的协同过滤。再次强调,这是一个简单的概念-你根据用户的行为相似性来推荐东西,推荐那些你还没有看过但与你喜欢的东西相似的东西。正如我们所讨论的,这也有其局限性。因此,让我们来谈谈用一种称为基于物品的协同过滤的技术来颠覆整个概念。
基于物品的协同过滤
现在让我们尝试用一种称为基于物品的协同过滤的技术来解决基于用户的协同过滤的一些缺点,我们将看到这种技术是如何更加强大的。实际上,这是亚马逊在幕后使用的技术之一,他们公开谈论过这一点,所以我可以告诉你这么多,但让我们看看为什么这是一个如此好的主意。基于用户的协同过滤是基于人与人之间的关系来进行推荐的,但如果我们将其转变为基于物品之间的关系呢?这就是基于物品的协同过滤。
理解基于物品的协同过滤
这将涉及到一些见解。首先,我们谈到人们喜新厌旧,他们的口味会随时间改变,因此基于他们的过去行为来比较一个人和另一个人变得非常复杂。人们有不同的阶段,他们有不同的兴趣,你可能不会将处于相同阶段的人进行比较。但是,物品永远是什么它是的。一部电影永远是一部电影,它永远不会改变。星球大战永远是星球大战,至少在乔治·卢卡斯稍微改动一下之前是这样的,但总的来说,物品不会像人一样改变。因此,我们知道这些关系更加持久,而且在计算物品之间的相似性时可以进行更直接的比较,因为它们随时间不会改变。
另一个优势是,通常你要推荐的东西比你要推荐给的人要少。所以,全世界有 70 亿人,你的网站上可能并不会有 70 亿个推荐的东西,所以通过评估物品之间的关系而不是用户之间的关系,你可以节省大量的计算资源,因为你的系统中物品的数量可能比用户的数量要少。这意味着你可以更频繁地运行推荐,使它们更加及时、更加更新、更好!你可以使用更复杂的算法,因为你需要计算的关系更少,这是一件好事!
操纵系统也更难。我们谈到了通过创建一些喜欢流行东西的假用户然后推广你想要推广的东西来操纵基于用户的协同过滤方法是多么容易。但是基于物品的协同过滤变得更加困难。你必须让系统相信物品之间存在关系,而且由于你可能没有能力根据许多其他用户创建假物品并与其他物品建立虚假关系,操纵基于物品的协同过滤系统就变得更加困难,这是一件好事。
当我谈到操纵系统时,另一件重要的事情是确保人们用自己的钱投票。避免刷单攻击或人们试图操纵你的推荐系统的一般技术是确保信号行为是基于人们实际花钱的。因此,当你基于人们实际购买的东西而不是他们浏览或点击的东西进行推荐时,你总是会得到更好、更可靠的结果,明白吗?
基于物品的协同过滤是如何工作的?
好了,让我们来谈谈基于物品的协同过滤是如何工作的。它与基于用户的协同过滤非常相似,但我们不是看用户,而是看物品。
所以,让我们回到电影推荐的例子。我们首先要做的是找到每一对被同一个人观看的电影。然后,我们测量所有观看过这部电影的人之间的相似性。通过这种方式,我们可以根据观看过这两部电影的人的评分来计算两部不同电影之间的相似性。
所以,假设我有一对电影,好吧?也许是《星球大战》和《帝国反击战》。我找到了所有观看过这两部电影的人的名单,然后我比较他们对这两部电影的评分,如果他们相似,那么我可以说这两部电影是相似的,因为观看过它们的人对它们的评分相似。这是这里的一般想法。这是一种方法,有多种方法可以做到!
然后我可以按电影对一切进行排序,然后按相似电影的相似度强度进行排序,这就是我得到的喜欢这个也喜欢那个或给这个高评分的人也给这个高评分等等的结果。就像我说的,这只是一种方法。
这是基于物品的协同过滤的第一步-首先我根据观看每一对电影的人之间的关系来找到电影之间的关系。当我们通过以下示例时,这将更加清晰:
(图片)
例如,让我们假设在上图中的这位年轻女士观看了《星球大战》和《帝国反击战》,并且喜欢这两部电影,所以给了它们五星或者其他什么评分。现在,又来了一个叫 Edgy Mohawk Man 的人,他也观看了《星球大战》和《帝国反击战》,并且也喜欢这两部电影。所以,此时我们可以说这两部电影之间存在关系,基于这两位喜欢这两部电影的用户。
我们要做的是查看每一对电影。我们有一对《星球大战》和《帝国反击战》,然后我们查看所有观看这两部电影的用户,这两个人,如果他们都喜欢这两部电影,那么我们可以说它们彼此相似。或者,如果他们都不喜欢,我们也可以说它们彼此相似,对吧?所以,我们只是在查看这两个用户与这对电影的相似度得分。
然后来了一个留着小胡子的伐木工艺师,他看了《帝国反击战》,他生活在一个奇怪的世界,他看了《帝国反击战》,但不知道《星球大战》这部第一部电影的存在。
好吧,我们根据这两个人的行为计算了《帝国反击战》和《星球大战》之间的关系,所以我们知道这两部电影彼此相似。因此,鉴于小胡子先生喜欢《帝国反击战》,我们可以有信心地说他也会喜欢《星球大战》,然后我们可以将这部电影推荐给他作为他的首选电影推荐。就像下面的插图一样:
你可以看到最终结果非常相似,但我们已经颠覆了整个事情的本质。所以,我们不再把系统的重点放在人与人之间的关系上,而是放在物品之间的关系上,而这些关系仍然是基于所有观看它们的人的集体行为。但从根本上讲,我们正在研究物品之间的关系,而不是人与人之间的关系。明白了吗?
使用 Python 进行协同过滤
好了,让我们开始吧!我们有一些 Python 代码,将使用 Pandas 和我们可以使用的各种其他工具,用非常少的代码创建电影推荐。
我们要做的第一件事是向你展示基于物品的协同过滤的实践。所以,我们将建立“看过这个电影的人也看过”的关系,基本上就是“喜欢某些东西的人也喜欢这个东西”,所以我们将基于我们从 MovieLens 项目中获得的真实数据来构建这些电影之间的关系。所以,如果你去 MovieLens.org,那里实际上有一个开放的电影推荐系统,人们可以对电影进行评分,并获得新电影的推荐。
他们将所有的基础数据公开供研究人员使用。因此,我们将使用一些真实的电影评分数据-它有点过时,大约是 10 年前的,所以请记住这一点,但这是我们最终要使用的真实行为数据。我们将使用这些数据来计算电影之间的相似性。这些数据本身就很有用。你可以使用这些数据来说“喜欢这部电影的人也喜欢……”。所以,假设我正在查看一部电影的网页。系统可以说:“如果你喜欢这部电影,并且考虑到你正在查看它,你可能也会喜欢这些电影。”即使我们不知道你是谁,这就是一种推荐系统的形式。
现在,这是真实世界的数据,所以我们将遇到一些真实世界的问题。我们最初的结果看起来不太好,所以我们将花一点额外的时间来尝试弄清楚原因,这正是作为数据科学家所花费时间的很大一部分-纠正这些问题,然后重新运行,直到得到有意义的结果。
最后,我们将完全进行基于物品的协同过滤,根据个人的行为向他们推荐电影。所以,让我们开始吧!
寻找电影的相似性
让我们应用基于物品的协同过滤的概念。首先,找出电影之间的相似性-找出哪些电影与其他电影相似。特别是,我们将尝试找出哪些电影与星球大战相似,基于用户评分数据,我们将看看我们得到了什么。让我们深入研究一下!
好的,让我们继续计算基于物品的协同过滤的前半部分,即找到物品之间的相似性。下载并打开SimilarMovies.ipynb
文件。
在这种情况下,我们将根据用户行为来查看电影之间的相似性。我们将使用 GroupLens 项目的一些真实电影评分数据。GroupLens.org 提供了真实的电影评分数据,由真正使用MovieLens.org网站给电影评分并获得推荐的人提供。
我们已经在课程资料中包含了您需要的 GroupLens 数据集的数据文件,我们需要做的第一件事是将其导入 Pandas DataFrame 中,我们将在这个示例中真正看到 Pandas 的全部功能。这很酷!
理解代码
我们要做的第一件事是导入 MovieLens 数据集中的u.data
文件,这是一个包含数据集中每个评分的制表符分隔文件。
import pandas as pd
r_cols = ['user_id', 'movie_id', 'rating']
ratings = pd.read_csv('e:/sundog-consult/packt/datascience/ml-100k/u.data',
sep='\\t', names=r_cols, usecols=range(3))
请注意,您需要在这里添加路径,指向您在计算机上存储下载的 MovieLens 文件的位置。因此,即使我们在 Pandas 上调用read_csv
,我们也可以指定一个不同于逗号的分隔符。在这种情况下,它是一个制表符。
我们基本上是在说,从u.data
文件中取前三列,并将其导入一个新的 DataFrame,有三列:user_id
,movie_id
和rating
。
我们最终得到的是一个 DataFrame,对于每个user_id
,都有一行,用于标识某个人,然后,对于他们评价的每部电影,我们有movie_id
,这是给定电影的一些数字缩写,因此星球大战可能是第 53 部电影之类的,以及他们的评分,1 到 5 星。因此,我们在这里有一个数据库,一个 DataFrame,包含每个用户和他们评价的每部电影,好吗?
现在,我们希望能够使用电影标题,这样我们可以更直观地解释这些结果,所以我们将使用它们的可读名称。
如果您使用的是真正庞大的数据集,您会将其保存到最后,因为您希望尽可能长时间地使用数字,它们更紧凑。不过,出于示例和教学的目的,我们将保留标题,这样您就可以看到发生了什么。
m_cols = ['movie_id', 'title']
movies = pd.read_csv('e:/sundog-consult/packt/datascience/ml-100k/u.item',
sep='|', names=m_cols, usecols=range(2))
MovieLens 数据集中有一个名为u.item
的单独数据文件,它是以管道分隔的,我们导入的前两列将是movie_id
和该电影的title
。因此,现在我们有两个 DataFrame:r_cols
包含所有用户评分,m_cols
包含每个movie_id
的所有标题。然后,我们可以使用 Pandas 中神奇的merge
函数将它们全部合并在一起。
ratings = pd.merge(movies, ratings)
让我们添加一个ratings.head()
命令,然后运行这些单元格。我们最终得到的是类似以下表格的东西。那很快!
我们最终得到了一个新的 DataFrame,其中包含每个用户评价的user_id
和电影评分,并且我们既有movie_id
又有title
,可以阅读并了解它是什么。因此,读取这个数据的方式是user_id
号308
给Toy Story (1995)
电影评了4
星,user_id
号287
给Toy Story (1995)
电影评了5
星,依此类推。如果我们继续查看更多的这个 DataFrame,我们会看到不同的电影的不同评分。
现在 Pandas 的真正魔力显现出来了。因此,我们真正想要的是根据观看每对电影的所有用户之间的关系来查看电影之间的关系,因此最终我们需要一个包含每部电影、每个用户以及每个用户对每部电影的所有评分的矩阵。Pandas 中的pivot_table
命令可以为我们做到这一点。它基本上可以根据给定的 DataFrame 构建一个新表,几乎任何你想要的方式。为此,我们可以使用以下代码:
movieRatings = ratings.pivot_table(index=['user_id'],
columns=['title'],values='rating')
movieRatings.head()
所以,这段代码的意思是-取出我们的评分 DataFrame 并创建一个名为movieRatings
的新 DataFrame,我们希望它的索引是用户 ID,所以我们将为每个user_id
有一行,并且我们将每一列都是电影标题。因此,我们将为在该 DataFrame 中遇到的每个标题都有一列,并且如果存在的话,每个单元格将包含rating
值。让我们继续运行它。
然后,我们得到了一个新的 DataFrame,看起来像下表:
这真是太神奇了,现在你会看到一些NaN
值,代表不是一个数字,这就是 Pandas 表示缺失值的方式。因此,解释这个的方法是,例如,user_id
编号1
没有观看电影1-900(1994)
,但user_id
编号1
观看了《101 斑点狗》(1996)并给了它2
星的评价。user_id
编号1
还观看了《愤怒的公牛》(1957)并给了它5
星的评价,但没有观看电影《2 天在山谷(1996)》,例如,明白了吗?因此,我们最终得到的是一个稀疏矩阵,其中包含了每个用户和每部电影,以及每个用户对每部电影的评分值。
所以,现在你可以看到,我们可以非常容易地提取出用户观看的每部电影的向量,也可以提取出每个评价了给定电影的用户的向量,这正是我们想要的。所以,这对基于用户和基于物品的协同过滤都很有用,对吧?如果我想要找到用户之间的关系,我可以查看这些用户行之间的相关性,但如果我想要找到电影之间的相关性,对于基于物品的协同过滤,我可以根据用户行为查看列之间的相关性。这就是真正颠覆用户与基于物品相似性的实现的地方。
现在,我们要进行基于物品的协同过滤,所以我们要提取列,为此让我们运行以下代码:
starWarsRatings = movieRatings['Star Wars (1977)']
starWarsRatings.head()
现在,借助这个,让我们继续提取所有评价了《星球大战(1977)》的用户:
我们可以看到大多数人实际上都观看并评价了《星球大战(1977)》,并且每个人都喜欢它,至少在我们从 DataFrame 的开头取出的这个小样本中是这样。因此,我们得到了一组用户 ID 及其对《星球大战(1977)》的评分。用户 ID3
没有对《星球大战(1977)》进行评分,因此我们有一个NaN
值,表示那里有一个缺失值,但没关系。我们希望确保保留这些缺失值,以便我们可以直接比较不同电影的列。那么我们该如何做呢?
corrwith 函数
好吧,Pandas 一直让我们很容易,它有一个corrwith
函数,你可以在下面的代码中看到,我们可以使用它:
similarMovies = movieRatings.corrwith(starWarsRatings)
similarMovies = similarMovies.dropna()
df = pd.DataFrame(similarMovies)
df.head(10)
该代码将对给定的列与 DataFrame 中的每一列进行相关性计算,并计算相关性得分并将其返回给我们。所以,我们在这里做的是在整个movieRatings
DataFrame 上使用corrwith
,这是用户电影评分的整个矩阵,将其与starWarsRatings
列进行相关性计算,然后使用dropna
删除所有缺失的结果。这样我们就只剩下了有相关性的项目,有多于一个人观看的项目,然后我们基于这些结果创建一个新的 DataFrame,然后显示前 10 个结果。所以,再次回顾一下:
-
我们将建立《星球大战》与每部其他电影之间的相关性得分。
-
删除所有的
NaN
值,这样我们只有实际存在的电影相似性,有多于一个人对其进行了评分。 -
然后,我们将从结果中构建一个新的 DataFrame,并查看前 10 个结果。
在下面的截图中,我们看到了结果:
我们得到了《星球大战》与每部电影之间的相关性得分结果,例如,与电影《直到有了你(1997)》有惊人的高相关性得分,与电影《1-900(1994)》有负相关性,与《101 斑点狗(1996)》有非常弱的相关性。
现在,我们只需要按相似性得分排序,我们就可以得到《星球大战》的前十个电影相似性了,对吧?让我们继续做吧。
similarMovies.sort_values(ascending=False)
只需在生成的 DataFrame 上调用sort_values
,Pandas 使这变得非常容易,我们可以说ascending=False
,实际上按相关性得分的倒序排序。所以,让我们这样做:
好吧,《星球大战(1977)》排名靠前,因为它与自身相似,但其他的是什么?这是怎么回事?我们可以在前面的输出中看到一些电影,比如:《全速前进(1996)》、《年度人物(1995)》、《亡命之徒(1943)》。这些都是,你知道的,相当晦涩的电影,其中大多数我甚至从未听说过,但它们与《星球大战》有完美的相关性。这有点奇怪!显然我们在这里做错了什么。可能是什么呢?
事实证明,有一个完全合理的解释,这是一个很好的教训,为什么你在完成任何数据科学任务时总是需要检查你的结果-质疑结果,因为通常会有一些你忽略的东西,可能需要清理数据,可能你做错了什么。但你也应该怀疑地看待你的结果,不要只是盲目接受,好吗?如果你这样做,你会惹麻烦的,因为如果我真的把这些作为喜欢《星球大战》的人的推荐,我会被解雇的。不要被解雇!注意你的结果!所以,让我们深入研究下一节中出现的问题。
改进电影相似性的结果
让我们弄清楚我们的电影相似性出了什么问题。我们经历了所有这些令人兴奋的工作,计算了基于用户评分向量的电影之间的相关性得分,但我们得到的结果有点糟糕。只是为了提醒你,我们使用了这种技术寻找与《星球大战》相似的电影,结果我们得到了一堆怪异的推荐,排在前面的电影与《星球大战》有完美的相关性。
大多数都是非常晦涩的电影。那么,你认为可能发生了什么?嗯,可能有一个讲得通的解释,假设我们有很多人观看了《星球大战》和其他一些晦涩的电影。我们最终会得到这两部电影之间的很好的相关性,因为它们都与《星球大战》联系在一起,但归根结底,我们真的想要基于观看某些晦涩电影的一两个人的行为来做推荐吗?
可能不是!我的意思是,是的,世界上的两个人,或者无论是什么,看了电影《全速前进》,并且都喜欢它,除了《星球大战》,也许这对他们来说是一个很好的推荐,但对世界其他人来说可能不是一个很好的推荐。我们需要对相似性有一定的信心水平,通过强制执行观看给定电影的人数的最低限制来实现。我们不能仅仅基于一两个人的行为来判断一部电影是否好看。
因此,让我们尝试将这一见解付诸行动,使用以下代码:
import numpy as np
movieStats = ratings.groupby('title').agg({'rating': [np.size, np.mean]})
movieStats.head()
我们要做的是尝试识别那些实际上没有被很多人评价的电影,然后我们将它们排除,看看我们会得到什么。因此,为了做到这一点,我们将取得我们原始的评分 DataFrame,并且我们将说groupby('title')
,同样 Pandas 在其中有各种魔法。这将基本上构建一个新的 DataFrame,将给定标题的所有行聚合成一行。
我们可以说,我们想要特别聚合评分,并且我们想要显示每部电影的大小,即每部电影的评分人数,以及平均平均分数,即该电影的平均评分。因此,当我们这样做时,我们最终得到类似以下的东西:
例如,这告诉我们电影《101 斑点狗(1996 年)》有 109 人评价了这部电影,他们的平均评分是 2.9 颗星,所以实际上并不是很高的分数!因此,如果我们仅凭眼力观察这些数据,我们可以说好吧,我认为比较不知名的电影,比如《187(1997 年)》,有 41 个评分,但《101 斑点狗(1996 年)》,我听说过,你知道《愤怒的公牛(1957 年)》,我也听说过。似乎在大约 100 个评分处有一种自然的截止值,也许这是一个魔法值,事情开始变得有意义。
让我们继续摆脱少于 100 人评分的电影,是的,你知道我在这一点上有点凭直觉。正如我们稍后将讨论的,有更有原则的方法来做到这一点,你实际上可以进行实验,并在不同的阈值上进行训练/测试实验,找到实际表现最好的那个。但最初,让我们只是用常识来过滤掉少于 100 人评分的电影。同样,Pandas 使这变得非常容易。让我们通过以下示例来弄清楚:
popularMovies = movieStats['rating']['size'] >= 100
movieStats[popularMovies].sort_values([('rating', 'mean')], ascending=False)[:15]
我们可以说popularMovies
,一个新的 DataFrame,将通过查看movieStats
构建,我们只会取评分大小大于或等于 100 的行,然后我将按mean
评分排序,只是为了好玩,看看最受欢迎的广泛观看的电影。
我们这里有一份由 100 多人评分的电影列表,按其平均评分分数排序,这本身就是一个推荐系统。这些都是受欢迎的高评分电影。《剃须刀奇遇记(1995 年)》,显然是一部非常好的电影,很多人看过并且非常喜欢。
因此,这是一个非常古老的数据集,来自 90 年代末,所以即使你可能不熟悉电影《剃须刀奇遇记(1995 年)》,回头去重新发现它可能是值得的;把它加入你的 Netflix!《辛德勒的名单(1993 年)》并不是一个大惊喜,在大多数顶级电影列表中都会出现。《错误的裤子(1993 年)》,另一个例子,是一部不知名的电影,显然非常好看,也很受欢迎。因此,通过这样做,已经有一些有趣的发现了。
现在情况看起来好多了,所以让我们继续制作我们的新 DataFrame,其中包含与《星球大战》相似的电影,我们只基于出现在这个新 DataFrame 中的电影。所以,我们将使用join
操作,将我们原始的similarMovies
DataFrame 与这个只有超过 100 个评分的电影的新 DataFrame 进行连接,好吗?
df = movieStats[popularMovies].join(pd.DataFrame(similarMovies, columns=['similarity']))
df.head()
在这段代码中,我们基于similarMovies
创建了一个新的 DataFrame,从中提取了similarity
列,将其与我们的movieStats
DataFrame(即我们的popularMovies
DataFrame)进行了连接,并查看了合并的结果。然后,我们就有了输出!
现在,我们只限制了那些被 100 多人评价的电影,与《星球大战》的相似度得分。所以,现在我们需要做的就是使用以下代码对其进行排序:
df.sort_values(['similarity'], ascending=False)[:15]
在这里,我们将对其进行逆向排序,并只查看前 15 个结果。如果你现在运行它,你应该会看到以下内容:
情况开始好转了!《星球大战》(1977)因为与自己相似,所以排在第一位,《帝国反击战》(1980)排在第二位,《绝地归来》(1983)排在第三位,《夺宝奇兵》(1981)排在第四位。你知道,它还不完美,但这些更有意义,对吧?所以,你会期望原始三部曲的三部《星球大战》电影相互之间相似,这些数据还是在下一部三部曲之前,而《夺宝奇兵》(1981)也是一部风格非常相似的电影,排在第四位。所以,我对这些结果开始感到有点满意。还有改进的空间,但嘿!我们得到了一些有意义的结果,哇呜!
现在,理想情况下,我们还应该过滤掉《星球大战》,你不想看到与你开始的电影本身的相似性,但我们以后再担心这个!所以,如果你想再玩一下,就像我说的,100 是最低评分的一个任意截止点。如果你确实想尝试不同的截止值,我鼓励你回去尝试一下。看看它对结果有什么影响。你知道,在前面的表中,我们真正喜欢的结果实际上有更多的共同评分超过 100。所以,我们最终得到了《奥斯汀·鲍尔的国际人质》(1997)的评分相当高,只有 130 个评分,所以也许 100 还不够高!《木偶奇遇记》(1940)以 101 分进入,与《星球大战》不太相似,所以,你可能需要考虑更高的阈值,看看它会有什么影响。
请记住,这是一个非常小的、用于实验目的的有限数据集,它基于非常旧的数据,所以你只会看到较旧的电影。因此,从直觉上解释这些结果可能会有点具有挑战性,但结果并不差。
现在让我们继续,实际上进行全面的基于物品的协同过滤,通过使用更完整的系统向人们推荐电影,我们将在下一步中进行。
向人们推荐电影
好的,让我们实际构建一个完整的推荐系统,它可以查看系统中每个人的所有行为信息,以及他们评价的电影,并利用这些信息为数据集中的任何用户实际生成最佳推荐电影。这有点令人惊讶,你会对它有多简单感到惊讶。让我们开始吧!
让我们开始使用ItemBasedCF.ipynb
文件,首先导入我们拥有的 MovieLens 数据集。同样,我们现在只使用其中包含 10 万个评分的子集。但是,你可以从 GroupLens.org 获得更大的数据集-高达数百万个评分;如果你愿意的话。但是请记住,当你开始处理真正大的数据时,你将会推动单台机器和 Pandas 所能处理的极限。话不多说,这是第一段代码:
import pandas as pd
r_cols = ['user_id', 'movie_id', 'rating']
ratings = pd.read_csv('e:/sundog-consult/packt/datascience/ml-100k/u.data',
sep='\t', names=r_cols, usecols=range(3))
m_cols = ['movie_id', 'title']
movies = pd.read_csv('e:/sundog-consult/packt/datascience/ml-100k/u.item',
sep='|', names=m_cols, usecols=range(2))
ratings = pd.merge(movies, ratings)
ratings.head()
就像之前一样,我们将导入包含每个用户的所有个人评分以及他们评分的电影的u.data
文件,然后将其与电影标题联系起来,这样我们就不必只使用数字电影 ID。点击运行单元格按钮,我们得到以下 DataFrame。
例如,user_id
编号308
给玩具总动员(1995)
评了 4 星,user_id
编号66
给玩具总动员(1995)
评了 3 星。而且,这将包含每个用户对每部电影的每个评分。
然后,就像之前一样,我们使用 Pandas 中的pivot_table
命令来基于信息构建一个新的 DataFrame:
userRatings = ratings.pivot_table(index=['user_id'],
columns=['title'],values='rating')
userRatings.head()
在这里,每行是user_id
,列由数据集中所有独特的电影标题组成,每个单元格包含一个评分:
我们得到的是前面输出中显示的非常有用的矩阵,其中每行都有用户,每列都有电影。而且我们在这个矩阵中基本上有每部电影的每个用户评分。例如,user_id
编号1
给101 斑点狗(1996)
评了 2 星。而且,所有这些NaN
值代表缺失的数据。这只是表示,例如,user_id
编号1
没有对电影1-900(1994)
进行评分。
这是一个非常有用的矩阵。如果我们正在进行基于用户的协同过滤,我们可以计算每个单独用户评分向量之间的相关性以找到相似的用户。由于我们正在进行基于物品的协同过滤,我们更感兴趣的是列之间的关系。因此,例如,计算任意两列之间的相关性分数,这将为给定电影对给出相关性分数。那么,我们该如何做呢?事实证明,Pandas 也使这变得非常容易。
它有一个内置的corr
函数,实际上会计算整个矩阵中找到的每一对列的相关性分数-这几乎就像它们在为我们考虑。
corrMatrix = userRatings.corr()
corrMatrix.head()
让我们继续运行前面的代码。这是一个计算量相当大的事情,所以实际上需要一些时间才能得出结果。但是,我们得到了!
那么,在前面的输出中我们有什么?我们在这里有一个新的 DataFrame,其中每部电影都在行上,列中。因此,我们可以查看任意两部电影的交集,并根据我们最初拥有的userRatings
数据找到它们之间的相关性分数。这有多酷呢?例如,电影101 斑点狗(1996)
与自己完全相关,因为它具有相同的用户评分向量。但是,如果你看看101 斑点狗(1996)
电影与十二怒汉(1957)
电影的关系,它的相关性分数要低得多,因为这些电影相当不相似,这是有道理的,对吧?
现在我有了一个很棒的矩阵,可以给出任意两部电影之间的相似度分数。这有点令人惊讶,并且对我们即将要做的事情非常有用。就像之前一样,我们必须处理虚假的结果。所以,我不想看到基于少量行为信息的关系。
原来 Pandas 的corr
函数实际上有一些参数可以给它。其中一个是你想要使用的实际相关性评分方法,所以我要说使用pearson
相关性。
corrMatrix = userRatings.corr(method='pearson', min_periods=100)
corrMatrix.head()
你会注意到它还有一个min_periods
参数,你可以给它,基本上是说我只想要你考虑至少,例如在这个例子中,有 100 人评分过两部电影的相关性评分。运行这个将消除那些只基于少数人的虚假关系。运行代码后得到的矩阵如下:
这与我们在项目相似性练习中所做的有点不同,那里我们只是扔掉了少于 100 人评分的任何电影。我们在这里所做的是,扔掉了少于 100 人评分两部电影的电影相似性,好吗?所以,你可以看到在前面的矩阵中我们有更多的NaN
值。
实际上,甚至与自己相似的电影也被排除了,所以例如,电影1-900 (1994)
,据推测,被少于 100 人观看,所以它被完全抛弃了。然而,电影101 斑点狗 (1996)
以相关性评分1
幸存下来,而在这个数据集的这个小样本中,没有一部电影与另一部有 100 个共同观看的人不同。但是,有足够多的电影幸存下来以获得有意义的结果。
通过示例了解电影推荐
那么,我们用这些数据做什么呢?嗯,我们想要为人们推荐电影。我们这样做的方式是,我们查看给定人的所有评分,找到与他们评分相似的电影,这些电影就是向该人推荐的候选电影。
让我们从创建一个虚拟人来为其创建推荐开始。我实际上已经手动添加了一个虚拟用户,ID 号为0
,到我们正在处理的 MovieLens 数据集中。你可以用以下代码看到该用户:
myRatings = userRatings.loc[0].dropna()
myRatings
这给出了以下输出:
这有点像我这样的人,我喜欢《星球大战》和《帝国反击战》,但讨厌《飘》。所以,这代表着一个真正喜欢《星球大战》的人,但不喜欢老式的浪漫戏剧,好吗?所以,我给《帝国反击战 (1980)》和《星球大战 (1977)》评了5
星,给《飘 (1939)》评了1
星。所以,我要为这个虚构的用户找到推荐。
那么,我怎么做呢?嗯,让我们从创建一个名为simCandidates
的系列开始,我将浏览我评分的每一部电影。
simCandidates = pd.Series()
for i in range(0, len(myRatings.index)):
print "Adding sims for " + myRatings.index[i] + "..."
# Retrieve similar movies to this one that I rated
sims = corrMatrix[myRatings.index[i]].dropna()
# Now scale its similarity by how well I rated this movie
sims = sims.map(lambda x: x * myRatings[i])
# Add the score to the list of similarity candidates
simCandidates = simCandidates.append(sims)
#Glance at our results so far:
print "sorting..."
simCandidates.sort_values(inplace = True, ascending = False)
print simCandidates.head(10)
对于i
在范围0
到我在myRatings
中拥有的评分数量,我将把我评分的相似电影加起来。所以,我将拿那个corrMatrix
DataFrame,那个神奇的包含所有电影相似性的,然后我将用myRatings
创建一个相关性矩阵,删除任何缺失值,然后我将按我对那部电影的评分来缩放结果的相关性评分。
这里的想法是,我将浏览例如《帝国反击战》的所有相似之处,然后将其全部缩放 5 倍,因为我真的很喜欢《帝国反击战》。但是,当我浏览《飘》的相似之处时,我只会将其缩放 1 倍,因为我不喜欢《飘》。所以,这将使与我喜欢的电影相似的电影更有力量,而与我不喜欢的电影相似的电影则更弱一些,好吗?
所以,我只是浏览并建立了这个相似候选列表,如果你愿意的话,就是推荐候选,对结果进行排序并打印出来。让我们看看我们得到了什么:
嘿,这些看起来不错,对吧?显然,《帝国反击战》(1980)和《星球大战》(1977)排在前面,因为我明确喜欢这些电影,我已经看过并评分了。但是,排在榜单前列的还有《绝地归来》(1983),这是我们预料到的,《亚马逊探险记》(1981)也是。
让我们开始进一步完善这些结果。我们发现我们得到了重复的值。如果有一部电影与我评分的多部电影相似,它将在结果中出现多次,所以我们希望将它们合并在一起。如果我确实有相同的电影,也许应该将它们加在一起,形成一个更强大的推荐分数。例如,《绝地归来》实际上与《星球大战》和《帝国反击战》都很相似。我们该怎么做呢?
使用 groupby 命令来合并行
我们将继续探索。我们将再次使用groupby
命令来将所有属于同一部电影的行分组在一起。接下来,我们将总结它们的相关分数并查看结果:
simCandidates = simCandidates.groupby(simCandidates.index).sum()
simCandidates.sort_values(inplace = True, ascending = False)
simCandidates.head(10)
以下是结果:
嘿,这看起来真的很不错!
所以,《绝地归来》(1983)得分最高,得分为 7,紧随其后的是《亚马逊探险记》(1981),得分为 5,然后我们开始看到《印第安纳琼斯:最后的十字军东征》(1989)和一些其他电影,《桂河大桥》(1957),《回到未来》(1985),《刺激》(1973)。这些都是我真的会喜欢看的电影!你知道,我其实也喜欢老式的迪士尼电影,所以《灰姑娘》(1950)并不像看起来那么疯狂。
我们需要做的最后一件事是过滤掉我已经评分过的电影,因为推荐你已经看过的电影是没有意义的。
使用删除命令删除条目
所以,我可以使用以下代码快速删除任何出现在我的原始评分系列中的行:
filteredSims = simCandidates.drop(myRatings.index)
filteredSims.head(10)
运行这个命令让我看到最终的前 10 个结果:
就是这样!《绝地归来》(1983),《亚马逊探险记》(1981),《印第安纳琼斯:最后的十字军东征》(1989),这些都是我虚构用户的前几个推荐结果,而且都很合理。我看到了一些适合家庭观看的电影,你知道,《灰姑娘》(1950),《绿野仙踪》(1939),《小飞象》(1941),可能是因为《飘》的存在,即使它的权重被降低了,但它仍然在其中,仍然被计算在内。所以,这就是我们的结果。就是这样!挺酷的!
实际上我们已经为特定用户生成了推荐,我们可以为数据框中的任何用户这样做。所以,如果你愿意的话,可以尝试一下。我还想谈谈你如何更深入地参与其中,玩弄这些结果;试着改进它们。
这其实是一门艺术,你知道,你需要不断迭代,尝试不同的想法和不同的技术,直到你得到越来越好的结果,你可以一直这样做。我的意思是,我把整个职业都建立在这个基础上。所以,我不指望你像我一样花 10 年的时间来完善这个,但是有一些简单的事情你可以做,所以让我们谈谈这个。
改进推荐结果
作为一个练习,我想挑战你去让这些推荐变得更好。所以,让我们谈谈我有的一些想法,也许你也有一些自己的想法,可以尝试和实验一下;动手尝试,努力做出更好的电影推荐。
好吧,这些推荐结果仍然有很大的改进空间。我们在如何根据你对物品的评分来权衡不同的推荐结果,或者你想要为两部给定电影评分的人数选择最低阈值等方面做出了很多决定。所以,有很多事情你可以调整,很多不同的算法你可以尝试,你可以尝试通过系统来做出更好的电影推荐。所以,如果你感兴趣,我挑战你去做到这一点!
以下是一些关于如何实际上尝试改进本章结果的想法。首先,你可以直接去玩ItembasedCF.ipynb
文件并对其进行调整。例如,我们发现相关性方法实际上有一些相关性计算的参数,我们在示例中使用了 Pearson,但还有其他方法可以查找和尝试,看看它对你的结果有什么影响。我们使用了最小周期值为 100,也许这个值太高了,也许太低了;我们只是随意选择的。如果你调整这个值会发生什么?例如,如果你将它降低,我预计你会看到一些你从未听说过的新电影,但可能仍然是对那个人的一个很好的推荐。或者,如果你将它提高,你会看到,你知道,只有大片。
有时候你必须考虑一下你想从推荐系统中得到什么结果。在向人们展示他们听说过的电影和他们没听说过的电影之间,是否有一个很好的平衡?对于这些人来说,发现新电影有多重要,与通过看到许多他们听说过的电影来对推荐系统产生信心有多重要?所以,这确实是一种艺术。
我们还可以改进一下,因为我们在结果中看到了很多与《飘》相似的电影,尽管我不喜欢《飘》。你知道,我们将这些结果的权重低于我喜欢的电影的相似性,但也许这些电影实际上应该受到惩罚。如果我那么讨厌《飘》,也许与《飘》相似的电影,比如《绿野仙踪》,实际上应该受到惩罚,你知道,它们的得分应该降低而不是提高。
这是另一个简单的修改,你可以尝试一下。我们的用户评分数据集中可能有一些异常值,如果我把那些评价了大量电影的人排除掉会怎么样?也许他们在影响一切。你实际上可以尝试识别这些用户并将他们排除在外,这是另一个想法。而且,如果你真的想要一个大项目,如果你真的想要深入研究这些东西,你实际上可以通过使用训练/测试的技术来评估这个推荐引擎的结果。所以,如果不是使用每部电影的相关性得分的任意推荐得分,而是将其缩小到每部电影的预测评分,会怎么样呢?
如果我的推荐系统的输出是一部电影和我对那部电影的预测评分,在一个训练/测试系统中,我实际上可以尝试弄清楚我有多好地预测了用户实际上观看并评价过的电影?好吗?所以,我可以留出一些评分数据,看看我的推荐系统能够多好地预测用户对这些电影的评分。这将是一种定量和有原则的方法来衡量这个推荐引擎的误差。但是,这里比科学更多一点艺术。即使 Netflix 奖实际上使用了那个误差度量,称为均方根误差,这是他们特别使用的,但这真的是一个好的推荐系统的衡量标准吗?
基本上,你正在衡量你的推荐系统预测一个人已经观看的电影的能力。但是推荐引擎的目的不是推荐一个人尚未观看但可能会喜欢的电影吗?这是两回事。所以不幸的是,很难衡量你真正想要衡量的东西。有时,你确实必须凭直觉行事。而且,衡量推荐引擎结果的正确方式是衡量你试图通过它来推广的结果。
也许我试图让人们观看更多电影,或者更高评价新电影,或者购买更多东西。在真实网站上运行实际的控制实验将是优化的正确方式,而不是使用训练/测试。所以,你知道,我在那里详细介绍了一点,但教训是,你不能总是以黑白思维来考虑这些事情。有时,你不能直接和定量地衡量事物,你必须运用一点常识,这就是一个例子。
无论如何,这些是一些关于如何回头改进我们编写的推荐引擎结果的想法。所以,请随意尝试一下,看看你是否可以按照自己的意愿改进它,并且玩得开心。这实际上是书中非常有趣的部分,所以我希望你会喜欢它!
总结
所以,去尝试一下吧!看看你是否可以改进我们的初始结果。有一些简单的想法可以尝试使这些推荐更好,还有一些更复杂的想法。现在,没有对错答案;我不会要求你交作业,也不会审查你的工作。你知道,你决定玩弄它并熟悉一下,进行实验,看看你得到什么结果。这就是整个目的-只是让你更熟悉使用 Python 进行这种工作,并更熟悉基于物品的协同过滤背后的概念。
在本章中,我们看了不同的推荐系统-我们排除了基于用户的协同过滤系统,直接进入了基于物品的系统。然后,我们使用了 pandas 的各种函数来生成和完善我们的结果,我希望你在这里看到了 pandas 的强大之处。
在下一章中,我们将深入研究更高级的数据挖掘和机器学习技术,包括 K 最近邻算法。我期待着向你解释这些内容,并看看它们如何有用。
第七章:更多数据挖掘和机器学习技术
在这一章中,我们将讨论更多的数据挖掘和机器学习技术。我们将讨论一个称为k 最近邻居(KNN)的非常简单的技术。然后,我们将使用 KNN 来预测电影的评级。之后,我们将继续讨论降维和主成分分析。我们还将看一个 PCA 的例子,其中我们将 4D 数据降低到两个维度,同时仍保留其方差。
然后,我们将介绍数据仓库的概念,并了解新的 ELT 过程相对于 ETL 过程的优势。我们将学习强化学习的有趣概念,并了解智能吃豆人游戏的背后使用的技术。最后,我们将看到一些用于强化学习的花哨术语。
我们将涵盖以下主题:
-
K 最近邻居的概念
-
KNN 的实施以预测电影的评级
-
降维和主成分分析
-
鸢尾花数据集的 PCA 示例
-
数据仓库和 ETL 与 ELT
-
什么是强化学习
-
智能吃豆人游戏背后的工作
-
用于强化学习的花哨术语
K 最近邻居 - 概念
让我们谈谈雇主希望您了解的一些数据挖掘和机器学习技术。我们将从一个称为 KNN 的非常简单的技术开始。您会对一个好的监督式机器学习技术有多简单感到惊讶。让我们来看看!
KNN 听起来很花哨,但实际上是最简单的技术之一!假设您有一个散点图,并且可以计算该散点图上任意两点之间的距离。假设您已经对一堆数据进行了分类,可以从中训练系统。如果我有一个新的数据点,我只需根据该距离度量查看 KNN,并让它们全部对新点的分类进行投票。
让我们想象以下散点图正在绘制电影。方块代表科幻电影,三角形代表戏剧电影。我们将说这是根据评分与受欢迎程度绘制的,或者您可以想象其他任何东西:
在这里,我们有一种基于散点图上任意两点之间的评分和受欢迎程度计算的某种距离。假设有一个新点进来,一个我们不知道流派的新电影。我们可以将K设置为3,并取散点图上这一点的3个最近邻居;然后它们可以就新点/电影的分类进行投票。
您可以看到,如果我选择三个最近的邻居(K=3),我有 2 部戏剧电影和 1 部科幻电影。然后我会让它们全部投票,我们将根据这 3 个最近的邻居选择这个新点的戏剧分类。现在,如果我将这个圈扩大到包括 5 个最近的邻居,即K=5,我会得到一个不同的答案。在这种情况下,我挑选了 3 部科幻电影和 2 部戏剧电影。如果我让它们全部投票,我最终会得到一个新电影的科幻分类。
我们选择 K 可能非常重要。您要确保它足够小,以免走得太远并开始挑选无关的邻居,但它必须足够大,以包含足够的数据点以获得有意义的样本。因此,通常您将不得不使用训练/测试或类似的技术来实际确定给定数据集的K的正确值。但是,最终,您必须从直觉开始并从那里开始工作。
就是这么简单,就是这么简单。因此,这是一种非常简单的技术。您所做的就是在散点图上找到 k 个最近邻,让它们全部对分类进行投票。它确实符合监督学习,因为它使用一组已知点的训练数据,即已知的分类,来指导新点的分类。
但让我们对此做一些更复杂的事情,并且实际上根据它们的元数据玩弄电影。让我们看看是否可以实际上根据这些电影的内在值,例如其评分、类型信息,找出电影的最近邻:
理论上,我们可以使用 k 最近邻算法重新创建类似于观看此商品的客户还观看了(上图是亚马逊的截图)的东西。而且,我可以再进一步:一旦我根据 k 最近邻算法确定了与给定电影相似的电影,我可以让它们全部对预测的电影评分进行投票。
这就是我们下一个示例要做的。所以现在您已经了解了 KNN,k 最近邻的概念。让我们继续并将其应用于实际找到彼此相似的电影,并使用这些最近邻的电影来预测我们以前没有看过的电影的评分。
使用 KNN 来预测电影的评分
好了,我们将实际上采用 KNN 的简单思想,并将其应用于一个更复杂的问题,即仅根据其类型和评分信息预测电影的评分。因此,让我们深入研究并尝试仅基于 KNN 算法来预测电影评分,看看我们能得到什么。因此,如果您想跟着做,请打开KNN.ipynb
,您可以和我一起玩。
我们要做的是定义基于电影元数据的距离度量。通过元数据,我指的是仅与电影相关的信息,即与电影相关联的信息。具体来说,我们将查看电影的类型分类。
我们的MovieLens
数据集中的每部电影都有关于它所属类型的附加信息。一部电影可以属于多种类型,比如科幻、戏剧、喜剧或动画。我们还将查看电影的整体受欢迎程度,由评分人数给出,并且我们还知道每部电影的平均评分。我可以将所有这些信息结合在一起,基本上创建一个基于评分信息和类型信息的两部电影之间的距离度量。让我们看看我们得到了什么。
我们将再次使用 pandas 来简化生活,如果您跟着做,请确保将MovieLens
数据集的路径更改为您安装它的位置,这几乎肯定不是这个 Python 笔记本中的位置。
请继续进行更改,如果您想跟着做。与以前一样,我们将只导入实际的评分数据文件u.data
,使用 pandas 中的read_csv()
函数。我们将告诉它实际上是一个制表符分隔符而不是逗号。我们将导入前 3 列,这些列代表user_id
,movie_id
和评分,对于数据集中每个电影的评分:
import pandas as pd
r_cols = ['user_id', 'movie_id', 'rating']
ratings = pd.read_csv('C:\DataScience\ml-100k\u.data', sep='\t', names=r_cols, usecols=range(3))
ratings.head()ratings.head()
如果我们继续运行并查看顶部,我们可以看到它正在工作,输出应该如下所示:
我们最终得到一个具有user_id
,movie_id
和rating
的DataFrame
。例如,user_id 0
对movie_id 50
进行了评分,我相信这是《星球大战》,给了 5 颗星,依此类推。
我们接下来要做的是,为每部电影聚合评分信息。我们使用 pandas 中的groupby()
函数,实际上按movie_id
对所有内容进行分组。我们将合并每部电影的所有评分,并输出每部电影的评分数量和平均评分分数,即平均值:
movieProperties = ratings.groupby('movie_id').agg({'rating':
[np.size, np.mean]})
movieProperties.head()
让我们继续做这个 - 很快就会回来,以下是输出的样子:
这给我们另一个DataFrame
,告诉我们,例如,movie_id 1
有452个评分(这是它受欢迎程度的衡量,即有多少人实际观看并评分),以及平均评分为 3.8。因此,有452人观看了movie_id 1
,他们给出了平均评分为 3.87,这相当不错。
现在,评分的原始数量对我们来说并不那么有用。我的意思是,我不知道452是否意味着它受欢迎与否。因此,为了使其标准化,我们将基本上根据每部电影的最大和最小评分数量来衡量。我们可以使用lambda
函数来做到这一点。因此,我们可以以这种方式将函数应用于整个DataFrame
。
我们要做的是使用np.min()
和np.max()
函数来找到整个数据集中发现的最大评分数量和最小评分数量。因此,我们将找到最受欢迎的电影和最不受欢迎的电影,并将一切标准化到这个范围内:
movieNumRatings = pd.DataFrame(movieProperties['rating']['size'])
movieNormalizedNumRatings = movieNumRatings.apply(lambda x: (x - np.min(x)) / (np.max(x) - np.min(x)))
movieNormalizedNumRatings.head()
当我们运行它时,它给我们的是以下内容:
这基本上是每部电影的受欢迎程度的衡量,范围是 0 到 1。因此,这里的得分为 0 意味着没有人观看,这是最不受欢迎的电影,得分为1
意味着每个人都观看了,这是最受欢迎的电影,或者更具体地说,是最多人观看的电影。因此,我们现在有了一个可以用于我们的距离度量的电影受欢迎程度的衡量。
接下来,让我们提取一些一般信息。原来有一个u.item
文件,不仅包含电影名称,还包含每部电影所属的所有流派:
movieDict = {}
with open(r'c:/DataScience/ml-100k/u.item') as f:
temp = ''
for line in f:
fields = line.rstrip('\n').split('|')
movieID = int(fields[0])
name = fields[1]
genres = fields[5:25]
genres = map(int, genres)
movieDict[movieID] = (name, genres,
movieNormalizedNumRatings.loc[movieID].get('size'),movieProperties.loc[movieID].rating.get('mean'))
上面的代码实际上会遍历u.item
的每一行。我们正在以困难的方式做这个;我们没有使用任何 pandas 函数;这次我们将直接使用 Python。再次确保将路径更改为您安装此信息的位置。
接下来,我们打开我们的u.item
文件,然后逐行遍历文件中的每一行。我们去掉末尾的换行符,并根据该文件中的管道分隔符进行拆分。然后,我们提取movieID
,电影名称和所有单独的流派字段。因此,基本上在这个源数据中有 19 个不同字段中的一堆 0 和 1,其中每个字段代表一个给定的流派。最后,我们构建一个 Python 字典,将电影 ID 映射到它们的名称、流派,然后我们还将我们的评分信息折叠回去。因此,我们将得到名称、流派、受欢迎程度(在 0 到 1 的范围内)、以及平均评分。这段代码就是做这个的。让我们运行一下!并且,为了看看我们最终得到了什么,我们可以提取movie_id 1
的值:
movieDict[1]
以下是上述代码的输出:
我们字典中movie_id 1
的第一个条目恰好是《玩具总动员》,这是一部你可能听说过的 1995 年的皮克斯老电影。接下来是所有流派的列表,其中 0 表示它不属于该流派,1 表示它属于该流派。在MovieLens
数据集中有一个数据文件,可以告诉你这些流派字段实际对应的是什么。
对于我们的目的来说,这实际上并不重要,对吧?我们只是试图根据它们的流派来衡量电影之间的距离。数学上重要的是这个流派向量与另一部电影有多相似,好吗?实际的流派本身并不重要!我们只想看看两部电影在它们的流派分类上有多相同或不同。所以我们有那个流派列表,我们有我们计算的受欢迎程度分数,还有 Toy Story 的平均评分。好了,让我们继续想办法将所有这些信息结合到一个距离度量中,这样我们就可以找到 Toy Story 的 k 个最近邻居了。
我已经相当随意地计算了这个ComputeDistance()
函数,它接受两个电影 ID 并计算两者之间的距离分数。首先,我们将基于两个流派向量之间的相似性,使用余弦相似度度量来计算。就像我说的,我们将只是拿出每部电影的流派列表,看看它们彼此有多相似。再次强调,0
表示它不属于该流派,1
表示它属于该流派。
然后,我们将比较受欢迎程度分数,只取原始差异,这两个受欢迎程度分数之间的绝对值差异,并将其用于距离度量。然后,我们将仅使用这些信息来定义两部电影之间的距离。所以,例如,如果我们计算电影 ID 2 和 4 之间的距离,这个函数将返回一些仅基于该电影的受欢迎程度和这些电影的流派的距离函数。
现在,想象一下一个散点图,就像我们在前面的章节中看到的那样,其中一个轴可能是基于余弦度量的流派相似性的度量,另一个轴可能是受欢迎程度,好吗?我们只是在这两个事物之间找到距离:
from scipy import spatial
def ComputeDistance(a, b):
genresA = a[1]
genresB = b[1]
genreDistance = spatial.distance.cosine(genresA, genresB)
popularityA = a[2]
popularityB = b[2]
popularityDistance = abs(popularityA - popularityB)
return genreDistance + popularityDistance
ComputeDistance(movieDict[2], movieDict[4])
在这个例子中,我们试图使用我们的距离度量来计算电影 2 和 4 之间的距离,我们得到了一个 0.8 的分数:
记住,远距离意味着不相似,对吧?我们想要最近的邻居,距离最小。所以,0.8 的分数在 0 到 1 的范围内是一个相当高的数字。这告诉我这些电影实际上并不相似。让我们快速进行一次理智检查,看看这些电影实际上是什么:
print movieDict[2]
print movieDict[4]
结果是电影《黄金眼》和《短小的》这两部电影,它们是非常不同的电影:
你知道,你有詹姆斯·邦德动作冒险片,还有一部喜剧电影 - 完全不相似!它们在受欢迎程度上实际上是可比较的,但是流派的差异让它们不同。好了!那么,让我们把它全部整合在一起吧!
接下来,我们将写一小段代码来实际获取一些给定的电影 ID 并找到 KNN。所以,我们所要做的就是计算 Toy Story 和我们电影字典中的所有其他电影之间的距离,并根据它们的距离分数对结果进行排序。以下的代码片段就是这样做的。如果你想花点时间来理解一下,它其实非常简单。
我们有一个小小的getNeighbors()
函数,它将获取我们感兴趣的电影和我们想要找到的 K 个邻居。它将遍历我们拥有的每部电影;如果它实际上是一部不同于我们正在查看的电影,它将计算之前的距离分数,将其附加到我们的结果列表中,并对该结果进行排序。然后我们将挑选出前 K 个结果。
在这个例子中,我们将K设置为 10,找到 10 个最近的邻居。我们将使用getNeighbors()
找到 10 个最近的邻居,然后遍历所有这 10 个最近的邻居,并计算每个邻居的平均评分。这个平均评分将告诉我们对于所讨论的电影的评分预测。
作为一个副作用,我们还根据我们的距离函数得到了 10 个最近的邻居,我们可以称之为相似的电影。所以,这个信息本身是有用的。回到那个“观看此影片的顾客还观看了”这个例子,如果你想做一个类似的功能,它只是基于这个距离度量而不是实际的行为数据,这可能是一个合理的起点,对吧?
import operator
def getNeighbors(movieID, K):
distances = []
for movie in movieDict:
if (movie != movieID):
dist = ComputeDistance(movieDict[movieID],
movieDict[movie])
distances.append((movie, dist))
distances.sort(key=operator.itemgetter(1))
neighbors = []
for x in range(K):
neighbors.append(distances[x][0])
return neighbors
K = 10
avgRating = 0
neighbors = getNeighbors(1, K)
for neighbor in neighbors:
avgRating += movieDict[neighbor][3]
print movieDict[neighbor][0] + " " +
str(movieDict[neighbor][3])
avgRating /= float(K)
所以,让我们继续运行这个,看看我们得到了什么。以下是上述代码的输出:
结果并不那么不合理。所以,我们以《玩具总动员》这部电影为例,它是电影 ID 1,我们得到的前 10 个最近邻居,是一些相当不错的喜剧和儿童电影。所以,鉴于《玩具总动员》是一部受欢迎的喜剧和儿童电影,我们得到了一堆其他受欢迎的喜剧和儿童电影;所以,似乎是有效的!我们并没有使用一堆花哨的协同过滤算法,这些结果并不那么糟糕。
接下来,让我们使用 KNN 来预测评分,这里我们将评分视为这个例子中的分类:
avgRating
以下是上述代码的输出:
我们最终得到了一个预测评分为 3.34,实际上这与该电影的实际评分 3.87 并没有太大的不同。所以不是很好,但也不算太糟糕!我的意思是,实际上它的效果出奇的好,考虑到这个算法是多么简单!
活动
在这个例子中,大部分复杂性都在确定我们的距离度量上,你知道我们故意在那里搞了点花样,只是为了让它变得有趣,但你可以做任何其他你想做的事情。所以,如果你想玩弄一下这个,我绝对鼓励你这样做。我们选择 K 为 10 完全是凭空想象的,我就是编造出来的。这对不同的 K 值有什么影响?使用更高的 K 值会得到更好的结果吗?还是使用更低的 K 值?这有关系吗?
如果你真的想做一个更复杂的练习,你可以尝试将其应用到训练/测试中,实际上找到最能预测基于 KNN 的给定电影评分的 K 值。而且,你可以使用不同的距离度量,我也是凭空想象的!所以,玩一下距离度量,也许你可以使用不同的信息来源,或者以不同的方式权衡事物。这可能是一件有趣的事情。也许,流行度并不像流派信息那样重要,或者反过来也一样。看看这对你的结果有什么影响。所以,继续玩弄这些算法,玩弄代码并运行它,看看你能得到什么!如果你真的找到了一种显著的改进方法,那就和你的同学分享吧。
这就是 KNN 的实际运用!所以,这是一个非常简单的概念,但实际上它可能非常强大。所以,你看:仅仅基于流派和流行度就能找到相似的电影,没有别的。结果出奇的好!而且,我们使用了 KNN 的概念来实际使用那些最近的邻居来预测新电影的评分,这也实际上效果不错。所以,这就是 KNN 的实际运用,非常简单的技术,但通常效果相当不错!
降维和主成分分析
好了,是时候进入更高维度的世界了!我们要谈论更高维度和降维。听起来有点可怕!这里涉及到一些花哨的数学,但从概念上来说,它并不像你想象的那么难以理解。所以,让我们接下来谈谈降维和主成分分析。听起来非常戏剧化!通常当人们谈论这个时,他们谈论的是一种叫做主成分分析或 PCA 的技术,以及一种叫做奇异值分解或 SVD 的特定技术。所以 PCA 和 SVD 是本节的主题。让我们深入研究一下!
降维
那么,维度诅咒是什么?嗯,很多问题可以被认为有许多不同的维度。所以,例如,当我们在做电影推荐时,我们有各种电影的属性,每个单独的电影可以被认为是数据空间中的一个维度。
如果你有很多电影,那就有很多维度,你真的无法理解超过 3 个维度,因为这是我们成长演变的范围。你可能有一些你关心的许多不同特征的数据。你知道,在接下来的一刻,我们将看一个我们想要分类的花的例子,而且这个分类是基于花的 4 个不同的测量。这 4 个不同的特征,这 4 个不同的测量可以代表 4 个维度,再次,这是非常难以可视化的。
因此,降维技术存在是为了找到一种将更高维度信息降低到更低维度信息的方法。这不仅可以使它更容易查看和分类事物,而且还可以用于压缩数据。因此,通过保留最大方差,同时减少维度的数量,我们更紧凑地表示数据集。降维的一个非常常见的应用不仅仅是用于可视化,还用于压缩和特征提取。我们稍后会再谈一些。
降维的一个非常简单的例子可以被认为是 k 均值聚类:
所以你知道,例如,我们可能从数据集中开始有许多点,代表数据集中许多不同的维度。但是,最终,我们可以将其归纳为 K 个不同的质心,以及你到这些质心的距离。这是将数据归纳为更低维度表示的一种方法。
主成分分析
通常,当人们谈论降维时,他们谈论的是一种称为主成分分析的技术。这是一种更加复杂的技术,它涉及到一些相当复杂的数学。但是,从高层次来看,你需要知道的是它将一个更高维度的数据空间,找到该数据空间和更高维度内的平面。
这些更高维度的平面被称为超平面,并且它们由称为特征向量的东西定义。你可以取尽可能多的平面,最终在那些超平面上投影数据,那些就成为你的低维数据空间中的新轴:
你知道,除非你熟悉更高维度的数学并且之前考虑过它,否则很难理解!但是,最终,这意味着我们选择更高维度空间中的平面,仍然保留我们数据中的最大方差,并将数据投影到这些更高维度的平面上,然后将其带入更低维度的空间,好吗?
你真的不需要理解所有的数学来使用它;重要的是,这是一种非常有原则的方法,可以将数据集降低到更低维度的空间,同时仍然保留其中的方差。我们谈到了图像压缩作为这一应用的一个例子。所以你知道,如果我想要在图像中减少维度,我可以使用主成分分析将其归纳到其本质。
面部识别是另一个例子。所以,如果我有一个面部数据集,也许每张脸代表 2D 图像的第三个维度,并且我想将其归纳,SVD 和主成分分析可以是识别真正重要的特征的一种方法。因此,它可能更多地关注眼睛和嘴巴,例如,那些在保留数据集内方差方面是必要的重要特征。因此,它可以产生一些非常有趣和非常有用的结果,这些结果只是自然地从数据中出现,这有点酷!
为了使其更真实,我们将使用一个更简单的例子,使用所谓的鸢尾花数据集。这是一个包含在 scikit-learn 中的数据集。它在示例中经常被使用,其背后的想法是:鸢尾花实际上有两种不同类型的花瓣。一种叫做花瓣,就是你熟悉的花瓣,还有一种叫做萼片,它是花朵下部的一组支持性较低的花瓣。
我们可以拿一堆不同种类的鸢尾花,测量花瓣的长度和宽度,以及萼片的长度和宽度。因此,花瓣的长度和宽度,以及萼片的长度和宽度,共有 4 个不同的测量值对应于我们数据集中的 4 个不同维度。我想用这些来分类鸢尾花可能属于哪个物种。现在,PCA 将让我们在 2 个维度上可视化这个数据,而仍然保留数据集中的方差。所以,让我们看看这个方法的效果如何,并实际编写一些 Python 代码来对鸢尾花数据集进行 PCA。
这些就是降维、主成分分析和奇异值分解的概念。所有这些都是很高级的词汇,是的,这确实是一件高级的事情。你知道,我们正在以一种保留它们的方差的方式将高维空间缩减到低维空间。幸运的是,scikit-learn 使这变得非常容易,只需要 3 行代码就可以应用 PCA。所以让我们开始吧!
鸢尾花数据集的 PCA 示例
让我们将主成分分析应用于鸢尾花数据集。这是一个 4D 数据集,我们将将其降低到 2 个维度。我们将看到,即使丢弃了一半的维度,我们仍然可以保留数据集中的大部分信息。这是相当酷的东西,而且也相当简单。让我们深入研究一下,进行一些主成分分析,并解决维度的诅咒。继续打开PCA.ipynb
文件。
使用 scikit-learn 实际上非常容易!再次强调,PCA 是一种降维技术。所有这些关于高维度的讨论听起来非常科幻,但为了使其更具体和真实,一个常见的应用是图像压缩。你可以将一张黑白图片看作是 3 个维度,其中宽度是 x 轴,高度是 y 轴,每个单元格都有一个 0 到 1 的亮度值,即黑色或白色,或者介于两者之间的一些值。因此,这将是 3D 数据;你有 2 个空间维度,然后还有一个亮度和强度维度。
如果你将其精炼为仅有 2 个维度,那将是一个压缩图像,如果你以一种尽可能保留图像方差的技术来做到这一点,你仍然可以重构图像,理论上损失不会太大。所以,这就是降维,精炼为一个实际的例子。
现在,我们将使用鸢尾花数据集的另一个示例,scikit-learn 包含了这个数据集。它只是一个包含各种鸢尾花测量值和该数据集中每株鸢尾花物种分类的数据集。就像我之前说的,它还包括每株鸢尾花标本的花瓣和萼片的长度和宽度测量值。因此,在花瓣的长度和宽度以及萼片的长度和宽度之间,我们的数据集中有 4 个特征数据维度。
我们希望将其精炼为我们实际可以查看和理解的内容,因为你的大脑无法很好地处理 4 个维度,但你可以很容易地在纸上查看 2 个维度。让我们继续加载:
from sklearn.datasets import load_iris
from sklearn.decomposition import PCA
import pylab as pl
from itertools import cycle
iris = load_iris()
numSamples, numFeatures = iris.data.shape
print numSamples
print numFeatures
print list(iris.target_names)
scikit-learn 中有一个方便的load_iris()
函数,它可以直接加载数据,无需额外的工作;所以你可以专注于有趣的部分。让我们来看看这个数据集是什么样子的,前面代码的输出如下:
你可以看到我们正在提取数据集的形状,也就是我们有多少数据点,即150
,以及数据集有多少特征,或者说有多少维度,即4
。所以,我们的数据集中有150
朵鸢尾花标本,有 4 个信息维度。再次强调,这是萼片的长度和宽度,以及花瓣的长度和宽度,总共有4
个特征,我们可以将其视为4
个维度。
我们还可以打印出这个数据集中目标名称的列表,即分类,我们可以看到每朵鸢尾花属于三种不同的物种之一:山鸢尾、变色鸢尾或者维吉尼亚鸢尾。这就是我们要处理的数据:150 朵鸢尾花标本,分为 3 种物种之一,并且每朵鸢尾花都有 4 个特征。
让我们看看 PCA 有多容易。尽管在底层它是一个非常复杂的技术,但实际操作只需要几行代码。我们将整个鸢尾花数据集分配给 X。然后我们将创建一个 PCA 模型,并保持n_components=2
,因为我们想要 2 个维度,也就是说,我们要从 4 维降到 2 维。
我们将使用whiten=True
,这意味着我们将对所有数据进行归一化,确保一切都很好地可比较。通常情况下,为了获得良好的结果,你会想要这样做。然后,我们将把 PCA 模型拟合到我们的鸢尾花数据集X
上。然后我们可以使用该模型将数据集转换为 2 维。让我们来运行一下。这发生得非常快!
X = iris.data
pca = PCA(n_components=2, whiten=True).fit(X)
X_pca = pca.transform(X)
请思考刚才发生了什么。我们实际上创建了一个 PCA 模型,将 4 个维度降低到2
,它通过选择 2 个 4D 向量来实现这一点,以创建超平面,将 4D 数据投影到 2 维。你实际上可以通过打印 PCA 的实际成分来看到这些 4D 向量,即特征向量。所以,PCA代表主成分分析,这些主成分就是我们选择来定义平面的特征向量:
print pca.components_
前面代码的输出如下:
你实际上可以查看这些值,它们对你来说可能没有太多意义,因为你无法真正想象 4 个维度,但我们这样做是为了让你看到它实际上正在处理主成分。所以,让我们评估一下我们的结果:
print pca.explained_variance_ratio_
print sum(pca.explained_variance_ratio_)
PCA 模型给我们返回了一个叫做explained_variance_ratio
的东西。基本上,这告诉你在将原始的 4D 数据降低到 2 维时,有多少方差得以保留。所以,让我们来看看:
它实际上给出了一个包含 2 个项目的列表,用于我们保留的 2 个维度。这告诉我,在第一个维度中,我实际上可以保留数据中 92%的方差,而第二个维度只给了我额外的 5%方差。如果将它们加在一起,我将数据投影到的这 2 个维度中,仍然保留了源数据中超过 97%的方差。我们可以看到,其实并不需要 4 个维度来捕捉这个数据集中的所有信息,这是非常有趣的。这是相当酷的东西!
如果你仔细想想,你觉得可能是为什么呢?也许花的整体大小与其物种中心有一定的关系。也许是花瓣和萼片的长度与宽度之比。你知道,这些东西可能会随着给定物种或给定花的整体大小一起协调地移动。因此,也许这 4 个维度之间存在 PCA 自行提取的关系。这很酷,也很强大。让我们继续可视化这一点。
将这个数据降低到 2 个维度的整个目的是为了我们能够制作一个漂亮的 2D 散点图,至少这是我们在这个小例子中的目标。因此,我们将在这里做一些 Matplotlib 的魔术。这里有一些花里胡哨的东西,我至少应该提一下。所以,我们将创建一个颜色列表:红色、绿色和蓝色。我们将创建一个目标 ID 列表,使值 0、1 和 2 映射到我们拥有的不同的鸢尾花物种。
我们将把所有这些与每个物种的实际名称一起压缩。for 循环将遍历 3 种不同的鸢尾花物种,当它这样做时,我们将有该物种的索引,与之关联的颜色,以及该物种的实际可读名称。我们将一次处理一种物种,并在我们的散点图上用给定的颜色和标签绘制该物种的散点图。然后我们将添加我们的图例并显示结果:
colors = cycle('rgb')
target_ids = range(len(iris.target_names))
pl.figure()
for i, c, label in zip(target_ids, colors, iris.target_names):
pl.scatter(X_pca[iris.target == i, 0], X_pca[iris.target == i, 1],
c=c, label=label)
pl.legend()
pl.show()
以下是我们得到的结果:
这是我们将 4D 鸢尾花数据投影到 2 个维度。非常有趣!你可以看到它们仍然相当好地聚集在一起。你知道,所有的维吉尼亚人坐在一起,所有的变色鸢尾坐在中间,而山鸢尾则远在左侧。真的很难想象这些实际值代表什么。但是,重要的是,我们将 4D 数据投影到 2D,并且以这样的方式保留了方差。我们仍然可以清楚地看到这 3 个物种之间的明显区分。在其中有一些交织,不是完美的,你知道。但总的来说,它非常有效。
活动
正如你从explained_variance_ratio
中回忆起的那样,我们实际上在一个维度中捕获了大部分的方差。也许花的整体大小才是真正重要的分类因素;你可以用一个特征来指定这一点。所以,如果你感觉可以的话,继续修改结果。看看你是否可以用 2 个维度或者 1 个维度来完成!所以,把n_components
改成1
,看看你得到什么样的方差比率。
发生了什么?这有意义吗?玩弄一下,熟悉一下。这就是降维、主成分分析和奇异值分解的全部过程。非常、非常花哨的术语,你知道,在公平的情况下,在这些术语的背后是一些相当花哨的数学。但正如你所看到的,这是一种非常强大的技术,并且在 scikit-learn 中,应用起来并不难。因此,请将其放入你的工具箱中。
就是这样!一个关于花信息的 4D 数据集被简化为我们可以轻松可视化的 2 个维度,并且仍然可以清楚地看到我们感兴趣的分类之间的区分。因此,在这个例子中,PCA 的效果非常好。再次强调,这是一个用于压缩、特征提取或面部识别等方面的有用工具。因此,请将其放入你的工具箱中。
数据仓库概述
接下来,我们将稍微谈一下数据仓库。这是一个领域,最近被 Hadoop 的出现以及一些大数据技术和云计算所颠覆。所以,有很多大的关键词,但这些概念对你来说是重要的。
让我们深入探讨这些概念!让我们谈谈 ELT 和 ETL,以及数据仓库的一般情况。这更多是一个概念,而不是一个具体的实际技术,所以我们将从概念上来谈论它。但是,在工作面试中,这可能会出现。所以,让我们确保你理解这些概念。
我们将首先谈论一般的数据仓库。什么是数据仓库?嗯,它基本上是一个包含来自许多不同来源的信息的巨大数据库,并为你将它们联系在一起。例如,也许你在一家大型电子商务公司工作,他们可能有一个订单系统,将人们购买的商品的信息输入到你的数据仓库中。
你还可以从网络服务器日志中获取信息,将其注入到数据仓库中。这将使你能够将网站上的浏览信息与人们最终购买的商品联系起来。也许你还可以将来自客户服务系统的信息联系起来,并衡量浏览行为与客户最终的满意度之间是否存在关系。
数据仓库面临着从许多不同来源获取数据的挑战,将它们转换为某种模式,使我们能够同时查询这些不同的数据来源,并通过数据分析得出见解。因此,大型公司和组织通常会有这种情况。我们正在涉及大数据的概念。你可以有一个巨大的 Oracle 数据库,例如,其中包含所有这些东西,也许以某种方式进行了分区和复制,并且具有各种复杂性。你可以通过 SQL,结构化查询语言,或者通过图形工具,比如 Tableau,来查询它,这是目前非常流行的一种工具。这就是数据分析师的工作,他们使用诸如 Tableau 之类的工具查询大型数据集。
这就是数据分析师和数据科学家之间的区别。你可能实际上正在编写代码,对数据执行更高级的技术,涉及到人工智能,而不仅仅是使用工具从数据仓库中提取图表和关系。这是一个非常复杂的问题。在亚马逊,我们有一个专门负责数据仓库的部门,全职负责这些事情,他们从来没有足够的人手,我可以告诉你;这是一项重大工作!
你知道,做数据仓库有很多挑战。其中之一是数据规范化:因此,你必须弄清楚这些不同数据来源中的所有字段实际上是如何相互关联的?我如何确保一个数据源中的列可以与另一个数据源中的列进行比较,并具有相同的数据集、相同的规模和相同的术语?我如何处理缺失数据?我如何处理损坏的数据或来自异常值、机器人等的数据?这些都是非常大的挑战。维护这些数据源也是一个非常大的问题。
当你将所有这些信息导入数据仓库时,很多问题可能会出现,特别是当你需要进行非常大的转换,将从网络日志中保存的原始数据转换为实际的结构化数据库表,然后导入到你的数据仓库中。当你处理一个庞大的数据仓库时,扩展也可能会变得棘手。最终,你的数据会变得如此庞大,以至于这些转换本身开始成为一个问题。这开始涉及到 ELT 与 ETL 的整个话题。
ETL 与 ELT
让我们首先谈谈 ETL。它是什么意思?它代表提取、转换和加载-这是做数据仓库的传统方式。
基本上,首先从你想要的操作系统中提取你想要的数据。例如,我可能每天从我们的 Web 服务器中提取所有的 Web 日志。然后,我需要将所有这些信息转换为一个实际的结构化数据库表,可以将其导入到我的数据仓库中。
这个转换阶段可能会遍历每一行 Web 服务器日志,将其转换为一个实际的表,从每个 Web 日志行中提取会话 ID、他们查看的页面、时间、引荐者等信息,并将其组织成一个表格结构,然后将其加载到数据仓库本身,作为数据库中的一个实际表。因此,随着数据变得越来越大,这个转换步骤可能会成为一个真正的问题。想想在 Google、Amazon 或任何大型网站上处理所有 Web 日志并将其转换为数据库可以摄取的内容需要多少处理工作。这本身就成为一个可扩展性挑战,并且可能会通过整个数据仓库管道引入稳定性问题。
这就是 ELT 概念的出现,并且它有点颠覆了一切。它说,“如果我们不使用一个庞大的 Oracle 实例会怎样?如果我们使用一些允许我们在 Hadoop 集群上拥有更分布式数据库的新技术,让我们利用 Hive、Spark 或 MapReduce 这些分布式数据库的能力,在加载后实际进行转换。”
这里的想法是,我们将提取我们想要的信息,就像以前一样,比如从一组 Web 服务器日志中。但是,我们将直接将其加载到我们的数据存储库中,并且我们将使用存储库本身的功能来实际进行转换。因此,这里的想法是,与其进行离线过程来转换我的 Web 日志,例如,将其作为原始文本文件导入并逐行处理,使用类似 Hadoop 的东西的功能,实际上将其转换为更结构化的格式,然后可以跨整个数据仓库解决方案进行查询。
像 Hive 这样的东西让你在 Hadoop 集群上托管一个庞大的数据库。还有像 Spark SQL 这样的东西,让你也可以以非常类似 SQL 的数据仓库方式进行查询,实际上是在 Hadoop 集群上分布的数据仓库上进行查询。还有一些分布式 NoSQL 数据存储,可以使用 Spark 和 MapReduce 进行查询。这个想法是,你不是使用单一的数据库作为数据仓库,而是使用建立在 Hadoop 或某种集群之上的东西,实际上不仅可以扩展数据的处理和查询,还可以扩展数据的转换。
再次强调,首先提取原始数据,然后将其加载到数据仓库系统本身。然后使用数据仓库的能力(可能建立在 Hadoop 上)作为第三步进行转换。然后我可以一起查询这些东西。这是一个非常庞大的项目,非常庞大的主题。你知道,数据仓库本身就是一个完整的学科。我们将很快在这本书中更多地讨论 Spark,这是处理这个问题的一种方式——特别是有一个叫做 Spark SQL 的东西是相关的。
总体概念是,如果你从建立在 Oracle 或 MySQL 上的单一数据库转移到建立在 Hadoop 之上的这些更现代的分布式数据库之一,你可以在加载原始数据后进行转换阶段。这可能会更简单、更可扩展,并且利用今天可用的大型计算集群的能力。
这是 ETL 与 ELT 的对比,云计算中的传统方式与当今有意义的方式,当我们有大规模的计算资源可用于转换大型数据集时。这就是概念。
ETL 是一种老派的做法,你在导入和加载到一个巨大的数据仓库、单片数据库之前,离线转换一堆数据。但是在今天的技术中,使用基于云的数据库、Hadoop、Hive、Spark 和 MapReduce,你实际上可以更有效地做到这一点,并利用集群的力量在将原始数据加载到数据仓库后执行转换步骤。
这真的改变了这个领域,你知道这一点很重要。再次强调,关于这个主题还有很多要学习,所以我鼓励你在这个主题上进行更多的探索。但是,这就是基本概念,现在你知道人们谈论 ETL 与 ELT 时在谈论什么了。
强化学习
我们下一个话题是一个有趣的话题:强化学习。我们可以用 Pac-Man 的例子来理解这个概念。我们实际上可以创建一个能够自己玩得很好的智能 Pac-Man 代理。你会惊讶于构建这个智能 Pac-Man 背后的技术是多么简单。让我们来看看!
因此,强化学习的理念是,你有某种代理,比如 Pac-Man,在我们的例子中,这个空间将是 Pac-Man 所在的迷宫。随着它的前进,它学会了在不同条件下不同状态变化的价值。
例如,在前面的图像中,Pac-Man 的状态可能是由它南边有一个幽灵,西边有一堵墙,北边和东边是空的空间来定义的,这可能定义了 Pac-Man 的当前状态。它可以进行的状态变化是朝特定方向移动。然后我可以学习朝某个方向前进的价值。例如,如果我向北移动,实际上不会发生什么,这并没有真正的奖励。但是,如果我向南移动,我会被幽灵摧毁,这将是一个负值。
当我去探索整个空间时,我可以建立起一组所有可能的 Pac-Man 可能处于的状态,并与在每个状态中朝特定方向移动相关联的值,这就是强化学习。随着它探索整个空间,它会为给定状态细化这些奖励值,然后可以使用存储的奖励值来选择在当前条件下做出最佳决策。除了 Pac-Man,还有一个叫做 Cat & Mouse 的游戏,这是一个常用的例子,我们稍后会看一下。
这种技术的好处在于,一旦你探索了你的代理可能处于的所有可能状态,你可以在运行不同迭代时非常快速地获得非常好的性能。所以,你可以通过运行强化学习来制作一个智能 Pac-Man,让它探索在不同状态下可以做出不同决策的价值,然后存储这些信息,以便在未知条件下看到未来状态时快速做出正确的决策。
Q-learning
因此,强化学习的一个非常具体的实现被称为 Q-learning,这更加正式地阐述了我们刚刚谈到的内容:
-
因此,你从代理的一组环境状态开始(我旁边有幽灵吗?我前面有能量丸吗?诸如此类的事情。),我们将称之为 s。
-
我有一组可能在这些状态下采取的行动,我们将称之为 a。在 Pac-Man 的情况下,这些可能的行动是向上、向下、向左或向右移动。
-
然后我们有每个状态/行动对的一个值,我们将其称为 Q;这就是为什么我们称之为 Q 学习。因此,对于每个状态,Pac-Man 周围给定的一组条件,给定的行动将有一个值Q。因此,向上移动可能会有一个给定的值 Q,向下移动可能会有一个负的Q值,如果这意味着遇到幽灵,例如。
因此,我们从 Pac-Man 可能处于的每个可能状态开始,都有一个Q值为 0。随着 Pac-Man 探索迷宫,当 Pac-Man 遇到不好的事情时,我们会减少 Pac-Man 当时所处状态的Q值。因此,如果 Pac-Man 最终被幽灵吃掉,我们会惩罚他在当前状态所做的任何事情。当 Pac-Man 遇到好事时,当他吃到一个能量丸或吃掉一个幽灵时,我们将增加他所处状态的那个动作的Q值。然后,我们可以使用这些Q值来指导 Pac-Man 的未来选择,并构建一个可以表现最佳的小智能体,制作一个完美的小 Pac-Man。从我们刚才看到的 Pac-Man 的相同图像开始,我们可以通过定义他的西边有一堵墙,北边和东边有空地,南边有一个幽灵来进一步定义 Pac-Man 的当前状态。
我们可以看看他可以采取的行动:他实际上根本不能向左移动,但他可以向上、向下或向右移动,我们可以为所有这些行动分配一个值。向上或向右移动,实际上什么都不会发生,没有能量丸或点可以消耗。但如果他向左走,那肯定是一个负值。我们可以说对于由当前条件给出的状态,Pac-Man 所处的状态,向下移动将是一个非常糟糕的选择;对于那个给定状态的那些行动选择,应该有一个负的Q值。根本不能向左移动。向上或向右或保持中立,Q值对于那个给定状态的那些行动选择将保持为 0。
现在,你也可以向前看一点,使智能体变得更加智能。因此,实际上我离得到能量丸还有两步。因此,如果 Pac-Man 要探索这个状态,如果我在下一个状态吃到那个能量丸,我实际上可以将其纳入到先前状态的Q值中。如果你只有某种折扣因子,基于你在时间上有多远,你有多少步远,你可以将所有这些因素结合在一起。这是实际上在系统中建立一点记忆的方法。你可以使用折扣因子来计算 Q(这里s是先前的状态,*s’*是当前的状态)来向前看超过一步:
Q(s,a) += 折扣 * (奖励(s,a) + max(Q(s’)) - Q(s,a))
因此,当我消耗那个能量丸时所体验到的Q值实际上可能会提升我沿途遇到的先前Q值。这是使 Q 学习变得更好的一种方法。
探索问题
在强化学习中我们面临的一个问题是探索问题。在探索阶段,我如何确保有效地覆盖所有不同的状态和这些状态中的行动?
简单的方法
一种简单的方法是始终选择具有迄今为止计算出的最高Q值的给定状态的动作,如果有平局,就随机选择。因此,最初我的所有Q值可能都是 0,我会首先随机选择动作。
当我开始获得关于给定动作和给定状态的更好Q值的信息时,我将开始在前进时使用它们。但是,这最终会变得非常低效,如果我只将自己固定在始终选择到目前为止计算出的最佳Q值的这种刚性算法中,我实际上可能会错过很多路径。
更好的方法
因此,更好的方法是在探索时引入一些随机变化到我的行动中。因此,我们称之为一个 epsilon 项。假设我们有某个值,我掷骰子,得到一个随机数。如果它小于这个 epsilon 值,我实际上不遵循最高的Q值;我不做有意义的事情,我只是随机选择一条路径来尝试一下,看看会发生什么。这实际上让我在探索阶段更有效地探索更广泛的可能性、更广泛的行动,更广泛的状态。
因此,我们刚刚做的事情可以用非常花哨的数学术语来描述,但你知道概念上它相当简单。
花哨的词
我探索一些我可以在给定状态下采取的行动,我用它来指导与给定状态相关的给定行动的奖励,探索结束后,我可以使用那些Q值的信息,来智能地穿越一个全新的迷宫,例如。
这也可以称为马尔可夫决策过程。因此,很多数据科学只是给简单的概念赋予花哨、令人生畏的名字,强化学习中也有很多这样的情况。
马尔可夫决策过程
因此,如果你查阅马尔可夫决策过程的定义,它是“一个数学框架,用于建模决策,其中结果部分是随机的,部分受决策者控制”。
-
决策: 在给定状态的一系列可能性中,我们采取什么行动?
-
在结果部分是随机的情况下: 嗯,有点像我们的随机探索。
-
部分受决策者控制: 决策者是我们计算出的Q值。
因此,MDPs,马尔可夫决策过程,是一种花哨的方式来描述我们刚刚为强化学习描述的探索算法。符号甚至相似,状态仍然被描述为 s,s’是我们遇到的下一个状态。我们有被定义为P[a]的状态转移函数,对于给定的 s 和 s’。我们有我们的Q值,它们基本上被表示为一个奖励函数,对于给定的 s 和 s’有一个*R[a]*值。因此,从一个状态转移到另一个状态有一个与之相关的奖励,从一个状态转移到另一个状态由一个状态转移函数定义:
-
状态仍然被描述为s和s’’
-
状态转移函数被描述为Pa(s,s’)
-
我们的Q值被描述为奖励函数Ra(s,s’)
因此,再次描述我们刚刚做的事情,只是用数学符号和一个更花哨的词,马尔可夫决策过程。如果你想要听起来更聪明一点,你也可以用另一个名字来称呼马尔可夫决策过程:离散时间随机控制过程。听起来很聪明!但概念本身就是我们刚刚描述的东西。
动态规划
因此,更花哨的词:动态规划也可以用来描述我们刚刚做的事情。哇!听起来像是人工智能,计算机自我编程,《终结者 2》,天网之类的东西。但不,这只是我们刚刚做的事情。如果你查阅动态规划的定义,它是一种通过将复杂问题分解为一系列更简单的子问题来解决复杂问题的方法,每个子问题只解决一次,并理想地存储它们的解决方案,使用基于内存的数据结构。
下次出现相同的子问题时,不需要重新计算其解决方案,只需查找先前计算的解决方案,从而节省计算时间,但以(希望)在存储空间上进行适度的开销:
-
解决复杂问题的方法: 就像创造一个智能的吃豆人,这是一个相当复杂的最终结果。
-
通过将其分解为一系列更简单的子问题:例如,对于可能出现在 Pac-Man 中的给定状态,采取的最佳行动是什么。Pac-Man 可能会发现自己处于许多不同的状态,但每个状态都代表一个更简单的子问题,在这个子问题中,我可以做出有限的选择,并且有一个正确的答案来做出最佳的移动。
-
存储它们的解决方案:这些解决方案是我与每个可能的动作在每个状态关联的Q值。
-
理想情况下,使用基于内存的数据结构:当然,我需要以某种方式存储这些Q值并将它们与状态关联起来,对吧?
-
下次出现相同的子问题:下次 Pac-Man 处于我已经有一组Q值的给定状态时。
-
而不是重新计算其解决方案,只需查找先前计算的解决方案:我已经从探索阶段得到的Q值。
-
从而节省计算时间,以牺牲存储空间的适度开支:这正是我们刚刚用强化学习做的。
我们有一个复杂的探索阶段,找到与每个动作对应的给定状态的最佳奖励。一旦我们有了这个表格,我们就可以非常快速地使用它来使我们的 Pac-Man 在一个全新的迷宫中以最佳方式移动。因此,强化学习也是一种动态规划的形式。哇!
简而言之,你可以通过让它半随机地探索不同的移动选择来制作一个智能的 Pac-Man 代理,给定不同的条件,其中这些选择是动作,这些条件是状态。我们在进行时跟踪与每个动作或状态相关联的奖励或惩罚,我们实际上可以打折,回溯多步,如果你想让它变得更好。
然后我们存储这些Q值,我们可以使用它来指导其未来的选择。因此,我们可以进入一个全新的迷宫,并且有一个非常聪明的 Pac-Man,可以有效地避开幽灵并吃掉它们。这是一个非常简单但非常强大的概念。你也可以说你理解了一堆花哨的术语,因为它都是同一个东西。Q 学习,强化学习,马尔可夫决策过程,动态规划:都与同一个概念相关联。
我不知道,我觉得你实际上可以通过这样一个简单的技术制作出一种人工智能的 Pac-Man,这真的很酷!如果你想更详细地了解它,以下是一些示例,你可以查看其中一个实际的源代码,并且可能进行调试,Python 马尔可夫决策过程工具包:pymdptoolbox.readthedocs.org/en/latest/api/mdp.html
。
有一个 Python 马尔可夫决策过程工具包,它用我们谈到的所有术语包装起来。有一个你可以查看的示例,一个关于猫和老鼠游戏的工作示例,类似的。实际上,还有一个你可以在线查看的 Pac-Man 示例,它更直接地与我们谈论的内容相关。请随意探索这些链接,并了解更多。
这就是强化学习。更一般地说,这是一种构建代理程序的有用技术,该代理程序可以在可能具有一组与每个状态相关联的动作的不同状态中导航。因此,我们大多数时候在迷宫游戏的背景下讨论它。但是,你可以更广泛地思考,你知道每当你需要根据一组当前条件和一组可以采取的行动来预测某物的行为时。强化学习和 Q 学习可能是一种方法。所以,请记住这一点!
总结
在本章中,我们看到了一种最简单的机器学习技术,称为 k 最近邻算法。我们还看了一个 KNN 的例子,它预测了一部电影的评分。我们分析了降维和主成分分析的概念,并看到了一个 PCA 的例子,它将 4D 数据降低到两个维度,同时保留了其方差。
接下来,我们学习了数据仓库的概念,并看到了如何在今天使用 ELT 过程而不是 ETL 更有意义。我们深入了解了强化学习的概念,并看到了它在 Pac-Man 游戏背后的应用。最后,我们看到了一些用于强化学习的花哨词汇(Q 学习,马尔可夫决策过程和动态学习)。在下一章中,我们将看到如何处理真实世界的数据。