Python-数据科学和机器学习实践指南-全-

Python 数据科学和机器学习实践指南(全)

原文:Hands-On Data Science and Python Machine Learning

协议:CC BY-NC-SA 4.0

零、前言

成为科技行业的数据科学家是当今地球上最有意义的职业之一。 我去研究了技术公司中数据科学家角色的实际职位描述,并将这些要求简化为您将在本课程中看到的主题。

《Python 数据科学和机器学习实用指南》确实很全面。 我们将从 Python 速成班开始,并回顾一些基本的统计数据和概率,但随后我们将直接探讨数据挖掘和机器学习中的 60 多个主题。 其中包括贝叶斯定理,聚类,决策树,回归分析,实验设计等; 我们将全部看一下。 其中一些主题真的很有趣。

我们将使用实际的用户电影分级数据来开发实际的电影推荐系统。 我们将创建一个实际上可用于维基百科数据的搜索引擎。 我们将构建一个垃圾邮件分类器,该分类器可以正确地对您的电子邮件帐户中的垃圾邮件和非垃圾邮件进行分类,并且我们还将在整个章节中介绍如何使用 Apache Spark 将其扩展到在大数据上运行的集群。

如果您是想要过渡到数据科学职业的软件开发人员或程序员,那么本课程将教您最热门的技能,而不会涉及这些主题附带的所有数学符号和伪装。 我们只是要解释这些概念,并向您展示一些实际有效的 Python 代码,您可以深入研究并弄混这些概念,以使这些概念深入人心,如果您是金融行业的数据分析师, 该课程还可以教您如何过渡到科技行业。 您所需要的只是在编程或脚本编写方面的一些先验经验,您应该会很好。

本书的一般格式是我将从每个概念入手,并在许多章节和图形示例中对其进行解释。 我将向您介绍数据科学家喜欢使用的一些符号和奇特的术语,以便您可以说相同的语言,但是概念本身通常非常简单。 之后,我将带您进入一些实际的 Python 代码,这些代码可以正常运行,我们可以运行并弄乱它们,并且将向您展示如何将这些想法实际应用于实际数据。 这些将以 IPython 笔记本文件的形式呈现,这是一种格式,我可以在其中混合代码和代码周围的注释,以解释概念中发生的事情。 在阅读完本书后,您可以随身携带这些笔记本文件,并在以后的职业生涯中用作快速参考。在每个概念的结尾,我都鼓励您深入学习该 Python 代码, 一些修改,弄乱它,并通过动手实践并实际进行一些修改并查看其效果来获得更多的熟悉。

这本书是给谁的

如果您是一位新兴的数据科学家或数据分析师,想使用 Python 分析数据并从中获得切实可行的见解,那么本书非常适合您。 想要在 Data Science 领域赚钱的具有 Python 经验的程序员也会发现这本书非常有用。

约定

在本书中,您将找到许多可以区分不同类型信息的文本样式。 以下是这些样式的一些示例,并对其含义进行了解释。

文本中的代码字,数据库表名称,文件夹名称,文件名,文件扩展名,路径名,伪 URL,用户输入和 Twitter 句柄如下所示:“我们可以使用sklearn.metrics中的r2_score()函数进行测量。”

代码块设置如下:

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) 

当我们希望引起您对代码块特定部分的注意时,相关行或项目以粗体显示:

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) 

任何命令行输入或输出的编写方式如下:

spark-submit SparkKMeans.py  

新术语和重要词以粗体显示。 您在屏幕上看到的字词,例如在菜单或对话框中的字样如下所示:“在 Windows 10 上,您需要打开“开始”菜单,然后转到“Windows 系统 | 控制面板”以打开控制面板。”

警告或重要提示如下所示。

提示和技巧如下所示。

读者反馈

始终欢迎读者的反馈。 让我们知道您对这本书的看法-您喜欢或不喜欢的书。 读者反馈对我们很重要,因为它可以帮助我们开发出您真正能充分利用的标题。

要向我们发送一般反馈,只需通过电子邮件发送feedback@packtpub.com,然后在您的消息主题中提及该书的标题。

如果您有专业知识的主题,并且对写作或撰写书籍感兴趣,请参阅这个页面的作者指南。

客户支持

既然您是 Packt 书的骄傲拥有者,我们可以通过很多方法来帮助您从购买中获得最大收益。

下载示例代码

您可以从这个页面的帐户中下载本书的示例代码文件。 如果您在其他地方购买了此书,则可以访问这个页面并注册以将文件直接通过电子邮件发送给您。

您可以按照以下步骤下载代码文件:

  1. 使用您的电子邮件地址和密码登录或注册到我们的网站。
  2. 将鼠标指针悬停在顶部的“支持”选项卡上。
  3. 单击代码下载和勘误。
  4. 在搜索框中输入书籍的名称。
  5. 选择您要下载其代码文件的书。
  6. 从购买本书的下拉菜单中选择。
  7. 单击代码下载。

您还可以通过在 Packt Publishing 网站上的图书网页上单击“代码文件”按钮来下载代码文件。 通过在“搜索”框中输入书籍的名称,可以访问此页面。 请注意,您需要登录到 Packt 帐户。

下载文件后,请确保使用以下最新版本解压缩或解压缩文件夹:

  • Windows 的 WinRAR/7-Zip
  • 适用于 Mac 的 Zipeg/iZip/UnRarX
  • 适用于 Linux 的 7-Zip/PeaZip

本书的代码包也托管在这个页面。 我们还从这个页面提供了丰富的书籍和视频目录中的其他代码包。 去看一下!

下载本书的彩色图像

我们还为您提供了 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图像。 彩色图像将帮助您更好地了解输出中的变化。 您可以从这个页面下载此文件。

一、入门

由于将有与此书相关联的代码以及您需要获取的样本数据,所以让我首先向您展示如何获取该代码,然后我们将继续。 我们需要先进行一些设置。 首先,让我们获取本书所需的代码和数据,以便您可以随意学习并实际使用一些代码。 最简单的方法是转到“入门”。

在本章中,我们将首先在可运行的 Python 环境中安装并做好准备:

  • 安装 Enthought Canopy
  • 安装 Python 库
  • 如何使用 IPython/Jupyter 笔记本
  • 如何使用,阅读和运行本书的代码文件
  • 然后,我们将深入研究速成课程,以了解 Python 代码:
  • Python 基础-第 1 部分
  • 了解 Python 代码
  • 导入模块
  • 试验列表
  • 元组
  • Python 基础-第 2 部分
  • 运行 Python 脚本

一旦我们在本章中建立了环境并熟悉 Python,您将拥有使用 Python 进入数据科学的惊人旅程所需的一切。

安装 Enthought Canopy

让我们深入研究并获取安装所需的内容,以便在您的桌面上使用数据科学实际开发 Python 代码。 我将指导您安装名为 Enthought Canopy 的包,该包同时包含开发环境和您需要预安装的所有 Python 包。 它使生活变得非常轻松,但是,如果您已经了解 Python,那么您的 PC 上可能已经有一个现有的 Python 环境,并且如果您想继续使用它,也许可以。

最重要的是,您的 Python 环境具有 Python 3.5 或更高版本,它支持 Jupyter 笔记本(因为这是我们将在本课程中使用的语言),并且您已在本书中安装了所需的关键包。 环境。 我将通过几个简单的步骤来确切说明如何实现完整安装-这将非常容易。

让我们首先概述那些关键包,其中大部分 Canopy 都会自动为我们安装。 Canopy 将为我们安装 Python 3.5,以及我们需要的其他一些包,包括:scikit_learnxlrdstatsmodels。 我们需要手动使用pip命令来安装名为pydot2plus的包。 就是这样-Canopy 非常容易!

完成以下安装步骤后,我们将具备启动和运行所需的一切,因此,我们将打开一个小示例文件并进行一些实际的数据科学处理。 现在,让您开始快速入门所需的一切:

  1. 您需要做的第一件事是开发环境,称为 IDE,用于 Python 代码。 我们将在本书中使用的是 Enthought Canopy。 这是一个科学的计算环境,可以很好地与这本书一起使用:

  1. 要安装 Canopy,只需转到这个页面,然后单击下载:Canopy:

  1. 对于 Canopy Express 版,Enthought Canopy 是免费的-这是您想要的本书。 然后,您必须选择操作系统和架构。 对我来说,这是 Windows 64 位,但您需要单击适用于您的操作系统并带有 Python 3.5 选项的相应“下载”按钮:

  1. 在此步骤中,我们不必向他们提供任何个人信息。 有一个非常标准的 Windows 安装程序,因此只需下载即可:

  1. 下载完成后,我们继续打开 Canopy 安装程序,然后运行它! 您可能需要先阅读许可证,然后再由您自己决定,然后等待安装完成。
  2. 在安装过程结束时,单击“完成”按钮后,让其自动启动 Canopy。 您会看到 Canopy 然后自己建立了 Python 环境,这很棒,但这需要一两分钟。
  3. 安装程序完成设置 Python 环境后,您应该会看到类似下面的屏幕。 它说欢迎来到 Canopy 和一堆友好的大按钮:

  1. 美丽的是,您本书所需的几乎所有东西都预装了 Enthought Canopy,这就是为什么我建议使用它的原因!
  2. 我们只需要设置最后一件事,因此继续并单击 Canopy Welcome 屏幕上的“编辑器”按钮。 然后,您会看到出现“编辑器”屏幕,如果您在底部的窗口中单击,我希望您输入:
!pip install pydotplus 

  1. 这是在 Canopy Editor 窗口底部键入上面的行时在屏幕上显示的样子; 当然不要忘记按 Return 键:

  1. 按下“返回”按钮后,将安装一个额外的模块,在我们稍后讨论决策树和呈现决策树时,我们将在本书的稍后部分中使用该模块。
  2. 一旦完成pydotplus的安装,它应该会回来并说它已成功安装,瞧,您现在就拥有开始所需的一切! 至此,安装已完成-但让我们再采取一些步骤,以确认我们的安装运行良好。

进行安装测试

  1. 现在,让您的安装进行测试运行。 首先要做的是实际上完全关闭 Canopy 窗口! 这是因为我们实际上不会在此 Canopy 编辑器中进行编辑和使用代码。 取而代之的是,我们将使用称为 IPython 笔记本的东西,现在也称为 Jupyter 笔记本。
  2. 让我告诉你它是如何工作的。 如果您现在在操作系统中打开一个窗口,以查看下载的随附书文件,如本书序言中所述。 您应该为本书下载的.ipynb代码文件集看起来像这样:

现在,转到列表中的 Outliers 文件,即Outliers.ipynb文件,双击它,应该发生的事情是,它将首先启动 Canopy,然后启动您的 Web 浏览器! 这是因为 IPython/Jupyter 笔记本实际上位于您的 Web 浏览器中。 一开始可能会有一点停顿,并且第一次可能会有些混乱,但是您很快就会习惯这个想法。

您很快就会看到 Canopy,对于我来说,我的默认网络浏览器是 Chrome。 由于我们双击Outliers.ipynb文件,您应该看到以下 Jupyter 笔记本页面:

如果您看到此屏幕,则表示安装过程中一切正常,并且已准备好完成本书其余部分的旅程!

如果您偶尔遇到打开 IPNYB 文件的问题

偶尔,我注意到双击.ipynb文件时可能会出错。 不要惊慌! 有时,Canopy 可能会有点片状,您可能会看到一个屏幕在寻找某些密码或令牌,或者您偶尔会看到一个屏幕,表明它根本无法连接。

如果这两种情况中的任何一种发生在您身上,请不要惊慌,它们只是随机的怪癖,有时事情只是无法按正确的顺序启动,或者它们无法在您的 PC 上及时启动。 没关系。

您所要做的就是返回并再次尝试打开该文件。 有时需要两到三次尝试才能真正正确地加载它,但是如果您多次尝试,它最终会弹出,那么就像我们之前看到的处理异常值的 Jupyter 笔记本屏幕一样,您应该这样做。

使用和理解 IPython(Jupyter)笔记本

恭喜您安装成功! 现在让我们使用 Jupyter 笔记本(也称为 IPython Notebook)进行探索。 如今,更现代的名称是 Jupyter 笔记本,但是很多人仍将其称为 IPython 笔记本,因此,我认为这些名称对于工作中的开发人员而言可以互换。 我也确实找到了名称“IPython 笔记本”,它可以帮助我记住笔记本文件的文件名后缀,即.ipynb,因为您在本书中会对此非常了解!

好的,现在让我们再次从上而下-我们对 IPython/Jupyter 笔记本的首次探索。 如果您尚未这样做,请导航到DataScience文件夹,我们在其中下载了本书的所有材料。 对我来说,这是E:DataScience,如果您在前面的安装部分中没有这样做,请双击并打开Outliers.ipynb文件。

现在,当我们双击此 IPython .ipynb文件时,将首先触发 Canopy,如果尚未触发,则将其触发。 启动网络浏览器。 这是完整的Outliers笔记本电脑网页在浏览器中的显示方式:

正如您在此处看到的那样,笔记本的结构方式使我可以在实际代码本身中散布有关您在此处看到的内容的注释和提示,并且可以在 Web 浏览器中实际运行此代码! 因此,对于我来说,这是一种非常方便的格式,可以为您提供一些参考,您可以在以后的生活中使用它来提醒自己,我们将要讨论这些算法的工作原理,并实际对其进行实验和自己玩转它们。

IPython/Jupyter 笔记本文件的工作方式是,它们实际上是从您的浏览器内部运行的,就像网页一样,但是它们受到您安装的 Python 引擎的支持。 因此,您应该看到的屏幕类似于上一个屏幕快照中显示的屏幕。

在浏览器中向下滚动笔记本时,您会注意到有代码块。 它们很容易发现,因为它们包含我们的实际代码。 请在离群值笔记本中的顶部附近找到此代码的代码框:

%matplotlib inline 
import numpy as np 

incomes = np.random.normal(27000, 15000, 10000) 
incomes = np.append(incomes, [1000000000]) 

import matplotlib.pyplot as plt 
plt.hist(incomes, 50) 
plt.show() 

在这里,让我们快速看一下这段代码。 我们正在此代码中设置一些收入分配。 我们正在模拟人群中的收入分配,并且为了说明离群值可能对该分布产生的影响,我们正在模拟唐纳德·特朗普进入混合状态并弄乱收入分配的均值。 顺便说一句,我不是在发表政治声明,这一切都是在特朗普成为政治家之前完成的。 所以您知道,在那里完全公开。

我们可以通过单击笔记本中的任何代码块来选择它。 因此,如果现在单击包含上面刚刚查看的代码的代码块,则可以单击顶部的运行按钮来运行它。 在屏幕顶部的此处是您会找到“运行”按钮的区域:

在选择了代码块的情况下点击“运行”按钮,将导致重新生成该图:

同样,我们可以在更下方单击下一个代码块,您将发现具有以下单行代码的代码块:

incomes.mean() 

如果选择包含此行的代码块,然后单击“运行”按钮以运行代码,您将在其下方看到输出,由于该异常值的影响,该输出最终是一个非常大的值,如下所示:

127148.50796177129

让我们继续前进,找点乐子。 在下一个代码块中,您将看到以下代码,该代码试图检测唐纳德·特朗普之类的离群值并将其从数据集中删除:

def reject_outliers(data): 
    u = np.median(data) 
    s = np.std(data) 
    filtered = [e for e in data if (u - 2 * s < e < u + 2 * s)] 
    return filtered 

filtered = reject_outliers(incomes) 
plt.hist(filtered, 50) 
plt.show() 

因此,在笔记本中选择相应的代码块,然后再次按运行按钮。 执行此操作时,您将看到以下图形:

现在,我们看到了一个更好的直方图,代表了更典型的美国人-既然我们已经排除了使事情变得混乱的异常值。

因此,在这一点上,您已经具备了开始本课程所需的一切。 我们拥有您需要的所有数据,所有脚本以及 Python 和 Python 笔记本的开发环境。 所以,让我们摇滚。 接下来,我们将对 Python 本身进行一些速成课程,即使您熟悉 Python,它也可能是一个不错的小知识,因此您可能无论如何都要看它。 让我们深入学习 Python。

Python 基础-第 1 部分

如果您已经了解 Python,则可以跳过以下两节。 但是,如果您需要复习,或者以前从未使用过 Python,则需要进行这些学习。 关于 Python 脚本语言,您需要了解一些古怪的知识,因此让我们深入研究,直接进入池中,通过编写一些实际代码来学习一些 Python。

就像我之前说过的那样,在本书的要求中,您应该具有某种编程背景才能在本书中取得成功。 您已经使用某种语言进行了编码,即使它是脚本语言,JavaScript,我也不在乎它是 C++,Java 还是其他东西,但是如果您是 Python 的新手,这里我会给您一些速成课程。 我将继续深入探讨本节中的一些示例。

关于 Python 的一些怪癖与您可能已经看到的其他语言有些不同; 因此,我只想了解 Python 与您可能使用过的其他脚本语言的不同之处,而做到这一点的最佳方法是查看一些真实的示例。 让我们深入研究一些 Python 代码:

如果打开该类的DataScience文件夹(已在上一节的前面部分中下载),则应该找到Python101.ipynb文件; 继续并双击它。 如果您正确安装了所有组件,它应该会在 Canopy 中打开,并且看起来应该类似于以下屏幕截图:

新版本的 Canopy 将在您的 Web 浏览器中打开代码,而不是在 Canopy 编辑器中打开代码! 没关系!

关于 Python 的一件很酷的事情是,有几种方法可以使用 Python 运行代码。 您可以像使用普通编程语言一样将其作为脚本运行。 您还可以编写称为 IPython 笔记本的东西,这就是我们在这里使用的东西。 因此,在这种格式下,您实际上具有类似于 Web 浏览器的视图,在其中实际上可以在 HTML 标记内容中为自己写一些符号和注释,还可以嵌入实际使用 Python 解释器运行的实际代码。

了解 Python 代码

我想为您提供一些 Python 代码的第一个示例就在这里。 以下代码块代表了一些实际的 Python 代码,我们实际上可以在整个笔记本页面的该视图中直接运行这些代码,但让我们现在放大并查看该代码:

让我们看看发生了什么。 我们有一个数字列表和一个 Python 列表,有点像其他语言的数组。 由以下方括号指定:

我们具有一个包含数字 1 到 6 的列表的数据结构,然后要遍历该列表中的每个数字,我们将说for number in listOfNumbers:,这是用于遍历一列东西和一个冒号的 Python 语法。

制表符和空格在 Python 中具有真正的含义,因此您不能只是按照自己的方式设置格式。 您必须注意它们。

我想说明的是,在其他语言中,通常会有某种形式的方括号或大括号表示我在for循环,if块或某种内部代码块,但在 Python 中,所有这些都用空格指定。 Tab对于告诉 Python 哪个代码块实际上很重要:

for number in listOfNumbers: 
    print number, 
    if (number % 2 == 0): 
        print ("is even")
    else: 
        print ("is odd") 

print ("Hooray! We're all done.")

您会注意到,在此for块中,在整个块中有一个制表符,并且对于每个number in listOfNumbers,我们将执行由一个制表符停止符制表的所有代码。 我们将打印该数字,并且逗号仅表示我们此后将不执行新行。 我们将紧随其后打印其他内容,然后将其称为even。 否则,我们将其称为odd,完成后,我们将打印出All done

您可以在代码下方看到输出。 我之前已经运行过输出,因为我实际上已经将其保存在笔记本中,但是如果您想自己实际运行它,则可以在该块中单击并单击“播放”按钮,然后我们将实际执行并再次执行 。 只是为了使自己确信它确实在做某件事,让我们将print语句更改为其他内容,例如Hooray! We're all done. Let's party!。如果我现在运行此命令,可以肯定地看到那里的消息已更改:

同样,我想说的是空格很重要。 您将使用缩进或制表符指定一起运行的代码块,例如for循环或if then语句,因此请记住这一点。 另外,也要注意结肠。 您会注意到,其中许多子句以冒号开头。

导入模块

像任何语言一样,Python 本身在功能上也相当有限。 使用 Python 进行机器学习,数据挖掘和数据科学的真正力量是可用于此目的的所有外部库的力量。 这些库之一称为NumPy或数字 Python,例如,在这里我们可以import Numpy包,该包作为np包含在 Canopy 中。

这意味着我将NumPy包称为np,我可以将其命名为任何内容。 我可以称其为FredTim,但最好坚持使用确实有意义的东西。 现在,我正在调用NumPynp,我可以使用np来引用它:

import numpy as np

在此示例中,我将调用NumPy包的一部分提供的random函数,并调用其正态函数使用这些参数实际生成随机数的正态分布并打印出来。 由于它是随机的,因此每次我应该得到不同的结果:

import numpy as np
A = np.random.normal(25.0, 5.0, 10)
print (A)

输出应如下所示:

果然,我得到了不同的结果。 太酷了。

数据结构

让我们继续进行数据结构。 如果您需要暂停一下,让事物沉入一点,或者您想多玩一点,那就随便吧。 学习这些东西的最好方法是潜入并进行实际试验,因此,我绝对鼓励这样做,这就是为什么我为您提供 IPython/Jupyter 笔记本的原因,因此您实际上可以走进去,弄乱代码,做不同的事情。

例如,这里我们有一个围绕25.0的分布,但是让我们围绕它55.0

import numpy as np
A = np.random.normal(55.0, 5.0, 10)
print (A)

嘿,我所有的数字都改变了,现在已经接近 55,那又如何呢?

好了,让我们在这里稍微谈谈数据结构。 正如我们在第一个示例中看到的那样,您可以有一个列表,其语法如下所示。

尝试列表

x = [1, 2, 3, 4, 5, 6]
print (len(x))

您可以说,例如,调用列表x并将其分配给数字16,这些方括号表示我们正在使用 Python 列表,而这些是不可变对象,我可以根据需要实际添加和重新安排内容。 有一个用于确定列表长度的内置函数,称为len,如果我键入len(x),那我会得到数字6,因为列表中有 6 个数字。

只是要确保,然后再次理解这实际上是在这里运行真实代码,让我们在其中添加另一个数字,例如4545。 如果运行此命令,则会得到7,因为该列表中现在有 7 个数字:

x = [1, 2, 3, 4, 5, 6, 4545]
print (len(x))

前面的代码示例的输出如下:

7

回到那里的原始示例。 现在,您还可以切片列表。 如果要获取列表的子集,可以使用一种非常简单的语法:

x[3:]

上面的代码示例的输出如下:

[1, 2, 3]

冒号之前

例如,如果要获取列表的前三个元素(元素编号 3 之前的所有内容),我们可以说:3以获取前三个元素123,以及如果您考虑一下发生了什么,就象大多数语言一样,就索引而言,我们从 0 开始计数。因此元素 0 为1,元素 1 为2,元素 2 为3。 既然我们说我们要在元素 3 之前拥有所有东西,那就是我们得到的。

因此,请记住,永远不要忘记,在大多数语言中,您是从 0 开始而不是从 1 开始计数。

现在,这可能会使事情变得混乱,但是在这种情况下,确实是直观的。 您可以认为冒号意味着我想要一切,我想要前三个特征,并且我可以再次将其更改为四个,以表明我们实际上在这里做点真正的事情:

x[:4]

上面的代码示例的输出如下:

[1, 2, 3, 4]

冒号之后

现在,如果将冒号放在3的另一侧,则表示我想要3之后的所有内容,因此3之后也需要。 如果我说x[3:],那是第三个元素,即 0、1、2、3 及其后的所有内容。 在该示例中,这将返回 4、5 和 6,好吗?

x[3:]

输出如下:

[4, 5, 6]

您可能想要保留此 IPython/Jupyter 笔记本文件。 这是一个很好的参考,因为有时切片操作符是否包含该元素,或者是否包含或不包含该元素,可能会造成混淆。 因此,最好的方法是在这里玩转并提醒自己。

负值语法

您可以做的另一件事是使用以下负值语法:

x[-2:]

输出如下:

[5, 6]

通过说x[-2:],这意味着我想要列表中的最后两个元素。 这意味着从末尾倒数两个,这将给我56,因为那是我列表中的最后两件事。

将列表添加到列表

您也可以更改列表。 假设我要向列表添加一个列表。 我可以使用extend函数,如以下代码块所示:

x.extend([7,8])
x

上面代码的输出如下:

[1, 2, 3, 4, 5, 6, 7, 8]

我有123456清单。 如果要扩展它,可以说我在这里有一个新列表[7, 8],该括号表示这是它本身的新列表。 您可能知道,这可能是一个隐式列表,该列表在此内联,也可以由另一个变量引用。 您可以看到,一旦执行此操作,我得到的新列表实际上将78列表附加在其末尾。 因此,通过用另一个列表扩展该列表,我有了一个新列表。

append函数

如果您只想在该列表中添加其他内容,则可以使用append函数。 所以我只想在末尾贴上数字9,我们就可以了:

x.append(9)
x

上面代码的输出如下:

[1, 2, 3, 4, 5, 6, 7, 8, 9]

复杂的数据结构

您还可以使用带有列表的复杂数据结构。 因此,您不必仅在其中输入数字。 您实际上可以在其中放入字符串。 您可以在其中输入数字。 您可以在其中放入其他列表。 没关系 Python 是一种弱类型的语言,因此您几乎可以将所需的任何类型的数据放在任何地方,通常这样做是可以的:

y = [10, 11, 12]
listOfLists = [x, y]
listOfLists

在前面的示例中,我有第二个列表,其中包含101112,这些列表我称为y。 我将创建一个包含两个列表的新列表。 那真是令人震惊吗? 我们的listofLists列表将包含x列表和y列表,这是完全正确的事情。 您可以在此处看到我们有一个括号,指示listofLists列表,在其中,我们还有另一组括号,指示该列表中的每个单独的列表:

[[ 1, 2, 3, 4, 5, 6, 7, 8, 9 ], [10, 11, 12]]

因此,有时诸如此类的事情会派上用场。

提领单个元素

如果要提领列表的单个元素,可以使用如下方括号:

y[1]

上面代码的输出如下:

11

因此y[1]将返回元素1。 请记住,y中包含101112-请观察前面的示例,我们从 0 开始计数,因此元素 1 实际上将是列表中的第二个元素,在这种情况下即数字11,好吗?

排序函数

最后,让我们拥有一个可以使用的内置排序函数:

z = [3, 2, 1]
z.sort()
z

因此,如果我从列表z(即3, 21)开始,则可以在该列表上调用 sort,现在z将按顺序进行排序。 上面代码的输出如下:

[1, 2, 3]

反向排序

z.sort(reverse=True)
z

上面代码的输出如下:

[3, 2, 1]

如果需要进行反向排序,则可以只说reverse=True作为属性,作为sort函数中的参数,然后将其放回321

如果您需要一点点沉浸,请随时返回并阅读更多内容。

元组

元组就像列表一样,只是它们是不可变的,因此您实际上无法对其进行扩展,追加或排序。 它们就是它们,它们的行为类似于列表,除了您无法更改它们之外,并且您指出它们是不可变的并且是元组,而不是列表,使用括号而不是方括号。 因此,您可以看到它们的工作方式几乎相同:

#Tuples are just immutable lists. Use () instead of []
x = (1, 2, 3)
len(x)

先前代码的输出如下:

3

我们可以说x= (1, 2, 3)。 我仍然可以使用length - len来表示该元组中包含三个元素,即使您不熟悉tuple术语,tuple实际上也可以包含任意数量的元素 。 即使听起来像是拉丁文中的数字三,也不代表您有三样东西。 通常,它只有两件事。 实际上,它们可以有任意多个。

提领元素

我们还可以提领一个元组的元素,因此元素编号 2 再次成为第三个元素,因为我们从 0 开始计数,这将在以下屏幕快照中返回数字6

y = (4, 5, 6)
y[2]

上面代码的输出如下:

6

元组列表

我们也可以像使用列表一样使用元组作为列表的元素。

listOfTuples = [x, y]
listOfTuples

上面代码的输出如下:

[(1, 2, 3), (4, 5, 6)]

我们可以创建一个包含两个元组的新列表。 因此,在前面的示例中,我们有(1, 2, 3)x元组和(4, 5, 6)y元组; 然后我们列出这两个元组的列表,然后返回此结构,其中我们用方括号表示一个列表,该列表包含用括号表示的两个元组。在进行数据科学,或进行任何类型的数据管理或处理时,通常使用元组来为读入的输入数据分配变量。在下面的示例中,我想带您逐步了解一下发生的情况:

(age, income) = "32,120000".split(',')
print (age)
print (income)

上面代码的输出如下:

32
120000

假设我们输入了一行输入数据,它是一个逗号分隔的值文件,其中包含年龄(例如32),以收入逗号分隔的年龄(例如120000),用于弥补年龄。 我所能做的就是在每行输入时,我可以调用split函数将其实际上分成一对用逗号定界的值,然后将结果元组从split中分离出来并将其分配给两个变量-ageincome-同时定义了年龄,收入和说我想将其设置为等于split函数产生的元组。

因此,这基本上是您一次将多个字段分配给多个变量的常见缩写。 如果运行该命令,您会发现age变量实际上最终分配给了32income分配给了120,000,因为那里有一些小技巧。 在执行此类操作时,您一定要小心,因为如果在结果元组中没有预期的字段数或预期的元素数,或少于您期望在此处看到的东西,那么如果尝试分配更多的字段,则会出现异常。

字典

最后,我们将在 Python 中看到的最后一个数据结构是字典,您可以将其视为其他语言的映射或哈希表。 这是一种基本拥有某种迷你数据库,某种内置于 Python 的键/值数据存储的方式。 假设我想建立一本有关《星际迷航》船及其船长的小词典:

我可以设置一个captains = {},其中大括号表示一个空字典。 现在,我可以使用这种语法在字典中分配条目,因此我可以说EnterprisecaptainsKirkEnterprise D的是PicardDeep Space Nine的是Sisko,对于VoyagerJaneway。 现在,基本上,有了这个查询表,它将船名与他们的船长相关联,例如,我可以说print captains["Voyager"],然后返回Janeway

一个非常有用的工具,基本上可以进行某种形式的查找。 假设您在数据集中具有某种标识符,该标识符映射到一些易于理解的名称。 在打印出来时,您可能会使用字典来实际进行查找。

如果您尝试查找不存在的内容,我们还可以看到会发生什么。 好吧,我们可以在字典上使用get函数来安全地返回条目。 因此,在这种情况下,Enterprise确实在我的词典中有一个条目,它只给我返回了Kirk,但是如果我在词典中调用NX-01船,我就没有定义它的船长,因此它会返回空。 在此示例中,使用None值比抛出异常更好,但是您确实需要意识到这是可能的:

print (captains.get("NX-01"))

上面代码的输出如下:

None

队长是乔纳森·阿切尔(Jonathan Archer),但你知道,我现在有点讨厌。

遍历条目

for ship in captains:
     print (ship + ": " + captains[ship])

上面代码的输出如下:

让我们看一个迭代字典中条目的小例子。 如果我想遍历字典中的每艘船并打印出captains,则可以在captains中键入ship,这将遍历字典中的每个键。 然后,我可以打印出每艘船的船长的查找值,这就是我到达那里的输出。

你有它。 这基本上是您在 Python 中会遇到的主要数据结构。 还有其他一些东西,例如集合,但是在本书中我们不会真正使用它们,所以我认为这足以使您入门。 在下一节中,让我们深入研究 Python 的细微差别。

Python 基础-第 2 部分

除了“Python 基础知识-第 1 部分”之外,让我们现在尝试详细了解更多 Python 概念。

Python 中的函数

让我们谈谈 Python 中的函数。 与其他语言一样,您可以使用一些函数,使您可以使用不同的参数反复地重复一组操作。 在 Python 中,执行此操作的语法如下:

def SquareIt(x):
    return x * x
print (SquareIt(2))

上面代码的输出如下:

4

您可以使用def关键字声明一个函数。 只是说这是一个函数,我们将其称为SquareIt,然后在括号内跟随参数列表。 这个特定的函数仅采用一个我们称为x的参数。 同样,请记住,空格在 Python 中很重要。 不会有任何花括号或任何包含此函数的东西。 它由空格严格定义。 因此,我们有一个冒号,表示该函数声明行已结束,但是事实是,它被一个或多个选项卡制表,从而告诉解释器我们实际上位于SquareIt函数之内。

因此def SquareIt(x):选项卡返回x * x,这将返回此函数中x的平方。 我们可以继续尝试一下。 print squareIt(2)是我们调用该函数的方式。 看起来就像是使用其他任何语言一样。 这应该返回数字4; 我们运行代码,实际上确实如此。 惊人的! 这很简单,函数就这么简单。 显然,如果需要,我可以有多个参数,甚至可以根据需要设置多个参数。

现在,您可以使用 Python 中的函数完成一些奇怪的事情,这很酷。 您可以做的一件事就是像传参数一样传递函数。 让我们仔细看一下这个例子:

#You can pass functions around as parameters
def DoSomething(f, x):
    return f(x)
print (DoSomething(SquareIt, 3))

前面代码的输出如下:

9

现在,我有一个名为DoSomethingdef DoSomething的函数,它将带有两个参数,一个我称为f,另一个我称为x,如果发生,我实际上可以将这些参数之一传递到函数中。 因此,请仔细考虑一下。 多看一点这个例子。 在这里,DoSomething(f,x):将返回xf; 它基本上会调用带有 x 作为参数的 f 函数,并且在 Python 中没有强类型,因此我们只需要确保传递给第一个参数的实际上是一个可以正常工作的函数 。

例如,我们说 print DoSomething,对于第一个参数,我们传入SquareIt(实际上是另一个函数)和数字3。 这应该做的就是说对SquareIt函数和3参数进行操作,这将返回(SquareIt, 3),而我上次检查的3平方是9,当然实际上可以。

对于您来说,这可能是一个新概念,将函数作为参数传递给周围,因此,如果您需要在此停留一分钟,请稍等片刻,然后尝试使用它,请随时这样做。 再次,我鼓励您停下来并按照自己的步调前进。

Lambda 函数-函数式编程

Lambda 函数的概念是另外一种类似于 Python 的事情,在其他语言中可能看不到,这就是函数式编程。 这个想法是您可以在函数中包含一个简单函数。 举个例子,这最有意义:

#Lambda functions let you inline simple functions
print (DoSomething(lambda x: x * x * x, 3))

上面代码的输出如下:

27

我们将打印DoSomething,并记住我们的第一个参数是一个函数,因此,除了传递命名函数外,我还可以使用lambda关键字内联声明此函数。 Lambda 基本上意味着我正在定义一个仅存在的未命名函数。 它是暂时的,它采用参数x。 在这里的语法中,lambda表示我正在定义某种内联函数,然后定义其参数列表。 它只有一个参数x和冒号,其后是该函数实际执行的操作。 我将使用x参数,并将其自身乘以三倍,以基本获得参数的立方。

在此示例中,DoSomething将作为第一个参数传入此 Lambda 函数,该函数将计算x3参数的立方。 那么,这实际上是在做什么呢? 该lambda函数本身就是一个函数,该函数在上一示例中传递到DoSomething中的f中,此处的x将是3。 这将返回xf,最终将对值3执行我们的 Lambda 函数。 这样3进入我们的x参数,然后我们的 Lambda 函数将其转换为3乘以3乘以3,当然也就是27

现在,当我们开始进行 MapReduce 和 Spark 之类的工作时,就会遇到很多问题。 因此,如果以后要处理 Hadoop 的各种技术,这是一个非常重要的概念。 再次,我鼓励您花些时间让它沉入其中,并根据需要了解发生了什么。

了解布尔表达式

布尔表达式语法有点怪异或不寻常,至少在 Python 中如此:

print (1 == 3)

上面代码的输出如下:

False

像往常一样,我们有一个双等于符号可以测试两个值之间的相等性。 所以1等于3,不,不等于False。 值False是由 F 指定的特殊值。请记住,当您要进行测试时,正在执行布尔运算时,相关的关键字是带有 T 的True和带有 F 的False。 与我使用过的其他语言略有不同,因此请记住这一点。

print (True or False)

上面代码的输出如下:

True

好吧,TrueFalseTrue,因为其中一个是True,请运行它,然后它返回True

if语句

print (1 is 3)

先前代码的输出如下:

False

我们可以做的另一件事是使用is,它等同于相同的事物。 它是 Python 式的平等表示,因此1 == 31 is 3是同一回事,但这被认为是使用 Python 的方式。 因此,1 is 3返回为False,因为1不是3

if-else循环

if 1 is 3:
    print "How did that happen?"
elif 1 > 3:
    print ("Yikes")
else:
    print ("All is well with the world")

上面代码的输出如下:

All is well with the world

我们也可以在此处执行if-elseelse-if块。 让我们在这里做一些更复杂的事情。 如果1 is 3,我将打印How did that happen?,但是1当然不是3,所以我们将退回到else-if块,否则,如果1不是3,我们测试是否1 > 3。 嗯,这也不是正确的,但是如果确实如此,我们将打印Yikes,最后我们将进入将打印All is well with the world的所有else子句。

实际上,1既不是3,也不是1大于3,并且肯定是All is well with the world。 因此,您知道其他语言具有非常相似的语法,但这是 Python 的特性以及如何执行if-elseelse-if块。 因此,请再次随身携带此笔记本。 以后可能是一个很好的参考。

for循环

我想在 Python 基础知识中涉及的最后一个概念是循环,我们已经看到了几个示例,但让我们再做一个:

for x in range(10):
 print (x),

先前代码的输出如下:

0 1 2 3 4 5 6 7 8 9

例如,我们可以使用此范围运算符自动定义范围内的数字列表。 因此,如果我们说range(10)中的for x,则range 10将产生一个09的列表,并通过对该列表中的x说,我们将遍历该列表中的每个单独条目,然后打印出来。 同样,print语句后的逗号表示不要给我换行,请继续。 因此,此输出最终是该列表的所有元素彼此相邻打印。

为了做一些更复杂的事情,我们将做类似的事情,但是这次我们将展示continuebreak的工作方式。 与其他语言一样,您实际上可以选择跳过循环迭代的其余处理,或者实际上过早地停止循环迭代:

for x in range(10):
    if (x is 1):
 continue
 if (x > 5):
    break
 print (x),

上面代码的输出如下:

0 2 3 4 5

在此示例中,我们将遍历 0 到 9 的值,如果我们打到数字 1,我​​们将继续进行操作,然后将其打印出来。 我们基本上将跳过数字 1,如果数字大于5,我们将中断循环并完全停止处理。 我们期望的输出是我们将打印出05的数字,除非它是1,在这种情况下,我们将跳过数字1,并且可以肯定的是,它就是这样做的。

while循环

另一种语法是while循环。 在大多数语言中,这都是一种标准的循环语法:

x = 0
while (x < 10):
    print (x),
    x += 1

先前代码的输出如下:

0 1 2 3 4 5 6 7 8 9

我们也可以说,以x = 0while (x < 10):开头,将其打印出来,然后以1递增x。 这将一遍又一遍,递增x直到它小于 10,这时我们跳出while循环,然后完成。 因此,它的功能与此处的第一个示例相同,只是风格不同。 它使用while循环打印出09的数字。 那里只是一些例子,没有什么太复杂的。 同样,如果您之前做过任何形式的编程或脚本编写,这都应该非常简单。

现在要真正让它陷入,我已经在整章中说过,进入那里,弄脏你的手,然后玩。 所以我会让你这样做。

探索活动

这是一项活动,对您来说是一个挑战:

这是一个不错的小代码块,您可以在其中开始编写自己的 Python 代码,运行它并对其进行操作,所以请这样做。 您面临的挑战是编写一些代码来创建整数列表,循环遍历该列表的每个元素,到目前为止,这非常容易,并且仅打印出偶数。

现在这应该不太困难。 本笔记本中有一些可以做所有这些事情的例子。 您要做的就是将其放在一起并使其运行。 因此,重点不是给您带来困难的东西。 我只是希望您在编写自己的 Python 代码并实际运行并看到它可以运行时获得一定的信心,所以请这样做。 我绝对鼓励您在这里进行互动。 因此,祝您好运,欢迎使用 Python。

所以,那显然是您的 Python 崩溃过程,那里只是一些非常基本的东西。 当我们在本书中遍历越来越多的示例时,由于您有更多的示例要看,这将变得越来越有意义,但是如果此时您确实感到有些害怕,那么也许您是编程或脚本编写的新手,在继续之前进行 Python 修订可能是一个好主意,但是如果您对到目前为止所看到的一切感到满意,请继续进行下去,我们将继续。

运行 Python 脚本

在整本书中,我们将使用到目前为止一直在使用的 IPython/Jupyter 笔记本格式(.ipynb文件),对于这样的书,这是一种很好的格式,因为它可以让我放一些小块代码,然后在其中放一些文字和说明操作的内容,然后您就可以尝试实际操作。

当然,从这个角度来看,这很棒,但是在现实世界中,您可能不会使用 IPython/Jupyter 笔记本在生产中实际运行 Python 脚本,所以让我简要地简要介绍一下运行 Python 代码的其他方法,以及其他交互方式。 因此,这是一个非常灵活的系统。 让我们来看看。

不仅仅是 IPython/Jupyter 笔记本的更多选择

我想确保您知道有多种方法可以运行 Python 代码。 现在,在整本书中,我们将使用 IPython/Jupyter 笔记本格式,但在现实世界中,您将不会像笔记本一样运行代码。 您将以独立的 Python 脚本运行它。 因此,我只想确保您知道该怎么做并查看其工作原理。

因此,让我们回到本书中的第一个示例,只是为了说明空白的重要性。 我们可以从笔记本格式中选择并复制该代码,然后将其粘贴到新文件中。

这可以通过单击最左侧的“新建”按钮来完成。 因此,让我们制作一个新文件并将其粘贴并保存该文件,并将其命名为test.py,其中py是我们提供给 Python 脚本的常用扩展名。 现在,我可以用几种不同的方式运行它。

在命令提示符下运行 Python 脚本

我实际上可以在命令提示符下运行脚本。 如果我转到“工具”,则可以转到“Canopy 命令提示符”,这将打开一个命令窗口,其中已包含用于运行 Python 的所有必要环境变量。 我只需要输入python test.py并运行脚本,结果就会出现:

因此,在现实世界中,您可能会做类似的事情。 可能在 Crontab 上或类似的东西上,谁知道呢? 但是在生产环境中运行真实的脚本就这么简单。 现在,您可以关闭命令提示符。

使用 Canopy IDE

回过头来,我还可以在 IDE 中运行脚本。 因此,在 Canopy 中,我可以转到“运行”菜单。 我可以去运行| 运行文件,或单击小播放图标,这也会执行我的脚本,并在输出窗口的底部看到结果,如以下屏幕快照所示:

因此,这是另一种方法,最后,您还可以在底部交互式显示的交互式提示中运行脚本。 实际上,我可以一次只键入一个 Python 命令,然后让它们执行并停留在该环境中:

例如,我可以说stuff,将其命名为list,并具有1234,现在我可以说len(stuff),那将给我4

我可以说for x in stuff:print x,我们得到的输出为1 2 3 4

因此,当您在底部的交互式提示中单击并一次执行一件事情时,就可以看到某种化妆脚本。 在此示例中,stuff是我们创建的变量,一个保留在内存中的列表,有点像该环境中其他语言中的全局变量。

现在,如果我确实想重置此环境,如果我想摆脱stuff并重新开始,那么执行此操作的方式是转到此处的“运行”菜单,然后可以说“重新启动内核”,这将会带给你一个空白面板:

所以现在我有了一个干净的新 Python 环境,在这种情况下,我叫它什么? 类型stuffstuff尚不存在,因为我有一个新的环境,但是我可以添加其他名称,例如[4, 5, 6]; 运行它,就可以了:

这样便有了运行 Python 代码的三种方式:IPython/Jupyter 笔记本,我们将在整本书中使用它,只是因为它是一个很好的学习工具,您还可以将脚本作为独立的脚本文件运行,也可以在交互式命令提示符中执行 Python 代码。

因此,您拥有了它,并且这里有三种不同的方式来运行 Python 代码以及在生产中进行实验和运行。 所以记住这一点。 在本书的其余部分中,我们将一直使用笔记本电脑,但是,到时候,您还有其他选择。

总结

在本章中,我们从构建书中最重要的垫脚石-安装有思想的檐篷开始了旅程。 然后,我们转向安装其他库并安装不同类型的包。 我们还借助各种 Python 代码掌握了 Python 的一些基础知识。 我们介绍了诸如模块,列表和元组之类的基本概念,并最终通过更好地了解 Python 中的函数和循环知识,进一步了解了 Python 基础知识。 最后,我们从运行一些简单的 Python 脚本开始。

在下一章中,我们将继续理解统计和概率的概念。

二、统计和概率回顾和 Python 实践

在本章中,我们将介绍一些统计和概率的概念,这可能会让您有些耳目一新。 如果您想成为数据科学家,这些概念很重要。 我们将看到一些示例以更好地理解这些概念。 我们还将研究如何使用实际的 Python 代码实现这些示例。

本章将涵盖以下主题:

  • 您可能会遇到的数据类型以及如何相应地处理它们
  • 均值,中位数,众数,标准差和方差的统计概念
  • 概率密度函数和概率质量函数
  • 数据分布的类型以及如何绘制它们
  • 了解百分位数和矩

资料类型

好吧,如果您想成为数据科学家,我们需要讨论您可能会遇到的数据类型,如何对它们进行分类以及如何以不同的方式对待它们。 让我们深入了解您可能会遇到的各种数据类型:

这看起来很基础,但是我们必须从简单的内容开始,然后逐步发展到更复杂的数据挖掘和机器学习方面。 了解您要处理的数据类型非常重要,因为不同的技术可能会根据您要处理的数据类型而产生不同的细微差别。 因此,如果您愿意的话,有几种类型的数据,而我们将主要关注三种特定类型的数据。 他们是:

  • 数值数据
  • 分类数据
  • 序数数据

同样,您可能会使用不同的技术来处理不同类型的数据,因此在分析数据时,您始终需要记住要处理的数据类型。

数值数据

让我们从数值数据开始。 它可能是最常见的数据类型。 基本上,它表示您可以测量的一些可量化的事物。 例如,人员高度,页面加载时间,股票价格等。 事物各不相同,您可以衡量的事物,具有广泛可能性的事物。 现在基本上有两种数值数据,所以如果可以的话,可以说是一种风味。

离散数据

有离散数据,该数据是基于整数的,例如可以是某种事件的计数。 例如,一个客户一年内进行了几次购买。 好吧,那只能是离散值。 他们买了一件事,或者他们买了两件事,或者他们买了三件事。 他们买不了 2.25 东西或三分之三或四分之三的东西。 这是一个离散值,具有整数限制。

连续数据

数值数据的另一种类型是连续数据,这种东西具有无限范围的可能性,您可以在其中进行细分。 因此,例如,回到人的身高,人的身高是无限的。 您可能身高 5 英尺高 10.37625 英寸,或者花很多时间在网站上进行诸如结帐之类的操作,所花时间为 10.7625 秒,或者一天中有多少降雨。 再次,那里有无限的精度。 这就是连续数据的一个例子。

回顾一下,数字数据可以用数字进行定量测量,它既可以是离散的(如事件计数一样基于整数),也可以是连续的(可以为数据提供无限范围的精度)。

分类数据

我们将要讨论的第二种数据类型是分类数据,这是没有内在数值含义的数据。

大多数时候,您不能真正将一个类别直接与另一个类别进行比较。 诸如性别,是/否问题,种族,居住状态,产品类别,政党等内容; 您可以将数字分配给这些类别,并且经常会分配,但是这些数字没有内在的含义。

因此,例如,我可以说德克萨斯州的面积大于佛罗里达州的面积,但我不能仅说德克萨斯州大于佛罗里达州,它们只是类别。 它们没有真正的数值可量化的含义,这只是我们对不同事物进行分类的方式。

再一次,我可能会对每种状态进行某种数值分配。 我的意思是,我可以说佛罗里达州是 3 号州,德克萨斯州是 4 号州,但是 3 号和 4 号州之间没有真正的关系,对,这只是更简洁地表示这些类别的简写。 同样,分类数据没有任何内在的数值含义; 这只是您选择根据类别拆分一组数据的一种方式。

序数数据

关于数据类型,您通常会听到的最后一个类别是序数数据,它是数字数据和分类数据的混合。 一个常见的例子是电影或音乐的星级,或者您拥有什么。

在这种情况下,我们得到的分类数据可能是 1 到 5 颗星,其中 1 颗代表劣而 5 颗代表优,但它们确实具有数学意义。 我们确实知道 5 表示它比 1 更好,因此在这种情况下,我们拥有不同类别之间具有数值关系的数据。 因此,就质量而言,我可以说 1 星小于 5 星,可以说 2 星小于 3 星,可以说 4 星大于 2 星。 现在,您还可以将实际的恒星数量视为离散的数值数据。 因此,在这些类别之间绝对是一条很好的界限,在许多情况下,您实际上可以将它们互换使用。

因此,您有三种不同的类型。 有数字,分类和有序数据。 让我们看看它是否沉入其中。不要担心,我不会让您参与工作或其他任何事情。

快速测验:对于这些示例中的每个,数据是数字的,分类的还是有序的?

  1. 让我们从您的油箱中有多少气体开始。 你怎么认为? 好吧,正确的答案是数字。 这是一个连续的数值,因为您的储罐中可能存在无限范围的气体。 是的,我的意思是,您可以容纳多少气体可能有一个上限,但是您拥有多少气体的可能值的数量没有尽头。 它可能是储罐的四分之三,可能是储罐的六分之一,可能是储罐的1 / pi,我的意思是谁知道,对吗?

  2. 如果您以 1 到 4 的等级阅读您的整体健康状况,那这些选择对应于差,中,好和优秀类别,那又如何呢? 你怎么认为? 这是序数数据的一个很好的例子。 这非常类似于我们的电影收视率数据,并且再次取决于您的建模方式,您可能也可以将其视为离散的数值数据,但从技术上讲,我们将其称为序数数据。

  3. 那你同学的种族呢? 这是分类数据的一个很明显的例子。 您无法真正将紫色人与绿色人进行比较,对,他们只是紫色和绿色,但是您可能需要研究它们并了解其他维度之间的区别。

  4. 几岁的同学年龄如何? 那里有一个技巧问题; 如果我说它必须是整数年,例如 40、50 或 55 年,那么那将是离散的数值数据,但是如果我具有更高的精度(例如 40 年 3 个月和 2.67 天),那将是连续的数值数据,但是无论哪种方式,它都是数值数据类型。

  5. 最后,花钱在商店里。 同样,这可能是连续数值数据的一个示例。 同样,这仅是重要的,因为您可能将不同的技术应用于不同类型的数据。

例如,在某些概念中,我们可能对分类数据执行一种类型的实现,而对数值数据进行另一种类型的实现。

因此,您只需了解通常会发现的不同类型的数据,并且在本书中我们将重点介绍这些数据。 它们都是非常简单的概念:您拥有数字,分类和有序数据,并且数字数据可以是连续或离散的。 根据要处理的数据类型,可以对数据应用不同的技术,我们将在整本书中介绍这一点。 让我们继续。

均值,中位数和众数

让我们来复习一下统计 101。这就像是小学学习的东西,但是再次学习它,看看如何使用这些不同的技术是很不错的:均值,中位数和众数。 我敢肯定您之前听过这些术语,但是很高兴看到它们的用法有所不同,所以让我们深入研究。

既然我们已经开始真正地研究一些实际的统计数据,那么这应该是对大多数人的回顾,快速复习。 让我们看一些实际数据,并弄清楚如何测量这些东西。

均值

您可能知道,平均值只是平均值的另一个名称。 要计算数据集的平均值,您要做的就是将所有值相加并除以您拥有的值数。

Sum of samples/Number of samples

让我们以这个示例为例,它计算出我附近每所房屋的平均孩子数。

假设我在附近的家中挨家挨户问每个人,家里有多少个孩子。 (顺便说一句,它是离散数值数据的一个很好的例子;还记得上一节吗?)假设我四处走走,发现第一间房子里没有孩子,第二间房子里有两个孩子, 第三户有三个孩子,依此类推。 我收集了这个离散的数字数据的小数据集,然后找出平均值,我要做的就是将它们加在一起,然后除以我去过的房屋数量。

我这条街上每所房子的孩子人数:

0, 2, 3, 2, 1, 0, 0, 2, 0

平均值为(0 + 2 + 3 + 2 + 1 + 0 + 0 + 2 + 0) / 9 = 1.11

得出的结果是 0 加 2 加 3 加所有其余的这些数字除以我看过的房屋总数,即 9,而样本中每座房屋的平均孩子数为 1.11。 所以,意思是。

中位数

中位数有些不同。 计算数据集中位数的方式是对所有值进行排序(按升序或降序),然后取一个中间值。

因此,举例来说,让我们使用附近社区的相同儿童数据集

0, 2, 3, 2, 1, 0, 0, 2, 0

我将对其进行数字排序,然后可以将数据中间的 slap dab 取为 1。

0, 0, 0, 0, 1, 2, 2, 2, 3

同样,我要做的就是获取数据,对其进行数字排序,然后获取中心点。

如果您有偶数个数据点,则中位数实际上可能落在两个数据点之间。 目前尚不清楚哪一个实际上是中间的。 在这种情况下,您要做的就是取两个确实落在中间的平均值,然后将该数字视为中位数。

离群因素

现在,在前面的每个家庭中孩子数量的示例中,中位数和均值非常接近,因为离群值并不多。 我们有 0、1、2 或 3 个孩子,但我们没有一个古怪的家庭有 100 个孩子。 那确实会使均值产生偏差,但可能不会使中位数变化太大。 这就是为什么中位数通常是非常有用的东西,而且经常被忽略。

中位数比均值更不容易受到离群值的影响。

人们有时会误导统计人员。 我将尽可能在本书中始终指出这一点。

例如,您可以谈论美国的平均家庭收入,而从我去年查到的数据来看,去年的实际数字是 72,000 美元左右,但这并不能真正提供典型美国人的准确情况。 制造。 这是因为,如果您查看收入中位数,则要低得多,为 51,939 美元。 这是为什么? 好吧,因为收入不平等。 美国有一些非常有钱的人,在许多国家中也是如此。 美国甚至不是最坏的,但您知道那些亿万富翁,生活在华尔街,硅谷或其他超级富豪地区的那些超级富豪,他们的意思是平均的。 但是它们很少,以至于它们并没有真正影响中位数。

这是一个很好的例子,在这个例子中,中位数比平均数更能说明典型数字或数据点的故事。 每当有人谈论均值时,您都必须考虑数据分布是什么样的。 是否有异常值可能会歪曲那个意思? 如果答案可能是肯定的,那么您还应该要求中位数,因为通常,中位数比均值或平均值提供的洞察力更大。

众数

最后,我们将讨论众数。 在实践中,这种情况并不会经常出现,但是如果不谈论众数,就无法谈论均值和中位数。 所有众数均值是数据集中最常见的值。

让我们回到我的例子中,每个房子里有多少孩子。

0, 2, 3, 2, 1, 0, 0, 2, 0

每个值有多少个:

0: 4, 1: 1, 2: 3, 3: 1

众数为 0

如果我只看一下最常出现的数字,它的值为 0,因此该数据的众数为 0。在该邻里给定房屋中,最常见的孩子数是没有孩子,这意味着 。

现在,这实际上是连续数据与离散数据的一个很好的例子,因为这仅适用于离散数据。 如果我有连续的数据范围,那么除非我以某种方式将其量化为离散值,否则我无法真正谈论出现的最常见值。 因此,我们已经在这里遇到了一个示例,其中数据类型很重要。

众数通常仅与离散数值数据相关,而与连续数据无关。

许多现实世界中的数据往往是连续的,所以也许这就是为什么我对众数知之甚少的原因,但是为了完整起见,我们在这里看到它。

简而言之,您便拥有了:均值,中位数和众数。 您可以做一些最基本的统计工作,但是我希望您在中位数和均值之间进行选择的重要性方面有所收获。 他们可以讲述截然不同的故事,但是人们倾向于将它们等同起来,因此请确保您是一名负责任的数据科学家,并以能够传达您想要表达的含义的方式来代表数据。 如果要显示一个典型值,由于离群值,通常选择中值比使用平均值更好,因此请记住这一点。 让我们继续。

在 Python 中使用均值,中位数和众数

让我们开始用 Python 进行一些实际的编码,看看如何在 IPython 笔记本文件中使用 Python 计算均值,中位数和众数。

因此,如果您想继续,请从本节的数据文件中打开MeanMedianMode.ipynb文件,我绝对鼓励您这样做。 如果您需要返回上一节中有关从何处下载这些资料的信息,请执行此操作,因为本节将需要这些文件。 让我们潜入吧!

使用 NumPy 包计算平均值

我们要做的是创建一些虚假的收入数据,回到上一节中的示例。 在本例中,我们将创建一些假数据,在这些数据中,典型的美国人每年的收入约为 27,000 美元,我们要说的是正态分布且标准差为 15,000。 所有数字都完全组成了,如果您还不知道正态分布和标准差的含义,请不要担心。 我将在本章的稍后部分介绍,但是我只想让您知道这些不同参数在本例中代表什么。 稍后将有意义。

在我们的 Python 笔记本中,请记住将 NumPy 包导入 Python,这使得计算均值,中位数和众数变得非常容易。 我们将使用import numpy as np指令,这意味着我们可以从现在开始使用np作为调用numpy的简写。

然后,我们将使用np.random.normal函数创建一个称为incomes的数字列表。

import numpy as np 

incomes = np.random.normal(27000, 15000, 10000) 
np.mean(incomes) 

np.random.normal函数的三个参数表示我希望数据以27000为中心,标准差为15000,并且我希望 python 在此列表中建立10000数据点。

完成此操作后,我只需在数据列表incomes上调用np.mean,即可计算出这些数据点的平均值或均值。 就这么简单。

让我们继续运行它。 确保选择了该代码块,然后可以单击“播放”按钮以实际执行该代码块,并且由于这些收入数字包含随机成分,因此每次运行该代码块时,我都会得到略有不同的结果,但是它应始终非常接近27000

Out[1]: 27173.098561362742

好的,这就是使用 Python 计算均值的全部内容,仅使用 NumPy(np.mean)即可使其变得非常简单。 您不必编写一堆代码或实际上将所有内容加起来就可以算出您拥有多少项并进行除法。 NumPy 的意思是,这一切都为您做。

使用 Matplotlib 可视化数据

让我们可视化这些数据,使其更具意义。 因此,还有一个名为matplotlib的包,以后我们将再次讨论更多,但这是一个包,可以让我在 IPython 笔记本中制作漂亮的图形,因此这是一种可视化您的数据的简单方法,看看发生了什么。

在此示例中,我们使用matplotlib创建我们的收入数据的直方图,该直方图分为50个不同的存储桶。 因此,基本上,我们要获取连续数据并将其离散化,然后可以在matplotlib.pyplot上调用show来实际显示该直方图。 请参考以下代码:

%matplotlib inline 
import matplotlib.pyplot as plt 
plt.hist(incomes, 50) 
plt.show() 

继续,选择代码块并点击播放。 它实际上将为我们创建一个新图,如下所示:

如果您不熟悉直方图或需要复习,可以用以下方法来解释:将数据离散化后的每个存储桶都在显示该数据的频率。

因此,举例来说,对于每个给定的值范围,我们看到在该邻域中大约有600个数据点。 有很多人都在 27,000 大关附近,但是当您走到80,000之类的离群值时,并没有很多人,而且显然还有一些穷人甚至在-40,000时负债累累,但又一次,它们非常罕见且不太可能,因为我们定义了正态分布,这就是正态概率曲线的样子。 再次,我们将在以后更详细地讨论这一点,但是如果您还不知道,那么我只是想让您想到这个想法。

使用 NumPy 包计算中位数

好了,因此计算中位数与计算均值一样简单。 就像我们有 NumPy mean一样,我们也有 NumPy median函数。

我们可以只使用incomes上的median函数,这是我们的数据列表,这将为我们提供中位数。 在这种情况下,总计 26,911 美元,与平均值 26988 美元相差无几。 同样,初始数据是随机的,因此您的值将略有不同。

np.median(incomes) 

以下是上述代码的输出:

Out[4]: 26911.948365056276 

我们不希望看到很多异常值,因为这是一个很好的正态分布。 当您没有很多奇怪的异常值时,中值和均值将是可比的。

分析异常值的影响

为了证明一点,让我们添加一个异常值。 我们带唐纳德·特朗普; 我认为他有资格成为离群值。 让我们继续添加他的收入。因此,我将使用np.append手动将其添加到数据中,并假设将 10 亿美元(显然不是唐纳德·特朗普的实际收入)添加到收入数据中。

incomes = np.append(incomes, [1000000000]) 

我们将要看到的是,这个离群值并没有真正改变很多中位数,您知道,这仍然是大约 26,911 美元,因为我们实际上并没有改变中间点的位置, 该值,如以下示例所示:

np.median(incomes) 

这将输出以下内容:

Out[5]: 26911.948365056276 

这给出了一个新的输出:

np.mean(incomes) 

以下是上述代码的输出:

Out[5]:127160.38252311043 

啊哈,就在那里! 这是一个很好的例子,尽管人们倾向于用平凡的语言将中位数和平均数等同起来,但是它们可能截然不同,并讲述了一个非常不同的故事。 因此,一个离群值导致此数据集中的平均收入每年超过 127160 美元,但更准确的图片对于该数据集中的典型数字来说,每年的收入接近 27,000 美元。 我们的均值只有一个较大的异常值。

这个故事的寓意是:如果您怀疑其中可能包含离群值,那么请任何谈论均值或均值的人都参与进来,而收入分配肯定就是这种情况。

使用 SciPy 包计算众数

最后,让我们看看众数。 我们将只生成一堆随机整数,准确地说是其中的 500 个,范围在1890之间。 我们将为人们创造一堆假年龄。

ages = np.random.randint(18, high=90, size=500) 
ages 

您的输出将是随机的,但应类似于以下屏幕截图:

现在,SciPy(类似于 NumPy)是一堆类似方便的统计函数,因此我们可以使用以下语法从 SciPy 导入stats。 它与我们之前看到的有点不同。

from scipy import stats 
stats.mode(ages) 

该代码意味着,从scipy包导入stats,而我仅将该包称为stats,意味着我不需要像以前使用 NumPy 时那样的别名, 只是做事的方式不同。 两种方式都可以。 然后,我在ages上使用了stats.mode函数,这是我们随机年龄的列表。 当我们执行上面的代码时,我们得到以下输出:

Out[11]: ModeResult(mode=array([39]), count=array([12])) 

因此,在这种情况下,实际众数是39,它是该数组中最常见的值。 实际上发生了12次。

现在,如果我实际创建一个新的发行版,我将期望得到一个完全不同的答案,因为这些数据实际上是完全随机的,这些数字是多少。 让我们再次执行上述代码块以创建新的发行版。

ages = np.random.randint(18, high=90, size=500) 
ages 

from scipy import stats 
stats.mode(ages) 

随机化方程的输出如下:

确保选择了该代码块,然后可以单击“播放”按钮以实际执行它。

在这种情况下,众数最终为29次,发生了14次。

Out[11]: ModeResult(mode=array([29]), count=array([14])) 

因此,这是一个非常简单的概念。 您可以再玩几次,只是为了好玩。 这有点像旋转轮盘赌。 我们将再次创建一个新的发行版。

简而言之,您将拥有平均值,中位数和众数。 使用 SciPy 和 NumPy 包非常简单。

一些练习

在本节中,我将给您一些任务。 如果打开MeanMedianExercise.ipynb文件,则可以玩一些东西。 我希望您卷起袖子,然后尝试这样做。

在文件中,我们有一些随机的电子商务数据。 该数据代表的是每笔交易的总支出,同样,就像我们之前的示例一样,它只是数据的正态分布。 我们可以运行它,而您的作业是继续使用 NumPy 包查找该数据的均值和中位数。 您几乎可以想象到的最简单的任务。 您需要的所有技术都在我们先前使用的MeanMedianMode.ipynb文件中。

这里的重点并不是要挑战您,而只是让您实际编写一些 Python 代码并说服自己您实际上可以得到结果并使此处发生某些事情。 因此,继续进行下去。 如果您想更多地使用它,请随时在此处试用数据分布,看看您可以对数字产生什么影响。 尝试添加一些离群值,就像我们对收入数据所做的那样。 这是学习这些东西的方式:掌握基础知识,然后学习高级知识。 有它,有乐趣。

准备就绪后,让我们继续下一个概念,即标准差和方差。

标准差和方差

让我们谈谈标准差和方差。 您可能已经听说过这些概念和术语,但是让我们更深入地了解它们的真正含义以及如何计算它们。 这是对数据分布范围的一种度量,这将在几分钟后变得更有意义。

标准差和方差是数据分发的两个基本量,您将在本书中反复看到。 因此,如果需要复习,让我们看看它们是什么。

方差

让我们看一个直方图,因为方差和标准差都与数据的散布,数据集的分布形状有关。 看看下面的直方图:

假设我们有一些关于飞机到达机场的频率的数据,例如,该直方图表明我们每分钟有大约 4 班飞机到达,并且在我们查看该数据的 12 天左右发生。 但是,我们也有这些异常值。 我们有一个非常慢的一天,每分钟只有一次到达,而我们只有一个非常快的一天,每分钟接近 12 个到达。 因此,读取直方图的方法是查找给定值的存储桶,它告诉您该值在数据中出现的频率,直方图的形状可以告诉您很多有关给定集合的数据的概率分布。

从这些数据中我们知道,我们的机场很可能每分钟有 4 个左右的进港旅客,但几乎不可能有 1 或 12 个进港旅客,我们还可以专门讨论一下两者之间所有数字的机率。 因此,每分钟不仅有 12 个到达的可能性,每分钟 9 个到达的可能性也很小,而且一旦我们开始达到 8 个左右,事情就会开始加速。 直方图可以提供很多信息。

方差度量数据的分布的程度。

测量方差

我们通常将方差称为σ²,您会立即找出原因,但现在,仅知道方差是均值平方差的平均值。

  1. 要计算数据集的方差,首先要弄清楚它的平均值。 假设我有一些数据可以代表任何东西。 假设在一个小时内排队的最大人数。 在第一个小时,我观察到有一个人在排队,然后依次是 4、5、4、8。
  2. 计算方差的第一步只是找到该数据的平均值或平均值。 我将它们全部相加,将总和除以数据点的数量,得出 4.4,这是排队的平均人数(1 + 4 + 5 + 4 + 8) / 5 = 4.4
  3. 现在,下一步是找到每个数据点均值的差异。 我知道平均值是 4.4。 所以对于我的第一个数据点,我有 1,所以1 - 4.4 = -3.4,下一个数据点是 4,所以4 - 4.4 = -0.4, 等等等等。 好的,因此我得到了这些正数和负数,它们代表每个数据点(-3.4, -0.4, 0.6, -0.4, 3.6)与平均值的方差。
  4. 现在,我需要一个代表整个数据集方差的数字。 因此,我接下来要做的就是找到这些差异的平方。 我只是要仔细研究均值中的每一个原始差异,然后将它们平方。 这有两个不同的原因:

让我们看一下发生了什么,所以(-3.4)²是正 11.56,而(-0.4)²最终是一个小得多的数字,即 0.16,因为这更接近 4.4 的平均值。 同样,0.6²接近平均值,仅为 0.36。 但是当我们达到正离群值时,3.6²最终为 12.96。 这样就得出:11.56, 0.16, 0.36, 0.16, 12.96

为了找到实际的方差值,我们只取所有那些平方差的平均值。 因此,我们将所有这些平方的方差相加,将总和除以 5(即我们拥有的值的数量),最后得出方差 5.04。

好的,这就是所有差异。

标准差

现在,通常,我们谈论标准差多于方差,事实证明标准差只是方差的平方根。 就这么简单。

因此,如果我的差异为5.04,则标准差为2.24。 现在您知道为什么我们说方差= σ²。 这是因为σ本身代表标准差。 因此,如果我取σ²的平方根,则得到σ。 在此示例中最终为 2.24。

使用标准差识别离群值

这是我们在前面的示例中查看的用于计算方差的实际数据的直方图。

现在我们看到4在我们的数据集中出现了两次,然后我们有一个1,一个5和一个8

通常使用标准差作为思考如何识别数据集中异常值的方法。 如果我说我在 4.4 平均值的一个标准差内,则认为这是正态分布中的典型值。 但是,您可以在上图中看到18实际上不在该范围内。 因此,如果我采用 4.4 的正负 2.24,我们最终会在72周围,而18都落在标准差的范围。 因此,我们可以从数学上说 1 和 8 是离群值。 我们不必猜测和盯着它。 现在仍然需要根据数据点与平均值有多少标准差来判断离群值。

通常,您可以通过与平均值之间的标准差(或有时为多少个σ)来谈论一个数据点有多少离群值。

因此,您会看到在现实世界中使用的标准差。

总体方差与样本方差

标准差和方差有一点细微差别,那就是您在谈论总体与样本方差时。 如果您要使用一整套数据,一整套观察结果,那么您将按照我告诉您的那样做。 您只需从均值中取所有平方方差的平均值即可,这就是您的方差。

但是,如果要采样数据,也就是说,如果只是为了简化计算而获取数据的子集,则必须做一些不同的事情。 除了将样本数除以 1 之外,不除以样本数。让我们来看一个示例。

我们将使用刚为排队的人研究的样本数据。 我们将方差的总和除以 5,即得到的数据点数为 5.04。

σ² = (11.56 + 0.16 + 0.36 + 0.16 + 12.96) / 5 = 5.04

如果我们看一下由表示的样本方差,它是由方差平方和除以 4 得出的,即n-1。 这给我们提供了 6.3 的样本方差。

S² = (11.56 + 0.16 + 0.36 + 0.16 + 12.96) / 4 = 6.3

同样,如果这是我们从较大的数据集中获取的某种样本,那么您将要这样做。 如果它是完整的数据集,请除以实际数字。 好的,这就是我们计算总体和样本方差的方式,但是其背后的实际逻辑是什么?

数学解释

至于为什么总体和样本方差之间存在差异,它涉及关于您可能不想考虑太多的概率的真正怪异的东西,并且它需要一些花哨的数学符号,在本书中我尽量避免使用符号,因为我认为概念更重要,但这已经足够基本了,您将一遍又一遍地看到它。

如我们所见,总体方差通常指定为 σ²,其中σ为标准差,我们可以说这是每个数据点X的总和减去平均值,μ,平方,即每个样本在N(数据点的数量)上平方的方差,我们可以用以下公式表示:

  • X表示每个数据点
  • µ表示平均值
  • N表示数据点数

样本方差类似地指定为,其等式如下:

  • X表示每个数据点
  • M表示平均值
  • N-1表示数据点数减去 1

这里的所有都是它的。

分析直方图上的标准差和方差

让我们在这里编写一些代码,并使用一些标准差和方差。 因此,如果您拉起StdDevVariance.ipynb文件 IPython 笔记本,然后在这里跟随我。 请这样做,因为最后有一项活动我想让您尝试。 我们将在此处执行的操作与前面的示例一样,因此从以下代码开始:

%matplotlib inline 
import numpy as np 
import matplotlib.pyplot as plt 
incomes = np.random.normal(100.0, 20.0, 10000) 
plt.hist(incomes, 50) 
plt.show() 

我们使用matplotlib绘制一些正态分布的随机数据的直方图,并将其称为incomes。 我们说它将以100为中心(希望这是一个小时费率或某值,而不是每年,或一些怪异的面额),其标准差为2010,000数据点。

让我们继续执行上面的代码块并将其绘制,如下图所示,生成它:

我们有 10,000 个以 100 个为中心的数据点。正态分布和 20 个标准差(衡量此数据的传播程度)可以看到,最常见的情况是在 100 个左右,并且随着我们的发展越来越远 ,事情变得越来越不可能了。 我们指定的标准差点 20 在 80 左右,在 120 左右。您可以在直方图中看到这是事物开始急剧下降的点,因此我们可以说超出标准差边界的事物是不寻常的。

使用 Python 计算标准差和方差

现在,NumPy 还使计算标准差和方差变得异常容易。 如果要计算我们生成的该数据集的实际标准差,只需在数据集本身上调用std函数。 因此,当 NumPy 创建列表时,它不仅仅是一个普通的 Python 列表,实际上还附加了一些其他内容,因此您可以在其上调用函数,例如std作为标准差。 让我们现在开始:

incomes.std() 

这给了我们类似下面的输出(请记住,我们使用了随机数据,因此您的数字将与我的数字不完全相同):

20.024538249134373 

执行该操作时,我们得到的数字接近 20,因为这是我们创建随机数据时指定的数字。 我们希望标准差为 20。可以肯定的是 20.02,非常接近。

差异只是调用var的问题。

incomes.var() 

这给了我以下内容:

400.98213209104557 

结果接近 400,即 20 2 。 是的,所以这个世界很有意义! 标准差只是方差的平方根,或者您可以说方差是标准差的平方。 果然,那行得通,所以世界按其应有的方式行事。

自己尝试

我想让您潜入这里并真正玩转它,使其真实,因此在生成正常数据时尝试不同的参数。 记住,这是对数据分布形状的度量,因此,如果我更改该中心点会怎样? 有关系吗? 它实际上会影响形状吗? 您为什么不尝试一下并找出答案?

尝试弄乱我们指定的实际标准差,以了解对图形形状的影响。 也许尝试使用 30 的标准差,就可以知道它实际如何影响事物。 让我们将其变得更加生动,例如 50。只要玩一下 50。您将看到图形开始变得有点胖。 尝试不同的值,只需要了解这些值的运作方式即可。 这是真正直观地了解标准差和方差的唯一方法。 来看看一些不同的例子,看看它的效果。

这就是实践中的标准差和方差。 您在那里接触了其中的一些东西,我希望您能花一点时间来熟悉它。 这些是非常重要的概念,我们将在整本书中谈论很多标准差,而且毫无疑问,在您从事数据科学的整个职业中,因此请确保掌握这一点。 让我们继续。

概率密度函数和概率质量函数

因此,对于本书中的某些示例,我们已经看到了一些正态分布函数的示例。 那是概率密度函数的一个例子,那里还有其他类型的概率密度函数。 因此,让我们深入了解它的真正含义以及它们的其他示例。

概率密度函数和概率质量函数

我们已经为本书中所见的一些代码看到了一些正态分布函数的示例。 那是概率密度函数的一个例子,那里还有其他类型的概率密度函数。 让我们深入研究一下,这实际上意味着什么,以及它们的其他一些示例。

概率密度函数

让我们谈谈概率密度函数,我们已经在书中使用了其中之一。 我们只是没有这样称呼。 让我们形式化一些我们已经谈论过的东西。 例如,我们已经看到几次正态分布,这就是概率密度函数的一个例子。 下图是正态分布曲线的示例

从概念上来说,尝试将此图视为出现给定值的概率很容易,但是当您谈论连续数据时,这有点误导。 因为在连续数据分布中有无限多个实际可能的数据点。 可能为 0 或 0.001 或 0.00001,因此发生非常特定的值的实际概率非常非常小,并且无限小。 概率密度函数实际上告诉了给定范围的值出现的概率。 因此,这就是您必须考虑的方式。

因此,例如,在上图中所示的正态分布中,在平均值(0)和一个与平均值的标准差()之间,存在34.1%值落在该范围内的机会。 您可以根据需要加强或扩展它,找出实际值,但这是考虑概率密度函数的方法。 对于给定的值范围,它为您提供了一种方法来找出该范围出现的可能性。

  • 您可以在图表中看到,在接近一个平均值(-1σ)的情况下,接近平均值(0), 很可能降落在那里。 我的意思是,如果您将 34.1 和 34.1 相加,等于 68.2%,您的落地概率就在平均值的一个标准差之内。
  • 但是,当您获得两个到三个标准差(-3σ-2σ)时, 下降到略高于 4%(准确地说是 4.2%)。
  • 当您超出三个标准差(-3σ)时,我们的误差就远远小于 1%。

因此,图形只是可视化和讨论给定数据点发生概率的一种方式。 同样,概率分布函数为您提供数据点落入给定值的某个给定范围内的概率,而正态函数只是概率密度函数的一个示例。 我们稍后再看。

概率质量函数

现在,当您处理离散数据时,关于无限数量的可能值的细微差别就消失了,我们称之为不同的东西。 因此,这是一个概率质量函数。 如果要处理离散数据,则可以讨论概率质量函数。 这是一个图形,可以帮助可视化此图形:

例如,您可以在图中所示的黑色曲线上绘制连续数据的正态概率密度函数,但是如果我们像对直方图那样将其量化为离散数据集,则可以说数字 3 设置次数,实际上您可以说数字 3 发生的可能性略高于 30%。 因此,概率质量函数是我们可视化离散数据出现概率的方式,它看起来很像直方图,因为它基本上是直方图。

术语差异:概率密度函数是一条实线,描述了连续数据发生一系列值的概率。 概率质量函数是在数据集中出现的给定离散值的概率。

数据分布类型

让我们来看一些一般的概率分布函数和数据分布的真实示例,并在数据分布以及如何可视化它们并在 Python 中使用它们的过程上多花些头。

继续并从书籍材料中打开Distributions.ipynb,如果愿意,您可以在这里与我一起关注。

均匀分布

让我们从一个非常简单的示例开始:均匀分布。 均匀分布仅表示在给定范围内出现值的概率恒定为常数。

import numpy as np 
Import matplotlib.pyplot as plt 

values = np.random.uniform(-10.0, 10.0, 100000) 
plt.hist(values, 50) 
plt.show() 

因此,我们可以使用 NumPy random.uniform函数创建均匀分布。 前面的代码说,我想要一个均匀分布的随机值集,范围在-1010之间,而我想要其中的100000。 然后,如果我创建这些值的直方图,则可以看到如下所示。

在数据中出现给定值或值范围的可能性几乎相等。 因此,与正态分布不同,在正态分布中,我们看到了均值附近的值集中,而均匀分布在您定义的范围内的任何给定值上具有相等的概率。

那么,这种概率分布函数是什么样的呢? 好吧,我希望基本上不会在-10的范围内或超出10的范围内。 但是,当我介于-1010之间时,我会看到一条平线,因为这些值范围中的任何一个出现的可能性都是恒定的。 因此,在均匀分布中,您会在概率分布函数上看到一条平线,因为基本上存在恒定的概率。 每个值,每个值范围都有与其他任何值一样出现的机会。

正态分布或高斯分布

现在,我们已经在本书中看到了正态分布函数(也称为高斯分布)。 您实际上可以使用 Python 可视化它们。 scipy.stats.norm包函数中有一个称为pdf(概率密度函数)的函数。

因此,让我们看下面的示例:

from scipy.stats import norm 
import matplotlib.pyplot as plt 

x = np.arange(-3, 3, 0.001) 
plt.plot(x, norm.pdf(x)) 

在前面的示例中,我们正在创建一个 x 值列表,以使用arange函数绘制介于-3 和 3 之间的值,并在它们之间以 0.001 为增量。 因此,这些是图形上的 x 值,我们将使用这些值绘制x轴。y轴将成为正态函数norm.pdf,即在这些 x 值上正态分布的概率密度函数。 我们最终得到以下输出:

具有正态分布的 pdf 函数看起来就像在上一节中一样,即,对于我们提供的给定数字,正态分布,其中 0 表示平均值,数字-3-2-1123是标准差。

现在,我们将生成具有正态分布的随机数。 我们已经做过几次了; 考虑一下这。 请参考以下代码块:

import numpy as np 
import matplotlib.pyplot as plt 

mu = 5.0 
sigma = 2.0 
values = np.random.normal(mu, sigma, 10000) 
plt.hist(values, 50) 
plt.show() 

在上面的代码中,我们使用 NumPy 包的random.normal函数,并且第一个参数mu表示要围绕数据居中的平均值。 sigma是该数据的标准差,基本上是它的传播。 然后,我们使用正态概率分布函数(此处为10000)指定所需的数据点数。 因此,这是使用概率分布函数(在本例中为正态分布函数)生成一组随机数据的方法。 然后,我们可以使用分解为50存储桶的直方图来进行绘制并显示出来。 以下输出是我们最终得到的结果:

它看起来或多或少看起来像是正态分布,但是由于存在随机元素,因此它并不是完美的曲线。 我们在谈论概率; 事情有些不尽如人意。

指数概率分布或幂律

您经常看到的另一个分布函数是指数概率分布函数,该函数以指数方式下降。

当您谈论指数下降时,您会期望看到一条曲线,很有可能会发生某件事,接近零,但是随着距离的增加,曲线会很快消失。 自然界中有很多事物以这种方式表现。

要在 Python 中做到这一点,就像我们在scipy.stats中有针对norm.pdf的函数一样,我们也有expon.pdf或指数概率分布函数在 Python 中做到这一点,我们可以对指数分布使用为正态分布所做的相同语法,如以下代码块所示:

from scipy.stats import expon 
import matplotlib.pyplot as plt 

x = np.arange(0, 10, 0.001) 
plt.plot(x, expon.pdf(x)) 

同样,在上面的代码中,我们仅使用 NumPy arange函数创建 x 值,以在010之间创建一堆值,步长为0.001。 然后,我们将这些 x 值绘制在 y 轴上,并将其定义为函数expon.pdf(x)。 输出看起来像指数下降。 如以下屏幕截图所示:

二项式概率质量函数

我们还可以可视化概率质量函数。 这称为二项式概率质量函数。 同样,我们将使用与以前相同的语法,如以下代码所示:

from scipy.stats import expon 
import matplotlib.pyplot as plt 

x = np.arange(0, 10, 0.001) 
plt.plot(x, expon.pdf(x)) 

因此,我们只使用binom而不是exponnorm。 提醒:概率质量函数处理离散数据。 一直以来,我们一直都在,这就是您的想法。

回到我们的代码,我们在010之间以0.01的间隔创建一些离散的x值,我们说我想使用该数据绘制一个二项式概率质量函数。 使用binom.pmf函数,我实际上可以使用两个形状参数np指定该数据的形状。 在这种情况下,它们分别是100.5。 输出如下图所示:

如果您要尝试不同的值以查看其影响,那么这是一种直观了解这些形状参数如何在概率质量函数上工作的好方法。

泊松概率质量函数

最后,您可能会听到的另一个分布函数是泊松概率质量函数,它具有非常特殊的应用。 它看起来很像正态分布,但是有些不同。

这里的想法是,如果您掌握了给定时间段内发生的平均事件数的某些信息,则此概率质量函数可以为您提供一种方法,以预测在给定的将来一天获得另一个值的几率。

例如,假设我有一个网站,平均每天有 500 位访问者。 我可以使用泊松概率质量函数来估计在特定日期看到其他值的概率。 例如,以我平均每天 500 位访问者为例,一天中看到 550 位访问者的几率是多少? 这就是泊松概率质量函数可以为您提供以下代码:

from scipy.stats import poisson 
import matplotlib.pyplot as plt 

mu = 500 
x = np.arange(400, 600, 0.5) 
plt.plot(x, poisson.pmf(x, mu)) 

在此代码示例中,我说的是平均亩地 500 亩。 我将设置一些 x 值,以0.5的间距在400600之间查看。 我将使用poisson.pmf函数进行绘制。 假设正态分布,我可以使用该图查询获得不是500的任何特定值的几率:

事实证明,在给定的一天看到550位访客的几率约为0.002或 0.2% 的概率。 很有意思。

好了,这些是您在现实世界中可能会遇到的一些常见数据分布。

请记住,我们对连续数据使用了概率分布函数,但是当处理离散数据时,我们使用了概率质量函数。

这就是概率密度函数和概率质量函数。 基本上,是一种可视化和测量数据集中给定值范围的实际机会的方法。 非常重要的信息,也是一件非常重要的事情。 我们将反复使用该概念。 好吧,让我们继续前进。

百分位数和矩

接下来,我们将讨论百分位数和矩。 您一直在新闻中听到有关百分位数的消息。 收入最高的 1% 的人:这是百分位数的一个例子。 我们将对此进行解释,并提供一些示例。 然后,我们将讨论矩,这是一个非常花哨的数学概念,但事实证明,从概念上理解它非常简单。 让我们深入探讨百分位和矩,这是统计学中的几个非常基本的概念,但是同样,我们正在努力研究艰巨的事物,因此在本次回顾中,请耐心等待。

百分位数

让我们看看百分位数的含义。 基本上,如果您要对数据集中的所有数据进行排序,则给定的百分位数就是该数据百分比小于您所处的点。

您看到的谈论很多的常见示例是收入分配。 当我们谈论第 99 个百分位或一个百分位数时,请想象您要拿走该国每个人(在本例中为美国)的所有收入,然后按收入排序。 第 99 个百分位数将是该国其他地区 99% 的收入低于该水平的收入水平。 这是一种很容易理解的方法。

在数据集中,百分位是x%的值小于该点的值的点。

下图是收入分配的示例:

上图显示了收入分配数据的示例。 例如,在第 99 个百分点,我们可以说代表美国人口的数据点的 99% 每年收入不到 506,553 美元,而百分之一的收入更高。 相反,如果您是单中心用户,那么每年的收入就超过 506,553 美元。 恭喜你! 但是,如果您是比较典型的中位数人,则第 50 个百分位数定义了一半的人的收入低于您而一半的人的收入高于您的水平,这就是中位数的定义。 第 50 个百分位数与中位数相同,根据该数据集,该百分位数为$ 42,327。 因此,如果您在美国的年收入为$ 42,327,那么您的收入就是该国的中位数。

您可以在上图中看到收入分配问题。 事情往往集中在图表的高端,这是该国目前的一个非常大的政治问题。 我们将看到发生了什么,但这超出了本书的范围。 简而言之,这就是百分位。

四分位数

在谈论分配中的四分位数时,也使用百分位。 让我们看一下正态分布以更好地理解这一点。

以下示例说明了正态分布中的百分位数:

查看上图中的正态分布,我们可以讨论四分位数。 中间的四分位数 1(Q1)和四分位数 3(Q3)只是包含 50% 数据的点,因此 25% 位于中位数的左侧,而 25% 位于中位数的右侧。

在此示例中,中位数接近平均值。 例如,当我们谈论一个分布时,四分位数范围IQR)是分布中间的区域,其中包含 50% 的值。

图像的最上部分是所谓的盒须图的示例。 暂时不要担心盒子边缘的东西。 这有点令人困惑,我们稍后再讨论。 即使它们被称为四分位数 1(Q1)和四分位数 3(Q1),它们并不能真正代表 25% 的数据,但是还没有挂断。 重点关注中间的四分位数代表数据分布的 25%。

用 Python 计算百分位数

让我们看一些使用 Python 的百分位数的例子,并亲身体验一下,并在概念上多一点。 如果您想继续,请继续打开Percentiles.ipynb文件,我再次鼓励您这样做,因为我希望您稍后再进行操作。

首先,我们生成一些随机分布的正态数据或正态分布的随机数据,请参考以下代码块:

%matplotlib inline 
import numpy as np 
import matplotlib.pyplot as plt 

vals = np.random.normal(0, 0.5, 10000) 

plt.hist(vals, 50) 
plt.show() 

在此示例中,我们要做的是生成一些以零为中心的数据,即均值为零,标准差为0.5,我将使用该数据作为10000数据点的分布。 然后,我们将绘制一个直方图,看看我们得出了什么。

生成的直方图看起来非常像正态分布,但是由于存在随机分量,因此在此示例中,我们在-2 的偏差附近有一个离群值。 事物的平均含义是稍微倾斜,那里的随机变化使事物变得有趣。

NumPy 提供了一个非常方便的百分位数函数,它将为您计算此分布的百分位数值。 因此,我们使用np.random.normal创建了vals数据列表,我可以使用以下代码调用np.percentile函数以找出第 50 个百分位值:

np.percentile(vals, 50) 

以下是上述代码的输出:

0.0053397035195310248

输出结果为 0.005。 因此请记住,第 50 个百分位数只是中位数的另一个名称,结果表明该数据的中位数非常接近于零。 您可以在图中看到我们向右倾斜了一点,这并不奇怪。

我想计算第 90 个百分位数,这为我提供了 90% 的数据小于该值的点。 我们可以使用以下代码轻松地做到这一点:

np.percentile(vals, 90) 

这是该代码的输出:

Out[4]: 0.64099069837340827 

该数据的第 90 个百分位数为 0.64,因此它在此处,并且基本上,在那一点上,不到 90% 的数据小于该值。 我可以相信。 10% 的数据大于 0.64,90% 的数据小于 0.65。

让我们计算第 20 个百分位值,这将为我提供一个点,在该点上 20% 的值小于我得出的数字。 同样,我们只需要对代码进行非常简单的修改:

np.percentile(vals, 20) 

这给出以下输出:

Out[5]:-0.41810340026619164 

第 20 个百分位数大致为 -0.4,我再次相信这一点。 也就是说,20% 的数据位于-0.4 的左侧,相反,80% 的数据更大。

如果您想了解这些断点在数据集中的位置,则百分位函数是一种简单的计算方法。 如果这是代表收入分配的数据集,我们可以调用np.percentile(vals, 99)并找出第 99 个百分位数。 您可以弄清楚人们一直在谈论的那些一心一意的人到底是谁,如果您是其中之一。

好吧,现在弄脏你的手。 我希望您能处理这些数据。 这是一个 IPython 笔记本,出于某种原因,例如,您可以将其弄乱并与代码弄混,尝试使用不同的标准差值,查看其对数据形状的影响以及这些百分位数最终位于何处。 尝试使用较小的数据集大小,并在事物中添加更多随机变化。 只是对它感到满意,对其进行操作,然后发现您实际上可以做到这一点并编写一些有效的真实代码。

接下来,让我们谈一谈。 力矩是一个花哨的数学短语,但是您实际上不需要数学学位即可理解。 直观上,它比听起来简单得多。

这是统计学和数据科学领域的人们喜欢使用夸张的术语来使自己听起来真的很聪明的例子之一,但是这些概念实际上非常容易掌握,这就是您将在此反复听到的主题。 书。

基本上,矩是真正衡量数据分布,概率密度函数或任何事物形状的方法。 在数学上,我们有一些非常特别的符号来定义它们:

如果您确实知道微积分,那么它实际上并不是那么复杂的概念。 我们将每个值之间的差值从某个值提高到n次幂,其中n是矩数,并在整个函数中积分(从负无穷大到无穷大)。 但是从直观上讲,它比微积分要容易得多。

矩可以定义为概率密度函数形状的定量度量。

准备好? 开始了!

  1. 第一矩只是您要查看的数据的平均值。 而已。 第一刻是平均值。 就这么简单。

  2. 第二矩是变化。 而已。 数据集的第二个矩与方差值相同。 这些东西自然地不属于数学范畴,似乎有些令人毛骨悚然,但请考虑一下。 方差实际上是基于均值差异的平方,因此想出一种数学方法来表示方差与均值相关并不是很大。 就这么简单。

  3. 现在,当我们进入第三和第四矩时,事情会变得有些棘手,但它们仍然是易于理解的概念。 第三矩称为偏斜,基本上可以衡量分布的偏斜程度。

  • 您可以在上面的两个示例中看到,如果我在左侧有一个较长的尾巴,那么那现在是负偏斜,而如果我在右边有一个较长的尾巴,那就是正偏斜。 虚线表示没有偏斜的正态分布形状。 虚线在左侧出现,然后在该示例中以负偏斜结束,或者在另一侧以正偏斜结束。 好的,这就是所有的偏差。 它基本上是在一侧或另一侧伸出尾巴,并且可以衡量分布的偏斜程度或偏斜程度。
  1. 第四矩称为峰度。 哇,真是个好话! 真正的意思是,尾巴有多厚,峰有多尖。 同样,它是对数据分布形状的一种度量。 这是一个例子:

  • 您可以看到较高的峰值具有较高的峰度值。 最上面的曲线的峰度高于最下面的曲线。 这是一个非常细微的差异,但是仍然存在差异。 它基本上可以衡量数据的峰值。

让我们回顾一下所有情况:第一矩是均值,第二矩是方差,第三矩是偏斜,第四矩是峰度。 我们已经知道均值和方差是什么。 偏斜是数据的偏斜程度,可能是尾巴之一伸出的程度。 峰度是如何达到高峰,如何将数据分布压缩在一起。

用 Python 计算矩

让我们在 Python 中玩转并实际计算这些矩,然后看看如何做到这一点。 要解决这个问题,请继续打开Moments.ipynb,您可以在这里与我一起关注。

让我们再次创建随机数据的相同正态分布。 同样,我们将使其以零为中心,具有 0.5 个标准差和 10,000 个数据点,并将其绘制出来:

import numpy as np 
import matplotlib.pyplot as plt 

vals = np.random.normal(0, 0.5, 10000) 

plt.hist(vals, 50) 
plt.show() 

再一次,我们得到一个随机生成的数据集,该数据集具有零附近的正态分布。

现在,我们找到均值和方差。 我们之前已经做过; NumPy 只是为您提供了meanvar函数来进行计算。 因此,我们只需调用np.mean即可找到第一刻,这只是平均值的一个奇特词,如以下代码所示:

np.mean(vals)

在我们的示例中,将提供以下输出:

Out [2]:-0.0012769999428169742

输出结果非常接近零,就像我们期望以零为中心的正态分布数据一样。 因此,到目前为止,世界是有意义的。

现在我们找到第二个矩,这只是方差的另一个名字。 我们可以使用下面的代码来做到这一点,就像我们之前看到的那样:

np.var(vals)

提供以下输出:

Out[3]:0.25221246428323563

该输出结果约为 0.25,并且再次进行了良好的健全性检查。 请记住,标准差是方差的平方根。 如果您取 0.25 的平方根,则得出 0.5,这是我们在创建此数据时指定的标准差,因此再次进行检验。

第三矩是歪斜的,为此,我们将需要使用 SciPy 包而不是 NumPy。 但是,这再次内置于任何科学计算包中,例如 Enthought Canopy 或 Anaconda。 一旦有了 SciPy,函数调用就和前面的两个一样简单:

import scipy.stats as sp
sp.skew(vals)

这将显示以下输出:

Out[4]: 0.020055795996111746

我们只需要在vals列表中调用sp.skew,就可以得到偏斜值。 由于此中心位于零附近,因此应该几乎为零偏斜。 事实证明,在随机变化的情况下,它确实会稍微偏斜,并且实际上与我们在图中看到的形状吻合。 看来我们确实有点消极了。

第四个矩是峰度,它描述了尾巴的形状。 同样,对于应该为zero.SciPy的正态分布,我们可以进行另一个简单的函数调用

sp.kurtosis(vals)

这是输出:

Out [5]:0.059954502386585506

确实,它确实为零。 峰度以两种链接的方式显示了我们的数据分布:尾巴的形状或峰的尖锐程度如果我只是将尾巴压扁,它会推高该峰以使其更尖,或者同样地,如果将其分布降低,您可以想象这有点分散了东西,使尾巴更胖,峰值更矮了。 这就是峰度的含义,在此示例中,峰度几乎为零,因为它只是一个普通的正态分布。

如果您想使用它,请继续,然后再次尝试修改发行版。 使它以除 0 以外的内容为中心,并查看该值是否实际发生任何变化。 应该是? 嗯,这确实不应该,因为这些都是分布形状的度量,也并没有真正说明分布的确切位置。 这是形状的度量。 那就是矩的全部。 继续尝试一下,尝试不同的中心值,尝试不同的标准差值,看看它对这些值有什么影响,并且它根本不会改变。 当然,您期望诸如均值之类的东西会发生变化,因为您正在更改均值,但方差,偏斜也许不会。 玩耍,找出答案。

那里有百分位数和矩。 百分位数是一个非常简单的概念。 片刻听起来很难,但实际上很容易理解如何做,在 Python 中也很容易。 现在,您已经可以使用它了。 现在该继续前进了。

总结

在本章中,我们看到了您可能会遇到的数据类型(数字,分类和有序数据),以及如何对它们进行分类以及如何根据要处理的数据类型来区别对待它们。 我们还介绍了均值,中位数和众数的统计概念,并且还看到了在中位数和均值之间进行选择的重要性,并且由于离群值的原因,中位数通常比均值更好。

接下来,我们分析了如何在 IPython 笔记本文件中使用 Python 计算均值,中位数和众数。 我们了解了标准差和深度方差的概念以及如何在 Python 中进行计算。 我们看到它们是对数据分布范围的衡量。 我们还看到了一种使用概率密度函数和概率质量函数可视化和测量给定值范围在数据集中出现的实际机会的方法。

我们通常研究了数据分布的类型(均匀分布,正态分布或高斯分布,指数概率分布,二项式概率质量函数,泊松概率质量函数)以及如何使用 Python 对其进行可视化。 我们分析了百分位数和矩的概念,并了解了如何使用 Python 计算它们。

在下一章中,我们将更广泛地使用matplotlib库,并深入探讨协方差和相关性的更高级主题。

三、Matplotlib 和高级概率概念

在上一章中介绍了一些简单的统计和概率概念之后,我们现在将注意力转移到一些更高级的主题上,您需要熟悉这些主题才能充分利用本章的其余部分。 书。 不用担心,它们并不太复杂。 首先,让我们玩得开心,看看matplotlib库的惊人图形功能。

本章将涵盖以下主题:

  • 使用matplotlib包绘制图形
  • 了解协方差和相关性来确定数据之间的关系
  • 通过示例了解条件概率
  • 了解贝叶斯定理及其重要性

Matplotlib 中的速成课程

实际上,您的数据仅能将其呈现给其他人,因此,让我们来讨论一下如何绘制和图形化数据以及如何将其呈现给其他人并使您的图形看起来更漂亮。 我们将更全面地介绍 Matplotlib 并逐步进行。

我将向您展示一些技巧,以使您的图表尽可能漂亮。 让我们玩一下图吧。 用您的作品制作漂亮的图片总是很好的。 这将在您的工具箱中提供更多工具,以使用不同类型的图形可视化不同类型的数据并使它们看起来更漂亮。 我们将使用不同的颜色,不同的线型,不同的轴,诸如此类。 使用图形和数据可视化来尝试在数据中找到有趣的模式不仅很重要,而且将您的发现很好地呈现给非技术受众也很有趣。 事不宜迟,让我们深入了解 Matplotlib。

继续打开MatPlotLib.ipynb文件,您可以和我一起玩这些东西。 我们将首先绘制一个简单的折线图。

%matplotlib inline 

from scipy.stats import norm 
import matplotlib.pyplot as plt 
import numpy as np 

x = np.arange) 

plt.plot(x, norm.pdf(x)) 
plt.show() 

因此,在此示例中,我将matplotlib.pyplot导入为plt,并且从现在开始,我们可以在此笔记本中将其称为plt。 然后,我使用np.arange(-3, 3, 0.001)创建一个以 0.001 为增量填充-33之间的值的 x 轴,并使用pyplotplot()函数绘制x。 y 函数将为norm.pdf(x)。 因此,我将基于x值创建一个具有正态分布的概率密度函数,然后使用scipy.stats norm包进行此操作。

因此,将其结合到上一章的概率密度函数中,这里我们使用matplotlib绘制正态概率密度函数。 因此,我们只需要调用pyplotplot()方法来设置绘图,然后使用plt.show()进行显示即可。 运行前面的代码时,将得到以下输出:

这就是我们得到的:具有所有默认格式的漂亮图形。

在一张图上生成多个图

假设我要一次绘制多个内容。 实际上,您可以在调用 show 之前多次调用 plot,以向图形实际添加多个绘图。 让我们看下面的代码:

plt.plot(x, norm.pdf(x)) 
plt.plot(x, norm.pdf(x, 1.0, 0.5)) 
plt.show() 

在此示例中,我将原来的函数称为正态分布,但我还将在此处渲染另一个正态分布,均值约为1.0,标准差为0.5。 然后,我将一起展示这两者,以便您了解它们之间的比较。

您可以看到,默认情况下,matplotlib会为您自动为每个图形选择不同的颜色,这非常好用。

将图形另存为图像

如果我想将此图形保存到文件中,或者想将其包含在文档中或其他东西中,则可以执行以下代码:

plt.plot(x, norm.pdf(x)) 
plt.plot(x, norm.pdf(x, 1.0, 0.5)) 
plt.savefig('C:\\Users\\Frank\\MyPlot.png', format='png') 

我不仅可以调用plt.show(),还可以调用plt.savefig(),并提供一个指向我要保存此文件的位置以及所需文件格式的路径。

如果要继续,则需要将其更改为计算机上存在的实际路径。 您的系统上可能没有Users\Frank文件夹。 还要记住,如果您使用的是 Linux 或 macOS,则要使用反斜杠而不是反斜杠,而不会使用驱动器号。 对于所有这些 Python 笔记本,每当看到这样的路径时,请确保将其更改为在系统上可用的实际路径。 我在 Windows 上,并且确实有Users\Frank文件夹,因此我可以继续运行它。 如果在Users\Frank下检查我的文件系统,则有一个MyPlot.png文件可以打开并查看,并且可以在所需的任何文档中使用它。

太酷了。 要注意的另一件事是,根据设置的不同,保存文件时可能会遇到权限问题。 您只需要找到适合您的文件夹。 在 Windows 上,Users\Name文件夹通常是一个安全的选择。 好吧,让我们继续前进。

调整轴

假设我不喜欢上图中的该值的轴的默认选择。 它会自动将其拟合为您可以找到的最紧密的一组轴值,这通常是一件好事,但有时您想要的东西是绝对的。 看下面的代码:

axes = plt.axes() 
axes.set_xlim([-5, 5]) 
axes.set_ylim([0, 1.0]) 
axes.set_xticks([-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]) 
axes.set_yticks([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]) 
plt.plot(x, norm.pdf(x)) 
plt.plot(x, norm.pdf(x, 1.0, 0.5)) 
plt.show() 

在此示例中,首先使用plt.axes获得轴。 一旦有了这些轴对象,就可以对其进行调整。 通过调用set_xlim,我可以将 x 范围设置为 -5 到 5,并通过设置set_ylim来将 y 范围设置为 0 到 1。您可以在下面的输出中看到,我的 x 值的范围是-55,并且 y 从 0 到 1。我还可以明确控制轴上刻度线的位置。 因此,在前面的代码中,我想让 x 刻度线位于-5-4- 3等位置,而 y 刻度线则使用set_xticks()set_yticks()函数。 现在,我可以使用arange函数来更紧凑地执行此操作,但要点是您可以明确控制那些刻度线的确切位置,也可以跳过一些标记。 您可以按所需的增量或分布进行配置。 除此之外,这是一回事。

调整轴后,我便用要绘制的函数调用了plot(),并调用了show()来显示它。 果然,您可以得到结果。

添加网格

如果要在图表中显示网格线怎么办? 好吧,同样的想法。 我要做的就是在从plt.axes()返回的轴上调用grid()

axes = plt.axes() 
axes.set_xlim([-5, 5]) 
axes.set_ylim([0, 1.0]) 
axes.set_xticks([-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]) 
axes.set_yticks([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]) 
axes.grid() 
plt.plot(x, norm.pdf(x)) 
plt.plot(x, norm.pdf(x, 1.0, 0.5)) 
plt.show() 

通过执行以上代码,我得到了漂亮的小网格线。 尽管它使事情变得有些混乱,但是这使得查看特定点在哪里更加容易。 那里是一种风格选择。

更改线型和颜色

如果我想使用线型和颜色玩游戏怎么办? 你也可以那样做。

axes = plt.axes() 
axes.set_xlim([-5, 5]) 
axes.set_ylim([0, 1.0]) 
axes.set_xticks([-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]) 
axes.set_yticks([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]) 
axes.grid() 
plt.plot(x, norm.pdf(x), 'b-') 
plt.plot(x, norm.pdf(x, 1.0, 0.5), 'r:') 
plt.show() 

因此,您可以在前面的代码中看到plot()函数的末尾实际上有一个额外的参数,我可以传递一个描述线条样式的小字符串。 在第一个示例中,b-指示的是我想要一条蓝色的实线。 b代表蓝色,破折号表示实线。 对于我的第二个plot()函数,我将其绘制为红色,这就是r的含义,而冒号意味着我将用虚线绘制它。

如果执行此操作,则可以在上图中看到它的作用,并且可以更改不同类型的线型。

此外,您可以执行双破折号(--)。

axes = plt.axes() 
axes.set_xlim([-5, 5]) 
axes.set_ylim([0, 1.0]) 
axes.set_xticks([-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]) 
axes.set_yticks([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]) 
axes.grid() 
plt.plot(x, norm.pdf(x), 'b-') 
plt.plot(x, norm.pdf(x, 1.0, 0.5), 'r--') 
plt.show() 

前面的代码为您提供了红色虚线作为线条样式,如下图所示:

我也可以做破折号组合(-.)。

axes = plt.axes() 
axes.set_xlim([-5, 5]) 
axes.set_ylim([0, 1.0]) 
axes.set_xticks([-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]) 
axes.set_yticks([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]) 
axes.grid() 
plt.plot(x, norm.pdf(x), 'b-') 
plt.plot(x, norm.pdf(x, 1.0, 0.5), 'r-.') 
plt.show() 

您得到的输出看起来像下面的图形图像:

因此,这些是那里的不同选择。 我什至可以用斜杠(g:)将其变为绿色。

axes = plt.axes() 
axes.set_xlim([-5, 5]) 
axes.set_ylim([0, 1.0]) 
axes.set_xticks([-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]) 
axes.set_yticks([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]) 
axes.grid() 
plt.plot(x, norm.pdf(x), 'b-') 
plt.plot(x, norm.pdf(x, 1.0, 0.5), ' g:') 
plt.show() 

我将得到以下输出:

如有需要,可以尝试一些有趣的事情,尝试不同的值,然后可以获得不同的线条样式。

标记轴并添加图例

您将更常做的事情是标记轴。 您永远都不想真空呈现数据。 您绝对想告诉人们它代表什么。 为此,您可以使用plt上的xlabel()ylabel()函数在轴上实际放置标签。 我将标记 x 轴 Greebles 和 y 轴概率。 您还可以添加图例插图。 通常,这是同一回事,但只是为了表明它是独立设置的,我还在以下代码中设置了图例:

axes = plt.axes() 
axes.set_xlim([-5, 5]) 
axes.set_ylim([0, 1.0]) 
axes.set_xticks([-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]) 
axes.set_yticks([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]) 
axes.grid() 
plt.xlabel('Greebles') 
plt.ylabel('Probability') 
plt.plot(x, norm.pdf(x), 'b-') 
plt.plot(x, norm.pdf(x, 1.0, 0.5), 'r:') 
plt.legend(['Sneetches', 'Gacks'], loc=4) 
plt.show() 

在图例中,您基本上传入了要为每个图形命名的列表。 因此,我的第一个图将被称为 Sneetches,而我的第二个图将被称为 Gacks,loc参数指示您想要的位置,其中4代表右下角。 让我们继续运行代码,您应该看到以下内容:

可以看到,我正在绘制 Sneetches 和 Gacks 的 Greebles 与概率。 苏斯博士为您提供了一些参考资料。 这样便可以设置轴标签和图例。

一个有趣的例子

这里有个有趣的例子。 如果您熟悉 Webcomic XKCD,则 Matplotlib 中有一些复活节彩蛋,您可以在其中实际以 XKCD 样式绘制内容。 以下代码显示了如何执行此操作。

plt.xkcd() 

fig = plt.figure() 
ax = fig.add_subplot(1, 1, 1) 
ax.spines['right'].set_color('none') 
ax.spines['top'].set_color('none') 
plt.xticks([]) 
plt.yticks([]) 
ax.set_ylim([-30, 10]) 

data = np.ones(100) 
data[70:] -= np.arange(30) 

plt.annotate( 
    'THE DAY I REALIZED\nI COULD COOK BACON\nWHENEVER I WANTED', 
    xy=(70, 1), arrowprops=dict(arrowstyle='->'), xytext=(15, -10)) 

plt.plot(data) 

plt.xlabel('time') 
plt.ylabel('my overall health') 

在此示例中,您调用plt.xkcd(),这将 Matplotlib 置于 XKCD 模式。 完成此操作后,事物将具有一种漫画字体和自动弯曲的线条的样式。 这个小小的示例将显示一个有趣的小图形,其中我们在绘制您的健康状况与时间的关系图,一旦您意识到可以随时煮熏肉,健康状况就会急剧下降。 我们在那里所做的只是使用xkcd()方法进入该模式。 您可以看到以下结果:

我们实际上是如何将这张图放在一起的,这里有一些有趣的 Python。 我们首先制作一条数据线,该数据线不过是 100 个数据点上的值 1。 然后,我们使用旧的 Python 列表切片运算符获取值 70 之后的所有内容,然后从该 30 项子列表中减去 0 到 30 的范围。因此当您越过 70 时,它具有线性减去较大值的效果,导致这条线从点 70 下方下降到 0。

因此,这只是其中一些 Python 列表切片操作的小示例,以及对arange函数的一点创造性使用,以修改您的数据。

生成饼图

现在,回到现实世界,我们可以通过在 Matplotlib 上调用rcdefaults()来删除 XKCD 模式,然后在这里回到普通模式。

如果要使用饼图,只需调用plt.pie,并为其提供一个数组,其中包含值,颜色,标签以及是否要爆炸的项目(如果爆炸了,则爆炸了多少)。 这是代码:

# Remove XKCD mode: 
plt.rcdefaults() 

values = [12, 55, 4, 32, 14] 
colors = ['r', 'g', 'b', 'c', 'm'] 
explode = [0, 0, 0.2, 0, 0] 
labels = ['India', 'United States', 'Russia', 'China', 'Europe'] 
plt.pie(values, colors= colors, labels=labels, explode = explode) 
plt.title('Student Locations') 
plt.show() 

您可以在这段代码中看到我正在创建一个值为125543214值的饼图。 我为这些值的每一个分配了明确的颜色,为这些值的每一个分配了明确的标签。 我将饼图的俄文部分放大了 20%,并为该图添加了“学生位置”标题并显示出来。 以下是您应该看到的输出:

这里的所有都是它的。

生成条形图

如果要生成条形图,这也非常简单。 这与饼图类似。 让我们看下面的代码。

values = [12, 55, 4, 32, 14] 
colors = ['r', 'g', 'b', 'c', 'm'] 
plt.bar(range(0,5), values, color= colors) 
plt.show() 

我定义了一个值数组和一个颜色数组,然后绘制数据。 上面的代码使用values数组中的 y 值,并使用colors数组中列出的显式颜色,从 0 到 5 的范围进行绘制。 继续进行显示,然后显示条形图:

生成散点图

在本书中,我们经常会看到散点图。 因此,假设您要为同一组人或事物绘制几个不同的属性。 例如,也许我们正在针对每个人的年龄绘制图表,其中每个点代表一个人,而轴代表这些人的不同属性。

使用散点图进行此操作的方式是,使用要定义的两个轴调用plt.scatter(),即,两个属性包含要相互绘制的数据。

假设我在XY中具有随机分布,并且将散布在散点图上,然后将其显示:

from pylab import randn 

X = randn(500) 
Y = randn(500) 
plt.scatter(X,Y) 
plt.show() 

您将获得以下散点图作为输出:

看起来很酷。 由于在两个轴上都使用正态分布,因此您可以在此处看到中心的某种集中度,但是由于它是随机的,因此这两个点之间没有真正的相关性。

生成直方图

最后,我们会提醒自己直方图的工作原理。 我们已经在书中看到了很多次。 让我们看下面的代码:

incomes = np.random.normal(27000, 15000, 10000) 
plt.hist(incomes, 50) 
plt.show() 

在此示例中,我称一个以 27,000 为中心的正态分布,标准差为 15,000,具有 10,000 个数据点。 然后,我只调用pyplot的直方图函数,即hist(),并指定输入数据和我们要在直方图中将事物分组的存储桶数。 然后我叫show(),其余的都是魔术。

生成箱形图

最后,让我们看一下箱形图。 请记住,在上一章中,当我们谈论百分位数时,我谈到了这一点。

同样,使用箱形图,该框代表两个内部四分位数,其中 50% 的数据位于该四分位数中。 相反,另外 25% 的居民位于该盒子的两侧。 胡须(在我们的示例中为虚线)代表数据的范围(异常值除外)。

我们将箱形图中的离群值定义为四分位间距或框的大小的 1.5 倍以外的任何值。 因此,我们将盒子的大小乘以 1.5,直到点状胡须上的那个点,我们将这些部分称为四分位数外。 但是外部四分位数之外的任何东西都被视为离群值,这就是外部四分位数以外的线所代表的含义。 那就是我们根据盒须图绘制的定义来定义离群值的地方。

关于箱形图的几点要记住:

  • 它们对于可视化数据的分散度和偏斜很有用
  • 方框中间的线代表数据的中位数,方框代表第一四分位数和第三四分位数的边界
  • 一半的数据存在于框内
  • “胡须”表示数据的范围,但异常值除外,这些异常值绘制在胡须之外。
  • 离群值是四分位数范围的 1.5 倍或更大。

现在,在这里仅举一个例子,我们创建了一个伪数据集。 以下示例创建了-40 至 60 之间均匀分布的随机数,以及100之上和-100之下的一些异常值:

uniformSkewed = np.random.rand(100) * 100 - 40 
high_outliers = np.random.rand(10) * 50 + 100 
low_outliers = np.random.rand(10) * -50 - 100 
data = np.concatenate((uniformSkewed, high_outliers, low_outliers)) 
plt.boxplot(data) 
plt.show() 

在代码中,我们具有统一的数据随机分布(uniformSkewed)。 然后,我们在高端high_outliers上添加了一些离群值,并在low_outliers上添加了一些负值。 然后,我们将这些列表连接在一起,并使用 NumPy 从这三个不同的集合中创建了一个数据集。 然后,我们采用统一数据和一些离群值的组合数据集,并使用plt.boxplot()进行绘制,这就是您得到箱形图的方式。 调用show()将其可视化,然后就可以了。

您可以看到该图形显示的框代表了所有数据的内部 50%,然后我们有了这些离群值线,在其中我们可以看到位于其中的每个离群值的小叉(在您的版本中可能是圆圈) 范围。

自己尝试

好了,这就是 Matplotlib 中的速成课程。 是时候动手了,并在这里进行一些练习。

作为您的挑战,我希望您创建一个散点图,该散点图代表您根据年龄和观看电视所花费的时间制作的随机数据,并且您可以做任何想要的事情。 如果您想玩的是不同的虚拟数据集,请尝试一下。 创建一个散点图,以散点图的方式绘制两组随机数据,并标记您的轴。 让它看起来漂亮,玩转它,玩得开心。 您需要参考和示例的所有内容都应该在此 IPython 笔记本中。 如果您愿意的话,它就像是一份备忘单,您可能需要为生成不同种类的图形和不同样式的图形而做的不同事情。 我希望它证明有用。 现在该回到统计信息了。

协方差和相关

接下来,我们将讨论协方差和相关性。 假设我有某种事物的两个不同属性,并且我想看看它们是否确实相互关联。 本节将为您提供所需的数学工具,我们将深入研究一些示例,并使用 Python 实际找出协方差和相关性。 这些是测量一组数据中两个不同属性是否相互关联的方法,这可能是非常有用的事情。

定义概念

想象一下,我们有一个散布图,每个数据点都代表我们测量的一个人,并且我们在一个轴上绘制了他们的年龄,而在另一个轴上绘制了他们的收入。 这些点中的每个点都代表一个人,例如,它们的 x 值表示他们的年龄,y 值表示他们的收入。 我完全弄清楚了,这是假数据。

现在,如果我有一个散布图,看起来像上图中的左图,您会发现这些值往往遍布整个地方,这将告诉您,根据此数据,年龄和收入之间没有真正的相关性。 对于任何给定的年龄,收入的范围可能很大,并且往往集中在中间,但是我们并没有真正看到年龄和收入这两个不同属性之间的明确关系。 现在,相比之下,在右侧的散点图中,您可以看到年龄和收入之间存在非常明显的线性关系。

因此,协方差和相关性为我们提供了一种手段来衡量这些事物之间的相关性。 我希望左侧散点图中的数据具有非常低的相关性或协方差,但是右侧散点图中的数据具有非常高的协方差和相关性。 这就是协方差和相关性的概念。 它可以衡量我正在衡量的这两个属性在多大程度上相互依赖。

测量协方差

用数学方法测量协方差有点困难,但是我会尽力解释。 这些步骤是:

  • 将两个变量的数据集视为高维向量
  • 将这些转换为均值方差向量
  • 取两个向量的点积(它们之间夹角的余弦值)
  • 除以样本量

了解如何使用它及其含义真的更重要。 要实际导出它,请将数据的属性视为高维向量。 我们将为每个数据点的每个属性执行的操作是计算每个点的均值方差。 因此,现在有了这些高维向量,其中每个数据点,每个人(如果愿意)都对应于一个不同的维。

在这个高维空间中,我有一个向量,代表一个属性的年龄平均值的所有方差。 然后,我有另一个向量,表示与其他某些属性(例如收入)的均值相比的所有方差。 然后,我要做的就是采用这些向量来测量每个属性的均值方差,并采用两者之间的点积。 从数学上讲,这是一种测量这些高维向量之间的角度的方法。 因此,如果它们最终彼此之间非常接近,则可以告诉我,这些差异在这些不同的属性之间相互之间的步调一致。 如果我采用最终的点积并将其除以样本量,那便是得出协方差量的方式。

现在,您将不再需要自己实际计算困难的方式。 我们将看到如何在 Python 中以简单的方式执行此操作,但是从概念上讲,这就是它的工作方式。

现在,协方差的问题在于它可能难以解释。 如果我的协方差接近于零,那么我知道这告诉我这些变量之间根本没有太大的相关性,但是大的协方差意味着存在某种关系。 但是多大呢? 根据我使用的单位,可能会有非常不同的解释数据的方式。 这是相关性解决的问题。

相关性

相关性通过每个属性的标准差对所有事物进行归一化(只需将协方差除以两个变量的标准差即可对事物进行归一化)。 这样做,我可以很清楚地说,相关系数为 -1 表示存在完美的逆相关性,因此,一个值增大,另一个值减小,反之亦然。 相关性为 0 表示两组属性之间完全没有相关性。 相关性为 1 表示完全相关,其中这两个属性的移动方式与查看不同数据点时完全相同。

请记住,关联并不意味着因果关系。 仅仅因为您发现一个很高的相关值并不意味着这些属性之一会导致另一个属性。 这仅表示两者之间存在某种关系,并且这种关系可能是由完全不同的事物引起的。 真正确定因果关系的唯一方法是通过受控实验,我们将在后面讨论。

用 Python 计算协方差和相关性

好吧,让我们在这里通过一些实际的 Python 代码来了解协方差和相关性。 再次,您可以在概念上将协方差视为将这些多维方差向量从每个属性的均值中提取出来,并计算它们之间的角度作为协方差的量度。 做到这一点的数学比听起来容易得多。 我们正在谈论高维向量。 这听起来像是史蒂芬·霍金(Stephen Hawking)的东西,但实际上,从数学角度来看,这非常简单。

计算相关性-困难的方法

我将从困难的方式开始。 NumPy 确实有一种方法可以为您计算协方差,稍后我们将讨论这一点,但是现在我想表明您实际上可以从第一个原理上做到这一点:

%matplotlib inline 

import numpy as np 
from pylab import * 

def de_mean(x): 
    xmean = mean(x) 
    return [xi - xmean for xi in x] 

def covariance(x, y): 
    n = len(x) 
    return dot(de_mean(x), de_mean(y)) / (n-1) 

同样,协方差被定义为点积,它是两个向量之间的夹角的量度。 一个是给定数据集与平均值的偏差,另一个是相同数据的数据点与另一给定数据集的平均值的偏差。 然后在这种情况下将其除以n-1,因为实际上是在处理样本。

因此de_mean(),我们与均值函数的偏差是获取一组数据x,实际上是一个列表,并且它正在计算该组数据的均值。 return行包含一些 Python 技巧。 语法是说,我将创建一个新列表,并遍历x中的每个元素,将其称为xi,然后为此返回xi和整个数据集的均值xmean之间的差。 该函数返回一个新的数据列表,该列表表示每个数据点与平均值的偏差。

我的covariance()函数将对输入的两组数据执行此操作,除以数据点的数量减去 1。还记得上一章中关于样本数与总体数的事情吗? 好吧,这正在这里发挥作用。 然后,我们可以使用这些函数,看看会发生什么。

为了扩展此示例,我将构造一些数据,以尝试找到页面速度之间的关系,即页面在网站上的呈现速度以及人们的花费。 例如,在亚马逊,我们非常担心页面呈现的速度与人们在经历之后花费多少钱之间的关系。 我们想知道网站的速度与人们实际在网站上花费的钱之间是否存在实际关系。 这是您可能要弄清楚的一种方法。 让我们只为页面速度和购买量生成一些正态分布的随机数据,由于它是随机的,因此它们之间不会有真正的相关性。

pageSpeeds = np.random.normal(3.0, 1.0, 1000) 
purchaseAmount = np.random.normal(50.0, 10.0, 1000) 

scatter(pageSpeeds, purchaseAmount) 

covariance (pageSpeeds, purchaseAmount) 

因此,就像在这里进行健全性检查一样,我们将从散布这些东西开始:

您会看到,由于每个属性的正态分布,它倾向于聚集在中间,但是两者之间没有真正的关系。 对于任何给定的页面速度而言,花费的金额是多种多样的,对于任何给定的金额而言,页面速度的种类繁多,因此,除了那些来自随机性或正态分布性质的页面速度之外,没有任何实际的相关性。 果然,如果我们计算这两组属性的协方差,我们最终会得到一个非常小的值 -0.07。 因此,这是一个非常小的协方差值,接近零。 这意味着这两件事之间没有真正的联系。

现在,让生活变得更加有趣。 让我们实际使购买金额成为页面速度的真实函数。

purchaseAmount = np.random.normal(50.0, 10.0, 1000) / pageSpeeds 

scatter(pageSpeeds, purchaseAmount) 

covariance (pageSpeeds, purchaseAmount) 

在这里,我们使事情有点随机,但是我们在这两组值之间创建了真实的关系。 对于给定的用户,他们遇到的页面速度和花费的金额之间存在真实的关系。 如果将其绘制出来,我们将看到以下输出:

您可以看到实际上存在一条小曲线,事物趋于紧密对齐。 事情在底部附近变得有点奇怪,只是因为事情是随机发生的。 如果我们计算协方差,则最终会得到更大的值 -8,而重要的是该数量的大小。 正负号只表示正相关或负相关,但是值 8 表示该值比零高得多。 因此,这里发生了一些事情,但是同样很难解释 8 的实际含义。

这就是相关性的体现,我们通过标准差对所有内容进行归一化,如以下代码所示:

def correlation(x, y): 
stddevx = x.std() 
stddevy = y.std() 
return covariance(x,y) / stddevx / stddevy  #In real life you'd check for divide by zero here 

correlation(pageSpeeds, purchaseAmount) 

再次,从第一原理开始,我们可以获取两组属性之间的相关性,计算每个属性的标准差,然后计算这两个属性之间的协方差,然后除以每个数据集的标准差。 这样便得到了相关值,该值被标准化为 -1 比 1。我们最终得到的值是 -0.4,这表明在负方向上这两件事之间存在一些相关性:

这不是一条完美的线,应该是 -1,但是那里发生了一些有趣的事情。

相关系数 -1 表示完全负相关,0 表示没有相关,1 表示完全正相关。

计算相关性-NumPy 方法

现在,NumPy 实际上可以使用corrcoef()函数为您计算相关性。 让我们看下面的代码:

np.corrcoef(pageseeds, purchaseAmount) 

此行提供以下输出:

array([(1\.         ,-046728788], 
      [-0.46728788], 1\.       ]) 

因此,如果我们想以一种简单的方式进行操作,则可以使用np.corrcoef(pageSpeeds, purchaseAmount),然后返回给您的是一个数组,该数组为您提供传入数据集的每种可能组合之间的相关性。 读取输出是:1 表示比较pageSpeeds与自身和purchaseAmount与自身之间存在完美的关联,这是可以预期的。 但是,当您开始比较pageSpeedspurchaseAmountpurchaseAmountpageSpeeds时,最终会得到 -0.4672 的值,这大约是我们用困难的方式得到的结果。 几乎不会有精度误差,但这并不是很重要。

现在,我们可以通过构造一个完全线性的关系来强制建立一个完美的相关性,因此让我们看一个例子:

purchaseAmount = 100 - pageSpeeds * 3 

scatter(pageSpeeds, purchaseAmount) 

correlation (pageSpeeds, purchaseAmount) 

再一次,在这里,我们期望相关性达到 -1,以获得完美的负相关性,实际上,这就是我们得出的结果:

再次提醒您:相关性并不意味着因果关系。 仅仅因为人们在拥有更快的页面速度时可能会花更多钱,也许这意味着他们可以负担得起更好的互联网连接。 也许这并不意味着页面呈现的速度与人们的花费之间实际上存在因果关系,但是它告诉您存在一种有趣的关系,值得进一步研究。 如果不运行实验,就无法说出因果关系,但是相关性可以告诉您可能要运行的实验。

相关性的活动

因此,请弄脏您的手,卷起袖子,我希望您使用numpy.cov()函数。 这实际上是让 NumPy 为您计算协方差的一种方法。 我们看到了如何使用corrcoef()函数计算相关性。 因此,返回并仅使用numpy.cov()函数重新运行这些示例,看看是否获得相同的结果。 它应该是相当不错的,所以不要使用我从头开始编写的协方差函数来做到这一点,而要使用 NumPy 看看是否可以获得相同的结果。 同样,此练习的目的是使您熟悉使用 NumPy 并将其应用于实际数据。 因此,看看它在哪里。

在理论和实践中都有协方差和相关性。 有一项非常有用的技术,因此请务必记住本节。 让我们继续。

条件概率

接下来,我们将讨论条件概率。 这是一个非常简单的概念。 它试图找出发生其他事情的可能性。 尽管听起来很简单,但实际上可能很难将它的一些细微差别包裹住。 因此,多喝杯咖啡,确保您的思维能力处于开启状态,并且如果您准备在此处提出一些更具挑战性的概念。 我们开工吧。

条件概率是一种衡量彼此发生的两件事之间的关系的方法。 假设我想找到一个事件发生的可能性,因为另一个事件已经发生。 条件概率为您提供了解决这一问题的工具。

我试图以条件概率来查找是否有两个相互依赖的事件。 也就是说,两者都会发生的概率是多少?

用数学符号表示,这里的方式是P(A, B)表示AB彼此独立发生的概率。 也就是说,无论其他什么情况,这两种情况发生的概率是多少?

而将P(B | A)表示为给定AB的概率。那么,给定事件A已经发生的B的概率是多少? 有点不同,这些东西是这样关联的:

给定AB的概率等于AB的概率超过A单独发生的概率,因此这可以消除B的概率取决于A的概率。

这里有一个例子会更有意义,所以请允许我。

假设我给您,我的读者两个测试,并且你们中有 60% 通过了这两个测试。 现在,第一个测试更加容易,您中的 80% 通过了该测试。 我可以使用此信息来计算通过第一项考试的读者中还通过第二项考试的读者所占的百分比。 因此,这是一个真实的例子,说明给定BB概率与AB的概率之间的差异。

我将A表示通过第一项测试的概率,将B表示通过第二项测试的概率。 我要寻找的是给定您通过了第一项考试即第二项考试的概率,即P(B | A)

因此,假设您通过了第一个测试,则通过第二个测试的概率等于通过两个测试的概率P(A, B)(我知道您中有 60% 的人通过了两个测试,而每个测试都与其他无关)除以通过第一项测试的概率P(A),即 80%。 算出 60% 的人通过了两个测试,80% 的人通过了第一次测试,因此,如果您通过了第一个测试,则通过第二次测试的概率为 75%。

好吧,围绕这个概念有点困难。 我花了一些时间才真正地内化了某些事物给定某种事物的概率与两件事相互独立发生的概率之间的差异。 在继续之前,请确保内部化此示例及其实际工作方式。

Python 中的条件概率练习

好了,让我们继续使用一些真实的 Python 代码做另一个更复杂的示例。 然后,我们可以看到如何使用 Python 实际实现这些想法。

让我们在这里将条件概率付诸行动,并使用一些想法使用一些捏造的数据找出年龄和购买物品之间是否存在关系。 继续并在此处打开ConditionalProbabilityExercise.ipynb,如果愿意,可以跟我来。

我要做的是写一些 Python 代码来创建一些假数据:

from numpy import random 
random.seed(0) 

totals = {20:0, 30:0, 40:0, 50:0, 60:0, 70:0} 
purchases = {20:0, 30:0, 40:0, 50:0, 60:0, 70:0} 
totalPurchases = 0 
for _ in range(100000): 
    ageDecade = random.choice([20, 30, 40, 50, 60, 70]) 
    purchaseProbability = float(ageDecade) / 100.0 
    totals[ageDecade] += 1 
    if (random.random() < purchaseProbability): 
        totalPurchases += 1 
        purchases[ageDecade] += 1 

我要做的是容纳 100,000 个虚拟人员,并将他们随机分配到年龄段。 他们可以是 20 多岁,30 多岁,40 多岁,50 多岁,60 多岁或 70 多岁。 我还将为他们分配他们在一段时间内购买的一些商品,并根据他们的年龄来加权购买商品的可能性。

该代码最终要做的是使用 NumPy 的random.choice()函数将每个人随机分配到一个年龄组。 然后,我将分配购买商品的可能性,并对其进行加权,以使年轻人购买商品的可能性低于老年人。 我要遍历 100,000 个人,然后将所有内容加起来,最后得到的是两个 Python 词典:一个给我每个年龄段的总人数,另一个给我每个年龄段内购买的商品数。 我还将跟踪总体上购买的商品总数。 让我们继续运行该代码。

如果您想花一点时间研究一下该代码,并弄清楚它是如何工作的,则可以使用 IPython 笔记本。 您也可以稍后再讨论。 让我们看看最终的结果。

我们的totals字典告诉我们每个年龄段有多少人,并且分布均匀,就像我们期望的那样。 实际上,每个年龄段的购买量都随着年龄的增长而增加,因此 20 岁的人只购买了大约 3,000 东西,而 70 岁的人购买了大约 11,000 的东西,整个人口总共购买了大约 45,000 的东西。

让我们使用这些数据来研究条件概率的想法。 让我们首先弄清楚,如果您 30 岁左右,可以购买东西的可能性是多少。 如果我们称购买商品为E,则表示该符号为P(E | F);如果您的年龄在 30 多岁,则表示F

现在我们有了这个奇特的方程式,它为您提供了一种在给定P(E, F)P(E)的情况下计算P(E | F)的方式 ,但我们不需要。 您不会在看到任何东西时盲目地应用方程式。 您必须直观地考虑数据。 它告诉我们什么? 考虑到您 30 多岁,我想算出购买商品的可能性。 好吧,我拥有直接计算所需的所有数据。

PEF = float(purchases[30]) / float(totals[30]) 

我在purchases[30]桶中得到 30 岁的人购买了多少东西,我知道那里有多少 30 岁的人。 因此,我可以将这两个数字相除,得出 30 岁购买次数与 30 岁购买次数的比率。 然后,我可以使用print命令将其输出:

print ("P(purchase | 30s): ", PEF) 

考虑到您 30 岁左右时大约占 30%,我最终有可能购买某种东西:

P(purchase | 30s): 0.2992959865211 

请注意,如果您使用的是 Python 2,则print命令没有括号,因此应该是:

print "p(purchase | 30s): ", PEF 

如果我想找到P(F),那仅是总体上是 30 的概率,我可以将 30 岁的总人数除以我的数据集中的人数,即 100,000 :

PF = float(totals[30]) / 100000.0 
print ("P(30's): ", PF) 

同样,如果您使用的是 Python 2,请删除print语句周围的括号。这应该提供以下输出:

P(30's): 0.16619 

我知道进入您的30s的可能性约为 16%。

现在,我们将找出P(E),它仅代表购买产品的总体可能性,与您的年龄无关:

PE = float(totalPurchases) / 100000.0 
print ("P(Purchase):", PE) 

P(Purchase): 0.45012 

在此示例中,结果约为 45%。 我可以将每个人购买的商品总数(不分年龄),再除以总人数即可得出购买的总体概率。

好吧,那我这里有什么? 假设您在 30 多岁时购买的东西大约是 30%,那么我有购买的可能性,那么总体上,我有购买大约 45% 的东西的可能性。

现在,如果EF独立,并且年龄无关紧要,那么我希望P(E | F)P(E)大致相同。 考虑到您 30 多岁,我希望购买商品的可能性与购买商品的整体可能性大致相同,但事实并非如此,对吧? 而且因为它们不同,所以告诉我它们实际上是某种程度上的依赖。 因此,这是使用条件概率来梳理数据中这些依赖关系的一种小方法。

让我们在这里做更多的注释。 如果一起看到P(E)P(F),则意味着将这些概率相乘。 我可以将购买的总概率乘以30s中的总概率:

print ("P(30's)P(Purchase)", PE * PF) 

P(30's)P(Purchase) 0.07480544280000001 

算下来大约是 7.5%。

从概率的工作方式来看,我知道,如果我想获得两件事同时发生的概率,那就像乘以它们各自的概率一样。 因此,事实证明P(E, F)的发生与P(E)P(F)相同。

print ("P(30's, Purchase)", float(purchases[30]) / 100000.0) 
P(30's, Purchase) 0.04974 

现在,由于数据的随机分布,它不可能完全相同。 记住,我们在这里谈论的是概率,但是它们在同一范围内,所以有意义的是,大约 5% 对 7%,足够接近。

现在,这又不同于P(E | F),因此既在30s中又购买东西的概率不同于在30s中购买东西的概率。

现在,让我们在这里进行一些理智检查。 我们可以检查前面在“条件概率”部分中看到的等式,该方程式表示,假设您在30s中的购买某物的概率,等于在30s中并购买某物的概率,除以购买某物的概率。 也就是说,我们检查P(E | F) = P(E, F) / P(F)

(float(purchases[30]) / 100000.0) / PF  

这给我们:

Out []:0.29929598652145134 

果然,它确实可以解决。 如果我考虑买入某物的概率高于您在30s中的总概率,那么我们最终得到大约 30%,这几乎是我们最初为P(E | F)。 所以方程式起作用了,是的!

好吧,很难缠住这些东西。 我知道这有点令人困惑,但是如果您需要,请再次进行遍历,研究一下,并确保您了解这里发生的事情。 我在这里尝试输入足够的示例,以说明对这些内容的不同思考组合。 一旦您将其内部化,我将挑战您自己在这里实际进行一些工作。

条件概率分布

我要您做的是修改以下在上一节中使用的 Python 代码。

from numpy import random 
random.seed(0) 

totals = {20:0, 30:0, 40:0, 50:0, 60:0, 70:0} 
purchases = {20:0, 30:0, 40:0, 50:0, 60:0, 70:0} 
totalPurchases = 0 
for _ in range(100000): 
ageDecade = random.choice([20, 30, 40, 50, 60, 70]) 
purchaseProbability = 0.4 
totals[ageDecade] += 1 
if (random.random() < purchaseProbability): 
totalPurchases += 1 
purchases[ageDecade] += 1 

对其进行修改,使其实际上不依赖购买和年龄。 也要使机会均匀分布。 看看对您的结果有什么影响。 您是否最终有条件地置入30s并购买商品与购买整体商品的概率不同? 关于您的数据以及这两个不同属性之间的关系,这告诉您什么? 继续尝试,并确保您实际上可以从这些数据中获得一些结果并了解正在发生的事情,而我将在一分钟之内通过我自己的解决方案来完成该练习。

因此,无论从理论上还是在实践中,这都是条件概率。 您会看到它有很多细微差别,并且有很多令人困惑的符号。 如果您需要将头包起来,请返回并再次浏览本节。 我给您分配了一项作业,所以现在就去做,看看您是否可以实际修改我的 IPython 笔记本中的代码,以产生针对不同年龄段的固定购买概率。 回来,我们将看看我如何解决该问题以及取得的结果。

我的作业解决方案

你做功课了吗,但愿如此。 让我们看一下我的解决方案,看看条件概率如何告诉我们假数据集中年龄和购买概率之间是否存在关系。

提醒您,我们试图做的是消除年龄和购买可能性之间的依存关系,看看我们是否可以在条件概率值中实际反映这一点。 这是我得到的:

from numpy import random 
random.seed(0) 

totals = {20:0, 30:0, 40:0, 50:0, 60:0, 70:0} 
purchases = {20:0, 30:0, 40:0, 50:0, 60:0, 70:0} 
totalPurchases = 0 
for _ in range(100000): 
    ageDecade = random.choice([20, 30, 40, 50, 60, 70]) 
    purchaseProbability = 0.4 
    totals[ageDecade] += 1 
    if (random.random() < purchaseProbability): 
        totalPurchases += 1 
        purchases[ageDecade] += 1 

我在这里所做的是,我使用了原始代码段来创建我们的年龄组字典,以及每个年龄组为 100,000 个随机人群购买了多少代码。 我没有使购买概率取决于年龄,而是使它成为 40% 的恒定概率。 现在,我们只是将人们随机分配到一个年龄段,他们都有相同的购买可能性。 让我们继续运行它。

现在,这一次,如果我计算P(E | F),即假设您在30s中,则有购买某物品的可能性,我得出了大约 40% 的概率。

PEF = float(purchases[30]) / float(totals[30]) 
print ("P(purchase | 30s): ", PEF) 

P(purchase | 30s):  0.398760454901 

如果将其与购买的总体可能性进行比较,那也大约是 40%。

PE = float(totalPurchases) / 100000.0 
print ("P(Purchase):", PE) 

P(Purchase): 0.4003 

我在这里可以看到,假设您身在30s中,则购买商品的可能性与不考虑年龄的商品(即P(E | F) 非常接近P(E))。 这表明这两件事之间没有真正的关系,实际上,我知道这些数据中没有。

现在,在实践中,您可能只是看到随机的机会,因此,您想要研究多个年龄段的人。 您可能希望查看多个数据点,以查看是否确实存在关联,但这表明在我们修改的此示例数据中,年龄和购买可能性之间没有关联。

因此,这是有条件的概率在起作用。 希望您的解决方案相当接近,并能获得类似的结果。 如果没有,请返回并研究我的解决方案。 如果您需要打开书本并对其进行研究和使用,它就在本书的数据文件中,即ConditionalProbabilitySolution.ipynb。 显然,数据的随机性会使您的结果有所不同,并且取决于您为总体购买概率做出的选择,但这就是想法。

接下来,让我们继续进行贝叶斯定理。

贝叶斯定理

既然您已经了解了条件概率,就可以了解如何应用基于条件概率的贝叶斯定理。 这是一个非常重要的概念,特别是如果您要进入医学领域,但是它也广泛适用,一会儿您就会明白为什么。

您会听到很多有关此的信息,但是很少有人真正了解它的含义或意义。 当人们误以统计数据误导您时,它可以非常定量地告诉您,因此让我们看看它是如何工作的。

首先,让我们从高层次谈谈贝叶斯定理。 贝叶斯定理就是这样:给定BA的概率等于A的概率乘以给定BB的概率乘以B的概率。因此,您可以将AB替换为所需的任何东西。

关键的见解是,依赖于B的事物的概率在很大程度上取决于BA的基本概率。人们一直都忽略这一点。

一个常见的例子是药物测试。 我们可能会说,假设您的药物测试呈阳性,那么成为药物的实际使用者的可能性是多少。 贝叶斯定理之所以重要,是因为它指出,这在很大程度上取决于A的概率和B的概率。假设您测试为阳性,成为吸毒者的概率在很大程度上取决于B的基本总体概率。 成为毒品使用者和测试呈阳性的总体可能性。 药物测试准确的可能性在很大程度上取决于成为人群中吸毒者的总体可能性,而不仅仅是测试的准确率。

这也意味着,给定AB的概率与给定BA的概率不是同一回事。也就是说,假设您被测为阳性,成为吸毒者的概率与假设为正的概率非常不同。 你是吸毒者。 您可以看到前进的方向。 这是一个非常现实的问题,其中医学诊断测试或药物测试会产生许多误报。 您仍然可以说,测试检测到用户的可能性很高,但这并不一定意味着在您测试为肯定的情况下成为用户的可能性就很高。 这是两个不同的事物,贝叶斯定理可让您量化该差异。

让我们再把这个例子钉在家里。

同样,药物测试可能是应用贝叶斯定理证明观点的常见示例。 即使是高度准确的药物测试,也会产生比真实阳性更多的假阳性。 因此,在这里的示例中,我们将提出一种药物测试,该测试可以在 99% 的时间内准确地识别出某种药物的使用者,并且对于 99% 的非使用者来说准确地得出阴性结果,但只有 0.3% 整个人口实际上正在使用有问题的药物。 因此,我们实际成为毒品使用者的可能性很小。 看来 99% 的非常高的准确率实际上还不够高,对吧?

我们可以算出以下公式:

  • 事件A是该药物的使用者
  • 事件B是对该药物进行了正面测试

因此,让事件A表示您是某种药物的使用者,事件B表示您使用此药物测试对药物进行了积极测试的事件。

我们需要确定总体上进行积极测试的可能性。 如果您是用户,则可以通过测试呈阳性的概率之和,如果您不是用户,则可以通过测试呈阳性的概率之和。 因此,在此示例中,p(B)计算为 1.3%(0.99 * 0.003 + 0.01 * 0.997)。 因此,我们有B的概率,即在不了解您的情况下对整个药物进行积极测试的概率。

假设您的测试积极,让我们进行数学计算并计算出成为该药物使用者的可能性。

因此,假设您实际上是一名吸毒者,则检测结果呈阳性的概率就是成为吸毒者整体(P(A))的概率,即 3%(您知道 (3% 的人口是吸毒者)乘以P(B | A),即假设您是用户,则该测试为阳性的概率除以总体测试为阳性的概率 1.3 %。 再次,此测试听起来像是 99% 的非常高的准确率。 我们有 0.3% 的人口使用某种药物,乘以 99% 的准确率,再除以总体检测呈阳性的概率,即 1.3%。 因此,如果您对它进行了阳性测试,那么成为该药物的实际使用者的概率只有 22.8%。 因此,即使此药物测试在 99% 的时间内都是准确的,但在您测试呈阳性的大多数情况下,它仍提供错误的结果。

即使P(B | A)高(99%),也并不意味着P(A | B)高。

人们一直无视这一点,因此,如果要从贝叶斯定理中吸取教训,那就总是将这类事情付之东流。 将贝叶斯定理应用于这些实际问题,您经常会发现,如果您要解决给定问题的总体发生率较低的情况,那么听起来准确率很高的结果实际上可能会产生非常令人误解的结果。 我们在癌症筛查和其他类型的医学筛查中也看到了同样的事情。 这是一个非常现实的问题。 由于不了解贝叶斯定理,导致许多人接受非常非常非常真实且不必要的手术。 如果您要进入具有大数据的医学专业,请,请记住这个定理。

这就是贝叶斯定理。 始终记住,某物被赋予某种其他东西的可能性与另一种东西是不同的,它实际上在很大程度上取决于您要测量的这两件事的基本概率。 记住这是非常重要的事情,并且始终牢记这一点。 贝叶斯定理为您提供量化效果的工具。 我希望它证明有用。

总结

在本章中,我们讨论了如何绘制和绘制数据图形以及如何使用 Python 中的matplotlib库使图形看起来更漂亮。 我们还介绍了协方差和相关性的概念。 我们看了一些示例,并找出了使用 Python 的协方差和相关性。 我们分析了条件概率的概念,并查看了一些示例以更好地理解它。 最后,我们看到了贝叶斯定理及其重要性,特别是在医学领域。

在下一章中,我们将讨论预测模型。

四、预测模型

在本章中,我们将研究什么是预测建模,以及预测建模如何使用统计数据来预测现有数据的结果。 我们将介绍真实的示例,以更好地理解这些概念。 我们将了解回归分析的含义,并详细分析其某些形式。 我们还将看一个为我们预测汽车价格的示例。

这些是我们将在本章中介绍的主题:

  • 线性回归以及如何在 Python 中实现
  • 多项式回归,其应用和实例
  • 多元回归以及如何在 Python 中实现
  • 我们将构建一个使用 Python 预测汽车价格的示例
  • 高阶模型的概念以及有关它们的一些知识

线性回归

让我们谈谈回归分析,这是数据科学和统计学中非常流行的主题。 这是关于尝试将曲线或某种函数拟合到一组观察值,然后使用该函数来预测尚未看到的新值的全部方法。 这就是线性回归的全部!

因此,线性回归使一条直线适合一组观测值。 例如,假设我有一堆人,我测量过,这些人的两个特征是他们的体重和身高:

我在x轴上显示了重量,在y轴上显示了高度,我可以绘制所有这些数据点,例如人们的体重与身高, 我可以说:“嗯,这看起来像是线性关系,不是吗?也许我可以在其中拟合一条直线,然后用它来预测新值”,这就是线性回归的作用。 在本例中,我的斜率为 0.6,y截距为 130.2,它们定义了一条直线(直线的等式为y = mx + b, 其中,m是斜率,by截距)。 给定一个斜率和一个y截距,该截距适合我所拥有的最佳数据,我可以使用该线来预测新值。

您可以看到,我观察到的重量仅上升到重 100 公斤的人。 如果有人重 120 公斤怎么办? 好吧,我可以根据之前的数据,使用那条线找出身高 120 公斤的人的身高在哪里。

我不知道为什么他们称其为回归。 回归意味着您正在向后做某事。 我想您可以想到这一点,因为您正在创建一条线以根据您过去的观察来预测新值,时间倒退,但这似乎有点困难。 老实说,这只是一个令人困惑的术语,并且是一种我们用非常花哨的术语来掩盖我们对非常简单的概念所做的一种方式。 全部就是将一条直线拟合到一组数据点。

普通最小二乘法

线性回归如何工作? 在内部,它使用一种称为普通最小二乘法的技术。 也称为 OLS。 您可能还会看到这个术语。 它的工作方式是尝试使每个点和线之间的平方误差最小化,其中误差只是每个点和线之间的距离。

因此,我们对这些误差的所有平方求和,这听起来很像我们计算方差时的样子,只是除了相对于均值而不是相对于所定义的直线而言。 我们可以测量那条线上的数据点的方差,并且通过最小化方差,我们可以找到最合适的线:

现在,您将不再需要真正地自己动手做,但是如果您出于某些原因必须这样做,或者您只是对幕后发生的事情感到好奇,我现在将为您描述整体算法,以及如何实际计算斜率和y-如果需要一天的时间,则很难拦截自己。 其实并不那么复杂。

还记得直线的斜率截距方程吗? 它是y = mx + c。 斜率刚好是两个变量之间的相关性乘以Y中的标准差除以X中的标准差。 标准差只是自然而然地渗入到数学中似乎有些奇怪,但是请记住,相关性也已纳入标准差,因此不必重新引入该术语也就不足为奇了。

然后可以将截距计算为Y的平均值减去斜率乘以X的平均值。 再说一次,尽管实际上并不是那么困难,Python 会为您完成所有工作,但要点是,这些并不是要运行的复杂事情。 实际上,它们可以非常有效地完成。

请记住,最小二乘最小化了从每个点到线的平方误差之和。 关于线性回归的另一种思考方式是,您定义一条线,该线代表该处观察线的最大可能性。 也就是说,对于给定的x值,y值的最大概率。

人们有时将线性回归最大似然估计称为“线性回归最大似然估计”,这只是人们在一个非常简单的名称上冠以幻想的例子,因此,如果您听到有人谈论最大似然估计,那么他们实际上就是在谈论回归。 他们只是想听起来很聪明。 但是现在您也知道该术语,因此您听起来也很聪明。

梯度下降技术

进行线性回归的方法不止一种。 我们已经将普通最小二乘法讨论为将一条线拟合到一组数据的一种简单方法,但是还有其他一些技术,梯度下降是其中之一,并且在三维数据中效果最好。 因此,它尝试为您遵循数据的轮廓。 这非常花哨,并且显然在计算上要贵一些,但是如果您要将 Python 与普通最小二乘法进行比较,Python 确实使您可以轻松地进行尝试。

在处理 3D 数据时,使用梯度下降技术可能很有意义。

通常,尽管最小二乘是进行线性回归的完美选择,并且始终是合法的事情,但是如果遇到梯度下降,您将知道这只是线性回归的另一种方法, 通常在高维数据中看到。

确定系数或 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 函数。 因此,我们将不使用支出金额的观察值,而只是使用slope乘以x加上从上述linregress()调用中获得的intercept来找到预测值。 本质上,在这里,我们将像以前一样做一个散点图,以显示原始数据点,即观察值。

然后,我们还将使用通过返回的线方程式创建的fitLine在同一pyplot实例上调用plot,并将它们一起显示。 当我们这样做时,它看起来如下图:

您可以看到我们的产品线实际上非常适合我们的数据! 它正好位于中间,您需要预测新值的就是此预测函数。 给定以前看不见的新页面速度,我们可以仅使用斜率乘以页面速度加截距来预测花费的金额。 这就是全部,我认为那太好了!

线性回归活动

现在该弄脏你的手了。 尝试增加测试数据中的随机变化,看看是否有影响。 请记住,R 平方是对拟合的度量,我们要捕获多少方差,因此方差的数量也是如此……您为什么不看看它是否真的有所作为。

那是线性回归,这是一个非常简单的概念。 我们要做的只是将一条直线拟合到一组观测值,然后我们可以使用该直线对新值进行预测。 这里的所有都是它的。 但是为什么要限制自己呢? 我们还可以执行其他类型的回归操作,这些操作更为复杂。 接下来,我们将探讨这些。

多项式回归

我们已经讨论了线性回归,其中我们将一条直线拟合到一组观测值。 多项式回归是我们的下一个主题,它使用高阶多项式来拟合您的数据。 因此,有时您的数据可能并不适合直线。 这就是多项式回归的地方。

多项式回归是回归的更一般情况。 那么,为什么将自己限制在一条直线上呢? 也许您的数据实际上没有线性关系,或者可能有某种曲线,对吗? 这种情况经常发生。

并非所有关系都是线性的,但是线性回归只是我们可以做的一整类回归的一个例子。 如果您还记得我们最终得到的线性回归线的形式为y = mx + b,则可以从普通最小二乘法或其他方法的线性回归分析中获得mb的值。 现在,这只是一阶或一阶多项式。 阶数或次数是您看到的x的幂。 这就是一阶多项式。

现在,如果需要,我们还可以使用二阶多项式,看起来像y = ax ^ 2 + bx + c。 如果我们使用二阶多项式进行回归,则将获得abc的值。 或者我们可以做一个ax ^ 3 + bx ^ 2 + 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 平方通过多元回归来衡量拟合的质量。 它以相同的方式工作,尽管在进行多元回归时您需要假设的一件事是因素本身并不相互依赖...而且并非总是如此。 因此,有时您需要在脑后留些小警告。 例如,在这个模型中,我们将假设汽车的行驶里程和寿命无关。 但实际上,它们可能紧密相关! 这是该技术的局限性,可能根本无法捕获效果。

使用 Python 的多元回归

幸运的是,有一个statsmodel包可用于 Python,使进行多元回归非常容易。 让我们来看看它是如何工作的。 让我们使用 Python 进行一些多元回归。 我们将在这里使用一些有关凯利蓝皮书中汽车价值的真实数据。

import pandas as pd
df = pd.read_excel('http://cdn.sundog-soft.com/Udemy/DataScience/cars.xls')

我们将在此处引入一个名为pandas的新包,它使我们能够非常轻松地处理表格数据。 它使我们能够读入数据表,并对它们进行重新排列,修改,切分并以不同的方式切成小方块。 我们将大量使用。

我们将pandas导入为pd,并且pd具有read_Excel()函数,我们可以使用该函数继续通过 HTTP 从 Web 读取 Microsoft Excel 电子表格。 因此,那里的 Pandas 功能相当强大。

我已经开始在自己的域上为您托管该文件,如果我们运行该文件,它将把它加载到我们称为dfDataFrame对象中。 现在,我可以在此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() 

现在,我遇到的问题是该模型是文本,例如 Century for Buick,并且您还记得,当我进行这种分析时,所有内容都必须是数字。 在代码中,我使用pandas中的Categorical() function实际将它在DataFrame中看到的一组模型名称转换为一组数字; 即一组代码。 我要说的是,此模型在 x 轴上的输入是里程(Mileage),模型转换为序数(Model_ord)和门数(Doors)。 我要在 y 轴上预测的是价格(Price)。

代码的后两行仅创建一个我称为est的模型,该模型使用普通的最小二乘 OLS,并使用我提供的列MileageModel_ordDoors来拟合该模型 。 然后,我可以使用摘要调用来打印出模型的外观:

您可以在此处看到 R 平方非常低。 模型的确不是很好,但是我们可以洞悉各种误差是什么,有趣的是,最低标准误差与行驶里程有关。

现在,我之前已经说过,系数是一种确定哪些项目重要的方法,尽管输入数据已标准化,但这是正确的。 也就是说,如果一切都在 0 到 1 的范围内。如果不是,则这些系数可以补偿所看到的数据的规模。 在这种情况下,如果您不处理规范化数据,则查看标准错误会更有用。 在这种情况下,我们可以看到里程数实际上是该特定模型的最大因素。 我们能早点弄清楚吗? 好吧,我们可以做一点切片和切块,以找出门的数量实际上根本不会影响价格。 让我们运行以下几行:

y.groupby(df.Doors).mean()

那里有一些pandas语法。 只需一行代码即可在 Python 中完成,这非常酷! 这将打印出一个新的DataFrame,其中显示了给定门数的平均价格:

我可以看到,平均而言,两门车的售价实际上要高于四门车的售价。 如果有的话,门的数量与价格之间存在负相关,这有点令人惊讶。 不过,这是一个很小的数据集,因此我们当然无法从中读到很多含义。

多元回归活动

作为一项活动,请在所需的地方弄乱伪造的输入数据。 您可以下载数据并与电子表格混淆。 从本地硬盘驱动器而不是 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 折交叉验证

现在有一个解决这个问题的方法,称为 K 折交叉验证,我们将在本书的后面看到一个示例,但是基本概念是您需要多次训练/测试。 因此,您实际上将数据不仅分为一个训练集和一个测试集,还分为多个随机分配的段,即k个段。 那就是 k 的来源。 然后将其中一个细分保留为测试数据,然后开始在其余细分上训练模型,并根据测试数据集衡量其表现。 然后,您可以从这些训练集模型的每个结果中获得平均表现,并获得其 R 平方平均得分。

这样一来,您实际上是在对数据的不同部分进行训练,并使用相同的测试集对其进行测量,如果您的模型过度适合您的训练数据的特定部分,则其他模型会将其平均化,这归功于 K 折交叉验证。

这是 K 折交叉验证步骤:

  1. 将您的数据分成 K 个随机分配的段
  2. 保留一个细分作为测试数据
  3. 在其余的K-1段上进行训练,并根据测试集衡量其表现
  4. 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 分(80 之后的所有事物)将进入测试集。 您可能在前面的《Python 基础知识》一章中记起了这一点,在该章中,我们介绍了执行此操作的语法,并且我们将在此处针对购买金额执行相同的操作:

trainX = pageSpeeds[:80] 
testX = pageSpeeds[80:] 

trainY = purchaseAmount[:80] 
testY = purchaseAmount[80:] 

现在,在前面的部分中,我已经说过,您不应该像这样将数据集一分为二,而是应该随机抽样以进行训练和测试。 但是在这种情况下,由于我的原始数据无论如何都是随机生成的,因此可以解决问题,所以实际上并没有韵律或原因。 但是在实际数据中,您需要在拆分数据之前先对其进行混洗。

现在,我们将介绍一种方便的方法,您可以将其用于整理数据的目的。 另外,如果您使用的是 Pandas 包,则其中有一些方便的功能可以自动为您制作训练和测试数据集。 但是,我们将在这里使用 Python 列表进行操作。 因此,让我们可视化最终得到的训练数据集。 我们将散布我们的训练页面速度和购买金额的图表。

scatter(trainX, trainY) 

这就是您的输出现在应如下所示:

基本上,已经绘制了从原始完整数据集中随机选择的 80 个点。 它的形状基本相同,所以这是一件好事。 它代表了我们的数据。 那很重要!

现在,让我们绘制保留为测试数据的其余 20 点。

scatter(testX, testY) 

在这里,我们看到剩下的 20 个测试数据也具有与原始数据相同的总体形状。 因此,我认为这也是一个代表性的测试集。 当然,它比您在现实世界中看到的要小一些。 例如,如果您有 1,000 点而不是 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 平方分数作为测试数据? 这里适合什么程度? 去玩吧。 这应该是一个非常容易的练习,同时对您也非常有启发性。

因此,这实际上是训练/测试,这是掌握的一项非常重要的技术,并且您将一遍又一遍地使用它,以确保您的结果很好地适合您所拥有的模型,并且您的结果可以很好地预测看不见的值。 这是在进行建模时防止过拟合的好方法。

贝叶斯方法-概念

您是否想过电子邮件中的垃圾邮件分类器如何工作? 它怎么知道电子邮件可能是垃圾邮件? 好吧,一种流行的技术是朴素贝叶斯,这就是贝叶斯方法的一个例子。 让我们进一步了解它是如何工作的。 让我们讨论贝叶斯方法。

在本书的前面,我们确实是在谈论诸如药物测试之类的东西如何在其结果中产生误导性的情况下谈论贝叶斯定理的。 但是您实际上可以将相同的贝叶斯定理应用于垃圾邮件分类器等较大的问题。 因此,让我们深入研究它是如何工作的,这称为贝叶斯方法。

因此,只要回顾一下贝叶斯定理,就可以知道 A 给定 B 的概率等于 A 的总概率乘以 B 给 A 的 B 概率与 B 的总概率之和:

我们如何在机器学习中使用它? 实际上,我可以为此构建垃圾邮件分类器:一种算法可以分析一组已知的垃圾邮件电子邮件和一组已知的非垃圾邮件电子邮件,并训练模型以实际预测新电子邮件是垃圾邮件还是垃圾邮件。 不是。 这是在现实世界中的实际垃圾邮件分类器中使用的一项实际技术。

例如,假设电子邮件中包含“免费”一词,那么我们就可以判断出该电子邮件为垃圾邮件的可能性。 如果人们向您承诺免费提供东西,那可能是垃圾邮件! 因此,让我们解决这个问题。 假设您的电子邮件中包含“免费”一词,则该电子邮件为垃圾邮件的概率,等于一个邮件为垃圾邮件的总概率,乘以垃圾邮件包含“免费”一词的概率,除以一个邮件包含“免费”一词的总概率:

分子可以认为是消息为Spam并包含单词Free的概率。 但这与我们要寻找的有所不同,因为这是完整数据集中的几率,而不仅仅是包含Free的事物中的几率。 分母只是包含单词Free的整体概率。 有时,您将无法从所拥有的数据中立即访问该数据。 如果不是,则可以将其扩展为以下表达式(如果需要派生):

这为您提供了包含“免费”一词的垃圾邮件的百分比,当您试图弄清是否为垃圾邮件时,这将是一件很有用的事情。

但是,英语中的所有其他单词呢? 因此,我们的垃圾邮件分类程序不仅仅应了解“免费”一词,还应了解更多。 理想情况下,它应该自动选择邮件中的每个单词,并弄清楚这对特定电子邮件是垃圾邮件的可能性有多大贡献。 因此,我们可以做的是在训练过程中遇到的每个单词上训练模型,抛出诸如atheand之类的东西以及诸如此类的无意义的单词。 然后,当我们浏览新电子邮件中的所有单词时,我们可以将每个单词被视为垃圾邮件的概率相乘,从而获得该电子邮件为垃圾邮件的总体概率。

现在有一个原因叫朴素贝叶斯。 朴素是因为我们假设单词本身之间没有任何关系。 我们只是在消息中单独查看每个单词,然后基本上将每个单词对垃圾邮件的贡献的所有概率结合起来。 我们不是在看单词之间的关系。 因此,更好的垃圾邮件分类器可以做到这一点,但显然要困难得多。

因此,这听起来很麻烦。 但是总体想法并不难,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 的CountVectorizerMultinomialNB

现在让我们详细研究这段代码。 现在,我们可以跳过readFiles()dataFrameFromDirectory()的函数定义,然后转到我们的代码实际要做的第一件事,即创建一个 Pandas DataFrame对象。

我们将从字典中构造它,该字典最初包含一些空列表,用于在类的空列表中显示消息。 因此,这种语法是这样说的:“我想要一个包含两列的DataFrame:一列包含消息,每封电子邮件的实际文本;一列包含每封电子邮件的类别,即垃圾邮件还是垃圾邮件。 火腿”。 就是说我想创建一个小的电子邮件数据库,该数据库有两列:电子邮件的实际文本以及是否为垃圾邮件。

现在,我们需要使用 Python 语法将某些东西放入该数据库中,即放入该DataFrame中。 因此,我们将两种方法称为append()dataFrameFromDirectory(),将来自我的spam文件夹的所有垃圾邮件和来自ham文件夹的所有垃圾邮件实际放入DataFrame中。

如果您在这里玩耍,请确保修改传递给dataFrameFromDirectory()函数的路径以匹配您在系统中安装书籍资料的位置! 同样,如果您使用的是 Mac 或 Linux,请注意反斜杠和正斜杠以及所有其他内容。 在这种情况下,这没关系,但是如果您不在 Windows 上,则不会有驱动器号。 因此,只需确保这些路径实际上指向此示例的spamham文件夹所在的位置即可。

接下来,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()来使用朴素贝叶斯(Naive Bayes)实际创建一个模型,该模型将根据我们提供的信息来预测新电子邮件是否为垃圾邮件。

让我们继续运行它。 它运行很快! 我将在这里使用几个示例。 让我们尝试一个仅显示Free Money now!!!的邮件正文,这显然是垃圾邮件,再发送一个更纯真的邮件,仅显示"Hi Bob, how about a game of golf tomorrow?",因此我们将其传递给我们。

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') 

果然,它行得通! 因此,考虑到这两个输入消息数组Free Money now!!!Hi Bob,它告诉我第一个结果以垃圾邮件的形式返回,第二个结果以火腿的形式返回,这是我所期望的。 太酷了。 所以你有它。

活动

我们这里有一个非常小的数据集,因此您可以根据需要尝试通过它运行一些不同的电子邮件,看看是否得到不同的结果。 如果您真的想挑战自己,请尝试对本示例进行训练/测试。 因此,判断我的垃圾邮件分类器是否良好的真正衡量标准不仅仅是直观地判断Free Money now!!!是垃圾邮件。 您想定量地进行测量。

因此,如果您想挑战一点,请继续尝试将这些数据分为训练集和测试数据集。 实际上,您可以在线查询 Pandas 如何将数据轻松地分为训练和测试集,或者您可以手工完成。 一切为您工作。 查看是否可以将MultinomialNB分类器实际应用于测试数据集并衡量其表现。 因此,如果您想做一点运动,一点挑战,那就继续尝试一下。

多么酷啊? 我们只是使用 Python 中的几行代码编写了自己的垃圾邮件分类器。 使用 Scikit-learn 和 Python 相当容易。 这就是朴素贝叶斯(Naive Bayes)的作用,实际上,您已经可以将垃圾邮件或火腿邮件分类,然后进行分类。 很酷的东西。 接下来让我们讨论集群。

K 均值聚类

接下来,我们将讨论 K 均值聚类,这是一种无监督的学习技术,您可以将一组要组合成各种聚类的东西收集起来。 也许是电影类型或人口统计,谁知道呢? 但这实际上是一个非常简单的想法,所以让我们看看它是如何工作的。

K 均值聚类是机器学习中一种非常普遍的技术,您仅尝试获取一堆数据并仅根据数据本身的属性来找到有趣的事物群集。 听起来很花哨,但实际上很简单。 我们在 K 均值聚类中所做的就是尝试将我们的数据分为 K 个组-这就是 K 的来源,这就是您要将数据分为几个不同的组-通过找到 K 个质心来做到这一点。

因此,基本上,给定数据点所属的组由散点图中最接近的质心点定义。 您可以在下图中直观地看到它:

这显示了一个用 K 为 3 的 K 均值聚类的示例,并且正方形表示散点图中的数据点。 圆圈表示 K 均值聚类算法得出的质心,并且根据每个点最接近的质心为其分配了一个聚类。 真的,这就是全部。 这是无监督学习的一个例子。 在这种情况下,我们不会拥有大量数据,而对于给定的一组训练数据,我们已经知道正确的群集; 相反,您只是获得了数据本身,并且仅基于数据的属性就尝试自然地在这些群集上收敛。 这也是一个示例,其中您尝试查找甚至都不知道的集群或分类。 与大多数无监督学习技术一样,关键是要找到潜值,直到算法向您展示它们之前,您才真正意识到它们。

例如,百万富翁住在哪里? 我不知道,也许有一些富人倾向于居住的有趣的地理集群,而 K 均值聚类可以帮助您弄清楚这一点。 也许我真的不知道今天的音乐类型是否有意义。 这些天替代是什么意思? 不多吧? 但是通过对歌曲属性使用 K 均值聚类,也许我可以找到彼此相关的有趣的歌曲聚类,并为这些聚类表示的名称命名。 或者,也许我可以查看人口统计数据,也许现有的刻板印象不再有用。 也许西班牙裔失去了意义,实际上还有其他一些属性可以定义人群,例如,我可以通过聚类来发现。 听起来不错,不是吗? 真的很复杂的东西。 用 K 集群进行无监督的机器学习,听起来很花哨,但是和大多数数据科学技术一样,这实际上是一个非常简单的想法。

这是简单的英语算法:

  1. 随机选择 K 个质心(K 均值):我们从一组随机选择的质心开始。 因此,如果我们的 K 为 3,我们将在组中寻找三个聚类,然后在散点图中分配三个随机定位的质心。
  2. 将每个数据点分配给最接近的质心:然后,我们将每个数据点分配给最接近其的随机分配质心。
  3. 根据每个质心点的平均位置重新计算质心:然后为我们得出的每个聚类重新计算质心。 也就是说,对于最终得到的给定群集,我们将质心移动为所有这些点的实际中心。
  4. 迭代直到点停止更改对质心的分配:我们将再次执行所有操作,直到这些质心停止移动,达到一个确定的阈值,确定,我们在这里进行了收敛。
  5. 预测新点的群集:要预测我以前从未见过的新点的聚类,我们可以遍历质心位置并找出最接近哪个质心来预测其聚类。

让我们看一个图形示例,使它更有意义。 我们在下图中将第一个数字称为 A,第二个称为 B,第三个称为 C,第四个称为 D。

图像 A 中的灰色方块代表我们的散点图中的数据点。 轴代表事物的某些不同特征。 也许是年龄和收入; 这是我一直使用的示例,但可以是任何示例。 灰色方块可能代表个人或个人歌曲或个人,我想在两者之间找到关系。

因此,我首先从散点图中随机选择三个点开始。 可以在任何地方。 要从某个地方开始吧? 我选择的三个点(质心)已在图像 A 中显示为圆圈。因此,我要做的下一件事是为每个质心计算出最接近的灰度点之一。 这样,以蓝色阴影表示的点与此蓝色质心相关联。 绿点最接近绿色质心,而该单个红点最接近我选择的那个红色随机点。

当然,您可以看到这并不能真正反映出实际群集的位置。 因此,我要做的是获取每个群集中的点,并计算这些点的实际中心。 例如,在绿色集群中,所有数据的实际中心都降低了一点。 我们将质心向下移动一点。 红色群集只有一个点,因此其中心向下移动到该单个点所在的位置。 蓝点实际上非常靠近中心,因此仅移动了一点。 在下一个迭代中,我们最终得到类似于图像 D 的图像。现在您可以看到我们的红色事物集群已经增长了一点,事物已经移动了一点,即从绿色集群中获取的事物。

如果我们再次这样做,您可能可以预测接下来会发生什么。 绿色质心会稍微移动一点,而蓝色质心仍会在它的位置附近。 但是到最后,您将获得您可能希望看到的群集。 这就是 K 均值的工作方式。 因此,它只是不断迭代,试图找到合适的质心,直到事情开始四处移动,然后我们收敛于一个解决方案。

K 均值聚类的局限性

因此,K 均值聚类存在一些限制。 他们来了:

  1. 选择 K:首先,我们需要选择正确的 K 值,这根本不是一件容易的事情。 选择 K 的主要方法是从低开始,并根据想要的组数不断增加 K 的值,直到不再大幅度减少平方误差为止。 如果查看每个点到其质心的距离,可以将其视为误差度量。 在停止减少该错误指标的那一刻,您知道您可能有太多的群集。 因此,此时您无法通过添加其他集群真正获得任何更多信息。
  2. 避免局部最小值:此外,存在局部最小值的问题。 您可能会对最初选择的质心感到非常不走运,而且它们最终可能只收敛于局部现象,而不是更多的全局群集,因此通常,您需要运行几次并可能求平均结果。 我们称这种集成学习。 我们稍后再讨论,但是使用一组不同的随机初始值多次运行 K 均值总是一个好主意,只是看看您是否最终得到相同的总体结果。
  3. 标记群集:最后,K 均值聚类的主要问题是没有针对您获得的集群的标签。 它只会告诉您这组数据点在某种程度上是相关的,但是您不能在上面加上名称。 它无法告诉您该群集的实际含义。 假设我正在看一堆电影,并且 K 均值聚类告诉我这堆科幻电影就在这里,但我不会将它们称为“科幻”电影。 我要真正去研究数据并弄清楚,这些东西到底有什么共同点? 我怎么用英语来描述? 那是最困难的部分,而 K 均值并不能帮助您。 再次说明,Scikit-learn 使执行此操作变得非常容易。

现在让我们来看一个例子,将 K 均值聚类付诸实践。

根据收入和年龄将人员聚类

让我们看看使用 Scikit-learn 和 Python 进行 K 均值聚类有多么容易。

我们要做的第一件事是创建一些我们想要尝试聚类的随机数据。 为了简化操作,我们实际上将一些集群构建到伪造的测试数据中。 因此,让我们假设这些数据之间存在一些真实的基本关系,并且其中存在一些真实的自然群集。

为此,我们可以使用 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个人的集群。 因此,我们将Nk传递给createClusteredData().

我们的代码计算出每个群集首先计算出多少个点并将其存储在pointsPerCluster中。 然后,它建立了从空开始的列表X。 对于每个聚类,我们将创建一个介于 20,000 和 200,000 美元之间的收入随机质心(incomeCentroid),以及一个介于 20 和 70 岁之间的年龄随机质心(ageCentroid)。

我们在这里所做的是创建一个虚假的散点图,该图将显示N人和k集群的收入与年龄。 因此,对于我们创建的每个随机质心,我将创建一个正态分布的随机数据集,其收入的标准差为 10,000,年龄的标准差为 2。 这将带给我们一堆年龄收入数据,这些数据被聚集成一些我们可以随机选择的既有聚类。 好的,让我们继续运行它。

现在,实际执行 K 均值,您将看到它很容易。

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()函数说出 5 个群集附近的 100 个随机人。 因此,对于我正在创建的数据,有 5 个自然群集。 然后,我们创建一个模型,即 K 均值模型,其 k 为 5,因此我们选择了 5 个聚类,因为我们知道这是正确的答案。 但是同样,在无监督学习中,您不一定知道k的真正值是什么。 您需要自己迭代并收敛。 然后,我们使用已有的数据,使用我的 K 均值model调用model.fit

现在我提到的规模是对数据进行标准化。 使用 K 均值的重要一件事是,如果所有数据均被标准化,则其效果最佳。 这意味着一切都在相同的规模上。 所以我这里的问题是我的年龄从 20 岁到 70 岁不等,但是我的收入一直到 20 万。 因此,这些值并不是真正可比的。 收入远大于年龄值。 Scale将获取所有数据并将其按比例缩放到一致的比例,这样我就可以将这些东西像苹果与苹果进行实际比较,这将对您的 K 均值结果大有帮助。

因此,一旦在模型上实际调用了fit,我们就可以实际查看得到的结果标签。 然后,我们实际上可以使用一点matplotlib魔法将其可视化。 您可以在代码中看到一个小技巧,将颜色分配给最终转换为浮点数的标签。 这只是一个小技巧,可用于将任意颜色分配给给定值。 因此,让我们看看最终的结果:

没多久。 您会看到结果基本上就是我将所有内容分配到的集群。 我们知道我们的假数据已经被预先聚类了,因此看起来它很容易识别出第一和第二个集群。 但是,在那之后,它有点混乱了,因为中间的集群实际上被融合在一起了。 它们并不是真的那么不同,所以对于 K 均值来说是一个挑战。 但是无论如何,它确实对集群提出了一些合理的猜测。 这可能是四个群集更自然地适合数据的示例。

活动

因此,我希望您为某项活动做的是尝试使用不同的 k 值,然后查看最终结果。 只是盯着前面的图,看起来四个就可以了。 真的吗?如果我增加 k 太大会怎样? 我的结果如何? 它试图将事物分解成什么,甚至有意义? 因此,尝试一下,尝试使用k的不同值。 因此,在n_clusters()函数中,将 5 更改为其他值。 再次运行所有内容,看看最终结果如何。

这就是 K 均值聚类的全部内容。 就这么简单。 您可以只使用 Scikit-learn 的cluster中的KMeans东西。 唯一的难题:请确保您缩放数据,对其进行规范化。 您要确保使用 K 均值的事物彼此可比,并且scale()函数将为您做到这一点。 因此,这些是 K 均值聚类的主要内容。 非常简单的概念,甚至更简单地使用 Scikit-learn 进行操作。

这里的所有都是它的。 这就是 K 均值聚类。 因此,如果您有一堆未分类的数据,并且事先没有真正的正确答案,那么这是一种尝试自然地找到有趣的数据分组的好方法,也许可以让您深入了解该数据是什么。 这是一个很好的工具。 我以前在现实世界中使用过它,而且使用起来并不难,因此请将其保存在您的工具箱中。

测量熵

很快我们将进入机器学习中最酷的部分之一,至少我认为是决策树。 但是在我们谈论这件事之前,有必要了解数据科学中的熵概念。

因此,就像物理学和热力学一样,熵是对数据集无序性(即数据集相同或不同)的一种度量。 因此,假设我们有一个不同类别的数据集,例如动物。 假设我有一堆按物种分类的动物。 现在,如果我数据集中的所有动物都是鬣蜥,那么它们的熵就非常低,因为它们都是一样的。 但是,如果我的数据集中的每只动物都是不同的动物,那么我就有鬣蜥,猪和树懒,并且知道其他什么,那么我的熵就更高了,因为我的数据集中存在更多的混乱。 事情比相同多得多。

熵只是量化我整个数据中相同性或差异性的一种方式。 因此,熵为 0 意味着数据中的所有类都是相同的,而如果一切都不相同,则我的熵将很高,介于两者之间的是介于两者之间的数字。 熵只是描述数据集中事物的相同或不同之处。

现在从数学上讲,它涉及的更多,所以当我实际计算熵的数字时,它是使用以下表达式计算的:

因此,对于我在数据中拥有的每个不同的类,我将拥有以下p个项之一,p[1], p[2],依此类推,直到p[n],适用于我可能拥有的n个不同的类。 p仅代表该类数据的比例。 而且,如果您实际上绘制了每个项pi* ln * pi的外观,则外观将如下图所示:

您为每个单独的类加起来。 例如,如果数据的比例(即,对于给定的类别)为 0,则对总熵的贡献为 0。如果所有内容都是该类别,则对总熵的贡献也为 0,因为在任一情况下,如果什么都是此类,或者什么都不是此类,则对整体熵没有任何贡献。

中间的东西是类的熵,这种分类和其他东西混合在一起。 将所有这些项加在一起时,最终得到整个数据集的整体熵。 因此,从数学上讲,这就是它的工作原理,但是概念又非常简单。 它只是衡量数据集混乱程度,数据中事物的相同或不同程度的一种度量。

决策树-概念

信不信由你,给定一组训练数据,您实际上可以使 Python 生成流程图供您做出决策。 因此,如果您要对某种分类进行预测,则可以使用决策树实际查看可以在流程图的每个级别上确定的多个属性。 您可以根据实际的机器学习打印出实际的流程图,以用于做出决策。 多么酷啊? 让我们看看它是如何工作的。

我个人发现决策树是机器学习中最有趣的应用之一。 决策树基本上为您提供了做出决策的流程图。您有一些因变量,例如我是否应该根据天气去外面玩。 当您做出取决于多个属性或多个变量的决策时,决策树可能是一个不错的选择。

天气的许多不同方面可能会影响我是否应该出去玩的决定。 例如,它可能与湿度,温度有关,而无论晴天与否。 决策树可以查看天气的所有这些不同属性或其他任何属性,并确定阈值是多少? 在决定是否应该在户外玩耍之前,我需要针对这些属性中的每一个做出哪些决定? 这就是一个决策树。 因此,这是一种监督学习的形式。

在此示例中,它的工作方式如下。 我将拥有某种历史天气数据集,以及有关人们是否在特定的日子玩耍的数据。 我将每天是否晴朗,湿度如何以及是否有风的数据提供给模型。 以及是否是在户外玩的好日子。 给定训练数据,决策树算法可以到达一棵树,该树为我们提供了可以打印的流程图。 看起来就像下面的流程图。 您可以根据当前属性,逐步弄清楚是否是在户外玩的好日子。 您可以使用它来预测一组新值的决策:

多么酷啊? 我们有一种算法,可以根据观测数据自动为您制作流程图。 更酷的是,一旦您了解了它的工作原理,一切都变得多么简单。

决策树示例

假设我要构建一个系统,该系统将根据简历中的信息自动过滤掉简历。 科技公司面临的一个大问题是,我们要获得大量的履历。 我们必须决定真正邀请谁参加面试,因为将某人赶出去并实际上抽出时间进行面试可能会很昂贵。 那么,如果有一种方法可以真正获取有关实际录用人员的历史数据并将其映射到简历中找到的信息呢?

我们可以构建一个决策树,让我们浏览一份个人简历,然后说:“好,这个人实际上很有可能被录用或不被录用”。 我们可以根据该历史数据训练决策树,并为将来的候选人逐步进行决策。 那不是一件很棒的事吗?

因此,让我们制作一些在本示例中将要使用的完全捏造的招聘数据:

在上表中,我们有仅由数字标识符标识的候选项。 我将选择一些我认为可能有趣或有助于预测它们是否是高薪的属性。 他们有多少年的经验? 他们目前有工作吗? 在此之前有多少位雇主? 他们的教育水平是多少? 他们有什么学位? 他们去了我们分类为一流学校的学校吗? 他们在大学期间有实习吗? 我们可以看一下历史数据,这里的因变量是Hired。 根据此信息,此人实际上是否获得了工作机会?

现在,显然有很多信息可能不在此模型中非常重要,但是我们从这些数据中训练出的决策树实际上可能在进行初步筛选以剔除某些候选人时很有用。 我们最终得到的可能是一棵看起来像下面的树:

  • 因此,事实证明,在我完全捏造的数据中,任何在大学实习过的人实际上都得到了工作机会。 所以我的第一个决定点是“这个人有没有实习?” 如果是的话,请继续进行。根据我的经验,实习实际上可以很好地预测一个人的好坏。 如果他们有主动去做实习,并在那个实习中实际学到一些东西,那是一个好兆头。
  • 他们目前有工作吗? 好吧,如果他们目前被雇用,在我的一个很小的假数据集中,事实证明他们值得雇用,只是因为其他人认为他们也值得雇用。 显然,这将是现实世界中微妙的决定。
  • 如果他们当前没有工作,那么他们以前的雇主少于一个吗? 如果是,则此人从未从事过工作,也从未实习过。 可能不是一个好的招聘决定。 不要雇用那个人。
  • 但是,如果他们确实有以前的雇主,他们是否至少上过一流学校? 如果不是这样,那就太不稳定了。 如果是这样,那么是的,我们应该根据我们训练的数据聘请此人。

浏览决策树

这就是您浏览决策树结果的方式。 就像遍历流程图一样,算法可以为您产生这种效果真是太棒了。 该算法本身实际上非常简单。 让我解释一下该算法的工作原理。

在决策树流程图的每个步骤中,我们都可以找到可以对数据进行分区的属性,从而使下一步的数据熵最小。 因此,我们得出了一组分类结果:在这种情况下,雇用或不雇用,我们希望在该步骤中选择属性决策,以最大程度地减少下一步的熵。

在每个步骤中,我们都希望做出所有剩余的选择,从而导致尽可能多的不聘用或尽可能多的聘用决策。 我们希望使数据越来越统一,以便按照流程图进行操作,最终最终得到一组候选人,这些候选人要么全部录用,要么全部录用,因此我们可以根据决策树分类是/否决策。 因此,我们只是沿着树走下去,通过选择正确的属性来决定每一步的熵,然后继续进行直到耗尽。

这个算法有个花哨的名字。 它称为 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,我们都有他们过去的工作经验,是否受雇,以前的雇主数量,他们的最高学历,是否上过一流学校以及是否进行过实习。 ; 最后,在“雇用”列中,答案是-我们知道我们是否向该人员提供了工作机会。

像往常一样,大部分工作只是在实际运行算法之前,对数据进行处理,准备数据,这就是我们在这里需要做的。 现在 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,而 Ns 转换为 0。 所以 1 表示是,0 表示否。 我们要做的只是从DataFrame中获取Hired列,然后使用字典在其上调用map()。 这将遍历整个DataFrame中的整个Hired列,并使用该字典查找来转换该列中的所有条目。 它返回一个新的DataFrame列,我将其放回Hired列。 这会将Hired列替换为已映射到 1 和 0 的列。

我们对“就业”,“顶级学校”和“实习生”做同样的事情,因此所有这些人都使用“是/否”字典进行映射。 因此,Y 和 N 分别变为 1 和 0。 对于“教育水平”,我们执行相同的技巧,我们只创建了一个字典,将 BS 分配为 0,MS 分配为 1,PhD 分配为 2,然后使用该字典将这些学位名称重新映射为实际数值。 因此,如果我继续运行并再次执行head(),则可以看到它起作用:

我所有的肯定是 1,我的否定是 0,并且我的教育水平现在由一个具有实际意义的数值表示。

接下来,我们需要准备一切以便实际进入决策树分类器,这并不难。 为此,我们需要将特征信息和目标列分开,这些信息是我们要根据其预测的属性,而目标列则包含了我们要预测的事物。要提取特征名称列的列表 ,我们将创建最多 6 个列的列表。我们继续进行打印。

features = list(df.columns[:6]) 
features 

我们得到以下输出:

上面是包含我们的特征信息的列名称:多年经验,受雇?,前任雇主,受教育程度,顶级学校和实习生。 这些是我们希望预测招聘的候选人的属性。

接下来,我们构造我们的向量,该向量被分配了我们要预测的内容,即Hired列:

y = df["Hired"] 
X = df[features] 
clf = tree.DecisionTreeClassifier() 
clf = clf.fit(X,y) 

此代码提取整个Hired列,并将其命名为y。 然后,它将所有列用于特征数据,并将其放入X中。 这是所有数据和所有特征列的集合,Xy是我们的决策树分类器需要的两件事。

要实际创建分类器本身,需要执行两行代码:调用tree.DecisionTreeClassifier()创建分类器,然后将其拟合到特征数据(X)和答案(y)中-无论人们是否被雇用。 因此,让我们继续进行操作。

显示图形数据有些棘手,我不想在这里过多分散我们的细节,因此请考虑以下样板代码。 您无需了解 GraphViz 在这里的工作方式-点文件以及所有其他内容:对于我们现在的旅程而言,这并不重要。 实际显示决策树的最终结果所需的代码很简单:

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()) 

因此,让我们继续运行它。

这就是您的输出现在应如下所示:

到了! 多么酷啊?! 我们在这里有一个实际的流程图。

现在,让我告诉您如何阅读它。 在每个阶段,我们都有一个决定。 记住我们大多数的数据是01。 因此,第一个决策点变为:是否就业? 小于0.5? 这意味着如果我们的就业值为 0,则为否,我们将向左移动;如果就业为 1,则为是,我们将向右移动。

那么,他们以前有工作吗? 如果不是,请左,如果是,请右。 事实证明,在我的样本数据中,当前受雇的每个人实际上都有工作机会,所以我可以很快地说出如果您目前在受雇,是的,您值得引进,我们这里将紧跟第二层。

那么,您如何解释呢? 基尼分数基本上是在每个步骤中使用的熵的量度。 请记住,当我们崩溃时,算法正在尝试使熵量最小化。 样本是先前决策未划分的剩余样本数。

可以说这个人被雇用了。 读取右侧叶子节点的方法是值列,该列告诉您此时我们有 0 个非聘用的候选人和 5 个非聘用的候选人。 再次,解释第一个决策点的方法是是否雇用? 是 1,我要走到右边,这意味着他们目前正在工作,这使我进入了一个每个人都有工作机会的世界。 所以,这意味着我应该雇用这个人。

现在,让我们说这个人目前没有工作。 接下来要看的是,他们有实习吗? 如果是的话,那么我们就是在每个人的训练数据中找到工作的机会。 因此,在那一点上,我们可以说我们的熵现在为 0(gini=0.0000),因为每个人都是相同的,并且他们都在这一点上得到了报价。 但是,您知道如果我们继续下去(那个人没有做过实习),我们将处于熵值为 0.32 的地步。 它越来越低,这是一件好事。

接下来,我们将看看他们有多少经验,他们是否拥有不到一年的经验? 而且,如果情况是他们确实有一定的经验,并且到目前为止,他们是一个很好的不聘用决定。 我们最终到达熵为零的点,但是我们的训练集中剩下的所有三个样本都没有雇用。 我们有 3 个无雇用和 0 个雇用。 但是,如果他们确实经验不足,那么他们可能刚大学毕业,他们仍然值得一看。

我们要看的最后一件事是他们是否去了一所一流的学校,如果是这样,他们最终将成为一个很好的被录用的预测。 如果没有,他们最终将被录用。 我们最终得到一个属于该类别的应聘者,即不聘用和 0 录用。 鉴于候选人确实上了一所顶级学校,我们有 0 名无雇用人员和 1 名雇用人员。

因此,您可以看到,对于每种情况,只要有可能,我们就一直保持熵达到 0。

集成学习–使用随机森林

现在,假设我们要使用随机森林,您知道,我们担心我们可能过拟合训练数据。 创建包含多个决策树的随机森林分类器实际上非常容易。

因此,为此,我们可以使用之前创建的相同数据。 您只需要Xy向量,这就是要在其上进行预测的特征集和列:

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 年的老兵的就业情况。 而且,果然,我们得到了一个结果:

因此,在这种情况下,我们最终决定了两者的聘用决定。 但是,有趣的是有一个随机成分。 实际上,您每次都不会得到相同的结果! 通常,失业者不会获得工作机会,如果您继续经营下去,通常会发现这种情况。 但是,装袋的随机性,自举聚合这些树的每一棵树,意味着您不会每次都得到相同的结果。 因此,也许十棵树还不够。 因此,无论如何,这是在这里学习的经验!

活动

对于一项活动,如果您想回去玩,请把我的输入数据弄乱。 继续编辑我们一直在探索的代码,并创建一个另类的宇宙,那里是一个混乱的世界。 例如,我现在提供工作的每个人都没有,反之亦然。 看看对您的决策树有什么影响。 只是把它弄乱,看看您能做什么并尝试解释结果。

因此,我认为决策树和随机森林是机器学习中更有趣的部分之一。 我一直认为,凭空生成这样的流程图非常酷。 因此,希望您会发现它有用。

集成学习

当我们谈论随机森林时,这就是集成学习的一个例子,实际上我们是将多个模型组合在一起以得出比任何单个模型都可以得出的更好的结果。 因此,让我们更深入地了解这一点。 让我们来谈谈集成学习。

那么,还记得随机森林吗? 我们有一堆决策树,它们使用输入数据的不同子样本以及将要分支的不同属性集,当您尝试对某些事物进行最后的分类时,它们都会对最终结果进行投票。 那是集成学习的一个例子。 另一个例子:当我们讨论 K 均值聚类时,我们想到了可能使用具有不同初始随机质心的不同 K 均值模型,并让它们都对最终结果进行投票。 这也是集成学习的一个例子。

基本上,您的想法是拥有多个模型,它们可能是同一类型的模型,也可能是不同类型的模型,但是您可以在一组训练数据上运行所有模型,并且无论您要预测什么,它们都对模型最终结果投票。 通常,您会发现,不同模型的组合所产生的结果要比任何单个模型单独产生的结果都要好。

几年前的一个很好的例子是 Netflix 奖。 Netflix 举办了一场竞赛,他们向可以超越现有电影推荐算法的任何研究人员提供一百万美元。 获胜的方法是整体方法,实际上他们可以一次运行多个推荐算法,然后让他们都对最终结果进行投票。 因此,集成学习可以是一种非常强大但简单的工具,可以提高机器学习中最终结果的质量。 现在让我们尝试探索各种类型的集成学习:

  • 自举集成或装袋:现在,随机森林使用一种称为袋装的技术,是自举集成的简称。 这意味着我们将从训练数据中随机抽取一些子样本,并将其输入相同模型的不同版本中,然后让它们全部对最终结果进行投票。 如果您还记得的话,随机森林采用了许多不同的决策树,它们使用训练数据的不同随机样本进行训练,然后它们最终聚集在一起,对最终结果进行投票。 装袋。
  • 提升: 提升是一个替代模型,这里的想法是从一个模型开始,但是每个后续模型都会增强处理那些被先前模型错误分类的区域的属性。 因此,您在模型上进行训练/测试,找出根本上出错的属性,然后在后续模型中增强这些属性-希望那些后续模型会更多地关注它们并正确得到它们。 因此,这就是推动发展的总体思路。 您可以运行一个模型,找出其薄弱环节,并在不断发展时将重点放在这些薄弱环节上,并基于前一个薄弱环节,继续构建越来越多的模型以不断完善该模型。
  • 模型桶: Netflix 获奖者所做的另一项技术称为模型桶,您可能会使用完全不同的模型来预测某些事物。 也许我正在使用 K 均值,决策树和回归。 我可以在一组训练数据上同时运行所有这三个模型,并在我试图预测某些结果时让它们全部对最终分类结果进行投票。 也许这比孤立使用其中任何一个模型要好。
  • 堆叠:堆叠具有相同的想法。 因此,您可以对数据运行多个模型,并以某种方式将结果组合在一起。 在存储桶和堆叠模型之间的细微区别是,您选择了获胜的模型。 因此,您将进行训练/测试,找到最适合您的数据的模型,然后使用该模型。 相比之下,堆叠会将所有这些模型的结果组合在一起,以得出最终结果。

现在,有一个关于集成学习的整个研究领域,试图找到进行集成学习的最佳方法,如果您想听起来很聪明,通常需要大量使用贝叶斯一词。 因此,有一些非常高级的集成学习方法,但是它们都有缺点,我认为这是又一课,因为我们应该始终使用最适合自己的简单技术。

现在,所有这些都是非常复杂的技术,我在本书的范围内无法真正涉及到,但是总而言之,很难仅胜过我们已经讨论过的简单技术。 这里列出了一些复杂的技术:

  • 贝叶斯光学分类器:从理论上讲,有一种称为贝叶斯最佳分类器的东西总是最好的,但是这是不切实际的,因为这样做在计算上是令人望而却步的。
  • 贝叶斯参数平均:许多人试图对贝叶斯最佳分类器进行变型以使其更实用,例如贝叶斯参数平均变型。 但是它仍然容易过拟合,并且经常由于装袋而表现不佳,这与随机森林的想法相同。 您只需多次对数据进行重新采样,运行不同的模型,然后让它们全部对最终结果进行投票。 事实证明,该方法同样有效,而且简单得多!
  • 贝叶斯模型组合:最后,有一种称为贝叶斯模型组合的东西试图解决贝叶斯最佳分类器和贝叶斯参数平均的所有缺点。 但是,归根结底,它并没有比对模型组合进行交叉验证做得更好。

同样,这些都是非常复杂的技术,很难使用。 在实践中,最好使用我们已详细讨论过的简单方法。 但是,如果您想听起来很聪明,并且经常使用贝叶斯一词,那么至少要熟悉这些技术并知道它们是什么是很好的。

因此,这就是集成学习。 再次强调,简单的技术通常是正确的选择,例如引导聚合,装袋,增强,堆叠或模型桶。 那里有许多更先进的技术,但它们在很大程度上是理论上的。 但是,至少您现在了解它们。

尝试集成学习总是一个好主意。 一次又一次地证明,它会比任何单个模型产生更好的结果,因此请务必考虑一下!

支持向量机概述

最后,我们将讨论支持向量机SVM),这是对高维数据进行聚类或分类的一种非常先进的方法。

那么,如果您要预测多种特征怎么办? SVM 可以是执行此操作的非常强大的工具,其结果可能非常好! 它的内幕非常复杂,但是重要的是要了解何时使用它以及它在更高层次上如何工作。 因此,让我们现在介绍 SVM。

支持向量机是一个花哨的名字,实际上是一个花哨的概念。 但幸运的是,它非常易于使用。 重要的是要知道它的作用以及它的优点。 因此,支持向量机很好地用于对高维数据进行分类,这意味着很多不同的特征。 因此,使用 K 均值聚类之类的数据来对具有二维的数据进行聚类很容易,例如,一个轴上的年龄,而另一个轴上的收入。 但是,如果我要尝试预测许多不同的特征,该怎么办? 好吧,支持向量机可能是这样做的好方法。

支持向量机找到可以在其上划分数据的高维支持向量(从数学上讲,这些支持向量定义了超平面)。 也就是说,在数学上,支持向量机可以做的是找到更高维的支持向量(这就是它的名字),它定义了将数据分成不同群集的更高维平面。

显然,这一切使数学很快变得很奇怪。 幸运的是,scikit-learn包将为您完成所有操作,而无需您真正参与其中。 在幕后,您需要了解,尽管它使用了一种称为核技巧的东西来实际上找到那些在较低维度上可能不明显的支持向量或超平面。 您可以使用不同的核,以不同的方式执行此操作。 要点是,如果您拥有具有许多不同特征的高维数据,并且可以使用具有不同计算成本并且可以更好地解决当前问题的不同核,则 SVM 是一个不错的选择。

重要的一点是,SVM 使用一些先进的数学技巧对数据进行聚类,并且可以处理具有许多特征的数据集。 它也相当昂贵-“核技巧”是唯一使之成为可能的事情。

我想指出的是,SVM 是一种监督学习技术。 因此,我们实际上将在一组训练数据上对其进行训练,并且我们可以使用它来对未来看不见的数据或测试数据进行预测。 它与 K 均值聚类有点不同,并且 K 均值完全不受监督。 相比之下,使用支持向量机,它是根据实际训练数据进行训练的,其中您可以对可以学习的某些数据集进行正确分类的答案。 因此,如果您愿意,SVM 对于分类和群集很有用-但这是一种监督技术!

您经常在 SVM 中看到的一个示例是使用一种称为支持向量分类的方法。 典型示例使用鸢尾花数据集,该数据集是 Scikit-learn 附带的示例数据集之一。 这组是对不同花朵的分类,对不同鸢尾花及其物种的不同观察。 想法是使用有关每朵花的花瓣的长度和宽度以及每朵花的萼片的长度和宽度的信息对这些信息进行分类。 (显然,萼片是花瓣下面的一个支撑结构。到目前为止,我也不知道。)那里有四个维度的属性;每个维度都有一个维度。 你有花瓣的长度和宽度,以及萼片的长度和宽度。 给定该信息,您可以使用它来预测鸢尾花的种类。

以下是使用 SVC 进行此操作的示例:基本上,我们将萼片宽度和萼片长度投影到了二维,因此我们可以对其进行可视化显示:

使用不同的核,您可能会得到不同的结果。 如上图所示,带有线性核的 SVC 会产生很多东西。 您可以使用多项式核或更高级的核,它们可能会向下投影到二维曲线,如图所示。 您可以通过这种方式进行一些漂亮的分类。

这些具有增加的计算成本,并且它们可以产生更复杂的关系。 但同样,在这种情况下,过多的复杂性可能会产生误导性的结果,因此您需要小心并在适当的时候实际使用训练/测试。 由于我们正在进行监督学习,因此您实际上可以进行训练/测试并找到合适的模型,或者使用整体方法。

您需要为手头的任务找到合适的核。 对于多项式 SVC 之类的东西,使用什么度数多项式是正确的? 甚至线性 SVC 之类的东西也会具有与之相关的不同参数,您可能需要对其进行优化。 对于一个真实的示例,这将更有意义,所以让我们深入研究一些实际的 Python 代码,并查看其工作原理!

通过 Scikit-learn 使用 SVM 将人员聚类

让我们在这里尝试一些支持向量机。 幸运的是,使用起来比理解起来容易得多。 我们将回到用于 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()函数创建具有 5 个不同群集的 100 个随机人员。 我们将创建一个散点图来说明它们,并查看它们的降落位置:

%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 的一种形式)将其实际划分为群集。 我们将使用线性核且 C 值为1.0的 SVM。 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 实际上是一种非常强大的技术。 它的真正优势在于高维特征数据。 继续玩吧。 顺便说一句,如果您不仅要可视化结果,还可以在 SVC 模型上使用predict()函数,就像在 Scikit-learn 中的几乎所有模型上一样,可以传入您感兴趣的特征数组。 如果我要预测 40 岁的年收入 200,000 美元的人的分类,我将使用以下代码:

svc.predict([[200000, 40]])

在我们的例子中,这将使该人进入集群 1:

如果我有一个 65 岁的人赚了 50,000 美元,我将使用以下代码:

svc.predict([[50000, 65]])

这就是您的输出现在应如下所示:

该人最终将出现在集群编号 2 中,无论在此示例中代表什么。 所以,继续玩吧。

活动

现在,线性只是您可以使用的许多核之一,就像我说的那样,您可以使用许多不同的核。 其中之一是多项式模型,因此您可能想尝试一下。 请继续并查找文档。 查看文档是对您的好习惯。 如果您打算以任何深度使用 Scikit-learn,则可以使用许多不同的功能和选项。 因此,请在线查找 Scikit-learn,找出 SVC 方法的其他核,然后尝试一下,看看您是否获得了更好的结果。

这是一个小练习,不仅是与 SVM 和各种 SVC 一起玩,还在于使您熟悉如何自己学习有关 SVC 的更多信息。 老实说,对于任何数据科学家或工程师来说,一个非常重要的特征就是能够在不知道答案的情况下自行查找信息。

所以,您知道,我不会偷懒不告诉您其他核是什么,我希望您习惯于必须自己查找这些内容的想法,因为如果您必须总是询问其他人这些事情,您在工作场所会非常快变得非常烦人。 因此,去查找它,玩转它,看看你会想到什么。

因此,这就是 SVM/SVC,这是一种非常强大的技术,可用于监督学习中的数据分类。 现在您知道了它的工作原理和使用方法,因此请把它保存在技巧包中!

总结

在本章中,我们看到了一些有趣的机器学习技术。 我们介绍了机器学习背后的基本概念之一,即训练/测试。 我们看到了如何使用训练/测试来尝试找到适合给定数据集的正确次数的多项式。 然后,我们分析了有监督和无监督机器学习之间的区别。

我们看到了如何实现垃圾邮件分类器,以及如何使用朴素贝叶斯技术使它能够确定电子邮件是否为垃圾邮件。 我们讨论了 K 均值聚类,这是一种无监督的学习技术,可帮助将数据分组到聚类中。 我们还看了一个使用 Scikit-learn 的示例,该示例根据收入和年龄对人们进行聚类。

然后,我们继续研究熵的概念以及如何对其进行度量。 我们介绍了决策树的概念,并给出了一组训练数据,如何实际使用 Python 生成流程图以供您实际决策。 我们还建立了一个系统,该系统可根据简历中的信息自动过滤掉简历,并预测人员的聘用决定。

我们沿途学习了集成学习的概念,最后以支持向量机为例进行了总结,这是对高维数据进行聚类或分类的一种非常先进的方法。 然后,我们继续使用 SVM 通过 Scikit-learn 将人们聚集在一起。 在下一章中,我们将讨论推荐系统。

六、推荐系统

让我们谈谈我个人的专业领域-推荐系统,这样的系统可以根据其他人的建议向人们推荐东西。 我们将看一些这样的示例以及几种实现方法。 具体来说,两种技术称为基于用户的协同过滤和基于项目的协同过滤。 所以,让我们深入。

我的职业生涯大部分时间都在这个页面imdb.com度过,在那儿我做的很多事情都是开发推荐系统。 像购买此商品的人也买了为您推荐的东西,以及为人们推荐电影的东西。 因此,这是我个人非常了解的一些事情,希望与您分享一些知识。 我们将逐步介绍以下主题:

  • 什么是推荐系统?
  • 基于用户的协同过滤
  • 基于项目的协同过滤
  • 寻找电影的相似性
  • 向人们推荐电影
  • 改善推荐结果

什么是推荐系统?

好吧,就像我说的那样,亚马逊就是一个很好的例子,我非常熟悉。 因此,如果您转到他们的推荐部分,如下图所示,您将看到它会根据您过去在网站上的行为推荐您可能有兴趣购买的东西。

推荐系统可能包括您已评分或购买的内容以及其他数据。 我无法透露细节,因为它们会追捕我,而且您知道,对我做坏事。 但是,这很酷。 您还可以想到购买了该商品的人,同时也在亚马逊上购买了功能作为推荐系统。

所不同的是,您在亚马逊推荐页面上看到的推荐是基于您过去的所有行为,而购买此商品的人也购买了也查看了,诸如此类的事情,只是基于您当前正在查看的内容,并向您显示您可能也感兴趣的类似内容。事实证明,您现在正在做的事情,无论如何,可能是您的兴趣的信号。

另一个示例来自 Netflix,如下图所示(下图是 Netflix 的屏幕截图):

它们具有多种功能,可以根据您过去喜欢或观看的电影来推荐新电影或其他您尚未看过的电影,并且按类型将其分解。 他们对事物有不同的看法,他们尝试确定自己认为您最喜欢的类型或电影类型,然后向您展示这些类型的更多结果。 因此,这是推荐系统正在运行的另一个示例。

这样做的全部目的是帮助您发现以前可能不知道的事情,因此非常酷。 您知道,它使单个电影,书籍,音乐或其他东西有机会被以前从未听说过的人发现。 因此,您知道,这不仅是一项很酷的技术,而且还可以有点公平地竞争环境,并帮助大众发现新产品。 因此,它在当今社会中起着非常重要的作用,至少我想这样! 这样做的方法很少,我们将在本章中介绍主要方法。

基于用户的协同过滤

首先,让我们谈谈根据您过去的行为推荐事物。 一种技术称为基于用户的协同过滤,其工作方式如下:

顺便说一句,协同过滤只是一个奇特的名字,用于根据您所做的事情和其他所有人所做的事情对推荐内容进行推荐,好吗? 因此,它正在查看您的行为并将其与其他人的行为进行比较,以得出您尚未听说过的可能对您来说有趣的事情。

  1. 这里的想法是,我们建立一个矩阵,该矩阵包含每个用户曾经购买,查看,评价的所有事物,或者您希望基于该系统的任何感兴趣的信号。 因此,基本上,我们为系统中的每个用户最后一行,该行包含他们所做的所有操作,这些操作可能表明对特定产品有某种兴趣。 所以,想象一个表格,我有行的用户,每一列都是一个项目,好吗? 那可能是电影,产品,网页等等。 您可以将其用于许多不同的事物。
  2. 然后,我使用该矩阵来计算不同用户之间的相似度。 因此,我基本上将其每一行都视为一个向量,并且可以基于用户的行为来计算用户的每个向量之间的相似度。
  3. 两个最喜欢相同事物的用户会彼此非常相似,然后我可以根据这些相似性得分对它们进行排序。 如果我可以根据他们过去的行为找到所有与您相似的用户,则可以找到与我最相似的用户,并推荐他们喜欢我从未看过的东西。

让我们看一个真实的例子,它会更有意义:

假设上图中的这位漂亮女士看过《星球大战》和《帝国反击战》,她爱他们俩。 因此,我们有一位这位女士的用户向量,将《星球大战》和《帝国反击战》评为 5 星。

我们也说 Edgy Mohawk Man 先生来了,他只看了《星球大战》。 那是他唯一见过的东西,他不知道《帝国反击战》,但不知何故,他生活在一个陌生的宇宙中,他不知道实际上有很多很多《星球大战》电影,实际上每年都在增长。

我们当然可以说,这家伙实际上和另一位女士很相似,因为他们俩都非常喜欢《星球大战》,所以他们的相似度得分可能还不错,我们可以说,好吧,什么东西是这位女士喜欢但是没看过的? 而且,《帝国反击战》是一部,因此我们可以根据他们对《星际大战》的喜爱程度,得出这两个用户相似的信息,发现这位女士也喜欢《帝国反击战》,然后提出该建议作为对 Edgy Mohawk Man 先生。

然后,我们可以继续向他推荐《帝国反击战》,他可能会喜欢它,因为在我看来,这实际上是一部更好的电影! 但我不会在这里与您展开极客战争。

基于用户的协同过滤的局限性

现在,不幸的是,基于用户的协同过滤具有一些局限性。 当我们考虑人际关系并根据项目与人之间的关系以及诸如此类的东西推荐事物时,我们的想法倾向于与人之间的关系发展。 因此,我们希望找到与您相似的人,并推荐他们喜欢的东西。 这是一种直觉的事情,但这不是最好的事情! 以下是基于用户的协作筛选的一些限制列表:

  • 一个问题是人们善变。 他们的口味总是在变化。 因此,也许上一个例子中的那个好女人经历了一段简短的科幻动作片阶段,然后她克服了这个阶段,也许在她生命的后期,她开始更多地涉足戏剧,浪漫电影或 romcoms。 那么,如果我的 Edgy Mohawk 小伙子根据她早期的科幻小说而最终与她具有高度相似性,并且最终向他推荐浪漫喜剧片,那该怎么办? 那将是不好的。 我的意思是,在计算相似度分数时有一定的针对性,但这仍然会污染我们的数据,人们的口味会随着时间变化。 因此,将人与人进行比较并不总是一件容易的事,因为人会改变。
  • 另一个问题是,系统中的人数通常比系统中的人数要多。因此,全世界有 70 亿,而且还在继续增长,全世界可能没有 70 亿部电影,或者菜单中的 70 亿个商品,你可能要推荐它们。 查找系统中所有用户之间的所有相似性的计算问题可能比查找系统中项目之间的相似性的问题要大得多。 因此,通过将系统聚焦于用户,您将使计算问题变得比原本要困难得多,因为您有很多用户,至少希望您为一家成功的公司工作。
  • 最终的问题是人们做坏事。 有一种非常真实的经济动机来确保您的产品或电影或任何推荐的产品被推荐给人们,并且有些人试图通过游戏系统来实现他们的新电影,新产品或新产品的实现。 新书或其他内容。

通过创建新用户并让他们进行一系列喜欢很多受欢迎的物品然后又喜欢您的物品的事件,在系统中制造假角色很容易。 这就是所谓的先验攻击,我们理想情况下希望拥有一个可以应对这种情况的系统。

关于如何在基于用户的协同过滤中检测和避免这些先发攻击的研究很多,但是一个更好的方法是使用完全不同的方法,这种方法不太容易受到系统游戏的影响。

那是基于用户的协同过滤。 再次,这是一个简单的概念-您根据用户的行为查看他们之间的相似性,并推荐用户喜欢的东西,这些东西与您相似,而您尚未看到。 现在,确实有我们所讨论的局限性。 因此,让我们谈谈使用基于项目的协同过滤技术将整个事情颠倒过来的事情。

基于项目的协同过滤

现在,让我们尝试使用一种基于项目的协同过滤技术来解决基于用户的协同过滤中的一些缺点,我们将看到这种方法如何更强大。 它实际上是亚马逊内部使用的技术之一,他们已经公开讨论过这一点,所以我可以告诉你很多,但是让我们看看为什么这是一个好主意。 通过基于用户的协同过滤,我们的建议基于人与人之间的关系,但是如果我们将其翻转并基于项目之间的关系又会如何呢? 这就是基于项目的协同过滤。

了解基于项目的协同过滤

这将借鉴一些见解。 一方面,我们谈到了人们善变,他们的品味会随着时间而变化,因此根据他们过去的行为将一个人与另一个人进行比较变得非常复杂。 人们具有不同的阶段,他们具有不同的兴趣,您可能没有将处于同一阶段的人们彼此进行比较。 但是,一个项目将永远是任何东西。 电影永远是电影,永远不会改变。 《星球大战》将永远是《星球大战》,直到乔治·卢卡斯(George Lucas)稍加修改,但在大多数情况下,物品的变化不会像人们那样大。 因此,我们知道这些关系是更永久的,并且在计算项目之间的相似性时可以进行更多直接比较,因为它们不会随时间变化。

另一个优点是,您要推荐的东西通常比推荐的人少。 再说一遍,世界上有 70 亿人,您可能没有在网站上提供 70 亿东西推荐给他们,因此您可以通过评估项目之间的关系而不是用户来节省大量计算资源,因为您可能拥有的商品数量少于系统中用户的数量。 这意味着您可以更频繁地运行您的建议,使它们更加最新,更新和更好! 您可以使用更复杂的算法,因为您需要计算的关系较少,这是一件好事!

游戏系统也比较困难。 因此,我们讨论了通过创建一些假用户(这些用户喜欢一堆受欢迎的东西,然后创建您要推广的东西)来玩基于用户的协同过滤方法有多么容易。 使用基于项目的协同过滤将变得更加困难。 您必须通过游戏系统来考虑物品之间是否存在关联,并且由于您可能没有能力基于许多其他用户来创建与其他物品具有虚假联系的假物品,因此,游戏一个基于物品的协作的过滤系统要困难得多,这是一件好事。

当我谈论游戏系统时,另一重要的事情是确保人们用他们的钱投票。 避免先令攻击或试图玩推荐系统的人的通用技术是确保信号行为基于实际花钱的人。 因此,当您根据人们实际购买的商品(而不是他们所观看的内容或点击的内容)提出建议时,您总是会获得更好,更可靠的结果,好吗?

基于项目的协同过滤如何工作?

好了,让我们谈谈基于项目的协同过滤是如何工作的。 这与基于用户的协同过滤非常相似,但是我们正在查看项目而不是用户。

因此,让我们回到电影推荐的示例。 我们要做的第一件事是找到同一个人观看的每对电影。 因此,我们遍历并找到了同一个人观看过的每部电影,然后我们衡量了观看该电影的所有其他人之间的相似性。 因此,通过这种方式,我们可以根据观看这两部电影的观众的收视率来计算两部电影之间的相似度。

所以,假设我有一对电影,好吗? 也许是《星球大战》和《帝国反击》。 我找到了所有看过这两部电影的人的名单,然后将它们的收视率进行了比较,如果它们相似,那么我可以说这两部电影是相似的,因为看过这两部电影的人对它们的评分相似 。 这是这里的总体思路。 那是做到这一点的一种方式,而不仅仅是做到这一点的方式!

然后,我可以按影片对所有内容进行排序,然后按与所有相似影片的相似度进行排序,结果是喜欢该影片的人也喜欢高度评价等等,依此类推。 就像我说的那样,这只是其中一种方式。

这是基于项目的协作筛选的第一步,首先,我根据观看每对给定电影对的人的关系来确定电影之间的关系。 当我们看下面的例子时,它将更有意义:

例如,假设上图中的一位漂亮的年轻女士看了《星球大战》和《帝国反击战》,并且都喜欢它们,因此将它们评为五颗星或类似星光。 现在,出现了 Edgy Mohawk Man 先生,他还观看了《星球大战》和《帝国反击战》,也都喜欢了它们。 因此,在这一点上,我们可以说存在某种关系,根据这两个都喜欢这部电影的用户,《星球大战》和《帝国反击战》之间存在相似之处。

我们要做的是看每对电影。 我们有一对《星球大战》和《帝国反击战》,然后我们看了看过这两个家伙的所有用户,这两个家伙,如果他们俩都喜欢,那么我们可以说他们与每个其他的人都相似。 或者,如果他们俩都不喜欢他们,我们也可以说他们彼此相似,对吗? 因此,我们仅查看这对电影中这两个电影相关的两个用户行为的相似性得分。

因此,随之而来的是 Moustachy Lumberjack Hipster Man 先生,他观看《帝国反击战》,他生活在一个陌生的世界里,在那里他观看了《帝国反击战》,但不知道《星球大战》的第一部电影存在。

很好,我们根据这两个人的行为来计算《帝国反击战》和《星球大战》之间的关系,因此我们知道这两部电影彼此相似。 因此,考虑到 Hipster Man 先生喜欢《帝国反击战》,我们可以很有信心地说他也喜欢《星球大战》,然后我们可以推荐给他作为他的顶级电影推荐。 如下图所示:

您可以看到最终得到的结果非常相似,但是我们已经将整个事情抛在脑后了。 因此,我们将系统重点放在项目之间的关系上,而不是将系统重点放在人与人之间的关系上,这些关系仍然基于所有监视它们的人员的汇总行为。 但从根本上讲,我们正在研究项目之间的关系,而不是人与人之间的关系。 知道了?

使用 Python 的协同过滤

好吧,让我们开始吧! 我们有一些将使用 Pandas 的 Python 代码以及我们可以使用的所有其他工具来创建电影推荐,而这些代码的数量却很少。

我们要做的第一件事是向您展示基于项目的协同过滤。 因此,我们将建立观看者同时基本上也观看,您知道,对它评价很高的人对它评价也很高,因此建立了电影与电影的关系。 因此,我们将基于从 MovieLens 项目获得的真实数据。 因此,如果您访问 MovieLens.org,实际上那里有一个开放的电影推荐系统,人们可以在其中对电影进行评分并获得有关新电影的推荐。

而且,它们将所有基础数​​据公开提供给像我们这样的研究人员。 因此,我们将使用一些真实的电影收视率数据-有点过时了,大约有 10 年的历史了,因此请记住这一点,但最终要在这里使用的是真实的行为数据 。 并且,我们将使用它来计算电影之间的相似度。 而且,这些数据本身很有用。 您可以使用该数据说喜欢它的人也喜欢。 因此,假设我正在看电影的网页。 然后,系统会说:如果您喜欢这部电影,并且考虑到正在观看它,则可能对它感兴趣,那么您可能也喜欢这些电影。 这就是推荐系统的一种形式,尽管我们甚至都不知道您是谁。

现在,它是真实数据,因此我们将遇到一些真实问题。 我们最初的结果看起来不会很好,所以我们将花费一些额外的时间来找出原因,这是您花费大量时间作为数据科学家纠正问题的原因,然后返回并再次运行它,直到获得有意义的结果。

最后,我们实际上将整体上进行基于项目的协同过滤,实际上,我们会根据个人的行为向他们推荐电影。 所以,让我们开始吧!

寻找电影相似之处

让我们应用基于项目的协同过滤的概念。 首先,电影相似性-找出哪些电影与其他电影相似。 特别是,我们将根据用户评分数据来尝试找出与《星球大战》相似的电影,然后我们会从中得到什么。 让我们潜入吧!

好的,让我们继续计算基于项目的协同过滤的前半部分,这将发现项目之间的相似性。 下载并打开SimilarMovies.ipynb文件。

在这种情况下,我们将基于用户行为来研究电影之间的相似性。 并且,我们将使用 GroupLens 项目中的一些真实电影收视率数据。 GroupLens.org 由使用 MovieLens.org 网站对电影进行评分的真实人提供真实的电影收视率数据,并向他们推荐要观看的新电影。

我们已经将您需要的 GroupLens 数据集中的数据文件包含在课程资料中,我们要做的第一件事是将它们导入 Pandas DataFrame,在此示例中,我们将真正看到 Pandas 的全部功能 。 这是很酷的东西!

了解代码

我们要做的第一件事是将u.data文件作为 MovieLens 数据集的一部分导入,这是一个制表符分隔的文件,其中包含数据集中的每个评分。

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_idmovie_idrating

我们最终得到的是一个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_idtitle。 因此,现在我们有两个数据帧:r_cols具有所有用户评分,m_cols具有每个movie_id的所有标题。 然后,我们可以在 Pandas 中使用神奇的merge函数将其融合在一起。

ratings = pd.merge(movies, ratings) 

让我们添加一个ratings.head()命令,然后运行那些单元格。 我们最终得到的是类似下表的内容。 那太快了!

我们最终得到一个新的DataFrame,其中包含用户评分的每部电影的user_id和评分,并且我们可以读取movie_idtitle并查看其真实含义。 因此,读取方式为user_id编号为Toy Story (1995)电影4星级的user_id编号user_id编号为Toy Story (1995)电影5星级的287等,依此类推 。 而且,如果我们要继续关注这个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 Dalmatians (1996)并将它评为2星。 user_id数字1也观看了12 Angry Men (1957)并将其评为5星,但没有观看电影2 Days in the Valley (1996),例如,好吗? 因此,我们最终得到的基本上是一个稀疏矩阵,其中包含每个用户和每个电影,并且在用户对电影进行评分的每个路口处都有一个评分值。

因此,现在您可以看到,我们可以很容易地提取用户观看的每部电影的向量,我们还可以提取对给定电影评分的每个用户的向量,这正是我们想要的。 因此,这对于基于用户和基于项目的协同过滤都很有用,对吗? 如果要查找用户之间的关系,可以查看这些用户行之间的相关性,但是如果要查找电影之间的相关性,则对于基于项目的协同过滤,可以根据用户行为查看列之间的相关性。 因此,这就是真正的反转用户与项目之间的相似性的地方。

现在,我们要进行基于项目的协同过滤,因此我们想提取列,为此,我们运行以下代码:

starWarsRatings = movieRatings['Star Wars (1977)'] 
starWarsRatings.head() 

现在,借助于此,让我们继续提取所有对Star Wars (1977)评分的用户:

而且,至少在我们从DataFrame负责人那里获得的这个小样本中,我们可以看到实际上大多数人都在观看和评级Star Wars (1977),并且每个人都喜欢它。 因此,我们最终得到了一组用户 ID 及其Star Wars (1977)的等级。 用户 ID 3未对Star Wars (1977)进行评分,因此我们有一个NaN值,表示此处缺少一个值,但这没关系。 我们要确保保留那些丢失的值,以便我们可以直接比较不同电影中的列。 那么,我们该怎么做呢?

corrwith函数

好吧,Pandas 一直在简化我们的工作,并且具有corrwith函数,您可以在以下我们可以使用的代码中看到该函数:

similarMovies = movieRatings.corrwith(starWarsRatings) 
similarMovies = similarMovies.dropna() 
df = pd.DataFrame(similarMovies) 
df.head(10) 

该代码将继续进行操作,并将给定的列与DataFrame中的所有其他列相关联,并计算相关性得分并将其返回给我们。 因此,我们在这里所做的是在整个movieRatings数据帧上使用corrwith,即用户电影评分的整个矩阵,仅将其与starWarsRatings列相关联,然后将所有缺失的结果删除为dropna。 因此,剩下的就是具有相关性的项目,有多个人查看了该项目,然后根据这些结果创建一个新的DataFrame,然后显示前 10 个结果。 再次重申一下:

  1. 我们将建立《星球大战》与其他每部电影之间的相关性得分。
  2. 删除所有NaN值,以便我们仅拥有实际存在的电影相似之处,并且有多个人对此进行评分。
  3. 并且,我们将根据结果构建一个新的DataFrame,并查看前 10 个结果。

结果如下图所示:

我们最终得出了《星球大战》每部电影之间的相关性得分结果,例如,我们发现与电影'Til There Was You (1997)101 Dalmatians (1996)的相关性高得令人惊讶,与电影1-900 (1994)的负相关性却非常弱。

现在,我们所要做的就是按照相似性得分对它进行排序,并且我们应该拥有《星球大战》中电影的顶级相似性,对吗? 让我们继续做。

similarMovies.sort_values(ascending=False) 

只需在生成的DataFrame上调用sort_values,Pandas 再次使它变得非常容易,并且我们可以说ascending=False,实际上是按相关度得分将其反向排序。 因此,让我们这样做:

好的,所以Star Wars (1977)的排名非常接近顶部,因为它与自身相似,但是这些其他东西又是什么呢? 有没有搞错? 我们可以在前面的输出中看到一些电影,例如:Full Speed (1996)Man of the Year (1995)The Outlaw (1943)。 您所知道的这些都是相当晦涩的电影,其中大多数我从未听说过,但它们与《星球大战》有着完美的关联。 有点奇怪! 因此,显然我们在这里做错了。 会是什么呢?

好吧,事实证明,这里有一个非常合理的解释,这是一个很好的课程,它说明了为什么在完成任何种类的数据科学任务后,您总是需要检查结果,对结果提出质疑,因为经常会遗漏一些东西, 您可能需要清除数据中的某些内容,或者您​​做错了某些事情。 但是,您也应该始终对您的结果持怀疑态度,不要仅仅相信它们,好吗? 如果这样做,您将遇到麻烦,因为如果我实际上将这些建议作为推荐给喜欢《星球大战》的人,我将被解雇。 不要被解雇! 注意您的结果! 因此,让我们在下一节中深入研究出了什么问题。

改善电影相似度的结果

让我们找出那里的电影相似之处出了什么问题。 我们经历了所有这些激动人心的工作,根据电影的用户评级向量计算电影之间的相关性得分,结果令人有些吃惊。 因此,提醒您,我们使用该技术寻找了与《星球大战》相似的电影,最后我们在顶部提出了一堆奇怪的建议,它们之间有着完美的关联。

而且,其中大多数都是非常晦涩的电影。 那么,您认为那里可能会发生什么? 好吧,也许有意义的一件事是,我们有很多人在看《星球大战》和其他一些不起眼的电影。 由于这两部电影是由《星球大战》捆绑在一起的,所以我们最终会获得很好的相关性,但是到最后,我们是否真的要根据一个或两个观看某些电影的人的行为提出建议? 晦涩的电影?

可能不是! 我的意思是,世界上两个人,或者说是什么人,都在看电影《 Full Speed》,除了《星球大战》之外,他们俩都喜欢它,也许这对他们来说是一个不错的建议,但是对于剩下的世界来说,这可能不是一个好的建议。 我们需要通过限制观看某部电影的人数的最低界限来对我们的相似性具有某种置信度。 我们不能仅仅根据一个或两个人的行为来判断一部给定的电影是好的。

因此,让我们尝试使用以下代码将该见解付诸实践:

import numpy as np 
movieStats = ratings.groupby('title').agg({'rating': [np.size, np.mean]}) 
movieStats.head() 

我们要做的是尝试找出实际上没有被很多人评价的电影,然后我们将它们扔出去看看我们能得到什么。 因此,要做到这一点,我们将采用原始评分DataFrame,并说groupby('title'),Pandas 再次具有各种魔力。 并且,这基本上将构造一个新的DataFrame,它将给定标题的所有行汇总在一起。

可以说,我们要专门对收视率进行汇总,并且要显示每部电影的大小,收视率数量,以及平均平均得分,该电影的平均收视率。 因此,当我们这样做时,最终会得到以下内容:

例如,这告诉我们,对于电影101 Dalmatians (1996)来说,109人们对该电影进行了评分,他们的平均评分为 2.9 星,因此得分并不是很高! 因此,如果我们只看一下这些数据,就可以很好地说,我认为晦涩难懂的电影,例如187 (1997),具有41等级,但是101 Dalmatians (1996),我听说过,你知道12 Angry Men (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 人评分的电影列表,按其平均评分得分排序,这本身就是一个推荐系统。 这些是好评率很高的流行电影。 显然A Close Shave (1995)是一部非常不错的电影,很多人都观看了,他们真的很喜欢它。

再说一次,这是一个非常古老的数据集,始于 90 年代后期,因此即使您可能不熟悉影片A Close Shave (1995),也值得回顾一下并重新发现它; 将其添加到您的 Netflix! Schindler's List (1993)并不是什么大惊喜,它出现在大多数顶级电影列表的顶部。 The Wrong Trousers (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 个结果。 如果现在运行该程序,应该看到以下内容:

这看起来开始好一点了! 因此,Star Wars (1977)排在首位,因为它与自身相似,The Empire Strikes Back (1980)是 2 号,Return of the Jedi (1983)是 3 号,Raiders of the Lost Ark (1981)也是 4 号。您知道,它仍然不完美,但是它更有意义,对吧? 因此,您可以期待原始三部曲中的三部《星球大战》电影彼此相似,这些数据可以追溯到下三部电影之前,而且Raiders of the Lost Ark (1981)在风格上也与《星球大战》非常相似,而且排在第 4 位。因此,我开始对这些结果感到好一些。 仍有改进的空间,但是,嘿! 我们得到了一些有意义的结果,哇!

现在,理想情况下,我们还将筛选出《星球大战》,您不想看到与您开始的电影本身的相似之处,但我们稍后会担心! 因此,如果您想更多地玩这个游戏,就像我说的 100 就是最低评分数的任意截止值。 如果您确实想尝试不同的临界值,建议您回去做。 看看对结果有什么影响。 您知道,您可以在上表中看到我们真正喜欢的结果实际上有 100 多个共同点。 因此,最终Austin Powers: International Man of Mystery (1997)仅以130评分进入了很高的排名,因此 100 可能还不够高! Pinocchio (1940)位于101,与《星球大战》不太相似,因此,您可能要考虑一个更高的阈值并查看其作用。

也请记住,这是一个很小的,有限的数据集,我们用于实验目的,它基于非常古老的数据,因此您只会看较老的电影。 因此,直观地解释这些结果可能会带来一些挑战,但结果并不差。

现在,让我们继续进行实际的基于项目的协作式过滤,在此我们向使用更完整系统的人推荐电影,接下来我们将进行介绍。

向人们推荐电影

好的,让我们实际构建一个成熟的推荐器系统,该系统可以查看系统中每个人的所有行为信息以及他们评价的电影,并使用它们为我们数据集中的任何给定用户实际生成最佳推荐电影。 有点神奇,它会变得如此简单,您会感到惊讶。 我们走吧!

让我们开始使用ItemBasedCF.ipynb文件,并开始导入已有的 MovieLens 数据集。 再说一次,我们现在使用的子集仅包含 100,000 个评分。 但是,您可以从 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编号308Toy Story (1995)4星,以及user_id编号66Toy Story (1995)3星。 并且,它将包含每个用户,每个电影的每个评分。

再次,就像前面一样,我们在 Pandas 中使用了出色的pivot_table命令,基于以下信息构造了一个新的DataFrame

userRatings = ratings.pivot_table(index=['user_id'],
                                  columns=['title'],values='rating') 
userRatings.head() 

在这里,每一行都是user_id,各列由我的数据集中的所有唯一电影标题组成,并且每个单元格都包含一个评分:

我们最终得到的是上面输出中显示的极其有用的矩阵,其中包含每一行的用户和每一列的电影。 而且,对于该矩阵中的每部电影,我们基本上都有每个用户的评分。 因此,例如user_id数字1101 Dalmatians (1996) 2 星级。 而且,所有这些NaN值仍然代表丢失的数据。 因此,这仅表示,例如user_id编号1没有对影片1-900 (1994)进行评分。

同样,这是一个非常有用的矩阵。 如果我们正在执行基于用户的协同过滤,则可以计算每个单独的用户评分向量之间的相关性,以找到相似的用户。 由于我们正在执行基于项目的协同过滤,因此我们对列之间的关系更感兴趣。 因此,例如,在任意两列之间进行相关性评分,这将为我们提供给定电影对的相关性评分。 那么,我们该怎么做呢? 事实证明,Pandas 也使这件事变得非常容易。

它具有内置的corr函数,该函数实际上将计算整个矩阵中找到的每个列对的相关性得分-就像他们在想我们一样。

corrMatrix = userRatings.corr() 
corrMatrix.head() 

让我们继续运行前面的代码。 这是一项在计算上相当昂贵的事情,因此花一点时间才能真正返回结果。 但是,我们有它!

那么,我们在前面的输出中有什么? 我们这里有一个新的DataFrame,其中每个电影都在行和列中。 因此,我们可以查看任意两部给定电影的交集,并根据我们最初在此处获得的userRatings数据找到彼此之间的相关性得分。 多么酷啊? 例如,电影101 Dalmatians (1996)当然与自身完美相关,因为它具有相同的用户评级向量。 但是,如果您查看101 Dalmatians (1996)电影与电影12 Angry Men (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 Dalmatians (1996)的相关分数为1,并且该数据集的这个小样本中实际上没有电影,彼此之间有 100 位共同观看者。 但是,有足够多的电影幸存下来以获得有意义的结果。

通过示例了解电影推荐

那么,我们如何处理这些数据? 好吧,我们要做的是向人们推荐电影。 我们这样做的方法是查看给定人员的所有收视率,查找与他们所收视的东西相似的电影,这些电影是向该人推荐的候选对象。

让我们从创建一个假人开始为其创建建议。 我实际上已经在我们正在处理的 MovieLens 数据集中手动添加了一个伪造的用户 ID 号0。 您可以使用以下代码查看该用户:

myRatings = userRatings.loc[0].dropna() 
myRatings 

这给出以下输出:

这种代表像我这样的人,他们喜欢《星球大战》和《帝国反击战》,但讨厌电影《乱世佳人》。 那么,这代表一个真正热爱星球大战但不喜欢旧风格,浪漫戏剧的人,好吗? 因此,我将5星的评分定为The Empire Strikes Back (1980)Star Wars (1977),将1星的评分定为Gone with the Wind (1939)。 因此,我将尝试为该虚拟用户找到建议。

那么,我该怎么做呢? 好吧,让我们开始创建一个名为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) 

对于0范围内的i到我在myRatings中的评分数量,我将添加与我评分的电影相似的电影。 因此,我将采用corrMatrix DataFrame,它是具有所有电影相似性的神奇数据库,并且我将使用myRatings创建一个相关矩阵,删除所有缺失的值,然后我将根据我对这部电影的评价程度来衡量相关得分。

因此,这里的想法是,例如,我将经历《帝国反击战》的所有相似之处,并且我会将其全部缩放 5,因为我真的很喜欢《帝国反击战》。 但是,当我经历并获得《乱世佳人》的相似性时,我只会将其缩放 1,因为我不喜欢《乱世佳人》。 因此,这将使类似于我喜欢的电影的电影具有更多的力量,而类似于我不喜欢的电影的电影具有较少的力量,好吗?

因此,我只是仔细研究并建立了相似性候选者列表,如果可以的话,推荐候选者,对结果进行排序并打印出来。 让我们看看我们得到了什么:

嘿,看起来还不错吧? 因此,显然The Empire Strikes Back (1980)Star Wars (1977)排名第一,因为我明确喜欢那些电影,因此我已经看过它们并对其进行评分。 但是,冒充到榜首的是Return of the Jedi (1983),我们期望它和Raiders of the Lost Ark (1981)

让我们开始进一步完善这些结果。 我们看到我们正在获得重复的值。 如果我们所拍的电影与我所评价的不止一部电影相似,那么它将在结果中返回不只一次,因此我们希望将它们组合在一起。 如果我确实有同一部电影,也许应该将它们加在一起,形成一个综合的,更强的推荐评分。 例如,《绝地武士》的回归与《星球大战》和《帝国反击战》都很相似。 我们该怎么做?

使用groupby命令合并行

我们将继续进行探索。 我们将再次使用groupby命令将同一电影的所有行组合在一起。 接下来,我们将总结它们的相关性得分并查看结果:

simCandidates = simCandidates.groupby(simCandidates.index).sum() 
simCandidates.sort_values(inplace = True, ascending = False) 
simCandidates.head(10) 

结果如下:

嘿,看起来真不错!

因此Return of the Jedi (1983)的得分最高,为 7,Raiders of the Lost Ark (1981)以 5 分紧随其后,然后我们开始进入Indiana Jones and the Last Crusade (1989),还有更多电影The Bridge on the River Kwai (1957)Back to the Future (1985), The Sting (1973)。 这些都是我真正喜欢看的电影! 您知道,我实际上也确实喜欢老式的迪斯尼电影,因此Cinderella (1950)并不像看起来那样疯狂。

我们需要做的最后一件事是过滤掉我已经评分过的电影,因为推荐你已经看过的电影没有意义。

使用drop命令删除条目

因此,我可以使用以下代码迅速删除原始评分序列中的所有行:

filteredSims = simCandidates.drop(myRatings.index) 
filteredSims.head(10) 

运行该命令,将使我看到最终的前 10 个结果:

我们终于得到它了! Return of the Jedi (1983)Raiders of the Lost Ark (1981)Indiana Jones and the Last Crusade (1989),对于我的虚拟用户来说,都是最重要的结果,并且它们都是有意义的。 我正在看几部适合家庭观看的电影,Cinderella (1950)The Wizard of Oz (1939)Dumbo (1941)可能会悄悄潜入,这可能是由于《飘》的存在,即使它被向下加权也仍然在在那儿。 而且,我们得到了结果,所以。 你有它! 太酷了!

实际上,我们已经为给定用户生成了建议,并且可以为整个DataFrame中的任何用户提供建议。 因此,如果需要,请继续尝试。 我还想谈一谈如何实际使您的手变脏一点,并发挥这些结果。 尝试改善他们。

您知道这有点艺术,您需要不断迭代并尝试不同的想法和技术,直到获得越来越好的结果,并且您可以永远做到这一点。 我的意思是说,我整个职业生涯都是如此。 因此,我不希望您再花 10 年的时间像我一样尝试完善此方法,但是您可以做一些简单的事情,所以让我们来谈谈。

改善推荐结果

作为练习,我想挑战您,使这些建议变得更好。 因此,让我们谈谈我的一些想法,也许您也会有一些自己的想法,您可以实际尝试一下; 让您的手变脏,并尝试提出更好的电影推荐。

好的,这些推荐结果还有很多改进的余地。 关于如何根据您对该项目的评价,或者您希望为对两部给定电影的评价的最低人数选择一个阈值,我们做出了很多决定。 因此,您可以调整很多事情,可以尝试许多不同的算法,并且尝试从系统中提出更好的电影推荐可以带来很多乐趣。 因此,如果您感到满意,我正在挑战您去做!

以下是一些有关如何实际尝试改善本章结果的想法。 首先,您可以继续播放ItembasedCF.ipynb文件并进行修改。 因此,例如,我们看到相关方法实际上具有一些用于相关性计算的参数,我们在示例中使用了 Pearson,但是您还可以查找并尝试其他参数,看看它对结果有什么作用。 我们使用了最小周期值 100,也许这太高了,也许它太低了; 我们只是随意选择了它。 如果您使用该值怎么办? 例如,如果您降低该值,我希望您会看一些也许您从未听说过的新电影,但对于该人可能仍然是一个不错的推荐。 或者,如果您将其提高,您将看到,除了大片之外,您一无所知。

有时,您必须考虑要从推荐系统中得到的结果。 在向人们展示他们听过的电影和他们从未听过的电影之间有一个很好的平衡吗? 对于发现这些新电影而言,与通过观看很多他们听说过的电影对推荐系统产生信心相比,对他们来说有多重要? 再说一遍,这是一种艺术。

我们还可以改善以下事实:即使我不喜欢《乱世佳人》,我们在结果中也看到了很多与《乱世佳人》相似的电影。 您知道我们对这些结果的加权程度低于与我喜欢的电影的相似度,但是也许这些电影实际上应该受到惩罚。 如果我非常讨厌《乱世佳人》,那么也许应该惩罚《绿野仙踪》等与《乱世佳人》的相似之处,而且,你知道他们的得分降低了而不是提高了。

这是您可以进行的另一个简单修改。 我们的用户评分数据集中可能存在一些离群值,如果我要抛弃对一些荒谬电影评分的人怎么办? 也许他们歪曲了一切。 实际上,您可以尝试识别这些用户并将其丢弃,这是另一个想法。 而且,如果您真的想要一个大型项目,如果您真的想尽全力投入其中,则可以使用训练/测试技术来实际评估此推荐引擎的结果。 因此,如果不是用一个任意的推荐分数来求和各个电影的相关性分数,而是实际将其缩小到每个给定电影的预测等级,该怎么办?

如果我的推荐系统的输出是一部电影,并且是该电影的预测收视率,那么在训练/测试系统中,我实际上可以尝试找出我对用户实际上已经看过并收视过的电影的预测程度如何? 好的? 因此,我可以搁置一些收视率数据,看看我的推荐器系统能够如何预测用户对这些电影的收视率。 并且,这将是一种量化和原则性的方法来衡量此推荐引擎的错误。 但是,与此同时,还有一门艺术而不是一门科学。 即使 Netflix 奖品实际上使用了这种误差度量,即所谓的均方根误差,也是他们特别使用的误差度量,这是否真的可以衡量一个好的推荐系统?

基本上,您正在衡量推荐系统预测一个人已经看过的电影的收视率的能力。 但是,推荐器引擎的目的不是推荐一个人没有看过的,可能会喜欢的电影吗? 那是两件事。 因此,不幸的是,要真正测量要测量的东西并不容易。 因此有时候,您确实需要遵循直觉。 而且,衡量推荐器引擎结果的正确方法是衡量您试图通过该引擎推广的结果。

也许我是想让人们看更多的电影,或者对新电影给予更高的评价,或者购买更多的东西。 与使用训练/测试相反,在真实的网站上运行实际的受控实验将是为此进行优化的正确方法。 因此,您知道的是,我在那儿进了一些细节,比我可能应该做的要多,但是教训是,您不能总是以黑白方式考虑这些问题。 有时,您不能真正地直接,定量地测量事物,而您必须使用一些常识,这就是一个例子。

无论如何,这些都是关于如何回溯并改进我们编写的此推荐器引擎的结果的一些想法。 因此,请随时调整它,看看是否可以根据自己的意愿进行改进,并从中获得乐趣。 这实际上是本书中非常有趣的一部分,所以希望您喜欢!

总结

因此,请尝试一下! 看看您是否可以改善我们的初步结果。 那里有一些简单的想法可以使这些建议变得更好,也有一些更复杂的想法。 现在,没有正确或错误的答案。 我不会要你上交工作,也不会去审查你的工作。 您知道,您决定使用它并对其有所了解,然后进行实验并查看获得的结果。 这就是重点-只是为了使您更加熟悉使用 Python 处理这类事情,并更加熟悉基于项目的协同过滤背后的概念。

在本章中,我们研究了不同的推荐系统-我们排除了基于用户的协同过滤系统,并直接涉足基于项目的系统。 然后,我们使用了 Pandas 的各种功能来生成和完善我们的结果,希望您在这里已经看到了 Pandas 的力量。

在下一章中,我们将介绍更高级的数据挖掘和机器学习技术,包括 K 近邻。 我期待着向您解释这些内容,并看看它们将如何有用。

七、更多数据挖掘和机器学习技术

在本章中,我们将讨论更多的数据挖掘和机器学习技术。 我们将讨论一种非常简单的技术,称为 K 最近邻KNN)。 然后,我们将使用 KNN 来预测电影的收视率。 之后,我们将继续讨论降维和主成分分析。 我们还将看一下 PCA 的示例,在该示例中,我们将 4D 数据缩小为二维,同时仍保留其方差。

然后,我们将遍历数据仓库的概念,并查看较新的 ELT 流程相对于 ETL 流程的优势。 我们将学习强化学习的有趣概念,并了解 PacMan 游戏中智能 PacMan 智能体背后使用的技术。 最后,我们将看到一些用于强化学习的奇特术语。

我们将涵盖以下主题:

  • 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 笔记本中的内容。

如果要继续,请继续进行更改。 和以前一样,我们只是要导入实际收视率数据文件,它是使用 Pandas 中的read_csv()函数输入的u.data。 我们要说的是它实际上有一个制表符分隔符,而不是逗号。 我们将导入数据集中每个电影评分的前三列,分别代表user_idmovie_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_idmovie_idratingDataFrame结尾。 例如,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的条目 1 恰好是《玩具总动员》,您可能听说过 1995 年的皮克斯老电影。 接下来是所有类型的列表,其中 0 表示它不属于该类型,1 表示它不属于该类型。 MovieLens数据集中有一个数据文件,该文件将告诉您这些流派字段实际上对应于什么。

就我们的目的而言,这实际上并不重要,对吧? 我们只是试图根据电影的类型来衡量电影之间的距离。 那么,在数学上所有重要的事情是,这个流派向量与另一部电影有多相似,好吗? 实际类型本身并不重要! 我们只想看看两部电影的流派分类相同或不同。 因此,我们具有该类型列表,具有计算出的受欢迎程度得分,并且具有“玩具总动员”的平均或平均评分。 好的,让我们继续前进,弄清楚如何将所有这些信息组合在一起成为距离度量,以便例如可以找到“玩具总动员”中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] 

原来是 GoldenEye 和 Get Shorty 这两个电影,与其他电影截然不同:

您知道,您有 James Bond 动作冒险片和一部喜剧电影-一点都不相似! 它们在人气上实际上是可比的,但是在风格上却有所不同。 所以,让我们把它们放在一起!

接下来,我们将编写一些代码以实际获取一些给定的 movieID 并找到 KNN。 因此,我们要做的就是计算玩具总动员和电影词典中所有其他电影之间的距离,并根据它们的距离得分对结果进行排序。 这就是下面的几段代码。 如果您想花点时间围绕它,那很简单。

我们有一个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) 

因此,让我们继续进行此操作,看看最终会得到什么。 以下代码的输出如下:

结果并非没有道理。 因此,我们以电影《玩具总动员》(玩具总动员)为例,该电影名为 movieID 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 个不同的质心,以及到这些质心的距离。 这是将数据精简为低维表示的一种方法。

主成分分析

通常,当人们谈论降维时,他们在谈论一种称为主成分分析的技术。 这是一种花哨的技术,它涉及到一些相当复杂的数学。 但是,从高层次上讲,您需要知道的是它需要一个较高维的数据空间,并且可以在该数据空间中找到较高维的平面。

这些较高维的平面称为超平面,它们由称为特征向量的事物定义。 最终,您可以选择任意数量的平面,将数据投影到那些超平面上,这些平面将成为您较低维度数据空间中的新轴:

您知道的,除非您熟悉高维数学并且您之前已经考虑过,否则很难动手! 但是,归根结底,这意味着我们要在高维空间中选择仍保留数据差异最大的平面,然后将数据投影到这些高维平面上,然后将其带入较低维空间, 好的?

您并不需要真正理解所有数学知识就可以使用它。 重要的一点是,这是将数据集缩减到较低维度空间,同时仍保留其中差异的一种非常原则性的方法。 我们讨论了将图像压缩作为此方法的一种应用。 因此,您知道,如果我想减少图像的尺寸,可以使用 PCA 将其精简为本质。

面部识别是另一个例子。 因此,如果我有一个人脸数据集,也许每个人脸都代表 2D 图像的第三维,并且我想将其归结为 SVD 和主成分分析可以成为一种识别在人脸中真正起作用的特征的方法。 因此,最终可能会更多地关注眼睛和嘴巴,例如,保留该数据集中的差异所必需的那些重要特征。 因此,它可以产生一些非常有趣且非常有用的结果,这些结果会自然地从数据中浮现出来,这很酷!

为了使其真实,我们将使用一个更简单的示例,即所谓的鸢尾花数据集。 这是 Scikit-learn 附带的数据集。 它在示例中非常常用,这是它的背后思想:因此,鸢尾花实际上在其花上有 2 种不同的花瓣。 有人称它为花瓣,它是您熟悉的花瓣,并且也有称为萼片的东西,它是花朵上这种支撑性较低的花瓣。

我们可以取一堆不同种类的鸢尾花,测量花瓣的长度和宽度,以及萼片的长度和宽度。 因此,花瓣的长度和宽度以及萼片的长度和宽度一起是 4 个不同的度量,它们对应于我们数据集中的 4 个不同维度。 我想用它来对鸢尾花可能属于哪个物种进行分类。 现在,PCA 将让我们在 2 维而不是 4 维上可视化该维,同时仍保留该数据集中的方差。 因此,让我们看看它的工作原理,并实际编写一些 Python 代码来使 PCA 发生在鸢尾花数据集上。

因此,这些就是降维,主成分分析和奇异值分解的概念。 是的,所有花哨的大话都是,这是一件花哨的事情。 您知道,我们正在设法以保留维数差异的方式将维空间缩小为维空间减少。 幸运的是,Scikit-learn 使此操作非常容易,就像实际应用 PCA 所需的仅 3 行代码一样。 因此,让我们实现这一目标吧!

使用鸢尾花数据集的 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。 基本上,这告诉您当我将其缩小为 2 维时保留了原始 4D 数据中的多少差异。 因此,让我们继续看一下:

它所带给您的实际上是我们保留的 2 个维度的 2 个项目的列表。 这告诉我,在第一个维度中,我实际上可以保留数据中 92% 的方差,而第二个维度仅给了我 5% 的方差。 如果将它们放在一起,这就是我将数据投影到的这两个维度,我仍然保留了源数据中 97% 以上的方差。 我们可以看到,实际上并不需要 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 维而不是 2 维! 因此,将n_components更改为1,看看您得到什么样的方差比。

怎么了? 有道理吗? 试一试,熟悉一下。 即降维,主成分分析和奇异值分解都在起作用。 非常非常喜欢的术语,而且您知道,公平地说,这是一些非常有趣的数学知识。 但是,正如您所看到的,这是一项非常强大的技术,并且使用 Scikit-learn 并不难应用。 因此,请将其放在工具箱中。

在那里,您拥有了! 花卉信息的 4D 数据集归结为 2 个维度,我们既可以轻松地对其进行可视化,又可以清晰地看到我们感兴趣的分类之间的轮廓。因此,PCA 在此示例中的确非常有效。 同样,它还是用于压缩,特征提取或面部识别之类的有用工具。 因此,请将其保留在工具箱中。

数据仓库概述

接下来,我们将讨论一些有关数据仓库的知识。 Hadoop 的出现,一些大数据技术和云计算的出现,最近确实改变了这一领域。 因此,那里有很多流行语,但对您来说很重要的概念也要理解。

让我们深入探讨这些概念! 让我们讨论一下 ELT 和 ETL,以及一般的数据仓库。 与特定的实际技术相反,这更多是一个概念,因此我们将在概念上进行讨论。 但是,这可能是在求职面试中出现的。 因此,请确保您了解这些概念。

我们将从总体上讨论数据仓库开始。 什么是数据仓库? 嗯,这基本上是一个巨大的数据库,其中包含来自许多不同来源的信息,并将它们捆绑在一起。 例如,也许您在一家大型电子商务公司工作,他们可能有一个订购系统,可以提供有关人们购买到您的数据仓库中的东西的信息。

您可能还具有从 Web 服务器日志中提取到数据仓库中的信息。 这样,您就可以将网站上的浏览信息与人们最终订购的内容联系在一起。 也许您还可以结合来自客户服务系统的信息,并衡量浏览行为与一天结束时客户的满意度之间是否存在联系。

数据仓库面临的挑战是从许多不同的来源获取数据,将它们转换为某种模式,该模式使我们能够同时查询这些不同的数据源,并且它使我们能够通过数据分析获得见解。 因此,大型公司和组织通常会遇到这种情况。 在这里,我们将讨论大数据的概念。 例如,您可以拥有一个庞大的 Oracle 数据库,其中包含所有这些内容,并且可能以某种方式对其进行了分区,复制以及具有各种复杂性。 您可以通过 SQL,结构化查询语言,或通过图形工具(例如 Tableau)查询该查询,Tableau 在当今非常流行。 这就是数据分析师的工作,他们使用 Tableau 之类的工具查询大型数据集。

这就是数据分析师和数据科学家之间的区别。 您可能实际上是在编写代码,以对 AI 边界的数据执行更高级的技术,而不是仅仅使用工具从数据仓库中提取图形和关系。 这是一个非常复杂的问题。 在亚马逊,我们有一个负责数据仓库的整个部门,全职负责这方面的工作,而他们却没有足够的人,我可以告诉你; 这是一项艰巨的工作!

您知道,进行数据仓库有很多挑战。 一种是数据规范化:因此,您必须弄清楚这些不同数据源中的所有字段实际上如何相互关联? 我如何实际上确保一个数据源中的一列与另一数据源中的一列具有可比性,并且使用相同的术语,并且以相同的比例具有相同的数据集? 如何处理丢失的数据? 我该如何处理损坏的数据或来自异常值或机器人等之类的数据? 这些都是很大的挑战。 维护这些数据提要也是一个很大的问题。

当您将所有这些信息导入数据仓库时,很多事情都会出错,尤其是当您需要进行非常大的转换时,需要将从 Web 日志中保存的原始数据导入到实际的结构化数据库表中, 导入到您的数据仓库中。 当您处理整体数据仓库时,扩展规模也可能会变得棘手。 最终,您的数据将变得如此庞大,以至于这些转换本身开始成为问题。 这开始涉及 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 之类的东西还使您还可以在实际上分布在 Hadoop 集群上的数据仓库上,以类似于 SQL 的数据仓库的方式进行查询。 还存在可以使用 Spark 和 MapReduce 查询的分布式 NoSQL 数据存储。 这个想法是,您不是在数据仓库中使用整体数据库,而是在 Hadoop 或某种集群之上构建的东西,实际上不仅可以扩展数据的处理和查询,而且还可以扩展该数据的转换。

再次,您首先提取原始数据,但是接下来我们将其直接加载到数据仓库系统本身中。 然后,使用可能建立在 Hadoop 上的数据仓库的功能作为第三步进行此转换。 然后,我可以一起查询事物。 因此,这是一个非常大的项目,非常重要的话题。 您知道,数据仓库本身就是一门完整的学科。 我们很快将在本书中进一步讨论 Spark,这是处理此问题的一种方式-特别是有一个称为 Spark SQL 的东西。

这里的总体概念是,如果您从基于 Oracle 或 MySQL 构建的单片数据库迁移到基于 Hadoop 构建的这些较现代的分布式数据库之一,则可以进入转换阶段,并在加载原始数据后执行它,和以前不同。 最终可能会变得更简单,更具可伸缩性,并利用当今可用的大型计算集群的强大功能。

这就是 ETL 与 ELT,这是在基于云的计算中遍历许多群集的传统方式,而今天,当我们确实拥有可用于转换大型数据集的大型计算云时,这种方式才有意义。 这就是概念。

ETL 是一种古老的做法,您需要先脱机转换一堆数据,然后再将其导入并将其加载到巨型数据仓库,整体数据库中。 但是,利用当今的技术,基于云的数据库,Hadoop,Hive,Spark 和 MapReduce,您实际上可以更高效地完成此任务,并利用集群的力量在将原始数据加载到数据仓库中之后,真正地执行该转换步骤。

这确实在改变着这个领域,了解这一点很重要。 同样,在这个主题上还有很多要学习的东西,因此我鼓励您在这个主题上进行更多的探索。 但是,这是基本概念,现在您知道人们在谈论 ETL 与 ELT 时在谈论什么。

强化学习

下一个主题是一个有趣的主题:强化学习。 我们可以通过吃豆人的例子来实际使用这个想法。 实际上,我们可以创建一个小小的智能 PacMan 智能体,该智能体可以很好地玩 PacMan 游戏。 您会惊讶于在这种智能吃豆人背后建立智能的技术多么简单。 让我们来看看!

因此,强化学习背后的想法是,您有某种媒介,在本例中为“吃豆人”,它探索某种空间,而在我们的示例中,空间将是“吃豆人”所在的迷宫。 ,它将了解不同条件下不同状态变化的值。

例如,在上图中,“吃豆人”的状态可能是由于它对南方有一个幽灵,对西方有一个墙以及对北方和东方具有空的空间而定义的, 吃豆人的当前状态。 可以采取的状态更改是沿给定方向移动。 然后,我可以学习朝某个方向前进的值。 因此,例如,如果我要向北迁移,则什么都不会发生,因此没有任何实际的回报。 但是,如果我向南移动,我会被幽灵摧毁,那将是一个负值。

在探索整个空间的过程中,我可以建立一套吃豆人可能处于的所有可能状态,以及在每个状态中沿给定方向移动所关联的值,这就是强化学习。 在探索整个空间时,它会针对给定状态优化这些奖励值,然后可以使用这些存储的奖励值来选择最佳决策,以在给定当前条件的情况下做出决定。 除《吃豆人》外,还有一个名为《猫与老鼠》的游戏,该游戏是一个常用的示例,稍后我们将进行介绍。

这种技术的好处是,一旦您探究了智能体可能处于的所有可能状态集,那么当您运行此智能体的不同迭代时,您很快就会获得非常好的表现。 因此,您知道,您可以基本地做出智能的吃豆人,通过运行强化学习,并让其探索可以在不同状态下做出的不同决策的值,然后存储该信息,从而给定它在未知条件下看到的未来状态时,快速做出正确的决策。

Q 学习

因此,强化学习的一个非常具体的实现称为 Q 学习,这使我们刚才讨论的内容正式化了:

  • 同样,从智能体的一组环境状态开始(我旁边有鬼吗?在我面前有药丸吗?类似的事情。),我们将其称为s
  • 在这些状态下,我可以执行一组可能的操作,我们将其称为a。 对于吃豆人,这些可能的动作是向上,向下,向左或向右移动。
  • 然后我们为每个状态/动作对都有一个值,我们将其称为Q; 这就是为什么我们将其称为 Q 学习。 因此,对于每个状态,围绕吃豆人的一组给定条件,给定动作将具有值Q。 因此,例如,向上移动可能具有给定的值Q,而向下移动可能具有负的Q值,例如,这意味着遇到重影。

因此,对于吃豆人可能处于的每种可能状态,我们从Q值开始为 0。而且,当吃豆人探索迷宫时,随着吃豆人的坏事发生,我们减少了 “吃豆人”当时所处状态的Q值。 因此,如果吃豆人最终被鬼魂吞噬,我们将惩罚他在当前状态下所做的一切。 吃豆人吃东西或吃鬼东西时,吃豆人都会遇到好事,我们会为该动作增加Q值,以了解他所处的状态。然后, 可以做的就是使用这些Q值来告知 PacMan 未来的选择,并建立一种表现最佳的智能智能体,从而制作出完美的 PacMan。 从上面看到的相同的 PacMan 图像,我们可以进一步定义 PacMan 的当前状态,方法是定义他在西边有一堵墙,在北边和东边有空白空间,在南边有个幽灵。

我们可以看看他可以采取的行动:他实际上根本不能向左移动,但是他可以向上,向下或向右移动,我们可以为所有这些动作分配一个值。 向上或向右走,实际上什么也没有发生,没有药丸或点药可消耗。 但是,如果他走了,那绝对是一个负值。 可以说,对于吃豆人所处的当前状况所给出的状态,下移将是一个非常糟糕的选择。 为此,应该有一个负的Q值。 根本无法向左移动。 向上或向右移动或保持中性,对于给定状态的那些动作选择,Q值将保持为 0。

现在,您还可以稍作展望,以打造一个更加智能的智能体。 所以,我实际上距离这里获得药丸只有两步之遥。 因此,当吃豆人探索这个状态时,如果我想在下一个状态下吃该药的情况,我实际上可以将其计入上一个状态的Q值中。 如果您只是某种折扣因素,则根据您的时间间隔,距离的步数,您可以将所有因素综合考虑在内。 因此,这实际上是在系统中构建一点内存的一种方式。 您可以在计算Q时使用折扣因子“向前看”多个步骤(此处s是先前状态, s'是当前状态):

Q(s,a) += discount * (reward(s,a) + max(Q(s')) - Q(s,a))

因此,我在服用该药丸时遇到的Q值实际上可能会提高我在此过程中遇到的先前Q值。 因此,这是使 Q 学习更好的一种方法。

探索问题

我们在强化学习中遇到的一个问题是探索问题。 如何确保在探索阶段有效地涵盖了所有不同的状态以及这些状态中的动作?

简单的方法

一种简单的方法是始终为我到目前为止计算出的最高Q值的给定状态选择动作,如果有平局,则随机选择。 因此,最初我所有的Q值都可能为 0,而我一开始只是随机选择动作。

当我开始获得有关给定动作和给定状态的更好的Q值的信息时,我将开始使用它们。 但是,这最终会导致效率低下,如果我将自己束缚于始终选择迄今为止计算出的最佳Q值的严格算法中,那么我实际上可能会错过很多路径。

更好的方法

因此,一种更好的方法是在我探索时在我的动作中引入一些随机变化。 因此,我们称其为ε项。 因此,假设我们有一些值,我掷骰子,我有一个随机数。 如果最终小于该ε值,则我实际上不遵循最高的Q值; 我没有做有意义的事情,我只是随意走一条路尝试一下,看看会发生什么。 实际上,这使我能够在探索阶段更有效地探索更广泛的可能性,更广泛的行动,从而更有效地应对更广泛的国家。

因此,我们所做的工作可以用非常花哨的数学术语来描述,但是从概念上讲,这很简单。

花哨的词

我探索了针对给定状态集可以采取的某些动作,用它来告知与给定状态集给定动作相关的奖励,在完成探索之后,我可以使用该信息,Q值,例如,可以智能地浏览全新的迷宫。

这也可以称为马尔可夫决策过程。 同样,很多数据科学只是给一些简单的概念分配花哨的,令人生畏的名字,而在强化学习中有很多这样的名字。

马尔可夫决策过程

因此,如果您查找马尔可夫决策过程的定义,它就是“在结果部分随机且部分受决策者控制的情况下,对决策建模的数学框架”。

  • 决策:在给定状态下,如果给定一组可能性,我们将采取什么行动?
  • 在结果部分为随机的情况下:嗯,有点像我们在那里的随机探索。
  • 部分受决策者的控制:决策者是我们计算出的Q值。

因此,MDP,马尔可夫决策过程是描述我们刚刚为强化学习而描述的探索算法的一种理想方式。 表示法甚至相似,状态仍然描述为s,而s'是我们遇到的下一个状态。 对于给定的ss'状态,我们具有定义为P[a]的状态转换函数。 我们有Q值,它们基本上表示为奖励函数,对于给定的ss'R[a]值。 因此,从一种状态转移到另一种状态具有与之相关的给定奖励,并且从一种状态转移到另一种状态由状态转换函数定义:

  • 状态仍然描述为ss'
  • 状态转换函数描述为P[a](s, s')
  • 我们的Q值描述为奖励函数R[a](s, s')

因此,再次描述马尔科夫决策过程,只是描述我们所做的事情,只有一种数学符号和一个听起来更奇特的单词。 而且,如果您想听起来更聪明,也可以使用另一个名称来调用马尔可夫决策过程:离散时间随机控制过程。 听起来很聪明! 但是概念本身就是我们刚刚描述的东西。

动态规划

因此,甚至可以说是花哨的话:动态规划可以用来描述我们刚刚所做的事情。 哇! 听起来像人工智能,计算机自己编程,《终结者 2》,天网之类的东西。 但是,不,这就是我们所做的。 如果您查看动态规划的定义,则它是一种解决复杂问题的方法,可以将其分解为更简单的子问题的集合,一次解决每个子问题,并使用基于内存的数据结构理想地存储其解决方案。

下一次出现相同的子问题时,无需重新计算其解决方案,只需查找先前计算出的解决方案,从而节省了计算时间,但以(希望的)适度的存储空间开销为代价:

  • 解决复杂问题的方法:与创建智能吃豆人相同,最终结果非常复杂。
  • 通过将其分解为更简单的子问题的集合:例如,对于 PacMan 可能处于的给定状态,采取的最佳措施是什么。PacMan 可以陷入许多不同的状态,但这些状态中的每个状态都代表着一个更简单的子问题,我只能做出有限的选择,并且有一个正确的答案可以做出最好的举动。
  • 存储其解:这些解是与每个状态下每个可能动作相关的Q值。
  • 理想情况下,使用基于内存的数据结构:嗯,当然,我需要存储这些Q值并将它们与状态关联,对吗?
  • 下次再次出现相同的子问题:下次吃豆人处于给定状态时,我具有一组Q值。
  • 无需重新计算其解,而是简单地查找先前计算的解:我已经从探索阶段获得了Q值。
  • 从而节省了计算时间,但以适度的存储空间开销为代价:这正是我们刚刚进行的强化学习。

我们有一个复杂的探索阶段,该阶段寻找给定状态下与每个动作相关的最佳奖励。 一旦有了针对给定状态要采取的正确行动的表格,我们就可以非常快速地使用它来使“吃豆人”以最佳方式移动,这是他之前从未见过的全新迷宫。 因此,强化学习也是动态规划的一种形式。 哇!

概括地说,您可以通过半随机地探索给定不同条件的运动的不同选择来制作智能的 PacMan 智能体,其中,这些选择是动作,而这些条件是状态。 我们会随时跟踪与每个动作或状态相关的奖励或惩罚,实际上,我们可以打折,如果您想使其更好的话,可以分多个步骤进行。

然后,我们存储最终与每个状态相关联的Q值,并可以使用它来告知其将来的选择。 因此,我们可以进入一个全新的迷宫,并拥有一个真正聪明的吃豆人,可以独自避开鬼魂并有效地将它们吃掉。 这是一个非常简单的概念,但是功能非常强大。 您也可以说您了解很多花哨的术语,因为它们全称为同一件事。 Q 学习,强化学习,马尔可夫决策过程,动态规划:所有这些都捆绑在同一个概念中。

我不知道,我认为通过这样一种简单的方法实际上可以制造出一种人工智能的吃豆人真是太酷了,它确实有效! 如果您想更详细地研究它,请参考以下示例,这些示例具有一个实际的源代码,您可以查看并可以使用 Python 马尔可夫决策过程工具箱

有一个 Python Markov 决策过程工具箱,将其包装在我们所讨论的所有术语中。 您可以查看一个示例,一个类似于猫和老鼠游戏的示例。 而且,实际上还有一个“吃豆人”示例,您也可以在网上查看,该示例与我们所讨论的内容更直接相关。 随意探索这些链接,并进一步了解它。

因此,这就是强化学习。 更一般而言,这是一种用于构建智能体的有用技术,该智能体可以通过可能的不同状态集进行导航,该状态集可能具有与每个状态相关联的一组动作。 因此,我们主要是在迷宫游戏中谈论它。 但是,您可以进行更广泛的思考,并且知道在遇到某种情况时,需要根据给定的一组当前条件和可以采取的行动来预测某项行为。 强化学习和 Q 学习可能是一种方法。 所以记住这一点!

总结

在本章中,我们看到了一种最简单的机器学习技术,称为 k 近邻。 我们还看了一个 KNN 实例,该实例可以预测电影的收视率。 我们分析了降维和主成分分析的概念,并看到了 PCA 的示例,该示例将 4D 数据缩小为二维,同时仍保留其方差。

接下来,我们了解了数据仓库的概念,并了解了今天如何使用 ELT 流程而不是 ETL 更有意义。 我们介绍了强化学习的概念,并了解了如何在《吃豆人》游戏中使用它。 最后,我们看到了一些用于强化学习的奇特词汇(Q 学习,Markov 决策过程和动态学习)。 在下一章中,我们将看到如何处理现实世界的数据。

八、处理真实数据

在本章中,我们将讨论处理现实数据的挑战,以及您可能会遇到的一些怪癖。 本章首先讨论偏差方差权衡,这是一种更原则性的方式,用于讨论您可能过拟合和欠拟合数据的不同方式,以及它们之间如何相互关联。 然后,我们讨论 K 折交叉验证技术,该技术是打击过拟合的重要工具,并介绍了如何使用 Python 实现该技术。

接下来,我们分析在实际应用任何算法之前清除数据并对其进行规范化的重要性。 我们看到一个示例,用于确定网站上最受欢迎的页面,这些页面将演示清洁数据的重要性。 本章还涵盖了记住对数值数据进行规范化的重要性。 最后,我们研究如何检测异常值并加以处理。

具体而言,本章涵盖以下主题:

  • 分析偏差/方差的权衡
  • K 折交叉验证的概念及其实现
  • 数据清理和规范化的重要性
  • 确定网站受欢迎页面的示例
  • 归一化数值数据
  • 检测并处理异常值

偏差/方差权衡

在处理现实世界数据时,我们面临的基本挑战之一是拟合过高与拟合过低而不是拟合数据,模型或预测。 当我们谈论欠拟合和过拟合时,我们经常可以在偏差和方差以及偏差与方差之间进行权衡。 所以,让我们谈谈这意味着什么。

因此,从概念上讲,偏差和方差非常简单。 偏差与您与正确值的距离有多远,也就是说,您的总体预测在预测正确的总体值方面有多好。 如果您采用所有预测的均值,它们或多或少都在正确的位置上? 还是您的错误始终在一个方向或另一个方向上始终偏斜? 如果是这样,则您的预测会偏向某个方向。

方差只是衡量预测的分散程度和分散程度的指标。 因此,如果您的预测无处不在,那么差异就很大。 但是,如果他们非常关注正确的值,或者在高偏差的情况下甚至不正确的值,那么您的差异就很小。

让我们看一些例子。 让我们想象一下,以下飞镖代表了我们所做的一系列预测,而我们试图预测的实际值位于靶心的中心:

  • 从左上角的飞镖板开始,您可以看到我们的点都分散在中心周围。 因此,总的来说,您知道平均误差非常接近实际。 我们的偏见实际上非常低,因为我们的预测都围绕着相同的正确点。 但是,我们的方差很高,因为这些点散布在整个地方。 因此,这是低偏差和高方差的示例。
  • 如果我们继续移动到右上角的飞镖板上,就会看到我们的点始终一致地从其应有的位置偏向西北。 因此,这是我们的预测中存在高度偏见的一个示例,在这些偏见中始终存在一定偏差。 我们的差异很小,因为它们都紧密地聚集在错误的位置,但是至少它们彼此靠近,因此我们的预测是一致的。 那是低方差。 但是,偏见很高。 同样,这是高偏差,低方差。
  • 在左下角的飞镖板上,您可以看到我们的预测分散在错误的均值周围。 因此,我们有很高的偏见。 一切都偏向了不该有的地方。 但是我们的差异也很大。 因此,这是两个世界中最糟糕的一个。 在此示例中,我们具有较高的偏见和较高的方差。
  • 最后,在一个奇妙的完美世界中,您将看到一个示例,例如右下方向的飞镖盘,其中我们的偏见低,所有事物都集中在应该放置的地方,而方差很小,所有事物都紧密地聚集在应该放置的地方。 因此,在一个完美的世界中,这就是最终的结果。

实际上,您通常需要在偏差和方差之间进行选择。 归结为过拟合对数据的拟合不足。 让我们看下面的例子:

这与偏见和差异的思考方式有些不同。 因此,在左图中,我们有一条直线,相对于这些观察,您可以认为这具有非常低的方差。 因此,该行没有太多变化,即,变化很小。 但是偏差(每个点的误差)实际上很高。

现在,将其与右图中的过拟合数据进行对比,我们已经竭尽全力来拟合观察值。 该线具有较高的方差,但具有较低的偏差,因为每个点都非常接近应有的位置。 因此,这是我们用方差权衡偏差的一个例子。

归根结底,您并不想减少偏差或减少方差,而是想减少误差。 真正重要的是,事实证明您可以将误差表示为偏差和方差的函数:

以此来看,误差等于偏差平方加方差。 因此,这些因素都会造成整体误差,而偏差实际上会造成更大的误差。 但请记住,这是您真正要最小化的误差,而不是具体的偏差或方差,并且过于复杂的模型最终可能具有高方差和低偏差,而过于简单的模型将具有低方差和高偏差。 偏见。 但是,它们最终都可能在一天结束时具有相似的错误条件。 尝试拟合数据时,您只需要找到这两件事的正确的快乐媒介即可。 在接下来的部分中,我们将讨论一些实际上避免过拟合的更原则的方法。 但是,这只是我想克服的偏见和差异的概念,因为人们确实在谈论它,并且您将被期望知道这意味着什么。

现在,让我们将其与本书中较早的概念联系起来。 例如,在k个最近的邻居中,如果我们增加 K 的值,我们就会开始平均扩展到更大区域的邻居。 这具有减少方差的效果,因为我们可以在更大的空间上使事物平滑,但是它可能会增加我们的偏见,因为我们将要挑选到的人群可能与我们开始时的相关性越来越小 。 通过在大量邻居上平滑 KNN,我们可以减小方差,因为我们正在通过更多值平滑事物。 但是,我们可能会引入偏见,因为我们引入了越来越多的点,而这些点与开始时的点之间的关联性却不那么高。

决策树是另一个例子。 我们知道,单个决策树很容易过拟合,因此可能暗示它具有很大的方差。 但是,随机森林寻求权衡一些方差以减少偏差,并且它通过拥有多个随机变异的树并将其所有解决方案求平均来做到这一点。 就像当我们通过增加 KNN 中的 K 来对事物进行平均时:我们可以通过使用多个使用相似森林的随机森林决策树来平均决策树的结果。

这是偏差方差的权衡。 您知道必须在值的整体准确率,值的分散程度或紧密程度之间做出决定。 那就是偏差-方差的权衡,它们都会导致整体误差,这是您真正关心的要最小化的事情。 因此,请记住这些条款!

用于避免过拟合的 K 折交叉验证

在本书的前面,我们讨论了训练和测试,将其作为防止过拟合并实际测量模型在从未见过的数据上的表现的好方法。 我们可以使用称为 K 折交叉验证的技术将其提升到一个新的水平。 因此,让我们谈谈您的武器库中用于对抗过拟合的强大工具; K 折交叉验证并了解其工作原理。

回想一下训练/测试,当时的想法是,我们将要构建机器学习模型的所有数据分为两个部分:训练数据集和测试数据集。 想法是,我们仅使用训练数据集中的数据来训练模型,然后使用为测试数据集保留的数据评估模型的表现。 这样可以防止我们过拟合现有的数据,因为我们正在针对该模型进行过从未见过的数据测试。

但是,训练/测试仍然有其局限性:您仍然可能最终过度适合您的特定训练/测试单元。 也许您的训练数据集并不能真正代表整个数据集,并且太多的东西最终出现在您的训练数据集中,使事情发生了偏差。 因此,这就是 K 折交叉验证的用武之地,它需要训练/测试并将其提升一个档次。

这个想法虽然听起来很复杂,但相当简单:

  1. 我们没有将数据分为两个存储桶,一个用于训练,一个用于测试,而是将其分为 K 个存储桶。
  2. 我们保留其中一个存储桶用于测试目的,以评估模型的结果。
  3. 我们针对剩余的K-1桶对模型进行训练,然后获取测试数据集,并使用该数据集来评估模型在所有这些不同训练数据集中的表现。
  4. 我们将这些结果误差度量(即那些 R 平方值)平均,以从 K 折交叉验证中获得最终误差度量。

仅此而已。 这是一种进行训练/测试的更强大的方法,而这也是一种方法。

现在,您可能会好好考虑一下,如果我对保留的那一个测试数据集过拟合该怎么办? 对于这些训练数据集中的每个数据集,我仍使用相同的测试数据集。 如果该测试数据集也不能真正代表事物怎么办?

有 K 折交叉验证的变体,也可以将其随机化。 因此,您可以每次都随机选择训练数据集是什么,而只是保持随机地将事物分配给不同的存储桶并测量结果。 但是通常,当人们谈论 K 折交叉验证时,他们谈论的是这种特定技术,即您保留一个存储桶用于测试,其余的存储桶用于训练,然后当您为每个桶建立一个模型时,相对于测试数据集评估全部训练数据集。

使用 Scikit-learn 的 K 折交叉验证的示例

幸运的是,Scikit-learn 使得此操作确实非常容易,并且比进行常规训练/测试更容易! 进行 K 折叠交叉验证非常简单,因此您也可以这样做。

现在,这一切在实践中的工作方式是您将要尝试调整一个模型,并且该模型将具有不同的变体,您可能需要对其进行调整的不同参数,对吗?

例如,多项式拟合的多项式次数。 因此,我们的想法是尝试使用模型的不同值,不同的变体,使用 K 折交叉验证对它们进行度量,然后找到一个对测试数据集最小化误差的变量。 那是您在那里的最佳去处。 在实践中,您想使用 K 折交叉验证来衡量模型相对于测试数据集的准确率,并且只是不断完善该模型,继续尝试其中的不同值,继续尝试该模型的不同变体,甚至尝试完全不同的建模方法,直到您找到使用 K 折交叉验证最大程度地减少误差的技术为止。

让我们深入一个例子,看看它是如何工作的。 我们将再次将其应用于鸢尾花数据集,重新访问 SVC,然后我们将进行 K 折交叉验证,看看它有多简单。 我们实际上使用一些真实的 Python 代码在此处进行 K 折交叉验证和训练/测试。 您会发现它实际上非常易于使用,这是一件好事,因为这是您应使用的一种技术,用于在监督学习中测量模型的准确率和有效性。

请继续打开KFoldCrossValidation.ipynb,然后继续操作。 我们将再次查看鸢尾花数据集; 还记得我们在谈到降维时介绍过的吗?

只是为了刷新您的记忆,鸢尾花数据集包含一组 150 个鸢尾花花的测量值,其中每朵花的花瓣的长度和宽度以及其萼片的长度和宽度。 我们也知道每朵花属于 3 种不同鸢尾花中的哪一种。 面临的挑战是创建一个模型,只要给定其花瓣和萼片的长度和宽度,就可以成功地预测鸢尾花的种类。 因此,让我们继续进行。

我们将使用 SVC 模型。 如果您还记得,那只是对数据进行分类的一种非常可靠的方法。 如果您需要去刷新内存,则有一个章节:

import numpy as np 
from sklearn import cross_validation 
from sklearn import datasets 
from sklearn import svm 

iris = datasets.load_iris() 

# Split the iris data into train/test data sets with 
#40% reserved for testing 
X_train, X_test, y_train, y_test = cross_validation.train_test_split(iris.data, 
                                    iris.target, test_size=0.4, random_state=0) 

# Build an SVC model for predicting iris classifications 
#using training data 
clf = svm.SVC(kernel='linear', C=1).fit(X_train, y_train) 

# Now measure its performance with the test data 
clf.score(X_test, y_test) 

我们要做的是使用 Scikit-learn 的cross_validation库,我们首先进行常规的训练测试拆分,仅进行一次训练/测试拆分,然后看看如何工作。

为此,我们有一个train_test_split()函数,使它非常容易。 因此,此方法的工作方式是将一组特征数据输入到train_test_split()中。 iris.data仅包含每朵花的所有实际测量值。 iris.target基本上是我们要预测的事情。

在这种情况下,它包含每朵花的所有种类。 test_size说我们要训练与测试的百分比。 因此,0.4 表示我们将随机抽取 40% 的数据用于测试,并将 60% 的数据用于训练。 这带给我们的是 4 个数据集,基本上是特征数据和目标数据的训练数据集和测试数据集。 因此,X_train最终包含鸢尾花测量值的 60%,X_test包含 40% 用于测试模型结果的测量值。 y_trainy_test包含这些段中每个段的实际种类。

然后,在此之后,我们继续构建一个 SVC 模型,以根据鸢尾花物种的测量值来预测它们,然后仅使用训练数据来构建该模型。 我们使用线性核,仅使用训练特征数据和训练种类数据(即目标数据)来拟合此 SVC 模型。 我们称该模型为clf。 然后,我们在clf上调用score()函数以仅针对我们的测试数据集测量其表现。 因此,我们根据为鸢尾花测量保留的测试数据和鸢尾花种类对模型进行评分,并观察其效果如何:

事实证明,它确实做得很好! 在超过 96% 的时间中,我们的模型仅基于该鸢尾花的测量值就能够正确预测它从未见过的鸢尾花的种类。 太酷了!

但是,这是一个相当小的数据集,如果我没记错的话,大约有 150 朵花。 因此,我们仅使用 150 朵花中的 60% 进行训练,而仅使用 150 朵花中的 40% 进行测试。 这些仍然是很小的数字,因此我们仍然可能过度适合于我们进行的特定训练/测试分组。 因此,让我们使用 K 折交叉验证来防止这种情况。 事实证明,使用 K 折交叉验证(即使它是更可靠的技术),实际上实际上比训练/测试更容易使用。 所以,这很酷! 因此,让我们看看它是如何工作的:

# We give cross_val_score a model, the entire data set and its "real" values, and the number of folds: 
scores = cross_validation.cross_val_score(clf, iris.data, iris.target, cv=5) 

# Print the accuracy for each fold: 
print scores 

# And the mean accuracy of all 5 folds: 
print scores.mean() 

我们已经有了一个模型,我们为此预测定义了 SVC 模型,您需要做的就是在cross_validation包上调用cross_val_score()。 因此,您要在此函数中传递给定类型(clf)的模型,即所有测量的整个数据集,即我的所有特征数据(iris.data)和我的所有目标数据 (所有物种),iris.target

我想要cv=5,这意味着它实际上将使用 5 个不同的训练数据集,同时保留1进行测试。 基本上,它将运行 5 次,这就是我们需要做的。 这将针对整个数据集自动评估我们的模型,分解出五种不同的方式,并给我们单独的结果。

如果我们将其输出打印出来,它将为我们提供这些迭代中的每一个迭代(即,这些折叠中的每一个)的实际误差度量的列表。 我们可以将它们平均在一起,以获得基于 K 折交叉验证的总体误差度量:

当我们这样做超过 5 倍时,我们可以看到我们的结果甚至比我们想象的还要好! 98% 的准确率。 太酷了! 实际上,在几次运行中我们都具有完美的准确率。 所以这真是太神奇了。

现在,让我们看看是否可以做得更好。 以前我们使用线性核,如果我们使用多项式核甚至更高级怎么办? 这会过拟合还是实际上会更好地拟合我们拥有的数据? 这种类型取决于这些花瓣测量值与实际物种之间是否存在线性关系或多项式关系。 因此,让我们尝试一下:

clf = svm.SVC(kernel='poly', C=1).fit(X_train, y_train)
scores = cross_validation.cross_val_score(clf, iris.data, iris.target, cv=5)
print scores
print scores.mean()

我们将使用相同的技术再次运行所有这些。 但是这次,我们使用的是多项式内核。 我们将其适合我们的训练数据集,在这种情况下您适合的位置并不重要,因为cross_val_score()会一直为您重新运行它:

事实证明,当我们使用多项式拟合时,最终得出的总体得分甚至低于我们的原始得分。 因此,这告诉我们多项式内核可能过拟合。 当我们使用 K 折交叉验证时,它显示出比线性核函数更低的分数。

这里重要的一点是,如果我们仅使用单个训练/测试单元,就不会意识到我们过拟合。 如果我们像在线性内核上那样只进行一次训练/测试拆分,我们实际上会得到相同的结果。 因此,我们可能会无意间在此处过拟合数据,甚至在不使用 K 折交叉验证的情况下甚至都不知道。 因此,这是一个很好的例子,它说明了救援人员使用 K 折的情况,并警告您过拟合,在这种情况下,单次训练/测试分站可能无法解决这一问题。 因此,请将其放在工具箱中。

如果您想更多地玩这个游戏,请尝试不同程度的尝试。 因此,您实际上可以指定其他数量的度数。 多项式内核的默认值为 3 度,但是您可以尝试其他一个,也可以尝试两个。

这样会更好吗? 如果降为 1,则基本上可以降级为线性核,对吗? 因此,也许仍然存在多项式关系,也许只是二阶多项式。 试试看,看看你能得到什么。 这就是 K 折交叉验证。 如您所见,感谢 Scikit-learn,它非常易于使用。 这是一种非常健壮的方式来衡量模型的良好状态的重要方法。

数据清理和规范化

现在,这是最简单的部分之一,但可能是整本书中最重要的部分。 我们将讨论清理输入数据,这将花费很多时间。

您清理输入数据的方式以及对原始输入数据的理解程度将对结果质量产生巨大影响-甚至可能比您选择的模型或模型的调优程度更大。 所以,要注意; 这是重要的东西!

作为数据科学家,清理原始输入数据通常是最重要且最耗时的工作!

让我们谈谈数据科学的一个不便之处,那就是您花费了大部分时间实际上只是在清理和准备数据,而实际上却很少用它来分析和尝试新的算法。 它并不像人们一直认为的那样迷人。 但是,这是一件非常重要的事情要注意。

您可能会在原始数据中找到很多不同的东西。 传入的数据(仅是原始数据)将非常脏,并且将以多种不同方式受到污染。 如果您不加以处理,它将使您的结果产生偏差,最终将最终导致您的公司做出错误的决定。

如果再次出现错误,那就是您提取了很多不良数据并没有考虑到这些错误,没有对这些数据进行清理,您告诉您的公司来根据结果来做某事,后来完全是错误的,您将会遇到很多麻烦! 所以,要注意!

您需要注意许多不同类型的问题和数据:

  • 离群值:因此,也许您的人员在数据中表现得有些奇怪,而当您对它们进行挖掘时,它们却是您不应该首先关注的数据。 一个很好的例子是,如果您正在查看 Web 日志数据,并且看到一个会话 ID 不断地反复出现,并且它以人们永远无法完成的高速度执行某项操作。 您可能会看到一个机器人,该脚本正在某个地方运行以实际抓取您的网站。 甚至可能是某种恶意攻击。 但是无论如何,您都不希望这些行为数据通知您的模型,这些数据只能用来预测使用您的网站的真实人类的行为。 因此,监视异常值是识别在构建模型时可能希望从模型中删除的数据类型的一种方法。
  • 缺失数据:当数据不存在时您会怎么做? 回到网络日志的示例,您可能在该行中有一个引荐来源网址,也可能没有。 如果不存在该怎么办? 是否为丢失或未指定的内容创建新分类? 还是您将那条线全部淘汰? 您必须考虑正确的做法。
  • 恶意数据:可能有人在尝试玩您的系统,有人可能在试图欺骗系统,而您不希望那些人逃避它。 假设您正在建立一个推荐系统。 可能有人在外面捏造行为数据以推广他们的新产品,对吗? 因此,您需要警惕此类情况,并确保对输入数据识别先令攻击或其他类型的攻击,并从结果中过滤掉它们,不要让它们获胜。 。
  • 错误数据:如果某些系统中某处出现软件错误,而该错误仅在某些情况下会写出错误的值? 这有可能发生。 不幸的是,您没有很好的方法来了解这一点。 但是,如果您看到的数据看起来有些混乱,或者结果对您而言没有意义,那么深入研究有时可能会发现潜在的错误,这些错误首先导致写入错误的数据。 也许某些时候事情没有被正确地组合在一起。 可能不是整个会议期间都举行会议。 例如,人们在访问网站时可能会丢弃其会话 ID 并获取新的会话 ID。
  • 不相关数据:此处非常简单。 也许您只对纽约市人的数据感兴趣,或出于某种原因。 在这种情况下,来自世界各地的人们的所有数据都与您要查找的内容无关。 您要做的第一件事就是丢弃所有数据并限制数据,将其缩减为您真正关心的数据。
  • 不一致数据:这是一个很大的问题。 例如,在地址中,人们可以用许多不同的方式编写相同的地址:他们可能会缩写街道,或者可能不会缩写街道,他们可能根本不会将街道放在街道名称的末尾。 他们可能以不同的方式将行组合在一起,可能会拼写不同的东西,可能在美国使用邮政编码,或者在美国使用邮政编码加 4 码,他们可能有一个国家,他们可能没有一个国家。 您需要以某种方式找出您看到的变化以及如何将它们全部归一化。
  • 也许我正在看有关电影的数据。 电影在不同国家/地区可能具有不同的名称,或者一本书在不同国家/地区可能具有不同的名称,但它们的含义相同。 因此,您需要在需要规范化数据,可以用许多不同方式表示相同数据的地方,并需要将它们组合在一起以获得正确的结果。
  • 格式化:这也可能是一个问题; 事物的格式可能不一致。 以日期为例:在美国,我们总是执行月日年(MM/DD/YY),但是在其他国家,他们可能会执行日月年(DD/MM/YY),谁知道呢。 您需要了解这些格式差异。 电话号码可能在区号周围带有括号,也许没有。 也许数字的每个部分之间都有破折号,也许没有。 也许社会保险号有破折号,也许没有。 这些都是您需要注意的事情,并且您需要确保在处理过程中不会将格式的变化视为不同的实体或不同的分类。

因此,有很多事情需要提防,而前面的列表只是要注意的主要问题。 记住:垃圾进,垃圾出。 您的模型仅与您提供给它的数据一样好,这是非常非常正确的! 您可以拥有一个非常简单的模型,如果您向其提供大量干净的数据,则其表现将非常好,并且实际上它可能会在较脏的数据集上胜过复杂的模型。

因此,确保您拥有足够的数据和高质量的数据通常是最主要的任务。 您会惊讶于现实世界中使用的一些最成功的算法是如此简单。 它们只有通过输入的数据质量和输入的数据量才能成功。 您并不总是需要精美的技术来获得良好的结果。 通常,数据的质量和数量与其他任何事物一样重要。

始终质疑您的结果! 您不想返回并且仅在得到不满意的结果时在输入数据中查找异常。 这会给您的结果带来无意的偏差,让您希望或期望的结果毫无疑问地通过,对吗? 您想一直质疑事物,以确保您一直在寻找这些事物,因为即使您找到自己喜欢的结果,即使结果是错误的,它仍然是错误的,它仍然会以错误方向通知您的公司。 以后可能会再次咬你。

例如,我有一个名为“无恨新闻”的网站。 这是非营利性组织,所以我不会试图通过告诉您来赚钱。 假设我只想在我拥有的该网站上找到最受欢迎的页面。 这听起来像是一个非常简单的问题,不是吗? 我应该只能够浏览一下我的 Web 日志,并计算出每个页面有多少次匹配,并对它们进行排序,对吗? 它能有多难?! 好吧,事实证明这真的很难! 因此,让我们深入研究该示例,看看为什么会很难,并查看一些必须进行的实际数据清除的示例。

清理 Web 日志数据

我们将展示清除数据的重要性。 我有一个自己的小网站上的一些网络日志数据。 我们将尝试查找该网站上浏览量最高的页面。 听起来很简单,但是正如您所看到的,这实际上是非常具有挑战性的! 因此,如果您想继续学习,TopPages.ipynb是我们在这里工作的笔记本电脑。 开始吧!

我实际上有一个从实际网站获取的访问日志。 这是来自 Apache 的真实 HTTP 访问日志,并包含在您的书中。 因此,如果您确实想在这里玩,请确保更新路径以将访问日志移动到保存书籍材料的位置:

logPath = "E:\\sundog-consult\\Packt\\DataScience\\access_log.txt" 

在网络日志上应用正则表达式

因此,我去了互联网,获得了以下几段代码,这些代码段将 Apache 访问日志行解析为一堆字段:

format_pat= re.compile( 
    r"(?P<host>[\d\.]+)\s" 
    r"(?P<identity>\S*)\s" 
    r"(?P<user>\S*)\s" 
    r"\[(?P<time>.*?)\]\s" 
    r'"(?P<request>.*?)"\s' 
    r"(?P<status>\d+)\s" 
    r"(?P<bytes>\S*)\s" 
    r'"(?P<referer>.*?)"\s' 
    r'"(?P<user_agent>.*?)"\s*' 
) 

这段代码包含主机,用户,时间,实际页面请求,状态,引荐来源网址user_agent(意味着实际上是使用哪个浏览器来查看此页面)之类的内容。 它建立了所谓的正则表达式,我们正在使用re库来使用它。 从本质上讲,这是用于对大字符串进行模式匹配的非常强大的语言。 因此,我们实际上可以将此正则表达式应用于访问日志的每一行,并将该访问日志行中的信息位自动分组到这些不同的字段中。 让我们继续运行。

在这里要做的显而易见的事情是,让我们编写一个小脚本,该脚本计算我们遇到的每个被请求的 URL,并计算被请求的次数。 然后,我们可以对该列表进行排序并获得首页,对吗? 听起来很简单!

因此,我们将构建一个名为URLCounts的 Python 字典。 我们将打开日志文件,并针对每一行应用正则表达式。 如果实际上返回的结果与我们要匹配的模式成功匹配,我们会说,好吧,这看起来像访问日志中的一条不错的线。

让我们从中提取请求字段,它是实际的 HTTP 请求,即浏览器实际上正在请求的页面。 我们将其分为三个部分:它由一个动作(例如 GET 或 POST)组成; 实际请求的 URL; 以及所使用的协议。 鉴于该信息已拆分,我们然后可以仅查看该 URL 是否已存在于我的字典中。 如果是这样,我将增加1遇到该 URL 的次数的计数; 否则,我将为该 URL 引入一个新的字典条目,并将其初始化为1的值。 我对日志中的每一行都执行此操作,以相反的顺序对结果进行数字排序并打印出来:

URLCounts = {}
with open(logPath, "r") as f:
    for line in (l.rstrip() for l in f):
        match= format_pat.match(line)
        if match:
            access = match.groupdict()
            request = access['request']
            (action, URL, protocol) = request.split()
            if URLCounts.has_key(URL):
                URLCounts[URL] = URLCounts[URL] + 1
            else:
                URLCounts[URL] = 1
results = sorted(URLCounts, key=lambda i: int(URLCounts[i]), reverse=True)

for result in results[:20]:
    print result + ": " + str(URLCounts[result])

因此,让我们继续进行以下操作:

糟糕! 我们最终在这里遇到了这个大错误。 告诉我们,我们需要超过1值才能解压缩。 因此,显然,我们得到了一些不包含操作,URL 和包含其他内容的协议的请求字段。

让我们看看那里发生了什么! 因此,如果我们打印出所有不包含三个项目的请求,我们将看到实际显示的内容。 因此,我们将在此处执行类似的一小段代码,但实际上将在 request 字段上进行拆分,并打印出无法得到预期的三个字段的情况。

URLCounts = {}

with open(logPath, "r") as f:
    for line in (l.rstrip() for l in f):
        match= format_pat.match(line)
        if match:
            access = match.groupdict()
            request = access['request']
            fields = request.split()
            if (len(fields) != 3):
                print fields

让我们看看里面到底有什么:

因此,我们有一堆空字段。 那是我们的第一个问题。 但是,然后我们有了第一个充满垃圾的字段。 谁知道那是哪里来的,但是显然是错误的数据。 好的,让我们修改脚本。

修改一-过滤请求字段

实际上,我们只会抛出请求中没有期望的 3 个字段的任何行。 这似乎是一件合法的事情,因为实际上其中确实包含完全无用的数据,这并不是说我们在这样做时会漏掉任何东西。 因此,我们将修改脚本来执行此操作。 在实际尝试处理之前,我们已经引入了if (len(fields) == 3)行。 我们将运行:

URLCounts = {}

with open(logPath, "r") as f:
    for line in (l.rstrip() for l in f):
        match= format_pat.match(line)
        if match:
            access = match.groupdict()
            request = access['request']
            fields = request.split()
            if (len(fields) == 3):
                URL = fields[1]
                if URLCounts.has_key(URL):
                    URLCounts[URL] = URLCounts[URL] + 1
                else:
                    URLCounts[URL] = 1

results = sorted(URLCounts, key=lambda i: int(URLCounts[i]), reverse=True)

for result in results[:20]:
    print result + ": " + str(URLCounts[result])

嘿,我们得到了结果!

但这与我网站上的首页并不完全相同。 请记住,这是一个新闻网站。 因此,我们得到了很多 PHP 文件,即 Perl 脚本。 那里发生了什么事? 我们的最佳结果是此xmlrpc.php脚本,然后是WP_login.php,然后是主页。 所以,不是很有用。 然后是robots.txt,然后是一堆 XML 文件。

您知道当我稍后对此进行调查时,发现我的网站实际上受到了恶意攻击; 有人试图闯进去。 他们尝试使用此xmlrpc.php脚本猜测我的密码,并尝试使用登录脚本进行登录。 幸运的是,在他们真正进入本网站之前,我将其关闭。

这是一个恶意数据被引入到我的数据流中的例子,我必须将其过滤掉。 因此,通过查看,我们不仅可以看到恶意攻击在查看 PHP 文件,而且还试图执行内容。 这不仅是在执行 GET 请求,还在于在脚本上进行发布请求,以实际尝试在我的网站上执行代码。

修改二-过滤 POST 请求

现在,我知道我所关心的数据是根据我要弄清楚的事情来确定的,那就是人们从我的网站获取网页。 因此,对我而言,合法的做法是从这些日志中过滤掉不是 GET 请求的所有内容。 所以,让我们接下来做。 我们将再次检查请求字段中是否有三个字段,然后还要检查操作是否得到执行。 如果不是,我们将完全忽略该行:

URLCounts = {}

with open(logPath, "r") as f:
    for line in (l.rstrip() for l in f):
        match= format_pat.match(line)
        if match:
            access = match.groupdict()
            request = access['request']
            fields = request.split()
            if (len(fields) == 3):
                (action, URL, protocol) = fields
                if (action == 'GET'):
                    if URLCounts.has_key(URL):
                        URLCounts[URL] = URLCounts[URL] + 1
                    else:
                        URLCounts[URL] = 1

results = sorted(URLCounts, key=lambda i: int(URLCounts[i]), reverse=True)

for result in results[:20]:
    print result + ": " + str(URLCounts[result])

我们应该越来越接近我们现在想要的东西,以下是前面代码的输出:

是的,这看起来似乎更加合理。 但是,它仍然没有真正通过健全性检查。 这是一个新闻网站; 人们去看新闻。 他们是否真的在阅读我仅有两篇文章的小博客? 我不这么认为! 这似乎有点可疑。 因此,让我们深入探讨一下,看看谁真正在看那些博客页面。 如果您实际上要手动进入该文件并进行检查,您会发现很多此类博客请求实际上都没有任何用户代理。 他们只有-的用户代理,这非常不常见:

如果一个真正的人使用一个真正的浏览器来尝试获取此页面,它将说出诸如 Mozilla 或互联网 Explorer 或 Chrome 之类的内容。 因此,这些请求似乎来自某种刮板。 同样,潜在的恶意流量无法识别其身份。

修改三-检查 UserAgent

也许,我们也应该查看 UserAgent,以查看这些用户是否是提出请求的实际人员。 让我们继续打印所有遇到的不同 UserAgent。 因此,按照实际上总结了所看到的不同 URL 的代码的精神,我们可以查看所看到的所有不同 UserAgent,并按此日志中最流行的user_agent字符串对它们进行排序:

UserAgents = {}

with open(logPath, "r") as f:
    for line in (l.rstrip() for l in f):
        match= format_pat.match(line)
        if match:
            access = match.groupdict()
            agent = access['user_agent']
            if UserAgents.has_key(agent):
                UserAgents[agent] = UserAgents[agent] + 1
            else:
                UserAgents[agent] = 1

results = sorted(UserAgents, key=lambda i: int(UserAgents[i]), reverse=True)

for result in results:
    print result + ": " + str(UserAgents[result])

我们得到以下结果:

您可以看到其中大部分看起来都是合法的。 因此,如果是刮板,在这种情况下,它实际上是恶意攻击,但实际上是在假装自己是合法的浏览器。 但是这个破折号user_agent也显示了很多。 因此,我不知道那是什么,但我知道它不是一个实际的浏览器。

我看到的另一件事是来自网络爬虫的大量流量。 因此,有百度是中国的搜索引擎,还有 Googlebot 只是在抓取页面。 我想我也看到了俄罗斯搜索引擎 Yandex。 因此,我们的数据正受到许多爬取程序的污染,这些爬取程序正试图为搜索引擎目的挖掘我们的网站。 再次,该流量不应计入我的分析的预期目的,即查看这些实际人类在我的网站上浏览的页面。 这些都是自动化脚本。

过滤蜘蛛/机器人的活动

好吧,所以这有点棘手。 仅基于用户字符串,没有真正好的识别蜘蛛或机器人的好方法。 但是我们至少可以对其进行合理的破解,并过滤掉其中包含bot一词的所有内容,或者从我的缓存插件中过滤出可能还需要提前请求页面的任何内容。 我们还将去除我们的朋友单破折号。 因此,我们将再次优化脚本,以除其他所有功能外,删除所有看上去很脏的 UserAgent:

URLCounts = {}

with open(logPath, "r") as f:
    for line in (l.rstrip() for l in f):
        match= format_pat.match(line)
        if match:
            access = match.groupdict()
            agent = access['user_agent']
            if (not('bot' in agent or 'spider' in agent or 
                    'Bot' in agent or 'Spider' in agent or
                    'W3 Total Cache' in agent or agent =='-')):
                request = access['request']
                fields = request.split()
                if (len(fields) == 3):
                    (action, URL, protocol) = fields
                    if (action == 'GET'):
                        if URLCounts.has_key(URL):
                            URLCounts[URL] = URLCounts[URL] + 1
                        else:
                            URLCounts[URL] = 1

results = sorted(URLCounts, key=lambda i: int(URLCounts[i]), reverse=True)

for result in results[:20]:
    print result + ": " + str(URLCounts[result])

URLCounts = {}

with open(logPath, "r") as f:
    for line in (l.rstrip() for l in f):
        match= format_pat.match(line)
        if match:
            access = match.groupdict()
            agent = access['user_agent']
            if (not('bot' in agent or 'spider' in agent or 
                    'Bot' in agent or 'Spider' in agent or
                    'W3 Total Cache' in agent or agent =='-')):
                request = access['request']
                fields = request.split()
                if (len(fields) == 3):
                    (action, URL, protocol) = fields
                    if (URL.endswith("/")):
                        if (action == 'GET'):
                            if URLCounts.has_key(URL):
                                URLCounts[URL] = URLCounts[URL] + 1
                            else:
                                URLCounts[URL] = 1

results = sorted(URLCounts, key=lambda i: int(URLCounts[i]), reverse=True)

for result in results[:20]:
    print result + ": " + str(URLCounts[result])

我们得到什么?

好了,我们开始吧! 对于前两个条目,这似乎开始变得更加合理,该主页是最受欢迎的,这是可以预期的。 奥兰多的头条新闻也很受欢迎,因为我使用这个网站的人比其他任何人都多,而且我住在奥兰多。 但是在那之后,我们得到了一堆根本不是网页的东西:一堆脚本,一堆 CSS 文件。 这些不是网页。

修改四-应用网站特定的过滤器

我可以应用一些有关我的网站的知识,而我碰巧知道我网站上的所有合法页面的 URL 都以斜杠结尾。 因此,让我们继续进行修改,以去除所有不以斜杠结尾的内容:

URLCounts = {}

with open (logPath, "r") as f:
    for line in (l.rstrip() for 1 in f):
        match= format_pat.match(line)
        if match:
            access = match.groupdict()
            agent = access['user_agent']
            if (not('bot' in agent or 'spider' in agent or
                    'Bot' in agent or 'Spider' in agent or
                    'W3 Total Cache' in agent or agent =='-')):
                request = access['request']
                fields = request.split()
                if (len(fields) == 3):
                    (action, URL, protocol) = fields
                    if (URL.endswith("/")):
                        if (action == 'GET'):
                            if URLCounts.has_key(URL):
                                URLCounts[URL] = URLCounts[URL] + 1
                            else:
                                URLCounts[URL] = 1

results = sorted(URLCounts, key=lambda i: int(URLCounts[i]), reverse=True)

for result in results[:20]:
    print result + ": " + str(URLCounts[result])

让我们运行它!

最后,我们得到了一些似乎有意义的结果! 因此,看起来像是,我小的 No-Hate News 网站上实际人类所要求的首页是首页,然后是orlando-headlines,接着是世界新闻,接着是漫画,接着是天气,以及关于屏幕。 因此,这开始看起来更加合法。

如果您要进行更深入的研究,您会发现此分析仍然存在问题。 例如,那些提要页面仍然来自机器人,只是试图从我的网站获取 RSS 数据。 因此,这在看似简单的分析如何在获得有意义的结果之前需要进行大量的预处理和清理源数据方面是一个很好的寓言。

再次,确保在清理数据过程中所做的事情是原则性的,并且您不仅仅是在挑剔与您的先入为主的概念不符的问题。 因此,请始终对您的结果提出疑问,始终查看您的源数据,并寻找其中的奇怪内容。

Web 日志数据活动

好吧,如果您想进一步解决这个问题,可以解决该订阅问题。 继续并删除包含提要的内容,因为我们知道这不是一个真正的网页,只是为了熟悉代码。 或者,更仔细地查看日志,以了解那些供稿页面实际来自何处。

也许有一种更好,更强大的方法来将流量识别为更大的类别。 因此,请随意解决。 但是,我希望您能吸取教训:数据清理-非常重要,这将花费大量时间!

因此,令人惊讶的是,在一个简单的问题(例如“我的网站上浏览量最高的页面是什么?”)上获得合理的结果是多么困难。 您可以想象,如果要为清理这样一个简单的问题而花费大量的精力来清理数据,请考虑一下脏数据可能实际上影响更复杂问题和复杂算法的结果的所有细微差别。

了解您的源数据,查看它,查看它的代表性示例,确保您了解系统中将要包含的内容非常重要。 始终对您的结果提出疑问,并将其与原始源数据联系起来,以查看可疑结果的来源。

归一化数值数据

这是一个非常快速的部分:我只想提醒您标准化数据的重要性,请确保您的各种输入特征数据在相同范围内并且具有可比性。 而且,有时很重要,有时没有。 但是,您只需要了解它的时间即可。 只需将其放在脑后,因为如果不这样做,有时会影响结果的质量。

因此,有时模型将基于几个不同的数值属性。 如果您还记得多变量模型,那么我们可能正在寻找的汽车具有不同的属性,并且它们可能不是直接可比的度量。 或者,例如,如果我们查看年龄与收入之间的关系,年龄可能在 0 到 100 之间,但以美元计的收入可能在 0 到十亿之间,并且取决于货币,范围可能更大! 某些模型可以。

如果您要进行回归分析,通常这没什么大不了的。 但是,除非将这些值先按比例缩小到一个通用比例,否则其他模型的表现将不佳。 如果不小心,可能会导致某些属性的数量超过其他属性。 如果您试图将这两个值视为模型中的可比较值,那么收入可能最终会超过年龄。

因此,这也会给属性带来偏差,这也是一个问题。 您知道,也许您的一组数据是偏斜的,有时您需要对事物与该组值看到的实际范围进行归一化,而不仅仅是将 0 缩放到最大刻度。 对于何时应该和不应该进行这种归一化没有固定的规则。 我只能说您始终在阅读文档以了解所使用的技术。

因此,例如,在 Scikit-learn 中,其 PCA 实现具有whiten选项,该选项将自动为您标准化数据。 您可能应该使用它。 它还具有一些可用的预处理模块,这些模块也将自动为您标准化和缩放内容。

也要注意实际上应该以数字或常规方式表示的文本数据。 如果您具有yesno数据,则可能需要将其转换为10并以一致的方式进行。 因此,再次阅读文档。 大多数技术对于未标准化的原始数据都可以很好地工作,但是在您第一次开始使用新技术之前,只需阅读文档并了解输入是否应该先缩放或归一化或白化。 如果是这样,Scikit-learn 可能会使您很容易做到这一点,您只需要记住要这样做! 如果要缩放输入数据,请不要忘记在完成后重新缩放结果。

如果您希望能够解释所获得的结果,有时您需要在完成后将其缩放回原始范围。 如果您正在缩放事物,甚至在将它们输入模型之前甚至将它们偏向某个量,请确保在实际将这些结果呈现给他人之前先对它们进行缩放并使其没有偏差。 否则,它们将毫无意义! 提醒一下,如果您愿意的话,还需要一点寓言,在将数据传递到给定模型之前,请务必检查是否应该对数据进行规范化或变白。

没有与本节相关的练习; 这只是我想让您记住的事情。 我只是想把重点讲清楚。 有些算法需要变白或标准化,而另一些则不需要。 因此,请务必阅读文档! 如果您确实需要标准化进入算法的数据,通常会告诉您,并且这样做非常容易。 请注意这一点!

检测异常值

实际数据的一个常见问题是离群值。 您总是会有一些奇怪的用户或某些奇怪的代理程序在污染您的数据,而这些行为异常而非典型地来自于典型用户。 它们可能是合法的异常值; 它们可能是由真实的人而不是某种恶意流量或伪造数据引起的。 所以有时候,删除它们是适当的,有时候不是。 确保做出负责任的决定。 因此,让我们深入研究一些处理异常值的示例。

例如,如果我正在进行协同过滤,并且试图提出电影推荐或类似的建议,那么您可能会有一些超级用户观看过每部电影,并对每部电影进行了评分。 他们最终可能会对其他所有人的建议产生过分的影响。

您实际上并不希望少数人在您的系统中拥有如此强大的功能。 因此,这可能是一个示例,其中过滤掉异常值并通过它们实际放入系统中的评级来识别它们是合理的事情。 或者,也许一个离群值就是没有足够评分的人。

我们可能正在查看 Web 日志数据,就像我们在前面的示例中进行数据清理时所看到的那样,离群值可能会告诉您,您的数据一开始有很多错误。 可能是恶意流量,也可能是漫游器或其他不应代表您要建模的实际人类的代理。

如果有人真的想要美国的平均平均收入(而不是中位数),那么您不应该因为不喜欢唐纳德·特朗普而把他扔掉。 您知道的事实是,即使他的中位数不超出中位数,他的数十亿美元也将推动该中位数的增长。 因此,不要通过排除异常值来弄乱数字。 但是,如果它与您首先要建模的内容不一致,则将其排除在外。

现在,我们如何识别异常值? 好吧,还记得我们的老朋友标准差吗? 我们在本书的开头部分已经介绍了这一点。 这是检测异常值的非常有用的工具。 原则上,您可以计算应该具有或多或少正态分布的数据集的标准差。 如果您看到的数据点超出一两个标准差,则那里有一个异常值。

记住,我们之前也讨论过箱形图和胡须图,它们还具有检测和可视化异常值的内置方法。 这些图将异常值定义为位于四分位数范围的 1.5 之外。

您选择几倍? 好吧,您必须使用常识,您知道,关于异常值没有严格的规定。 您必须查看您的数据并加以关注,查看分布,查看直方图。 看看是否有实际的事物会以明显的异常值突出于您,并在您将它们扔掉之前了解它们是什么。

处理异常值

因此,让我们来看一些示例代码,看看在实践中如何处理异常值。 让我们弄乱一些异常值。 这是一个非常简单的部分。 一点点审查实际上。 如果您想继续,我们在Outliers.ipynb中。 因此,如果您愿意,请继续打开它:

import numpy as np

incomes = np.random.normal(27000, 15000, 10000)
incomes = np.append(incomes, [1000000000])

import matplotlib.pyplot as plt
plt.hist(incomes, 50)
plt.show()

在本书的早期,我们做了非常相似的事情,在那本书中,我们创建了一个虚假的美国收入分配直方图。 我们要做的是从这里的收入的正态分布开始,每年的平均收入为 27,000 美元,标准差为 15,000。 我将创建 10,000 个在该分布中有收入的假美国人。 顺便说一下,尽管与现实相距不远,但这完全是虚构数据。

然后,我要坚持一个离群值-叫唐纳德·特朗普(Donald Trump),他有十亿美元。 我们将把这个家伙留在数据集的末尾。 因此,我们有一个约 27,000 美元的正态分布数据集,然后我们将在最后坚持 Donald Trump。

我们将继续将其绘制为直方图:

哇! 这不是很有帮助! 我们把该国其他所有人的整个正态分布压缩到直方图的一个桶中。 另一方面,我们在右边有唐纳德·特朗普,把整个事情搞砸了十亿美元。

另一个问题也是,如果我要回答这个问题,典型的美国人能赚多少钱。 如果我要尝试找出答案,这将不是一个非常有用的数字:

incomes.mean ()

前面代码的输出如下:

126892.66469341301

当我知道我不包括唐纳德·特朗普的正态分布数据的真实平均值仅为 27,000 美元时,唐纳德·特朗普就将这个数字自己提高到了 126,000 美元,并且有些变化。 因此,正确的做法是使用中位数而不是均值。

但是,假设我们出于某种原因不得不使用均值,而解决此问题的正确方法将是排除唐纳德·特朗普等离群值。 因此,我们需要弄清楚如何识别这些人。 好吧,您可以选择一些任意的界限,然后说:“我将淘汰所有亿万富翁”,但这并不是一件非常原则性的事情。 10 亿来自哪里?

我们数数的方式只是偶然。 因此,更好的方法是实际测量数据集的标准差,并将异常值识别为偏离平均值的标准差的倍数。

因此,以下是我编写的一个小函数。 称为reject_outliers()

def reject_outliers(data): 
    u = np.median(data) 
    s = np.std(data) 
    filtered = [e for e in data if (u - 2 * s < e < u + 2 * s)] 
    return filtered 

filtered = reject_outliers(incomes) 

plt.hist(filtered, 50) 
plt.show() 

它接收数据列表并找到中位数。 它还找到该数据集的标准差。 因此,我将其过滤掉,因此只保留数据中位数两个标准差内的数据点。 因此,我可以在收入数据上使用此便捷的reject_outliers()函数,以自动自动剔除怪异的异常值:

果然,它可行! 现在,我得到了一张更漂亮的图表,其中不包括唐纳德·特朗普,而是着重介绍了位于中心的更典型的数据集。 所以,很酷的东西!

因此,这是识别异常值并自动将其删除或在您认为合适时对其进行处理的一个示例。 请记住,请始终以有原则的方式进行此操作。 不要因为不便而将异常值排除在外。 了解它们的来源,以及它们实际上如何影响您要在精神上衡量的事物。

顺便说一下,我们的意思现在也更有意义了。 现在我们已经摆脱了离群值,因此应该接近 27,000。

异常活动

因此,如果您想玩这个游戏,就可以像我通常要求您那样去弄弄它。 尝试使用标准差的不同倍数,尝试添加更多离群值,并尝试添加不像唐纳德·特朗普那样离群值的离群值。 您知道,只需在此处构造一些额外的伪造数据并进行处理,看看您是否可以成功识别这些人。

所以你有它! 离群值; 非常简单的概念。 因此,这是通过查看标准差并仅查看与您关心的均值或中值的标准差的数量来识别异常值的示例。 实际上,考虑到离群值本身可能会扭曲均值,因此中值实际上可能是一个更好的选择,对吗? 因此,通过使用标准差,这是一种以更原则性的方式识别异常值的好方法,而不仅仅是选择一些任意的截止值。 同样,您需要决定对这些异常值进行正确的处理。 您实际上想衡量什么? 实际丢弃它们是否合适? 所以,把它放在你的头上!

总结

在本章中,我们讨论了在偏差和方差之间保持平衡并最小化误差的重要性。 接下来,我们看到了 K 折交叉验证的概念以及如何在 Python 中实现它以防止过拟合。 我们了解了在处理数据之前清理数据并对其进行标准化的重要性。 然后,我们看到了一个确定网站受欢迎页面的示例。 在第 9 章,“Apache Spark-大数据机器学习”中,我们将使用 Apache Spark 学习大数据机器学习。

九、Apache Spark-大数据机器学习

到目前为止,在本书中,我们已经讨论了很多可以在数据科学职业中使用的常规数据挖掘和机器学习技术,但是它们都已经在您的桌面上运行了。 因此,您只能使用 Python 和 Scikit-learn 之类的技术来运行一台计算机可以处理的尽可能多的数据。

现在,每个人都在谈论大数据,而您可能正在为一家实际上有大数据要处理的公司工作。 大数据意味着您无法实际控制所有内容,也无法仅在一个系统上实际处理所有内容。 您实际上需要使用整个云(一组计算资源)的资源来对其进行计算。 这就是 Apache Spark 的用武之地。ApacheSpark 是一个非常强大的工具,用于管理大数据并在大型数据集上进行机器学习。 在本章结束时,您将对以下主题有深入的了解:

  • 安装和使用 Spark
  • 弹性分布式数据集RDD
  • MLlib机器学习库
  • Spark 中的决策树
  • Spark 中的 K 均值聚类

安装 Spark

在本节中,我将为您设置使用 Apache Spark 的设置,并向您展示一些实际使用 Apache Spark 解决与本书过去使用单计算机解决的相同问题的示例。 我们需要做的第一件事是在您的计算机上设置 Spark。 因此,在接下来的两节中,我们将引导您完成该操作。 这是非常简单的东西,但是有一些陷阱。 因此,不要只跳过这些部分; 要使 Spark 成功运行,需要特别注意一些事项,尤其是在 Windows 系统上。 让我们在系统上进行 Apache Spark 的设置,这样您就可以真正开始使用它了。

现在,我们将仅在您自己的桌面上运行它。 但是,我们将在本章中编写的相同程序可以在实际的 Hadoop 集群上运行。 因此,您可以采用我们在 Spark 独立模式下在桌面上本地编写和运行的这些脚本,并从实际 Hadoop 集群的主节点实际运行它们,然后使其扩展到 Hadoop 集群的全部功能,并以这种方式处理海量数据集。 即使我们打算将事情设置为在您自己的计算机上本地运行,但请记住,这些相同的概念也可以扩展到在群集上运行。

在 Windows 上安装 Spark

在 Windows 上安装 Spark 涉及几个步骤,我们将在这里逐步指导您。 我只是假设您使用 Windows,因为大多数人在家中使用这本书。 稍后我们将讨论与其他操作系统打交道的内容。 如果您已经熟悉在计算机上安装东西和处理环境变量的知识,则可以选择以下小备忘单,然后再做。 如果您不太熟悉 Windows 的内部组件,我将在接下来的部分中一次逐步引导您。 以下是针对这些 Windows 专业人士的快速步骤:

  1. 安装 JDK:您需要首先安装 JDK,这是 Java 开发工具包。 您可以直接访问 Sun 的网站并下载并安装(如果需要)。 我们需要 JDK,因为即使在本课程中我们将使用 Python 进行开发,也需要将其转换为 Scala 代码,这是 Spark 本身开发的。 而且,Scala 反过来又在 Java 解释器上运行。 因此,为了运行 Python 代码,您需要一个 Scala 系统,该系统将作为 Spark 的一部分默认安装。 另外,我们需要 Java 或更具体地说是 Java 的解释器来实际运行 Scala 代码。 就像技术层蛋糕。
  2. 安装 Python:显然,您将需要 Python,但是如果您到此为止,则应该已经建立了 Python 环境,并希望使用 Enthought Canopy。 因此,我们可以跳过此步骤。
  3. 安装适用于 Hadoop 的 Spark 的预构建版本:幸运的是,Apache 网站提供了可用的 Spark 的预构建版本,这些预构建版本可以立即使用为最新 Hadoop 版本进行预编译的版本。 您无需构建任何东西,只需将其下载到计算机上,然后将其放置在正确的位置即可,大部分时间都很好。
  4. 创建conf/log4j.properties文件:我们需要处理一些配置事项。 我们要做的一件事就是调整警告级别,这样我们在工作时就不会收到很多警告垃圾邮件。 我们将逐步介绍如何做到这一点。 基本上,您需要重命名其中一个属性文件,然后在其中调整错误设置。
  5. 添加一个SPARK_HOME环境变量:接下来,我们需要设置一些环境变量,以确保您可以实际从任何路径运行 Spark。 我们将添加一个指向您安装 Spark 的位置的SPARK_HOME环境变量,然后将%SPARK_HOME%\bin添加到您的系统路径中,以便在您运行 Spark Submit,PySpark 或所需的任何 Spark 命令时,Windows 会知道在哪里找到它。
  6. 设置一个HADOOP_HOME变量:在 Windows 上,我们还需要做一件事,我们还需要设置一个HADOOP_HOME变量,因为它将期望找到一点点 Hadoop,即使您不在独立系统上使用 Hadoop。
  7. 安装winutils.exe:最后,我们需要安装一个名为winutils.exe的文件。 本书的资源中有指向winutils.exe的链接,因此您可以在那里找到它。

如果您想详细了解这些步骤,可以参考后面的章节。

在其他操作系统上安装 Spark

关于在其他操作系统上安装 Spark 的快速说明:相同的步骤基本上也将适用于它们。 主要区别在于您如何在系统上设置环境变量,方式是每次登录时都会自动应用它们。这会因操作系统而异。 macOS 的功能与 Linux 的各种功能有所不同,因此您至少必须对使用 Unix 终端命令提示符以及如何操纵您的环境有所了解。 但是,大多数从事开发工作的 macOS 或 Linux 用户已经掌握了这些基础知识。 当然,如果您不在 Windows 上,则不需要winutils.exe。 因此,这些是在不同操作系统上安装的主要区别。

安装 Java 开发套件

要安装 Java 开发工具包,请返回浏览器,打开一个新标签,然后仅搜索jdk(Java 开发工具包的缩写)。 这将带您到 Oracle 站点,从该站点可以下载 Java:

在 Oracle 网站上,单击JDK DOWNLOAD。 现在,单击“接受许可协议”,然后可以为您的操作系统选择下载选项:

对我来说,这将是 Windows 64 位,等待 198 MB 的好消息下载:

下载完成后,找到安装程序并开始运行。 请注意,我们不能仅在此处接受 Windows 安装程序中的默认设置。 因此,这是 Windows 专用的解决方法,但在撰写本书时,Spark 的当前版本为 2.1.1,事实证明 Windows 上的 Java 上的 Spark 2.1.1 存在问题。 问题是,如果您已经将 Java 安装到其中有空格的路径中,则它不起作用,因此我们需要确保将 Java 安装到其中没有空格的路径中。 这意味着即使您已经安装了 Java,也无法跳过此步骤,因此,让我向您展示如何执行此操作。 在安装程序上,单击“下一步”,您将看到,如以下屏幕所示,无论版本是什么,它都希望默认安装到C:\Program Files\Java\jdk路径:

Program Files路径中的空格将引起麻烦,因此让我们单击“更改...”按钮并安装到c:\jdk,这是一个很好的简单路径,易于记住,并且其中没有空格:

现在,它还希望安装 Java 运行时环境,因此为了安全起见,我还将其安装到没有空格的路径中。

在 JDK 安装的第二步,我们应该在屏幕上显示以下内容:

我还将更改该目标文件夹,为此我们将创建一个名为C:\jre的新文件夹:

好的,安装成功。 呼!

现在,您需要记住我们安装 JDK 的路径,在我们的例子中是C:\jdk。 我们还有更多步骤可去。 接下来,我们需要安装 Spark 本身。

安装 Spark

让我们回到这里的新浏览器选项卡,转到这个页面,然后单击Download Spark按钮:

现在,我们在本书中使用了 Spark 2.1.1,但是 2.0 以后的任何东西都可以正常工作。

确保您获得了预构建的版本,然后选择“直接下载”选项,以便所有这些默认设置都可以正常使用。 继续并单击说明 4 旁边的链接以下载该包。

现在,它下载了您可能不熟悉的 TGZ(GZip 中的 Tar)。 老实说,Windows 是 Spark 的事后想法,因为在 Windows 上,您将没有内置的工具来实际解压缩 TGZ 文件。 这意味着,如果您还没有一个,则可能需要安装它。 我使用的一个称为 WinRAR,您可以从这个页面中进行选择。 如果需要,请转到“下载”页面,然后下载 32 位或 64 位 WinRAR 的安装程序,具体取决于您的操作系统。 正常安装 WinRAR,这将允许您在 Windows 上实际解压缩 TGZ 文件:

因此,让我们继续解压缩 TGZ 文件。 我将打开我的Downloads文件夹以找到我们下载的 Spark 存档,然后继续右键单击该存档并将其解压缩到我选择的文件夹中-我现在将它放置在我的Downloads文件夹中。 再次,WinRAR 现在为我执行此操作:

因此,我现在应该在与该包关联的Downloads文件夹中有一个文件夹。 让我们打开它,其中就有 Spark 本身。 您应该看到类似如下所示的文件夹内容。 因此,您需要将其安装在可以记住的地方:

您显然不想将其保留在Downloads文件夹中,所以让我们继续在此处打开一个新的文件浏览器窗口。 我转到C驱动器并创建一个新文件夹,让我们将其命名为spark。 因此,我的 Spark 安装将在C:\spark中运行。 同样,很容易记住。 打开该文件夹。 现在,我回到下载的spark文件夹,并使用Ctrl + A选择 Spark 发行版中的所有内容,Ctrl + C复制它,然后返回C:\spark,我要放置在其中,然后Ctrl + V粘贴到:

记住要粘贴spark文件夹的内容,而不是spark文件夹本身很重要。 因此,我现在应该是带有spark文件夹的C驱动器,其中包含 Spark 发行版中的所有文件和文件夹。

好了,我们还需要配置一些东西。 因此,当我们进入C:\spark文件夹时,让我们打开conf文件夹,为确保我们不会被日志消息所淹没,我们将在此处更改日志记录级别设置。 为此,请右键单击log4j.properties.template文件,然后选择“重命名”:

删除文件名的.template部分,使其成为实际的log4j.properties文件。 Spark 将使用此配置日志记录:

现在,在某种文本编辑器中打开此文件。 在 Windows 上,您可能需要右键单击那里,然后选择“打开方式”,然后选择“写字板”。 在文件中,找到log4j.rootCategory=INFO

让我们将其更改为log4j.rootCategory=ERROR,这将消除我们运行内容时打印出的所有日志垃圾邮件的混乱情况。 保存文件,然后退出编辑器。

到目前为止,我们已经安装了 Python,Java 和 Spark。 现在,我们下一步要做的是安装一些东西,使您的 PC 误以为 Hadoop 存在,而此步骤仅在 Windows 上才是必需的。 因此,如果您使用的是 Mac 或 Linux,则可以跳过此步骤。

我有一个小文件可以解决问题。 让我们转到这个页面。 下载winutils.exe将为您提供可执行文件的一小段副本,可用于诱使 Spark 认为您确实拥有 Hadoop:

现在,由于我们要在桌面上本地运行脚本,所以这没什么大不了的,我们不需要真正安装 Hadoop。 这只是绕过了另一个在 Windows 上运行 Spark 的怪癖。 因此,现在我们有了它,让我们在Downloads文件夹中找到它,Ctrl + C复制它,然后转到C驱动器并创建一个位置来放置:

因此,再次在根C驱动器中创建一个新文件夹,我们将其命名为winutils

现在,打开此winutils文件夹并在其中创建一个bin文件夹:

现在,在此bin文件夹中,我希望您粘贴我们下载的winutils.exe文件。 因此,您应该先安装C:\winutils\bin,然后再安装winutils.exe

仅在某些系统上才需要执行下一步,但是为了安全起见,请在 Windows 上打开“命令提示符”。 您可以执行以下操作:转到“开始”菜单,然后转到“Windows 系统”,然后单击“命令提示符”。 在这里,我希望您键入cd c:\winutils\bin,这是我们粘贴winutils.exe文件的位置。 现在,如果您输入dir,那么您应该在那里看到该文件。 现在输入winutils.exe chmod 777 \tmp\hive。 这只是确保成功成功运行 Spark 所需的所有文件权限都到位,没有任何错误。 完成该步骤后,您可以关闭命令提示符。 哇,信不信由你,我们快完成了。

现在,我们需要设置一些环境变量以使事情正常进行。 我将向您展示如何在 Windows 上执行此操作。 在 Windows 10 上,您需要打开“开始”菜单并转到“Windows 系统| Windows”。 控制面板打开控制面板:

在控制面板中,单击系统和安全性:

然后,单击系统:

然后从左侧列表中单击高级系统设置:

在这里,单击环境变量...:

我们将获得以下选项:

现在,这是一种非常特定于 Windows 的设置环境变量的方法。 在其他操作系统上,您将使用不同的进程,因此您必须查看如何在它们上安装 Spark。 在这里,我们将设置一些新的用户变量。 单击第一个New ...按钮以获取新的用户变量,并将其命名为SPARK_HOME,如下所示,全部为大写。 这将指向我们安装 Spark 的位置,对我们来说它是c:\spark,所以键入它作为变量值,然后单击 OK:

我们还需要设置JAVA_HOME,因此再次单击New ...并在JAVA_HOME中输入变量名称。 我们需要指出 Java 的安装位置,对我们来说是c:\jdk

我们还需要设置HADOOP_HOME,这就是我们安装winutils包的位置,因此我们将其指向c:\winutils

到目前为止,一切都很好。 我们需要做的最后一件事是修改路径。 您应该在此处具有PATH环境变量:

单击PATH环境变量,然后单击“编辑...”,然后添加一个新路径。 这将是%SPARK_HOME%\bin,我要添加另一个%JAVA_HOME%\bin

基本上,这使 Spark 的所有二进制可执行文件都可用于 Windows,无论您在何处运行它。 单击此菜单上的前两个菜单上的确定。 我们终于完成了一切。

Spark 介绍

让我们从 Apache Spark 的高级概述开始,看看它的全部含义,它的优点以及它的工作方式。

什么是星火? 好吧,如果您访问 Spark 网站,他们会为您提供一个非常高级的手动答案,即“用于大规模数据处理的快速通用引擎”。 它切成薄片,切成小方块,洗衣服。 好吧,不是真的。 但这是一个用于编写可以处理大量数据的作业或脚本的框架,并且可以为您在整个计算集群中分配该处理。 基本上,Spark 的工作原理是让您将数据加载到称为弹性分布式数据存储(RDD)的这些大对象中。 它可以根据这些 RDD 自动执行转换和创建动作的操作,您可以将其视为大型数据帧。

它的优点在于,如果有可用的计算机,Spark 会自动并以最佳方式在整个计算机群集中分散该处理过程。 您不再受限于在一台计算机或一台计算机的内存上可以执行的操作。 实际上,您可以将其扩展到机器集群可用的所有处理能力和内存,并且在当今时代,计算非常便宜。 实际上,您可以通过 Amazon 的 Elastic MapReduce 服务之类的工具在群集上租用时间,而仅花几美元就可以在整个计算机群集上租用一些时间,并执行您无法在自己的台式机上运行的工作。

可扩展

Spark 如何可扩展? 好吧,让我们在这里更详细地说明所有工作原理。

它的工作方式是,编写一个驱动程序,它只是一个小脚本,看起来和其他 Python 脚本一样,并且使用 Spark 库实际编写脚本。 在该库中,您定义了所谓的 Spark 上下文,它是您在 Spark 中进行开发时在其中工作的根对象。

从那里开始,Spark 框架将接管并为您分发事务。 因此,如果您在自己的计算机上以独立模式运行,就像我们在接下来的部分中将要做的那样,那么显然,所有这些都将保留在您的计算机上。 但是,如果您在集群管理器上运行,Spark 可以找出并自动利用它。 Spark 实际上具有自己的内置集群管理器,您甚至可以在没有安装 Hadoop 的情况下单独使用它,但是如果您确实有一个 Hadoop 集群可用,它也可以使用它。

Hadoop 不仅限于 MapReduce; 实际上有一个称为 YARN 的 Hadoop 组件将 Hadoop 的整个集群管理部分分离出来。 Spark 可以与 YARN 交互以实际使用它来在该 Hadoop 集群可用的资源之间最佳地分配处理组件。

在集群中,您可能正在运行单个执行器任务。 它们可能在不同的计算机上运行,​​或者可能在同一计算机的不同内核上运行。 他们每个人都有自己的缓存和各自运行的任务。 驱动程序,Spark Context 和群集管理器一起工作以协调所有这些工作并将最终结果返回给您。

这样做的好处是,您要做的就是编写初始的小脚本(驱动程序),该程序使用 Spark 上下文在较高级别上描述您要对该数据进行的处理。 Spark 与您正在使用的集群管理器一起工作,找出了如何进行分发和分发的方法,因此您不必担心所有这些细节。 好吧,如果它不起作用,显然,您可能必须进行一些故障排除,才能确定您是否有足够的资源来处理手头的任务,但是,从理论上讲,这只是魔术。

它很快

Spark 有什么大不了的? 我的意思是,类似 MapReduce 的类似技术已经存在了很长时间。 不过,Spark 速度很快,在网站上,他们声称“在内存中运行作业时,Spark 的速度比 MapReduce 快 100 倍,在磁盘上快 10 倍”。 当然,这里的关键词是“最多”,您的里程可能会有所不同。 我认为我从未见过比 MapReduce 运行速度快得多的东西。 实际上,一些精心设计的 MapReduce 代码仍然可以提高效率。 但我会说,Spark 确实使许多常见操作变得更加容易。 MapReduce 迫使您将内容真正分解为映射器和简化器,而 Spark 则更高一些。 您不必总是花太多的精力去做 Spark 正确的事情。

部分原因导致 Spark 如此之快的另一个原因。 它具有 DAG 引擎,有向无环图。 哇,那是另一个花哨的词。 这是什么意思? Spark 的工作方式是,编写一个描述如何处理数据的脚本,并且您可能具有一个 RDD,该 RDD 基本类似于数据框架。 您可以对其进行某种转换,或对其进行某种操作。 但是,除非您实际对该数据执行操作,否则实际上不会发生任何事情。 此时发生的事情是,Spark 会说:“嗯,好的。所以,这是您要在此数据上得到的最终结果。为了达到这一点,我还需要做其他哪些其他事情,以及达到此目的的最佳方法是什么? 制定实现这一目标的策略?” 因此,在幕后,它将找出拆分处理并分发信息以获得所需最终结果的最佳方法。 因此,这里的关键是,Spark 等待直到您告诉它实际产生结果,然后才真正知道如何产生该结果。 因此,那里的想法很酷,这是提高效率的关键。

还年轻

Spark 是一项非常热门的技术,并且相对来说还很年轻,因此它仍在不断涌现并迅速变化,但是很多大人们正在使用它。 例如,亚马逊声称他们正在使用它,eBay,NASA 的喷气推进实验室,Groupon,TripAdvisor,Yahoo 以及许多其他许多公司也使用了它。 我敢肯定,有很多公司都在使用它,但如果您转到 Spark Apache Wiki 页面

实际上,您可以找到一个列表,其中列出了使用 Spark 解决实际数据问题的知名大公司。 如果您担心自己正在进入最前沿,请不要担心,您与很多非常大的人在一起,他们在生产中使用 Spark 解决实际问题。 在这一点上,它是相当稳定的东西。

不难

这也不难。 您可以选择使用 Python,Java 或 Scala 进行编程,它们都基于我之前所描述的相同概念构建,即,弹性分布式数据集,简称 RDD。 我们将在本章的后续部分中对此进行更详细的讨论。

Spark 的组成

实际上,Spark 具有许多不同的组件。 因此,有一个 Spark Core 可以让您仅使用 Spark Core 函数就可以完成您可以梦想的任何事情,但是在 Spark 之上构建的其他这些功能也很有用。

  • Spark Streaming:Spark Streaming 是一个库,可让您实际实时处理数据。 数据可以连续不断地从服务器上流入服务器,例如,Spark Streaming 可以帮助您永久地实时处理这些数据。
  • Spark SQL:这实际上使您可以将数据视为 SQL 数据库,并在其上实际执行 SQL 查询,如果您已经熟悉 SQL,这将很酷。
  • MLlib:这是本节中要重点介绍的内容。 它实际上是一个机器学习库,可让您执行通用的机器学习算法,而 Spark 则在后台运行,以在整个集群中实际分配该处理。 您可以在比其他更大的数据集上执行机器学习。
  • GraphX:这不是用于制作漂亮的图表。 它是指网络理论意义上的图形。 考虑一下社交网络; 那是一个图的例子。 GraphX 仅具有一些功能,可让您分析信息图的属性。

Python 与 Scala 上的 Spark

当我向人们介绍 Apache Spark 时,有时确实会有些关于使用 Python 的错误,但是有一种让我疯狂的方法。 确实,很多人在编写 Spark 代码时都会使用 Scala,因为这是 Spark 本身开发的。 因此,通过迫使 Spark 将 Python 代码转换为 Scala,然后在一天结束时转换为 Java 解释器命令,会产生一些开销。

但是,Python 容易得多,并且您不需要编译任何东西。 管理依赖关系也容易得多。 您可以真正地将时间集中在算法和您正在做的事情上,而不是真正地构建,运行,编译以及所有这些废话。 另外,显然,到目前为止,这本书一直专注于 Python,在这些讲座中,继续使用我们所学的知识并坚持使用 Python 是有意义的。 以下是这两种语言的优缺点的简要摘要:

Python Scala
无需编译,管理依赖项等。 Scala 可能是 Spark 更受欢迎的选择
较少的编码开销 Spark 是在 Scala 中构建的,因此 Scala 中的编码是 Spark “原生”的。
您已经知道 Python
让我们专注于概念而非新语言

但是,我要说的是,如果您要在现实世界中进行一些 Spark 编程,那么人们很有可能会使用 Scala。 不过,不必太担心它,因为在 Spark 中,Python 和 Scala 代码最终看起来非常相似,因为它们都围绕相同的 RDD 概念。 语法略有不同,但没有太大不同。 如果您能弄清楚如何使用 Python 执行 Spark,那么学习在 Scala 中使用它的步伐并不是很大。 这是两种语言的相同代码的快速示例:

所以,这就是 Spark 本身的基本概念,为什么这么大的事情,以及它如何强大到可以让您在非常大的数据集或任何算法上运行机器学习算法。 现在让我们更详细地讨论它是如何做到的,以及弹性分布式数据集的核心概念。

Spark 和弹性分布式数据集(RDD)

让我们更深入地了解 Spark 的工作方式。 我们将讨论称为 RDD 的弹性分布式数据集。 这是您在 Spark 中编程时使用的核心,我们将提供一些代码片段来尝试使其成为现实。 我们将在这里为您提供 Apache Spark 速成班。 除了我们在接下来的几节中将要介绍的内容之外,它还涉及很多其他内容,但是我只是向您提供一些基本知识,您需要它们才能真正理解这些示例中发生的事情,并希望可以开始并指向正确的方向。

如上所述,Spark 的最基本部分称为弹性分布式数据集(RDD),这将是您用来实际加载和转换并从尝试处理的数据中获取所需答案的对象。 了解这是非常重要的事情。 RDD 中的最后一个字母代表数据集,而到了最后,一切都结束了。 它只是一堆信息行,可以包含几乎所有内容。 但是关键是 R 和第一个 D。

  • 弹性:这是有弹性的,因为 Spark 可以确保如果您正在集群上运行此服务器,并且其中一个集群发生故障,它可以自动从中恢复并重试。 现在,请注意,这种恢复力到目前为止还没有达到。 如果您没有足够的资源来尝试运行该作业,则该作业仍然会失败,因此您必须向其添加更多资源。 它可以从中恢复很多东西。 重试给定任务的次数有限制。 但是,它确实尽最大努力来确保面对不稳定的群集或不稳定的网络,它仍将继续尽最大的努力来完成任务。
  • 分布式:显然,它是分布式的。 使用 Spark 的全部目的是可以将其用于大数据问题,在此您可以实际在整个 CPU 和计算机集群的内存能力之间分配处理。 它可以水平分布,因此您可以根据需要将任意数量的计算机扔给给定问题。 问题越大,计算机越多; 在那里您可以做什么实际上没有上限。

SparkContext对象

您总是通过获取一个SparkContext对象来启动 Spark 脚本,这是体现 Spark 勇气的对象。 它是要给您要处理的 RDD 的原因,因此它是生成您在处理中使用的对象的原因。

您知道,当您实际编写 Spark 程序时,实际上并没有考虑太多SparkContext,但这是在后台运行它们的基础。 如果您在 Spark 外壳中以交互方式运行,则它已经具有一个sc对象,可用于创建 RDD。 但是,在独立脚本中,您将必须显式创建该SparkContext,并且必须注意所使用的参数,因为您实际上可以告诉 Spark 上下文如何分配它们。 我应该利用我拥有的每个核心吗? 我应该在群集上运行还是在本地计算机上独立运行? 因此,在这里可以设置 Spark 运行方式的基本设置。

创建 RDD

让我们看一下实际创建 RDD 的一些小代码片段,我认为所有这些都将变得更加有意义。

使用 Python 列表创建 RDD

以下是一个非常简单的示例:

nums = parallelize([1, 2, 3, 4]) 

如果我只想从普通的旧 Python 列表中创建 RDD,则可以调用 Spark 中的parallelize()函数。 这样会将一系列东西(在这种情况下,只是数字 1、2、3、4)转换为称为nums的 RDD 对象。

这只是从硬编码的内容列表中创建 RDD 的最简单情况。 这个清单可能来自任何地方。 它也不必进行硬编码,但是这种做法破坏了大数据的目的。 我的意思是,如果必须先将整个数据集加载到内存中,然后才能从中创建 RDD,那有什么意义呢?

从文本文件加载 RDD

我还可以从文本文件加载 RDD,该文件可以在任何地方。

sc.textFile("file:///c:/users/frank/gobs-o-text.txt")  

在此示例中,我有一个巨大的文本文件,它是整个百科全书之类的东西。 我正在从本地磁盘读取该文件,但是如果我想将此文件托管在分布式 AmazonS3 存储桶中,也可以使用 s3n;如果我要引用存储在分布式 HDFS 集群中的数据(则代表 hdfs),也可以使用 hdfs Hadoop 分布式文件系统(如果您不熟悉 HDFS)。 当您处理大数据并使用 Hadoop 集群时,通常就是您的数据所在的地方。

该行代码实际上会将该文本文件的每一行都转换为 RDD 中自己的行。 因此,您可以将 RDD 视为行的数据库,并且在此示例中,它将把我的文本文件加载到 RDD 中,其中每一行每一行都包含一行文本。 然后,我可以在该 RDD 中进行进一步处理,以解析或分解该数据中的分隔符。 但这就是我的出发点。

还记得本书前面提到的 ETL 和 ELT 吗? 这是一个很好的示例,说明您实际上可能在哪里将原始数据加载到系统中,并在用于查询数据的系统本身上进行转换。 您可以获取未经处理的原始文本文件,并使用 Spark 的功能将其实际转换为结构化数据。

它还可以与 Hive 之类的内容进行通信,因此,如果您在公司中建立了现有的 Hive 数据库,则可以基于 Spark 上下文创建 Hive 上下文。 多么酷啊? 看一下下面的示例代码:

hiveCtx = HiveContext(sc)  rows = hiveCtx.sql("SELECT name, age FROM users")  

您实际上可以创建一个 RDD(在本例中称为行),该 RDD 是通过在 Hive 数据库上实际执行 SQL 查询而生成的。

创建 RDD 的更多方法

还有更多创建 RDD 的方法。 您可以从 JDBC 连接创建它们。 基本上,任何支持 JDBC 的数据库都可以与 Spark 对话并从中创建 RDD。 Cassandra,HBase,Elasticsearch 以及 JSON 格式,CSV 格式的文件,序列文件目标文件以及一堆其他压缩文件(如 ORC)都可以用来创建 RDD。 我不想深入了解所有这些细节,您可以阅读一本书,并在需要时查找它们,但要点是,无论数据来自何处,无论从何处创建 RDD,在本地文件系统或分布式数据存储上,都非常容易。

同样,RDD 只是一种加载和维护大量数据并同时跟踪所有数据的方法。 但是,从概念上讲,在脚本中,RDD 只是一个包含大量数据的对象。 您无需考虑规模,因为 Spark 为您做到了。

RDD 操作

现在,一旦拥有 RDD,便可以执行两种不同类型的事情,可以进行转换,也可以进行操作。

转换

让我们先谈谈转换。 转换正是它们的本质。 这是一种采用 RDD 并将 RDD 中的每一行转换为基于您提供的函数的新值的方法。 让我们看一下其中的一些函数:

  • map()flatmap()mapflatmap是您最常看到的函数。 两者都将具有您可以梦想的任何功能,将 RDD 的一行作为输入,并且将输出转换后的行。 例如,您可能会从 CSV 文件中获取原始输入,并且您的map操作可能会将该输入获取并基于逗号分隔符将其分解为各个字段,然后返回 Python 列表,该列表以更结构化的格式包含该数据,您可以对其进行进一步处理。 您可以将映射操作链接在一起,因此一个map的输出可能最终会创建一个新的 RDD,然后对其进行另一次转换,依此类推。 再次,关键是,Spark 可以将这些转换分布在整个群集中,因此它可能会包含 RDD 的一部分并在一台机器上进行转换,而 RDD 的另一部分又会在另一台计算机上进行转换。

就像我说的,mapflatmap是您将看到的最常见的转换。 唯一的区别是map仅允许您为每一行输出一个值,而flatmap则允许您实际为给定行输出多个新行。 因此,实际上您可以创建比使用flatmap.时更大的 RDD 或更小的 RDD。

  • filter()filter可用于以下情况:仅创建一个布尔函数,该布尔函数表示“是否应保留此行?是或否”。
  • different()distinct是一种不太常用的转换,仅会返回 RDD 中的不同值。
  • sample():此函数可让您从 RDD 中抽取随机样本
  • union()intersection()subtract()cartesian():您可以执行诸如交集,并集,差集甚至生成 RDD 中存在的每个笛卡尔组合的交集操作。

使用map()

这是一个有关如何在工作中使用map函数的小例子:

rdd = sc.parallelize([1, 2, 3, 4]) 
rdd.map(lambda x: x*x) 

假设我只是从列表 1、2、3、4 中创建了一个 RDD,然后可以使用x的 Lambda 函数调用rdd.map(),该函数在每一行(即该 RDD 的每个值)中都称为x, 然后将函数x乘以x平方。 如果然后我要收集该 RDD 的输出,它将是 1、4、9 和 16,因为它将采用该 RDD 的每个单独的条目并将其平方,然后将其放入新的 RDD 中。

如果您不记得什么是 Lambda 函数,我们在本书的前面部分就已经讨论过了,但是作为回顾,Lambda 函数只是在线定义函数的简写。 因此rdd.map(lambda x: x*x)与单独的函数def squareIt(x): return x*x完全相同,并且说rdd.map(squareIt)

这只是您要作为转换传递的非常简单的函数的简写。 它消除了实际将其声明为自己的单独命名函数的需要。 这就是函数编程的全部思想。 因此,可以说您现在已经了解函数式编程! 但实际上,这只是用于将内联函数定义为map()函数的参数或任何相关转换的简写形式。

操作

当您想实际获得结果时,还可以对 RDD 执行操作。 以下是一些您可以执行的操作示例:

  • collect():您可以在 RDD 上调用collect(),这将带给您一个普通的旧 Python 对象,您可以对其进行遍历并打印出结果,或者将其保存到文件中,或者执行任何操作。
  • count():您也可以调用count(),这将迫使它此时实际上计算 RDD 中的条目数。
  • countByValue():此函数将为您提供该 RDD 中每个唯一值出现多少次的细分。
  • take():您也可以使用take()从 RDD 中采样,这将从 RDD 中随机抽取条目。
  • top()top()将为您提供该 RDD 中的前几项,如果您只是想了解其中的内容以进行调试的话。
  • reduce()reduce()是更强大的操作,它实际上使您可以将相同的公用键值的值组合在一起。 您还可以在键值数据的上下文中使用 RDD。 reduce()函数可让您定义一种将给定键的所有值组合在一起的方式。 它的实质与 MapReduce 非常相似。 reduce()基本上与 MapReduce 中的reducer()类似,并且map()mapper()类似。 因此,实际上很容易进行 MapReduce 作业,并使用这些函数将其转换为 Spark。

还要记住,在您调用动作之前,Spark 实际上不会发生任何事情。 调用了其中一种操作方法后,Spark 便会退出并使用有向无环图进行操作,并实际上计算出获取所需答案的最佳方法。 但是请记住,在该操作发生之前,什么都不会发生。 因此,有时在编写 Spark 脚本时可能会绊倒您,因为您可能在其中有一个打印语句,并且可能希望得到一个答案,但是直到实际执行操作时它才会真正出现。

简而言之就是 Spark 101。 这些是 Spark 编程所需的基础。 基本上,什么是 RDD,以及您可以对 RDD 执行哪些操作。 一旦掌握了这些概念,就可以编写一些 Spark 代码。 现在让我们改变立场,谈谈 MLlib,以及 Spark 中的一些特定功能,这些功能使您可以使用 Spark 进行机器学习算法。

MLlib 简介

幸运的是,当您进行机器学习时,您不必在 Spark 中做困难的事情。 它具有一个称为 MLlib 的内置组件,该组件位于 Spark Core 的顶部,这使使用大量数据集执行复杂的机器学习算法变得非常容易,并将该处理过程分布在整个计算机集群中。 因此,非常令人兴奋的东西。 让我们进一步了解它可以做什么。

某些 MLlib 功能

那么,MLlib 可以做什么? 好吧,其中之一就是特征提取。

您可以按比例做的一件事是词频和逆文档频率,这对于例如创建搜索索引很有用。 实际上,本章后面将通过一个示例。 同样,关键是它可以使用海量数据集在整个集群中做到这一点,因此您可以潜在地为网络创建自己的搜索引擎。 它还提供基本的统计功能,卡方检验,Pearson 或 Spearman 相关性,以及一些更简单的内容,例如最小值,最大值,均值和方差。 这些本身并不十分令人兴奋,但令人兴奋的是,您实际上可以跨庞大的数据集计算方差,均值或其他任何东西或相关分数,并且实际上会将数据集分解为多个块,并在必要时在整个群集中运行。

因此,即使其中的一些操作不是很有趣,但有趣的是它可以运行的规模。 它还可以支持线性回归和逻辑回归等功能,因此,如果您需要将函数拟合到大量数据并将其用于预测,则也可以这样做。 它还支持支持向量机。 我们将在这里介绍一些更高级的算法,一些更高级的算法,并且它们也可以使用 Spark 的 MLlib 扩展到海量数据集。 MLlib 中内置了一个朴素贝叶斯分类器,因此,还记得我们在本书前面构建的垃圾邮件分类器吗? 实际上,您可以使用 Spark 在整个电子邮件系统中执行此操作,然后将其扩展到所需的范围。

决策树,这是我在机器学习中最喜欢的东西之一,也得到了 Spark 的支持,我们实际上将在本章的后面有一个示例。 我们还将研究 K 均值聚类,您可以使用 K 均值和带有 Spark 和 MLlib 的海量数据集进行集群。 甚至主成分分析和 SVD奇异值分解)也可以通过 Spark 进行,我们也将提供一个示例。 最后,MLlib 内置了一个内置的推荐算法,称为最小二乘。 就我个人而言,它的结果有些复杂,就我的口味而言,它有点像一个黑匣子,但是我是一个推荐系统势利小人,所以带一点盐就可以了!

特殊 MLlib 数据类型

使用 MLlib 通常非常简单,只需调用一些库函数即可。 它确实引入了一些新的数据类型。 但是,您需要了解,其中之一就是向量。

向量数据类型

还记得本书前面提到的电影相似之处和电影推荐吗? 向量的一个示例可能是给定用户评分的所有电影的列表。 向量有两种类型:稀疏和密集。 让我们看一个例子。 世界上有很多电影,无论用户是否实际观看,密集的向量实际上代表每个电影的数据。 例如,假设我有一个用户观看过《玩具总动员》,显然我会存储他们对玩具总动员的评分,但是如果他们不看电影《星球大战》,我实际上会存储一个事实,那就是《星球大战》没有数字。 因此,我们最终以密集的向量为所有这些丢失的数据点占用了空间。 稀疏向量仅存储存在的数据,因此不会在丢失的数据上浪费任何内存空间,好的。 因此,它是内部表示向量的一种更紧凑的形式,但是显然在处理时会引入一些复杂性。 因此,如果您知道向量中会有很多丢失的数据,那么这是节省内存的好方法。

LabeledPoint数据类型

还出现了LabeledPoint数据类型,这听起来就是这样,该点具有某种与之相关的标签,以人类可读的方式传达此数据的含义。

Rating数据类型

最后,如果在 MLlib 中使用推荐,则会遇到Rating数据类型。 此数据类型可以接受代表 1-5 或 1-10 的等级(无论一个人可能拥有的星级),并使用该等级自动通知产品推荐。

因此,我认为您终于拥有了开始所需的一切,让我们深入研究一下实际的 MLlib 代码并运行它,然后它将变得更加有意义。

MLlib 的 Spark 中的决策树

好吧,我们实际上使用 Spark 和 MLlib 库构建一些决策树,这是非常酷的事情。 无论您将本书的课程资料放在哪里,我都希望您现在转到该文件夹​​。 确保完全封闭 Canopy 或用于 Python 开发的任何环境,因为我想确保您是从此目录启动的,好吗? 并找到SparkDecisionTree脚本,然后双击该脚本以打开 Canopy:

现在,到目前为止,我们一直在为代码使用 IPython 笔记本,但是您实际上不能将它们与 Spark 很好地结合使用。 使用 Spark 脚本,您需要将它们实际提交到 Spark 基础结构并以非常特殊的方式运行它们,我们将很快看到其工作原理。

探索决策树代码

因此,我们现在只看原始的 Python 脚本文件,而没有 IPython 笔记本内容的任何常规修饰。 让我们来看一下脚本中发生的事情。

我们将慢慢进行介绍,因为这是您在本书中看到的第一个 Spark 脚本。

首先,我们将从pyspark.mllib导入 Spark 的机器学习库中所需的位。

from pyspark.mllib.regression import LabeledPoint 
from pyspark.mllib.tree import DecisionTree 

我们需要LabeledPoint类(这是DecisionTree类所需的数据类型),以及DecisionTree类本身,它们是从mllib.tree导入的。

接下来,您看到的几乎每个 Spark 脚本都将包含以下行,我们在其中导入SparkConfSparkContext

from pyspark import SparkConf, SparkContext 

创建SparkContext对象是需要的,它是您在 Spark 中所做的所有操作的根。

最后,我们将从numpy导入数组库:

from numpy import array 

是的,您仍然可以使用NumPyscikit-learn以及在 Spark 脚本中使用的任何内容。 首先,您只需确保要在其上运行它的每台计算机上都安装了这些库。

如果您在集群上运行,则需要确保这些 Python 库已经以某种方式就位,并且还需要了解 Spark 不会使 Scikit-learn 方法变得不可思议,例如可以魔术地扩展。 您仍然可以在给定的map函数或类似的上下文中调用这些函数,但是它只会在该进程中的一台计算机上运行。 不要过于依赖这些东西,但是对于诸如管理数组之类的简单事情,这完全是一件好事。

创建SparkContext

现在,我们将首先设置SparkContext,并为其提供一个SparkConf配置。

conf = SparkConf().setMaster("local").setAppName("SparkDecisionTree") 

这个配置对象说,我要将主节点设置为“local”,这意味着我只在自己的本地桌面上运行,实际上根本不在集群上运行, 只是要在一个过程中运行。 我还要给它一个应用名称“SparkDecisionTree”,您可以随便叫它什么,弗雷德,鲍勃,蒂姆,只要浮船就可以。 就像您稍后要在 Spark 控制台中查看它一样,它就是该工作的样子。

然后,我们将使用该配置创建SparkContext对象:

sc = SparkContext(conf = conf) 

这为我们提供了可用于创建 RDD 的sc对象。

接下来,我们有很多函数:

# Some functions that convert our CSV input data into numerical 
# features for each job candidate 
def binary(YN): 
    if (YN == 'Y'): 
        return 1 
    else: 
        return 0 

def mapEducation(degree): 
    if (degree == 'BS'): 
        return 1 
    elif (degree =='MS'): 
        return 2 
    elif (degree == 'PhD'): 
        return 3 
    else: 
        return 0 

# Convert a list of raw fields from our CSV file to a 
# LabeledPoint that MLLib can use. All data must be numerical... 
def createLabeledPoints(fields): 
    yearsExperience = int(fields[0]) 
    employed = binary(fields[1]) 
    previousEmployers = int(fields[2]) 
    educationLevel = mapEducation(fields[3]) 
    topTier = binary(fields[4]) 
    interned = binary(fields[5]) 
    hired = binary(fields[6]) 

    return LabeledPoint(hired, array([yearsExperience, employed, 
        previousEmployers, educationLevel, topTier, interned])) 

现在让我们简单介绍一下这些函数,稍后再返回。

导入和清理我们的数据

让我们转到在此脚本中实际执行的 Python 代码的第一位。

我们要做的第一件事是加载此PastHires.csv文件,这与我们在本书前面所做的决策树练习中使用的文件相同。

让我们暂停一下,以提醒自己该文件的内容。 如果您没记错的话,我们有很多应聘者的属性,而且我们有一个字段是否应聘这些人。 我们正在尝试做的是建立一个可以预测的决策树-考虑到这些属性,我们会雇用还是不雇用一个人?

现在,让我们快速浏览PastHires.csv,它将是一个 Excel 文件。

您可以看到 Excel 实际上将其导入到表中,但是如果您要查看原始文本,则会看到它由逗号分隔的值组成。

第一行是每列的实际标题,因此我们上面的内容是在先经验的年限,是否在职的候选人,以前的雇主人数,受教育程度,是否上了最好的学校,他们在学校期间是否有实习机会,最后是我们要预测的目标,他们是否最终获得了工作机会。 现在,我们需要将该信息读入 RDD,以便可以对其进行处理。

让我们回到我们的脚本:

rawData = sc.textFile("e:/sundog-consult/udemy/datascience/PastHires.csv") 
header = rawData.first() 
rawData = rawData.filter(lambda x:x != header) 

我们需要做的第一件事是读入 CSV 数据,我们将丢弃第一行,因为请记住,这是我们的标头信息。 因此,这里有一些技巧。 我们首先从该文件中的每一行导入到原始数据 RDD 中,我可以随意调用它,但是我们将其命名为sc.textFileSparkContext具有textFile函数,该函数将获取文本文件并创建一个新的 RDD,其中,每个条目(RDD 的每一行)都由一行输入组成。

确保将文件的路径更改为实际安装该文件的位置,否则它将无法正常工作。

现在,我将使用first函数从该 RDD 中提取第一行,即第一行。 因此,现在标题 RDD 将包含一个条目,该条目仅是列标题的这一行。 现在,看看上面的代码中发生了什么,我在包含该 CSV 文件中所有信息的原始数据上使用filter,并且正在定义filter函数,该函数仅当该行不等于该初始标题行的内容时,将允许行通过。 我在这里所做的是,我已经获取了原始 CSV 文件,并通过仅允许与第一行不相等的行继续存在而删除了第一行,然后将其返回给rawData RDD 变量再次。 因此,我将使用rawData,过滤掉第一行,并创建一个仅包含数据本身的新rawData。 到目前为止和我在一起? 没那么复杂。

现在,我们将使用map函数。 接下来,我们需要开始从此信息中构造出更多结构。 现在,我的 RDD 的每一行都只是一行文本,它是逗号分隔的文本,但是它仍然只是一个巨大的文本行,我想采用逗号分隔的值列表并将其实际拆分成单个文本字段。 归根结底,我希望将每个 RDD 从一行包含一堆用逗号分隔的信息的文本行转换为 Python 列表,该列表对我拥有的每列信息都有实际的单独字段。 因此,这就是 Lambda 函数的作用:

csvData = rawData.map(lambda x: x.split(",")) 

它调用内置的 Python 函数split,该函数将接受一行输入,并将其分割为逗号字符,然后将其分成每个用逗号分隔的字段的列表。

这个map函数的输出是一个新的 RDD,称为csvData,我在其中传递了一个 Lambda 函数,该函数仅基于逗号将每一行拆分为多个字段。 而且,此时csvData是一个 RDD,它在每一行上包含一个列表,其中每个元素都是源数据中的一列。 现在,我们越来越近了。

事实证明,为了将决策树与 MLlib 一起使用,需要做两件事。 首先,输入必须采用LabeledPoint数据类型的形式,并且本质上必须全部为数字。 因此,我们需要将所有原始数据转换为 MLlib 实际可以使用的数据,这就是我们先前跳过的createLabeledPoints函数所做的。 我们将在一秒钟内解决这个问题,首先是它的调用:

trainingData = csvData.map(createLabeledPoints) 

我们将在csvData上调用一个映射,并将其传递给createLabeledPoints函数,该函数将把每个输入行转换为更接近最终需求的内容。 因此,让我们看一下createLabeledPoints的作用:

def createLabeledPoints(fields): 
    yearsExperience = int(fields[0]) 
    employed = binary(fields[1]) 
    previousEmployers = int(fields[2]) 
    educationLevel = mapEducation(fields[3]) 
    topTier = binary(fields[4]) 
    interned = binary(fields[5]) 
    hired = binary(fields[6]) 

    return LabeledPoint(hired, array([yearsExperience, employed, 
        previousEmployers, educationLevel, topTier, interned])) 

它包含一个字段列表,并且只是为了再次提醒您看起来是什么样子,让我们再次提取.csv Excel 文件:

因此,在这一点上,每个 RDD 条目都有一个字段,它是一个 Python 列表,其中第一个元素是经验的年限,第二个元素是所使用的,依此类推。 这里的问题是我们希望将这些列表转换为“标记点”,并且希望将所有内容转换为数值数据。 因此,所有这些是和否答案都需要转换为一和零。 这些经验水平需要从学位名称转换为某个数字序数值。 例如,也许我们将零值分配给没有学历,例如,一个可以表示 BS,两个可以表示 MS,而三个可以表示 PhD。 同样,所有这些是/否值都必须转换为零和一,因为到了最后,进入决策树的所有内容都必须是数字,这就是createLabeledPoints的作用。 现在,让我们回到代码并运行它:

def createLabeledPoints(fields): 
    yearsExperience = int(fields[0]) 
    employed = binary(fields[1]) 
    previousEmployers = int(fields[2]) 
    educationLevel = mapEducation(fields[3]) 
    topTier = binary(fields[4]) 
    interned = binary(fields[5]) 
    hired = binary(fields[6]) 

    return LabeledPoint(hired, array([yearsExperience, employed, 
        previousEmployers, educationLevel, topTier, interned])) 

首先,它接受我们准备好将其转换为LabeledPointsStringFields列表,其中标签是目标值-此人是否被雇用? 0 或 1 后跟一个包含我们关心的所有其他字段的数组。 因此,这就是创建DecisionTree MLlib类可以使用的LabeledPoint的方式。 因此,在上面的代码中,您看到我们正在将多年的经验从字符串转换为整数值,对于所有的是否字段,我们都将其称为binary函数,该函数是我在顶部定义的代码,但我们尚未讨论:

def binary(YN): 
    if (YN == 'Y'): 
        return 1 
    else: 
        return 0 

它所做的只是将字符yes转换为 1,否则返回 0。因此,Y 将变为 1,N 将变为 0。类似地,我有一个mapEducation函数:

def mapEducation(degree): 
    if (degree == 'BS'): 
        return 1 
    elif (degree =='MS'): 
        return 2 
    elif (degree == 'PhD'): 
        return 3 
    else: 
        return 0 

正如我们前面所讨论的,这只是将不同类型的度转换为序数数值,其方式与是/否字段完全相同。

提醒一下,这是使我们通过这些特征运行的代码行:

trainingData = csvData.map(createLabeledPoints) 

此时,在使用该createLabeledPoints函数映射了我们的 RDD 之后,我们现在有了一个trainingData RDD,而这正是 MLlib 想要构造决策树的原因。

创建测试候选者并建立我们的决策树

让我们创建一个可以使用的测试候选者,以便我们可以使用模型来实际预测是否会雇用新人。 我们要做的是创建一个候选测试,其中包含与 CSV 文件中每个字段相同的值数组:

testCandidates = [ array([10, 1, 3, 1, 0, 0])] 

让我们快速将该代码与 Excel 文档进行比较,以便可以看到数组映射:

同样,我们需要将这些映射回它们的原始列表示形式,以便 10、1、3、1、0、0 表示 10 年的先前工作经验(当前受雇),三个以前的雇主(具有 BS 学士学位), 最好的学校,没有做实习。 如果愿意,我们实际上可以创建一个完整的 RDD,其中包含所有候选人,但是我们现在只做一个。

接下来,我们将使用并行化将该列表转换为 RDD:

testData = sc.parallelize(testCandidates) 

那里没有新东西。 好了,现在让魔术开始下一个代码块:

model = DecisionTree.trainClassifier(trainingData, numClasses=2, 
                    categoricalFeaturesInfo={1:2, 3:4, 4:2, 5:2}, 
                    impurity='gini', maxDepth=5, maxBins=32) 

我们将调用DecisionTree.trainClassifier,这实际上是在构建决策树本身。 我们传入trainingData,它只是一个充满LabeledPoint数组numClasses=2的 RDD,因为基本上我们会做出一个是或否的预测,这个人是否会被录用 ? 下一个参数称为categoricalFeaturesInfo,这是一个 Python 字典,它将字段映射到每个字段中的类别数。 因此,如果您对给定的字段有连续的范围(例如经验的年限),则根本不会在此处指定,但是对于本质上属于类别字段(例如,他们拥有什么程度), 例如,将fieldID3,映射到获得的学位,它有四种不同的可能性:没有学历,BS,MS 和 PhD。 对于所有的是否字段,我们将它们映射到 2 个可能的类别,是否或 0/1 是将它们转换为的类别。

继续进行DecisionTree.trainClassifier调用,我们将在测量熵时使用'gini'杂质度量。 我们的maxDepth为 5,这只是我们要走多远的上限,如果您愿意,可以更大。 最后,maxBins只是一种折衷计算费用的方法,因此,它至少需要至少是每个特征中具有类别的最大数量。 记住,在我们调用一个动作之前,什么都不会发生,所以我们将实际使用该模型对测试候选人进行预测。

我们使用DecisionTree模型,该模型包含根据测试训练数据进行训练的决策树,并告诉我们对测试数据进行预测:

predictions = model.predict(testData) 
print ('Hire prediction:') 
results = predictions.collect() 
for result in results: 
     print (result) 

我们将返回一个预测列表,然后可以进行迭代。 因此,predict返回一个普通的旧 Python 对象,并且是collect可以执行的操作。 让我稍微改一下一下:collect将根据我们的预测返回一个 Python 对象,然后我们可以遍历该列表中的每个项目并打印预测的结果。

我们还可以使用toDebugString打印出决策树本身:

print('Learned classification tree model:') 
print(model.toDebugString()) 

这实际上会打印出它在内部创建的决策树的一点表示,您可以自己动手执行。 所以,这也很酷。

运行脚本

好了,请花些时间,多看一点这个脚本,了解正在发生的事情,但是,如果您准备好了,那就继续并实际运行这个野兽吧。 因此,要做到这一点,您不仅可以直接从 Canopy 中运行它。 我们将转到“工具”菜单,打开一个 Canopy 命令提示符,这将打开一个 Windows 命令提示符,其中包含在 Canopy 中运行 Python 脚本所需的所有必要环境变量。 确保工作目录是您将所有课程资料安装到的目录。

我们需要做的就是调用spark-submit,所以这是一个脚本,您可以从 Python 运行 Spark 脚本,然后是脚本的名称SparkDecisionTree.py。 这就是我要做的。

spark-submit SparkDecisionTree.py 

点击回车,它就会消失。 同样,如果我在集群上执行此操作,并相应地创建了SparkConf,则该代码实际上将分发到整个集群,但是,现在,我们仅在计算机上运行它。 完成后,您应该看到以下输出:

因此,在上面的图像中,您可以在上面放置的测试人员中看到,我们预测该人员将被聘用,而且我还打印了决策树本身,所以这很酷。 现在,让我们再次打开该 Excel 文档,以便将其与输出进行比较:

我们可以逐步了解它的含义。 因此,在输出决策树中,我们实际上以 4 个深度结束,有 9 个不同的节点,并且再次提醒我们,这些不同的字段与什么相关,读取方式如下:If(feature 1 in 0),这意味着如果所雇用的人员为“否”,那么我们将下拉至特征 5。该列表基于零,因此 Excel 文档中的特征 5 为实习职位。 我们可以像这样穿过这棵树:这个人目前没有工作,没有实习,没有过往的工作经验,并且拥有学士学位,我们不会雇用这个人。 然后我们进入其他条款。 如果该人具有较高的学位,我们将根据我们对其进行过培训的数据来雇用他们。 因此,您可以算出这些不同的特征 ID 返回原始源数据的含义,请记住,您始终从 0 开始计数,并据此进行解释。 请注意,在它看到的可能类别的列表中,所有分类特征均以布尔值表示,而连续数据在数值上表示为小于或大于关系。

有了它,使用 Spark 和 MLlib 构建的实际决策树就可以正常工作并且有意义。 很棒的东西。

Spark 中的 K 均值聚类

好吧,让我们看一下在 MLlib 中使用 Spark 的另一个示例,这一次我们将研究 K 均值聚类,就像我们对决策树所做的一样,我们将采用与使用决策树相同的示例 Scikit-learn,我们将改为在 Spark 中进行,因此它实际上可以扩展到海量数据集。 因此,再次,我确保将所有其他内容都排除在外,并且我将进入我的书中,并打开SparkKMeans Python 脚本,让我们研究发生了什么。

好了,再次,我们从一些样板开始。

from pyspark.mllib.clustering import KMeans 
from numpy import array, random 
from math import sqrt 
from pyspark import SparkConf, SparkContext 
from sklearn.preprocessing import scale 

我们将从集群MLlib包中导入KMeans包,我们将从numpy中导入arrayrandom,因为同样,我们可以自由使用您想要的任何东西,这是当天最后的 Python 脚本,并且MLlib需要使用numpy数组作为输入。 我们将要导入sqrt函数和通常的样板内容,我们需要SparkConfSparkContext,几乎每次都需要从pyspark中引入。 我们还将从scikit-learn导入scale函数。 同样,只要确保将scikit-learn安装在要运行此作业的每台计算机上,就可以使用scikit-learn,并且不要以为scikit-learn会神奇地自我扩展,仅仅因为你在 Spark 上运行它。 但是,由于我仅将其用于缩放特征,因此可以。 好吧,让我们继续进行设置。

我将首先创建一个全局变量:

 K=5 

在本示例中,我将使用 k 为 5 的 K 均值聚类,即五个不同的聚类。 然后,我将继续设置仅在我自己的桌面上运行的本地SparkConf

conf = SparkConf().setMaster("local").setAppName("SparkKMeans") 
sc = SparkContext(conf = conf) 

我将把应用的名称设置为SparkKMeans,并创建一个SparkContext对象,然后将其用于创建在本地计算机上运行的 RDD。 现在,我们将跳过createClusteredData函数,然后转到要运行的第一行代码。

data = sc.parallelize(scale(createClusteredData(100, K)))  

  1. 我们要做的第一件事是通过并行化我正在创建的一些虚假数据来创建 RDD,这就是createClusteredData函数的作用。 基本上,我要告诉您创建围绕 K 个质心聚类的 100 个数据点,这与我们在本书前面介绍 K 均值聚类时所看的代码几乎相同。 如果需要复习,请继续阅读该章。 基本上,我们要做的是创建一堆随机质心,通常我们会围绕这些质心分发年龄和收入数据。 因此,我们正在尝试根据人们的年龄和收入对他们进行聚类,并且我们正在构造一些数据点来实现此目的。 这将返回我们假数据的numpy数组。
  2. createClusteredData返回该结果后,我将其命名为scale,这将确保我的年龄和收入处于可比的范围内。 现在,还记得我们研究过的那一部分,您必须记住有关数据标准化的内容吗? 这是重要的示例之一,因此我们正在使用scale对该数据进行归一化,以便从 K 均值中获得良好的结果。
  3. 最后,我们使用parallelize将数组的结果列表并行化为 RDD。 现在,我们的数据 RDD 包含我们所有的假数据。 我们要做的就是比我们的决策树更容易做,就是在训练数据上调用KMeans.train
clusters = KMeans.train(data, K, maxIterations=10, 
        initializationMode="random") 

我们传入想要的群集数,即 K 值,该参数为要执行的处理量设置了上限。 然后,我们告诉它使用默认的 K 均值初始化模式,在该模式中,我们仅对群集进行初始质心抽取,然后开始对其进行迭代,然后返回可使用的模型。 我们将其称为clusters

好了,现在我们可以使用该集群了。

让我们开始打印出每个点的聚类分配。 因此,我们将获取原始数据并使用 Lambda 函数对其进行转换:

resultRDD = data.map(lambda point: clusters.predict(point)).cache() 

该函数只是将每个点转换为根据我们的模型预测的聚类数。 同样,我们只是采用数据点的 RDD。 我们正在调用clusters.predict来确定我们的 K 均值模型将它们分配给哪个集群,然后将结果放入resultRDD中。 现在,我想在这里指出的一件事是上述代码中的此缓存调用。

在执行 Spark 时,重要的一点是,每当您要在 RDD 上调用多个动作时,首先对其进行缓存很重要,因为当您在 RDD 上调用动作时,Spark 会熄灭并弄清楚 DAG,以及如何最佳地获得该结果。

它将关闭并实际执行所有操作以获得该结果。 因此,如果我在同一个 RDD 上调用两个不同的动作,它实际上将最终对该 RDD 进行两次评估,并且如果要避免所有这些额外的工作,则可以缓存 RDD 以确保它不会不止一次重新计算。

这样,我们确保随后的这两个操作可以正确执行操作:

print ("Counts by value:") 
counts = resultRDD.countByValue() 
print (counts) 

print ("Cluster assignments:") 
results = resultRDD.collect() 
print (results) 

为了获得实际结果,我们要做的是使用countByValue,而要做的就是给我们返回一个 RDD,该 RDD 的每个群集中有多少个点。 请记住,resultRDD当前已将每个单独的点映射到最终的集群,因此现在我们可以使用countByValue来计算每个给定集群 ID 所能看到的值。 然后,我们可以轻松地将该列表打印出来。 实际上,通过在其上调用collect,我们也可以查看该 RDD 的原始结果,这将使我获得每个点群集的分配,我们可以打印出所有这些点。

在设定的平方误差总和内(WSSSE)

现在,我们如何衡量集群的质量? 好吧,一个指标被称为平方误差的内部设置总和,哇,听起来很花哨! 这个词太大了,我们需要简称 WSSSE。 所有这些,我们看一下每个点到它的质心的距离,即每个聚类中的最后一个质心,取该误差的平方并将其求和为整个数据集。 它只是每个点与质心的距离的度量。 显然,如果我们的模型中存在很多错误,则它们往往会与可能应用的质心相距甚远,因此,例如,我们需要更高的 K 值。 我们可以继续计算该值,并使用以下代码将其打印出来:

def error(point): 
    center = clusters.centers[clusters.predict(point)] 
    return sqrt(sum([x**2 for x in (point - center)])) 

WSSSE = data.map(lambda point: error(point)).reduce(lambda x, y: x + y) 
print("Within Set Sum of Squared Error = " + str(WSSSE)) 

首先,我们定义此error函数,该函数计算每个点的平方误差。 它只是计算从点到每个聚类的质心中心的距离并将其求和。 为此,我们获取源数据,在其上调用一个 Lambda 函数,该函数实际上计算每个质心中心点的误差,然后可以在此处将不同的操作链接在一起。

首先,我们调用map计算每个点的误差。 然后,为了得到代表整个数据集的最终总数,我们在该结果上调用reduce。 因此,我们正在执行data.map以计算每个点的误差,然后执行reduce以获取所有这些误差并将它们加在一起。 这就是 Lambda 小函数的作用。 这基本上是一种幻想的表达方式,“我希望您将这个 RDD 中的所有内容加起来成为一个最终结果。” reduce会同时提取整个 RDD,一次两件事,然后使用您提供的任何函数将它们组合在一起。 我上面提供的函数是“将要合并的两行合并起来。”

如果我们在 RDD 的每个条目中都这样做,那么最终将得出最终的总计。 总结一堆值似乎有些费解,但是通过这种方式,我们可以确保我们可以根据需要实际分发此操作。 实际上,我们最终可以在一台计算机上计算一个数据的总和,然后在另一台计算机上计算另一数据的总和,然后将这两个总和相结合,以将它们组合在一起成为最终结果。 此reduce函数表示,如何从此操作中获取任何两个中间结果,并将它们组合在一起?

同样,如果您希望它沉入其中,请花一点时间凝视一下更长的时间。在这里并没有什么特别的幻想,但是有一些重要的要点:

  • 如果您要确保不要对要使用多次的 RDD 进行不必要的重新计算,则介绍了缓存的使用。
  • 我们介绍了reduce函数的使用。
  • 在这里,我们还有几个有趣的映射器函数,因此在此示例中有很多东西可以学习。

归根结底,它将仅进行 K 均值聚类,因此让我们继续运行它。

运行代码

转到“工具”菜单的“Canopy 命令提示符”,然后键入:

spark-submit SparkKMeans.py  

点击回车,它就会消失。 在这种情况下,您可能需要等待片刻才能使输出出现在您面前,但是您应该看到类似以下内容:

很好用! 因此请记住,我们要求的输出首先是每个集群中最终获得多少点的计数。 因此,这告诉我们集群 0 的空间为 21 点,集群 1 的空间为 20 点,依此类推。 最终分布均匀,所以这是一个好兆头。

接下来,我们打印出每个点的聚类分配,并且,如果您还记得的话,构成该数据的原始数据是按顺序进行的,因此,将所有 3 个和所有 1 个一起查看实际上是一件好事 ,以及所有 4s 在一起,看起来好像开始与 0s 和 2s 有点混淆了,但是总的来说,它似乎非常好地发现了我们最初创建数据的集群。

最后,我们计算了 WSSSE 指标,在本示例中为 19.97。 因此,如果您想稍微尝试一下,我鼓励您这样做。 您可以看到随着增加或减小 K 值,该误差度量会发生什么,并思考为什么会这样。 如果不对所有数据进行归一化,您还可以尝试发生什么,这是否会对您的结果产生有意义的影响? 这实际上是一件重要的事情吗? 您还可以在模型本身上使用maxIterations参数进行试验,从而很好地了解实际对最终结果的影响以及它的重要性。 因此,可以随意使用它并进行实验。 这是使用 MLlib 和 Spark 以可扩展方式完成的 K 均值聚类。 很酷的东西。

TF-IDF

因此,我们最后的 MLlib 示例将使用称为词频逆文档频率或 TF-IDF 的东西,它是许多搜索算法的基本组成部分。 和往常一样,这听起来很复杂,但没有听起来那么糟糕。

因此,首先让我们讨论 TF-IDF 的概念,以及如何使用它来解决搜索问题。 我们实际上要使用 TF-IDF 的方法是使用 MLlib 中的 Apache Spark 为维基百科创建一个基本的搜索引擎。 那有多棒? 让我们开始吧。

TF-IDF 代表词频和文档逆向频率,这基本上是两个指标,它们在进行搜索和弄清楚给定单词与文档的相关性(给定大量文档)时紧密相关。 因此,例如,对于维基百科上的每篇文章,可能都会有一个词频与之相关,对于该文档中出现的每个单词,互联网上的每个页面都可能会与词频相关联。 听起来很花哨,但是,正如您将看到的,这是一个非常简单的概念。

  • 所有词频表示给定文档中给定单词出现的频率。 那么,在一个网页内,在一篇维基百科文章内,在任何内容内,给定单词在该文档中的普遍程度如何? 您知道,该单词在该文档中所有单词中的出现率是多少? 而已。 这就是所有单词的频率。
  • 文档频率是相同的想法,但是这次是该单词在整个文档库中的频率。 因此,这个词多久出现在我拥有的所有文档,所有网页,维基百科上的所有文章中,无论如何。 例如,athe之类的常用词的文档出现频率很高,我希望它们的单词出现频率也很高,但这并不一定意味着它们与给定文档相关 。

您可以看到我们将如何发展。 因此,假设给定单词的词频很高,而文档词频很低。 这两件事的比例可以使我对该词与文档的相关性有所了解。 因此,如果我看到某个单词在给定文档中经常出现,但在文档的整体空间中却很少出现,那么我知道这个单词可能对该特定文档传达了一些特殊含义。 它可能传达了本文档的实际含义。

因此,这就是 TF-IDF。 它仅代表词频 x 反向文档频率,这只是表达词频高于文档频率的一种奇妙方式,也只是一种表达该单词在本文档中出现的频率与在文档中出现的频率相比的奇特方式。 整个文件? 就这么简单。

实践中的 TF-IDF

在实践中,我们如何使用它有一些细微差别。 例如,我们使用反向文档频率的实际对数而不是原始值,这是因为现实中的词频倾向于呈指数分布。 因此,考虑到它们的整体流行性,通过记录日志,我们最终得出的词权重稍好一些。 这种方法有一些局限性,很明显,一个是我们基本上假设一个文档不过是一堆单词,我们假设单词本身之间没有任何关系。 而且,显然并非总是如此,将其解析出来可能是工作的一个很好的部分,因为您必须处理同义词和各种时态的单词,缩写,大写字母,拼写错误等问题。 这回到了清理数据的想法,这是您作为数据科学家的大部分工作,在处理自然语言处理内容时尤其如此。 幸运的是,那里有一些库可以帮助您解决此问题,但这是一个实际的问题,它将影响结果的质量。

我们与 TF-IDF 一起使用的另一种实现技巧是,为了节省空间并提高效率,我们没有将实际的字符串单词及其词频和反文档频率存储起来,而是将每个单词映射为一个数值,即一个哈希值, 称它为。 我们的想法是,我们有一个函数,可以接受任何单词,查看其字母,然后以某种相当均匀的方式将其分配给范围内的一组数字。 这样,我们可以不使用单词represented,而是将其哈希值指定为 10,然后从现在开始可以将单词represented称为“10”。 现在,如果您的哈希值空间不够大,您最终可能会用相同的数字表示不同的单词,这听起来比实际情况要糟糕。 但是,您知道,您想确保自己具有相当大的哈希空间,以免发生这种情况。 这些被称为哈希冲突。 它们可能会引起问题,但实际上,人们通常只用英语使用这么多的单词。 您可以逃脱 100,000 左右的罚款。

大规模地做到这一点是困难的部分。 如果要在整个维基百科上执行此操作,则必须在群集上运行此操作。 但是为了争辩,我们现在仅使用一小部分维基百科数据在自己的桌面上运行此文件。

使用 TF-IDF

我们如何将其变成实际的搜索问题? 一旦有了 TF-IDF,我们就可以衡量每个单词与每个文档的相关性。 我们该怎么办? 嗯,您可以做的一件事是为我们在整个文档正文中遇到的每个单词计算 TF-IDF,然后假设我们要搜索给定项目,给定单词。 假设我们要搜索“我的一组维基百科文章中与葛底斯堡最相关的维基百科文章?” 我可以按照葛底斯堡的 TF-IDF 分数对所有文档进行排序,并获得最佳结果,而这就是我对葛底斯堡的搜索结果。 而已。 只需使用您的搜索词,计算 TF-IDF,然后获得最高的结果。 而已。

显然,在现实世界中,要搜索的内容不止这些。 Google 有许多人致力于解决这个问题,实际上它的方法更加复杂,但这实际上将为您提供有效的搜索引擎算法,并产生合理的结果。 让我们继续前进,看看它是如何工作的。

使用 Spark MLlib 搜索维基百科

我们将使用 MLlib 中的 Apache Spark 为一个维基百科构建实际的工作搜索算法,并且将用不到 50 行的代码来完成所有这些工作。 这可能是我们整本书中最酷的事情!

进入您的课程资料并打开TF-IDF.py脚本,这应该使用以下代码打开 Canopy:

现在,退后一会儿,让它沉入其中,因为我们实际上是在创建一个有效的搜索算法,以及一些在不到 50 行代码中使用它的示例,并且它是可扩展的。 我可以在集群上运行它。 太神奇了。 让我们逐步看一下代码。

导入语句

我们将从导入在 Python 中运行的任何 Spark 脚本所需的SparkConfSparkContext库开始,然后使用以下命令导入HashingTFIDF

from pyspark import SparkConf, SparkContext 
from pyspark.mllib.feature import HashingTF 
from pyspark.mllib.feature import IDF 

因此,这就是在我们的文档中计算词频(TF)和反向文档频率(IDF)的原因。

创建初始 RDD

我们将从创建样板 Spark 的东西开始,它创建一个本地SparkConfiguration和一个SparkContext,然后可以从中创建初始的 RDD。

conf = SparkConf().setMaster("local").setAppName("SparkTFIDF") 
sc = SparkContext(conf = conf) 

接下来,我们将使用SparkContextsubset-small.tsv创建一个 RDD。

rawData = sc.textFile("e:/sundog-consult/Udemy/DataScience/subset-small.tsv") 

这是一个包含制表符分隔值的文件,它代表维基百科文章的一小部分。 同样,无论您在何处安装本书的课程资料,都需要根据前面的代码更改路径。

这给了我一个 RDD,其中每个文档都位于 RDD 的每一行中。 tsv文件在每一行上包含一个完整的维基百科文档,并且我知道这些文档中的每一个都被拆分为表格字段,其中包含有关每篇文章的各种元数据。

我要做的下一件事是将它们分开:

fields = rawData.map(lambda x: x.split("\t")) 

我将基于它们的制表符分隔符将每个文档拆分为一个 Python 列表,并创建一个新的fields RDD,它代替原始输入数据,现在包含该输入数据中每个字段的 Python 列表。

最后,我将映射该数据,获取每个字段列表,提取第三个字段x[3],我碰巧知道它是文章本身,实际文章文本,而我将反过来基于空格进行拆分:

documents = fields.map(lambda x: x[3].split(" ")) 

x[3]的作用是从每个维基百科文章中提取文本的正文,并将其拆分为单词列表。 我的新documents RDD 每个文档都有一个条目,并且该 RDD 中的每个条目都包含该文档中出现的单词列表。 现在,我们实际上知道稍后在评估结果时如何称呼这些文档。

我还将创建一个存储文档名称的新 RDD:

documentNames = fields.map(lambda x: x[1]) 

所有操作都采用相同的fields RDD,并使用此map函数提取文档名称,而我恰好知道该名称在第一字段中。

因此,我现在有两个 RDD,documents包含每个文档中出现的单词列表,documentNames包含每个文档的名称。 我也知道它们的顺序相同,因此稍后我可以将它们组合在一起以查找给定文档的名称。

创建和转换HashingTF对象

现在,魔术发生了。 我们要做的第一件事是创建一个HashingTF对象,我们将传入 100,000。 这意味着我将每个单词散列为 100,000 个数值之一:

hashingTF = HashingTF(100000)  

与其在内部将单词表示为字符串(效率很低),不如将其尽可能地均匀地分配给唯一的哈希值。 我给它最多 100,000 个哈希值供您选择。 基本上,这是在一天结束时将单词映射到数字。

接下来,我将使用我的实际 RDD 文档调用hashingTF上的transform

tf = hashingTF.transform(documents) 

这将把我在每个文档中的单词列表转换为哈希值列表,即代表每个单词的数字列表。

实际上,这实际上表示为稀疏向量,以节省更多空间。 因此,我们不仅将所有单词都转换为数字,而且还删除了所有丢失的数据。 如果单词没有出现在文档中,而您没有存储单词没有显式出现的事实,则可以节省更多空间。

计算 TF-IDF 分数

为了实际计算每个文档中每个单词的 TF-IDF 分数,我们首先缓存此tf RDD。

tf.cache() 

我们这样做是因为我们将不止一次使用它。 接下来,我们使用IDF(minDocFreq=2),这意味着我们将忽略至少出现两次的任何单词:

idf = IDF(minDocFreq=2).fit(tf) 

我们在tf上调用fit,然后在下一行中在tf上调用transform

tfidf = idf.transform(tf) 

我们最终得到的是每个文档中每个单词的 TF-IDF 分数的 RDD。

使用维基百科搜索引擎算法

让我们尝试使用该算法。 让我们尝试为Gettysburg一词查找最佳文章。 如果您不熟悉美国历史,那是亚伯拉罕·林肯发表著名演讲的地方。 因此,我们可以使用以下代码将“葛底斯堡”一词转换为其哈希值:

gettysburgTF = hashingTF.transform(["Gettysburg"]) 
gettysburgHashValue = int(gettysburgTF.indices[0]) 

然后,我们将针对该哈希值的 TF-IDF 分数提取到每个文档的新 RDD 中:

gettysburgRelevance = tfidf.map(lambda x: x[gettysburgHashValue])  

这是从每个文档映射的哈希值中提取葛底斯堡的 TF-IDF 分数,并将其存储在此gettysburgRelevance RDD 中。

然后,我们将其与documentNames组合在一起,以便可以看到结果:

zippedResults = gettysburgRelevance.zip(documentNames)  

最后,我们可以打印出答案:

print ("Best document for Gettysburg is:") 
print (zippedResults.max()) 

运行算法

因此,让我们开始运行,看看会发生什么。 像往常一样,要运行 Spark 脚本,我们不会只点击播放图标。 我们必须转到“工具”>“Canopy 命令提示符”。 在打开的命令提示符中,我们将输入spark-submit TF-IDF.py,然后关闭。

我们要求它分拆相当多的数据,即使它只是维基百科的一小部分,它仍然是相当多的信息,因此可能需要一段时间。 让我们看看获得葛底斯堡最佳文件匹配的结果,哪些文件的 TF-IDF 得分最高?

是亚伯拉罕·林肯! 那不是很棒吗? 我们只用了几行代码就制作了一个真正有效的搜索引擎。

在那里,有了 MLlib 和 TF-IDF 中使用 Spark 的一小部分维基百科的实际有效搜索算法。 好处是,如果我们有足够大的集群可以运行它,那么我们可以按需扩展到整个维基百科。

希望我们对 Spark 有兴趣,并且您可以看到如何将其应用于以分布式方式解决非常复杂的机器学习问题。 因此,这是一个非常重要的工具,我想确保您在不至少了解如何将 Spark 应用于大数据问题的概念的前提下,不阅读本书中的数据科学。 因此,当您需要超越一台计算机可以完成的工作时,请记住,Spark 可供您使用。

将 Spark 2.0 DataFrame API 用于 MLlib

本章最初是为 Spark 1 编写的,因此让我们讨论一下 Spark 2 的新增功能以及 MLlib 现在具有哪些新功能。

因此,Spark 2 的主要优点是它们越来越趋向于数据帧和数据集。 数据集和数据帧有时可以互换使用。 从技术上讲,数据帧是行对象的数据集,它们有点像 RDD,但是唯一的区别是,RDD 仅包含非结构化数据,而数据集具有已定义的架构。

数据集会提前准确知道每一行中存在哪些信息列,以及这些信息的类型。 因为它可以提前知道该数据集的实际结构,所以可以更有效地优化事物。 它还使我们可以将此数据集的内容视为一个小型微型数据库,实际上,如果它位于集群上,则是一个非常大的数据库。 这意味着我们可以做一些事情,例如在上面执行 SQL 查询。

这将创建一个更高级别的 API,通过该 API 我们可以查询和分析 Spark 集群上的大量数据集。 这是很酷的东西。 它速度更快,有更多的优化机会,并且具有更易于使用的高级 API。

Spark 2.0 MLlib 如何工作

在 Spark 2.0 中,MLlib 正在将数据帧作为其主要 API。 这是未来的方式,所以让我们看一下它是如何工作的。 我已经继续并在 Canopy 中打开了SparkLinearRegression.py文件,如下图所示,所以让我们逐步了解一下:

如您所见,一方面,我们使用ml而不是MLlib,这是因为新的基于数据帧的 API 在那里。

实现线性回归

在此示例中,我们要做的是实现线性回归,而线性回归只是将一条线拟合到一组数据的一种方式。 在本练习中,我们要做的是获取一堆二维的伪造数据,并尝试使用线性模型将一条线拟合到该数据上。

我们将数据分为两组,一组用于构建模型,另一组用于评估模型,并且我们将比较此线性模型在实际预测实际值方面的表现。 首先,在 Spark 2 中,如果要使用SparkSQL接口并使用数据集进行操作,则必须使用SparkSession对象而不是SparkContext。 要进行设置,请执行以下操作:

spark = SparkSession.builder.config("spark.sql.warehouse.dir", "file:///C:/temp").appName("LinearRegression").getOrCreate() 

请注意,仅在 Windows 和 Spark 2.0 中才需要中间位。 老实说,它可以解决一些小错误。 因此,如果您使用的是 Windows,请确保您具有C:/temp文件夹。 如果要运行此程序,请根据需要立即创建。 如果您不在 Windows 上,则可以删除整个中间部分以保留:spark = SparkSession.builder.appName("LinearRegression").getOrCreate()

好的,所以您可以说spark,给它一个appNamegetOrCreate()

这很有趣,因为一旦创建了 Spark 会话,如果它意外终止,那么您实际上可以在下次运行它时从中恢复。 因此,如果我们有一个检查点目录,则它实际上可以使用getOrCreate从上次中断的地方重新启动。

现在,我们将使用课程材料中随附的此regression.txt文件:

inputLines = spark.sparkContext.textFile("regression.txt")  

那只是一个文本文件,具有两列以逗号分隔的值,它们只是线性相关数据(或多或少随机)的两列。 它可以代表您想要的任何东西。 假设它代表了身高和体重。 因此,第一列可能代表高度,第二列可能代表重量。

在机器学习的术语中,我们谈论标签和特征,其中标签通常是您要预测的事物,特征是用于进行预测的数据的一组已知属性。

在此示例中,也许高度是标签,特征是权重。 也许我们正在尝试根据您的体重预测身高。 可以是任何东西,没关系。 所有这些都归一化为介于 -1 和 1 之间的数据。任何地方的数据规模都没有任何实际意义,您可以假装它确实意味着您想要的任何东西。

要将其与 MLlib 一起使用,我们需要将数据转换为期望的格式:

data = inputLines.map(lambda x: x.split(",")).map(lambda x: (float(x[0]), Vectors.dense(float(x[1]))))  

我们要做的第一件事是使用map函数将数据拆分,该函数仅将每行拆分为列表中的两个不同值,然后将其映射为 MLlib 期望的格式。 这将是一个浮点标签,然后是特征数据的密集向量。

在这种情况下,我们只有一点特征数据即权重,因此我们拥有的向量中只有一件事,但是即使只是一件事,MLlib 线性回归模型也需要一个密集的向量。 这就像旧版 API 中的labeledPoint,但是我们必须在这里用困难的方式做到这一点。

接下来,我们需要为这些列实际分配名称。 这是执行此操作的语法:

colNames = ["label", "features"] 
df = data.toDF(colNames) 

我们将告诉 MLlib,结果 RDD 中的这两列实际上对应于标签和特征,然后我可以将该 RDD 转换为DataFrame对象。 此时,我有一个实际的数据帧,或者,如果有的话,我将有一个包含两个列,标签和特征的数据集,其中标签是浮点高度,而特征列是浮点权重的密集向量。 这是 MLlib 要求的格式,而 MLlib 可能对此有些挑剔,因此请务必注意这些格式。

现在,就像我说的,我们将数据分成两半。

trainTest = df.randomSplit([0.5, 0.5]) 
trainingDF = trainTest[0] 
testDF = trainTest[1] 

我们将在训练数据和测试数据之间进行 50/50 的分配。 这将返回两个数据帧,一个将用于实际创建我的模型,另一个将用于评估我的模型。

接下来,我将在此处设置一些标准参数来创建实际的线性回归模型。

lir = LinearRegression(maxIter=10, regParam=0.3, elasticNetParam=0.8) 

我们将调用lir = LinearRegression,然后将模型与我保留用于训练的数据集(训练数据帧)相匹配:

model = lir.fit(trainingDF) 

这给了我一个可以用来进行预测的模型。

让我们继续做。

fullPredictions = model.transform(testDF).cache() 

我将调用model.transform(testDF),这将根据我的测试数据集中的权重来预测高度。 我实际上有已知的标签,即实际的正确高度,这将向该数据帧添加一个称为预测的新列,该列具有基于该线性模型的预测值。

我将缓存这些结果,现在我可以提取它们并将它们进行比较。 因此,让我们像在 SQL 中一样使用select来抽出预测列,然后我将实际转换该数据帧并从中抽出 RDD,并使用它将其映射到一个普通的旧 RDD,在这种情况下,它充满浮点高度:

predictions = fullPredictions.select("prediction").rdd.map(lambda x: x[0]) 

这些是预测的高度。 接下来,我们将从标签列中获取实际高度:

labels = fullPredictions.select("label").rdd.map(lambda x: x[0]) 

最后,我们可以将它们重新拉在一起,并排打印出来,看看效果如何:

predictionAndLabel = predictions.zip(labels).collect() 

for prediction in predictionAndLabel: 
    print(prediction) 

spark.stop() 

这是一种复杂的操作方式。 我这样做是为了与前面的示例更加一致,但是一种更简单的方法是实际上选择预测并将其一起标记到单个 RDD 中,该 RDD 将这两列映射在一起,然后不必压缩它们,但是无论哪种方式都有效。 您还将注意到,在最后,我们需要停止 Spark 会话。

因此,让我们看看它是否有效。 让我们转到“工具”,“Canopy 命令提示符”,然后输入spark-submit SparkLinearRegression.py,看看会发生什么。

使用数据集实际运行这些 API 会有更多的前期时间,但是一旦开始使用,它们就会非常快。 好吧,那里有。

在这里,我们并排显示了实际值和预测值,您可以看到它们还不错。 他们往往或多或少地在同一个球场上。 您已经有了一个使用 Spark 2.0 的线性回归模型,该模型使用了新的基于 MLlib 的基于数据帧的 API。 越来越多地,您将在 Spark 中使用 MLlib 来使用这些 API,因此请确保尽可能选择这些 API。 好的,这就是 Spark 中的 MLlib,它是一种在整个集群中实际分配海量计算任务的方法,用于在大数据集上进行机器学习。 因此,具有良好的技能。 让我们继续。

总结

在本章中,我们从安装 Spark 开始,然后继续深入介绍 Spark,同时了解 Spark 如何与 RDD 结合使用。 在探索不同的操作时,我们还介绍了创建 RDD 的各种方法。 然后,我们介绍了 MLlib,并逐步介绍了 Spark 中决策树和 K 均值聚类的一些详细示例。 然后,我们完成了使用 TF-IDF 仅用几行代码创建搜索引擎的技巧。 最后,我们研究了 Spark 2.0 的新功能。

在下一章中,我们将介绍 A/B 测试和实验设计。

十、测试与实验设计

在本章中,我们将看到 A/B 测试的概念。 我们将通过 T 检验,T 统计量和 p 值,使用所有有用的工具来确定结果是真实的还是随机变化的结果。 我们将深入研究一些真实的示例,并使用一些 Python 代码来处理问题,并计算 T 统计量和 p 值。

接下来,我们将研究您得出结论之前应该进行多长时间的实验。 最后,我们将讨论可能损害实验结果并可能导致您得出错误结论的潜在问题。

我们将涵盖以下主题:

  • A/B 测试概念
  • T 检验和 p 值
  • 使用 Python 测量 T 统计量和 p 值
  • 确定进行实验的时间
  • A/B 测试陷阱

A/B 测试概念

如果您是网络公司的数据科学家,则可能会要求您花一些时间分析 A/B 测试的结果。 这些基本上是网站上的受控实验,用于衡量给定更改的影响。 因此,让我们谈谈 A/B 测试是什么以及它们如何工作。

A/B 测试

如果您要成为一家大型科技网站公司的数据科学家,那么您肯定会参与其中,因为人们需要进行实验以尝试在网站上进行其他操作并衡量其结果, 实际上,这并不像大多数人想象的那么简单。

什么是 A/B 测试? 好吧,这是一个通常在网站上运行的受控实验,它也可以应用于其他环境,但是通常我们谈论的是网站,并且我们将测试该网站的某些更改的效果, 与以前相比。

基本上,您有一个对照组查看旧网站的人,以及一个测试组查看该网站更改的人,其目的是在这两组之间进行比较来衡量行为差异,并使用该数据来实际确定此更改是否有益。

例如,我拥有一家拥有网站的公司,我们向人们授权软件,现在我有一个漂亮,友好的橙色按钮,人们在想购买许可证时可以单击该按钮,如下图左图所示 。 但是,如果将按钮的颜色更改为蓝色(如右图所示),会发生什么?

因此,在此示例中,如果我想确定蓝色是否会更好。 我怎么知道?

我的意思是,从直觉上讲,也许可以更吸引人们的注意力,或者从直觉上讲,也许人们更习惯于看到橙色的购买按钮,并且更有可能点击该按钮,我可以任意选择旋转,对吗? 因此,我自己的内部偏见或成见并不重要。 重要的是人们如何对我实际网站上的更改做出反应,这就是 A/B 测试的目的。

A/B 测试会将人们分成看到橙色按钮的人们和看到蓝色按钮的人们,然后我可以衡量这两组之间的行为以及它们之间的差异,并应该基于该数据决定按钮的颜色。

您可以使用 A/B 测试来测试各种事物。 这些包括:

  • 设计更改:这些可以是按钮颜色,按钮位置或页面布局的更改。

  • UI 流程:因此,也许您实际上是在改变购买渠道的工作方式以及人们在您的网站上结帐的方式,并且您实际上可以衡量其效果。

  • 算法更改:让我们考虑在第 6 章,“推荐系统”中讨论过的电影推荐示例。 也许我想测试一种算法与另一种算法。 我真正关心的不是去依靠错误指标和我进行训练测试的能力,而是要去驱动购买或租赁,或者本网站上的任何内容。

    • A/B 测试可以让我直接测量该算法对我真正关心的最终结果的影响,而不仅仅是我预测别人已经看过的电影的能力。
    • 您还可以梦想得到的任何其他东西,实际上,影响用户与您的网站交互方式的任何更改都值得测试。 也许甚至可以使网站更快,或者可以是任何东西。
  • 定价更改:这有点争议。 您知道,从理论上讲,您可以使用 A/B 测试对不同的价格点进行试验,看看它是否确实增加了交易量以抵消价格差或其他原因,但请谨慎使用。

    • 如果客户因别人无缘无故地获得比别人更好的价格而发狂,那么他们对您不会很满意。 请记住,进行定价实验可能会带来负面影响,而您不想处于这种情况。

测量 A/B 测试的转换

在网站上设计实验时,需要弄清楚的第一件事是您要针对什么进行优化? 您真正希望通过此更改实现什么? 这并不总是很明显的事情。 也许这是人们花费的金额,收入的金额。 好吧,我们讨论了使用支出量方面存在差异的问题,但是如果您有足够的数据,仍然可以多次在该指标上达成共识。

但是,也许这不是您实际要优化的。 也许您实际上是在故意亏本出售某些物品,只是为了夺取市场份额。 您的定价策略所涉及的复杂性不仅仅是收入。

也许您真正想衡量的是利润,而这可能是一件非常棘手的事情,因为很多事情都会削减给定产品可能赚到的钱,而这些事情可能并不总是显而易见的。 再说一次,如果您有损失负责人,则此实验将抵消那些应有的损失。 也许您只是关心增加网站上的广告点击量,或订购数量以减少差异,也许人们对此表示满意。

最重要的是,您必须与正在测试的区域的企业所有者交谈,并弄清他们正在针对哪些领域进行优化。 他们在衡量什么? 他们的成功基于什么? 他们的关键表现指标是什么?或者 NBA 想称其为什么? 并确保我们正在衡量对他们而言至关重要的事物。

您也可以一次测量多个事物,而不必选择一个事物,实际上可以报告许多不同事物的影响:

  • 收入
  • 利润
  • 点击次数
  • 广告观看次数

如果这些事情都一起朝着正确的方向发展,那么这是一个非常有力的信号,表明这一变化产生的积极影响远不止一种。 那么,为什么要限制自己使用一个指标呢? 只要确保您知道哪个最重要,这将是您提前成功完成此实验的标准。

如何归因转化

要注意的另一件事是将转化归因于下游的更改。 如果您尝试执行的操作没有在用户体验到您正在测试的东西时立即发生,那么事情就会变得有些模糊。

假设我更改了页面 A 上按钮的颜色,然后用户转到页面 B 并执行其他操作,最终从页面 C 购买了东西。

那么,谁能从这次购买中获得信贷? 是 A 页,还是 B 页,或介于两者之间? 我是否会根据该人为获得该转化操作而获得的点击次数来折算该转化的功劳? 看到更改后,我是否只是立即放弃未执行的任何转换操作? 这些都是复杂的事情,很容易通过弄清楚您如何解释转换和要测量的更改之间的这些不同距离而产生误导性结果。

方差是你的敌人

您真正需要内部化的另一件事是,在运行 A/B 测试时,差异就是您的敌人。

人们常犯的一个不知道自己正在使用数据科学做什么的错误是,他们将在网页上进行测试,无论是蓝色按钮还是橙色按钮,无论运行什么,并运行一周, 每个组的平均支出。 然后他们说:“哦,看!用蓝色按钮的人比用橙色按钮的人平均要花一美元;蓝色很棒,我喜欢蓝色,现在我要在整个网站上放蓝色!”

但是,实际上,他们可能只看到购买时的随机变化。 他们的样本量不足,因为人们购买的物品不多。 您获得了很多意见,但相比之下,您的网站上可能没有很多购买,而且这些购买金额可能会有很多差异,因为不同的产品价格不同。

因此,您很容易最终做出错误的决定,从长远来看最终会花费公司金钱,如果您不了解差异对这些结果的影响,那么您就可以赚取公司金钱。 在本章的后面,我们将讨论一些用于度量和核算的主要方法。

您需要确保您的企业主了解这是一个重要的影响,您需要在进行 A/B 测试或您在网络上进行的任何实验之后做出业务决策之前,先进行量化和了解。

现在,有时您需要选择方差较小的转化指标。 网站上的数字可能只是意味着您必须进行数年的实验才能获得基于收入或支出金额等方面的显著结果。

有时,如果您查看的是多个指标(例如订单金额或订单数量),而与之相关的差异较小,则可能会先看到订单数量上的信号,然后再看到收入上的信号。 归根结底,这最终是一个判断电话。 如果您看到订单数量显着增加,而收入却没有那么显着,那么您必须说:“好吧,我认为这里可能会有一些真实而有益的事情。”

但是,统计数据和数据大小唯一可以告诉您的是效果是真实的概率。 在一天结束时,由您决定是否真实。 因此,让我们详细讨论如何执行此操作。

这里的重点是,仅仅看一下手段上的差异是不够的。 当您尝试评估实验结果时,还需要考虑方差。

T 检验和 p 值

您如何知道 A/B 测试产生的更改实际上是更改内容的真实结果,还是仅仅是随机变化? 嗯,我们可以使用两种统计工具,称为 T 检验或 T 统计量,以及 p 值。 让我们进一步了解它们是什么,以及它们如何帮助您确定实验是否良好。

目的是找出结果是否真实。 这仅仅是数据本身固有的随机差异的结果,还是我们看到对照组和测试组之间的行为发生了统计学上显着的实际变化? T 检验和 p 值是一种计算方法。

请记住,具有统计意义确实没有特定含义。 归根结底,这必须是一个判断电话。 您必须选择要接受的结果是否为实数的概率值。 但是总是有机会仍然是随机变化的结果,并且您必须确保利益相关者理解这一点。

T 统计量或 T 检验

让我们从 T 统计量开始,也称为 T 检验。 基本上,它是衡量对照组和治疗组两组之间行为差异的一种量度,以标准误差为单位表示。 它基于标准误差,该标准误差说明了数据本身固有的差异,因此,通过使用该标准误差对所有数据进行归一化,我们可以考虑到这两组差异,从而对这两组之间的行为变化进行某种程度的度量。

解释 T 统计量的方法是,高 T 值意味着这两组之间可能存在真正的差异,而低 T 值意味着相差不大。 您必须决定什么是您愿意接受的阈值? T 统计量的符号会告诉您它是正的还是负的变化。

如果将对照组与治疗组进行比较,结果 T 统计量为负,则表明这是一个不好的变化。 您最终希望该 T 统计量的绝对值较大。 T 统计量的值被认为有多大? 好吧,这值得商.。 我们将很快看一些示例。

现在,这确实假设您的行为是正常分布的,并且当我们谈论诸如人们在网站上花费的金额之类的事情时,通常这是一个不错的假设。 确实有多少人支出呈正态分布。

但是,对于其他特定情况,您可能需要查看 T 统计的更多精炼版本。 例如,当您谈论点击率时,有一个名为 Fisher 的精确测试,当您在谈论每个用户的交易时,有 E 测试,例如他们看到了多少页面,以及卡方测试,这通常与您查看订单数量有关。 有时,您会希望查看给定实验的所有这些统计信息,然后选择最适合您要尽力而为的实验。

p 值

现在,谈论 p 值要比 T 统计量容易得多,因为您不必考虑,我们在谈论多少个标准差? 实际值是什么意思? 人们容易理解 p 值,这使它成为更好的工具,可以将实验结果传达给企业中的利益相关者。

p 值基本上是该实验满足原假设的概率,即对照和治疗行为之间没有真正差异的概率。 p 值低表示没有效果的可能性很低,有点双重负数,因此有点直觉,但是到最后,您只需要了解低 p 值意味着您的更改很有可能产生真正的效果。

您想要看到的是高 T 统计量和低 p 值,这将意味着显着的结果。 现在,在开始实验之前,您需要确定成功的门槛,这意味着与业务负责人一起确定门槛。

那么,您愿意接受什么 p 值来衡量成功? 是百分之一吗? 是 5% 吗? 同样,这基本上是没有实际影响的可能性,这仅仅是随机方差的结果。 这只是一天结束时的判断电话。 很多时候人们使用 1%,有时如果感觉有点风险则使用 5%,但总有机会将您的结果只是虚假的随机数据输入。

但是,您可以选择愿意接受的可能性,使其具有足够的效果,这是真实的效果,值得将其推广到生产中。

当实验结束时,我们将讨论当您宣布实验结束时,您要测量 p 值。 如果它小于您确定的阈值,那么您可以拒绝原假设,并且可以说:“嗯,这种变化很有可能产生真正的积极或消极结果。”

如果结果是肯定的,那么您可以将更改推广到整个网站,而不再是实验,它是您网站的一部分,随着时间的流逝,它有望为您带来越来越多的收益,如果结果是负面结果,您希望在花费更多钱之前摆脱它。

请记住,当实验结果为负数时,运行 A/B 测试会产生实际成本。 因此,您不想将其运行太久,因为您可能会亏钱。

这就是为什么您要每天监控实验结果的原因,因此,如果有早期迹象表明更改对网站造成了可怕的影响,也许其中存在错误或可怕的东西,如有必要你可以过早拔插头,并限制损坏。

我们来看一个实际示例,看看如何使用 Python 测量 T 统计量和 p 值。

使用 Python 测量 T 统计量和 p 值

让我们构造一些实验数据,并使用 T 统计量和 p 值确定给定的实验结果是否是真实的效果。 我们将实际构造一些虚假的实验数据,并对它们运行 T 统计量和 p 值,并查看其工作原理以及如何在 Python 中进行计算。

对一些实验数据进行 A/B 测试

假设我们正在网站上进行 A/B 测试,并且已将用户随机分为两组,即组A和组BA组将成为我们的测试对象,我们的治疗组,而B组将成为我们的对照组,基本上就是网站过去的样子。 我们将使用以下代码进行设置:

import numpy as np 
from scipy import stats 

A = np.random.normal(25.0, 5.0, 10000) 
B = np.random.normal(26.0, 5.0, 10000) 

stats.ttest_ind(A, B) 

在此代码示例中,我们的治疗组(A)将具有随机分布的购买行为,即他们平均每笔交易花费 25 美元,标准差为 5 万和 1 万个样本,而过去在相同的标准差和样本量的情况下,以前的网站每笔交易的平均值为 26 美元。 我们基本上是在寻找一个结果为阴性的实验。 确定 T 统计和 p 值所需要做的只是使用scipy中的这种方便的stats.ttest_ind方法。 您要做的是,将其传递给治疗组和对照组,然后得出 T 统计量,如以下输出所示:

在这种情况下,我们的 T 统计量为-14。 否定表示这是消极的变化,这是一件坏事。 而且 p 值非常非常小。 因此,这意味着这种变化仅仅是随机机会的结果的可能性极低。

请记住,为了声明重要性,我们需要看到较高的 T 值 T 统计量和较低的 p 值。

这就是我们在这里看到的,我们看到的是 -14,它是 T 统计的绝对值非常高,为负则表明这是一件坏事,而 p 值却非常低,告诉我们实际上这不可能是随机变化的结果。

如果您在现实世界中看到了这些结果,则将尽快拔出该实验的插头。

当两组之间没有真正的区别时

就像进行健全性检查一样,让我们​​继续进行更改,以使这两组之间没有真正的区别。 因此,我将更打乱B(在这种情况下为对照组),使其与治疗相同,其中均值为 25,标准差不变,样本大小不变,如下所示:

B = np.random.normal(25.0, 5.0, 10000) 

stats.ttest_ind(A, B) 

如果继续进行下去,您可以看到我们的 T 检验现在低于 1 了:

请记住,这是根据标准差得出的。 因此,这意味着除非我们还有更高的 p 值(超过 30%),否则那里可能没有真正的变化。

现在,这些数字仍然相对较高。 您会看到随机变化可能是一种阴险的事情。 这就是为什么您需要提前决定 p 值可接受的限制的原因。

您知道之后,可以看看事实,然后说:“30% 的几率,这还不错,我们可以接受,”但是,没有。 我的意思是,在现实和实践中,您希望看到 p 值低于 5%,理想情况下低于 1%,而 30% 的值实际上意味着结果不那么强。 因此,不要在事后证明其合理性,而要进行实验以了解阈值是多少。

样本量是否有所不同?

让我们对样本大小进行一些更改。 我们将在相同条件下创建这些集合。 让我们看看是否通过增加样本量实际上在行为上有所不同。

样本数量增加到六位数

因此,我们将从10000样本转换为100000样本,如下所示:

A = np.random.normal(25.0, 5.0, 100000) 
B = np.random.normal(25.0, 5.0, 100000) 

stats.ttest_ind(A, B) 

您可以在下面的输出中看到,p 值实际上降低了一点,而 T 检验却稍微增大了一点,但是仍然不足以声明真正的差异。 它实际上正朝着您不希望它走向的方向发展? 有点有趣!

但是这些仍然是很高的值。 同样,这只是随机方差的影响,它可能会带来比您想象的更多的影响。 特别是在谈论订单金额的网站上。

样本数量增加了七位数

让我们实际上将样本大小增加到1000000,,如下所示:

A = np.random.normal(25.0, 5.0, 1000000) 
B = np.random.normal(25.0, 5.0, 1000000) 

stats.ttest_ind(A, B) 

结果如下:

那是做什么的? 好吧,现在,我们的 T 统计量回到了 1 以下,我们的值约为 35%。

随着样本数量的增加,我们会在任一方向上看到这种波动。 这意味着从 10,000 个样本增加到 100,000 个至 1,000,000 个样本并不会改变您的结果。 进行这样的实验是一种良好的直觉,可以使您感觉需要进行多长时间。 实际需要多少个样本才能得出明显的结果? 而且,如果您提前知道有关数据分布的信息,则可以实际运行这些模型。

A/A 测试

如果我们将集合与自身进行比较,这称为 A/A 测试,如以下代码示例所示:

stats.ttest_ind(A, A) 

在下面的输出中,我们可以看到0的 T 统计量和1.0的 p 值,因为实际上这两个集合之间没有任何区别。

现在,如果您使用真实的网站数据来运行这些数据,而您正在看的是完全相同的人,却看到了一个不同的值,则表明运行测试的系统本身存在问题。 就像我说的那样,归根结底,这全都是判断。

继续进行下去,看看不同标准差对初始数据集,均值差异和不同样本量的影响。 我只想让您深入研究这些不同的数据集并进行实际运行,并查看它们对 T 统计量和 p 值的影响。 希望这会使您对如何解释这些结果有更多的了解。

同样,要了解的重要一点是,您正在寻找较大的 T 统计量和较小的 p 值。 P 值很可能就是您要与企业沟通的内容。 记住,p 值越低越好,您希望看到单位数,最好在宣布胜利之前低于 1%。

在本章的其余部分中,我们将更多地讨论 A/B 测试。 SciPy 可以非常轻松地为给定的数据集计算 T 统计量和 p 值,因此您可以非常轻松地比较对照组和治疗组之间的行为,并衡量该效应是真实的还是只是概率随机变化的结果。 确保您专注于这些指标,并且正在衡量进行这些比较时关心的转化指标。

确定要进行多长时间的实验

您需要进行多长时间的实验? 实际需要多长时间? 你什么时候放弃? 让我们更详细地讨论。

如果您公司中的某人开发了一个新的实验,他们想要测试的新变更,那么他们就对成功有浓厚的兴趣。 他们投入了大量的工作和时间,并且希望它取得成功。 也许您已经进行了数周的测试,但您仍未在该实验中获得正面或负面的重大成果。 您知道他们将无限期地继续运行它,以期最终显示出积极的结果。 由您决定您愿意运行此实验的时间。

如何知道完成 A/B 测试的时间? 我的意思是,预测获得显着结果所需的时间并不总是那么简单,但是很明显,如果您获得了显着结果,p 值低于 1% 或 5% 或任何阈值, 选择,您就完成了。

届时,您可以拔掉实验的插头,将更改更广泛地推出或删除,因为它实际上具有负面影响。 您总是可以告诉人们回去再试一次,利用他们从实验中学到的知识,也许可以进行一些更改后再试一次,并减轻一点打击。

可能发生的另一件事是根本没有收敛。 如果您没有看到 p 值随时间的变化趋势,则可能是一个很好的信号,表明您不会很快看到这一趋势。 不管运行多长时间,它都不会对行为产生足够的影响甚至无法衡量。

在这种情况下,您每天想做的是在给定实验的图形上绘制 p 值,T 统计量,以及用来衡量该实验成功与否的任何信息,以及是否某些东西看起来很有希望,您会看到 p 值随时间开始下降。 因此,获得的数据越多,您的结果应获得的意义就越大。

现在,如果您看到一条平坦的线或到处都是一条线,则表明您的 p 值不会随处可见,并且不管实验进行多长时间都没有事情发生。 您需要事先达成共识,如果您看不到 p 值的任何趋势,那么您愿意为该实验进行的最长时间是什么? 是两个星期吗? 是一个月吗?

要记住的另一件事是,一次在站点上运行多个实验可以混淆您的结果。

花在实验上的时间是很有价值的商品,您无法在世界上花费更多的时间。 在给定的一年中,您实际上只能运行尽可能多的实验。 因此,如果您花费太多时间来进行一项实际上没有机会融合结果的实验​​,那么您将有机会错过在此期间进行另一项可能更有价值的实验的机会,而这是您浪费在另一项实验上的机会。

在实验链接上划清界限非常重要,因为在网站上进行 A/B 测试时,时间是非常宝贵的商品,至少只要您的想法多于时间(希望如此)。 确保在测试给定实验所花费的时间上达成一致的上限,并且如果您没有看到 p 值的趋势看起来令人鼓舞,那么该是时候采取行动了。

A/B 测试陷阱

我要说明的一个重要问题是,即使您使用 p 值以原则方式测量 A/B 测试的结果,也不是福音。 实际上,有许多影响因素可能会扭曲实验结果,并导致您做出错误的决定。 让我们仔细研究其中的一些,让您知道如何注意它们。 让我们谈谈 A/B 测试的一些陷阱。

说 p 值为 1% 听起来很正式,这意味着给定实验只有 1% 的机会是由于虚假结果或随机变化引起的,但做一个实验并不是衡量成功与否的全部。 有许多事情可能会歪曲或混淆您需要注意的结果。 因此,即使您看到一个看起来很令人鼓舞的 p 值,您的实验仍然可能对您不利,并且您需要了解可以实现该目标的事情,以免做出错误的决定。

请记住,关联并不意味着因果关系。

即使进行了精心设计的实验,您所能说的还是很有可能是这种影响是由您所做的更改引起的。

归根结底,总会有没有真正效果的机会,或者您甚至可能会测量错误的效果。 仍然可能是随机的机会,可能还会有其他事情发生,您有责任确保企业所有者了解需要解释这些实验结果,并将其作为决策的一部分。

它们不能成为决策所依据的一切,因为结果中存在错误的余地,并且有些事情可能会使结果产生偏差。 而且,如果此变化有更大的业务目标,而不仅仅是增加短期收入,则还必须考虑到这一点。

新颖性效应

一个问题是新颖性效应。 A/B 测试的一个主要致命弱点是它们倾向于在很短的时间内运行,这会带来很多问题。 首先,更改可能会产生较长期的影响,您将不会进行衡量,但是,网站上的某些内容也会有所不同。

例如,也许您的客户习惯于一直浏览网站上的橙色按钮,如果蓝色按钮出现并且引起了他们的注意,只是因为它与众不同。 但是,随着新客户的到来,他们从未见过您的网站,因此他们不会注意到这与众不同,而且随着时间的流逝,即使您的老客户也习惯了新的蓝色按钮。 很有可能,如果您在一年后进行相同的测试,那没有什么区别。 也许他们会反过来。

我可以很容易地看到您测试橙色按钮与蓝色按钮的情况,并且在头两周中蓝色按钮获胜。 人们购买更多商品是因为他们对它更具吸引力,因为它与众不同。 但是一年过去了,我可能会运行另一个网络实验室,将蓝色按钮与橙色按钮放在一起,橙色按钮将再次获胜,这仅仅是因为橙色按钮是不同的,并且它是新的并因此单独引起了人们的注意。

因此,如果您所做的更改有些争议,那么最好稍后再运行该实验,看看您是否可以真正复制其结果。 这确实是我了解新颖性影响的唯一方法。 实际上,当它不再新颖时,或者不再仅仅是因为它与众不同而引起人们关注的变化,才再次对其进行衡量。

而且,我确实不能低估了解这一点的重要性。 这确实会使很多结果产生偏差,使您偏向将不应该得到的积极变化归因于某些事情。 本身不同并不是一种美德。 至少不是在这种情况下。

季节性影响

如果您在圣诞节期间进行实验,那么人们在圣诞节期间的行为往往不会与一年中的其他时间一样。 在那个季节里,他们的花钱肯定不同,他们在家里与家人在一起的时间更多,他们可能会有点没工作,所以人们会有不同的心态。

甚至可能与天气有关,夏季人们的行为有所不同,因为天气炎热,他们感到有些懒惰,他们度假的频率更高。 也许您碰巧在人口稠密地区的暴风雨期间进行实验,这也会使您的结果产生偏差。

同样,只是意识到潜在的季节性影响,假期是一个值得注意的大假期,并且如果假期在已知的季节性期间内运行,请一定要多花些盐。

您可以通过实际查看要衡量的指标作为成功指标来定量地确定这一指标,无论它叫什么转换指标,并查看其去年同期的行为。 您每年都会看到季节性波动吗? 如果是这样,您希望避免在这些高峰或低谷之一中进行实验。

选择偏见

另一个可能导致结果偏差的潜在问题是选择偏见。 将客户随机分配到您的对照组或治疗组,A 组或 B 组非常重要。

但是,有一些微妙的方法可以使随机分配毕竟不是随机的。 例如,假设您要对客户 ID 进行哈希处理,以将其放入一个存储桶或另一个存储桶中。 散列函数如何影响具有较低客户 ID 的人与具有较高客户 ID 的人之间可能存在一些细微的偏差。 这可能会使所有长期的,更忠诚的客户进入对照组,而对您不太了解的新客户也将进入治疗组。

结果,您最终所衡量的只是新客户与旧客户之间行为上的差异。 审核系统以确保在实际分配给对照组或治疗组的人员中没有选择偏见非常重要。

您还需要确保分配是粘性的。 如果要衡量更改在整个会话中的影响,则要衡量他们是否在 A 页上看到了更改,但是,在 C 页上他们实际上进行了转换,因此必须确保他们在这些点击之间没有切换组。 因此,您需要确保在给定的会话中,人们保持在同一组中,并且如何定义会话也可能变得晦涩难懂。

现在,所有这些问题都可以使用已建立的现成框架(例如 Google Experiments 或 Optimizely)或其中的一个来解决,因此您不必为解决所有这些问题而费力。 如果您的公司确实有内部开发的内部解决方案,因为他们不愿意与外部公司共享数据,那么值得检查是否存在选择偏见。

审核选择偏见问题

审核选择偏见问题的一种方法是运行所谓的 A/A 测试,如我们先前所见。 因此,如果您实际进行的实验在处理和对照之间没有差异,那么最终结果就不会有差异。 比较这两件事时,行为不应有任何形式的变化。

A/A 测试可以很好地测试 A/B 框架本身,并确保没有固有的偏差或其他问题(例如,会话泄漏等),您需要解决。

数据污染

另一个大问题是数据污染。 我们详细讨论了清理输入数据的重要性,这在 A/B 测试的情况下尤其重要。 如果您有一个机器人,这是一个恶意爬取程序,该蠕虫始终不停地在您的网站中爬行,进行不自然的交易,该怎么办? 如果那个机器人最终被分配治疗或控制该怎么办?

那个机器人可能会使您的实验结果产生偏差。 研究实验中的输入并寻找异常值,然后分析这些异常值以及是否应将其排除在外非常重要。 您实际上是在让某些机器人泄漏到您的测量中,并且它们使实验结果发生偏差吗? 这是一个非常非常普遍的问题,您需要认识到这一点。

那里有恶意的机器人,有人试图入侵您的网站,有一些良性的抓取工具只是试图对您的网站进行爬取以获取搜索引擎之类的东西。 网站上发生了各种各样的怪异行为,您需要过滤掉这些行为并吸引真正是您客户的人,而不是这些自动化脚本。 这实际上可能是一个非常具有挑战性的问题。 如果可以的话,请使用 Google Analytics(分析)等现成的框架的另一个原因。

归因错误

前面我们简要讨论了归因错误。 这是如果您实际上正在使用更改的下游行为,并且该行为变成灰色区域。

您需要了解如何根据与您所做的更改之间的距离来实际计算这些转换,并与您的业务利益相关者事先商定如何衡量这些影响。 您还需要知道是否一次运行多个实验。 他们会互相冲突吗? 是否存在页面流,某人可能在同一会话中实际遇到两个不同的实验?

如果是这样,那将是一个问题,您必须对这些更改是否实际上可以某种有意义的方式相互干扰并以某种有意义的方式影响客户的行为做出判断。 同样,您需要一粒盐来获得这些结果。 有很多事情可能会使结果产生偏差,您需要意识到这些问题。 只需了解它们,并确保您的企业主也意识到 A/B 测试的局限性,一切都会好起来的。

同样,如果您实际上无法花大量时间进行实验,则需要花些盐来获得这些结果,理想情况下稍后在不同的时间段对其进行重新测试。

总结

在本章中,我们讨论了什么是 A/B 测试以及围绕它们的挑战。 我们介绍了一些示例,这些示例说明了如何使用 T 统计量和 p 值指标实际测量方差的影响,并使用 Python 进行 T 检验的编码和测量。 然后,我们继续讨论 A/B 测试的短期性质及其局限性,例如新颖性影响或季节性影响。

这也结束了我们在本书中的时间。 祝贺您取得了如此长的成就,这是一项重大成就,您应该为自己感到自豪。 我们在这里讨论了很多材料,我希望您至少了解这些概念,并且对当今数据科学中使用的大多数技术有一点动手经验。 这是一个非常广阔的领域,因此我们已经涉及了那里的所有内容。 因此,再次恭喜您。

如果您想在这一领域进一步发展,我真正鼓励您做的是与您的老板交谈。 如果您在一家可以访问自己的一些有趣数据集的公司工作,请查看是否可以使用它们。 显然,在使用公司拥有的任何数据之前,您首先要与老板交谈,因为围绕它可能会有一些隐私限制。 您想确保自己没有侵犯公司客户的隐私,这可能意味着您只能在工作场所的受控环境中使用或查看这些数据。 因此,在执行此操作时要小心。

如果您获得了每周几天才真正上班的许可,并且知道其中一些数据集并弄清楚了可以做什么,不仅表明您有主动让自己成为一个更好的员工,您实际上可能会发现一些对您的公司有价值的东西,这可能会让您看起来更好,并且实际上可能导致内部转移,进入与您的职业生涯更直接相关的领域。

因此,如果您想从我这里获得一些职业建议,我会得到一个普遍的问题:“嘿,我是一名工程师,我想更多地进入数据科学领域,我该怎么做?” 做到这一点的最佳方法就是做到这一点,实际上,您要做一些辅助项目并表明您可以做到,并从中证明了一些有意义的结果。 告诉老板,看看它带领你。 祝你好运。

posted @ 2025-09-03 10:21  绝不原创的飞龙  阅读(40)  评论(0)    收藏  举报