数据科学原理第三版-全-

数据科学原理第三版(全)

原文:annas-archive.org/md5/b72afac8ae73f313f85223bedb99e9b3

译者:飞龙

协议:CC BY-NC-SA 4.0

序言

数据科学原理 连接了数学、编程和商业分析,帮助你自信地提出并解决复杂的数据问题,并构建有效的机器学*管道。本书将为你提供必要的工具,帮助你将抽象概念和原始统计数据转化为可操作的洞察力。

从数据清理和准备开始,你将探索有效的数据挖掘策略和技术,之后逐步构建数据科学各个环节如何协同工作的整体画面。整本书中,你将学*到统计模型,帮助你控制和导航最密集或最稀疏的数据集,并学会创建强大的可视化图表,传达数据中隐藏的故事。

本版着重于应用,涵盖了自然语言处理(NLP)和计算机视觉任务中的高级迁移学*和预训练模型。你将掌握减轻数据算法偏差以及模型和数据漂移的先进技术。最后,你将探索中级数据治理,包括数据来源、隐私保护和删除请求处理。

到本书结束时,你将掌握计算数学和统计学的基本知识,同时深入了解现代机器学*及大型预训练模型,如 GPT 和 BERT 的复杂性。

本书适合谁阅读?

如果你是一个有志成为数据科学家的初学者,渴望扩展自己的知识,那么本书适合你。无论你是否具备基础数学技能并希望将其应用于数据科学领域,还是你擅长编程但缺乏必要的数学基础,这本书都会对你有所帮助。如果你熟悉 Python 编程,学*体验将更为丰富。

本书内容

第一章**,数据科学术语,介绍了数据科学家使用的基本术语。我们将讨论那些经常混淆的术语之间的区别,并通过实际示例加深对每个术语的理解,从而真正学会如何用数据科学的语言进行沟通。我们将从广义的数据科学开始,逐步深入,最终探讨数据科学的各个子领域,如机器学*和统计推断。本章还将介绍数据科学的三个主要领域:数学、编程和领域知识。我们将分别讨论每个领域,并理解它们的用途。同时,我们还将介绍本书中将使用的基本 Python 包和语法。

第二章**,数据类型,讲解了数据类型及数据观察的方式。我们将探讨数据的不同层级以及不同的数据形式。具体来说,我们将了解结构化/非结构化数据、定量/定性数据等之间的差异。

第三章**,《数据科学的五个步骤》,涉及数据科学流程以及数据清洗和准备。我们将详细探讨数据科学的五个步骤,并在每个步骤中提供相关实例。讲解完数据科学的五个步骤后,我们将转向数据清洗,这是数据探索/准备阶段。为了更好地理解这些原理,我们将通过大量实例来解释每个步骤。我还会提供探索数据时应注意的一些提示,包括查看不同尺度的数据、分类变量和缺失数据。我们将使用 pandas 来检查并修正这些问题。

第四章**,《基础数学》,讲解任何数据科学家必备的基础数学技能。我们将深入探讨函数分析,使用矩阵代数和微积分来展示并证明基于现实数据问题的各种结果。

第五章**,《不可能或不太可能——概率的温和介绍》,重点讲解了数据科学所需的基本概率。我们将运用概率规则从数据中推导结果,并开始看到如何通过概率来审视现实世界的问题。本章将具有很强的实用性,并将使用 Python 编写代码来展示例子。

第六章**,《高级概率》,本章将探讨如何使用 Python 解决更复杂的概率问题,并且会介绍一种新的概率类型——贝叶斯推断。我们将利用这些定理来解决现实数据情境中的问题,如天气预测。

第七章**,《几率有多大?统计学入门》,讲解数据科学所需的基本统计学知识。我们还将通过实例探讨统计错误类型,包括类型 I 和类型 II 错误。这些错误与实际结果一样,对于我们的分析至关重要。错误及其不同类型使我们能够更深入地分析结论,避免潜在的灾难性结果。我们将使用 Python 编写代码来解决统计问题并展示结果。

第八章**,《高级统计学》,本章的重点是标准化。理解为何以及如何标准化数据将是至关重要的。我们将介绍基本的图形绘制方法,如散点图、条形图和直方图。本章还将讲解使用数据进行统计建模的内容。我们不仅将定义这一概念——使用数学对现实世界情境进行建模——还将使用实际数据来推导我们自己的统计模型。我们还将讨论过拟合问题。Python 将用于编写统计问题和结果的代码。

第九章**,数据传播,讲述了从我们的分析中传播结果的不同方式。我们将探讨不同的展示风格和可视化技术。 本章的重点是将我们的结果整理成连贯、易懂的方式,以便无论是数据专家还是非专业人士都能够理解并使用我们的结果。我们将讨论的许多内容将涉及如何通过标签、键、颜色等元素来创建有效的图表。我们还将探讨一些更高级的可视化技术,例如*行坐标图。

第十章**,如何判断你的烤面包机是否在学*——机器学*基础,重点介绍了机器学*作为数据科学的一个组成部分。我们将定义不同类型的机器学*,并展示每种类型的示例。我们将特别涉及回归、分类和无监督学*领域。本章将讲解机器学*是什么,以及它在数据科学中的应用。我们将重新审视机器学*与统计建模的区别,并探讨机器学*如何成为后者的更广泛类别。我们的目标是利用统计学和概率论,理解并应用机器学*的基本技能,进而在营销等实际行业中实现应用。示例将包括预测餐厅评论的星级评分、预测疾病的存在、垃圾邮件检测等。 本章更多关注统计和概率模型。下一章将处理那些不属于该类别的模型。我们还将专注于能够告诉我们模型准确性的指标。我们将使用这些指标来得出结论并利用机器学*做出预测。

第十一章**,预测不是天生的,或者它们是吗?,主要讲述不被视为统计或概率模型的机器学*。这些模型无法用单一方程式表示,比如线性回归或朴素贝叶斯。 本章中的模型虽然仍基于数学原理,但比单一方程式要复杂。模型包括 KNN、决策树和无监督聚类的介绍。指标在这里将变得非常重要,因为它们将成为衡量我们理解和模型效果的基础。我们还将在本章中探讨数据科学的一些伦理问题。我们将看到机器学*在隐私和广告等领域可能会触及的边界,并试图得出关于预测伦理的结论。

第十二章**,迁移学*与预训练模型简介,介绍了迁移学*,并举例说明如何将机器的学*从预训练模型转移到微调模型。我们将深入了解开源模型的世界,并在自然语言处理和计算机视觉任务中实现最先进的表现。

第十三章**,减轻算法偏*与应对模型与数据漂移,介绍了算法偏*以及如何量化、识别和缓解数据和模型中的偏*。我们将看到偏*的数据如何导致偏*的模型。我们还将看到如何尽早识别偏*,并捕捉在现有模型中出现的新偏*。

第十四章**,人工智能治理,介绍了模型和数据中的漂移,以及量化和应对漂移的正确方法。我们将看到数据如何随时间漂移,以及如何正确更新模型以应对漂移,确保我们的管道保持最佳性能。

第十五章**,在实际案例中驾驭现实世界的数据科学案例研究,介绍了基本的治理结构以及如何处理删除请求、隐私/权限结构和数据来源。

要充分利用本书

你需要 Python 3.4 或更高版本,并安装 GitHub requirements.txt文件中指定的 Python 库版本。你可以使用 pip 或 conda 安装 Python,或者如果愿意,也可以在 Google Colab 上运行我们的代码!

本书涵盖的软件/硬件 操作系统要求
Python Windows、macOS 或 Linux

如果你使用的是本书的数字版本,我们建议你亲自输入代码或从本书的 GitHub 仓库访问代码(下一节中提供了链接)。这样可以帮助你避免与复制粘贴代码相关的潜在错误。

如果你想了解更多有关机器学*/人工智能/大语言模型的内容,可以查看 Sinan 的其他书籍和在线课程,网址为sinanozdemir.ai

下载示例代码文件

你可以从 GitHub 上下载本书的示例代码文件,网址为github.com/PacktPublishing/Principles-of-Data-Science-Third-Edition。如果代码有更新,GitHub 仓库中会同步更新。

我们还有来自我们丰富书籍和视频目录中的其他代码包,提供于github.com/PacktPublishing/。快去看看吧!

使用的约定

本书中使用了多种文本约定。

Code in text:表示文本中的代码字、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟 URL、用户输入以及 Twitter 用户名。例如:“在这个例子中,相关的推文是 RT @robdv: $TWTR 现在是 Andor 的最大持仓,超过了 $AAPL。”

代码块如下设置:

tweet = "RT @j_o_n_dnger: $TWTR now top holding for Andor, unseating $AAPL" 
words_in_tweet = tweet.split(' ') # list of words in tweet 
for word in words_in_tweet: # for each word in list 
if "$" in word: # if word has a "cashtag" 
print("THIS TWEET IS ABOUT", word) # alert the user

粗体:表示一个新术语、一个重要的词或你在屏幕上看到的词。例如,菜单或对话框中的词语会显示为粗体。例如:“words_in_tweet 变量将推文进行分词(按词分开)。”

提示或重要说明

显示如下。

联系我们

我们总是欢迎读者的反馈。

[email protected] 并在邮件主题中注明书名。

勘误:尽管我们已尽最大努力确保内容的准确性,但错误难免。如果你在本书中发现错误,请通过访问 www.packtpub.com/support/errata 并填写表单来向我们报告。

[email protected] 并附上材料链接。

如果你有兴趣成为作者:如果你在某个主题上有专业知识,并且有兴趣写作或参与编写一本书,请访问 authors.packtpub.com

分享你的想法

一旦你阅读了数据科学原理,我们很想听听你的想法!请点击这里直接前往亚马逊书评页面并分享你的反馈。

你的评价对我们以及技术社区都很重要,能帮助我们确保提供高质量的内容。

下载这本书的免费 PDF 副本

感谢购买本书!

喜欢随时随地阅读但又不能随身携带纸质书籍?

你的电子书购买是否与你选择的设备不兼容?

别担心,现在每本 Packt 书籍都附带免费的无 DRM PDF 版本。

随时随地,在任何设备上阅读。直接将你最喜欢的技术书籍中的代码复制并粘贴到你的应用程序中。

好处不止这些,你还可以获得独家折扣、新闻通讯和每日免费优质内容的邮件。

按照这些简单步骤来获得福利:

  1. 扫描二维码或访问下面的链接

packt.link/free-ebook/9781837636303

  1. 提交你的购买证明

  2. 就是这样!我们会直接把免费的 PDF 和其他福利发送到你的邮箱

第一章:数据科学术语

我们生活在数据时代。无论你从事哪个行业,无论是 IT、时尚、食品还是金融,数据无疑都会影响你的生活和工作。在今天、本周或本月的某个时刻,你将会参与或听到关于数据的对话。新闻媒体越来越多地报道数据泄露、网络犯罪,以及现代人工智能和机器学*算法如何改变我们工作和生活的方式。

本书将尝试简明扼要地讲解我们如何解读、互动、操作和利用数据的原则。我们将试图涵盖数据科学的基本原则。在开始探讨如此庞大的主题之前,首先,我们需要在脚下打下坚实的基础。

为了开始我们的旅程,本章将探索现代数据科学家的术语和词汇。我们将学*在本书的讨论中至关重要的关键词和短语。我们还将学*为什么我们使用数据科学,并了解数据科学的三个关键领域,在开始研究本书的主要语言 Python 的代码之前。

本章将涵盖以下主题:

  • 数据科学的基本术语

  • 数据科学的三个领域

  • 基本的 Python 语法

什么是数据科学?

这是一个简单的问题,但在继续之前,让我们先来看一些在本书中将使用的基本定义。数据科学领域的一个伟大/糟糕之处在于,它足够年轻,以至于即便是基本的定义和术语,也可能在不同的出版物和人群中引起争议。基本的定义是:数据科学是通过数据获得知识的过程

对于如此庞大的话题来说,这似乎是一个简单的定义,确实如此!数据科学涵盖了许多内容,要列出所有内容可能需要几页纸。换句话说,数据科学的核心就是我们如何获取数据,利用它来获得知识,然后将这些知识用于以下目的:

  • 做出明智的决策

  • 预测未来

  • 理解过去/现在

  • 创造新的行业/产品

本书专注于数据科学的方法,包括如何处理数据、获取*解,并利用这些*解做出明智的决策和预测。

理解基本的数据科学术语

以下定义足够通用,可以在日常对话和工作中使用,旨在服务本书的目的,即数据科学原理的入门介绍

让我们从定义“数据”开始。这可能看起来像是一个愚蠢的定义,但它非常重要。每当我们使用“数据”一词时,我们指的是以结构化或非结构化格式收集的信息。这些格式具有以下特点:

  • 结构化数据:指的是那些被排序为行/列结构的数据,其中每一行代表一个独立的观察值,而每一列则代表该观察值的特征。

  • 非结构化数据:这是指以自由形式存在的数据,通常是文本或原始音频/信号,必须进一步解析才能变得结构化。

数据无处不在,来自多种来源,包括日常的互联网浏览、社交媒体活动以及技术过程中的系统日志等。当这些数据被结构化时,它们就成为了各种算法和企业的有用工具。举个例子,考虑一下你的在线购物历史。每一笔交易都会被记录,包括产品、价格、日期和时间、支付方式等细节。这些结构化的信息以行和列的形式呈现,清晰地描绘出你的购物*惯、偏好和模式。

然而,并非所有数据都被整齐地包装好。非结构化数据,例如社交媒体或电子商务网站上的评论和评价,通常没有固定的格式。它们可能包含文本、图片,甚至视频,这使得组织和分析变得更加困难。然而,一旦正确处理,这些自由流动的信息可以提供有价值的洞察,例如情感分析,帮助我们更深入地了解客户的态度和观点。从本质上讲,能够同时利用结构化和非结构化数据是解锁我们每日产生的大量信息潜力的关键。

打开 Excel 或任何电子表格软件,你会看到一个空白的网格,专为结构化数据设计。这些工具并不适合处理非结构化数据。尽管我们的主要关注点是结构化数据,因为它易于解读,但我们也不会忽视原始文本和其他非结构化数据类型的丰富性,以及使其可理解的技术。

数据科学的关键在于运用数据揭示那些原本会被隐藏的洞察。以医疗保健环境为例,数据科学技术可以预测哪些患者可能无法按时参加预约。这不仅优化了资源分配,还确保其他患者可以利用这些空余的时间段。理解数据科学不仅仅是掌握它的功能,更重要的是理解其重要性,并意识到为什么掌握它如此需求旺盛。

为什么是数据科学?

数据科学不会取代人类大脑(至少短期内不会),而是增强和补充它,与之并肩工作。数据科学不应被看作是解决我们数据问题的万能答案;它仅仅是一个观点——一个经过充分信息分析的观点,但依然是观点而已。它值得在讨论中占有一席之地。

在这个数据时代,很明显我们拥有大量数据。但是,为什么这就需要一套全新的词汇呢?我们之前的分析方法有什么问题吗?首先,数据的庞大规模使得人类在合理的时间框架内无法解析它。数据以各种形式和来源收集,且常常以非常非结构化的格式出现。

数据可能缺失、不完整或完全错误。很多时候,我们会遇到不同量级的数据,这使得比较它们变得非常困难。假设我们正在查看有关二手车定价的数据。车的一项特征是它的生产年份,另一项可能是该车的行驶里程。一旦我们清理了数据(在本书中我们将花费大量时间讨论这一部分),数据之间的关系变得更加明显,而曾经深藏在数百万行数据中的知识便会显现出来。数据科学的一个主要目标就是制定明确的实践和流程,发现并应用数据中的这些关系。

让我们花一分钟的时间,使用一个非常相关的例子来讨论它今天的作用。

示例 – 使用机器学*预测 COVID-19

本书的一个重要部分是关于如何利用强大的机器学*算法,包括深度学*,来解决现代复杂的任务。一个这样的任务是使用深度学*来帮助诊断、治疗和预防致命疾病,包括 COVID-19。自 2020 年全球疫情爆发以来,全球许多组织转向数据科学,以缓解和解决与 COVID-19 相关的问题。例如,以下图示展示了一种使用机器学*(在这种情况下是深度学*)筛查 COVID-19 的过程,该过程于 2020 年 3 月发布。那时,全球对 COVID-19 的了解仅有几个月,但我们已经能够相对轻松地将机器学*技术应用于如此新颖的使用场景:

图 1.1 – 基于深度学*的 COVID-19 筛查算法可视化图(2020 年)

图 1.1 – 基于深度学*的 COVID-19 筛查算法可视化图(2020 年)

这个筛查算法是首批尝试识别 COVID-19 并将其与流感等已知疾病区分开来的算法之一。像这样的算法表明,我们可以在突发灾难发生时,依靠数据和机器学*来提供帮助。我们将在本书后面学*如何开发这样的算法,这些具有改变生活潜力的系统。创建此类算法需要结合三种不同的技能,这些技能结合在一起,构成了数据科学的基础。它需要那些了解 COVID-19 的人、那些知道如何创建统计模型的人,以及那些知道如何将这些模型投入生产,使人们能够受益的人。

数据科学的维恩图

许多人误以为只有拥有博士学位或是数学天才的人才能理解数据科学背后的数学和编程。这是错误的。理解数据科学从三个基本领域开始:

  • 数学/统计学:这涉及使用方程式和公式来进行分析

  • 计算机编程:这意味着能够使用代码在计算机上创建结果。

  • 领域知识:这指的是理解问题所在的领域(医学、金融、社会科学等)

以下的维恩图提供了数据科学这三个领域交集的可视化展示:

图 1.2 – 数据科学的维恩图

图 1.2 – 数据科学的维恩图

具有黑客技能的人可以利用计算机语言构思和编写复杂的算法。有了数学和统计学的背景,你就能够理论化并评估算法,并调整现有的程序以适应特定的情况。拥有扎实的专业知识(领域知识)则能够让你以有意义且有效的方式应用概念和结果。

虽然只具备这三种特质中的两种可以让你变得聪明,但也会留下空白。比如说,如果你在编码方面非常熟练,并且有正式的日间交易训练,你可能会创建一个自动化系统来代替你进行交易,但却缺乏数学技能来评估你的算法。这意味着从长远来看,你可能会亏损。只有当你在编码、数学和领域知识方面都增强了自己的技能时,你才能真正执行数据科学。

可能令你感到意外的特质是领域知识。它只是你所工作的领域的知识。如果一位金融分析师开始分析有关心脏病发作的数据,他们可能需要心脏病专家的帮助才能理解许多数字。

数据科学是前述三个关键领域的交集。要从数据中获得知识,我们必须能够利用计算机编程访问数据,理解我们推导出的模型背后的数学原理,最重要的是,理解我们分析结果在我们所在领域中的作用。这还包括数据的展示。如果我们正在创建一个预测病人心脏病发作的模型,是创建一个 PDF 文件来展示信息,还是开发一个应用程序,在其中输入数字并快速获得预测结果更好?所有这些决策都必须由数据科学家来做出。

数学和编码的交集就是机器学*。本书稍后会详细讨论机器学*,但需要注意的是,如果没有将任何模型或结果明确地推广到一个特定领域的能力,机器学*算法就仅仅是算法而已——它们只会停留在你的计算机上。你可能拥有最好的癌症预测算法。基于过去癌症患者的数据,你可以用超过 99%的准确率预测癌症,但如果你不知道如何在实际应用中将这个模型应用到医生和护士能够轻松使用的程度,那么你的模型可能就毫无用处。

本书将广泛涵盖计算机编程和数学内容。领域知识既来源于数据科学的实践,也来源于阅读其他人的分析示例。

数学

一旦有人说出“数学”这个词,大多数人就会停止听讲。他们可能会点头表示赞同,试图掩饰自己对这个话题的完全不屑,但请听我说完。作为一名经验丰富的数学老师,我保证本书将引导你学*数据科学所需的数学,特别是统计学和概率论。我们将利用这些数学子领域来创建所谓的模型。数据模型是指数据元素之间的组织和正式关系,通常用于模拟现实世界中的现象。

使用数学的核心理念是,我们将利用数学来规范变量之间的关系。作为一名曾经的纯数学家和现任数学教师,我知道这有多困难。我将尽最大努力清楚地解释所有内容。在数据科学的三个领域中,数学是让我们从一个领域转到另一个领域的关键。理解理论使我们能够将为时尚行业构建的模型应用到金融领域。

本书涉及的数学内容从基础代数到高级概率与统计建模不等。即便你已经了解这些内容或对这些内容感到害怕,也不要跳过这些章节。我会小心翼翼地、有目的地介绍每一个数学概念,并通过示例加以说明。本书中的数学对数据科学家至关重要。

数据模型有很多种类型,包括概率模型和统计模型。它们都是更大范式——机器学*的子集。这三者背后的核心理念是,我们使用数据来找到最佳的模型。我们不再依赖人类的直觉——而是依赖数据。数学和编码是工具,能够让数据科学家在几乎任何地方都能应用他们的技能。

计算机编程

说实话,你可能觉得计算机科学比数学更酷。没关系,我不怪你。新闻里没有像关于技术的新闻那样多的数学新闻(虽然我认为这有点遗憾)。你不会打开电视看到关于素数的新理论或者欧拉方程的报道 —— 相反,你会看到调查报告,讲解最新的智能手机如何拍摄更清晰的猫咪照片,或者像 ChatGPT 这样的生成式 AI 模型如何学会从零开始创建网站。计算机语言是我们与机器沟通的方式,用来指示它们按照我们的要求执行任务。计算机能讲多种语言,就像书籍可以用多种语言书写一样;同样,数据科学也可以使用多种语言来完成。Python、Julia 和 R 都是我们可以使用的语言之一。本书将专门使用 Python。

为什么选择 Python?

我们将使用 Python 有多个原因,列举如下:

  • Python 是一种非常简单的语言,易于阅读和编写,即使你之前从未编写过代码,这也会让未来的示例变得易于理解和阅读,即使你已经读完了本书。

  • 它是最常*的语言之一,无论是在生产环境还是学术环境中(实际上,它是增长最快的语言之一)。

  • 该语言的在线社区庞大而友好。这意味着快速搜索问题的解决方案应该能找到很多遇到过并解决过类似(如果不是完全相同)问题的人。

  • Python 拥有预构建的数据科学模块,既适合新手也适合经验丰富的数据科学家使用。

最后一点可能是我们将重点关注 Python 的最大原因。这些预构建的模块不仅强大,而且容易上手。在前几章结束时,你将对这些模块非常熟悉。以下是一些模块的例子:

  • pandas

  • PyTorch

  • Scikit-learn

  • Seaborn

  • NumPy/scipy

  • Requests(用于从网络中挖掘数据)

  • BeautifulSoup(用于网页 HTML 解析)

我们假设你具备基本的 Python 编程技能。这包括能够识别并使用基本数据类型(intfloatboolean 等),以及能够轻松创建函数和类。我们还假设你掌握了逻辑运算符的使用,包括 ==>=<=

示例 – 解析单条推文

这里有一些 Python 代码,应该能让你轻松阅读。在这个例子中,我将解析一些关于股票价格的推文。如果你能轻松跟上这个例子,那么你已经准备好继续前进了!

tweet = "RT @j_o_n_dnger: $TWTR now top holding for Andor, unseating $AAPL"
words_in_tweet = tweet.split(' ') # list of words in tweet
for word in words_in_tweet: # for each word in list
 if "$" in word: # if word has a "cashtag"
  print("THIS TWEET IS ABOUT", word) # alert the user

我将逐行指出这个代码片段的一些细节,具体如下:

  1. 首先,我们设置一个变量来保存一些文本(在 Python 中称为字符串)。在这个例子中,推文内容是RT @robdv: $TWTR 现在是 Andor 的主要持股, 取代了 $AAPL

  2. words_in_tweet 变量将推文进行分词(按单词分开)。如果你打印这个变量,你将看到如下内容:

    ['RT', '@robdv:', '$TWTR', 'now', 'top', 'holding', 'for', 'Andor,', 'unseating', '$AAPL']
    
  3. 我们通过for循环遍历这个单词列表,一个一个地进行处理。

  4. 这里,我们又有了另一个if语句。对于这条推文中的每个单词,如果该单词包含$字符,这表示 Twitter 上的股票代码。

    if "$" in word: # if word has a "cashtag"
    
  5. 如果前面的if语句为True(也就是说,如果推文中包含股票符号),则打印并显示给用户。

    这段代码的输出将如下所示:

    THIS TWEET IS ABOUT $TWTR
    
    THIS TWEET IS ABOUT $AAPL
    

每当我在本书中使用 Python 时,我会确保尽可能清楚地说明我在每一行代码中做了什么。我知道在代码中迷失的感觉。我知道看到别人写代码时,想让他们慢下来并解释正在发生的事情的感觉。

作为一个完全自学 Python 的人,并且也在最高水*上教授过 Python,我向你保证,当你阅读本书时,我会尽我所能确保你不会有那种感觉。

领域知识

正如我之前提到的,领域知识主要关注的是了解你所从事的特定主题。例如,如果你是一个金融分析师,正在处理股市数据,那么你拥有大量的领域知识。如果你是一个记者,正在研究全球的普及率,你可能会受益于咨询该领域的专家。本书将尝试展示来自多个问题领域的示例,包括医学、营销、金融,甚至是 UFO 目击事件!

这是否意味着如果你不是医生,就不能处理医疗数据?当然不是!优秀的数据科学家能够将他们的技能应用于任何领域,即使他们并不精通该领域。数据科学家能够适应该领域,并在完成分析后做出有意义的贡献。

领域知识的一个重要部分是呈现方式。根据你的受众群体,你如何呈现你的发现可能会非常重要。你的结果的价值取决于你的沟通工具。你可以以 99.99%的准确率预测市场走势,但如果你的程序无法执行,那么你的结果就无法使用。同样,如果你的沟通工具不适合该领域,你的结果也会同样无法使用。我知道我已经给你提供了很多信息,但我们还应该再看几个相关的术语,以便我们能够顺利起步。

更多的术语

到这个时候,你可能已经兴奋地查阅了很多数据科学的资料,并看到了一些我还没有用到的单词和短语。以下是一些你可能会遇到的常*术语:

  • 机器学*:这指的是让计算机能够从数据中学*,而不需要程序员明确提供“规则”。在本章前面,我们已经看到了机器学*作为具备编程和数学技能的人的结合体的概念。在这里,我们尝试对这个定义进行形式化。机器学*将计算机的强大能力与智能学*算法相结合,用于自动发现数据中的关系并创建强大的数据模型。

  • 统计模型:这指的是利用统计定理,通过一个(通常是)简单的数学公式来形式化数据元素之间的关系。

  • 探索性数据分析EDA):这指的是准备数据以标准化结果并快速获取*解。EDA 关注数据可视化和准备。在这个过程中,我们将非结构化数据转化为结构化数据,并清理缺失或不正确的数据点。在 EDA 过程中,我们会创建许多类型的图表,并利用这些图表识别关键特征和关系,从而在我们的数据模型中加以利用。

  • 数据挖掘:这是发现数据元素之间关系的过程。数据挖掘是数据科学的一个环节,在这个环节中,我们尝试找到变量之间的关系(想想看孵化-招募模型)。

我一直尽量避免使用“大数据”这个术语。因为我认为这个术语被滥用得非常严重——很多时候都被误用。大数据是指数据量大到单台机器无法处理的情况(如果你的笔记本电脑崩溃了,它可能正遭遇大数据的困扰)。

以下图表展示了这些数据科学概念之间的关系。

图 1.3 – 数据科学的现状(到目前为止)

图 1.3 – 数据科学的现状(到目前为止)

有了这些术语牢牢地印在脑海中,我们可以继续进入本书的主要教育资源:数据科学案例研究。

数据科学案例研究

本书的主要内容将围绕使用数据科学和机器学*的真实案例展开。数学、计算机编程和领域知识的结合赋予了数据科学巨大的力量,但没有具体的编码示例,它往往显得过于抽象。

很多时候,一个人很难掌握这三个领域的所有知识。这就是为什么公司通常会雇佣数据科学团队,而不是一个人单打独斗。让我们来看一些数据科学应用中的强大示例及其成果。

案例研究 – 自动化政府文书处理

社会保障申请被认为是一个大麻烦,既对阅读它们的代理人,也对提交申请的人来说如此。某些申请需要两年多的时间才能完全解决,这简直荒谬!让我们看看下面的图表,展示了一个申请的内容:

图 1.4 – 社会保障表格示例

图 1.4 – 社会保障表格示例

不错。尽管它大多数是文字。填这个,然后填那个,再填这个,依此类推。你可以想象一个代理人整天阅读这些表格,一个接一个,会有多么困难。肯定有更好的方法!

其实是有的。Elder Research Inc.对这些非结构化数据进行了分析,成功实现了自动化处理 20%的残疾社会保障表格。这意味着计算机可以查看 20%的这些书面表格,并对批准结果提出意*。

除此之外,负责评估表单批准的第三方公司给机器评分的表单比人工评分的表单得分更高。所以,不仅计算机*均处理了 20%的负载,而且它的表现也优于人类。

现代语言模型,如 GPT-3 和 BERT,凭借突破性的发展,已经让自然语言处理(NLP)领域风靡一时,推动了我们之前认为不可能的界限。我们将在本书后面花费大量时间讨论这些模型。

开除所有人类,对吧?

在我收到一堆愤怒的电子邮件和推文,声称数据科学正在导致人类工人消失之前,请记住,计算机在我们之前的例子中只能处理 20%的负载。这意味着它可能在 80%的表单上表现得非常糟糕!这是因为计算机可能擅长处理简单的表单。那些本应由人类花费数分钟计算的声明,计算机只用了几秒钟。但这些分钟累计起来,不久后,每个人类每天节省的时间将超过一小时!

对人类来说容易阅读的表单,计算机也可能容易处理。当表单非常简洁,或者作者开始偏离常规语法时,计算机就会开始失败。这个模型非常好,因为它使人类能够更多地专注于那些复杂的声明,并且在处理海量文件时不会被分散注意力。

注意我使用了“模型”这个词。记住,模型是元素之间的关系。在这个例子中,关系是写出的词语和声明的批准状态之间的关系。

案例研究 – 职位描述中包含了什么内容?

在找数据科学相关的工作吗?太好了!让我来帮忙。在这个案例研究中,我已经抓取(使用代码从网页上读取)了 1,000 个正在积极招聘数据科学家的公司的职位描述。这里的目标是查看一些人们在职位描述中常用的关键词,如以下截图所示:

图 1.5 – 数据科学家职位列表示例

图 1.5 – 数据科学家职位列表示例

在以下的 Python 代码中,前两个导入用于抓取来自Indeed.com的网页数据,第三个导入则只是为了简单地计算一个词或短语出现的次数,如下所示的代码:

import requests
from bs4 import BeautifulSoup
from sklearn.feature_extraction.text import CountVectorizer
# grab postings from the web
texts = []
# cycle through 100 pages of indeed job resources
for i in range(0,1000,10):
 response = requests.get('http://www.indeed.com/jobs?q=data+scientist&sta rt='+str(i)).text
 soup  = BeautifulSoup(response)
 texts += [a.text for a in soup.findAll('span', {'class':'summary'})]
print(type(texts))
print(texts[0]) # first job description

这个循环的作用就是遍历 100 页职位描述,对于每一页,它都会抓取每个职位描述。这里的关键变量是texts,它是一个包含超过 1,000 个职位描述的列表,如以下代码所示:

type(texts) # == list
vectorizer = CountVectorizer(ngram_range=(1,2), stop_words='english')
# Get basic counts of one and two word phrases
matrix = vectorizer.fit_transform(texts)
# fit and learn to the vocabulary in the corpus
print len(vect.get_feature_names()) # how many features there are

在我的案例中,总共有 10,857 个一词和二词短语!由于网页是实时抓取的,这些页面可能会在你运行代码时发生变化,因此你得到的数字可能会与 10,857 不同。

我在这里省略了一些代码,因为我们将在后面的 NLP 章节中更深入地讨论这些包,但它们已经存在于本书的 GitHub 仓库中。结果如下(按短语表示,后面是其出现的次数):

图 1.6 – 在 Indeed 网站上查看“数据科学家”职位描述时出现的最常*一词和两词短语

图 1.6 – 在 Indeed 网站上查看“数据科学家”职位描述时出现的最常*一词和两词短语

在这个案例研究中有许多有趣的地方值得注意,但最大的收获是,数据科学角色由许多关键词和短语组成。这不仅仅是数学、编程或领域知识;实际上,正是这三者的结合(无论是体现为一个人的团队,还是跨多人的团队)使得数据科学变得可能且强大。

总结

在这一章中,我们探讨了基本的数据科学术语,并看到即使是“数据科学”这个术语也可能充满了模糊和误解。我们还了解到,编程、数学和领域知识是数据科学的基本构建块。在我们寻求发现数据趋势的创新方式时,背后潜伏着一只怪兽。我说的不是数学或编程的学*曲线,也不是数据的过剩。工业时代留下了我们与污染作斗争的长期遗产。随后的信息时代则留下了一串大数据的足迹。那么,数据时代可能给我们带来什么危险呢?

数据时代可能带来更为险恶的东西——通过大规模数据对个体的去人性化,以及机器学*系统中自动化偏*的兴起。

越来越多的人跳进了数据科学领域,其中大多数人没有数学或计算机科学的基础,这表面上看起来是件好事。普通的数据科学家可以访问数百万条约会资料、推文、在线评论等数据来启动他们的学*。然而,如果你在没有正确理论或编码实践的基础上进入数据科学,并且不尊重你所从事的领域,你将面临将你试图建模的现象过于简化的风险。

现在,是时候开始了。在下一章中,我们将探索世界上存在的不同类型的数据,从自由格式的文本到高度结构化的行/列文件。我们还将研究不同类型数据所允许的数学运算,并根据数据的形式推导出洞察。

第二章:数据类型

在进入数据科学的第一步时,让我们来看看数据可以如何被构造。在本章中,我们将探讨数据的三种关键分类:

  • 结构化数据与非结构化数据

  • 定量数据与定性数据

  • 数据的四个层级

我们将通过展示数据科学家如何查看和处理数据的例子,进一步探讨这些话题。本章的目标是让我们熟悉数据的基本类型,以便当我们最终看到第一个数据集时,能够准确地解剖、诊断并分析其中的内容,最大化我们的洞察力和机器学*性能。

首先要注意的是我使用的“数据”一词。在上一章中,我将数据定义为仅仅是信息的集合。这个模糊的定义之所以存在,是因为我们可能会将数据分成不同的类别,需要一个宽松的定义。

在我们进入本章内容时,接下来要记住的是,大多数情况下,当我谈论数据类型时,我指的是数据集的某个特定特征(列/特征)或整个数据集。我会在任何时候非常明确地指出我指的是哪一个。

起初,可能觉得在深入研究统计学和机器学*等有趣的内容之前,停下来思考我们拥有的数据类型似乎毫无价值,但这无疑是你进行数据科学工作时需要采取的最重要步骤之一。

当我们拿到一个新的数据集进行分析时,通常会急于开始探索、应用统计模型,并研究机器学*的应用,以尽快得到结果。然而,如果你不了解自己所处理的数据类型,那么你可能会浪费大量时间应用那些已知对特定数据类型无效的模型。

结构化数据与非结构化数据

我们在处理整个数据集时,首先要问自己的是,我们处理的是结构化数据还是非结构化数据。这个问题的答案可能意味着进行适当分析所需的时间差异——是三天还是三周。

基本的划分如下(这是对组织化与非组织化数据的再定义,来源于第一章):

  • 结构化(即,组织化)数据:这类数据可以看作是观察结果和特征。它通常通过表格方式(行和列)进行组织,可以在电子表格格式或关系型数据库中进行组织。

  • 非结构化(即,非组织化)数据:这类数据作为自由实体存在,并不遵循任何标准的组织层次结构,例如图像、文本或视频。

这里有几个例子,可以帮助你区分这两者:

  • 大多数以文本形式存在的数据,包括服务器日志和 Facebook 帖子,都是非结构化数据

  • 科学观察结果,作为科学家记录的内容,通常以非常整齐和有组织的(结构化)格式保存。

  • 一个化学核苷酸的基因序列(例如 ACGTATTGCA)是非结构化的,即使核苷酸的顺序很重要,因为我们不能使用行/列格式来形成该序列的描述,而不进一步分析。

结构化数据通常被认为更容易处理和分析。大多数统计和机器学*模型是在结构化数据的基础上构建的,无法处理非结构化数据的松散解释。自然的行列结构对于人类和机器来说都容易理解。那么,为什么还要讨论非结构化数据呢?因为它非常普遍!大多数估计认为,非结构化数据占全球数据的 80-90%。这种数据以多种形式存在,并且在人类看来,大多数时候它们作为数据源并没有被注意到。推文、电子邮件、文献和服务器日志通常是非结构化数据的形式。

虽然数据科学家可能更偏好结构化数据,但他们必须能够处理世界上大量的非结构化数据。如果全球 90%的数据是非结构化的,那意味着大约 90%的信息被困在一种难以处理的格式中。

因此,由于我们的大多数数据都存在于这种自由格式中,我们必须借助一种称为预处理的预分析技术,为至少一部分数据应用结构,以便进一步分析。后面的章节将详细讨论预处理;目前,我们将考虑预处理中的一部分内容,即我们尝试应用变换将非结构化数据转换为结构化数据的对应物。稍后我们将在本书中看到几个这样的例子。

在结构化数据和非结构化数据之间,存在一个被称为半结构化数据的混合类别。结构化数据遵循严格的模式,具有定义的行和列格式,而非结构化数据则没有特定的格式,半结构化数据包含了两者的元素。

半结构化数据是一种数据形式,它不完全符合与关系数据库或其他数据表形式相关的正式数据模型结构,但包含标签或其他标记,用于分隔语义元素并强制数据中的记录和字段层级。半结构化数据的例子包括 XML 和 JSON 文件、电子邮件以及某些类型的 NoSQL 数据库。这种类型的数据在网络数据和某些类型的科学和健康研究中非常常*。

定量数据与定性数据

当你问数据科学家,这是什么类型的数据?他们通常会认为你在问它是主要的定量数据还是定性数据。这可能是描述数据集特征最常*的方式。

在大多数情况下,当谈论定量数据时,通常(并非总是)是在谈论具有严格行/列结构的结构化数据集(因为我们不假设非结构化数据甚至任何特征)。这正是预处理步骤如此重要的更多原因。

这两种数据类型可以定义如下:

  • 定量数据:这些数据可以用数字描述,并且可以进行基本的数学运算,包括加法。

  • 定性数据:这些数据不能用数字和基本数学来描述。这些数据通常被认为是用自然类别和语言描述的。

让我们看一个小企业中定性和定量数据的例子。

例如——咖啡店数据

假设我们正在处理一个大城市里的本地咖啡店的顾客,使用五个描述符(特征)来描述每个顾客:

  • 咖啡店的名称

  • 收入(以千美元计)

  • 邮政编码

  • *均每月顾客数

  • 咖啡的产地国家

这些特征可以被分类为定量或定性的,这种简单的区分可以改变一切。让我们来看看每一个:

  • 咖啡店的名称:定性的

    咖啡店的名称不以数字表示,我们不能对店名进行数学运算。

  • 收入:定量的

    一家咖啡店带来多少收入可以用一个数字来描述。此外,我们可以进行基本操作,比如将 12 个月的收入相加,得到一年的收入。

  • 邮政编码:定性的

    这个有点棘手。邮政编码总是用数字表示,但使它成为定性的是它不符合定量的第二部分定义——我们不能对邮政编码进行基本的数学运算。如果我们将两个邮政编码加在一起,这是一个无意义的测量。我们不一定会得到一个新的邮政编码,也不会得到“两倍的邮政编码”。

  • *均每月顾客数:定量的

    再次,用数字和加法描述这个因素是有意义的。将所有月度顾客相加,得到你的年度顾客数。

  • 咖啡的产地国家:定性的

    我们假设这是一个非常小的咖啡店,只有来自单一产地的咖啡。这个国家是用名称(埃塞俄比亚,哥伦比亚)来描述的,而不是数字。

注意

即使邮政编码是用数字描述的,它并不是定量的。这是因为你不能谈论所有邮政编码的总和或*均邮政编码。这些都是无意义的描述。

如果你在试图确定数据是定性还是定量时遇到困难,可以问自己关于数据特性的一些基本问题:

  • 你能用数字描述这个值吗?

    • 不是?很可能是定性的。

    • 是的?继续下一个问题。

  • 如果把它们加在一起,数值上是否有意义?

    • 没有?它们很可能是定性的。

    • 是吗?你可能手头有定量数据。

这个方法将帮助你将大多数(如果不是全部的话)数据分类到这两类中的一种。如果你在想定性数据如何用数字来描述,试想调查结果要求人们按 1 到 5 的等级来排序某件事。虽然内容用数字来描述,但将它们“加起来”没有意义。一个人的 1 分和另一个人的 3 分加起来并不会变成 4 分。

这两类的区别定义了你可以针对每一列提出的问题。对于定量列,你可以问如下问题:

  • *均值是多少?

  • 这个数量是随着时间的推移增加还是减少的(如果时间是一个因素)?

  • 是否有一个阈值,当这个数字变得过高或过低时,会对公司造成问题?

对于定性列,上述问题都无法回答。然而,以下问题仅适用于定性值:

  • 哪个值出现得最多,哪个值最少?

  • 有多少个独特值?

  • 这些独特值是什么?

示例 – 检查世界酒精消费数据

世界卫生组织WHO)发布了一个数据集,描述了世界各国人们的*均饮酒*惯。我们将使用 Python 和数据探索工具 pandas 来更好地了解:

import pandas as pd
read in the CSV file from a URL drinks =
pd.read_csv('https://raw.githubusercontent.com/sinanuozdemir/principles_of_ data_science/master/data/chapter_2/drinks.csv')
examine the data's first five rows
drinks.head()# print the first 5 rows

上述代码块产生了这个 DataFrame:

图 2.1 – 来自世界卫生组织的酒精消费数据的前五行

图 2.1 – 来自世界卫生组织的酒精消费数据的前五行

这三行代码做了以下操作:

  • 导入 pandas,未来将其简称为pd

  • 读取一个逗号分隔值CSV)文件,作为一个名为drinks的变量。

  • 调用一个方法,head,显示数据集的前五行。

上述图表列出了来自drink.csv文件的前五行数据。我们在这个示例中处理了六个不同的列:

  • country: 定性

  • beer_servings: 定量

  • spirit_servings: 定量

  • wine_servings: 定量

  • total_litres_of_pure_alcohol: 定量

  • continent: 定性

我们来看一下定性列continent。我们可以使用 pandas 获取关于这个非数字特征的一些基本统计信息。这里使用了describe()方法,它首先判断该列可能是定量还是定性,然后提供关于该列的基本信息。这可以通过以下方式完成:

drinks['continent'].describe()
  >> count
170
  >> unique
5
  >>
top
AF
  >>
freq
53

上述代码显示了世界卫生组织收集了关于五个独特大陆的数据,其中出现最多的是AF(非洲),在193次观测中出现了53次。

如果我们查看其中一个定量列并调用相同的方法,我们可以看到输出的差异,如下所示:

drinks['beer_servings'].describe()
  count
193.000000
  mean
106.160622
  std
101.143103
  min
0.000000
  25%
20.000000
  50%
76.000000
  75%
188.000000
  max
376.000000

现在,我们可以看看每个国家人均啤酒消费量的*均值(106.2 杯),以及最低的啤酒消费量0和记录的最高啤酒消费量376(这比一天一杯还多)。

深入探讨

量化数据可以分解为离散连续量。

这些可以定义如下:

  • 离散数据:这描述的是已计数的数据。它只能取某些特定的值。

    离散量化数据的例子包括掷骰子,因为它只能取六个值,以及咖啡店的顾客数量,因为你不能有一个范围内的顾客人数。

  • 连续数据:这描述的是已测量的数据。它存在于无限范围的值中。

    连续数据的一个好例子是一个人的体重,因为它可以是 150 磅或 197.66 磅(注意小数)。一个人或建筑物的身高是一个连续数值,因为它可以有无限的小数。其他连续数据的例子包括时间和温度。

数据整体可以是结构化的或非结构化的,这意味着数据可以采取有组织的行/列结构,且每一行的数据集都有明确的特征描述,或者数据以自由形式存在,通常需要预处理成易于消化的形式。

如果数据是结构化的,我们可以将数据集的每一列(特征)看作是量化的或定性的。基本上,列是否可以用数学和数字来描述?本章的下一部分将数据分解成四个非常具体且详细的层级。在每个层级,我们将应用更复杂的数学规则,从而更直观和量化地理解数据。

数据的四个层级

一般认为,结构化数据的特定特征(特征/列)可以分解为四个数据层级。它们如下:

  • 名义水*

  • 序数水*

  • 区间水*

  • 比例水*

随着我们向下列表移动,我们会获得更多的结构化数据,因此从分析中得到更多的回报。每个层级都有自己的公认做法来衡量数据的中心。我们通常认为*均数/均值是一个可接受的中心衡量方式。

然而,这只适用于特定类型的数据。

名义水*

数据的第一个层级,即名义层级,由仅通过名称或类别描述的数据组成。基本的例子包括性别、国籍、物种或啤酒中的酵母菌株。它们不通过数字来描述,因此是定性的。以下是一些例子:

  • 动物的种类属于名义数据水*。我们也可以说,如果你是黑猩猩,那你也属于哺乳类。

  • 词性也被认为是在名义数据层级。单词“she”是代词,它也是名词。

当然,作为定性的,我们无法进行任何定量的数学运算,比如加法或除法。这些运算是没有意义的。

名义层次上允许的数学运算

我们无法在数据的名义层次上进行数学运算,除了基本的相等和集合成员函数,如下两个示例所示:

  • 成为一名科技企业家就像进入科技行业一样,但反过来就不一定成立

  • 被描述为正方形的图形也可以被视为矩形,但反过来则不行

现在,我们将讨论中心度量。

中心度量

中心度量是一个描述数据倾向的数字。它有时被称为数据的*衡点。常*的例子包括均值、中位数和众数。

查找continent列。

中心度量,如均值和中位数,在这个层次上没有意义,因为我们无法对观察结果进行排序,甚至无法将它们加在一起。

名义层次数据的特点

名义层次的数据大多是分类的。因为我们通常只能用词语来描述数据,它在不同国家之间可能会出现翻译错误,甚至可能拼写错误。

尽管这个层次的数据肯定是有用的,但我们必须小心从中得出的任何结论。由于只有众数作为基本的中心度量,我们无法得出关于*均值的结论。这个概念在这个层次上不存在。只有在下一个层次,我们才能开始对观察结果进行真正的数学运算。

序数层次

名义层次由于一个看似不重要的事实,未能提供我们太多数学运算的灵活性:我们无法以任何自然的方式对观察结果进行排序。序数层次的数据为我们提供了排序秩次或将一个观察放置在另一个观察之前的手段。然而,它并未提供观察之间的相对差异,这意味着尽管我们可以按顺序对观察结果进行排序,但无法对它们进行加减运算以获得任何实际意义。

示例

李克特量表是最常*的序数层次量表之一。每当你收到一份调查,要求你在 1 到 10 的范围内评分你的满意度时,你实际上是在提供序数层次的数据。你的答案,必须介于 1 和 10 之间,可以进行排序:8 比 7 好,3 比 9 差。

然而,数字之间的差异并没有太多意义。7 和 6 之间的差异可能与 2 和 1 之间的差异不同。

序数层次上允许的数学运算

在这个层次上,我们在数学运算方面有更多的自由。我们继承了来自序数层次的所有数学(相等和集合成员关系),并且还可以在名义层次允许的运算列表中添加以下内容:

  • 排序

  • 比较

排序是指数据所提供的自然顺序。然而,有时候这可能很难弄清楚。当谈论可*光谱时,我们可以引用颜色的名称 – 红色橙色黄色绿色蓝色靛色紫色。自然地,随着我们从左到右移动,光获得能量和其他性质。我们可以将这称为自然顺序:

图 2.2 – 颜色的自然顺序

图 2.2 – 颜色的自然顺序

然而,如果需要,艺术家可以对数据施加另一种顺序,例如根据制造所需颜色的材料成本对颜色进行排序。这可能会改变数据的顺序,但只要我们在定义顺序的方式上保持一致,定义它的方式并不重要。

比较是此级别允许的另一种新操作。在序数级别上,说一个国家比另一个国家更好或一个词类比另一个词类更糟是没有意义的。在序数级别上,我们可以进行这些比较。例如,我们可以讨论在调查中给出 7 比给出 10 更糟糕的情况。

中心度量

在序数水*上,中位数通常是定义数据中心的合适方式。然而,*均值将是不可能的,因为在这个水*上不允许除法。我们也可以使用众数,就像在名义水*上一样。

让我们看一个使用中位数的例子。

想象一下,你向员工进行了一项调查,问“在一个从 1 到 5 的尺度上,你对在这里工作感到多么满意?”,结果如下:

5, 4, 3, 4, 5, 3, 2, 5, 3, 2, 1, 4, 5, 3, 4, 4, 5, 4, 2, 1, 4, 5, 4, 3, 2,4,4,5,4,3,2,1]

让我们使用 Python 找到这些数据的中位数。值得注意的是,大多数人会认为这些分数的*均值也可以工作得很好。*均值不如数学上可行的原因是,如果我们减去/加上两个分数,比如减去 2 的分数 4,差值 2 没有任何意义。如果分数之间的加法/减法没有意义,那么*均值也没有意义:

import numpy
results = [5, 4, 3, 4, 5, 3, 2, 5, 3, 2, 1, 4, 5, 3, 4, 4, 5, 4, 2, 1, 4, 5, 4, 3, 2, 4, 4, 5, 4, 3, 2, 1]
sorted_results = sorted(results)
print(sorted_results)
'''
[1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5]
'''  # The ''' (triple apostrophe) denotes a longer (over two lines) comment
print(numpy.mean(results)) # == 3.4375
print(numpy.median(results)) # == 4.0

重要说明

结果表明,中位数不仅更为可靠,而且使调查结果看起来更好。

区间水*

现在,我们正逐渐深入有趣的地方。在区间水*上,我们开始研究可以通过非常可量化的方式表达的数据,以及允许使用更复杂的数学公式。序数级别与区间级别的基本区别就是这个区别。

区间水*的数据允许数据点之间有意义的减法。

区间水*的数据示例

温度是区间级别数据的一个很好的例子。当将美国德州的 100 华氏度与土耳其伊斯坦布尔的 80 华氏度进行比较时,我们可以清楚地看到德州比伊斯坦布尔高 20 度。这个简单的比较凸显了区间级别数据相较于其他度量级别提供的更高操作和分析潜力。

看起来,顺序级别的例子(使用 1 到 5 的调查)符合区间级别的要求。然而,记住,当你减去分数时,差异并没有意义;因此,这些数据不能称为区间级别数据。

区间级别允许的数学运算

我们可以使用所有在较低级别允许的运算(排序、比较等),以及另外两个重要的运算:

  • 加法

  • 减法

这两种运算的允许使我们能够以全新的方式讨论此级别的数据。

中心度量

在这个级别,我们可以使用中位数和众数来描述数据。然而,通常情况下,数据中心的最准确描述是算术*均数,通常称为均值。回忆一下,均值的定义要求我们将所有测量值加在一起。在之前的级别,加法是没有意义的。因此,均值会失去极端值。只有在区间级别及以上,算术*均数才有意义。

现在,让我们来看一个使用均值的例子。

假设我们在查看一个包含制药公司新疫苗的冰箱温度。我们每小时测量一次温度,得到了以下数据点(单位为华氏度):

31, 32, 32, 31, 28, 29, 31, 38, 32, 31, 30, 29, 30, 31, 26

再次使用 Python,我们来计算数据的均值和中位数:

import numpy
temps = [31, 32, 32, 31, 28, 29, 31, 38, 32, 31, 30, 29, 30, 31, 26]
  print(numpy.mean(temps))
#
==
30.73
  print(numpy.median(temps))
#
==
31.0

注意,均值和中位数非常接近,且两者都约为31度。问题冰箱有多冷?的*均回答大约是31。然而,疫苗附带有警告:

“不要将此疫苗存放在低于 29 度的温度下。”*

注意,温度至少有两次低于29度,但我们最终认为这不足以造成危害。

这时,变异度量能帮助我们了解冰箱情况可能有多糟糕。

变异度量

这是我们尚未讨论过的新内容。讨论数据的中心是一个方面,但在数据科学中,提到数据“分布”的情况也是非常重要的。描述这种现象的度量称为变异度量。你可能在统计学课上听说过标准差。这个概念非常重要,我想简要介绍一下。

变异度量(例如标准差)是一个数值,用于描述数据的分布情况。

连同中心度量,变异度量几乎可以仅凭两个数字完全描述一个数据集。

标准差

可以说,标准差是最常用的变异度量,适用于区间等级及更高等级。标准差可以被视为“数据点与均值之间的*均距离”。虽然这个描述在技术和数学上不完全正确,但它是一个很好的思考方式。标准差的公式可以分解成以下几个步骤:

  1. 找到数据的均值。

  2. 对数据集中的每个数字,先减去均值,再将其*方。

  3. 找到每个*方差的*均值。

  4. 步骤 3中得到的数值取*方根——这就是标准差。

注意我们在步骤中如何计算算术均值。

例如,我们回顾一下温度数据集。让我们使用 Python 找到该数据集的标准差:

import numpy
temps = [31, 32, 32, 31, 28, 29, 31, 38, 32, 31, 30, 29, 30, 31, 26]
  mean = numpy.mean(temps)
# == 30.73
squared_differences = []
# empty list o squared differences
for temperature in temps:
difference = temperature - mean
# how far is the point from the mean
squared_difference = difference**2
# square the difference
squared_differences.append(squared_difference)
# add it to our list
average_squared_difference = numpy.mean(squared_differences)
# This number is also called the "Variance"
standard_deviation = numpy.sqrt(average_squared_difference)
# We did it!
print(standard_deviation) # == 2.5157

所有这些代码最终告诉我们,数据集的标准差约为 2.5,这意味着,*均而言,一个数据点离均值 31 度的温度大约有 2.5 度的偏差。这意味着温度很可能会再次降到 29 度以下。

重要提示

我们之所以要计算每个点与均值之间的“*方差”而不是“实际差异”,是因为*方的操作会强调异常值——那些远离均值的数据点。

变异度量让我们清晰地了解数据的分布或离散程度。特别是当我们关心数据范围和数据波动(比如股票的百分比回报)时,这一点尤为重要。

数据在这一等级和下一个等级之间的巨大差异在于一个不明显的地方。区间等级的数据没有自然起点或自然零点。然而,0 摄氏度并不意味着没有温度。

比例等级

最后,我们将看一下比例等级。在经历了三个允许不同数学操作的等级后,比例等级证明是四个等级中最强的。

比例等级允许的数学操作

我们不仅可以定义顺序和差异,比例等级还允许我们进行乘法和除法运算。这看起来似乎不算什么大事,但它几乎改变了我们在此等级上看待数据的方式。

示例

虽然华氏温度和摄氏温度停留在区间等级,但开尔文温标具有自然零点。0 开尔文意味着没有热量,它是一个非任意的起始零点。我们可以科学地说,200 开尔文的热量是 100 开尔文的两倍。

银行账户里的钱属于比例等级。你可以没有钱,这样$200,000 就是$100,000 的两倍。

很多人可能会争辩说摄氏度和华氏度也有一个起点(主要是因为我们可以从开尔文温标转换为这两者)。这里的真正区别看起来可能很傻,但因为转换到摄氏度和华氏度会使计算结果变成负数,它并没有定义一个明确且“自然”的零点。

中心度量

算术*均数在这个层次上依然有意义,几何*均数也有新含义,它是所有数值的乘积的*方根。

例如,在我们的冰箱温度数据中,我们可以按如下方式计算几何*均数:

import numpy
temps = [31, 32, 32, 31, 28, 29, 31, 38, 32, 31, 30, 29, 30, 31, 26]
num_items = len(temps)
product = 1.
for temperature in temps:
product *= temperature
geometric_mean = product**(1./num_items)
print(geometric_mean)              # == 30.634

再次注意它与先前计算的算术*均数和中位数接近。并非所有情况下都如此,之后会在第八章**, 高级统计学中详细讨论。

比例等级的问题

即使在这个层次上增加了所有这些功能,我们通常还必须做出一个非常大的假设,这使得比例等级有些限制。仅仅因为这一点,许多数据科学家更喜欢间隔等级而非比例等级。这个限制属性的原因是,如果我们允许负值,比例可能并不总是合理的。

假设我们在银行存款例子中允许债务发生。如果我们的余额是 50,000 美元,以下比例将完全没有意义:

50,000 / -50,000 = -1

数据是由观察者定义的

我们是有可能对数据施加结构的。例如,虽然我说在序数尺度上你技术上不能使用均值,但许多统计学家并不认为使用这个数字作为数据集的描述符是个问题。

你解释数据的层次是一个巨大的假设,应该在任何分析开始时做出,并且需要小心和深思熟虑。如果你正在看一般认为是序数级别的数据,并应用算术*均数和标准差等工具,数据科学家必须意识到这一点。主要是因为,如果你继续认为这些假设在分析中有效,你可能会遇到问题。例如,如果你错误地假设序数级别的数据可以除法,你就强加了一个结构,而这个结构可能并不存在。

我们已经看到很多关于数据的数据。让我们通过总结我们学到的内容来结束。

总结

本章概述了数据类型在数据科学中扮演的重要角色,强调了在开始任何分析之前理解数据本质的重要性。我们讨论了在遇到新数据集时需要问的三个关键问题:数据是结构化的还是非结构化的?每一列是定量的还是定性的?每一列中的数据层次(名义、序数、间隔或比例)是什么?

完成本章内容后,你应该能够识别他们所处理的数据类型,并理解这些数据类型对分析的影响。这些知识将帮助你选择合适的图表,解释结果,并确定分析过程中的下一步。你还应该熟悉将数据从一个层次转换到另一个层次的概念,以获得更多的洞察。

通过这些知识,并且能够通过各种示例将数据分类为名义型或序数型,我们可以更有效地解决数据挑战,并在数据驱动的项目中做出明智的决策。

在接下来的章节中,我们将深入探讨数据科学家如何在数据发现和可视化过程中利用数据类型,进一步扩展本章所讨论概念的实际应用。

问题与答案

对以下陈述进行分类为序数型或名义型:

  • 你杯中咖啡豆的来源:名义型

  • 某人在完成跑步比赛后获得的名次:序数型

  • 他们在比赛中获奖后所获得的奖牌所用的金属:名义型

  • 客户的电话号码:名义型

  • 你每天喝多少杯咖啡:序数型

第三章:数据科学的五个步骤

本章将深入探讨数据科学流程中的五个核心步骤,并且每一步都会有示例。这五个步骤包括定义实际问题、收集和预处理数据、探索和分析数据、得出结论以及有效地传达结果。

我们还将深入探讨数据探索和数据可视化的重要话题。数据探索涉及检查数据中的特征和模式,以便更好地理解它,而数据可视化则涉及使用图形、图表和其他可视化工具来表示和传达数据及其发现。

本章结束时,你将对数据科学流程有一个扎实的理解,并且能够将其应用于解决实际问题。那么,开始吧!

本章还将涵盖以下主题:

  • 数据科学的真正介绍

  • 高效探索数据

  • 各级数据探索技巧

  • 使用pandas进行数据处理和优化

数据科学简介

过去十年里,我每个月至少被问一次的问题是数据科学和数据分析有什么区别? 有人可能会认为这两者之间没有区别;也有人会认为它们之间有成百上千的不同!我相信,无论这两个术语之间有多少区别,以下内容都是适用的:

数据科学遵循一个结构化的、逐步的过程,当遵循这个过程时,能够保持结果的完整性,并加深对数据及其来源环境的理解。

和其他科学探索一样,必须遵循这个过程,否则分析和结果可能会受到质疑。从更简单的角度看,遵循严格的流程可以让任何数据科学家、爱好者或专业人士比在没有明确目标的情况下探索数据更快地获得结果。

尽管这些步骤是为业余分析师提供的指导性课程,它们也为所有数据科学家奠定了基础,即使是那些在商业和学术界处于最高层次的人。每个数据科学家都认识到这些步骤的价值,并以某种方式遵循它们。

五个步骤概述

数据科学的过程包括一系列步骤,这些步骤对于有效地从数据中提取洞察和知识至关重要。这些步骤如下所示:

  1. 提出一个有趣的问题:任何数据科学项目的第一步是确定一个你想通过分析解决的问题或挑战。这涉及找到一个相关、重要且可以通过数据来解决的话题。

  2. 获取数据:一旦你确定了问题,下一步就是收集回答该问题所需的数据。这可能涉及从各种来源获取数据,例如数据库、在线*台,或通过数据抓取或数据收集方法。

  3. 探索数据:在收集完数据后,下一步是探索数据,更好地了解其特征和模式。这可能包括检查总结统计数据、可视化数据,或应用统计学或机器学*ML)技术来识别趋势或关系。

  4. 建模数据:一旦你探索完数据,下一步是建立可以用来进行预测或辅助决策的模型。这可能涉及应用机器学*算法、构建统计模型,或使用其他技术来发现数据中的模式。

  5. 沟通和可视化结果:最后,重要的是以清晰有效的方式将你的发现传达给他人。这可能包括创建报告、演示文稿或可视化,帮助解释你的结果及其影响。

通过遵循这五个关键步骤,你可以有效地利用数据科学解决现实世界的问题,并从数据中提取有价值的*解。

需要注意的是,不同的数据科学家可能有不同的数据科学过程方法,之前列出的步骤仅仅是组织过程的一种方式。一些数据科学家可能会将步骤分组不同,或加入额外的步骤,如特征工程或模型评估。

尽管存在这些差异,大多数数据科学家一致认为,之前列出的步骤对于数据科学过程至关重要。无论这些步骤是否以这种具体的方式组织,它们对于有效地利用数据解决问题和提取有价值的*解都非常关键。让我们逐一深入了解这些步骤。

提出一个有趣的问题

这可能是我最喜欢的一步。提出一个有趣且相关的问题是数据科学过程中第一步,也是最重要的一步。它决定了你的分析方向和重点,并决定了你需要收集和分析的数据和资源。

作为一名企业家,你可能已经*惯了不断地提问和寻找答案。这个步骤可以像头脑风暴会议一样进行,在会议中你写下所有问题和想法,无论你是否认为有数据可以回答它们。这有助于避免偏*,并允许你考虑更多可能性。

在提问时,务必具体且聚焦。这将帮助你有效地解决问题,并从数据中提取有价值的*解。还需要考虑问题的范围和可行性,以及你需要的资源和数据。

通过提出一个有趣且相关的问题,你可以为成功的数据科学项目奠定基础,开始从数据中提取有价值的*解。

获取数据

获取数据是数据科学过程中的一个关键步骤。它涉及从各种来源收集你需要的数据,以回答你已识别的问题或解决所面临的难题。数据可以来自多种来源,包括数据库、在线*台、研究研究,或是数据抓取或收集方法。

这一过程可能非常富有创意,因为你需要创造性地思考从哪里获取与问题最相关的数据。你可能需要探索不同的来源和*台,并且可能需要使用多种数据收集方法来收集所需的数据。

在收集数据时,重要的是要注意数据的质量,以及数据中可能存在的任何偏差或局限性。同时,还需要考虑伦理和法律问题,例如获得适当的同意和保护敏感或机密数据。

一旦收集到数据,至关重要的是对数据进行清理和预处理,以确保它处于可用的格式。这可能包括去除缺失或不准确的数据,将数据格式化以便于使用,并确保数据的一致性和准确性。

通过有效地收集和预处理数据,你可以为成功的数据科学项目奠定基础,并为下一步的数据探索和分析做好准备。

探索数据

探索数据是数据科学过程中的一个关键步骤,它涉及检查数据中的特征和模式,以便更好地理解数据。此步骤对于识别趋势、关系和洞察非常重要,这些可以为你的分析提供信息,并回答你的研究问题。

探索数据有多种方法,包括使用图形、图表和绘图来可视化数据,以及应用统计和机器学*技术来识别模式和关系。重要的是要注意你所处理的数据类型,因为不同类型的数据可能需要不同的探索方法。

这一过程可能会非常耗时,因为它可能涉及花费数小时了解相关领域,并使用代码或其他工具来操作和探索数据。完成这一步骤后,分析师应该对数据中可能包含的潜在洞察有较好的理解,并能够形成关于数据可能传递的信息的假设。

探索数据是数据科学过程中的一个关键步骤,因为它有助于指导分析的方向,并帮助你选择合适的建模和分析技术。在此步骤中,重要的是要细致入微,因为你获得的洞察可能会对结果和结论产生重要影响。

数据建模

建模数据是数据科学过程中的重要一步,因为它涉及使用统计学和机器学*技术建立可以用来进行预测或指导决策的模型。这一步可能很复杂,因为它涉及为你的数据和目标拟合并选择合适的模型,并且还需要实施数学验证指标来量化模型的有效性。

数据科学中可以使用多种不同类型的模型,包括线性回归模型、逻辑回归模型、决策树模型以及神经网络NN)模型等等。选择最适合你的数据和你要回答的问题的模型非常重要。

一旦你选择了模型,你需要将其拟合到数据中。这涉及使用统计或机器学*算法来找到最佳拟合数据模式的参数。你还需要使用验证指标,如准确率、精确度和召回率,评估模型的性能。这些指标可以帮助你了解模型的有效性,并找出改进的空间。

通过有效地建模你的数据,你可以获得*解,并根据你的分析做出明智的决策。仔细考虑在这一步做出的选择非常重要,因为模型的质量可以极大地影响结果的准确性和实用性。

沟通和可视化结果

这可以说是最重要的一步。沟通和可视化结果涉及到有效地与他人分享你的发现和*解。这一步可能具有挑战性,因为它要求你清晰简洁地传达你的结果,以一种让受众能够理解并消化的方式。

有许多不同的方式可以沟通和可视化你的结果,包括创建报告、演示文稿以及图表、图形和绘图等可视化方式。选择最适合你的受众和你要传达的信息的方式非常重要。

同时,考虑你的结果的影响和发现的意义也很重要。这可能涉及讨论分析的局限性、结果的含义,以及可能需要的任何建议或下一步行动。

本书将主要集中在数据科学过程的第 3 步第 4 步第 5 步,即探索和分析数据、建模数据以及沟通和可视化结果。虽然提出有趣的问题和获取数据的前两步对于整个过程也至关重要,但我们将在这里简要提及这些步骤,主要关注过程中的更科学的方面。通过专注于这些步骤,我们旨在提供有趣的问题和数据集,供探索和分析使用。

为什么在本书中我们跳过了步骤 1 和步骤 2?

虽然前两步无疑是整个过程中的关键步骤,但它们通常在统计分析和编程系统之前进行。在本书后面的部分,我们将涉及获得数据的不同方式;然而,为了聚焦于过程中的更科学方面,我们将立即开始探索数据。

让我们更专注于第三步——探索我们的数据。

探索数据

数据探索的过程并不总是直截了当的,可能涉及多种方法和技术。数据探索中一些常*的任务包括识别不同类型的数据、转换数据类型以及使用代码系统地提高整个数据集的质量。这些任务可以通过工具来完成,例如常用的pandas Python 包,用于数据处理和分析。

在探索新数据集时,您应该考虑一些基本问题。这些问题可以帮助您了解数据并指导分析。以下是三个基本问题:

  • 数据集中存在哪些类型的数据?

  • 数据的特征和模式是什么?

  • 数据是如何组织的?需要进行哪些转化才能使其更易用?

通过回答这些问题并彻底探索数据,您可以更深入地理解数据,并为下一步的建模和分析做好充分准备。

数据探索的指导问题

当遇到一个新的数据集时,无论它是否熟悉,都应该使用以下问题作为初步分析的指南:

  • 数据是结构化的还是非结构化的?:我们需要检查数据是否以行/列结构呈现。大多数情况下,数据将以结构化的方式呈现。在本书中,超过 90%的示例将从结构化数据开始。然而,在深入分析之前,这是我们可以回答的最基本问题。一般来说,如果我们拥有非结构化数据,我们需要将其转换为行/列结构。例如,在本书的早些章节,我们探讨了如何通过统计单词/短语的数量来将文本转化为行/列结构。

  • 每一行代表什么?:一旦我们弄清楚数据是如何组织的,并且看到一个整齐的行/列结构数据集,我们就应该确定每一行实际代表什么。这个步骤通常非常迅速,并且能够帮助我们更快地将事情理清。

  • 每一列代表什么?:我们应该通过数据的级别来识别每一列,判断它是定量的还是定性的,等等。随着分析的进行,这种分类可能会发生变化,但尽早开始这一步是很重要的。

  • 是否有缺失的数据点?:数据并不完美。有时,由于人为或机械错误,我们可能会缺少数据。当这种情况发生时,作为数据科学家,我们必须决定如何处理这些差异。

  • 我们是否需要对列进行任何转换?:根据每列数据的级别/类型,我们可能需要执行某些类型的转换。例如,通常为了统计建模和机器学*的目的,我们希望每一列都是数值型的,因此我们会使用 Python 来进行转换。

在整个过程中,我们不断问自己一个总体问题:我们从初步推断统计中能推导出什么? 我们希望比最初发现数据时更好地理解这些数据。

够了,言归正传;让我们看一个实际操作的例子。

数据集 1 – Yelp

我们将要查看的第一个数据集是餐厅评论网站 Yelp 提供的公开数据集。所有的 个人可识别信息PII)都已被移除。我故意没有提供太多信息,因为我们的一部分目标是自己通过这个数据来推测一些内容。如果我告诉你更多,那还有什么乐趣呢?

假设我们刚刚得到了这些数据。我们需要做的第一件事是读取它,如下所示:

import pandas as pd
yelp_raw_data = pd.read_csv("yelp.csv")
yelp_raw_data.head()

这是对前面代码功能的简要回顾:

  1. 它导入 pandas 包并将其别名为 pd

  2. 它从网络中读取 .csv 文件,并将其命名为 yelp_raw_data/

  3. 它查看数据的头部(仅前几行)。

我们得到以下输出:

图 3.1 – 我们的 Yelp 数据集的 pandas DataFrame 中的前五行(头部)展示了不同的数据级别,包括名义数据(例如,text,business_id)和序数数据(cool,useful,funny)

图 3.1 – 我们的 Yelp 数据集的 pandas DataFrame 中的前五行(头部)展示了不同的数据级别,包括名义数据(例如,text,business_id)和序数数据(cool,useful,funny)

从这里开始,我们可以开始提出一些关键问题。我们先从这个问题开始:数据是结构化的还是非结构化的? 由于我们有一个清晰的行/列结构,我们可以得出结论,这个数据看起来是结构化的。

所以,接下来,合乎逻辑的下一个问题是:每一行代表什么? 看起来很明显,每一行代表一个用户对某个商家的评论。接下来,我们应该做的就是检查每一行,并根据它所包含的数据类型进行标注。此时,我们也可以使用 Python 来找出数据集的大小。我们可以使用 DataFrame 的 shape 属性来查找这个信息,如下所示:

yelp_raw_data.shape
# (10000,10)

它告诉我们这个数据集有 10000 行和 10 列。换句话说,这个数据集有 10,000 个观察值和 10 个特征。

好的——那么,接下来的问题是:每一列代表什么?(注意,我们有 10 列。)让我们来看看答案:

  • business_id: 这很可能是评论所针对的商家的唯一标识符。由于这个标识符没有自然顺序,它属于名义水*

  • date: 这很可能是评论发布的日期。注意,它似乎仅针对日期、月份和年份。尽管时间通常被认为是连续的,但由于日期具有自然顺序,这一列可能被认为是离散的,且处于顺序水*

  • review_id: 这很可能是每条帖子代表的评论的唯一标识符。由于这个标识符没有自然的顺序,因此它属于名义水*。

  • stars: 从快速查看来看(别担心,我们很快会进行进一步的分析),我们可以看到这是一个有序的列,表示评论者给餐厅的最终评分。它是有序且定性的,因此处于顺序水*。

  • text: 这很可能是每个评论者写的原始文本。像大多数文本一样,我们将其放在名义水*。

  • type: 在前五行中,我们看到的都是review这个词。这可能是一个标识每行数据为评论的列,意味着可能还有除评论之外的其他类型的行。我们稍后会再看这个。我们将其放在名义水*。

  • user_id: 这很可能是写评论的用户的唯一标识符。就像其他唯一标识符一样,我们把这个数据放在名义水*。

我们下一个问题是是否存在缺失的数据点? 执行isnull操作。例如,如果你的数据框名称是awesome_dataframe,可以尝试运行awesome_dataframe.isnull().sum()这个 Python 命令,它会显示每列缺失值的数量。

我们接下来的问题是我们是否需要对列进行任何变换? 此时,我们正在寻找几个问题。例如,我们是否需要更改某些定量数据的尺度,或者是否需要为定性变量创建虚拟变量?由于此数据集只有定性列,我们只能关注顺序和名义尺度上的变换。

在开始之前,让我们先了解一些pandas,这个 Python 数据探索模块的基本术语。

DataFrames

当我们读取数据集时,pandas会创建一个名为DataFrame的自定义对象。可以把它当作 Python 版本的电子表格(但要强大得多)。在这个例子中,yelp_raw_data变量就是一个 DataFrame。

要检查这是否在 Python 中为真,可以输入以下代码:

type(yelp_raw_data)
# pandas.core.frame.DataFrame

DataFrame 是二维的,这意味着它们的结构像电子表格一样按行/列组织。使用 DataFrame 的主要好处是,DataFrame 可以处理比大多数常*电子表格软件更大的数据。如果你熟悉 R 语言,你可能会认出 DataFrame 这个词。因为这个名字实际上是从 R 语言借来的!

由于我们处理的大部分数据是结构化的,因此 DataFrame 很可能是pandas中使用最广泛的对象,仅次于Series对象。

序列

Series对象本质上是一个 DataFrame,只不过它只有一个维度。实际上,它是一个数据点的列表。DataFrame 的每一列都被视为一个Series对象。让我们来检查一下——我们需要做的第一件事是从 DataFrame 中提取单列;我们通常使用被称为括号表示法的方式。以下是一个示例:

yelp_raw_data['business_id'] # grabs a single column of the Dataframe

我们将列出前几行和后几行,如下所示:

9yKzy9PApeiPPOUJEtnvkg
ZRJwVLyzEJq1VAihDhYiow
6oRAC4uyJCsJl1X0WZpVSA
_1QQZuf4zZOyFCvXc0o6Vg
6ozycU1RpktNG2-1BroVtw
-yxfBYGB6SEqszmxJxd97A
6zp713qNhx8d9KCJJnrw1xA

让我们使用type函数检查这一列是否是Series对象:

type(yelp_raw_data['business_id'])
# pandas.core.series.Series

pandasSeries对象会反复出现,因为它是DataFrame的核心组件。

定性数据探索提示

使用这两个pandas对象,我们开始进行一些初步的数据探索。对于定性数据,我们将特别关注名义级和顺序级数据。

名义级数据列

既然我们处于名义级数据层次,让我们回忆一下,在这个层次,数据是定性的,仅通过名称进行描述。在这个数据集中,这指的是business_idreview_idtexttypeuser_id。让我们使用pandas进行更深入的探索,正如下面所示:

yelp_raw_data['business_id'].describe()
  # count
10000
  # unique
4174
  #
top
ntN85eu27C04nwyPa8IHtw
  #
freq
37

describe函数会给我们输入列名后的一些快速统计数据。注意,pandas自动识别了business_id是一个定性列,并提供了合适的统计信息。当describe函数作用于定性列时,我们总会得到以下四项内容:

  • count:填入的值的数量

  • unique:填入的唯一值的数量

  • top:数据集中最常*项的名称

  • freq:数据集中最常*项出现的频率

在名义级数据中,我们通常会寻找一些信号,表明数据需要转换:

  • 我们是否有合理数量(通常低于 20 个)的唯一项?

  • 这个列是文本数据吗?

  • 这一列在所有行中是唯一的吗?

所以,对于business_id这一列,我们有 10,000 个计数。不过,不要被迷惑!这并不意味着我们这里有 10,000 家企业在被评审。这仅仅意味着在 10,000 条评审数据中,business_id这一列每一行都填入了数据。下一个标志unique告诉我们,在这个数据集中,涉及 4,174 个独特的企业被评审。最多评审的企业是JokKtdXU7zXHcr20Lrk29A,它共被评审了 37 次:

review_id which is a unique identifier for each review.
yelp_raw_data['review_id'].describe()
# count                            10000
# unique                           10000
#          Top                     M3jTv5NIipi_N4mgmZiIEg
#          Freq                    1

我们有一个count10000unique也为10000。稍微想一下——这有道理吗?想想每一行代表的是什么,这一列又代表的是什么。

(在这里插入《Jeopardy》主题曲)

当然有道理!该数据集的每一行应该代表一条单独的、独特的商业评论,而这一列旨在作为评论的唯一标识符。因此,review_id这一列中有10000个唯一项是合理的。那么,为什么eTa5KD-LTgQv6UT1Zmijmw最常*的评论呢?这只是从10000中随机选出的,并没有什么特别的含义:

yelp_raw_data['text'].describe()
count        10000
unique       9998
top          This review is for the chain in general. The l...
freq         2

这一列代表了人们写的实际文本,非常有趣。我们会想象这一列也应该像review_id一样,每一项都应该包含独特的文本,因为如果两个人写的完全相同,那就很奇怪了,但我们确实有两个评论的文本完全相同!让我们花点时间学*如何过滤 DataFrame,进一步研究这个问题。

Pandas 中的过滤

让我们稍微谈谈过滤是如何工作的。基于特定标准过滤行在pandas中非常简单。在 DataFrame 中,如果我们希望基于某些搜索条件来过滤行,我们需要逐行检查每一行是否满足该条件;pandas通过传递一个包含TrueFalse(布尔值)的Series对象来处理这个问题。

我们实际上是将一个包含TrueFalse数据的列表传递给 DataFrame,这意味着以下:

  • True:这一行满足条件

  • False:这一行不满足条件

所以,首先,让我们设定条件。在以下的代码行中,我将抓取那些出现了两次的文本:

yelp_raw_data['text'].describe()['top']

这是文本的一个片段:

"This review is for the chain in general. The location we went to is new so it isn't in Yelp yet. Once it is I will put this review there as well......."

立刻,我们可以猜测这可能是一个人去评论了两家属于同一连锁的商店,并写了完全相同的评论。不过,目前这只是猜测。

重要提示

duplicate_text变量是字符串类型的。

现在我们有了这些文本,让我们用一些魔法来创建这个Series对象,里面包含TrueFalse

duplicate_text = yelp_raw_data['text'].describe()['top']
text_is_the_duplicate = yelp_raw_data['text'] == duplicate_text

你可能会马上感到困惑。我们这里做的事情是将 DataFrame 中的text列与duplicate_text string进行比较。这很奇怪,因为我们似乎是在将一个包含10,000个元素的列表与一个单一的字符串进行比较。显然,答案应该是直接返回false,对吧?

Series有一个非常有趣的特性,就是如果你将它与一个对象进行比较,它将返回一个布尔值的系列,长度与原系列相同,其中每个TrueFalse实例都是对问题“这个元素是否与正在比较的元素相同?”的答案。非常方便!

接下来的代码块将展示这个新 Series 的内容预览。

type(text_is_the_duplicate) # it is a Series of Trues and Falses
text_is_the_duplicate.head() # shows a few Falses out of the Series

在 Python 中,我们可以像对待10一样加减TrueFalse,例如,True + False – True + False + True == 1。因此,我们可以通过对所有值求和来验证这个Series对象是否正确。由于只有两行包含重复文本,Series对象的总和应该是2,而实际结果也是如此!如下所示:

sum(text_is_the_duplicate) # == 2

现在我们有了一系列布尔值,我们可以直接将它们传入我们的 DataFrame,使用括号表示法,获取过滤后的行,如下所示:

filtered_dataframe = yelp_raw_data[text_is_the_duplicate] # the filtered Dataframe

图 3.2 – 表示两行重复的文本、星级和调查得分

图 3.2 – 表示两行重复的文本、星级和调查得分

数据很少是完美的,在处理原始文本时,常常需要检查重复字段的数量。在这个图表中,我们可以看到有两行重复的文本、星级和调查得分。很可能是同一个人给同一个连锁店的两个位置写的评论。

看来我们的怀疑是正确的,有一个人在同一天为两个不同的business IDs列写了完全相同的评论,推测这两个列可能属于同一个链条。让我们继续看接下来的列:

# count               10000
# unique              1
#          top        Review
#          freq       10000
yelp_raw_data['type'].describe()

记得这个列吗?结果发现它们都是完全相同的类型,即review

yelp_raw_data['user_id'].describe()
# count               10000
# unique              6403
#          top        fczQCSmaWF78toLEmb0Zsw
#          freq       38

类似于business_id列,所有 10,000 个值都由 6,403 个独特的用户填写,其中有一个用户评论了 38 次!

在这个例子中,我们无需进行任何转换。

顺序型列

对于顺序列,我们要查看的是datestars。对于这些列,让我们看看describe方法返回的结果:

yelp_raw_data['stars'].describe()
# count     10000.000000
# mean      3.777500
# std       1.214636
# min       1.000000
# 25%       3.000000
# 50%       4.000000
# 75%       5.000000
# max       5.000000

哇!即使这个列是顺序型的,describe方法返回的统计数据就像是数量型列的统计数据一样。这是因为软件看到了一堆数字,假设我们想要像均值、最小值和最大值这样的统计数据。这并不成问题。我们可以使用一个叫做value_counts的方法来查看计数分布,如下所示:

yelp_raw_data['stars'].value_counts()
# 4             3526
# 5             3337
# 3             1461
#       2       927
#       1       749

value_counts方法将返回任何列的值分布。在这种情况下,我们看到星级4是最常*的,拥有3526个值,其次是星级5。我们还可以将这些数据可视化。首先,我们按星级排序,然后使用预构建的plot方法绘制柱状图:

import datetime
dates = yelp_raw_data['stars'].value_counts()
dates.sort_values
dates.plot(kind='bar')

图 3.3 – 星级评分分布显示大多数人给 4 或 5 星,最常*的星级是 4

图 3.3 – 星级评分分布显示大多数人给 4 或 5 星,最常*的星级是 4

从这个图表中,我们可以得出结论:人们显然更倾向于给出好评星级而非差评星级!我们可以对date列进行相同的处理。我会留给你自己尝试。现在,让我们来看一个新的数据集。

数据集 2 – 泰坦尼克号

Titanic 数据集是数据科学中一种“入门”数据集。每个人都在某些教程或书籍中看到过这个数据集。它包含了 1912 年泰坦尼克号撞上冰山时的一部分乘客样本。我向你保证,往后我们会使用更有趣的数据集,但相信我,先从像这样的数据集和 Yelp 开始能更好地热身数据科学技能。

让我们继续导入新的数据集,并使用 head 方法输出前五行:

titanic = pd.read_csv('short_titanic.csv')
titanic.head()

这个表格表示 short_titanic.csv 数据集的 DataFrame。此数据肯定是以行/列结构组织的,就像大多数电子表格数据一样。让我们快速查看它的大小:

titanic.shape
# (891, 5)

我们有 891 行和 5 列。每一行似乎代表船上的一名乘客,至于列,以下列表告诉我们它们表示的内容:

  • 生还:这是一个二元变量,表示乘客是否在事故中幸存(1 表示幸存,0 表示死亡)。这可能属于名义层次,因为只有两个选项。

  • 舱位:这是乘客所乘的舱位(3 表示三等舱,以此类推)。这属于序数层次。

  • 姓名:这是乘客的姓名,绝对属于名义层次。

  • 性别:这表示乘客的性别。它处于名义层次。

  • 年龄:这个有点棘手。可以说,年龄可以放在定性或定量层次;不过,我认为年龄属于定量状态,因此属于比率层次。

至于转换,通常我们希望所有列都是数值型的,无论它们的定性状态如何。这意味着 姓名性别 列必须以某种方式转换为数值列。对于 性别,我们可以将该列更改为如果乘客是女性,则为 1;如果是男性,则为 0。让我们使用 pandas 来进行更改。我们将需要导入另一个 Python 模块,叫做 numpy 或数值 Python,如下所示:

import numpy as np
titanic['Sex'] = np.where(titanic['Sex']=='female', 1, 0)

np.where 方法接收三项内容,如下所示:

  • 一个布尔值列表(TrueFalse

  • 一个新值

  • 一个备份值

该方法会将所有 True 实例替换为第一个值(在本例中为 1),将 False 实例替换为第二个值(在本例中为 0),从而生成一个新的数值列,该列表示与原始 性别 列相同的内容:

titanic['Sex']
# 0         0
# 1         1
# 2         1
# 3         1
# 4         0
# 5         0
# 6         0
# 7         0

让我们使用一个快捷方式,一次性描述所有列,以便我们能快速了解数据的整体情况以及它的大致形态。执行此操作的代码如下所示:

titanic.describe()

图 3.4 – 我们的泰坦尼克号数据集的数值格式列的描述性统计

图 3.4 – 我们的泰坦尼克号数据集的数值格式列的描述性统计

显示这些特征的范围和均值差异很大。请注意,仅仅因为列是“数值型”的,并不意味着它是定量的。尽管Pclass这里显示为整数,但通常建议将其视为定性变量。

这张表列出了泰坦尼克号数据集的描述性统计信息。请注意,我们的定性列被当作定量列处理;然而,我正在寻找一些与数据类型无关的内容。注意count行:SurvivedPclassSex都有891个值(行数),但Age只有714个值。有些值是缺失的!为了再次验证,我们可以使用pandasisnullsum函数,如下所示:

titanic.isnull().sum()
Survived     0
Pclass       0
Name         0
Sex          0
Age          0

这将显示每列中缺失值的数量。所以,Age是唯一一个需要处理缺失值的列。

在处理缺失值时,通常有以下两个选择:

  • 删除含有缺失值的行

  • 尝试填充它

删除这一行是一个简单的选择;然而,你有可能丢失宝贵的数据!例如,在这个案例中,我们有 177 个缺失的年龄值(891-714),这几乎占了数据的 20%。为了填补这些数据,我们可以回去查找历史记录,逐个找出每个人并填写他们的年龄,或者我们可以用占位符值来填充这些年龄。

让我们用数据集中的所有人的*均年龄来填补Age列中的每个缺失值。为此,我们将使用两个新方法,分别是meanfillna。我们使用isnull来告诉我们哪些值是null,而mean函数则为我们提供Age列的*均值。fillna方法是pandas中的一个方法,它用给定的值替换空值:

print sum(titanic['Age'].isnull()) # == 177 missing values average_age = titanic['Age'].mean() # get the average age
titanic['Age'].fillna(average_age, inplace = True) #use the fillna method to remove null values
print sum(titanic['Age'].isnull()) # == 0 missing values

完成了!我们已将每个缺失值替换为26.69,即数据集中的*均年龄。以下代码现在确认没有任何空值:

titanic.isnull().sum()
Survived       0
Pclass         0
Name           0
Sex            0
Age            0

太棒了!没有缺失值,并且我们没有删除任何行。让我们再检查一下数据,看看head的结果:

titanic.head()

图 3.5 – 我们的泰坦尼克号数据集的前五行

图 3.5 – 我们的泰坦尼克号数据集的前五行

在这一点上,我们可以开始提出更复杂的问题——例如,女性或男性的*均年龄是多少? 为了回答这个问题,我们可以按性别进行筛选并计算*均年龄;pandas有一个内置的函数,叫做groupby,如下所示:

titanic.groupby('Sex')['Age'].mean()

这意味着:按Sex列对数据进行分组,然后给我每个组的*均年龄。这将给出如下输出:

Sex
0    30.505824
1    28.216730

我们将提出更多这些困难且复杂的问题,并能够通过 Python 和统计学来回答它们。

总结

探索数据只是数据科学过程中必不可少的步骤之一,在本书中,当我们处理不同的数据集时,我们将持续进行数据探索。通过遵循数据探索的步骤,我们可以转化、分解并标准化我们的数据,以便为建模和分析做好准备。

我们的五个步骤作为数据科学家的标准实践,可以应用于任何需要分析的数据集。虽然它们只是指导方针,但它们提供了一个探索和理解新数据的框架,帮助我们识别趋势、关系和洞察,从而为我们的分析提供参考。

随着我们在本书中的进展,我们将深入探讨使用统计、概率和机器学*模型来分析数据并做出预测。然而,在我们能够完全深入这些更复杂的模型之前,回顾一些支撑这些技术的基础数学知识是非常重要的。在下一章中,我们将介绍一些进行更复杂建模操作所必需的数学知识。别担心——这个过程所需的数学是最基础的,我们会一步步讲解,确保你打下坚实的基础。通过理解这些基础数学,我们可以更好地理解我们将使用的模型和技术,也能更有效地将它们应用于数据分析中。

第四章:基础数学

随着我们深入数据科学的领域,理解基本的数学原理和概念是非常重要的,这些概念是该领域的基础。尽管数学常常被认为令人生畏,但我的目标是尽可能让这个学*过程既有趣又愉快。在本章中,我们将涵盖一些关键主题,如基本符号和术语、对数与指数、集合论、微积分和矩阵(线性)代数。此外,我们还将探讨其他数学领域及其在数据科学和其他科学研究中的应用,包括以下内容:

  • 基本符号/术语

  • 对数/指数

  • 集合论

  • 微积分

  • 矩阵(线性)代数

需要记住的是,正如前面所讨论的,数学是数据科学的三个关键组成部分之一。本章中呈现的概念不仅在后续章节中有用,也有助于理解概率和统计模型。这些是任何有志成为数据科学家的人的基本构建块,因此应该被彻底理解。

作为一名数学老师,我的责任是教育和启发学生,让他们认识到数学在我们日常生活中的不可否认的重要性。从最简单的任务,如浇水和喂养宠物,到更复杂的工作,数学原理和概念始终在发挥作用。即使这些计算和预测不总是有意识地进行,但它们仍然由人脑完成。我的目标是帮助学生理解和欣赏数学在我们日常生活中的基础性作用,并意识到我们每个人内在的数学能力。

基本符号和术语

在接下来的部分,我们将回顾向量、矩阵、算术符号和线性代数的数学概念,以及数据科学家常用的一些更细致的符号表示。

向量和矩阵

向量被定义为具有大小和方向的对象。然而,这一定义有点复杂。就我们的目的而言,向量仅仅是表示一系列数字的单维数组。换句话说,向量就是一个数字列表。

它通常使用箭头或粗体字表示,如下所示:

x→orx

向量被分解为组件,它们是向量的各个成员。我们使用索引符号来表示我们指代的元素,如下所示:

Ifx→=368thenx1=3

在数学中,我们通常将第一个元素称为索引 1,而在计算机科学中,我们通常将第一个元素称为索引 0。记住你使用的是哪种索引系统是很重要的。

在 Python 中,我们可以用多种方式表示数组。我们可以简单地使用 Python 列表来表示前面的数组:x = [3, 6, 8]。然而,最好使用 numpy 数组类型来表示数组,如此处所示,因为它在进行向量操作时提供了更多的实用功能:

import numpy as np
x = np.array([3, 6, 8])

不管 Python 的表示方式如何,向量为我们提供了一种简单的方法来存储单个数据点/观察值的 多维 信息。

如果我们测量一家公司的三个部门员工的*均满意度评分(0-100),HR 部门为 57,工程部门为 89,管理部门为 94,我们可以用以下公式表示这一向量:

X=x1x2x3=578994

这个向量包含了我们数据的三个不同信息点。这是数据科学中向量的完美应用。

你也可以将向量看作是 pandas Series 对象的理论性推广。因此,我们自然需要某种方式来表示 DataFrame。

我们可以扩展数组的概念,使其超越单维并表示多维数据。

矩阵是一个二维的数字数组表示。矩阵(matrix 的复数)有两个主要特点需要我们注意。矩阵的维度用n x mn 乘 m)表示,告诉我们矩阵有n行和m列。矩阵通常用大写加粗字母表示,如X。考虑以下示例:

3485559

这是一个3 x 2 (3 乘 2)矩阵,因为它有三行和两列。

注意

如果矩阵的行数和列数相同,那么它就是方阵

矩阵是我们对 pandas DataFrame 的概括。可以说,它是我们工具箱中最重要的数学对象之一。它用于存储组织好的信息——在我们的案例中,就是数据。

重新审视我们之前的例子,假设我们有三个在不同地点的办公室,每个办公室都有相同的三个部门:人力资源、工程部和管理部。我们可以创建三个不同的向量,每个向量存储一个办公室的满意度评分,如下所示:

x=578994,y=678794,z=659860

然而,这不仅繁琐,而且不可扩展。如果你有 100 个不同的办公室怎么办?在这种情况下,你将需要 100 个不同的一维数组来存储这些信息。

这时,矩阵可以缓解这个问题。我们可以创建一个矩阵,其中每一行代表一个不同的部门,每一列代表一个不同的办公室,如表格 4.1所示:

办公室 1 办公室 2 办公室 3
人力资源 57 67 65
工程部 89 87 98
管理 94 84 60

表 4.1 – 我们希望建模为矩阵的一些示例数据

这样会更加自然。现在,我们去掉标签;我们将得到一个矩阵:

X=576765898798949460

快速练*

以下是一些快速练*,帮助你更好地理解矩阵:

  1. 如果我们增加一个第四个办公室,我们需要新的一行还是一列?

  2. 如果我们添加了第四个办公室,那么矩阵的维度会是多少?

  3. 如果我们从原始 X 矩阵中去掉管理部门,那么新矩阵的维度会是多少?

  4. 计算矩阵元素个数的通用公式是什么?

答案

以下是答案:

  1. 3 x 4

  2. 2 x 3

  3. n × m (n 是行数,m 是列数)

让我们来讨论一下算术符号。

算术符号

在本节中,我们将介绍一些与基本算术相关的符号,这些符号在大多数数据科学教程和书籍中都会出现。

总和

大写的西格玛符号 ∑ 是加法的通用符号。西格玛符号右边通常是可迭代的内容,意味着我们可以逐个遍历它(例如,一个向量)。

例如,我们来创建一个向量 X=[1,2,3,4,5] 的表示。

为了找到内容的总和,我们可以使用以下公式:

∑xi=15

在 Python 中,我们可以使用以下公式:

sum(x)#==15

例如,计算一系列数字的*均值的公式非常常*。如果我们有一个长度为 n 的向量 (x),那么可以按照以下公式计算该向量的*均值:

mean=1/n∑xi=15

这意味着我们将逐一加总 x 中的每个元素,记作 xi,然后将总和乘以 1/n,也就是除以 n(向量的长度)。

在 Python 中,我们可以使用以下公式来计算数组 x 的*均值:

mean=sum(x)/len(x)#==3

点积

点积是一种操作符,类似于加法和乘法。它用于将两个向量结合起来,如下所示:

37.95=3*9+7*5=62

这意味着什么呢?假设我们有一个向量,表示顾客对三种电影类型(喜剧、浪漫和动作)的情感。

注意

在使用点积时,请注意,结果是一个单一的数字,称为标量

在 1 到 5 的评分范围内,一位顾客喜欢喜剧,讨厌浪漫电影,并且对动作片持中立态度。我们可以如下表示:

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block"><mml:mfenced separators="|">mml:mrowmml:mtablemml:mtrmml:mtdmml:mn5</mml:mn></mml:mtd></mml:mtr>mml:mtrmml:mtdmml:mn1</mml:mn></mml:mtd></mml:mtr>mml:mtrmml:mtdmml:mn3</mml:mn></mml:mtd></mml:mtr></mml:mtable></mml:mrow></mml:mfenced></mml:math>

在这里,5 表示他们对喜剧的喜爱,1 表示他们对浪漫电影的厌恶,而3 表示顾客对动作片的冷漠。

现在,假设我们有两部新电影,一部是浪漫喜剧,另一部是搞笑动作片。这些电影将拥有各自的质量向量,如下所示:

m1=451m2=515

在这里,m1 代表我们的浪漫喜剧,m2 代表我们的搞笑动作片。

要做出推荐,我们必须应用顾客对每部电影的偏好的点积。较高的值将获胜,因此将推荐给用户。

让我们计算每部电影的推荐分数。对于电影 1,我们要计算以下内容:

513.451

我们可以将这个问题看作是:

图 4.1 – 如何解释点积

图 4.1 – 如何解释点积

我们得到的答案是 28,但这个数字代表什么?它是在什么尺度上?嗯,任何人能得到的最佳分数是所有值都为 5,结果如下:

555.555=52+52+52=75

最低可能的分数是当所有值为 1 时,如下所示:

111.111=12+12+12=3

因此,我们必须在从375的尺度上考虑28。数字 28 离 3 更近,而不是 75。让我们对电影 2 进行这个尝试:

513.515=(5*5)+(1*1)+(3*5)=41

这个比 28 还高!所以,在电影 1 和电影 2 之间,我们会向用户推荐电影 2。这本质上就是大多数电影预测引擎的工作原理。它们构建了一个客户档案,表示为一个向量。然后,它们将每个电影的向量表示与客户档案相结合(可能通过点积),从那里做出推荐。当然,大多数公司必须在更大的规模上做这件事,这时一种特别的数学领域——线性代数就显得非常有用;我们将在本章后面讨论它。

对数/指数

指数告诉你要将一个数字乘以自身多少次,如图 4.3图 4.4所示:

图 4.2 – 指数告诉你要将一个数字乘以自身多少次

图 4.2 – 指数告诉你要将一个数字乘以自身多少次

对数是回答“什么指数可以从底数得到这个数字?”这个问题的数字。可以表示如下:

图 4.3 – 图 4.3 中的指数以对数形式表示

图 4.3 – 图 4.3 中的指数以对数形式表示

如果这两个概念看起来相似,那么你是对的!指数和对数是密切相关的。事实上,指数和对数是同一个意思!对数就是指数。前面两个方程式是同一个东西的两种表现方式。基本的意思是 2 乘 2 乘 2 乘 2 等于 16。

图 4.5展示了我们如何使用两种版本表达相同的内容。注意我如何使用箭头从对数公式移动到指数公式:

图 4.4 – 对数和指数是一样的!

图 4.4 – 对数和指数是一样的!

考虑以下示例:

  • log3181=4because34=81

  • log5125=3because53=125

让我们重新写一下第一个公式,来注意一个有趣的地方:

log381=4

现在,让我们把 81 替换为等价的表达式,<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msupmml:mrowmml:mn3</mml:mn></mml:mrow>mml:mrowmml:mn4</mml:mn></mml:mrow></mml:msup></mml:math>,如下所示:

log5125=3

有趣的是,3s 似乎可以抵消。在处理比 3 和 4 更难计算的数字时,这一点非常重要。

指数和对数在处理增长时最为关键。通常,如果某个数量在增长(或下降的增长),指数/对数可以帮助模拟这种行为。

例如,数字 e 大约是 2.718,并且有很多实际应用。一个非常常*的应用是存款的利息计算。假设你在银行存入 5000 美元,并且以 3%的年利率进行连续复利。在这种情况下,你可以使用以下公式来模拟你存款的增长:

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block"><mml:mi mathvariant="normal">A</mml:mi>mml:mo=</mml:mo>mml:msupmml:mrow<mml:mi mathvariant="normal">P</mml:mi><mml:mi mathvariant="normal">e</mml:mi></mml:mrow>mml:mrow<mml:mi mathvariant="normal">r</mml:mi><mml:mi mathvariant="normal">t</mml:mi></mml:mrow></mml:msup></mml:math>

在这个公式中,我们有以下内容:

  • A 表示最终金额

  • P 表示本金投资额 (5000)

  • e 表示常数 (2.718)

  • r 表示增长率 (.03)

  • t 表示时间(单位为年)

我们的投资什么时候会翻倍?我需要把钱投资多长时间才能实现 100% 的增长?我们可以用数学形式表示如下:

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block">mml:mn10,000</mml:mn>mml:msupmml:mrowmml:mo=</mml:mo>mml:mn5,000</mml:mn><mml:mi mathvariant="normal">e</mml:mi></mml:mrow>mml:mrowmml:mo.</mml:mo>mml:mn03</mml:mn><mml:mi mathvariant="normal">t</mml:mi></mml:mrow></mml:msup></mml:math>

2=e.03t(dividedby5,000onbothsides)

此时,我们在指数中有一个变量,需要求解。遇到这种情况时,我们可以使用对数符号来计算:

图 4.5 – 从指数形式到对数形式的转换

图 4.5 – 从指数形式到对数形式的转换

这时我们得到的结果是 <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msubmml:mrowmml:mil</mml:mi>mml:mio</mml:mi>mml:mig</mml:mi></mml:mrow>mml:mrowmml:mie</mml:mi></mml:mrow></mml:msub>mml:mo(</mml:mo>mml:mn2</mml:mn>mml:mo)</mml:mo>mml:mo=</mml:mo>mml:mo.</mml:mo>mml:mn03</mml:mn>mml:mit</mml:mi></mml:math>

当我们对一个以 e 为底的数字取对数时,这叫做自然对数。我们可以将对数重写如下:

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block"><mml:mi mathvariant="normal">l</mml:mi><mml:mi mathvariant="normal">n</mml:mi>mml:mo(</mml:mo>mml:mn2</mml:mn>mml:mo)</mml:mo>mml:mo=</mml:mo>mml:mo.</mml:mo>mml:mn03</mml:mn><mml:mi mathvariant="normal">t</mml:mi></mml:math>

使用计算器(或 Python),我们可以发现 <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:mil</mml:mi>mml:min</mml:mi><mml:mfenced separators="|">mml:mrowmml:mn2</mml:mn></mml:mrow></mml:mfenced>mml:mo=</mml:mo>mml:mo.</mml:mo>mml:mn069</mml:mn></mml:math>

0.69−.03t

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block"><mml:mi mathvariant="normal">t</mml:mi>mml:mo=</mml:mo>mml:mn2.31</mml:mn></mml:math>

这意味着我们需要2.31年才能使我们的钱翻倍。

集合理论

集合理论涉及集合层级的数学运算。它有时被认为是支配其余数学的基本定理群体。为了我们的目的,我们将使用集合理论来操作元素群体。

集合是由不同对象组成的集合。

就是这样!集合可以被看作是一个没有重复对象的 Python 列表。Python 中甚至有集合对象:

s = set()
s = set([1, 2, 2, 3, 2, 1, 2, 2, 3, 2])
# will remove duplicates from a list
s == {1, 2, 3}

在 Python 中,大括号{ }可以表示一个集合或字典。请记住,Python 中的字典是由键值对组成的集合。一个示例如下:

dict = {"dog": "human's best friend", "cat": "destroyer of world"} dict["dog"]# == "human's best friend"
len(dict["cat"]) # == 18
but if we try to create a pair with the same key as an existing key dict["dog"] = "Arf"
dict
{"dog": "Arf", "cat": "destroyer of world"}
It will override the previous value
dictionaries cannot have two values for one key.

它们共享这个符号,因为它们具有相同的特性——集合不能有重复元素,就像字典不能有重复的键一样。

集合的大小是集合中元素的数量,表示如下:

|A|=magnitudeofA

我们可以使用len命令在 Python 中获取集合的大小:

# s == {1,2,3}
len(s) == 3 # magnitude of s

注意

空集合的概念是存在的,它用{}表示。这个空集的大小为 0。

如果我们想表示一个元素属于某个集合,可以使用ε符号表示,如下所示:

2∈{1,2,3}

这个符号表示元素2存在于集合123中。如果一个集合完全包含在另一个集合中,我们称其为该大集合的子集

A={1,5,6},B={1,5,6,7,8}

A⊆B

(A 是 B 的子集,因为 A 中的每个元素也在 B 中。)

所以,AB的子集,而B被称为A超集。如果AB的子集,但A不等于B(意味着在B中至少有一个元素不在A中),那么A被称为B真子集

考虑以下示例:

  • 偶数集合是所有整数的子集

  • 每个集合都是它自己的子集,但不是它自己的真子集

  • 所有推文的集合是英文推文的超集

在数据科学中,我们使用集合(和列表)来表示对象的列表,并且常常用来概括消费者的行为。将客户简化为一组特征是常*做法。

假设我们是一个营销公司,试图预测一个人想去哪里购物。我们已经获得了一个用户之前访问过的服装品牌集合,我们的目标是预测一个新的他们也会喜欢的商店。假设某个特定用户之前去过以下商店:

user1 = {"Target","Banana Republic","Old Navy"}
note that we use {} notation to create a set
compare that to using [] to make a list

所以,user1之前在TargetBanana RepublicOld Navy购物。让我们再看看另一个用户,称为user2,如下所示:

user2 = {"Banana Republic","Gap","Kohl's"}

假设我们想知道这两个用户有多相似。根据我们掌握的有限信息,一种定义相似性的方法是看他们都去过多少家商店。这个过程被称为交集

两个集合的交集是一个包含出现在两个集合中的元素的集合。它使用∩符号表示,如下所示:

user1∩user2={BananaRepublic}

user1∩user2={BananaRepublic}

|user1∩user2|=1

两个用户的交集只有一个商店。因此,直接看上去,这似乎不太理想。然而,每个用户在他们的集合中只有三个元素,所以拥有 1/3 的交集并不显得那么糟糕。假设我们想知道这两个用户之间有多少个商店被表示出来,这就叫做并集

两个集合的并集是一个集合,它的元素出现在任意一个集合中。它用<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:mo∪</mml:mo></mml:math>符号表示,如下所示:

user1∪user2={BananaRepublic,Target,OldNavy,Gap,Kohl′s}

在查看user1user2之间的相似性时,我们应该使用user1user2之间有一个共同元素,而这两个用户之间总共有五个不同的元素。因此,我们可以定义两个用户之间的相似度如下:

|user1∩user2||user1∪user2|=15=.2

这在集合论中有一个名字:Jaccard 度量。一般来说,对于AB集合,Jaccard 度量(Jaccard 相似度)定义如下:

JS(A,B)=|A∩B||A∪B|

它也可以定义为两个集合交集的大小与两个集合并集的大小之比。

这为我们提供了一种量化集合之间相似度的方法。

直观上,Jaccard 衡量是一个介于 01 之间的数值,当该数值越接近 0 时,表示两者差异越大;当数值越接近 1 时,表示两者相似度越高。

如果我们考虑这个定义,那么它就有意义了。再看一下这个衡量公式:

JS(A,B)=NumberofstorestheyshareincommonUniquenumberofstorestheylikedcombined

在这里,分子表示用户共同拥有的商店数量(即他们喜欢在那里购物),而分母表示他们喜欢的独特商店数量的总和。

我们可以使用一些简单的代码在 Python 中表示这一点,如下所示:

user1 = {"Target", "Banana Republic", "Old Navy"} user2 = {"Banana Republic", "Gap", "Kohl's"}
def jaccard(user1, user2):
stores_in_common = len(user1 & user2)
stores_all_together = len(user1 | user2)
return stores / stores_all_together
   # using our new jaccard function
jaccard(user1, user2) == # 0.2 or 1/5

集合论在我们进入概率世界和处理高维数据时变得非常重要。我们可以使用集合来表示现实世界中发生的事件,而概率则是建立在集合论基础上的一套词汇。

线性代数

正如我们之前看到的,电影推荐引擎利用了几个数学概念,以便向用户提供准确的个性化推荐。然而,在有 10,000 部电影可供推荐的情况下,计算效率变得尤为重要。线性代数,作为一种涉及矩阵和向量的数学领域,提供了进行这些计算所需的工具,使得计算更加高效。

线性代数专注于分析和操作矩阵与向量,以提取有用的信息并将其应用于实际情境。随着我们深入学*,理解线性代数的基本原理非常重要。因此,在进一步探讨之前,我们将回顾线性代数的几个关键规则。

矩阵乘法

和数字一样,我们可以将矩阵相乘。矩阵乘法本质上是一种批量化进行点积的方式。比如,我们可以尝试将以下矩阵相乘:

155878.3425

我们需要考虑几点:

  • 与数字不同,矩阵的乘法是非交换的,这意味着你相乘矩阵的顺序非常重要。

  • 要进行矩阵乘法,它们的维度必须匹配。这意味着第一个矩阵的列数必须与第二个矩阵的行数相同。

为了记住这一点,可以写出矩阵的维度。在这个例子中,我们有一个3 x 2的矩阵与一个2 x 2的矩阵相乘。如果第一个矩阵的第一维的第二个数与第二个矩阵的第二维的第一个数相同,就可以进行矩阵乘法。结果矩阵的维度将等于维度对外部的数字(你没有圈出的那些)。在这个例子中,结果矩阵的维度是3 x 2

如何将矩阵相乘

矩阵相乘有一个简单的步骤可以遵循。基本上,我们是在执行一系列的点积运算。

回顾我们之前的示例问题,问题如下:

155878.3425

我们知道,得到的矩阵的维度将是3 x 2。所以,我们知道它的形状大致是这样的:

m11m12m21m22m31m32

注意

请注意,矩阵的每个元素使用双重索引。第一个数字代表行,第二个数字代表列。所以,m32 元素是第二列第三行的元素。每个元素是原始矩阵的行与列之间的点积结果。

mxy元素是第一矩阵的第x行与第二矩阵的第y列之间的点积结果。我们来解决几个:

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block">mml:msubmml:mrowmml:mim</mml:mi></mml:mrow>mml:mrowmml:mn11</mml:mn></mml:mrow></mml:msub>mml:mo=</mml:mo><mml:mfenced separators="|">mml:mrow<mml:mfrac linethickness="0pt">mml:mrowmml:mn1</mml:mn></mml:mrow>mml:mrowmml:mn5</mml:mn></mml:mrow></mml:mfrac></mml:mrow></mml:mfenced>mml:mo.</mml:mo><mml:mfenced separators="|">mml:mrow<mml:mfrac linethickness="0pt">mml:mrowmml:mn3</mml:mn></mml:mrow>mml:mrowmml:mn2</mml:mn></mml:mrow></mml:mfrac></mml:mrow></mml:mfenced>mml:mo=</mml:mo>mml:mn13</mml:mn></mml:math>

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block">mml:msubmml:mrowmml:mim</mml:mi></mml:mrow>mml:mrowmml:mn12</mml:mn></mml:mrow></mml:msub>mml:mo=</mml:mo><mml:mfenced separators="|">mml:mrow<mml:mfrac linethickness="0pt">mml:mrowmml:mn1</mml:mn></mml:mrow>mml:mrowmml:mn5</mml:mn></mml:mrow></mml:mfrac></mml:mrow></mml:mfenced>mml:mo.</mml:mo><mml:mfenced separators="|">mml:mrow<mml:mfrac linethickness="0pt">mml:mrowmml:mn4</mml:mn></mml:mrow>mml:mrowmml:mn5</mml:mn></mml:mrow></mml:mfrac></mml:mrow></mml:mfenced>mml:mo=</mml:mo>mml:mn29</mml:mn></mml:math>

最终,我们会得到一个结果矩阵,如下所示:

132931603768

太好了!我们回到电影推荐的例子。回忆一下用户的电影类型偏好:喜剧、浪漫和动作,如下所示:

U=userprefs=513

现在,假设我们有 10,000 部电影,每部电影都有这三类的评分。为了进行推荐,我们需要将偏好向量与每一部 10,000 部电影进行点积计算。我们可以使用矩阵乘法来表示这一过程。

我们不需要将它们全部写出来,而是使用矩阵表示法。我们已经有了U,它在这里被定义为用户的偏好向量(也可以视作一个3 x 1的矩阵),但我们还需要一个电影矩阵:

M=movies=3x10,000

现在,我们有两个矩阵;一个是3 x 1,另一个是3 x 10,000。由于维度不匹配,我们不能直接相乘。我们需要稍微调整一下U。为此,我们可以对矩阵进行转置(将所有行变为列,将所有列变为行)。这样就能改变维度:

UT=transposeofU=(513)

现在,我们有了可以相乘的两个矩阵。让我们来可视化一下这个过程:

(513).45....14....51x13....3x1,000

生成的矩阵将是一个1 x 1,000的矩阵(一个向量),包含 10,000 个每部电影的预测结果。让我们在 Python 中试试这个:

import numpy as np
# create user preferences
user_pref = np.array([5, 1, 3])
create a random movie matrix of 10,000 movies movies = np.random.randint(5,size=(3,1000))+1
Note that the randint will make random integers from 0-4
so I added a 1 at the end to increase the scale from 1-5

我们正在使用numpy数组函数来创建我们的矩阵。我们将有一个user_pref矩阵和一个movies矩阵来表示我们的数据。

为了检查我们的矩阵维度,我们可以使用numpy shape变量,如下所示:

print(user_pref.shape) # (1, 3)
print(movies.shape) # (3, 1000)

这没问题。最后但同样重要的是,让我们使用numpy的矩阵乘法方法(称为 dot)来执行这个操作:

np.dot does both dot products and matrix multiplication np.dot(user_pref, movies)

结果是一个整数数组,表示每部电影的推荐结果。

为了快速扩展这一点,让我们运行一些代码来预测超过 10,000 部电影的结果:

import numpy as np
import time
for num_movies in (10000, 100000, 1000000, 10000000, 100000000):
movies = np.random.randint(5,size=(3, num_movies))+1
now = time.time()
np.dot(user_pref, movies)
print((time.time() - now), "seconds to run", num_movies, "movies")
0.000160932540894 seconds to run 10000 movies
0.00121188163757 seconds to run 100000 movies
0.0105860233307 seconds to run 1000000 movies
0.096577167511 seconds to run 10000000 movies
4.16197991371 seconds to run 100000000 movies

使用矩阵乘法运行 1 亿部电影仅用了稍长于 4 秒。

总结

在本章中,我们回顾了一些基本的数学原理,这些原理将在本书接下来的内容中变得非常重要。无论是对数/指数、矩阵代数还是比例关系,数学在分析数据以及我们生活的许多方面都扮演着重要角色。

接下来的章节将深入探讨数学的两个重要领域:概率论和统计学。我们的目标是定义并解释这两个巨大的数学领域中的最小和最大定理。

在接下来的几章中,所有的内容将开始结合起来。到目前为止,我们已经看过了数学实例、数据探索指南以及对数据类型的基本理解。现在是时候将所有这些概念串联在一起了。

第五章:不可能或不太可能——概率的温和介绍

在接下来的几章中,我们将探讨概率和统计作为检视数据驱动的情境和现实世界场景的方法。概率规则掌握着预测的基础。我们使用概率来定义事件发生的机会。

在本章中,我们将探讨以下主题:

  • 我们所说的“概率”是什么意思?

  • 频率派方法与贝叶斯方法之间的差异

  • 如何可视化概率

  • 如何利用概率规则

  • 使用混淆矩阵查看基本指标

概率将帮助我们建模包含随机性和机遇的现实生活事件。在接下来的两章中,我们将探讨概率定理背后的术语以及如何将其应用于建模可能会突然出现的情况。

基本定义

概率的最基本概念之一是过程的概念。过程是导致结果的行为,例如掷骰子或访问网站。

事件是一个过程的结果集合,例如掷硬币得到正面或在仅四秒钟后离开网站。简单事件是过程的一个结果/事件,无法进一步分解。例如,掷两个骰子可以分解成两个简单事件:掷骰子 1 和掷骰子 2。

样本空间是一个过程的所有可能简单事件的集合。例如,进行一次实验,连续掷三次硬币。这个实验的样本空间大小是多少?

答案是八,因为结果可以是以下样本空间中的任何一种可能性:{HHH, HHT, HTT, HTH, TTT, TTH, THH, 或 THT}

我们所说的“概率”是什么意思?

事件概率表示该事件发生的频率或机会。

对于符号表示,如果 A 是一个事件,P(A)表示事件发生的概率。

我们可以按如下方式定义事件 A 的实际概率:

P(A)=numberofwaysAoccurssizeofsamplespace

这里,A 是我们关注的事件。可以将整个事件宇宙看作是一个包含所有可能性的大圆,而单个事件 A 则是其中的一个小圆,如下图所示:

图 5.1 – 一个事件(通常用字母如 A 表示)只是可能事件宇宙中的一个单一事件

图 5.1 – 一个事件(通常用字母如 A 表示)只是可能事件宇宙中的一个单一事件。

现在,让我们假设我们的宇宙涉及一项关于人类的研究,事件 A 是研究中患有癌症的人群。

如果我们的研究有 100 个人,且事件 A 发生在 25 个人身上,那么事件 A 的概率或 P(A) 是 25/100。

任何事件的最大概率为 1。可以理解为红色圆圈扩大到足够大,变成了宇宙的大小(更大的圆圈)。

最基本的例子(我保证后面会更有趣)是硬币抛掷。假设我们有两个硬币,想要知道抛出两个正面的概率。我们可以很容易地计算出两枚硬币成为两个正面的方式。只有一种!两枚硬币必须都是正面。那么,有多少种可能呢?可以是两个正面、两个反面或是正反组合。

首先,让我们定义事件 A。A 是指出现两个正面的事件。A 发生的方式有 1 种。

这个实验的样本空间是 {HH, HT, TH, TT},其中每个由两个字母组成的单词表示第一次和第二次抛硬币的结果。样本空间的大小为 4。所以,P(获得两个正面) = 1/4

让我们参考一个简单的表格来证明这一点。下表将硬币 1 的选项作为列,硬币 2 的选项作为行。在每个单元格中,要么是 正确,要么是 错误正确值表示满足条件(两个正面),而错误则表示其他情况:

硬币 1 为正面 硬币 1 为反面
硬币 2 为正面 正确 错误
硬币 2 为反面 错误 错误

表 5.1 – 这个二项表显示了如果你抛掷两个独立的硬币(硬币 1 和硬币 2),可能出现的四种情况。

让我们通过一个例子来帮助理解前面的图;例如,右下角的错误值表示两枚硬币都为反面的情况。只有四种可能的组合,因此我们可以说实验的“样本空间”大小为 4。

因此,当我们抛掷这两个硬币时,可能的结果有四种,其中只有一种结果符合我们的要求。

贝叶斯方法与频率主义

上面的例子说实话太简单了。实际上,我们几乎不可能真正计算出某件事情发生的所有方式。例如,假设我们想知道一个人每天至少吸烟一次的概率。如果我们想用经典方法(前面的公式)来解决这个问题,我们需要弄清楚一个人吸烟的不同方式——一个人每天至少吸烟一次——这几乎是不可能的!

在面对这样的问题时,实际计算概率时有两种主要的思维方式:频率派方法贝叶斯方法。本章将重点讨论频率派方法,而下一章将深入探讨贝叶斯方法。

频率派方法

在频率派方法中,事件的概率通过实验计算得出。它利用过去的数据来预测未来事件发生的机会。基本公式如下:

P(A)=numberoftimesAoccurrednumberoftimestheprocedurewasrepeated

基本上,我们观察事件的多个实例,并统计满足条件 A 的次数。这些数值的比值是概率的近似值。

贝叶斯方法则不同,它要求通过理论手段来推断概率。采用贝叶斯方法时,我们需要对事件及其发生的原因进行更为深思熟虑的分析。两种方法并非在所有情况下都是完全正确的答案,通常取决于具体问题和使用任何一种方法的难度。

频率派方法的核心是相对频率。

相对频率是事件发生的频率,即事件发生的次数除以总观察次数。

示例 – 市场统计

假设你对确定一个访问你网站的人之后有多大可能再次访问感兴趣。这有时被称为重复访客的比例。在前面的定义中,我们将事件 A 定义为访问者再次访问该网站。然后我们必须计算一个人如何能再次访问,这实际上是没有意义的!在这种情况下,很多人会转向贝叶斯方法;然而,我们可以计算所谓的相对频率。

所以,在这种情况下,我们可以获取访客日志并计算事件 A(重复访问者)的相对频率。假设在过去的一周中,1,458 名独立访客中有 452 名是重复访客。我们可以这样计算:

RF(A)=4521458=0.31

所以,大约 31% 的访客是重复访问者。

大数法则

我们之所以可以使用频率派方法来解决诸如营销示例等问题,正是因为大数法则,它指出如果我们重复执行某个过程多次,相对频率的概率将接近实际的概率。让我们尝试用 Python 来演示这一点。

如果我问你 1 和 10 的*均值,你会很快回答大约是 5。这个问题本质上与让你找出 1 到 10 之间的*均数是一样的。让我们设计这个实验,如下所示。

Python 会选择 n 个介于 1 到 10 之间的随机数并计算它们的*均值。

我们将多次重复这个实验,每次使用更大的 n,然后绘制结果图。步骤如下:

  1. 选取一个介于 1 到 10 之间的随机数并计算其*均值。

  2. 选取两个介于 1 到 10 之间的随机数并计算它们的*均值。

  3. 选取三个介于 1 到 10 之间的随机数并计算它们的*均值。

  4. 选取 10,000 个介于 1 到 10 之间的随机数并计算它们的*均值。

  5. 绘制结果图。

让我们看一下代码:

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
%matplotlib inline
results = []
for n in range(1, 10000):
    nums = np.random.randint(low=1,high=10, size=n) # choose n numbers between 1 and 10
    mean = nums.mean()# find the average of
these numbers
    results.append(mean) # add the average to a
running list
# POP QUIZ: How large is the list results? len(results) # 9999
This was tricky because I took the range from 1 to 10000 and usually we do from 0 to 10000
df = pd.DataFrame({ 'means' : results})
print (df.head()) # the averages in the beginning are all over the place!
means
9.0
5.0
6.0
4.5
4.0
print (df.tail()) # as n, our size of the sample size, increases, the averages get closer to 5!
means
4.998799
5.060924
4.990597
5.008802
4.979198
df.plot(title='Law of Large Numbers')
plt.xlabel("Number of throws in sample")
plt.ylabel("Average Of Sample")

结果图如图 5.2所示:

图 5.2 – 大数法则的图形表示

图 5.2 – 大数法则的图形表示

如果我们取 10,000 个介于 1 到 10 之间的随机数并计算它们的*均值,得到的数值会非常接近 5。这是因为当我们增加实验的样本量时,得到的值会接近真实的*均值。

很酷,对吧?这实际上向我们展示的是,随着相对频率的样本量增大,频率逐渐接近实际的*均值(概率)5。

在我们的统计章节中,我们将更加严格地定义这一法则,但现在只需知道,它用于将事件的相对频率与其实际概率联系起来。

复合事件

有时候,我们需要处理两个或更多的事件。这些被称为复合事件。复合事件是指由两个或多个简单事件组合而成的事件。当发生这种情况时,我们需要一种特殊的符号表示。

给定事件 A 和 B,请注意以下几点:

  • 事件 A 和 B 同时发生的概率是 P(A ∩ B) = P(A 和 B)

  • 事件 A 或 B 发生的概率是 P(A ∪ B) = P(A 或 B)

理解为何我们对这些复合事件使用集合符号是非常重要的。还记得我们早些时候如何用圆形表示宇宙中的事件吗?假设我们的宇宙是 100 个人,他们参加了一个新癌症测试的实验:

图 5.3 – 在我们的宇宙中进行概率和统计,宇宙是由 100 人和事件 A 组成

图 5.3 – 在我们的宇宙中进行概率和统计,宇宙是由 100 人和事件 A 组成

请记住,当我们谈论像 A 这样的事件时,有时是指我们试图帮助的人。概率与统计通常是为了帮助我们的人类同胞而进行的

在前面的图示中,红色圆圈 A 代表实际患有癌症的 25 人。使用相对频率法,我们可以说 P(A) = 患癌人数/研究中的总人数,即 25/100 = ¼ = 0.25。这意味着某人患癌症的概率是 25%。

让我们引入第二个事件,称为 B,如下所示,B 包含测试结果为阳性(即测试声称他们患有癌症)的人。假设这包括 30 人。那么,P(B) = 30/100 = 3/10 = 0.3。这意味着测试结果为阳性的概率是 30%:

图 5.4 – 我们经常需要考虑多个事件,因此我们将事件 B 与事件 A 一起考虑

图 5.4 – 我们经常需要考虑多个事件,因此我们将事件 B 与事件 A 一起考虑

这两个事件是独立的,但它们相互作用。也就是说,它们可能会交叉或有共同的个体,示例如下:

图 5.5 – 当有两个事件有可能同时发生时,我们称这些事件为交集

图 5.5 – 当有两个事件有可能同时发生时,我们称这些事件为交集

在 A 和 B 都占据的区域中的任何人,也就是 A 与 B 的交集,或者说 A ∩ B,是那些测试结果显示为癌症阳性(A),而他们实际上也确实患有癌症的人。假设这是 20 人。测试显示 20 人是阳性,也就是说,他们患有癌症,如下所示:

图 5.6 – 如果事件 A 代表患有癌症的人,B 代表癌症检测结果为阳性的人,那么 A 和 B 的交集表示那些被告知自己患有癌症且检测结果准确的人

图 5.6 – 如果事件 A 代表患有癌症的人,B 代表癌症检测结果为阳性的人,那么 A 和 B 的交集表示那些被告知自己患有癌症且检测结果准确的人

这意味着P(A 和 B) = 20/100 = 1/5 = 0.2 = 20%。

如果我们想说某人患有癌症,或者测试结果为阳性,这将是两个事件的总和(或并集),即 5、20 和 10 的总和,结果是 35。因此,35/100 的人要么患有癌症,要么测试结果为阳性。这意味着 P(A 或 B) = 35/100 = 0.35 = 35%。

总的来说,我们有以下四个不同类别的人:

  • 粉色:指的是那些患有癌症并且测试结果为阴性的人

  • 紫色A 与 B 的交集):这些人既患有癌症又测试结果为阳性

  • 蓝色:指的是那些没有癌症并且测试结果为阳性的人

  • 白色:指的是那些没有癌症并且测试结果为阴性的人

因此,实际上,测试只有在白色和紫色区域是准确的。在蓝色和粉色区域,测试结果是不准确的。

条件概率

假设我们从这项研究的 100 个人中随机选择一个人。我们还假设你被告知他们的测试结果是阳性。那么,我们知道事件 B 已经发生,即他们的检测结果是阳性。现在的问题是:他们有癌症的概率是多少,也就是 P(A)?这就是所谓的在 B 条件下 A 的条件概率P(A|B)。本质上,它要求你计算在另一个事件已经发生的情况下,某个事件发生的概率。

你可以将条件概率看作是改变相关的样本空间。P(A|B)(称为在 B 的条件下 A 的概率)是指,假设我的整个样本空间现在是 B,A 发生的概率是多少?这也叫做转化样本空间。

放大我们之前的图表,我们的样本空间现在是 B,我们关心的是 B 内部的 AB(A 和 B)。

该公式可以表示如下:

P(A|B)=P(AandB)/P(B)=(20/100)/(30/100)=20/30=0.66=66%

如果测试结果为阳性,那么该人患癌症的概率是 66%。实际上,这是实验者最关心的主要概率。他们想知道测试在预测癌症方面的准确性。

如何利用概率规则

在概率论中,我们有一些规则,当可视化计算变得过于繁琐时,这些规则变得非常有用。它们帮助我们轻松计算复合概率。

加法规则

加法规则用于计算事件的概率。为了计算 P(A ∪ B),或者 P(A 或 B),我们使用以下公式:

P(A∪B)=P(A)+P(B)−P(A∩B)

公式的第一部分(P(A) + P(B))帮助我们得到两个事件的并集;我们需要将圆形区域的面积加起来。但为什么要减去 P(A 和 B)呢?这是因为当我们加上两个圆时,我们已经把交集的面积加了两次,如下图所示:

图 5.7 – 加法规则:计算两个事件的概率,并小心不要重复计算它们的交集

图 5.7 – 加法规则:计算两个事件的概率,并小心不要重复计算它们的交集

你看,两个红色圆圈都包括了 A 和 B 的交集吗?所以,当我们将它们加在一起时,我们需要减去其中的一个交集区域,以纠正这一点,从而得出我们的公式。

你会回忆起我们想要计算的是既得癌症又得到阳性测试结果的人数。如果 A 表示某人得了癌症,B 表示测试结果为阳性,那么我们有以下公式:

P(AorB)=P(A)+P(B)−P(AandB)=0.25+0.30−0.2=0.35

互斥性

我们说两个事件是互斥的,如果它们不能同时发生。这意味着 A∩B=∅,或者只是说这两个事件的交集是空集。当这种情况发生时,P(A ∩ B) = P(A 和 B) = 0。

如果两个事件是互斥的,那么以下规则适用:

P(A∪B)=P(AorB)

=P(A)+P(B)−P(A∩B)=P(A)+P(B)

这使得加法规则变得更加简单。以下是一些互斥事件的例子:

  • 一位顾客同时在 Twitter 和 Facebook 上首次访问你的网站

  • 今天是星期六,而今天是星期三

  • 我没有通过经济学 101,但我通过了经济学 101

这些事件不能同时发生。

乘法规则

乘法规则用于计算事件的概率。要计算 P(A ∩ B) = P(A 和 B),我们使用以下公式:

P(A∩B)=P(AandB)=P(A)P(B|A)

为什么我们用 B|A 而不是 B?这是因为 B 可能依赖于 A。如果是这种情况,仅仅将 P(A)和 P(B)相乘并不能给我们完整的视角。

在我们的癌症试验例子中,假设我们要求 P(A 和 B)。为了做到这一点,我们重新定义 A 为试验呈阳性,而 B 为病人患有癌症(因为我们给事件起什么名字并不重要)。该方程将如下所示:

P(A∩B)=P(AandB)=P(A)P(B|A)=0.3*0.6666=0.2=20%

很难看出使用条件概率的真正必要性,因此我们再尝试一个更难的问题。

比如,在随机选取的 10 人中,6 人使用 iPhone,4 人使用安卓手机。那么,如果我随机选两个人,他们都使用 iPhone 的概率是多少?这个例子可以通过事件空间重新叙述如下。

我有以下两个事件:

  • A:此事件表示我第一次选择到有 iPhone 的人

  • B:此事件表示我第二次选择到有 iPhone 的人

所以,基本上我想要的是:

  • P(A 和 B):P(我选择一个有 iPhone 的人和另一个有 iPhone 的人)。所以,我们可以使用公式 P(A 和 B) = P(A) P(B|A)。

P(A)很简单,对吧?10 个人中有 6 个人有 iPhone。所以,我有 6/10 = 3/5 = 0.6 的机会选择 A。这意味着 P(A) = 0.6。

所以,如果我有 0.6 的机会选择到有 iPhone 的人,那么选择到两个人的概率应该就是 0.6 * 0.6,对吧?

等等!我们现在只剩下 9 个人来选择第二个人了,因为有一个已经被选走。所以,在我们新的样本空间中,总共有 9 个人,其中 5 个有 iPhone,4 个有安卓手机,因此 P(B) = 5/9 = 0.555。

所以,选择两个人都有 iPhone 的概率是 0.6 * 0.555 = 0.333 = 33%。

我有 1/3 的机会从 10 个人中选择两个都有 iPhone 的人。条件概率在乘法规则中非常重要,因为它可以大幅改变你的答案。

独立性

两个事件是独立的,当其中一个事件不影响另一个事件的结果时,即 P(B|A) = P(B)且 P(A|B) = P(A)。

如果两个事件是独立的,那么适用以下公式:

P(A∩B)=P(A)P(B|A)=P(A)P(B)

一些独立事件的例子如下:

  • 在旧金山下雨,在印度出生了一只小狗

  • 我抛硬币得到正面,再抛一次硬币得到反面

这些事件对彼此没有影响。

互补事件

A 的补集是 A 的反面或否定。如果 A 是一个事件,那么Ā表示 A 的补集。例如,如果 A 是某人得癌症的事件,Ā则是某人没有癌症的事件。

计算Ā的概率,使用以下公式:

P(Ā)=1−P(A)

例如,当你掷两个骰子时,掷出的点数大于 3 的概率是多少?

让 A 代表掷出大于 3 的点数。

以下公式表示掷出 3 点或以下的概率。

P(A)=1−P(Ā)

PA=l−P2+P3=1−236+236=1−436=3236=89=0.89

例如,一个初创团队即将与三位投资者会面。我们有以下几种概率:

  • 从第一次会议获得资金的机会为 60%

  • 从第二次会议获得资金的机会为 15%

  • 从第三次会议获得资金的机会为 45%

他们从至少一次会议中获得资金的概率是多少?

设 A 为至少从一个投资者那里获得资金的团队,Ā为没有获得任何投资的团队。P(A)可以按以下方式计算:

P(A)=1−P(Ā)

为了计算 P(Ā),我们需要计算以下内容:

P(Ā) = P(投资者 1 未投资且投资者 2 未投资且投资者 3 未投资)

假设这些事件是独立的(它们之间没有联系):

P(Ā) = P(投资者 1 未投资) * P(投资者 2 未投资) * P(投资者 3 未投资) =

0.4*0.85*0.55=0.187

P(A)=1−0.187=0.813=81%

所以,初创团队有 81%的机会从至少一次会议中获得资金!

二分类器介绍

不深入探讨机器学*术语,我们之前提到的癌症测试就是所谓的二分类器,即它仅从两个选项中进行预测:患癌或未患癌。在处理二分类器时,我们可以绘制所谓的混淆矩阵,它是一个 2 x 2 的矩阵,包含我们实验的四种可能结果。

我们来尝试一些不同的数字。假设有 165 人参与了研究。因此,我们的n(样本量)是 165 人。所有 165 人都进行了测试,并被询问是否患有癌症(通过各种其他方式提供)。下面的混淆矩阵展示了这个实验的结果:

图 5.8 – 混淆矩阵

图 5.8 – 混淆矩阵

该矩阵显示有 50 人被预测为没有癌症且实际没有癌症,100 人被预测为有癌症且实际患有癌症,等等。我们有以下四类,每一类都有不同的名称:

  • 真阳性是测试正确地预测为阳性(癌症)== 100

  • 真阴性是测试正确地预测为阴性(无癌症)== 50

  • 假阳性是测试错误地预测为阳性(癌症)== 10

  • 假阴性是测试错误地预测为阴性(无癌症)== 5

前两类表示测试结果正确,或为真实的。后两类表示测试结果错误,或为假。

假阳性有时被称为类型 I 错误,而假阴性被称为类型 II 错误

我们将在后续章节中详细讨论不同类型的错误。现在,我们只需要理解为什么我们使用集合符号来表示复合事件的概率。这是因为它们确实是复合事件。当事件 A 和 B 存在于同一个领域时,我们可以使用交集和并集来表示它们同时发生,或者表示一个事件发生而另一个没有发生。

我们将在后续章节中更深入地探讨这一点,但现在引入它是很好的。

总结

在本章中,我们回顾了概率的基础知识,并将在下一章中继续深入探讨这一领域。我们大部分的思考方式是频率学派,并表达了实验基本原理及使用概率预测结果的方法。

下一章将讨论贝叶斯概率方法,并探讨使用概率解决更复杂问题的方式。我们将把这些基本的概率原则应用于更复杂的场景。

第六章:高级概率

在上一章中,我们回顾了概率的基础知识,以及如何将简单的定理应用于复杂的任务。简要总结一下,概率是用来建模可能发生或不发生的事件的数学方法。我们使用公式来描述这些事件,甚至研究多个事件如何一起发生。

在本章中,我们将探讨更复杂的概率定理,以及如何将它们用于预测能力。高级主题,如贝叶斯定理随机变量,催生了许多常*的机器学*算法,比如朴素贝叶斯算法(本书中也有介绍)。

本章将重点讨论概率论中的一些高级主题,包括以下内容:

  • 穷尽事件

  • 贝叶斯定理

  • 基本预测规则

  • 随机变量

在后续章节中,我们将重新审视贝叶斯定理,并利用它创建一个非常强大且快速的机器学*算法,称为朴素贝叶斯算法。这个算法捕捉了贝叶斯思维的力量,并将其直接应用于预测学*的问题。现在,让我们开始了解贝叶斯思维吧!

贝叶斯思想回顾

在上一章中,我们简要谈到了一些贝叶斯思维的方式。请记得,贝叶斯思维的核心是让数据塑造并更新我们的信念。我们从先验概率开始,也就是我们对某个假设的直觉猜测,然后通过一些数据来得到后验概率,也就是在给定数据的情况下,我们对该假设的看法。

贝叶斯定理

贝叶斯定理无疑是贝叶斯推理中最著名的部分。回顾一下,我们之前定义了以下内容:

  • P(A) = 事件 A 发生的概率

  • P(A|B) = 在 B 发生的前提下,A 发生的概率

  • P(A, B) = 事件 A 和 B 同时发生的概率

  • P(A, B) = P(A) * P(B|A)

最后一项可以解读为:“事件 A 和 B 同时发生的概率等于事件 A 发生的概率乘以在事件 A 已经发生的情况下,事件 B 发生的概率。”

从最后一项开始,我们知道以下内容:

P(A,B)=P(A)*P(B|A)

P(B,A)=P(B)*P(A|B)

我们还知道:

P(A,B)=P(B,A)

所以,我们可以将这些结合在一起,看到:

P(B)*P(A|B)=P(A)*P(B|A)

两边除以 P(B)得到贝叶斯定理,如下所示:

P(A|B)=P(A)*P(B|A)P(B)

因此,我们的最终结果就是贝叶斯定理!这是从 P(A|B)得到 P(B|A)(如果你只有其中一个)以及从 P(A)得到 P(A|B)(而不知道 B)的方法。

让我们尝试使用术语假设数据来思考贝叶斯。

假设 H = 关于给定数据的你的假设,D = 给定给你的数据。

贝叶斯可以解释为试图计算 P(H|D)(即我们的假设在观察到的数据下正确的概率)。

让我们使用之前的术语:

P(H|D)=P(D|H)P(H)P(D)

让我们来看看那个公式:

  • P(H) 是我们观察数据之前的假设概率,称为先验概率或简称先验

  • P(H|D) 是我们观察数据后要计算的假设概率,称为后验概率

  • P(D|H) 是在给定假设下数据的概率,称为似然

  • P(D) 是在任何假设下数据的概率,称为归一化常数

这个概念与机器学*和预测分析的思想非常相似。在很多情况下,当我们考虑预测分析时,我们使用给定的数据来预测结果。使用当前的术语,H(我们的假设)可以视为我们的结果,而 P(H|D)(在给定数据的情况下,我们的假设为真的概率)则是另一种说法:根据眼前的数据,我的假设正确的概率是多少?

让我们来看一个如何在工作场所使用贝叶斯公式的例子。

假设你有两个人负责为公司写博客文章,Lucy 和 Avinash。从过去的表现来看,你喜欢 Lucy 的作品 80%,而只喜欢 Avinash 的作品 50%。早上,一篇新的博客文章送到了你的桌面,但没有提到作者。你喜欢这篇文章。A+。那么,这篇文章来自 Avinash 的概率是多少?每位博客作者的写作速度差不多。

在我们慌乱之前,让我们做任何一位经验丰富的数学家(现在是你)会做的事。让我们列出所有信息,如下所示:

  • H(假设)= 这篇博客来自 Avinash。

  • D(数据)= 你喜欢这篇博客文章。

  • P(H|D) = 在你喜欢它的情况下,文章来自 Avinash 的概率。

  • P(D|H) = 在假设它来自 Avinash 的情况下,你喜欢它的概率。

  • P(H) = 文章来自 Avinash 的概率。

  • P(D) = 你喜欢一篇文章的概率。

请注意,其中一些变量没有上下文几乎毫无意义。P(D),即你喜欢任何放在桌子上的文章的概率,是一个奇怪的概念,但相信我——在贝叶斯公式的背景下,它很快会变得非常相关。

同时,请注意,在最后两项中,他们假设没有其他因素。P(D) 不假设博客文章的来源;可以将 P(D) 理解为:如果一篇文章是从未知来源放到你桌上的,你喜欢它的概率是多少?(再说一次,我知道它在没有上下文的情况下听起来很奇怪)。

所以,我们想要知道P(H|D)。让我们尝试使用贝叶斯定理,如下所示:

P(H|D)=P(D|H)P(H)P(D)

但是我们知道这个方程右侧的数字吗?我说我们知道!我们来看看:

  • P(H) 是任何一篇博客文章来自 Avinash 的概率。由于博客作者的写作速度差不多,我们可以假设这个概率是 0.5,因为我们有 50/50 的机会文章来自任一位博客作者(注意我没有假设 D,即数据,来做这个假设)。

  • P(D|H) 是你喜欢 Avinash 文章的概率,我们之前说过是 50%——所以是 0.5。

  • P(D) 是很有趣的。这是你喜爱某篇文章的 一般 概率。这意味着我们必须考虑这样一个情况:文章是来自 Lucy 还是 Avinash。现在,如果假设形成了一个套件,我们可以使用我们在上一章中提到的概率法则。当一组假设是完全穷尽的——即至少有一个假设必须成立——并且是互斥的时,就形成了套件。通俗来说,在一个事件套件中,只有一个假设能成立,且只能有一个。在我们的例子中,两个假设是文章来自 Lucy,或者文章来自 Avinash。这无疑是一个套件,原因如下:

    • 至少有一个人写了它

    • 至多有一个人写了它

    • 因此,只有一个 人写了它

当我们有一个套件时,我们可以使用乘法和加法规则,如下所示:

图 6.1 – 乘法和加法规则的一个应用示例

图 6.1 – 乘法和加法规则的一个应用示例

呼!做得好!现在,我们可以完成我们的方程式,如下所示:

P(H|D)=P(D|H)P(H)P(D)P(H|D)=.5*.5.65=.38

这意味着这篇文章有 38% 的概率来自 Avinash。有趣的是,P(H) = 0.5 和 P(H|D) = 0.38。这意味着在没有任何数据的情况下,博客文章来自 Avinash 的概率是硬币翻转的概率,或者说是 50/50。考虑到一些数据(你对这篇文章的看法),我们更新了对假设的信念,而实际上这降低了概率。这就是贝叶斯思维的核心——根据新的数据更新我们对某个事物的后验信念,从而推翻先前的假设。

贝叶斯定理的更多应用

贝叶斯定理在许多应用中都会出现,通常是在我们需要根据数据和概率做出快速决策时。大多数推荐引擎,比如 Netflix,就使用了贝叶斯更新的一些元素。如果你考虑一下为什么这样做很有意义,你就能理解。

假设在我们这个简化的世界里,Netflix 只有 10 个类别可以选择。现在,假设在没有任何数据的情况下,用户在 10 个类别中选择喜剧片的概率是 10%(即 1/10)。

好的,现在假设用户给几部喜剧电影打了 5/5 的高分。那么,当 Netflix 想知道用户是否会喜欢另一部喜剧时,P(H|D)(他们可能会喜欢喜剧的概率)会比随机猜测的 10%要大!

让我们尝试更多的贝叶斯定理应用示例,使用更多的数据。这一次,我们来点更有挑战性的。

示例 – 泰坦尼克号

一个非常著名的数据集涉及到 1912 年泰坦尼克号沉船事故的幸存者。我们将运用概率理论来研究是否有任何人口统计特征与乘客的生还有关。主要的目的是看看我们是否能从数据集中找出一些特征,帮助我们了解哪些类型的人更可能在这场灾难中幸存。

首先,让我们读取数据,如下所示:

titanic =
pd.read_csv(https://raw.githubusercontent.com/sinanuozdemir/SF_DAT_15/maste r/data/titanic.csv')#read in a csv
titanic = titanic[['Sex', 'Survived']] #the Sex and Survived column titanic.head()

我们得到如下表格:

表 6.1 – 表示泰坦尼克数据集的性别和生还情况

表 6.1 – 表示泰坦尼克数据集的性别和生还情况

泰坦尼克数据集只有性别生还情况两个选项。虽然生还情况是一个相对直接的特征,但这也是我们第一次使用一个数据集的例子,在这个数据集里,数据解释随着时间的推移发生了“漂移”。我们将在后面的章节中重新审视漂移的概念,以及像泰坦尼克号数据集这样的数据集,尽管使用起来很方便,但其效用已经过时。

在前面的表格中,每一行代表一个船上的乘客,暂时我们将关注两个特定的特征——乘客的性别以及他们是否在沉船中幸存。例如,第一行代表一位未幸存的男性,而第四行(索引为 3——记住 Python 的列表是从 0 开始索引的)代表一位幸存的女性。

让我们从一些基础开始。首先,我们计算船上任何一个人幸存的概率,而不考虑他们的性别。为此,我们将统计生还列中“是”的数量,并将此数字除以总行数,如下所示:

  num_rows = float(titanic.shape[0]) # == 891 rows
  p_survived = (titanic.Survived=="yes").sum() / num_rows #
==
.38
  p_notsurvived = 1 - p_survived
#
==
.61

注意,我只需要计算P(Survived),并且使用共轭概率定律来计算P(Died),因为这两个事件是互补的。现在,让我们计算一个乘客是男性还是女性的概率:

p_male = (titanic.Sex=="male").sum() / num_rows # == .65
p_female = 1 - p_male # == .35

现在,让我们问自己一个问题——拥有某种性别是否会影响生还率?为此,我们可以估算P(Survived|Female),即假设一个人是女性的情况下,她幸存的概率。为此,我们需要将幸存的女性人数除以总女性人数,如下所示:

P(Survived|Female)=P(FemaleANDSurvives)P(Female)

这是该计算的代码:

number_of_women = titanic[titanic.Sex=='female'].shape[0] #== 314
women_who_lived = titanic[(titanic.Sex=='female') &
(``titanic.Survived=='yes')].shape[0] #==
p_survived_given_woman = women_who_lived / float(number_of_women)
p_survived_given_woman # == .74

这是一个相当大的差异。看起来性别在这个数据集中扮演了重要角色。

示例 - 医学研究

贝叶斯定理的一个经典应用是医学试验的解释。非法药物使用的常规检测在工作场所和学校中越来越普遍。进行这些检测的公司声称,测试具有很高的敏感性,这意味着如果体内有毒品,测试很可能呈阳性。他们还声称这些测试具有很高的特异性,这意味着如果没有毒品,测试很可能呈阴性。

*均而言,假设常*的药物测试的敏感性约为 60%,特异性约为 99%。这意味着,如果一个员工正在使用毒品,测试有 60%的机会呈阳性;而如果员工未使用毒品,测试有 99%的机会呈阴性。现在,假设这些测试应用于一个毒品使用率为 5%的劳动力群体。

这里真正的问题是,在那些检测呈阳性的人的群体中,实际上有多少人使用了毒品?

在贝叶斯术语中,我们想要计算在测试结果为阳性时使用毒品的概率:

  • D = 药物使用的事件

  • E = 检测结果为阳性的事件

  • N = 药物未使用的事件

我们要寻找的是 P(D|E)。

使用贝叶斯定理,我们可以进行如下推导:

P(D|E)=P(E|D)P(D)P(E)

先验概率 P(D),即在我们看到测试结果之前的毒品使用概率,约为 5%。似然函数 P(E|D),即在假定使用毒品的情况下检测为阳性的概率,这就是测试的敏感性。归一化常数 P(E) 稍微复杂一些。

我们需要考虑两个方面 —— P(E 和 D),以及 P(E 和 N)。基本上,我们必须假设当用户未使用药物时,测试可能会出现错误。请看以下方程式:

P(E)=P(EandD)orP(EandN)

P(E)=P(D)P(E|D)+P(N)P(E|N)

P(E)=.05*.6+.95*.01

P(E)=0.0395

所以,我们的原始方程式变为如下:

P(D|E)=.6*.050.0395

P(D|E)=.76

这意味着,在所有药物使用检测呈阳性的人中,大约四分之一是无辜的!

随机变量

一个 h 代表斜边,我们必须找出斜边的长度。我们也可以使用以下 Python 代码:

x=5

这两个变量在任一时刻的值都是相等的。在随机变量中,我们受到随机性的影响,这意味着变量的值就是——变量!它们可能根据环境变化而取不同的值。

如前所述,随机变量依然有一个值。我们所看到的变量和随机变量的主要区别在于,随机变量的值会根据情况发生变化。

然而,如果一个随机变量可以有许多值,我们如何跟踪它们呢?每个随机变量可能取的值都与一个百分比相关联,对于每个值,都有一个单一的概率,表示该变量会取该值。

通过随机变量,我们还可以获得随机变量的概率分布,它给出了变量的可能值及其对应的概率。

通常我们使用单个大写字母(主要是特定字母 X)来表示随机变量。例如,我们可能有以下内容:

  • X:掷骰子的结果

  • Y:公司今年获得的收入

  • Z:申请者在面试编程测验中的得分(0–100%)

实际上,随机变量是一个函数,它将一个事件的样本空间(所有可能结果的集合)中的值映射到一个概率值(介于 0 和 1 之间)。可以将该事件表示为如下:

f(event)=probability

该函数为每个选项分配一个概率。随机变量主要有两种类型——离散型连续型

离散随机变量

离散随机变量仅取有限个可能值,例如掷骰子的结果,如下所示:

图 6.2 – 表示离散随机变量

图 6.2 – 表示离散随机变量

请注意,我使用大写字母 X 来定义随机变量。这是一种常*做法。同时,请注意随机变量如何为每个单独的结果映射一个概率。随机变量有许多属性,其中两个是它们的期望值方差。我们将使用概率质量函数PMF)来描述离散随机变量。

它们的外观如下:

P(X=x)=PMF

因此,对于掷骰子,

P(X=1)=1/6andP(X=5)=1/6

下面是离散变量的几个例子:

  • 调查问题的可能结果(例如,1-10 的评分)

  • CEO 是否会在一年内辞职(是或否)

随机变量的期望值定义了随机变量在大量重复样本中的*均值。这有时被称为变量的 均值

例如,参考以下 Python 代码,它定义了一个掷骰子的随机变量:

import random
def random_variable_of_dice_roll():
return random.randint(1, 7) # a range of (1,7) # includes 1, 2, 3, 4, 5, 6, but NOT 7

这个函数将调用一个随机变量并返回响应。让我们掷 100 次骰子并计算结果的*均值,如下所示:

trials = []
num_trials = 100
for trial in range(num_trials):
trials.append( random_variable_of_dice_roll() ) print(sum(trials)/float(num_trials)) # == 3.77

所以,进行 100 次掷骰子并计算*均值,得出的结果是 3.77!我们可以尝试一下使用不同的试验次数,如下图所示:

num_trials = range(100,10000, 10)
avgs = []
for num_trial in num_trials:
trials = []
for trial in range(1,num_trial):
trials.append( random_variable_of_dice_roll() )
avgs.append(sum(trials)/float(num_trial))
plt.plot(num_trials, avgs)
plt.xlabel('Number of Trials')
plt.ylabel("Average")

我们得到以下图形:

图 6.3 – 表示 100 次掷骰子的*均值

图 6.3 – 表示 100 次掷骰子的*均值

上面的图表表示了随着掷骰子次数增加,*均掷骰子的结果如何变化。我们可以看到,*均掷骰子的值迅速接近 3.5。如果我们看图表的左边,我们会发现,如果我们只掷了大约 100 次骰子,那么不能保证得到*均 3.5 的结果。然而,如果我们连续掷 10,000 次骰子,就很有可能期望*均值接近 3.5

对于离散型随机变量,我们也可以使用一个简单的公式来计算期望值,如下所示:

Expectedvalue=E[X]=μx=∑xipi

这里,xi 是第 i 次结果,pi 是第 i 次的概率。

所以,对于我们的掷骰子,我们可以按如下方式计算期望值:

16(1)+16(2)+16(3)+16(4)+16(5)+16(6)=3.5

上面的结果告诉我们,对于每一次掷骰子,我们可以“期望”掷出 3.5 的结果。显然,这没有意义,因为我们不可能掷出 3.5,但从多次掷骰子的角度来看,这就有意义了。如果你掷 10,000 次骰子,你的*均值应该接近 3.5,正如前面图表和代码所示。

随机变量的期望值的*均值通常不足以完全理解该变量的含义。因此,我们将引入一个新的概念,称为方差。

随机变量的方差表示该变量的分布。它量化了期望值的变动性。

离散随机变量的方差公式如下所示:

Variance=V[X]=σ2x=∑(xi−μx)2pi

xipi代表与之前相同的值,而μ代表该变量的期望值。在这个公式中,我还提到过X的标准差 Sigma。Sigma 在此情况下是标准差,其定义为方差的*方根。让我们来看一个更复杂的离散随机变量的例子。

方差可以被看作是一个加减的度量。如果我说你可以期望从一手扑克中赢得$100,你可能会很高兴。如果我附加上这个细节,即你可能赢得$100,或者损失$80,或者输掉$80,那么你现在就面临一个较宽的预期范围,这可能会令人沮丧,并可能让一个风险厌恶的玩家更犹豫加入游戏。我们通常可以说我们有一个期望值,加减标准差。

假设您的团队使用李克特量表来衡量新产品的成功——即将其分为五个类别,其中 0 表示完全失败,4 表示极大成功。根据用户测试和产品表现的初步结果,他们估计新项目的成功概率如下(*图 6.4)。

我们首先需要定义我们的随机变量。令X随机变量表示我们产品的成功。X确实是一个离散随机变量,因为X变量只能取五个选项之一——0、1、2、3 或 4。

以下是我们随机变量X的概率分布。请注意,我们为X的每一个可能结果都设置了一列,并且在每个结果后面,我们都有该结果发生的概率:

图 6.4 – 我们随机变量的概率分布

图 6.4 – 我们随机变量的概率分布

例如,这个项目有 2% 的几率完全失败,并且有 26% 的几率获得巨大的成功!我们可以通过如下方式计算我们的期望值:

E[X]=0(0.02)+1(0.07)+2(0.25)+3(0.4)+4(0.26)=2.81

这个数字意味着,经理可以预期该项目的成功评分大约为 2.81。单凭这个数字并不十分有用。也许,在有多个产品可选的情况下,期望值可能是一种比较不同产品潜在成功的方式。然而,在这种情况下,当我们只有一个产品需要评估时,我们还需要更多的数据。接下来,我们来查看方差,公式如下:

方差 = V[X] = X² = (xi − μX)²pi = (0 − 2.81)²(0.02) + (1 − 2.81)²(0.07) + (2 − 2.81)²(0.25) + (3 − 2.81)²(0.4) + (4 − 2.81)²(0.26) = 0.93

现在,我们已经有了项目评分的标准差和期望值,让我们尝试总结一下我们的结果。我们可以说,项目的预期评分是 2.81,加减 0.96,意味着我们可以预期评分在 1.85 到 3.77 之间。

所以,我们可以这样处理这个项目:它的成功评分大约是 2.81,可能上下波动一个点左右。

你可能会想,哇,Sinan——也就是说,项目最好的评分是 3.8,最差是 1.8?其实不是这样。

它可能比 4 更好,也可能比 1.8 更差。为了进一步探讨,我们可以计算以下内容:

P(X>=3)

首先,花点时间自我确认一下,你能理解这个公式吗?当我询问 P(X >= 3) 时,我到底在问什么?老实说,花点时间弄明白这个问题。

P(X >= 3) 是我们的随机变量取值至少为 3 的概率。换句话说,我们的产品成功评分为 3 或更高的机会是多少?为了计算这个,我们可以进行如下计算:

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block">mml:miP</mml:mi>mml:mo(</mml:mo>mml:miX</mml:mi>mml:mo></mml:mo>mml:mo=</mml:mo>mml:mn3</mml:mn>mml:mo)</mml:mo>mml:mo=</mml:mo>mml:miP</mml:mi>mml:mo(</mml:mo>mml:miX</mml:mi>mml:mo=</mml:mo>mml:mn3</mml:mn>mml:mo)</mml:mo>mml:mo+</mml:mo>mml:miP</mml:mi>mml:mo(</mml:mo>mml:miX</mml:mi>mml:mo=</mml:mo>mml:mn4</mml:mn>mml:mo)</mml:mo>mml:mo=</mml:mo>mml:mo.</mml:mo>mml:mn66</mml:mn>mml:mo=</mml:mo>mml:mn66</mml:mn><mml:mi mathvariant="normal">%</mml:mi></mml:math>

这意味着我们有 66%的机会使得我们的产品评分为 3 或 4。

另一种计算方法是通过共轭方式,如下所示:

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block">mml:miP</mml:mi>mml:mo(</mml:mo>mml:miX</mml:mi>mml:mo></mml:mo>mml:mo=</mml:mo>mml:mn3</mml:mn>mml:mo)</mml:mo>mml:mo=</mml:mo>mml:mn1</mml:mn>mml:mo-</mml:mo>mml:miP</mml:mi>mml:mo(</mml:mo>mml:miX</mml:mi>mml:mo<</mml:mo>mml:mn3</mml:mn>mml:mo)</mml:mo></mml:math>

再次花点时间说服自己这个公式是成立的。我声称,找到产品至少被评为 3 的概率,等于 1 减去产品评分低于 3 的概率。如果这是真的,那么这两个事件(X >= 3 和 X < 3)必须是互补的。

这显然是正确的!该产品可以是以下两种选项之一:

  • 被评为 3 分或更高

  • 被评为低于 3 分

让我们检查一下我们的数学计算:

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block">mml:miP</mml:mi>mml:mo(</mml:mo>mml:miX</mml:mi>mml:mo<</mml:mo>mml:mn3</mml:mn>mml:mo)</mml:mo>mml:mo=</mml:mo>mml:miP</mml:mi>mml:mo(</mml:mo>mml:miX</mml:mi>mml:mo=</mml:mo>mml:mn0</mml:mn>mml:mo)</mml:mo>mml:mo+</mml:mo>mml:miP</mml:mi>mml:mo(</mml:mo>mml:miX</mml:mi>mml:mo=</mml:mo>mml:mn1</mml:mn>mml:mo)</mml:mo>mml:mo+</mml:mo>mml:miP</mml:mi>mml:mo(</mml:mo>mml:miX</mml:mi>mml:mo=</mml:mo>mml:mn2</mml:mn>mml:mo)</mml:mo></mml:math>

=0.02+0.07+0.25

=.0341−P(X<3)

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block">mml:mo=</mml:mo>mml:mn1</mml:mn>mml:mo-</mml:mo>mml:mo.</mml:mo>mml:mn34</mml:mn></mml:math>

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block">mml:mo=</mml:mo>mml:mo.</mml:mo>mml:mn66</mml:mn></mml:math>

=P(x>=3)

这个结果是正确的!

离散随机变量的类型

通过观察特定类型的随机变量,我们可以更好地理解随机变量如何在实践中工作。这些特定类型的随机变量模拟不同的情境,最终为非常复杂的事件建模提供了更简单的计算方法。

二项式随机变量

我们将首先介绍的第一种离散随机变量叫做 二项式随机变量。对于二项式随机变量,我们研究的是一种单一事件反复发生的情境,并尝试计算其正面结果出现的次数。

在我们理解随机变量本身之前,必须先了解其适用的条件。

二项式情境具有以下四个条件:

  • 可能的结果是成功或失败

  • 各次试验的结果互不影响

  • 试验次数是固定的(样本大小已设定)

  • 每次试验的成功概率必须始终为 p

二项式随机变量是一个离散随机变量,X,用于计数二项试验中成功的次数。其参数为 n = 试验次数p = 每次试验成功的概率

示例 – 筹款会议

在这个例子中,一家初创公司通过进行 20 次 风险投资VC)会议来筹集资金,并计算他们收到的提案数量。

概率质量函数PMF)适用于二项式随机变量,其公式如下:

P(X=k)=nkpk1−pn−k

这里,

nk=thebinomialcoefficient=n!n!(n−k)!k!

示例 – 餐厅开业

在这个例子中,一个新开的餐厅在第一年存活的机会为 20%。如果今年有 14 家餐厅开业,求恰好四家餐厅在开业一年后仍然存活的概率。

首先,我们需要证明这是一个二项分布的情境:

  • 可能的结果是成功或失败(餐厅要么存活,要么不存活)

  • 试验的结果不会影响其他试验的结果(假设一家餐厅的开业不会影响另一家餐厅的开业和存活)

  • 试验的数量设定为(14 家餐厅开业)

  • 每个试验的成功概率必须始终为p(我们假设它始终是 20%)

在这里,我们有两个参数:n = 14 和 p = 0.2。所以,我们可以将这些数字代入我们的二项式公式,如下所示:

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block"><mml:mi mathvariant="normal">P</mml:mi>mml:mo(</mml:mo><mml:mi mathvariant="normal">X</mml:mi>mml:mo=</mml:mo>mml:mn4</mml:mn>mml:mo)</mml:mo>mml:mo=</mml:mo><mml:mfenced separators="|">mml:mrow<mml:mfrac linethickness="0pt">mml:mrowmml:mn14</mml:mn></mml:mrow>mml:mrowmml:mn4</mml:mn></mml:mrow></mml:mfrac></mml:mrow></mml:mfenced>mml:mo.</mml:mo>mml:msupmml:mrowmml:mn2</mml:mn></mml:mrow>mml:mrowmml:mn4</mml:mn></mml:mrow></mml:msup>mml:mo.</mml:mo>mml:msupmml:mrowmml:mn8</mml:mn></mml:mrow>mml:mrowmml:mn10</mml:mn></mml:mrow></mml:msup>mml:mo=</mml:mo>mml:mo.</mml:mo>mml:mn17</mml:mn></mml:math>

所以,我们有 17%的概率恰好四家餐厅在一年后仍然开门营业。

示例 – 血型

在这个例子中,一对夫妻有 25%的概率生下 O 型血的孩子。假设他们有五个孩子,三人是 O 型血的概率是多少?

设 X = O 型血孩子的数量,n = 5,p = 0.25,如下所示:

P(X=3)=10(0.25)3(0.75)5−3=10(.25)3(0.75)2=0.087

我们可以计算 0、1、2、3、4 和 5 这些值的概率,从而了解概率分布:

图 6.5 – 0、1、2、3、4 和 5 这些值的概率

图 6.5 – 0、1、2、3、4 和 5 这些值的概率

从这里,我们可以计算这个变量的期望值和方差:

ExpectedValue=E[X]=μx=∑xipi=1.25

Variance=V[X]=σx2=∑(xi−μx)2pi=0.9375

所以,这个家庭可能有一个或两个 O 型血的孩子!

如果我们想知道至少有三个孩子是 O 型血的概率呢?为了知道至少有三个孩子是 O 型血的概率,我们可以使用以下针对离散随机变量的公式:

P(x>=3)=P(X=3)+P(X=4)+P(X=3)=.00098+.01465+.08789=0.103

因此,他们的孩子中有大约 10%的机会会有 O 型血。

二项分布期望值和方差的简化计算

二项式随机变量有特殊的计算公式,用于计算期望值和方差的精确值。如果X是一个二项式随机变量,那么我们得到以下结果:

E(X)=np

V(X)=np(1−p)

对于我们前面的例子,我们可以使用以下公式来计算精确的期望值和方差:

E(X)=.25(5)=1.25

V(X)=1.25(.75)=0.9375

二项式随机变量是一个离散随机变量,它计算二项分布设置中的成功次数。它广泛应用于各种基于数据的实验中,比如计算给定转化机会时,多少人会注册一个网站,甚至在简单的层面上,预测股价变动给定下跌的概率(别担心——稍后我们将应用更加复杂的模型来预测股市)。

几何随机变量

我们将要研究的第二个离散随机变量叫做几何随机变量。它实际上与二项式随机变量非常相似,因为我们关心的是一种设置,其中一个事件重复发生。然而,在几何设置中,主要的区别是我们没有固定样本大小。

我们不会像创业公司那样准确进行 20 次风投会议,也不会生五个孩子。相反,在几何分布中,我们模拟的是我们需要进行多少次试验,才能看到至少一次成功。具体来说,几何分布有以下四个条件:

  • 可能的结果要么是成功,要么是失败。

  • 一次试验的结果不能影响另一试验的结果。

  • 试验次数没有设定。

  • 每次试验的成功概率必须始终是p

请注意,这些条件与二项变量的条件完全相同,除了第三个条件。

几何随机变量是一个离散的随机变量 X,用来计数获得一次成功所需的试验次数。其参数为p = 每次试验成功的概率,以及(1 − p) = 每次试验失败的概率

要将之前的二项分布示例转化为几何分布示例,我们可能会这样做:

  • 计算创业公司需要进行多少次风投会议才能获得他们的第一次同意

  • 计算为了得到一次正面结果所需的硬币翻转次数(是的,我知道这很无聊,但这是个很好的例子!)。

PMF 的公式如下:

P(X=x)=(1−p)[x−1]

二项式和几何分布设置都涉及成功或失败的结果。主要的区别在于,二项式随机变量有固定的试验次数,用n表示。几何随机变量则没有固定的试验次数。相反,几何随机变量用于建模为获得第一次成功试验所需的样本数量,无论成功在这些实验条件下意味着什么。

示例 – 天气。

在这个例子中,四月的每一天都有 34%的概率下雨。找出四月四日是四月第一次下雨的概率。

令 X 为直到下雨(成功)所需的天数,p = 0.34,(1 − p) = 0.66。所以,四月四日之前下雨的概率如下:

P(X<=4)=P(1)+P(2)+P(3)+P(4)=.34+.22+.14+>1=.8

因此,四月的头四天内下雨的概率是 80%。

几何分布的期望值和方差的快捷计算方法。

几何随机变量也有专门的计算公式用于计算期望值和方差的准确值。如果X是一个几何随机变量,那么我们得到如下公式:

E(X)=1/p

V(X)=(1−p)/p2

泊松随机变量

离散随机变量的第三个也是最后一个具体示例是泊松随机变量。

为了理解为什么我们需要这个随机变量,想象一下我们希望建模的事件发生的概率很小,并且我们希望统计在一定时间范围内事件发生的次数。如果我们知道一个特定时间段内的*均发生次数µ,并且这个数据来自过去的情况,那么泊松随机变量X = Poi(µ)将统计该时间段内事件发生的总次数。

换句话说,泊松分布是一个离散概率分布,用于计算在给定时间间隔内发生的事件数量。

考虑以下泊松随机变量的示例:

  • 根据网站过去的表现,计算在一小时内网站访问者数量的概率

  • 基于过去的警察报告估算交叉路口发生车祸的数量

如果我们让X = 给定时间间隔内的事件数量,并且每个时间间隔内事件的*均数量是λ,那么在给定时间间隔内观察到X事件的概率可以通过以下公式计算:

P(X=x)=e−λλxx!

这里,e = 欧拉常数(2.718....)。

示例 – 一个呼叫中心

你所在的呼叫中心每小时接到五个电话,假设电话数量服从泊松分布。那么,晚上 10 点到 11 点之间接到恰好六个电话的概率是多少?

为了设置这个例子,让我们写出给定的信息。让X表示晚上 10 点到 11 点之间接到的电话数量。这是我们的泊松随机变量,均值为λ = 5。均值为5,因为我们使用5作为在这个时间段内接到电话的期望值。这个数字可能来自之前估算每小时接到的电话数量,或者是专门估算 10 点后接到的电话数量。重点是,我们确实对接到多少电话有一些了解,然后我们使用这些信息创建我们的泊松随机变量,并用它来做预测。

继续我们的例子,我们得到如下内容:

P(X=6)=0.146

这意味着在晚上 10 点到 11 点之间,恰好接到六个电话的概率大约为 14.6%。

泊松期望值和方差的快捷计算方法

泊松随机变量对于期望值和方差的确切值也有特殊的计算方法。如果X是一个具有均值的泊松随机变量,那么我们得到如下结果:

E(X)=λ

V(X)=λ

这实际上很有趣,因为期望值和方差是相同的数字,而这个数字就是给定的参数!现在我们已经看了三个离散随机变量的例子,我们必须看一下另一种类型的随机变量,称为连续随机变量。

连续随机变量

完全转换思路,与离散随机变量不同,连续随机变量可以取无限多个可能的值,而不仅仅是少数几个可数的值。我们称描述这些分布的函数为密度曲线,而不是概率质量函数。

考虑以下连续变量的例子:

  • 销售代表的电话时长(不是电话的数量)

  • 标记为 20 加仑的油桶中的实际油量(不是油桶的数量)

如果X是一个连续随机变量,那么对于任何常数ab,都有一个函数f(x)

P(a≤X≤b)=∫abf(x)dx

前述的 f(x) 函数被称为概率密度函数PDF)。PDF 是离散随机变量的概率质量函数(PMF)的连续随机变量版本。

最重要的连续分布是标准正态分布。你无疑听说过正态分布,或者曾经接触过它。它的概念相当简单。该分布的 PDF 如下所示:

f(x)=12πσ2e−(x−μ)22σ2

这里,μ 是变量的均值,σ 是标准差。虽然这看起来有些混乱,但让我们用 Python 绘制图形,均值为 0,标准差为 1,如下所示:

import numpy as np
import matplotlib.pyplot as plt
def normal_pdf(x, mu = 0, sigma = 1):
return (1./np.sqrt(2*3.14 * sigma**2)) * np.exp((-(x-mu)**2 / (2.* sigma**2)))
x_values = np.linspace(-5,5,100)
y_values = [normal_pdf(x) for x in x_values] plt.plot(x_values, y_values)

我们得到了这个图形:

图 6.6 – 表示均值为 0,标准差为 1

图 6.6 – 表示均值为 0,标准差为 1

这揭示了熟悉的钟形曲线。请注意,图形围绕 x = 0 线是对称的。让我们尝试更改一些参数。首先,尝试使用 μ = 5:

图 6.7 – 表示熟悉的钟形曲线

图 6.7 – 表示熟悉的钟形曲线

接下来,我们将尝试使用值 <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:miσ</mml:mi>mml:mo=</mml:mo>mml:mn5</mml:mn></mml:math>

图 6.8 – 表示值 ​ = 5​

图 6.8 – 表示值 <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"><mml:mi mathvariant="bold-italic">σ</mml:mi>mml:mo=</mml:mo>mml:mn5</mml:mn></mml:math>

最后,我们将尝试使用值 <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:miμ</mml:mi></mml:math> = 5 ,σ=5

图 6.9 – 表示值 ​​ = 5 ​,  = 5​

图 6.9 – 一个表示值的图表 <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"><mml:mi mathvariant="bold-italic">μ</mml:mi></mml:math> = 5 ,σ=5

在所有的图表中,我们都有大家熟悉的标准钟形曲线,但随着我们改变参数,可以看到钟形曲线可能变得更瘦、更厚,或是从左到右移动。

在接下来的章节中,重点讲解统计学时,我们将更广泛地使用正态分布,它在统计思维中具有重要作用。

总结

概率作为一个领域,旨在解释我们这个随机且混乱的世界。通过使用概率的基本法则,我们可以建模涉及随机性的现实生活事件。我们可以使用随机变量来表示可能取多个值的数值,并且可以使用概率质量函数或密度函数来比较产品线或查看测试结果。

我们已经看到了一些在预测中使用概率的复杂方法。使用随机变量和贝叶斯定理是为现实生活情境分配概率的优秀方法。

接下来的两章将重点讲解统计思维。与概率类似,这些章节将使用数学公式来建模现实世界中的事件。然而,主要的区别在于我们用来描述世界的术语以及我们建模不同类型事件的方式。在接下来的章节中,我们将尝试仅基于样本来建模整个数据点的群体。

我们将重新审视概率中的许多概念,以理解统计定理,因为它们紧密相连,且在数据科学领域中都是重要的数学概念。

第七章:机会有多大?统计学入门

本章将重点讲解任何有志于成为数据科学家的统计学知识。

我们将探索如何进行抽样并获取数据,避免受到偏差的影响,然后使用统计量度来量化和可视化我们的数据。通过使用 z 分数和经验法则,我们将看到如何标准化数据,以便于图表展示和解释。

本章将讨论以下主题:

  • 如何获取和抽样数据

  • 中心度量、方差和相对位置

  • 使用 z 分数对数据进行标准化

  • 经验法则

什么是统计学?

这可能是一个奇怪的问题,但我经常对许多人无法回答这个简单而又强有力的问题感到惊讶:什么是统计学?统计学是你总是在新闻和报纸上看到的数字。统计学在试图证明一个观点或吓唬某人时非常有用,但它究竟是什么?

要回答这个问题,我们需要先回顾一下,为什么我们要进行测量。这个领域的目标是尝试解释和建模我们周围的世界。为此,我们必须观察总体。

我们可以将总体定义为实验或模型中的所有对象的集合。

本质上,你的总体是你关心的人群。你想讨论谁?如果你想测试吸烟是否会导致心脏病,那么你的总体就是全球的吸烟者。如果你想研究青少年饮酒问题,那么你的总体就是所有青少年。

现在,假设你想对你的总体提出一个问题。例如,如果你的总体是所有员工(假设你有超过 1,000 名员工),或许你想知道他们中有多少人喜欢旅行。这个问题叫做参数——描述总体特征的数值度量。例如,如果你问了所有 1,000 名员工,其中 100 人喜欢旅行,那么旅行喜欢的比例就是 10%。这里的参数是 10%。

你可能无法询问每一个员工是否喜欢旅行。如果你有超过 10,000 名员工怎么办?追踪每个人以获得答案将变得非常困难。当这种情况发生时,确定这个参数几乎是不可能的。在这种情况下,我们可以估算这个参数。

首先,我们将从总体中抽取一个样本。我们可以将总体的样本定义为总体的一个子集(不一定是随机的)。也许你可以询问 1,000 名员工中的 200 名。在这 200 人中,假设 26 人喜欢旅行,那么比例就是 13%。在这里,13%不是参数,因为我们没有机会问到每个人。这个 13%是对参数的估计。你知道这叫什么吗?没错,叫做统计量

我们可以将统计量定义为描述总体样本特征的数字度量。统计量只是一个参数的估计值。它是一个数字,试图通过描述总体的一个子集来描述整个总体。这是必要的,因为你不可能希望对世界上每个青少年或每个吸烟者进行调查。这就是统计学的核心内容:从总体中获取样本并对这些样本进行测试。

所以,下次你看到一个统计数据时,只需记住,这个数字只是代表了该总体的一个样本,而不是整个研究对象池。

我们如何获取和采样数据?

如果统计学是关于从总体中获取样本,那么了解我们如何获得这些样本一定非常重要,你说得对。让我们集中讨论几种获取和采样数据的方法。

获取数据

收集数据的方法主要有两种:观察性实验性。这两种方式当然各有优缺点。它们产生的行为类型不同,因此需要不同的分析方法。

观察性

我们可以通过观察性手段获取数据,这包括测量特定特征,但不试图修改所研究的对象。例如,如果你在你的网站上安装了追踪软件,观察用户在网站上的行为,例如在某些页面上停留的时间和点击广告的频率,同时不影响用户的体验,那么这就是一种观察性研究。

这是获取数据的最常*方式之一,因为它非常简单。你只需要观察并收集数据。观察性研究也有限制,无法收集所有类型的数据。这是因为观察者(你)并不控制环境。你只能观察并收集自然行为。如果你希望诱导某种特定行为,观察性研究就不适用了。

实验

实验由一种处理方式和观察其对研究对象的影响组成。实验中的研究对象被称为实验单元。这通常是大多数科学实验室收集数据的方式。他们会将人分成两组或更多组(通常是两组),并称之为对照组和实验组。

对照组暴露在特定环境中,然后进行观察。实验组暴露在不同的环境中,然后进行观察。实验者将收集来自两组的数据,并根据哪些环境更有利来做出决策(“有利”是实验者自行决定的一个特质)。

在一个营销示例中,假设我们将一半的用户暴露于某个特定的着陆页,该页面有特定的图像和特定的风格(网站 A),并衡量他们是否注册了该服务。然后,我们将另一半用户暴露于一个不同的着陆页,不同的图像和不同的风格(网站 B),再次衡量他们是否注册。我们可以决定哪个网站表现更好,并且应当继续使用该网站。这种测试,特别被称为 A/B 测试。让我们来看一个 Python 示例!

假设我们运行了前面的测试,并获得以下结果作为一个列表:

results = [ ['A', 1], ['B', 1], ['A', 0], ['A', 0] ... ]

在这里,列表中的每个对象代表一个主体(即个人)。每个人都有以下两个属性:

  • 他们接触到的网站,由一个字符表示

  • 是否转化(0 代表未转化,1 代表已转化)

然后我们可以汇总出以下结果表:

users_exposed_to_A = []
users_exposed_to_B = []
# create two lists to hold the results of each individual website

一旦我们创建了这两个最终将包含每个单独转换值(布尔值 01)的列表,我们将遍历所有的测试结果,并将其添加到相应的列表中,如下所示:

for website, converted in results: # iterate through the results
will look something like website == 'A' and converted == 0 if website == 'A':
users_exposed_to_A.append(converted) elif website == 'B':
users_exposed_to_B.append(converted)

现在,每个列表中包含一系列 10 值。

重要说明

请记住,1 代表用户在看到该页面后实际注册了该网站,而 0 代表用户看到该页面后离开,并未注册或转化。

要获取暴露于网站 A 的总人数,我们可以使用 len() 函数。

让我们用 Python 来演示我们两个列表的元素:

len(users_exposed_to_A) == 188 #number of people exposed to website A
len(users_exposed_to_B) == 158 #number of people exposed to website B

要计算转换人数,我们可以使用列表的总和,如下所示:

sum(users_exposed_to_A) == 54 # people converted from website A
sum(users_exposed_to_B) == 48 # people converted from website B

如果我们减去列表的长度和列表的总和,剩下的就是每个网站未转化人数,如下所示:

len(users_exposed_to_A) - sum(users_exposed_to_A) == 134 # did not convert from website A
len(users_exposed_to_B) - sum(users_exposed_to_B) == 110 # did not convert from website B

我们可以将结果汇总到以下表格中,展示我们的转化率测试实验:

未注册 已注册
网站 A 134 54
网站 B 110 48

表 7.1 – 我们 A/B 测试的结果

我们可以快速得出一些描述性统计数据。我们可以说这两个网站的转化率如下:

  • 网站 A 转化率154 /(154+34) = .**288

  • 网站 B 转化率48/(110+48)= .3

区别不大,但仍然有所不同。即使 B 网站的转化率更高,我们能否真的说 B 版本的转化效果显著更好呢?还不能。为了检验这种结果的 统计显著性,应使用假设检验。我们将在下一章深入探讨这些检验方法,到时我们将重新审视这个完全相同的示例,并使用合适的统计检验来完成它。

抽样数据

记住,统计数据是通过测量一个人群的样本得出的。好了,我们现在要讨论两种非常常*的决定谁能成为我们测量样本的方式。我们将讨论主要的抽样类型——随机抽样,这也是决定样本大小和样本成员的最常*方式。

概率抽样

概率抽样是一种从人群中抽取样本的方法,在这种方法中,每个人都有已知的被选中的概率,但这个概率可能与其他人的概率不同。最简单的(也可能是最常*的)概率抽样方法是随机抽样

随机抽样

假设我们正在进行 A/B 测试,需要决定谁将进入 A 组,谁将进入 B 组。以下是我们数据团队提出的三种建议:

  • 根据位置进行用户分组:西海岸的用户进入 A 组,东海岸的用户进入 B 组

  • 根据用户访问网站的时间段进行分组:在晚上 7 点到凌晨 4 点之间访问的用户为 A 组,其他用户为 B 组

  • 完全随机分组:每个新用户都有 50/50 的机会被分配到任意一个小组

前两个选项是有效的样本选择方式,且实施起来相对简单,但它们都有一个根本的缺陷:它们都有可能引入抽样偏倚。

当样本的获取方式系统性地偏向某些结果而非目标结果时,就会发生抽样偏倚。这种偏倚可能以多种方式出现,比如使用不具有代表性的样本、根据某些标准选择参与者,或者使用有偏的抽样方法。当样本存在偏倚时,可能导致对研究人群的错误或误导性结论,因此,确保抽样方法适当且公正非常重要,以获得准确可靠的结果。

很容易理解为什么从前面列表中选择第一个或第二个选项可能会引入偏倚。如果我们根据人们的居住地或登录时间来选择小组,就会错误地启动实验,而此时,我们对结果的控制就大大减少了。

具体来说,我们有引入混杂因素的风险,这对分析是一个不好的消息。

混杂因素是我们没有直接测量的变量,但它与正在测量的变量相关。基本上,混杂因素就像是我们分析中缺失的元素,虽然它不可*,但会影响我们的结果。

在这种情况下,第一个选项没有考虑到地域口味这一潜在的混杂因素。例如,如果 A 网站对于西海岸的用户普遍不具吸引力,这将大大影响您的结果。

类似地,第二种选择可能会引入一个时间(基于时间的)混杂因素。假设 B 网站在夜间环境下观看效果更好(而这一时间段是为 A 网站保留的),用户仅仅因为在特定时间观看,才对该风格产生负面反应?这些都是我们想避免的因素,因此我们应该选择第三种方式,即随机抽样。

重要提示

虽然抽样偏差可能引起混杂,但它与混杂是不同的概念。第一和第二种选择都是抽样偏差,因为我们错误地选择了样本,同时也是混杂因素的例子,因为每个案例中都有一个第三变量影响了我们的决策。

随机抽样是通过选择每个个体,使得总体中每个成员被选中的机会与其他成员相等。

这可能是决定谁会成为样本的一种最简单且最方便的方法。每个人都有完全相同的机会被分配到任何特定的小组。随机抽样是一种有效减少混杂因素影响的方法。

不等概率抽样

回想我之前提到过,概率抽样可能对不同的潜在样本成员具有不同的概率。但如果这实际上引发了问题呢?假设我们有兴趣衡量员工的幸福感。我们已经知道不能询问每一位员工,因为那样既不切实际又非常疲劳。所以,我们需要抽取一个样本。我们的数据团队建议采用随机抽样,最初每个人都互相击掌庆祝,因为他们觉得自己非常聪明、统计方法也很高明。但接着有人问了一个看似无害的问题:有没有人知道我们公司男性/女性的比例?

击掌声停止,房间变得安静。

这个问题极为重要,因为性别可能是一个混杂因素。团队进行了调查,发现公司中男性占 75%,女性占 25%。

这意味着,如果我们引入随机样本,我们的样本可能会有类似的性别比例,从而偏向男性的结果,而不利于女性。为了解决这个问题,我们可以选择在调查中加入更多女性,而不是男性,以减少样本偏向男性的倾向。

乍一看,在我们的随机抽样中引入倾向性系统似乎是一个糟糕的主意;然而,缓解不*等的抽样,从而消除性别、种族、残疾等方面的系统性偏*,更为重要。一个简单的随机样本,每个人都拥有与其他人相同的机会,很可能会淹没少数群体成员的声音和意*。因此,在你的抽样方法中引入这种倾向性系统是可以接受的。

我们如何衡量统计数据?

一旦我们获得了样本,就该量化我们的结果了。假设我们希望推广员工的幸福感,或者我们想弄清楚公司内部薪资是否因人而异。

这些是一些常*的结果测量方式。

中心度量

中心度量是我们定义数据集的中间或中心的方式。我们这样做是因为有时我们希望对数据值进行概括。例如,也许我们想知道西雅图的*均降水量是多少,或者欧洲男性的中位数身高是多少。这是一种将大量数据进行概括,使其更容易传达给他人的方式。

中心度量是数据集中中间的一个值。这对不同的人来说可能意味着不同的事情。谁来定义数据集的中间位置呢?有许多不同的方法来定义数据的中心。我们来看看几种方法。

数据集的算术*均数是通过将所有值加起来然后除以数据值的数量来计算的。这可能是最常*的定义数据中心的方法,但也可能是有缺陷的!假设我们希望找到以下数字的*均值:

import numpy as np
np.mean([11, 15, 17, 14]) == 14.25

很简单;我们的*均值是14.25,并且所有值都比较接近它。但如果我们引入一个新值:31呢?

np.mean([11, 15, 17, 14, 31]) == 17.6

这大大影响了*均值,因为算术*均数对离群值敏感。新值31几乎是其他数字的两倍,因此拉高了均值。

另一种有时更好的中心度量是中位数。中位数是将数据集按顺序排序后位于中间的那个数,如下所示:

np.median([11, 15, 17, 14]) == 14.5
np.median([11, 15, 17, 14, 31]) == 15

注意,使用中位数引入31时,数据集的中位数变化不大。这是因为中位数对离群值的敏感度较低。

在处理包含大量离群值的数据集时,使用数据集的中位数有时更加有用,而如果数据集没有太多离群值且数据点彼此较为接近,那么均值可能是更好的选择。

那么,如何判断数据是否分散呢?嗯,我们将引入一种新的统计方法。

变异度量

中心度量用于量化数据的中间值,但现在我们将探索如何测量我们收集的数据的分散程度。这是一种有用的方法,可以帮助我们识别数据中是否隐藏着许多离群值。让我们从一个例子开始。

假设我们从 Facebook 随机抽取 24 个朋友,记录下他们每个人在 Facebook 上的朋友数量。以下是列表:

friends = [109, 1017, 1127, 418, 625, 957, 89, 950, 946, 797, 981, 125, 455, 731, 1640, 485, 1309, 472, 1132, 1773, 906, 531, 742, 621]
np.mean(friends) == 789.1

这个列表中所有值的*均值大约是789。所以,我们可以说根据这个样本,*均每个 Facebook 朋友有 789 个朋友。但那个只有 89 个朋友的人,或者有超过 1600 个朋友的人呢?事实上,这些数字中并没有多少接近 789。

那我们使用中位数怎么样?中位数通常不容易受到离群值的影响:

np.median(friends) == 769.5

中位数是 769.5,这个值与*均值相当接近。嗯,好尝试,但还是不能完全解释这些数据点之间的差异有多大。这就是统计学家所说的“衡量数据的变异性”。让我们从介绍最基本的变异度量——极差开始。极差只是最大值减去最小值,如下所示:

np.max(friends) - np.min(friends) == 1684

极差告诉我们两个极端值之间有多远。现在,极差通常不会广泛使用,但它确实有一些相关的应用。有时候,我们希望了解离群值之间的差异有多大。这在科学和安全测量中最为有用。

假设一家汽车公司想要测量安全气囊弹出的时间。知道这个时间的*均值很不错,但他们更希望了解最慢的时间与最快的时间之间的差异。这实际上可能是生死攸关的差距。

回到 Facebook 示例,1684 是我们的极差,但我不太确定它能告诉我们多少有关数据的信息。现在,让我们看看最常用的变异度量——标准差

我相信你们中很多人听过这个术语,甚至可能会感到一丝恐惧,但它到底是什么意思呢?本质上,当我们在处理一个总体的样本时,标准差,表示为 s,衡量的是数据值与算术*均值的偏差程度。

它基本上是用来查看数据的分布情况的。有一个通用的标准差计算公式,如下所示:

图 7.1 – 标准差公式

图 7.1 – 标准差公式

标准差公式刚开始看起来可能会让人害怕,但随着时间和实践的积累,它会成为一个熟悉的朋友,帮助我们展示数据到底有多分散。

让我们依次看看公式中的每个元素:

  • s 是我们样本的标准差

  • x 是每个数据点

  • x‾ 是数据的*均值

  • n 是数据点的数量

在你不知所措并想关掉书本时,让我们把它拆解开来。对于样本中的每一个值,我们将取该值,减去算术*均值,*方差值,然后,当我们把所有这些点的差值加起来后,再将整个总和除以 n,即样本中的数据点数。最后,我们取结果的*方根。

不深入分析公式时,可以这样理解:它实际上是从距离公式推导出来的。基本上,标准差计算的是数据值与算术*均值之间的*均距离。

如果你仔细看公式,你会发现它实际上是有道理的:

  • 通过取 x-x‾,你是在找出数值与样本均值之间的实际差异。

  • 通过将结果*方,(x-x‾)²,我们对异常值施加了更大的惩罚,因为*方一个大的误差会让它变得更大。

  • 通过除以样本中的数据项数,我们实际上是在计算每个数据点与均值之间的*均*方距离。

  • 通过取答案的*方根,我们将数值转化为我们能够理解的单位。例如,通过将好友数减去均值再*方,我们把单位变成了“好友数*方”,这显然没有意义。取*方根后,我们的单位就恢复为“好友数”。

让我们回到 Facebook 的例子,通过可视化和进一步的解释来说明这一点。

让我们从计算标准差开始——在这种情况下,计算几个标准差。

回想一下,数据的算术*均数大约是789,所以我们将789作为均值。

我们首先计算每个数据值与均值之间的差值,将其*方,累加所有这些值,再除以比数据项数少一个的数值,最后取*方根。其计算过程如下所示:

图 7.2 – 计算一组数据标准差的示例表示

图 7.2 – 计算一组数据标准差的示例表示

计算一组数据标准差的示例中,我们通过将列表中每个项目减去列表的*均值,将结果*方,累加所有这些数值,再除以项目数,最后取该结果的*方根。

另一方面,我们可以采用 Python 的方法,通过编程来完成所有这些工作(这通常是更受欢迎的做法):

np.std(friends) # == 425.2

数字425所代表的是数据的分散程度。你可以说,425 是数据值与均值之间的*均距离。用简单的话来说,这意味着数据是相当分散的。

所以,我们的标准差大约是425。这意味着这些人在 Facebook 上的好友数似乎并没有接近一个单一的数字,当我们把数据绘制成条形图,并且也绘制均值以及标准差的可视化时,这一点就非常明显。在接下来的图中,每个人将由条形图中的一条柱子表示,柱子的高度代表该人拥有的好友数:

import matplotlib.pyplot as plt
friends = [109, 1017, 1127, 418, 625, 957, 89, 950, 946, 797, 981, 125, 455, 731, 1640, 485, 1309, 472, 1132, 1773, 906, 531, 742, 621]
y_pos = range(len(friends))
plt.bar(y_pos, friends)
plt.plot((0, 25), (789, 789), 'b-')
plt.plot((0, 25), (789+425, 789+425), 'g-')
plt.plot((0, 25), (789-425, 789-425), 'r-')

这是我们得到的图表:

图 7.3 – 将每个数据点作为条形图中的一根条形图绘制,图中显示了*均值(蓝色,中间)、*均值减去 1 个标准差(红色,下方)以及*均值加上 1 个标准差(绿色,上方的线)

图 7.3 – 将每个数据点作为条形图中的一根条形图绘制,图中显示了*均值(蓝色,中间)、*均值减去 1 个标准差(红色,下方),以及*均值加上 1 个标准差(绿色,上方的线)

中间的蓝色线表示均值(789),底部附近的红色线表示均值减去标准差(789 - 425 = 364),最后,顶部的绿色线表示均值加上标准差(789 + 425 = 1,214)。

注意到大多数数据都位于绿色和红色线之间,而异常值位于这些线的外面。有三个人的朋友数低于红线,三个人的朋友数高于绿线。这是讨论数据异常值的一种常*方式——通过标准差。通常它作为单位距离使用。例如,你可以说,在这个“朋友”数据范围内,1,214 是“均值上方一个标准差”,或者 1,639 是“均值上方两个标准差”。

需要提到的是,标准差的单位实际上与数据的单位是相同的。因此,在这个例子中,我们可以说标准差是 Facebook 上的 425 个朋友。

重要提示

另一种变异性度量是方差,正如上一章所描述的那样。方差就是标准差的*方。

所以,现在我们知道标准差和方差适合用来检查数据的分布情况,并且我们可以结合均值来创建一个大多数数据所在的范围。但是如果我们想比较两个不同数据集的分布情况,甚至是完全不同单位的数据呢?这时变异系数就派上用场了。

变异系数

变异系数定义为数据的标准差与均值的比值。

这个比值(顺便提一句,只有在我们处于比率级别的测量时才有意义,因为在该层次下除法是允许且有意义的)是一种标准化标准差的方法,使得在不同数据集之间进行比较变得更加容易。当我们尝试比较均值时,这个度量是我们常用的,它能够跨越不同规模的人群。

示例 – 员工薪资

如果我们查看同一公司不同部门员工薪资的均值和标准差,我们会发现,乍一看,比较标准差可能很困难,因为它们处于完全不同的量级:

图 7.4 – 浏览各部门薪资的均值和标准差

图 7.4 – 浏览各部门的工资均值和标准差

浏览各部门工资的均值和标准差,如果不使用变异系数(最后一列)将它们按相同的尺度进行标准化,可能会很有挑战性。只有这样,我们才能看出,执行层的工资分布略大于其他部门的工资分布。

当一个部门的*均工资是$25,000,而另一个部门的*均工资处于六位数范围时,这一点尤为重要。

然而,如果我们看看最后一列——变异系数,我们就会更清楚地看到,尽管高层部门的人可能挣得更多,但他们的工资差异也非常大。这可能是因为 CEO 的工资远高于办公室经理,尽管他们都在高层部门,这使得数据分布非常广泛。

另一方面,尽管邮件室的每个人赚得不多,但他们的收入几乎与邮件室中的其他人相同,这就是为什么他们的变异系数只有 8%的原因。

通过变异度度量,我们可以开始回答一些大问题,例如如何分散这些数据,或者如何得出一个大多数数据落入的合理范围。

相对位置度量

我们可以将中心度量和变异度量结合起来,创建相对位置度量。变异度量衡量特定数据值相对于整个数据集的位置。

让我们从学*统计学中的一个非常重要的值——z-score 开始。z-score 是告诉我们单个数据值离均值有多远的一种方式。回想一下前面的章节,我提到过数据点距离均值的标准差数。x数据值的 z-score 正是这个计算,它的公式如下:

z=X−X‾s

让我们来分解这个公式:

  • X 是数据点

  • X‾是均值

  • s 是标准差

记住,标准差(sort of)是数据与均值之间的*均距离,而 z-score 是每个特定数据点的个性化值。我们可以通过将数据点减去均值并除以标准差来找到数据值的 z-score。输出的是数据值与均值的标准化距离。z-score 在统计学中被广泛使用。它是标准化存在于不同尺度上的数据的非常有效的方式,也可以将数据放在均值的上下文中。

让我们将之前关于 Facebook 上朋友数量的数据进行标准化处理,得到 z 值。对于每个数据点,我们将通过应用前面的公式来找到它的 z 值。我们会对每个个体进行处理,减去*均朋友数量的值,再除以标准差,如下所示:

z_scores = []
m = np.mean(friends) # average friends on Facebook
s = np.std(friends) # standard deviation friends on Facebook
for friend in friends:
z = (friend - m)/s# z-score
z_scores.append(z) # make a list of the scores for plotting

现在,让我们将这些 z 值绘制在柱状图上。以下图表显示的是我们前一个例子中使用 Facebook 朋友的相同个体,但是现在柱形图的高度不再揭示朋友的原始数量,而是每个柱形图代表他们在 Facebook 上朋友数量的 z 值。如果我们绘制 z 值,我们会注意到一些事情:

plt.bar(y_pos, z_scores)

我们得到如下图表:

图 7.5 – 我们数据的 z 值

图 7.5 – 我们数据的 z 值

我们数据的 z 值快速地显示了哪些数据点低于或高于*均值,以及偏差有多少个标准差。

我们可以看到,数据中有负值(这意味着数据点低于*均值)。这些条形图的长度不再代表朋友的原始数量,而是该朋友数量与*均值的偏差程度。

这个图表让我们很容易识别出那些朋友数量远低于或高于*均水*的个体。例如,索引为 0 的个体*均朋友较少(他们有 109 个朋友,而*均值为 789)。

如果我们想要绘制标准差的图形呢?回想一下,我们之前绘制了三条水*线:一条在*均值处,一条在*均值加上标准差处(x+s),还有一条在*均值减去标准差处(x-s)。

如果我们将这些值代入 z 值公式,我们得到以下结果:

Zscoreofx‾=x−x‾s=0s=0

Zscoreof(x+s)=(x+s)−x‾s=ss=1

Zscoreof(x−s)=(x−s)−x‾s=−ss=−1

这并非巧合!当我们使用 z 分数标准化数据时,标准差变成了我们选择的度量标准。让我们绘制一个新的图,加入标准差:

plt.bar(y_pos, z_scores)
plt.plot((0, 25), (1, 1), 'g-')
plt.plot((0, 25), (0, 0), 'b-')
plt.plot((0, 25), (-1, -1), 'r-')

前面的代码添加了以下三行:

  • 一条蓝线表示y = 0,即距离均值零个标准差的位置(该位置在x轴上)

  • 一条绿色的线表示高于均值一个标准差的位置

  • 一条红线表示低于均值一个标准差的位置

让我们看看得到的图形:

图 7.6 – 我们的 z 分数图,图中标出了 1 和-1 的位置

图 7.6 – 我们的 z 分数图,图中标出了 1 和-1 的位置

我们的 z 分数图,其中标出了 1 和-1 的线,类似于图 7.3,在该图中,值在均值的 1 个标准差范围内,显示在绿色和红色(顶部和底部)线之间。

这些线的颜色与之前图中绘制的原始朋友数量的线条一致。如果仔细观察,你会发现,仍然有相同的人位于绿色和红色线条之外。也就是说,依然有三个人的得分低于红色(下方)线,而有三个人的得分高于绿色(上方)线。

Z 分数是标准化数据的有效方法。这意味着我们可以将整个数据集放在相同的尺度上。例如,如果我们还测量每个人的总体幸福感尺度(介于 0 和 1 之间),我们可能会得到类似以下的数据集:

friends = [109, 1017, 1127, 418, 625, 957, 89, 950, 946, 797, 981, 125, 455, 731, 1640, 485, 1309, 472, 1132, 1773, 906, 531, 742, 621]
happiness = [.8, .6, .3, .6, .6, .4, .8, .5, .4, .3, .3, .6, .2, .8, 1, .6, .2, .7, .5, .3, .1, 0, .3, 1]
import pandas as pd
df = pd.DataFrame({'friends':friends, 'happiness':happiness})
df.head()

我们得到以下表格:

图 7.7 – 使用两列表示数据,一列为朋友数量,另一列为介于 0 和 1 之间的幸福感测量

图 7.7 – 使用两列表示数据,一列为朋友数量,另一列为介于 0 和 1 之间的幸福感测量

这些数据点位于两个不同的维度上,每个维度的尺度差异很大。朋友数量可以达到几千,而我们的幸福感得分则被限制在 0 和 1 之间。

为了解决这个问题(并且在某些统计学/机器学*建模中,这一做法将变得至关重要),我们可以使用 scikit-learn 中预构建的标准化包来简单地标准化数据集,如下所示:

from sklearn import preprocessing
df_scaled = pd.DataFrame(preprocessing.scale(df), columns = ['friends_scaled', 'happiness_scaled'])
df_scaled.head()

这段代码将同时缩放朋友数量列和幸福感列,从而显示每列的 z 分数。需要注意的是,当运行前面的代码时,sklearn中的预处理模块会分别对每一列执行以下操作:

  • 找到该列的均值

  • 找到列的标准偏差

  • z-分数函数应用于列中的每个元素

结果是两列,如所示,它们在同一比例尺上存在,即使以前不是:

图 7.8 – 使用*z*-分数,将每列标准化到同一比例尺

图 7.8 – 使用z-分数,将每列标准化到同一比例尺

现在,每个数字都具有相同的单位 – 标准偏差

现在,我们可以将朋友和幸福程度绘制在同一比例尺上,这个图表至少是可读的:

df_scaled.plot(kind='scatter', x = 'friends_scaled', y = 'happiness_scaled')

前面的代码为我们提供了这个图表:

图 7.9 – 绘制标准化数据

图 7.9 – 绘制标准化数据

绘制标准化数据通常比未标准化数据更容易阅读,因为在所有数据都在相同单位时比较数据更容易

现在,我们的数据被标准化为z-分数,这个散点图相当容易解释!在后面的章节中,标准化的实践不仅会使我们的数据更易于解释,而且还将成为模型优化的重要部分。许多机器学*算法要求我们具有标准化的列,因为它们依赖于尺度的概念。

洞察部分 – 数据中的相关性

在本书中,我们讨论了拥有数据和获取数据洞察力之间的区别。拥有数据只是成功实施数据科学操作的一步。能够获取、清洗和绘制数据有助于讲述数据提供的故事,但不能揭示故事的道德。为了进一步推进这个例子,我们将研究 Facebook 朋友数量与幸福感之间的关系。

在接下来的章节中,我们将研究一种特定的机器学*算法,该算法试图找到量化特征之间的关系,称为线性回归,但我们不必等到那时才开始形成假设。我们有一些人的样本,测量了他们的在线社交存在以及他们报告的幸福感。今天的问题是:我们能否找到 Facebook 上的朋友数量与整体幸福感之间的关系?

现在,显然,这是一个很大的问题,应该尊重对待。为了回答这个问题,应该在实验室设置中进行实验,但是我们可以开始对这个问题形成假设。鉴于我们数据的性质,我们确实只有以下三种假设的选择:

  • 在在线社交朋友数量和幸福感之间存在正向关联(一个增加,另一个也增加)

  • 它们之间存在负相关(朋友数量增加时,幸福感下降)

  • 这些变量之间没有关联(当一个变化时,另一个并没有真正发生太大变化)

我们能用基础统计学来对这个问题形成假设吗?我认为可以!但首先,我们必须引入一个叫做相关性的概念。

相关系数是一个定量度量,描述两个变量之间关联/关系的强度。

两组数据之间的相关性告诉我们它们是如何共同变化的。我们的目标是了解改变一个值是否有助于我们预测另一个值。这个概念不仅有趣,而且是许多机器学*模型对数据做出的核心假设之一。为了使许多预测算法有效,它们依赖于变量之间存在某种关系的事实。学*算法随后利用这种关系来做出准确的预测。

关于标准相关系数,有几点需要注意:

  • 它的值将位于 -1 和 1 之间。

  • 绝对值越大(越接近 -1 或 1),变量之间的关系越强:

    • 最强的相关性是 -1 或 1。

    • 最弱的相关性是 0。

  • 正相关意味着当一个变量增加时,另一个变量也倾向于增加。

  • 负相关意味着当一个变量增加时,另一个变量倾向于减少。

我们可以使用 pandas 快速展示每个特征与其他特征之间的相关系数,如下所示:

df.corr(). # correlation between variables

我们得到如下表格:

图 7.10 – 朋友与幸福感之间的相关性

图 7.10 – 朋友与幸福感之间的相关性。

朋友与幸福感之间的相关性约为 -0.2,这意味着根据这些数据,增加一个朋友通常会导致幸福感减少 0.2 个单位。

上表显示了朋友幸福感之间的相关性。请注意前两点:

  • 矩阵的对角线填充了正值 1。这是因为它们表示变量与自身之间的相关性,显然它们形成了一条完美的线,使得相关性完全是正相关的!

  • 矩阵在对角线两侧是对称的。对于在 pandas 中创建的任何相关矩阵,这一性质都成立。

信任相关系数时有一些注意事项。其一,通常情况下,相关性会试图衡量变量之间的线性关系。这意味着,如果通过这种测量没有揭示出明显的相关性,并不意味着变量之间没有关系,而是说明没有一条能够轻松通过这些数据点的最佳拟合线。可能存在一种非线性关系,定义了这两个变量之间的关系。

重要的是要意识到,相关性并不意味着因果关系。仅仅因为这两个变量之间存在较弱的负相关关系,并不一定意味着你在 Facebook 上的朋友数量增加时,你的整体幸福感会下降。这种因果关系必须进一步检验,在后面的章节中,我们将尝试做这件事。

总结一下,我们可以利用相关性来假设变量之间的关系,但我们需要使用更复杂的统计方法和机器学*算法来巩固这些假设。

经验法则

回忆一下,正态分布是指具有特定概率分布,形态像钟形曲线。在统计学中,我们喜欢我们的数据表现得正常。例如,我们可能有类似于正态分布的数据,如下所示:

图 7.11 – 正态分布的图形表示

图 7.11 – 正态分布的图形表示

正态分布作为许多统计学分支的指导线和许多统计检验的基础。这里展示的数据显示,遵循这种分布的数据让我们可以“预期”某些数据点会落在*均值的 1、2、3 个标准差范围内。

经验法则指出,我们可以预期某些数据将在一系列标准差之间分布。具体而言,经验法则对呈正态分布的数据做出了以下说明:

  • 约 68%的数据位于 1 个标准差范围内。

  • 约 95%的数据落在 2 个标准差范围内。

  • 约 99.7%的数据落在 3 个标准差范围内。

例如,让我们来看一下我们的 Facebook 朋友的数据是否符合这一规则。让我们使用我们的数据框(DataFrame)来计算落在*均值的 1、2、3 个标准差范围内的人群百分比,如下所示:

finding the percentage of people within one standard deviation of the mean
within_1_std = df_scaled[(df_scaled['friends_scaled'] <= 1) & (df_scaled['friends_scaled'] >= -1)].shape[0] within_1_std / float(df_scaled.shape[0])
0.75
finding the percentage of people within two standard deviations of the mean
within_2_std = df_scaled[(df_scaled['friends_scaled'] <= 2) & (df_scaled['friends_scaled'] >= -2)].shape[0] within_2_std / float(df_scaled.shape[0])
0.916
finding the percentage of people within three standard deviations of the mean
within_3_std = df_scaled[(df_scaled['friends_scaled'] <= 3) & (df_scaled['friends_scaled'] >= -3)].shape[0] within_3_std / float(df_scaled.shape[0])
1.0

我们可以看到,我们的数据似乎遵循了经验法则。大约 75%的人位于*均值的单个标准差范围内。约 92%的人位于两个标准差范围内,所有人都位于三个标准差范围内。

示例 – 考试成绩

假设我们正在测量考试成绩,并且成绩通常呈钟形正态分布。考试的*均成绩是 84%,标准差是 6%。我们可以以大致确定的概率说:

  • 约 68%的班级得分在 78%到 90%之间,因为 78 比 84 低 6 个单位,90 比 84 高 6 个单位。

  • 如果我们被问到班级中有多少百分比的学生得分在 72%到 96%之间,我们会注意到 72 比*均值低 2 个标准差,96 比*均值高 2 个标准差,所以经验法则告诉我们,大约 95%的班级得分在这个范围内。

然而,并非所有数据都服从正态分布,因此我们不能总是使用经验法则。我们有另一个定理可以帮助我们分析任何类型的分布。在下一章中,我们将深入讨论在何种情况下我们可以假设数据服从正态分布。这是因为许多统计测试和假设需要基础数据来自正态分布的总体。

重要提示

之前,当我们将数据标准化为 z 分数时,并不要求假设数据服从正态分布。

总结

在本章中,我们介绍了大多数数据科学家所需的基本统计知识——从如何获取/采样数据到如何根据 z 分数标准化数据以及经验法则的应用。我们还回顾了如何进行数据分析的抽样。此外,我们还回顾了各种统计度量,如均值和标准差,这些度量有助于描述数据。

在下一章中,我们将探讨更加高级的统计应用。我们将考虑的一项内容是如何对我们可以假设为正态的数据进行假设检验。在使用这些检验的过程中,我们还将量化我们的误差,并确定解决这些误差的最佳实践。

第八章:高级统计

在本章中,我们关心的是如何根据特定样本数据对整个总体进行推断。我们将使用假设检验以及不同的估计检验,以便在给定样本数据的情况下更好地理解总体。

本章我们将讨论的关键主题如下:

  • 点估计

  • 置信区间

  • 中心极限定理

  • 假设检验

理解点估计

回顾上一章,我们提到过获取总体参数的难度;因此,我们必须使用样本数据来计算一个统计量,该统计量是总体参数的估计值。当我们进行这些估计时,我们称之为点估计

点估计是基于样本数据对总体参数的估计。

我们使用点估计来估计诸如总体均值、方差以及其他统计量的内容。为了获得这些估计,我们只需将我们希望衡量的总体函数应用于样本数据。例如,假设有一家公司有 9,000 名员工,我们希望确定员工一天中休息的*均时长。由于我们不可能询问每一个人,我们将从这 9,000 人中抽取样本,并计算样本的均值。这个样本均值就是我们的点估计。我们将使用一个概率分布,即泊松分布,随机生成 9,000 个回答,问题是 你一天通常休息多少分钟? 这些回答将代表我们的总体。记住,来自第六章高级概率,泊松随机变量用于当我们知道某个事件的*均值,并希望围绕它建模分布时。

重要提示

我设置了一个随机种子以鼓励可重复性(这样我们每次都能得到相同的随机数)。

我们将对 100 名员工进行抽样(使用 Python 的随机抽样方法),并找到均值的点估计(称为样本均值)。

重要提示

请注意,这只是我们总体的 1%以上。

将我们的样本均值(100 名员工的样本均值)与总体均值进行比较。

让我们来看一下以下代码:

import numpy as np
import pandas as pd
from scipy import stats
from scipy.stats import poisson
np.random.seed(1234)
# represents 3000 people who take about a 60 minute break
long_breaks = stats.poisson.rvs(mu=60, size=3000)

long_breaks 变量代表了 3,000 个回答问题 你通常每次休息多少分钟? 的答案,这些答案将偏向较长的一侧。让我们来看一下该分布的可视化,具体如下:

pd.Series(long_breaks).hist()

图 8.1 – 我们较长休息时间的直方图,已知*均值为 60 分钟

图 8.1 – 我们较长休息时间的直方图,已知*均值为 60 分钟

我们可以看到,60 分钟的*均值位于分布的左侧。此外,由于我们只对 3,000 人进行了抽样,我们的柱状图在大约 700-800 人处达到最高。

现在,让我们模拟 6,000 人,这些人*均每次休息约 15 分钟。

让我们再次使用泊松分布模拟 6,000 人,如下所示:

# represents 6000 people who take about a 15 minute break
short_breaks = stats.poisson.rvs(mu=15, size=6000)
pd.Series(short_breaks).hist()

图 8.2 – 我们的短暂休息时间的直方图,已知*均值为 15 分钟

图 8.2 – 我们的短暂休息时间的直方图,已知*均值为 15 分钟

好的,我们有一个休息时间较长的人的分布图和一个休息时间较短的人的分布图。再次注意,我们的 15 分钟*均休息时间落在分布的左侧,并且注意到最高的柱形条对应大约 1,600 人:

breaks = np.concatenate((long_breaks, short_breaks))
# put the two arrays together to get our "population" of 9000 people

breaks变量是所有 9,000 名员工的合并数据,包括长时间和短时间休息的人。让我们在一个单一的可视化图中查看所有人的分布:

pd.Series(breaks).hist()

图 8.3 – 我们的两种休息类型的直方图

图 8.3 – 我们的两种休息类型的直方图

我们可以看到我们有两个山峰。左侧是大多数人(大约 15 分钟休息)的山峰,右侧是休息时间较长的人的山峰。稍后我们将进一步分析这个图表。

我们可以通过运行以下代码来找到总的*均休息时间:

breaks.mean()
# 29.99 minutes is our parameter.

我们公司*均的休息时间大约是 40 分钟。记住,我们的总体是整个公司 9,000 名员工,而我们的参数是 40 分钟。在现实世界中,我们的目标是估计总体参数,因为由于多种原因,我们不可能有足够的资源调查每个员工的*均休息时间。相反,我们将使用点估计。

因此,为了说明我们的观点,我们想要模拟一个世界,在这个世界里我们询问 100 个随机的人他们的休息时间长度。为此,我们从我们模拟的 9,000 名员工中随机抽取 100 名员工,如下所示:

sample_breaks = np.random.choice(a = breaks, size=100)
# taking a sample of 100 employees

现在,让我们计算样本的均值并从总体均值中减去它,看看我们差距有多大:

breaks.mean() - sample_breaks.mean()
# difference between means is 0.699 minutes, not bad!

这非常有趣,因为,仅凭大约 1%的样本(9,000 人中 100 人),我们就能够在离总体参数仅 1 分钟的误差范围内得到非常准确的总体均值估计。不错吧!

我们计算了均值的点估计,但我们也可以对比例参数进行这样的计算。这里的比例是指两个定量值之间的比率。

假设在一个有 10,000 名员工的公司中,我们的员工中有 20%是白人,10%是黑人,10%是西班牙裔,30%是亚洲人,30%是其他身份。我们将抽取 1,000 名员工,看看他们的种族比例是否相似:

employee_races = (["white"]*2000) + (["black"]*1000) +\
(["hispanic"]*1000) + (["asian"]*3000) +\
(["other"]*3000)

employee_races代表我们的员工总体。例如,在我们公司 10,000 名员工中,2,000 人为白人(占 20%),3,000 人为亚洲人(占 30%)。

让我们随机抽取 1,000 人,如下所示:

import random
demo_sample = random.sample(employee_races, 1000) # Sample 1000 value
for race in set(demo_sample):print( race + " proportion estimate:" )
print( demo_sample.count(race)/1000\. )

得到的输出如下:

hispanic proportion estimate:
0.103
white proportion estimate:
0.192
other proportion estimate:
0.288
black proportion estimate:
0.1
asian proportion estimate:
0.317

我们可以看到,种族比例估计值与基础人口比例非常接近。例如,我们在样本中获得了 10.3%的西班牙裔,而西班牙裔在总体中的比例是 10%。

抽样分布

第七章《机会有多大?统计学简介》中,我们提到过,当数据符合正态分布时,我们有多么喜欢它。原因之一是,许多统计检验(包括本章将使用的检验)依赖于符合正态模式的数据,而大多数真实世界的数据并不符合正态分布(惊讶吗?)。以我们员工休息数据为例——你可能会觉得我只是为了展示使用泊松分布而刻意创造数据,但我这样做是有原因的。我特别需要非正态数据,如下所示:

pd.DataFrame(breaks).hist(bins=50,range=(5,100))

图 8.4 – 我们休息数据的直方图,具有更多的区间,显示更细致的粒度

图 8.4 – 我们休息数据的直方图,具有更多的区间,显示更细致的粒度

如你所*,我们的数据显然不符合正态分布;它呈现双峰分布,意味着休息时间有两个峰值,大约在 25 分钟和 70 分钟左右。由于我们的数据不是正态的,许多最流行的统计检验可能不适用;然而,如果我们遵循给定的程序,我们可以创造出正态数据!觉得我疯了吗?好吧,自己看吧。

首先,我们需要使用所谓的抽样分布,它是多个相同大小样本的点估计值的分布。我们创建抽样分布的过程如下:

  1. 取 500 个不同的样本,每个样本包含 100 个数据点,作为休息时间。

  2. 对这 500 个不同的点估计值绘制直方图(显示它们的分布)。

样本中的元素数量(100)是任意选择的,但足够大以代表总体样本。我所取的样本数(500)也是任意选择的,但足够大,确保我们的数据会收敛到正态分布。

point_estimates = []
for x in range(500): # Generate 500 samples
# take a sample of 100 points
sample = np.random.choice(a=breaks, size=100)
# add the sample mean to our list of point estimates
point_estimates.append( sample.mean() )
# look at the distribution of our sample means
pd.DataFrame(point_estimates).hist()

图 8.5 – 样本均值的分布变得更加符合正态分布,这是中心极限定理的一个福音

图 8.5 – 样本均值的分布变得更加符合正态分布,这是中心极限定理的一个福音

看!即使我们从一个基础的双峰人口分布中取样,样本均值的抽样分布看起来依然是正态分布的。需要注意的是,这个直方图中的条形表示的是 500 个样本的*均休息时长,每个样本包含 100 人。换句话说,抽样分布是多个点估计值的分布。

我们的数据由于中心极限定理而收敛于正态分布,该定理指出抽样分布(点估计的分布)在增加抽样次数时将趋近于正态分布。

更重要的是,随着我们取得更多样本,抽样分布的均值将接近真实的总体均值,如下所示:

breaks.mean() - np.array(point_estimates).mean()
# .042 minutes difference

这实际上是一个非常令人兴奋的结果,因为它意味着我们可以通过采用多个点估计并利用中心极限定理来比单个点估计更接近!

重要提示

一般来说,随着我们增加抽样次数,我们的估计将更接近参数(实际值)。

置信区间

虽然点估计可以接受,但是对总体参数和抽样分布的估计更好。这些方法存在以下两个主要问题:

  • 单个点估计非常容易出错(由于抽样偏差等原因)

  • 对于抽样分布来说,取多个特定大小的样本可能不可行,并且有时甚至可能比实际找到总体参数更不可行。

出于这些原因以及更多原因,我们可能会转向一个被称为置信区间的概念来找到统计数据。

置信区间是基于点估计的一系列值,该值在某个置信水*下包含真实的总体参数。

置信是高级统计学中的一个重要概念。它的含义有时会被误解。非正式地,置信水*不代表正确概率;而是表示得到的答案准确的频率。例如,如果你希望用单个点估计有 95%的机会捕捉到真实的总体参数,你必须将你的置信水*设定为 95%。

重要提示

更高的置信水*会导致更宽(更大)的置信区间,以增加确信度。

计算置信区间涉及找到一个点估计,然后加入误差边界来创建一个范围。误差边界是一个值,代表我们对点估计准确性的确定程度,它基于我们期望的置信水*、数据的方差以及样本的大小。有许多计算置信区间的方法;出于简洁和简单起*,我们将看一种计算总体均值置信区间的方式。为了这个置信区间,我们需要以下内容:

  • 一个点估计。为此,我们将采用先前示例中断裂长度的样本均值。

  • 总体标准差的估计,代表数据的方差。这是通过取样本标准差(样本数据的标准差)并将该数字除以总体大小的*方根来计算的。

  • 自由度(样本大小 - 1)。

获取这些数字可能看起来是随意的,但相信我,它们背后是有原因的。不过,为了简化起*,我将使用预构建的 Python 模块,如图所示,来计算我们的置信区间,并展示其价值:

import math
sample_size = 100
# the size of the sample we wish to take
sample = np.random.choice(a= breaks, size = sample_size)
a sample of sample_size taken from the 9,000 breaks population from before
sample_mean = sample.mean()
# the sample mean of the break lengths sample
         sample_stdev = sample.std()
# sample standard deviation
sigma = sample_stdev/math.sqrt(sample_size)
# population standard deviation estimate
stats.t.interval(confidence = 0.95, # Confidence level 95%
df= sample_size - 1, # Degrees of freedom
loc = sample_mean, # Sample mean
scale = sigma) # Standard deviation
# (24.28, 33.14)

重申一下,这个数值范围(从24.2833.14)表示一个具有 95%置信度的*均休息时间置信区间。

我们已经知道我们的人群参数是29.99,并注意到该区间包含了总体均值29.99

如前所述,置信水*并不是我们的区间准确度的百分比,而是该区间包含总体参数的百分比概率。

为了更好地理解置信水*,让我们取 10,000 个置信区间,并查看我们的总体均值在多少次区间内。首先,让我们像示例中那样创建一个函数,从我们的休息数据中生成单个置信区间:

# function to make confidence interval
def makeConfidenceInterval():
sample_size = 100
sample = np.random.choice(a= breaks, size = sample_size)
sample_mean = sample.mean()  # sample mean
sample_stdev = sample.std()
# sample standard deviation
sigma = sample_stdev/math.sqrt(sample_size)
# population Standard deviation estimate
return stats.t.interval(confidence = 0.95, df= sample_size - 1, loc = sample_mean, scale = sigma)

现在我们有了一个创建单个置信区间的函数,让我们创建一个程序来测试单个置信区间包含真实总体参数29.99的概率:

  1. 10,000个样本均值的置信区间。

  2. 计算人群参数落入我们置信区间的次数。

  3. 输出参数落入区间的次数比例,基于 10,000 次试验:

breaks_mean = breaks.mean()
times_in_interval = 0
n = 10_000
for i in range(n):
    interval = makeConfidenceInterval()
    if breaks_mean >= interval[0] and breaks_mean <= interval[1]:  # if 29.99 falls in the interval
        times_in_interval += 1
print(times_in_interval / n)
# 0.9465

成功!我们看到大约 95%的置信区间包含了我们实际的人群均值。通过点估计和置信区间估计总体参数是一种相对简单且强大的统计推断形式。

让我们快速看一下,当我们改变置信水*时,置信区间的大小如何变化。我们将为多个置信水*计算置信区间,并通过查看两个数字之间的差异来观察区间的大小。我们的假设是,随着我们提高置信水*,我们可能会看到更大的置信区间,以确保能够捕捉到真实的总体参数:

for confidence in (.5, .8, .85, .9, .95, .99):
confidence_interval = stats.t.interval(confidence = confidence, df= sample_size - 1, loc = sample_mean, scale = sigma)
length_of_interval = round(confidence_interval[1] - confidence_interval[0], 2)
# the length of the confidence interval
print( "confidence {0} has a interval of size {1}".format(confidence, length_of_interval))
confidence 0.5 has a interval of size 2.95
confidence 0.8 has a interval of size 5.63
confidence 0.85 has a interval of size 6.33
confidence 0.9 has a interval of size 7.24
confidence 0.95 has a interval of size 8.65
confidence 0.99 has a interval of size 11.45

我们可以看到,当我们希望在区间内更加自信时,我们的区间会扩大以进行补偿。

接下来,我们将把置信水*的概念与统计假设检验结合起来,以便扩展这些主题,并创建(通常是)更强大的统计推断。

假设检验

假设检验是统计学中最广泛使用的检验方法之一。它们有多种形式,但所有形式的基本目的都是相同的。

假设检验是一种统计检验,用于确定我们是否可以假设某个条件对整个总体成立,前提是我们有一个数据样本。基本上,假设检验是检验我们关于整个总体的某个假设。检验的结果告诉我们是否应该相信这个假设,或者应该拒绝它并选择另一个假设。

你可以将假设检验的框架视为判断观察到的样本数据是否与总体预期的结果偏离。现在,这听起来像是一个困难的任务,但幸运的是,Python 来解救我们,它包含了内置的库来轻松进行这些检验。

假设检验通常会研究关于一个总体的两个对立假设。我们称之为零假设备择假设。零假设是被检验的命题,是默认的正确答案;它是我们的起点和最初的假设。备择假设是与零假设对立的命题。我们的检验将告诉我们应该相信哪个假设,应该拒绝哪个假设。

基于来自总体的样本数据,假设检验决定是否拒绝零假设。我们通常使用 p 值(基于我们的显著性水*)来得出这个结论。

重要提示

一个常*的误解是,统计假设检验是为了选择两个假设中更可能的一个。这是错误的。假设检验默认接受零假设,直到有足够的数据支持备择假设。

以下是一些你可以通过假设检验回答的问题示例:

  • 员工的*均休息时间是否与 40 分钟不同?

  • 在与网站 A 和网站 B 互动的用户之间,是否存在差异(A/B 测试)?

  • 一组咖啡豆的样本与整个咖啡豆群体的味道是否有显著差异?

进行假设检验

有多种假设检验方法,其中包括几十种不同的程序和度量标准。然而,大多数假设检验遵循五个基本步骤,具体如下:

  1. 指定假设:

    • 在这里,我们制定两个假设:零假设和备择假设。

    • 我们通常使用符号 <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msubmml:mrowmml:miH</mml:mi></mml:mrow>mml:mrowmml:mn0</mml:mn></mml:mrow></mml:msub></mml:math> 来表示零假设,使用 <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msubmml:mrowmml:miH</mml:mi></mml:mrow>mml:mrowmml:mia</mml:mi></mml:mrow></mml:msub></mml:math> 来表示我们的备择假设。

  2. 确定检验样本的样本量:

    • 这个计算取决于选择的检验方法。通常,我们必须确定一个合适的样本量,以便使用定理(如中心极限定理),并假设数据是正态分布的。
  3. 选择显著性水*(通常称为 alpha 或 α):

    • 0.05 的显著性水*是常*的。
  4. 收集数据:

    • 收集一组数据来进行检验。
  5. 决定是否拒绝或未能拒绝原假设:

    • 这个步骤会根据所使用的检验类型略有变化。最终结果要么是拒绝原假设,支持备择假设,要么是未能拒绝原假设。

本章中,我们将探讨以下三种假设检验:

  • 单样本 t 检验

  • 卡方拟合优度检验

  • 卡方关联/独立性检验

有许多其他检验方法。然而,这三种是独特、简单且强大的检验方法的良好组合。选择我们应该实现哪种检验时,最需要考虑的因素之一是我们所处理的数据类型——特别是我们是否处理的是连续数据还是分类数据。为了真正看到假设的效果,我建议我们直接进入一个例子。首先,让我们看看使用t检验来处理连续数据。

单样本 t 检验

单样本t检验是一种统计检验,用于确定一个定量(数值)数据样本是否与另一个数据集(总体或其他样本)存在显著差异。假设在我们之前的员工休息时间示例中,我们特别关注工程部门的休息时间,如下所示:

long_breaks_in_engineering = stats.poisson.rvs(loc=10, mu=55, size=100)
short_breaks_in_engineering = stats.poisson.rvs(loc=10, mu=15, size=300)
engineering_breaks = np.concatenate((long_breaks_in_engineering, short_breaks_in_engineering))
print(breaks.mean())
# 29.99
print(engineering_breaks.mean())
# 34.825

请注意,我采取了与创建原始休息时间相同的方法,但有以下两个不同之处:

  • 我从泊松分布中取了一个较小的样本(以模拟我们从工程部门抽取了 400 人的样本)

  • 我没有像以前一样使用mu60,而是使用了55来模拟工程部门的休息行为与公司整体行为并不完全相同的事实。

很容易看出,工程部门和整个公司之间似乎存在差异(超过 5 分钟)。通常我们无法获得整个总体和总体参数,但我已经进行了模拟,以便示例能够工作。所以,尽管我们(全知的读者)可以看到差异,我们将假设我们对这些总体参数一无所知,而是依赖统计检验来确定这些差异。

单样本 t 检验的示例

我们在这里的目标是确认整体员工(公司员工)休息时间与工程部门员工休息时间之间是否存在差异。

现在,让我们在 95%的置信水*下进行t检验,以查找差异(或没有差异!)。从技术上讲,这个检验将告诉我们样本是否来自与总体相同的分布。

单样本 t 检验的假设

在进入五个步骤之前,我们必须首先承认,t检验必须满足以下两个条件才能正常工作:

  • 总体分布应该是正态分布,或者样本应该足够大(n ≥ 30)

  • 为了假设样本是独立地、随机抽取的,足以确保总体大小应至少是样本大小的 10 倍(10n < N)。

请注意,我们的测试要求底层数据要么符合正态分布(虽然我们知道对于我们来说并不成立),要么样本量至少要有 30 个点。对于t检验,这个条件足以假定正态性。这个检验还要求独立性,通过采取足够小的样本来满足这个要求。听起来有点奇怪,对吧?基本的想法是,我们的样本必须足够大,以便可以假设正态性(通过类似于中心极限定理的结论),但又要足够小,才能与总体独立。

现在,让我们遵循我们的五个步骤:

  1. 指定假设。

    我们将让<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msubmml:mrowmml:miH</mml:mi></mml:mrow>mml:mrowmml:mn0</mml:mn></mml:mrow></mml:msub></mml:math>= 工程部门休息的时间与公司整体相同。

    如果我们让这个为公司*均值,我们可以写出以下内容:

    <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msubmml:mrowmml:miH</mml:mi></mml:mrow>mml:mrowmml:mn0</mml:mn></mml:mrow></mml:msub></mml:math>:(工程部门休息的时间与 其他人相同)

注意

注意,这是我们的原假设或默认假设。它是我们在没有数据的情况下会假设的。我们想要展示的是备择假设。

现在我们实际上有了一些备择假设的选项,我们可以说工程部门的均值(我们称之为这样)低于公司*均值,高于公司*均值,或者直接与公司*均值不同(无论是高还是低):

  • 如果我们希望回答问题,样本均值是否与公司*均值不同?,那么这就是双尾检验,我们的备择假设将如下所示:

Ha:(工程部门休息时间与公司其他部分 不同)

  • 如果我们想要了解样本均值是否低于公司*均值,或者样本均值是否高于公司*均值,那么我们处理的是单尾检验,我们的备择假设将是以下其中一个假设:

    • <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msubmml:mrowmml:miH</mml:mi></mml:mrow>mml:mrowmml:mia</mml:mi></mml:mrow></mml:msub></mml:math>:(工程部门休息时间 更长)

    • <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msubmml:mrowmml:miH</mml:mi></mml:mrow>mml:mrowmml:mia</mml:mi></mml:mrow></mml:msub></mml:math>:(工程学需要 更短的休息时间)

单尾与双尾的区别在于后续分母是否被除以 2。对于这两者,过程保持完全不变。对于这个例子,我们选择双尾检验。所以,我们要测试的是工程部门的*均休息时间是否与公司*均休息时间不同。

我们的测试将得出两种可能的结论之一:我们要么拒绝原假设,这意味着工程部门的休息时间与公司*均休息时间不同,要么我们未能拒绝原假设,这意味着样本中没有足够的证据支持拒绝原假设。

  1. 确定测试样本的样本量。

    如前所述,大多数检验(包括本检验)假设底层数据是正态分布的,或者我们的样本在适当的范围内:

    • 样本至少包含 30 个点(实际为 400)。

    • 样本量小于总体的 10%(总体为 900 人)。

  2. 选择显著性水*(通常称为 alpha 或α)。我们将选择 95%的显著性水*,这意味着我们的 alpha 实际上是1 - .95 = .**05

  3. 收集数据。这是通过两个泊松分布生成的。

  4. 决定是否拒绝或未拒绝原假设。如前所述,这一步骤取决于所使用的测试类型。对于单样本t检验,我们需要计算两个数字:测试统计量和p值。幸运的是,我们可以在 Python 中一行完成这一步。

测试统计量是从样本数据中得出的值,通常用于某种假设检验中。它们用于决定是否拒绝原假设。

测试统计量用于将观察到的数据与原假设下的预期数据进行比较。测试统计量与p值一起使用。

p值是指观察到的数据偶然发生的概率。

当数据强烈反驳原假设时,测试统计量会变得很大(无论是正值还是负值),而p值通常会变得非常小,这意味着我们的测试结果非常显著,所发生的事情很可能不是偶然的。

对于t检验来说,t值就是我们的测试统计量,如下所示:

t_statistic, p_value = stats.ttest_1samp(a= engineering_breaks, popmean= breaks.mean())

我们输入engineering_breaks变量(它包含 400 次休息时间)和人口均值(popmean),然后得到以下数字:

t_statistic == -5.742
p_value == .00000018

测试结果显示 t 值为 -5.742。这是一个标准化指标,揭示了样本均值与原假设之间的偏差。p 值给出了我们的最终答案。我们的 p 值告诉我们我们的结果出现的概率。例如,如果我们的 p 值为 .06,那么这意味着我们应该预期大约 6% 的时间会通过随机机会观察到这个数据。这意味着大约 6% 的样本会得到类似的结果。

我们关心的是我们的 p 值与显著性水*的比较:

  • 如果 p 值小于显著性水*,那么我们可以拒绝原假设。

  • 如果 p 值大于显著性水*,那么我们未能拒绝原假设。

我们的 p 值远低于 .05(我们选择的显著性水*),这意味着我们可以拒绝原假设,支持备择假设。这表明,工程部门似乎与整个公司在休息时间长度上存在差异!

重要提示

p 值的使用存在争议。许多期刊实际上已经禁止在显著性检验中使用 p 值。这是因为该值的特性。假设我们的 p 值为 .04。这意味着 4% 的时间,我们的数据只是随机地以这种方式出现,并没有任何显著性。4% 其实并不是一个很小的百分比!因此,许多人开始转向其他统计检验方法。然而,这并不意味着 p 值是无用的。它仅仅意味着我们必须小心并意识到这个数字告诉我们的含义。

还有许多其他类型的 t 检验,包括前面提到的单尾检验、配对检验以及双样本 t 检验(这些还没有提到)。这些程序在统计文献中很容易找到;然而,我们应该关注一个非常重要的问题——当我们出错时会发生什么。

I 型和 II 型错误

在统计假设检验的领域中,可能会出现两种错误:I 型错误和 II 型错误。这些错误分别与假阳性和假阴性的概念相同。理解这些概念至关重要,因为它们突显了推论统计学中的潜在局限性和风险,其中结论是从样本数据中推断出总体信息。

I 型错误解释

I 型错误通常被称为 假阳性。假设你手机上有一个应用程序,用来识别它听到的音乐片段中的鸟鸣声。如果该应用程序表示它从环境噪音中识别出了鸟声,但周围并没有鸟类,那么它就犯了 I 型错误——它在没有鸟类的情况下警告你“命中”。在统计假设检验中,当我们拒绝一个真实的原假设时,就会发生这种错误。原假设通常代表默认立场或没有效果的陈述——例如,假设一种新药物对疾病没有效果。

在设置假设检验时,我们确定显著性水*(用α表示),它定义了拒绝原假设所需的证据量阈值。通常使用 5%的显著性水*,这意味着在原假设实际为真时,拒绝原假设的概率为 5%。这个 5%是我们愿意承担的类型 I 错误的风险。

解释类型 II 错误

相反,类型 II 错误发生在我们忽略了某些重要的事情——即假阴性。以一个旨在检测疾病的医学测试为例。如果测试结果为阴性,而病人实际上患有疾病,那么这个测试就犯了类型 II 错误。在假设检验的背景下,这个错误发生在原假设明明是假的情况下却没有被拒绝。例如,我们可能得出结论,新的药物对疾病没有效果,实际上它是有效的。

类型 II 错误的可能性用β表示,并且与显著性水*α成反比。当我们通过选择较小的α(例如,设置更高的置信度水*,如 99%)来降低犯类型 I 错误的风险时,我们无意中增加了类型 II 错误的风险。这是因为要求更强的证据来拒绝原假设(即更高的置信度水*)可能会使得检测到实际效应变得更加困难。

*衡这些错误是设计实验和解释统计结果的关键部分。研究人员必须决定在类型 I 错误和类型 II 错误之间的可接受*衡,通常是根据研究的背景和错误结论的潜在后果来做出决定。

类别变量的假设检验

T-检验(以及其他检验)是用于比较和对比定量变量与基础人口分布的假设检验。在本节中,我们将探讨两种新的检验,它们都是用来探讨定性数据的检验形式。这两种检验都是卡方检验。这两种检验将为我们执行以下两个任务:

  • 确定是否从特定人群中抽取了类别变量样本(类似于t-检验)

  • 确定两个变量是否相互影响并且存在关联

卡方拟合优度检验

单样本t-检验用于检查样本均值是否与总体均值不同。卡方拟合优度检验与单样本t-检验非常相似,都是用来检验样本数据的分布是否与预期分布相符,但最大的不同在于它是用来检验类别变量的。

例如,卡方拟合优度检验可以用来检验你公司种族人口统计是否与美国整体城市人口匹配。它还可以用来检验你网站的用户是否与普通互联网用户表现出相似的特征。

因为我们正在处理分类数据,所以必须小心,因为类别如“男性”、“女性”或“其他”没有任何数学意义。因此,我们必须考虑变量的计数而不是实际变量本身。

一般情况下,我们在以下情况下使用卡方拟合优度检验:

  • 我们想要分析一个群体中的一个分类变量

  • 我们想要确定一个变量是否符合指定或预期的分布

在卡方检验中,我们比较观察到的与我们期望的。

卡方拟合优度检验的假设

这个测试有两个通常的假设,如下所示:

  • 所有预期计数至少为5

  • 单个观察值是独立的,人口应至少是样本的 10 倍(10n < N

第二个假设看起来应该很熟悉,类似于t检验;然而,第一个假设可能看起来很陌生。预期计数是我们尚未讨论但即将讨论的内容!

在制定此测试的零假设和备择假设时,我们考虑分类变量的默认分布。例如,如果我们有一个骰子,我们正在测试其结果是否来自公*骰子,我们的假设可能如下所示:

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msubmml:mrowmml:miH</mml:mi></mml:mrow>mml:mrowmml:mn0</mml:mn></mml:mrow></mml:msub></mml:math>:分类变量的指定分布是正确的。

p1=16,p2=16,p3=16,p4=16,p5=16,p6=16

我们的备择假设非常简单,如下所示:

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msubmml:mrowmml:miH</mml:mi></mml:mrow>mml:mrowmml:mn0</mml:mn>mml:mia</mml:mi></mml:mrow></mml:msub></mml:math>:分类变量的指定分布不正确。至少有一个 pi 值不正确。

t检验中,我们使用我们的检验统计量(t值)来找到我们的p值。在卡方检验中,我们的检验统计量是卡方值:

检验统计量:χ2 = 超过 k 个类别

自由度 = k − 1

临界值是当我们使用<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msupmml:mrowmml:miχ</mml:mi></mml:mrow>mml:mrowmml:mn2</mml:mn></mml:mrow></mml:msup></mml:math>以及我们的自由度和显著性水*时,如果p值低于我们的显著性水*,则拒绝原假设(与之前相同)。

让我们看一个例子,进一步理解这个过程。

卡方适配度检验示例

CDC 将成年人的 BMI 分为四个类别:过轻/正常超重肥胖极度肥胖。2009 年的一项调查显示,美国成年人群体的 BMI 分布分别为 31.2%、33.1%、29.4%和 6.3%。共有 500 名成人被随机抽样,并记录了他们的 BMI 类别。

图 8.6 – 各个 BMI 类别中人员的原始数据

图 8.6 – 各个 BMI 类别中人员的原始数据

是否有证据表明 BMI 趋势自 2009 年以来发生了变化?让我们在0.05的显著性水*下进行检验:

  1. 首先,让我们计算我们的预期值。在 500 人的样本中,我们预期有 156 人属于过轻/正常(即 500 人的 31.2%),然后以同样的方式填写剩余的类别:

图 8.7 – 与之前的图表相同的原始数据,但增加了每个类别中基于 2009 年调查“预期”值的行

图 8.7 – 与之前的图表相同的原始数据,但增加了每个类别中基于 2009 年调查“预期”值的行

  1. 现在,让我们检查一下我们测试的条件是否得到满足:

    • 所有预期计数都大于五

    • 每个观察值都是独立的,并且我们的总体非常庞大(远远超过 500 人的 10 倍)

  2. 接下来,我们将进行适配度检验。我们将设置原假设和备择假设:

    • <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msubmml:mrowmml:miH</mml:mi></mml:mrow>mml:mrowmml:mn0</mml:mn></mml:mrow></mml:msub></mml:math>:2009 年 BMI 分布依然准确。

    • <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msubmml:mrowmml:miH</mml:mi></mml:mrow>mml:mrowmml:mia</mml:mi></mml:mrow></mml:msub></mml:math>:2009 年 BMI 分布不再准确(至少有一个比例现在不同)。我们可以手动计算我们的检验统计量:

图 8.8 – 演示我们的检验统计量计算过程。继续阅读,了解如何使用 Python 代码为你完成计算!

图 8.8 – 演示我们的检验统计量计算过程。继续阅读,了解如何使用 Python 代码为你完成计算!

  1. 另外,我们也可以利用我们得心应手的 Python 技巧,像这样:

    observed = [102, 178, 186, 34]
    
    expected = [156, 165.5, 147, 31.5]
    
    chi_squared, p_value = stats.chisquare(f_obs= observed, f_exp= expected)
    
    chi_squared, p_value
    
    #(30.1817679275599, 1.26374310311106e-06)
    

我们的 p 值低于 .05,因此我们可以拒绝零假设,支持今天的 BMI 趋势与 2009 年有所不同这一事实。

卡方检验(Chi-square test)用于检验关联/独立性

概率论中的独立性概念是指知道一个变量的值并不能提供关于另一个变量值的任何信息。例如,我们可能会认为出生的国家和月份是独立的。然而,知道你使用的手机类型可能会暗示你的创造力水*。这些变量可能并非独立的。

卡方检验(Chi-square test)用于检验两个分类变量是否相互独立。独立性检验通常用于判断某些变量,如教育水*或税务等级,是否根据人口统计因素(如性别、种族和宗教)而有所不同。让我们回顾一下上一章中的例子——A/B 分流测试。

回顾一下我们做了一个测试,将一半用户暴露于某个着陆页(网站 A),另一半用户暴露于另一个着陆页(网站 B),然后测量了两个网站的注册率。我们得到了以下结果:

未注册 已注册
网站 A 134 54
网站 B 110 48

图 8.9 – 我们 A/B 测试的结果

我们计算了网站的转化率,但我们真正想知道的是两个变量之间是否存在差异:用户暴露于哪个网站?用户是否注册? 为此,我们将使用卡方检验。

卡方独立性检验的假设

该检验有以下两个假设:

  • 所有期望频数至少为 5

  • 个体观察是独立的,且总体的大小应至少是样本的 10 倍(10n < N

请注意,这与上一轮卡方检验的结果完全相同。

让我们建立我们的假设:

  • <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msubmml:mrowmml:miH</mml:mi></mml:mrow>mml:mrowmml:mn0</mml:mn></mml:mrow></mml:msub></mml:math>: 两个分类变量在研究人群中没有关联

  • <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msubmml:mrowmml:miH</mml:mi></mml:mrow>mml:mrowmml:mn0</mml:mn></mml:mrow></mml:msub></mml:math>: 两个分类变量在研究人群中是独立的

  • <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msubmml:mrowmml:miH</mml:mi></mml:mrow>mml:mrowmml:mia</mml:mi></mml:mrow></mml:msub></mml:math>: 两个分类变量在研究人群中有关系

  • <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msubmml:mrowmml:miH</mml:mi></mml:mrow>mml:mrowmml:mia</mml:mi></mml:mrow></mml:msub></mml:math>:在所关注的总体中,两个分类变量是相关的

你可能会注意到我们这里缺少了一些重要内容。期望频数在哪里?之前我们有一个先验分布来与我们的观察结果进行比较,但现在我们没有。为此,我们需要创建一些期望频数。我们可以使用以下公式计算每个值的期望值。在表格的每一格中,我们可以使用以下公式:

期望频数 = 用于计算我们的卡方检验统计量和自由度

检验统计量:χ2=∑观察r,c−期望r,c2期望r,c

自由度:r−1⋅c−1

这里,r是行数,c是列数。当然,像之前一样,当我们计算p值时,如果该p值小于显著性水*,我们将拒绝零假设。让我们使用一些内置的 Python 方法,如下所示,以便快速获得我们的结果:

observed = np.array([[134, 54],[110, 48]])
# built a 2x2 matrix as seen in the table above
chi_squared, p_value, degrees_of_freedom, matrix = stats.chi2_contingency(observed= observed)
chi_squared, p_value
# (0.04762692369491045, 0.82724528704422262)

我们可以看到我们的p值相当大;因此,我们无法拒绝零假设,也不能确定查看特定网站是否对用户是否注册有任何影响。这些变量之间没有关联。

总结

在本章中,我们研究了不同的统计检验,包括卡方检验和t检验,以及点估计和置信区间,以便根据样本数据推断总体参数。我们发现,即使在样本数据较小的情况下,我们也能对潜在的总体做出有力的假设。

运用本章回顾的概念,数据科学家将能够基于某些数据样本对整个数据集进行推断。此外,基于数据样本,他们还将能够使用假设检验来更好地理解完整的数据集。

统计学是一个非常广泛而深入的学科,不可能仅通过一章来全面覆盖;然而,对这一学科的理解将使我们能够继续讨论如何在下一章中通过数据科学运用统计和概率来传达我们的思想。

在下一章中,我们将讨论包括各种演示风格和可视化技术在内的不同数据分析结果传达方式。

第九章:沟通数据

本章讨论的是我们如何以不同的方式传达分析结果。在这里,我们将探讨不同的展示风格以及可视化技巧。本章的核心是将我们的结果以连贯、易懂的方式解释清楚,让任何人,无论是否精通数据,都能理解并运用我们的结果。

我们将讨论的许多内容将集中在如何通过标签、键、颜色等创建有效的图表。我们还将研究一些更高级的可视化技巧,例如*行坐标图。

在本章中,我们将探讨以下主题:

  • 识别有效和无效的可视化

  • 识别图表是否试图“欺骗”观众

  • 能够识别因果关系与相关性

  • 构建能提供有价值洞察的吸引人视觉效果

为什么沟通很重要?

能够在编程语言中进行实验和操作数据不足以进行实际的应用数据科学。这是因为数据科学通常只取决于它在实践中的使用效果。例如,一位医学数据科学家可能能够预测游客在发展中国家感染疟疾的几率,准确率超过 98%;然而,如果这些结果发表在一个营销不力的期刊上,且在线提及该研究的机会极少,那么他们可能拯救生命的突破性成果将永远无法真正为人所知。

因此,结果的沟通可以说与结果本身一样重要。一个著名的结果分发管理不善的例子是格雷戈尔·孟德尔的案例。孟德尔被广泛认为是现代遗传学的奠基人之一。然而,他的研究结果(包括数据和图表)直到他去世后才被广泛采用。孟德尔甚至将这些结果发送给查尔斯·达尔文,但达尔文大多忽视了孟德尔的论文,这些论文发表在不为人知的摩拉维亚期刊上。

通常,展示结果有两种方式:口头和视觉。自然,口头和视觉沟通形式可以细分为数十个子类别,包括幻灯片、图表、期刊论文,甚至大学讲座。然而,我们可以找到数据展示中的共性元素,这些元素可以帮助该领域的任何人提高意识,并提升沟通技巧。

让我们直接进入有效(和无效)的沟通形式,从视觉效果开始。

识别有效的可视化

数据可视化的主要目标是让读者快速消化数据,包括可能的趋势、关系等。理想情况下,读者不应花费超过 5-6 秒钟来理解单一可视化内容。因此,我们必须非常重视视觉效果,并确保尽可能地提升其有效性。让我们看看五种基本的图表类型:散点图、折线图、柱状图、直方图和箱线图。

散点图

散点图可能是最简单的图表之一。它是通过创建两个定量坐标轴并使用数据点来表示观察结果来构建的。散点图的主要目的是突出显示两个变量之间的关系,并且如果可能的话,揭示它们之间的相关性。

例如,我们可以观察两个变量:每天观看电视的*均小时数和工作表现的 0-100 分尺度(0 为表现很差,100 为表现优秀)。这里的目标是找出观看电视与工作表现之间的关系(如果存在的话)。

以下代码模拟了一个调查,调查了几个人在一天中观看电视的*均小时数,并与公司标准的工作表现指标进行了比较。这行代码生成了 14 个样本调查结果,回答了他们每天观看多少小时电视的问题:

import pandas as pd
hours_tv_watched = [0, 0, 0, 1, 1.3, 1.4, 2, 2.1, 2.6, 3.2, 4.1, 4.4, 4.4, 5]

下面这行代码创建了 14 个新的样本调查结果,这些结果是对同一群人根据其工作表现从 0 到 100 的评分。例如,第一个人每天看 0 小时的电视,工作表现评分为 87/100,而最后一个人*均每天看 5 小时电视,工作表现评分为 72/100:

work_performance = [87, 89, 92, 90, 82, 80, 77, 80, 76, 85, 80, 75, 73, 72]

在这里,我们正在创建一个数据框(DataFrame),以简化我们的探索性数据分析EDA),使得制作散点图变得更加容易:

df = pd.DataFrame({'hours_tv_watched':hours_tv_watched, 'work_performance':work_performance})

现在,我们实际上在制作我们的散点图:

df.plot(x='hours_tv_watched', y='work_performance', kind='scatter')

在下方的图表中,我们可以看到,坐标轴代表了每天观看电视的小时数和该人的工作表现指标:

图 9.1 – 散点图:观看电视的小时数与工作表现

图 9.1 – 散点图:观看电视的小时数与工作表现

散点图上的每个点代表一个单独的观察值(在这个例子中是一个人),其位置是该观察值在每个变量上的位置。这个散点图似乎确实显示了某种关系,这意味着我们一天看电视的时间似乎影响我们的工作表现。

当然,正如我们从前两章中学到的统计学知识,现在我们已经是统计学专家了,我们知道这可能并不是因果关系。散点图可能只是用来揭示相关性或关联性,而不是因果关系。像我们在第八章中看到的高级统计学测试,可能有助于揭示因果关系。在本章后面,我们将看到信任相关性可能带来的危害。

折线图

折线图可能是数据传递中最常用的图表之一。折线图通过连接数据点的线条来表示数据,通常将时间表示在 x 轴上。折线图是展示变量随时间变化的流行方式。折线图和散点图一样,用于绘制定量变量。

作为一个很好的例子,许多人会好奇我们在电视上看到的内容是否与我们在现实世界中的行为有某种联系。我的一个朋友曾将这种思考推向极端:他想知道是否能找到 X 档案 这档电视节目与美国 UFO 目击事件之间的关系。他找到了每年 UFO 目击的数量并将其绘制成时间图表。随后,他加上了一张简洁的图表,确保读者能够辨认出 X 档案 首播的时间点:

图 9.2 – 自 1963 年以来报告的 UFO 目击总数

图 9.2 – 自 1963 年以来报告的 UFO 目击总数

看起来很明显,1993 年 X 档案 首播之后,UFO 目击的数量开始急剧上升。

这张图表虽然轻松幽默,但却是一个简单折线图的绝佳示例。我们知道每个坐标轴表示什么,能够快速看到数据的一般趋势,并且可以理解作者的意图,即展示 UFO 目击数量与 X 档案 首播之间的关系。

另一方面,以下是一个不太令人印象深刻的折线图:

图 9.3 – 折线图:油价变化

图 9.3 – 折线图:油价变化

这条折线图试图通过绘制三个时间点来突出显示油价的变化。乍一看,它与前面的图表没有太大区别;我们在底部的 x 轴上标记时间,垂直的 y 轴上标记定量数值。这里的(并不那么)微妙的区别是,这三个点在 x 轴上等间隔排列;然而,如果我们查看它们的实际时间指示,它们在时间上并没有等间隔。第一个和第二个点之间相隔一年,而最后两个点之间仅相隔 7 天。

柱状图

我们通常在尝试比较不同组之间的变量时使用柱状图。例如,我们可以使用柱状图绘制每个大洲的国家数量。请注意,x 轴并不代表定量变量;事实上,在使用柱状图时,x 轴通常是一个类别变量,而 y 轴则是定量的。

请注意,对于这段代码,我使用了世界卫生组织WHO)关于各国酒精消费的报告:

from matplotlib import pyplot as plt
drinks =
pd.read_csv('https://raw.githubusercontent.com/sinanuozdemir/principles_of_ data_science/master/data/chapter_2/drinks.csv') drinks.continent.value_counts().plot(kind='bar', title='Countries per Continent')
plt.xlabel('Continent')
plt.ylabel('Count')

下图展示了各大洲的国家数量。我们可以看到柱子底部的洲代码,柱子的高度表示每个大洲的国家数量。例如,我们看到非洲的国家数量在我们的调查中最多,而南美洲的国家数量最少:

图 9.4 – 柱状图:各大洲国家数量

图 9.4 – 柱状图:各大洲国家数量

除了国家数量,我们还可以使用柱状图绘制每个大洲的*均啤酒供应量,如下所示:

drinks.groupby('continent').beer_servings.mean().plot(kind='bar')

上述代码给我们生成了这张图表:

图 9.5 – 条形图:每个国家*均啤酒消费量

图 9.5 – 条形图:每个国家*均啤酒消费量

注意,散点图或折线图无法支持这些数据,因为它们只能处理定量变量;而条形图能够展示分类值。

我们还可以使用条形图来绘制随时间变化的变量,就像折线图一样。

直方图

直方图通过将数据按范围划分为等间距的区间,并绘制每个区间中的观测频次,展示单个定量变量的频率分布。直方图实际上是一个条形图,其中X轴是一个区间(子范围)的值,Y轴是计数。例如,我将导入一项商店的每日独立顾客数量,如下所示:

rossmann_sales = pd.read_csv('data/rossmann.csv')
rossmann_sales.head()

我们得到以下表格:

图 9.6 – 商店每日独立顾客数量

图 9.6 – 商店每日独立顾客数量

注意,我们有多家商店的数据(在Store列中)。让我们仅提取第一家商店的数据,如下所示:

first_rossmann_sales = rossmann_sales[rossmann_sales['Store']==1]

现在,让我们绘制第一家商店的顾客数量直方图:

first_rossmann_sales['Customers'].hist(bins=20)
plt.xlabel('Customer Bins')
plt.ylabel('Count')

这是我们得到的:

图 9.7 – 直方图:顾客数量

图 9.7 – 直方图:顾客数量

现在,X轴是分类的,每个类别是一个选定的值范围;例如,600-620 顾客可能是一个类别。Y轴像条形图一样,绘制每个类别中的观测次数。在此图中,例如,可以得出大部分时间内,任何一天的顾客数量都会落在 500 到 700 之间。

总的来说,直方图用于可视化定量变量可能取值的分布。

重要提示

在直方图中,我们不会在条形之间留空隙。

箱线图

箱线图也用于展示值的分布。它们通过绘制以下五个数字的汇总来创建:

  • 最小值

  • 第一四分位数(将 25%的最低值与其余值分开的数值)

  • 中位数

  • 第三四分位数(将 25%的最高值与其余值分开的数值)

  • 最大值

pandas中,当我们创建箱线图时,红线表示中位数,箱子的上边缘(如果是横向图则为右边)是第三四分位数,箱子的下边缘(左边)是第一四分位数。

以下是一系列箱线图,展示了按大洲划分的啤酒消费分布:

drinks.boxplot(column='beer_servings', by='continent')

我们得到这个图:

图 9.8 – 箱线图:按大洲划分的啤酒消费量

图 9.8 – 箱线图:按大洲划分的啤酒消费量

现在,我们可以清楚地看到七大洲啤酒消费的分布情况及其差异。非洲和亚洲的啤酒消费中位数远低于欧洲或北美。

箱线图的额外优点是,它比直方图更能清晰地显示异常值。这是因为最小值和最大值都是箱线图的一部分。

回到顾客数据,让我们来看一下同一家商店的顾客数量,不过这次使用的是箱线图:

first_rossmann_sales.boxplot(column='Customers', vert=False)

这是我们得到的图表:

图 9.9 – 箱线图:顾客销售

图 9.9 – 箱线图:顾客销售

这与之前在直方图中绘制的数据完全相同;不过,现在它显示为箱线图。为了便于比较,我将依次展示这两张图:

图 9.10 – 直方图:顾客数量

图 9.10 – 直方图:顾客数量

图 9.11 – 箱线图:顾客销售

图 9.11 – 箱线图:顾客销售

注意每个图表的x轴是相同的,范围从 0 到 1200。箱线图可以更快速地为我们提供数据的中心,红线是中位数,而直方图则更适合展示数据的分布情况以及人群的最大数据集中位置。例如,直方图显示有一个很大的零人群体。这意味着在超过 150 天的数据中,有零个顾客。

注意,我们可以使用pandas中的describe功能来获取构建箱线图所需的准确数据,示例如下:

first_rossmann_sales['Customers'].describe()

|

min

|

0.000000

|

|

25%

|

463.000000

|

|

50%

|

529.000000

|

|

75%

|

598.750000

|

|

max

|

1130.000000

|

当图表和统计数据撒谎时

我需要明确的是:统计数据不会撒谎;人们会撒谎。欺骗观众的最简单方法之一就是将相关性和因果关系混淆。

相关性与因果关系

我认为如果不深入探讨相关性和因果关系之间的区别,我是不会被允许出版这本书的。为了这个例子,我将继续使用我的电视消费数据和工作表现数据。

相关性是一个介于-1 和 1 之间的定量指标,用来衡量两个变量如何相互变化。如果两个变量的相关性接近-1,这意味着当一个变量增加时,另一个变量减少;如果两个变量的相关性接近+1,这意味着这两个变量在同一方向上一起变化:一个增加时,另一个也增加,减少时也一样。

因果关系是指一个变量影响另一个变量的观点。例如,我们可以看两个变量:每天观看电视的*均小时数和 0-100 的工作表现评分(0 代表表现非常差,100 代表表现优秀)。我们可能会期望这两个因素是负相关的,这意味着在 24 小时内,电视观看的小时数越多,整体工作表现就越差。回想一下之前的代码,它如下所示。在这里,我查看的是之前 14 个人的样本,以及他们对于问题“你*均每天晚上观看多少小时电视?”的回答:

import pandas as pd
hours_tv_watched = [0, 0, 0, 1, 1.3, 1.4, 2, 2.1, 2.6, 3.2, 4.1, 4.4, 4.4, 5]

这些是之前提到的同样 14 个人,顺序不变,但现在,取而代之的是他们的工作表现,工作表现是由公司或第三方系统评分的:

work_performance = [87, 89, 92, 90, 82, 80, 77, 80, 76, 85, 80, 75, 73, 72]

然后,我们生成一个数据框(DataFrame):

df = pd.DataFrame({'hours_tv_watched':hours_tv_watched, 'work_performance':work_performance})

之前,我们查看了这两个变量的散点图,它似乎清晰地显示了变量之间的下降趋势:随着电视观看时间的增加,工作表现似乎下降。然而,相关系数,介于 -1 和 1 之间的数字,是识别变量之间关系的一个好方法,同时也可以量化它们并分类其强度。

现在,我们可以引入一行新代码,展示这两个变量之间的相关性:

df.corr() # -0.824

回想一下,接近 -1 的相关性表示强负相关,而接近 +1 的相关性则表示强正相关。

这个数字有助于支持假设,因为接近 -1 的相关系数不仅表示负相关,而且是强相关。我们可以通过两个变量之间的散点图看到这一点。因此,我们的图示和数字相互一致。这是一个在传达结果时应该始终成立的重要概念。如果你的图示和数字不一致,人们就不太可能认真对待你的分析:

图 9.12 – 相关性:观看电视的小时数与工作表现

图 9.12 – 相关性:观看电视的小时数与工作表现

我无法强调足够的是,相关性和因果关系是不同的。相关性只是量化变量共同变化的程度,而因果关系则是一个变量实际决定另一个变量的值。如果你希望分享你的相关性研究结果,听众中可能会有挑战者要求你做更多的工作。更可怕的是,没人知道分析是有缺陷的,而你可能会基于简单的相关性分析做出可操作的决策。

很常*的情况是,两个变量可能相关,但它们之间并没有因果关系。这可能有多种原因,其中一些如下:

  • 它们之间可能存在一个混杂因素。这意味着在背后可能有一个第三个变量没有被考虑进来,且它充当了两个变量之间的桥梁。例如,我们之前展示过你可能会发现看电视的时间与工作表现呈负相关;也就是说,当你看电视的时间增加时,你的总体工作表现可能会下降。这就是一种相关性。看电视似乎并不是工作质量下降的真正原因。更合理的解释可能是存在第三个因素,也许是每天的睡眠时间,来解答这个问题。也许,观看更多电视减少了你休息的时间,从而限制了你的工作表现。每晚的睡眠时间就是这个混杂因素。

  • 它们可能没有任何关系!这可能仅仅是巧合。有很多变量是相关的,但并不相互导致。考虑以下示例:

图 9.13 – 相关性分析:奶酪消费与土木工程博士学位

图 9.13 – 相关性分析:奶酪消费与土木工程博士学位

这些变量之间更有可能仅仅是偶然相关(比我们之前的例子更强烈,我补充一下),而不是奶酪消费决定了全球土木工程博士学位的数量。

你可能听过“相关性不等于因果关系”这句话,最后一张图正是数据科学家必须相信这一点的原因。仅仅因为变量之间存在数学相关性,并不意味着它们之间有因果关系。它们之间可能有混杂因素,或者它们可能根本没有任何关系!

让我们看看当我们忽视混杂变量时,相关性变得极具误导性。

辛普森悖论

辛普森悖论是一个正式的理由,说明为什么我们需要认真对待混杂变量。这个悖论指出,当我们考虑到不同的因素时,两个变量之间的相关性可能会完全颠倒。这意味着,即使一张图表显示正相关,在考虑另一个因素(很可能是混杂因素)时,这些变量可能会变成反相关。这对统计学家来说可能非常棘手。

假设我们希望探索两个不同着陆页之间的关系(回想一下我们之前在第七章中做的 A/B 测试,机会有多大?统计学入门)。我们将这些页面再次称为页面 A页面 B。我们有两个启动页面,希望进行比较和对比,我们选择的主要指标将是我们的转化率,就像之前一样。

假设我们进行了一项初步测试,并得到了以下转化结果:

Page A Page B
75% (263/350) 83% (248/300)

表格 9.1 – 进行初步测试

这意味着Page B的转化率比Page A高出近 10%。因此,一开始看起来Page B是更好的选择,因为它的转化率更高。如果我们要将这些数据传达给同事,似乎我们已经得出结论了!

然而,让我们看看当我们还考虑到用户所在的美国海岸时会发生什么,具体如下所示:

Page A Page B
西海岸 95% (76/80)
东海岸 72% (193/270)
两者 75% (263/350) 83% (248/300)

表格 9.2 – 按用户所在海岸分解我们网站的使用情况

因此,悖论出现了!当我们按地点划分样本时,似乎Page A两者类别中都表现更好,但总体表现较差。这就是悖论的魅力所在,同时也是它令人恐惧的原因。之所以会发生这种情况,是因为四个小组之间的类别不*衡。

Page A/东海岸组和Page B/西海岸组为样本提供了大部分人群,因此导致结果偏离预期。这里的混杂变量可能是页面在不同时间段提供,西海岸的人更可能看到Page B,而东海岸的人更可能看到Page A

Simpson 悖论是有解决办法的(因此也有答案);然而,证明这一点需要复杂的贝叶斯网络系统,超出了本书的范围。

Simpson 悖论的主要启示是,我们不应该过度赋予相关变量因果性。可能存在需要检验的混杂变量。因此,如果你能够揭示两个变量之间的相关性(例如网站类别与转化率,或电视消费与工作表现),那么你绝对应该尽量隔离可能导致相关性的变量,或者至少帮助进一步解释你的情况。

如果相关性不意味着因果关系,那么到底意味着什么呢?

作为数据科学家,处理相关性时常常令人沮丧,因为无法得出明确的因果关系。通常,获得因果关系的最佳方式是通过随机实验,例如我们在第八章《高级统计学》中看到的那种实验。人们必须将总体分成随机抽样的小组,并进行假设检验,以一定程度的确定性得出变量之间存在真正因果关系的结论。

口头交流

除了数据的可视化展示外,口头交流在展示结果时同样重要。如果你不仅仅是上传结果或发布数据,通常你是在向一群数据科学家和高层管理人员,或向一个会议大厅展示数据。

无论如何,进行口头报告时,尤其是当报告涉及数据发现时,有一些关键领域需要关注。

口头报告通常有两种风格:一种适用于更专业的场合,包括公司办公室,通常问题直接与公司业绩或某些关键绩效指标KPI)相关;另一种更适用于同龄人之间的交流,主要目的是激励观众关注并关心你的工作。

这就是讲故事

无论是正式还是非正式的演讲,人们都喜欢听故事。当你呈现结果时,你不仅仅是在罗列事实和数据;你是在尝试塑造观众的思维,让他们相信并关心你所说的内容。

在进行演讲时,要时刻关注你的观众,并尝试评估他们对你所说内容的反应和兴趣。如果他们看起来没有参与感,试着将问题与他们联系起来。例如,你可以以一个有冲击力的陈述开场,旨在吸引注意力,并暗示你演讲其余部分的内容:

想一想,当像《权力的游戏》这样受欢迎的电视剧回归时,你的员工们都会花更多时间看电视,因此工作表现会更低

现在,你引起了他们的注意。与观众建立联系是关键;无论是你的老板还是你妈妈的朋友,你都需要找到让内容相关的方法。

在更正式的场合中

当向更正式的观众展示数据发现时,我喜欢遵循以下六个步骤:

  1. 概述问题的现状:在这一步中,我们将讨论问题的当前状态,包括问题是什么以及问题是如何引起数据科学团队注意的。

  2. 定义数据的性质:在这一部分,我们将更深入地讨论这个问题影响的人群,解决方案如何改变现状,以及之前在该问题上的相关工作(如果有的话)。

  3. 揭示初步假设:在这一部分,我们陈述在进行任何工作之前我们认为的解决方案。这可能看起来像是一种更初级的演讲方式;然而,这也是一个很好的时机,不仅可以概述你最初的假设,还可以概述整个公司可能的假设。例如:“我们进行了调查,61%的员工认为看电视时间与工作表现之间没有相关性。”

  4. 描述解决方案及可能的工具:详细讲解你是如何解决问题的,使用了哪些统计检验,以及在解决问题过程中做出的假设。

  5. 分享你的解决方案对问题的影响:谈谈你的解决方案是否与最初的假设不同。未来会因此发生什么变化?我们如何能够采取行动,从这个解决方案中改进自己和公司?

  6. 未来步骤:分享为了解决问题可以采取的未来步骤,比如如何实施解决方案以及这项研究引发了哪些进一步的工作。

通过遵循这些步骤,我们可以涵盖数据科学方法的所有重要领域。在正式演示中,你首先要关注的就是行动。你希望你的语言和解决方案具有可操作性。项目完成后必须有一条清晰的路径,并且未来的步骤应该得到明确的定义。

演示的为什么/如何/什么策略

在较为非正式的场合,为什么/如何/什么策略是一种快速且简单的方式,可以制作出值得称赞的演示文稿。它非常简单,具体如下。

这个模型借鉴了著名的广告形式——那些在最后 3 秒才会告诉你产品是什么的广告。他们想先抓住你的注意力,然后最终揭示那个让人激动的东西是什么。考虑以下例子:

大家好。我在这里告诉大家为什么我们在奥运会播放时似乎很难专注于工作。在挖掘调查结果并将这些数据与公司标准的工作表现数据合并后,我发现了每天观看电视的小时数与*均工作表现之间的关联。知道这一点后,我们可以更加意识到自己的电视观看*惯,确保它不会影响我们的工作。 谢谢大家。

本章的格式其实就是这样!我们首先探讨了为什么我们应该关注数据传达,然后讨论了如何实现这一目标(通过相关性、可视化等),最后,我告诉你们什么,即为什么/如何/什么策略(在此插入震撼的音效)。

总结

数据传达不是一项容易的任务。理解数据科学如何运作的数学原理是一回事,但要试图说服一群数据科学家和非数据科学家相信你的结果及其对他们的价值则完全是另一回事。在本章中,我们讨论了如何制作基本图表,如何识别错误的因果关系,以及如何提升我们的口头表达技巧。

接下来的几章将真正开始触及数据科学中最重要的讨论点之一。在过去的九章中,我们讲述了如何获取数据、清理数据以及如何可视化数据,以便更好地理解数据所代表的环境。

接下来,我们转向了基本和高级的概率/统计学定律,以便利用量化的定理和测试对我们的数据进行分析,从而得到可操作的结果和答案。

在接下来的章节中,我们将深入探讨机器学*ML)及其在某些情况下表现优秀而在其他情况下表现不佳的情况。在探索这部分内容时,我敦促各位读者保持开放的心态,真正理解机器学*不仅是如何工作的,还要理解为什么我们需要使用它。

第十章:如何判断你的烤面包机是否在学*——机器学*基础

每次我们听到关于下一个伟大创业公司或打开新闻时,都会听到关于革命性机器学*ML)或人工智能AI)技术的消息,以及它如何改变我们的生活方式。本章关注机器学*作为数据科学的实际应用部分。我们将在本章中讨论以下主题:

  • 定义不同类型的机器学*,并提供每种类型的示例

  • 回归与分类

  • 什么是机器学*,它如何在数据科学中应用?

  • 机器学*与统计建模的区别,以及机器学*如何是后者的一个广泛类别

  • 线性回归简介

本章的目标是利用统计学、概率论和算法思维,理解并应用机器学*的基本技能于实际行业中,如营销。示例包括预测餐厅评论的星级评分、预测疾病的存在、垃圾邮件检测等。 本章关注机器学*作为一个整体以及作为单一统计模型。后续章节将涉及更多的模型,其中一些模型更为复杂。

我们还将把焦点转向度量标准,这些标准告诉我们模型的有效性。我们将使用度量标准来得出结果并使用机器学*做出预测。

介绍机器学*

第一章《数据科学术语》中,我们将机器学*(ML)定义为使计算机能够从数据中学*,而无需程序员提供明确的规则。这一定义仍然成立。机器学*关注的是从数据中识别出某些模式(信号)的能力,即使数据本身存在固有的错误(噪声)。

机器学*模型能够从数据中学*,而不需要人类的明确指导。这是机器学*模型与经典非机器学*算法之间的主要区别。

经典算法由人类直接告知如何在复杂系统中找到最佳答案,然后算法实现这些最佳解决方案,通常比人类更快、更高效。然而,这里的瓶颈是,人类首先需要提出最佳解决方案,然后告诉算法该如何操作。在机器学*中,模型并不直接告知最佳解决方案,而是提供多个问题实例,让其自行找出最佳解决方案。

机器学*只是数据科学家工具箱中的另一个工具。它与统计检验(如卡方检验或 t 检验)处于同一水*,或使用基本的概率或统计方法来估算总体参数。机器学*常被视为数据科学家唯一会做的事情,但这并不正确。真正的数据科学家能够识别机器学*何时适用,更重要的是,何时不适用。

机器学*是一个关于相关性和关系的游戏。现存的大多数机器学*算法都关注寻找和/或利用数据集之间的关系(通常表示为pandas DataFrame 中的列)。一旦机器学*算法能够确定某些相关性,模型就可以利用这些关系来预测未来的观察结果,或者将数据概括出来,揭示出有趣的模式。

也许解释机器学*的一个好方法是提供一个问题的例子,并附带两个可能的解决方案:一个使用机器学*算法,另一个使用非机器学*算法。

例子——人脸识别

这个问题,表面上看(双关语),非常简单:给定一张人脸照片,它属于谁?然而,让我们考虑一个稍微简单一点的任务。假设你希望实现一个家庭安全系统,能够识别谁正在进入你的家。很可能,在白天,你的家大部分时间是空的,只有当画面中有人的时候,人脸识别才会启动。这正是我提出的我们要解决的问题——给定一张照片,里面是否有可以识别的人脸?

给定这个任务定义,我提出以下两种解决方案:

  • 一个非机器学*算法将定义人脸为具有圆形结构、两只眼睛、头发、鼻子等。该算法随后在照片中寻找这些硬编码的特征,并返回是否能够找到这些特征。

  • 一个机器学*算法将以略微不同的方式工作。该模型将只给定一些标记为“人脸”和“非人脸”的图片。从这些例子(称为训练集)中,它将搞清楚“人脸”的定义。

机器学*版本的解决方案从未告诉它什么是“人脸”;它仅仅是给定了几个例子——一些是包含人脸的,一些是没有的。接下来,机器学*模型需要自己找出这两者之间的区别。一旦它搞清楚了这一点,它就会利用这些信息去分析一张新图片,预测其中是否有脸。例如,在训练系统时,我们可能会有如下图像,如图 10.1所示:

图 10.1 – 训练机器学*模型的输入图像

图 10.1 – 训练机器学*模型的输入图像

模型将弄清楚标记为人脸的照片和标记为非人脸的照片之间的区别,并能够利用这种差异在未来的照片中找到人脸。由于机器学*的承诺——仅通过数据进行学*,无需明确的人类干预——非常有吸引力,很多人可能会认为机器学*是完美的,但事实并非如此。

机器学*并不完美

机器学*有许多警告和注意事项。很多是针对特定模型的实现的,但一些假设对任何机器学*模型来说都是普遍适用的:

  • 所使用的数据,大多数已经通过前面章节中概述的方法进行预处理和清洗。几乎没有任何机器学*(ML)模型能够容忍极其脏乱或不完整的数据,尤其是带有缺失值或分类值的数据。

  • 每一行清理过的数据集代表了我们试图建模的环境中的单个观测值。

  • 整体数据应该能够代表我们正在解决的任务。这听起来可能很明显,但在许多情况下,人们使用的数据来训练机器学*模型与任务有某种关系,但并不完全相关。这在刑事司法领域尤为常*,可能有人使用逮捕数据来训练模型预测犯罪行为,但当然,逮捕并不等同于判定某人有罪。

  • 如果我们的目标是找到变量之间的关系,那么就有一个假设,即这些变量之间存在某种关系。再次强调,这似乎很明显,但如果整理数据的人有偏*,并且“相信”数据之间存在关系,那么他们可能错误地判断机器学*模型比实际更强大。

    这个假设尤其重要。许多机器学*模型非常重视这一假设。这些模型无法传达可能不存在关系的情况。

  • 机器学*模型通常被认为是半自动化的,这意味着仍然需要人类做出智能决策。

  • 机器非常聪明,但很难将事物置于上下文中。大多数模型的输出是一系列数字和指标,试图量化模型的表现如何。由人类来将这些指标放入上下文,并将结果传达给听众。

  • 大多数机器学*模型对噪声数据非常敏感。这意味着当你包含不合理的数据时,模型会感到困惑。例如,如果你试图找到全球经济数据之间的关系,而你的一列数据与首都城市的幼犬收养率相关,这个信息可能不相关,并且会让模型感到困扰。

这些假设在处理机器学*时会一再出现。它们非常重要,但往往被初学者数据科学家忽视。

机器学*是如何工作的?

每种类型的机器学*和每个单独的模型工作方式都非常不同,利用了不同的数学和数据科学部分。然而,一般来说,机器学*通过接收数据、寻找数据中的关系,并输出模型所学到的内容来工作,如图 10.2所示。

图 10.2 – 一个概述,展示了机器学*模型如何接收输入数据、学*信号,并识别模式,以便生成有意义且可解释的输出

图 10.2 – 一个概述,展示了机器学*模型如何接收输入数据、学*信号,并识别模式,以便生成有意义且可解释的输出

当我们探索不同类型的机器学*模型时,我们将看到它们如何以不同的方式操作数据,并为不同的应用得出不同的输出。

机器学*的类型

有许多方法可以划分 ML 并深入研究。在第一章数据科学术语中,我提到了统计模型和概率模型。这些模型利用我们在前几章中学到的统计学和概率学,旨在找出数据之间的关系并进行预测。在本章中,我们将实现这两类模型。在下一章中,我们将看到统计/概率学这个严格数学世界之外的 ML。你可以通过不同的特征划分 ML 模型,包括以下内容:

  • 它们使用的有机结构数据类型(树、图或神经 网络NN))

  • 与其最相关的数学领域(统计学或概率论)

  • 训练所需的计算量(深度 学*DL))

从 ML 的顶层分支出以下三个子集:

  • 监督 学*SL

  • 无监督 学*UL

  • 强化 学*RL

让我们逐一分析这些内容。我们的下一章将包含前两者的多个例子,第三个略超出我们这本入门书的范围。你可以在我们的代码库中找到更多的资源!

SL

简单来说,SL通过找到数据集的特征(自变量)与目标(因变量)之间的关联。例如,SL 模型可能会试图找出一个人的健康特征(心率、体重等)与该人心脏病发作风险(目标变量)之间的关联。这些关联使得监督学*模型能够基于过去的示例进行预测。监督学*SML)模型通常被称为预测分析模型,因其能够基于过去预测未来。这通常是人们一听到ML这个术语时首先想到的内容,但它并不能涵盖 ML 的全部领域。

SML 需要一种特殊类型的数据,称为标注数据——数据作为特征和目标变量的完整、正确和完整的示例。图 10.1展示了标注数据的一部分。目标是通过向模型提供带有正确答案的历史示例,让模型学*。

回想一下面部识别的例子。这是一个 SL 模型,因为我们正在用之前标注为“人脸”或“非人脸”的图片训练我们的模型,然后让模型预测新图片是否包含人脸。

首先,让我们将数据分成以下两部分:

  • 特征,即用于进行预测的列。这些有时被称为预测因子、输入值、变量和自变量。

  • 响应变量,即我们希望预测的列。这有时也被称为结果、标签、目标和因变量。

SL 尝试在特征和响应之间找到一种关系,以便进行预测。其思路是,在未来,某个数据观测值将出现,而我们只知道预测变量。模型将不得不使用这些特征来准确预测响应值。图 10**.3 显示了我们通常如何使用有监督模型的可视化:我们通过使用带标签的训练数据进行训练(拟合),然后利用结果对未*过的案例(只有特征没有响应)进行预测,最终做出预测:

图 10.3 – 有监督模型通过使用带标签的训练数据进行拟合,然后用来对未*过的案例进行预测

图 10.3 – 有监督模型通过使用带标签的训练数据进行拟合,然后用来对未*过的案例进行预测。

示例 – 心脏病预测

假设我们希望预测某人在一年内是否会发生心脏病发作。为了进行这个预测,我们会得到该人的胆固醇水*、血压、身高、吸烟*惯等信息。根据这些数据,我们需要评估心脏病发作的可能性。假设为了做出这个预测,我们查看了以前的患者及其病史。由于这些是以前的患者,我们不仅知道他们的预测变量(如胆固醇、血压等),还知道他们是否确实发生过心脏病发作(因为这已经发生了!)。

这是一个 SML 问题,因为我们正在做以下事情:

  • 我们正在对某个人进行预测。

  • 我们正在使用历史训练数据来寻找医学变量与心脏病发作之间的关系。

图 10**.4 显示了 SML 模型如何使用数据的基本框架:

图 10.4 – SML 模型通过使用预测变量和响应变量的数据来学*它们之间的关系,通常是为了在给定预测变量而没有响应变量的情况下进行未来预测

图 10.4 – SML 模型通过使用预测变量和响应变量的数据来学*它们之间的关系,通常是为了在给定预测变量而没有响应变量的情况下进行未来预测。

这里的希望是,未来某个时刻会有一个患者走进来,我们的模型能够根据其状况判断该患者是否有心脏病发作的风险(就像医生一样!)。

随着模型看到越来越多的多样化和具有代表性的数据,模型应该进行自我调整,以匹配训练数据中定义的正确标签。然后,我们可以使用不同的度量标准(将在下一章中进一步解释)来准确评估我们的 SML 模型的表现,以及如何更好地调整它。与 SML 相关的最大障碍之一是获得多样化和具有代表性的数据集,这往往非常难以获取。假设我们想要预测心脏病发作,我们可能需要成千上万的患者数据以及每个患者的所有医疗信息和数年的跟踪记录,这可能是一个获取噩梦。简而言之,监督模型使用历史带标签的数据,通过预定义的特征来对未来进行预测。一些 SL 的可能应用包括以下几种:

  • 股票价格预测:历史交易量和股价波动可以作为特征,同时社交媒体情绪也可以作为特征,未来价格则为目标变量

  • 天气预测:使用过去的气象数据,如温度、湿度和风速,来预测未来的天气状况

  • 疾病诊断:通过医学影像和患者历史记录来预测疾病的有无

  • 人脸识别:从人脸图像中提取的特征用于识别个人

  • 邮件过滤:通过邮件的特征将其分类为垃圾邮件或非垃圾邮件

  • 信用评分:通过历史金融行为数据来预测信用 worthiness

这些应用程序依赖于一个带标签的数据集,其中包含历史数据点和模型试图预测的目标变量。数据标签的质量和数量在监督学*(SL)中至关重要,因为它们直接影响模型的学*能力以及模型对新数据的泛化能力。在设计 SL 模型时,考虑所使用的特征非常重要。特征应当是相关的、有信息量的,并且不可冗余,以确保模型的有效性。此外,算法的选择取决于任务的性质(回归、分类)、数据集的大小和维度以及所需的计算效率。

通过精心准备数据集并选择合适的特征和模型,SL 可以为各种领域提供强大的预测洞察,从金融到医疗保健。

SL 模型类型

通常,SL 模型分为两种类型:回归模型和分类模型。二者的区别非常简单,关键在于响应变量的性质。

回归模型试图预测一个连续的响应。这意味着响应可以取无限范围的值。考虑以下示例:

  • 房价预测:预测的值是基于房屋的特征(如建筑面积、卧室数量和地理位置)来预测房子的价格

  • 温度预测:模型根据历史天气数据预测未来几天或几小时的温度。

  • 股市价格预测:模型根据股票的历史表现和其他经济指标预测股票的未来价格,预测结果是连续性的。

分类模型则预测离散的响应。这些响应是离散的,且有有限个值,通常被称为类或类别。以下是一些示例:

  • 邮件垃圾邮件检测:模型根据邮件内容、发件人信息和其他属性将邮件分类为“垃圾邮件”或“非垃圾邮件”。

  • 医学诊断:模型可能根据测试结果对患者的结局进行分类,预测如“患病”或“未患病”等类别。

  • 图像识别:根据像素数据和模式将图像分类为预定义的类别,如“猫”,“狗”,“汽车”等。

回归和分类任务都使用类似的从历史数据中学*的过程,但由于它们的输出性质不同,应用和评估指标也有所不同。我们之前的心脏病发作示例属于分类,因为问题是:“此人在一年内是否会发生心脏病发作?”这个问题只有两个可能的答案:。我们将在本章后面看到回归的完整示例,并在下一章中看到分类和回归的完整示例。

在分类和回归之间的选择

有时候,决定是否使用分类或回归算法可能会很棘手。假设我们关注的是外面的天气。我们可以问:“外面有多热?” 在这种情况下,答案是一个连续的数值,可能的答案有 60.7 度或 98 度。然而,作为练*,去问 10 个人外面温度是多少。我敢保证,其中一个人(如果不是大多数人)不会给出一个具体的度数,而是会将答案归类,比如说 “大约是 60 多度”

我们可能会将这个问题视为分类问题,其中响应变量不再是精确的温度,而是以桶的形式表示。理论上,桶的数量是有限的,这样可能会让模型更好地区分 60 度和 70 度之间的差异。

如果我们不进行预测,而是希望利用机器学*更好地理解和解释我们的数据,无监督学*(UL)就非常有用。

无监督学*

我们列表中的第二种机器学*方法并不是进行预测,而是具有更为开放的目标。无监督学*(UL)接收一组预测变量,并利用这些变量之间的关系来完成以下任务:

  • 通过将变量压缩在一起,它减少了数据的维度。一个例子就是文件压缩。压缩通过利用数据中的模式,将数据表示为更小的格式。这被称为维度约简

  • 它会找到行为相似的观察值并将它们聚集在一起,这就是聚类

这两者都是无监督学*(UL)的例子,因为它们不试图寻找预测变量与特定响应之间的关系,因此不会用于任何形式的预测。无监督模型则用于寻找数据中先前未知的组织结构和表示。

图 10**.5 展示了聚类分析的一个表示:

图 10.5 – 聚类分析将相似的数据点聚集在一起,为原始数据添加一层解释

图 10.5 – 聚类分析将相似的数据点聚集在一起,为原始数据添加一层解释

模型会识别出每个颜色不同的观察簇与另一个簇相似,但与其他簇不同。

无监督学*的一个大优势是它不需要标注数据,这意味着获取符合无监督学*模型的数据要容易得多。当然,这也有一个缺点,那就是我们失去了所有的预测能力,因为响应变量包含做出预测所需的信息,而没有它,我们的模型在做出任何预测时都将变得毫无希望。

一个大缺点是很难评估我们做得如何。在回归或分类问题中,我们可以通过将模型的预测结果与实际结果进行比较,轻松判断模型的预测效果。例如,如果我们的监督模型预测下雨,但外面却是晴天,那么预测就是错误的。如果我们的监督模型预测价格会上涨 1 美元,但实际上只上涨了 99 美分,那么预测就非常接近!但在无监督建模中,这一概念是陌生的,因为我们没有答案可以与模型进行比较。无监督模型仅仅是提出差异和相似性,这些差异和相似性需要人类来解释,就像在图 10**.6中所展示的那样:

图 10.6 – 无监督模型没有“目标”的概念,而是专注于在原始数据上添加一层结构

图 10.6 – 无监督模型没有“目标”的概念,而是专注于在原始数据上添加一层结构

简而言之,无监督模型的主要目标是找到数据观察值之间的相似性和差异,并利用这些比较为原始且未结构化的数据添加结构。

我们最后要讨论的机器学*类型走向了一个非常不同的方向。老实说,我们没有足够的时间和篇幅来充分讲解接下来的话题,但无论如何,它仍然值得在本书中占有一席之地。

强化学*(RL)

强化学*(RL)中,算法被称为代理,代理通过与环境互动学*做出决策。代理根据当前状态选择一个动作,然后根据该动作的结果接收奖励或惩罚。目标是学*一个策略——即从状态到动作的映射——以最大化累积奖励。

这种类型的机器学*与 SL 有显著区别,因为它不依赖标注的输入/输出对,也不需要明确地纠正次优的行动。相反,它侧重于在探索(尝试新行动)和利用(使用已知信息最大化奖励)之间找到*衡。

强化学*已成功应用于多个领域,包括以下内容:

  • 游戏玩法:AI 代理被训练来玩并在复杂游戏中表现出色,如围棋、国际象棋和各种视频游戏,通常超越人类专家的表现。

  • 机器人技术:机器人通过试错学*执行任务,如行走、拾取物体或在挑战性地形中导航。

  • 自动驾驶车辆:强化学*用于开发可以在动态和不可预测的环境中做出实时驾驶决策的系统。

OpenAI 在使用强化学*与人类反馈RLHF)方面的开创性工作为开发像 ChatGPT 这样的 AI 模型发挥了重要作用。通过结合人类偏好,这些模型被训练生成不仅相关,而且符合人类价值观的回应,从而增强其有用性并减少潜在的伤害。

一个典型的强化学*(RL)问题流程大致如下:

  1. 代理从环境中接收状态S

  2. 代理根据策略π采取行动A

  3. 环境向代理呈现一个新的状态S和奖励R

  4. 奖励通知代理该行动的有效性。

  5. 代理通过更新策略π来增加未来的奖励。

机器学*类型概述

在三种机器学*类型——SL、UL 和 RL 中,我们可以想象机器学*的世界就像图 10.7中的描绘:

图 10.7 – 我们的机器学*家谱有三个主要分支:SL、UL 和 RL

图 10.7 – 我们的机器学*家谱有三个主要分支:SL、UL 和 RL

机器学*范式——优缺点

如我们所知,机器学*可以大致分为三类,每类都有其各自的优缺点。

SML

这种方法利用输入预测因子与输出响应变量之间的关系来预测未来的数据观测。

它的优点如下:

  • 为未来事件提供预测分析。

  • 量化变量之间的关系和影响

  • 提供对变量之间如何相互作用和影响彼此的洞察。

让我们来看看其缺点:

  • 依赖标注数据的可用性,而标注数据通常稀缺且昂贵。

无监督机器学*(UML)

这种方法通过寻找数据点之间的相似性和差异来发现模式,而无需使用标注的响应。

其优点如下:

  • 识别出人类分析师可能难以察觉的微妙相关性。

  • 作为有监督学*(SL)的宝贵预处理步骤,将原始数据转化为结构化的簇。

  • 利用未标记的数据,这类数据通常更为丰富且易于获取。

以下是缺点:

  • 缺乏直接的预测能力

  • 验证模型效果是具有挑战性的,并且高度依赖于人工判断。

强化学*(RL)

RL 使用奖励系统训练智能体在其环境中采取最佳行动。

其优点如下:

  • 通过复杂的奖励系统开发复杂的人工智能行为。

  • 可适应广泛的环境,包括现实世界的场景。

其缺点如下:

  • 初始行为可能不可预测,因为智能体需要从其错误中学*。

  • 学*可能较慢,因为智能体可能需要时间从有益和有害的行为中辨别出有价值的行动。

  • 存在智能体变得过于谨慎的风险,可能限制其行动以避免负面结果。

够多的讨论了——让我们来看一下我们的第一个机器学*代码!

使用线性回归预测连续变量

最后,我们将探索第一个真正的机器学*模型!线性回归是一种回归方法,意味着它是一种机器学*模型,试图找到预测变量和响应变量之间的关系,而这个响应变量——你猜对了——是连续的!这个概念与做出最佳拟合线是同义的。虽然线性回归不再是最先进的机器学*算法,但它背后的路径可能有些复杂,它将作为我们进入这一领域的绝佳起点。

在线性回归的情况下,我们将尝试找到预测变量和响应变量之间的线性关系。形式上,我们希望解决以下格式的公式:

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block">mml:miy</mml:mi>mml:mo=</mml:mo>mml:msubmml:mrow<mml:mi mathvariant="normal">β</mml:mi></mml:mrow>mml:mrowmml:mn0</mml:mn></mml:mrow></mml:msub>mml:mo+</mml:mo>mml:msubmml:mrow<mml:mi mathvariant="normal">β</mml:mi></mml:mrow>mml:mrowmml:mn1</mml:mn></mml:mrow></mml:msub>mml:msubmml:mrowmml:mix</mml:mi></mml:mrow>mml:mrowmml:mn1</mml:mn></mml:mrow></mml:msub>mml:mo+</mml:mo>mml:mo…</mml:mo>mml:mo+</mml:mo>mml:msubmml:mrow<mml:mi mathvariant="normal">β</mml:mi></mml:mrow>mml:mrowmml:min</mml:mi></mml:mrow></mml:msub>mml:msubmml:mrowmml:mix</mml:mi></mml:mrow>mml:mrowmml:min</mml:mi></mml:mrow></mml:msub></mml:math>

让我们来看一下这个公式的组成部分:

  • y 是我们的响应变量

  • xi 是我们的第 i 个变量(第 i 列或第 i 个预测变量)

  • B0 是截距项。

  • Bi 是 xi 项的系数

在深入研究之前,让我们先看一下数据。这份数据集是公开的,旨在预测某一天共享单车项目中所需的单车数量:

# read the data and set the datetime as the index
# taken from Kaggle: https://www.kaggle.com/c/bike-sharing-demand/data
import pandas as pd
import matplotlib.pyplot as plt
url ='https://raw.githubusercontent.com/justmarkham/DAT8/master/data/bikeshare.csv'
bikes = pd.read_csv(url) bikes.head()

我们的数据可以在 图 10**.8 中看到:

图 10.8 – 我们共享单车数据的前五行(头部数据)

图 10.8 – 我们共享单车数据的前五行(头部数据)

我们可以看到,每一行代表一个小时的单车使用情况。在这种情况下,我们关注的是预测count值,表示该小时内租出的单车总数。

让我们使用 seaborn 模块仅使用 temp 特征来绘制一条最佳拟合线,如下所示:

import seaborn as sns #using seaborn to get a line of best fit
sns.lmplot(x='temp', y='count', data=bikes, aspect=1.5, scatter_kws={'alpha':0.2})

这段代码的输出可以在 图 10**.9 中看到:

图 10.9 – 我们的第一条最佳拟合线,展示了温度与共享单车数量之间的关系

图 10.9 – 我们的第一条最佳拟合线,展示了温度与共享单车数量之间的关系

图中的这条线试图可视化并量化 tempcount 之间的关系。为了进行预测,我们只需找出一个给定的温度,然后看看这条线会预测出多少单车数量。例如,如果温度为 20°C(记得是摄氏度哦),那么我们的拟合线将预测大约会租出 200 辆单车。如果温度超过 40°C,那么将需要超过 400 辆单车!

看起来,随着 temp 的上升,count 值也在上升。让我们看看我们的相关性值,它量化了变量之间的线性关系,是否也符合这一点:

bikes[['count', 'temp']].corr() # 0.3944

这两个变量之间存在一个(弱)正相关,考虑到我们的最佳拟合线,这是有意义的!现在,让我们使用 pandas 创建一个特征变量 (X) 和一个目标变量 (y):

# create X and y
feature_cols = ['temp'] # a list of the predictors
X = bikes[feature_cols] # subsetting our data to only the predictors
y = bikes['count'] # our response variable

我们的 Xy 变量分别代表预测变量和响应变量。然后,我们将导入我们的机器学*模块 scikit-learn,如下面所示:

# import scikit-learn, our machine learning module from sklearn.linear_model import LinearRegression

最后,我们将拟合模型到预测变量和响应变量,如下所示:

linreg = LinearRegression() #instantiate a new model linreg.fit(X, y) #fit the model to our data
# print the coefficients print(linreg.intercept_) print(linreg.coef_) 6.04621295962 # our Beta_0
[ 9.17054048] # our beta parameters

让我们尝试解释一下:

  • B0 (6.04) 是当 X = 0y 的值

  • 这是预测在温度为 0°C 时租出单车的数量

  • 所以,在 0°C 时,预计将有六辆单车被使用(天气很冷!)

有时,解释截距可能没有意义,因为在某些情况下可能没有零的概念。回想一下数据的层次。并非所有层次都具有零的概念。我们的目标变量确实有“没有单车”的固有概念;因此,我们是安全的。

相关性与因果关系

在线性回归的上下文中,系数表示预测变量与响应变量之间关系的强度和方向。然而,这种统计关系不应与因果关系混淆。

系数 B1,在我们之前的代码片段中值为 9.17,表示自变量(温度,以°C 为单位)每增加 1 单位时,因变量(租赁的自行车数量)的*均变化量。具体来说,这意味着以下内容:

  • 每上升 1°C,租赁的自行车数量*均增加约 9 辆

  • 该系数的正号表明了直接关系:随着温度的升高,自行车租赁数量也随之增加

然而,尽管B1表明了显著的相关性,我们仍然需要保持谨慎。这只是一个相关性,意味着它仅仅表明两个变量是共同变化的——它并不意味着其中一个变量导致另一个变量的变化。若系数为负数,则表明两者之间存在反向关系:温度上升时,租赁的自行车数量下降。但同样,这也并不能确认温度变化会导致自行车租赁数量的变化。

因果关系

要证明因果关系,我们需要进行控制实验设计或使用额外的统计技术,考虑混杂变量并建立因果联系。没有这样的证据,我们从回归分析中得出的结果应当作为相关性洞察呈现,这些洞察展示了可能需要进一步调查的模式,但并不确认因果关系。

因此,尽管我们的温度系数 B1 表明温暖天气与增加的自行车租赁数量之间存在相关性,但在没有更深入的因果分析之前,我们不能得出温暖天气导致更多人租赁自行车的结论。

现在我们已经对相关性分析结果的解释充满信心,让我们使用scikit-learn来进行一些预测:

linreg.predict(20) # a temperatureof 20 degrees would lead our model to predict 189.46 bikes to be in use

这意味着,如果温度为 20°C,那么大约会租出 189 辆自行车。

添加更多的预测变量

当然,温度并不是唯一能够帮助我们预测自行车数量的因素。向模型中添加更多预测变量就像是告诉scikit-learn线性回归模型它们的存在一样简单!

在我们进行下一步之前,应该查看数据字典,以便更好地理解一些特征:

  • season: 1 = 春季2 = 夏季3 = 秋季4 = 冬季

  • holiday: 是否是节假日

  • workingday: 是否是周末或节假日

  • weather: 1 = 晴天,少云,局部多云2 = 有雾+多云,有雾+破碎云层,有雾+少云,有雾3 = 小雪,轻微雨+雷暴+散云,轻微雨+散云4 = 大雨+冰雹+雷暴+雾,雪+雾

  • temp: 温度(单位:°C)

  • atemp: “体感温度”,考虑了风速的影响

  • humidity: 相对湿度

现在,让我们使用更多特征创建线性回归模型。如之前一样,我们首先创建一个包含希望查看的特征的列表,创建我们的特征和响应数据集(Xy),然后进行线性回归拟合。一旦我们拟合了回归模型,我们将查看模型的系数,以了解我们的特征是如何与响应变量互动的:

# create a list of features
feature_cols = ['temp', 'season', 'weather', 'humidity'] # create X and y
X = bikes[feature_cols] y = bikes['count']
# instantiate and fit linreg = LinearRegression() linreg.fit(X, y)
# pair the feature names with the coefficients result = zip(feature_cols, linreg.coef_) resultSet = set(result)
print(resultSet)
his gives us the following output:
[('temp', 7.8648249924774403),
('season', 22.538757532466754),
('weather', 6.6703020359238048),
('humidity', -3.1188733823964974)]

这意味着:

  • 在保持其他预测因子不变的情况下,温度增加 1 个单位与租赁量增加7.86辆自行车相关。

  • 在保持其他预测因子不变的情况下,季节增加 1 个单位与租赁量增加22.5辆自行车相关。

  • 在保持其他预测因子不变的情况下,天气增加 1 个单位与租赁量增加6.67辆自行车相关。

  • 在保持其他预测因子不变的情况下,湿度增加 1 个单位与租赁量减少3.12辆自行车相关。

这很有趣。

重要说明

请注意,当天气升高时(意味着天气逐渐接近阴天),自行车需求增加,与季节变量增加(意味着我们接近冬季)时一样。这完全不是我预期的结果,老实说!

虽然这些单独的相关性在很多方面都很有用,但至关重要的是要识别衡量整个机器学*系统的指标。通常,我们会根据正在执行的任务来考虑指标。某些指标对于分类任务很有用,而其他指标则更适用于回归任务。

回归指标

使用回归机器学*模型时,通常有三个主要的指标。它们如下:

  • *均绝对误差 (MAE):这是预测值与实际值之间绝对误差的*均值。它是通过将误差(预测值与实际值之间的差异)绝对值求和,再除以观察次数 n 来计算的:

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block">mml:mtextMAE</mml:mtext>mml:mo=</mml:mo>mml:mfracmml:mrowmml:mn1</mml:mn></mml:mrow>mml:mrowmml:min</mml:mi></mml:mrow></mml:mfrac>mml:mrowmml:munderover<mml:mo stretchy="false">∑</mml:mo>mml:mrowmml:mii</mml:mi>mml:mo=</mml:mo>mml:mn1</mml:mn></mml:mrow>mml:mrowmml:min</mml:mi></mml:mrow></mml:munderover>mml:mrow<mml:mfenced open="|" close="|" separators="|">mml:mrowmml:msubmml:mrowmml:miy</mml:mi></mml:mrow>mml:mrowmml:mii</mml:mi></mml:mrow></mml:msub>mml:mo-</mml:mo><mml:mover accent="true">mml:mrowmml:msubmml:mrowmml:miy</mml:mi></mml:mrow>mml:mrowmml:mii</mml:mi></mml:mrow></mml:msub></mml:mrow>mml:mo^</mml:mo></mml:mover></mml:mrow></mml:mfenced></mml:mrow></mml:mrow></mml:math>

  • 均方误差 (MSE):这是预测值与实际值之间误差的*方的*均值。计算方法是将每个误差*方后求和,再除以观察值的数量:

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block">mml:mtextMSE</mml:mtext>mml:mo=</mml:mo>mml:mfracmml:mrowmml:mn1</mml:mn></mml:mrow>mml:mrowmml:min</mml:mi></mml:mrow></mml:mfrac>mml:mrowmml:munderover<mml:mo stretchy="false">∑</mml:mo>mml:mrowmml:mii</mml:mi>mml:mo=</mml:mo>mml:mn1</mml:mn></mml:mrow>mml:mrowmml:min</mml:mi></mml:mrow></mml:munderover>mml:mrowmml:msupmml:mrow<mml:mfenced separators="|">mml:mrowmml:msubmml:mrowmml:miy</mml:mi></mml:mrow>mml:mrowmml:mii</mml:mi></mml:mrow></mml:msub>mml:mo-</mml:mo><mml:mover accent="true">mml:mrowmml:msubmml:mrowmml:miy</mml:mi></mml:mrow>mml:mrowmml:mii</mml:mi></mml:mrow></mml:msub></mml:mrow>mml:mo^</mml:mo></mml:mover></mml:mrow></mml:mfenced></mml:mrow>mml:mrowmml:mn2</mml:mn></mml:mrow></mml:msup></mml:mrow></mml:mrow></mml:math>

  • 均方根误差 (RMSE):这是 MSE 的*方根。通过对预测值与实际值之间的*方差的*均值开*方得到。RMSE 有用之处在于它将误差缩放到输出变量的原始单位,并且比 MSE 更易于解释:

RMSE=1n∑i=1nyi−yiˆ2

这些指标在评估回归模型性能时至关重要,每个指标都有其优势。MAE 提供了一个简单的*均误差量,MSE 对较大的误差进行了更严重的惩罚,而 RMSE 由于误差的*方而对大误差特别敏感。

让我们来看看 Python 中的实现:

# example true and predicted response values true = [9, 6, 7, 6]
pred = [8, 7, 7, 12]
# note that each value in the last represents a single prediction for a model
# So we are comparing four predictions to four actual answers
# calculate these metrics by hand! from sklearn import metrics
import numpy as np
print('MAE:', metrics.mean_absolute_error(true, pred)) print('MSE:', metrics.mean_squared_error(true, pred)) print('RMSE:', np.sqrt(metrics.mean_squared_error(true, pred)))

在这里,输出将如下所示:

MAE: 2.0
MSE: 9.5
RMSE: 3.08220700148

让我们使用 RMSE 来确定哪些列有助于哪些列有阻碍。让我们从只使用温度开始。请注意,我们的程序将按以下步骤进行:

  1. 创建我们的X和我们的y变量。

  2. 拟合线性回归模型。

  3. 使用模型基于X进行预测列表。

  4. 计算预测值和实际值之间的 RMSE。

让我们来看看代码:

from sklearn import metrics
# import metrics from scikit-learn
feature_cols = ['temp'] # create X and y
X = bikes[feature_cols] linreg = LinearRegression() linreg.fit(X, y)
y_pred = linreg.predict(X) np.sqrt(metrics.mean_squared_error(y, y_pred)) # RMSE # Can be interpreted loosely as an average error #166.45

现在,让我们尝试使用温度和湿度,如图所示:

feature_cols = ['temp', 'humidity'] # create X and y
X = bikes[feature_cols] linreg = LinearRegression() linreg.fit(X, y)
y_pred = linreg.predict(X) np.sqrt(metrics.mean_squared_error(y, y_pred)) # RMSE # 157.79

它变得更好了!让我们尝试使用更多的预测变量,如图所示:

feature_cols = ['temp', 'humidity', 'season', 'holiday', 'workingday', 'windspeed', 'atemp']
# create X and y
X = bikes[feature_cols] linreg = LinearRegression() linreg.fit(X, y)
y_pred = linreg.predict(X) np.sqrt(metrics.mean_squared_error(y, y_pred)) # RMSE # 155.75

更好!起初,这似乎是一个重大的胜利,但实际上这里存在着隐藏的危险。请注意,我们正在训练线来拟合Xy,然后要求它再次预测X!这实际上是机器学*中的一个巨大错误,因为它可能导致过度拟合,这意味着我们的模型只是记住数据并将其吐回给我们。

想象一下你是一名学生,走进第一天的课堂,老师说这门课的期末考试非常困难。为了帮助你准备,她给了你一次又一次的练*测试。期末考试的那天到来了,你震惊地发现考试中的每一道题都与练*测试中的完全相同!幸运的是,你做了那么多次,记住了答案并在考试中得到了满分。

这里也大体上是一样的。通过在相同数据上拟合和预测,模型正在记忆数据并在其上变得更好。解决这个过拟合问题的一个很好的方法是使用训练/测试方法来拟合机器学*模型,下面将详细介绍。

基本上,我们将采取以下步骤:

  1. 将数据集分成两部分:训练集和测试集。

  2. 在训练集上拟合我们的模型,然后在测试集上进行测试,就像在学校里,老师会从一组笔记教学,然后用不同(但相似)的问题来测试我们。

  3. 一旦我们的模型足够好(基于我们的指标),我们将模型的注意力转向整个数据集。

  4. 我们的模型等待先前任何人都未*的新数据。

    这可以在图 10**.10中可视化:

图 10.10 – 将数据拆分成训练集和测试集有助于正确评估我们模型预测未*数据的能力

图 10.10 – 将数据拆分成训练集和测试集有助于正确评估我们模型预测未*数据的能力

这里的目标是最小化我们模型在未*过的数据上的错误,也就是模型对新数据的预测错误。这很重要,因为监督模型的主要目标(通常来说)是预测新数据的结果。如果我们的模型不能从训练数据中泛化并用于预测未*过的案例,那么我们的模型就不够好。

上述图表概述了一种简单的方法,确保我们的模型能够有效地摄取训练数据,并利用这些数据预测模型从未*过的数据点。当然,作为数据科学家,我们知道测试集也附带答案,但模型并不知道这一点。

这一切可能听起来有点复杂,但幸运的是,scikit-learn包提供了一个内置方法来完成这一过程,如下所示:

from sklearn.cross_validation import train_test_split
# function that splits data into training and testing sets
# setting our overall data X, and y
feature_cols = ['temp']
X = bikes[feature_cols]
y = bikes['count']
# Note that in this example, we are attempting to find an association between only the temperature of the day and the number of bike rentals.
X_train, X_test, y_train, y_test = train_test_split(X, y) # split the data into training and testing sets
# X_train and y_train will be used to train the model # X_test and y_test will be used to test the model
# Remember that all four of these variables are just subsets of the overall X and y.
linreg = LinearRegression() # instantiate the model
linreg.fit(X_train, y_train)
# fit the model to our training set
y_pred = linreg.predict(X_test) # predict our testing set
np.sqrt(metrics.mean_squared_error(y_test, y_pred)) # RMSE # Calculate our metric:
# == 166.91

换句话说,我们的train_test_split函数确保我们所看的指标是对我们样本表现的更真实的估计。

现在,让我们使用更多的预测变量,再做一次尝试,如下所示:

feature_cols = ['temp', 'workingday']
X = bikes[feature_cols]
y = bikes['count']
X_train, X_test, y_train, y_test = train_test_split(X, y) # Pick a new random training and test set
linreg = LinearRegression() linreg.fit(X_train, y_train) y_pred = linreg.predict(X_test) # fit and predict
np.sqrt(metrics.mean_squared_error(y_test, y_pred)) # 166.95

加入这个特征后,我们的模型实际上变得更差了!这意味着workingday(工作日)可能对我们的预测变量——自行车租赁数量,并没有很强的预测能力。

这一切都很好,我们可以继续添加和删除特征来降低我们的 RMSE,但我们的模型在预测方面到底有多好,而不仅仅是猜测呢?我们有一个大约 167 辆自行车的 RMSE,这好么?我们该拿什么来对比呢?发现这一点的一个方法是评估空模型。

空模型在监督机器学*中代表了反复猜测预期结果,并查看你做得如何。例如,在回归分析中,如果我们总是猜测每小时自行车租赁的*均值,那么这个模型会有多好?

  1. 首先,让我们计算*均每小时自行车租赁量,如下所示:

    average_bike_rental = bikes['count'].mean() average_bike_rental
    
    # 191.57
    

    这意味着,在这个数据集中,无论天气、时间、星期几、湿度或其他因素如何,每小时*均外借的自行车数量大约是 192 辆。

  2. 让我们做一个假设的预测列表,其中每一个猜测值都是 191.57。我们为每一个小时做这个猜测,如下所示:

    num_rows = bikes.shape[0] num_rows
    
    # 10886
    
    null_model_predictions = [average_bike_rental] * num_rows null_model_predictions
    

    输出如下:

    [191.57413191254824,
    
    191.57413191254824,
    
    191.57413191254824,
    
    191.57413191254824,
    
    ...
    
    191.57413191254824,
    
    191.57413191254824,
    
    191.57413191254824,
    
    191.57413191254824]
    

    因此,我们有10,886个值,所有值都是*均每小时的自行车租赁数量。让我们看看,如果我们的模型只猜测每小时的*均租赁数量,RMSE 会是多少:

    np.sqrt(metrics.mean_squared_error(y, null_model_predictions))
    

    输出如下:

    181.13613
    

这意味着仅仅通过反复猜测*均值,我们的 RMSE 会是 181 辆自行车。因此,即使只有一两个特征,我们也能超过它!在机器学*中,打败空模型是一种基准。如果你想想看,如果你的机器学*模型甚至不如仅仅猜测的结果,为什么要费力去做呢?

总结

在这一章中,我们了解了机器学*及其不同的子类别。我们探索了监督学*(SL)、无监督学*(UL)和强化学*(RL)策略,并分析了每种策略在不同情况下的应用。

在研究线性回归时,我们能够找到预测变量与连续响应变量之间的关系。通过训练/测试集划分,我们能够帮助避免机器学*模型的过拟合,并获得更具泛化性的预测。我们还能够使用诸如 RMSE 等指标来评估我们的模型。

在接下来的几章中,我们将深入探讨更多的机器学*模型,同时,我们将学*新的指标、新的验证技术,以及——更重要的是——将数据科学应用于世界的新方式。

第十一章:预测不是从树上长出来的,还是它们真能长出来?

本章的目标是看到并应用从前几章学到的概念,构建和使用现代学*算法,从真实数据集中获取洞察力并做出预测。尽管我们在探索以下算法时,始终要记住,我们时刻关注着我们的评估指标。

在本章中,我们将研究以下机器学*算法:

  • 执行朴素贝叶斯分类

  • 理解决策树

  • 深入研究无监督学*UL

  • k-均值聚类

  • 特征提取和主成分分析PCA

执行朴素贝叶斯分类

让我们直接开始吧!我们从朴素贝叶斯分类开始。这个机器学*模型在很大程度上依赖于前几章的结果,特别是贝叶斯定理:

PH|D=PD|H⋅PHPD

让我们更仔细地看一下这个公式的具体特征:

  • P(H)是我们在观察数据之前,假设的概率,称为先验概率,或简称先验

  • P(H|D)是我们想要计算的内容:在观察到数据后,假设的概率,称为后验概率

  • P(D|H)是给定假设下数据的概率,称为似然函数

  • P(D)是任何假设下数据的概率,称为归一化常数

朴素贝叶斯分类是一种分类模型,因此是监督学*模型。基于这一点,我们需要什么类型的数据——有标签数据还是无标签数据?

(在此插入Jeopardy音乐)

如果你回答的是有标签数据,那么你已经在成为数据科学家的路上了!

假设我们有一个包含n 个特征(x1, x2, …, xn),以及一个类别标签C的数据集。例如,假设我们有一些涉及垃圾邮件文本分类的数据。我们的数据由每行独立的文本样本和每列的特征以及类别标签组成。我们的特征是文本样本中包含的单词和短语,而类别标签则仅是垃圾邮件非垃圾邮件。在这个场景中,我将把非垃圾邮件类别替换为更易于说出的词汇“火腿”。让我们看一下下面的代码片段,帮助我们更好地理解垃圾邮件和火腿数据:

import pandas as pd import sklearn
df = pd.read_table('..data/sms.tsv',
sep='\t', header=None, names=['label', 'msg'])
df

图 11**.1是以行列格式表示的文本数据示例:

图 11.1 – 我们的垃圾邮件与非垃圾邮件(火腿)消息样本

图 11.1 – 我们的垃圾邮件与非垃圾邮件(火腿)消息样本

让我们做一些初步统计,看看我们正在处理的数据。让我们看看正常邮件和垃圾邮件的数量差异:

df.label.value_counts().plot(kind="bar")

这给我们一个条形图,如图 11.2所示:

图 11.2 – 正常邮件与垃圾邮件的分布

图 11.2 – 正常邮件与垃圾邮件的分布

由于我们处理的是分类问题,因此列出我们将用来评估模型的部分指标会很有帮助。

分类指标

在评估分类模型时,使用的指标与回归模型不同。这些指标帮助我们理解模型的表现,特别是在正确预测不同类别方面。我们来看看它们是什么:

  1. 准确率:这是最直观的性能度量,它只是正确预测的观测值与总观测值的比率。适用于二分类和多分类问题:

准确率=正确预测的数量Totalnumberofpredictions

  1. 精度(最适合二分类 – 仅有两个类别):也称为正预测值,该指标有助于回答这个问题:“正识别的比例实际上有多少是正确的?”

精度=TPTP+FP

在这里,TP是正确正例的数量(预测为正且预测正确),而FP是假正例的数量(预测为正但预测错误)。

  1. 召回率(敏感性)(最适合二分类 – 仅有两个类别):该指标有助于回答这个问题:“实际的正例中有多少被正确识别?”

召回率=TPTP+FN

在这里,TP(预测为正且预测正确)是正确预测的正例数,而FN是假负例数(预测为负但预测错误)。

  1. F1 得分F1 得分是精度和召回率的加权*均值。因此,这个得分考虑了虚假正例和虚假负例。它是展示分类器在精度和召回率上都有较好表现的一个好方法:

F1=2×Precision×RecallPrecision+Recall

这些度量对于理解分类模型的行为至关重要,尤其是在虚假正例和虚假负例的成本差异很大的领域。

因为我们有远远更多的ham(火腿)信息而不是spam(垃圾邮件)。由于这是一个分类问题,知道我们的ham信息非常有用。下面是我们如何做到这一点:

df.label.value_counts() / df.shape[0]
ham0.865937
spam 0.134063

所以,如果我们盲目猜测是ham(火腿),我们大约有 87%的机会是正确的,但我们可以做得更好。

如果我们有一组类别 C 和特征<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msubmml:mrowmml:mix</mml:mi></mml:mrow>mml:mrowmml:mii</mml:mi></mml:mrow></mml:msub></mml:math>,那么我们可以使用贝叶斯定理来预测一行数据属于类别 C 的概率,使用以下公式:

PclassC|{xi}=P{xi}|classC⋅PclassCP{xi}

让我们更详细地看一下这个公式:

  • P(class C | { <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msubmml:mrowmml:mix</mml:mi></mml:mrow>mml:mrowmml:mii</mml:mi></mml:mrow></mml:msub></mml:math> }): 后验概率是指在给定特征{xi}的情况下,这一行数据属于类别 C 的概率。

  • P({ <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msubmml:mrowmml:mix</mml:mi></mml:mrow>mml:mrowmml:mii</mml:mi></mml:mrow></mml:msub></mml:math> } | class C): 这是在假设这一行属于类C的情况下,观察到这些特征的概率。

  • P(class C):这是先验概率。它是数据点属于类别 C 的概率,在我们看到任何数据之前。

  • P({ <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">mml:msubmml:mrowmml:mix</mml:mi></mml:mrow>mml:mrowmml:mii</mml:mi></mml:mrow></mml:msub></mml:math> }): 这是我们的标准化常数。

例如,假设我们有一封包含三个单词的电子邮件:send cash now。我们将使用朴素贝叶斯分类器将邮件分类为垃圾邮件或正常邮件:

Pspam|sendcashnow=Psendcashnow|spam⋅PspamPsendcashnow

Pham|sendcashnow=Psendcashnow|ham⋅PhamPsendcashnow

我们关注的是这两个数字之间的差异。我们可以使用以下标准来分类任何单个文本样本:

  • 如果 P(spam | send cash now) 大于 P(ham | send cash now),那么我们将把该文本分类为垃圾邮件。

  • 如果 P(ham | send cash now) 大于 P(spam | send cash now),那么我们将把该文本标记为正常邮件。

因为两个方程式的分母中都有 P (send money now),我们可以忽略它们。所以,现在我们关注以下内容:

Psendcashnow|spam⋅PspamVSPsendcashnow|ham⋅Pham

让我们在这个等式中计算一下具体的数字:

  • P(spam) = 0.134063

  • P(ham) = 0.865937

  • P(send cash now | spam) = ???

  • P(send cash now | ham) = ???

最终的两个可能性似乎并不难计算。我们只需要数一数包含“send money”这个短语的垃圾邮件数量,对吧?

现在,把这个短语分开,并除以所有垃圾邮件的总数:

df.msg = df.msg.apply(lambda x:x.lower())
# make all strings lower case so we can search easier
df[df.msg.str.contains('send cash now')].shape # == (0, 2)

哦不!一个都没有!确实没有!没有任何文本包含确切的短语send cash now。隐藏的问题在于,这个短语非常具体,我们不能假设在世界上有足够多的数据能够多次*到这个确切的短语。

相反地,我们可以在贝叶斯定理中做出一个朴素的假设。如果我们假设特征(单词)是条件独立的(即一个单词不会影响另一个单词的存在),那么我们可以重写公式:

Psendcashnow|spam=Psend|spam⋅Pcash|spam⋅Pnow|spam

这里是用 Python 计算的结果:

spams = df[df.label == 'spam']
for word in ['send', 'cash', 'now']:
print( word, spams[spams.msg.str.contains(word)].shape[0] / float(spams.shape[0]))

打印出条件概率结果如下:

  • P(send|spam) = 0.096

  • P(cash|spam) = 0.091

  • P(now|spam) = 0.280

有了这个,我们可以计算以下内容:

Psendcashnow|spam⋅Pspam=0.096⋅0.091⋅0.280⋅0.134=0.00032

对垃圾邮件进行相同的操作会得到以下结果:

  • P(send|ham) = 0.03

  • P(cash|ham) = 0.003

  • P(now|ham) = 0.109

这些数字都非常低这一事实不如垃圾邮件的概率远大于正常邮件的计算结果重要。如果我们进行计算,我们会发现 send cash now 的垃圾邮件概率是正常邮件的 38 倍!通过这种方式,我们可以将 send cash now 分类为垃圾邮件!很简单,对吧?

让我们使用 Python 来实现朴素贝叶斯分类器,而无需自己进行所有这些计算。

首先,让我们回顾一下 scikit-learn 中的计数向量化器,它将文本转换为数值数据。假设我们将对三个文档(句子)进行训练,以下是代码片段:

# simple count vectorizer example
from sklearn.feature_extraction.text import CountVectorizer # start with a simple example
train_simple = ['call you tonight',
'Call me a cab',
'please call me... PLEASE 44!']
# learn the 'vocabulary' of the training data vect = CountVectorizer()
train_simple_dtm = vect.fit_transform(train_simple) pd.DataFrame(train_simple_dtm.toarray(), columns=vect.get_feature_names())

图 11**.3 演示了我们从数据集中学*到的特征向量:

图 11.3 – 在将每个文本分解为唯一单词的计数后,我们的 SMS 数据集的前五行

图 11.3 – 在将每个文本分解为唯一单词的计数后,我们的 SMS 数据集的前五行

请注意,每一行代表三个文档(句子)之一,每一列代表文档中出现的单词之一,每个单元格包含该单词在每个文档中出现的次数。

然后,我们可以使用计数向量化器将新的输入测试文档转换为符合我们的训练集(三个句子):

# transform testing data into a document-term matrix (using existing vocabulary, notice don't is missing)
test_simple = ["please don't call me"] test_simple_dtm = vect.transform(test_simple) test_simple_dtm.toarray()
pd.DataFrame(test_simple_dtm.toarray(), columns=vect.get_feature_names())

图 11**.4 如下所示:

图 11.4 – 以与我们训练数据相同的词汇表示“请不要打电话给我” SMS

图 11.4 – 以与我们训练数据相同的词汇表示“请不要打电话给我” SMS

请注意,在我们的测试句子中,出现了一个新单词——即 don’t。当我们对其进行向量化时,由于在训练数据中没有*过这个词,向量化器简单地忽略了它。这一点非常重要,它促使数据科学家尽可能多地获取数据以用于训练集。

现在,让我们对实际数据进行此操作:

# split into training and testing sets
from sklearn.cross_validation import train_test_split
X_train, X_test, y_train, y_test = train_test_split(df.msg, df.label, random_state=1)
# instantiate the vectorizer vect = CountVectorizer()
# learn vocabulary and create document-term matrix in a single step train_dtm = vect.fit_transform(X_train)
train_dtm

以下是输出结果:

<4179x7456 sparse matrix of type '<class 'numpy.int64'>' with 55209 stored elements in Compressed Sparse Row format>

请注意,格式是稀疏矩阵,这意味着矩阵很大且充满零。处理此类对象有特殊的格式。看看列的数量。

共有 7,456 个单词。真不少!这意味着在我们的训练集中,有 7,456 个唯一的单词需要考虑。现在我们可以将测试数据转换为符合我们的词汇:

# transform testing data into a document-term matrix test_dtm = vect.transform(X_test)
test_dtm

输出如下:

<1393x7456 sparse matrix of type '<class 'numpy.int64'>'
with 17604 stored elements in Compressed Sparse Row format>

请注意,我们有相同数量的列,因为它符合我们的测试集,确保与之前的词汇完全一致。没有更多,也没有更少。

现在,让我们构建一个朴素贝叶斯模型(类似于线性回归过程):

## MODEL BUILDING WITH NAIVE BAYES
# train a Naive Bayes model using train_dtm from sklearn.naive_bayes import MultinomialNB # import our model
nb = MultinomialNB()
# instantiate our model
nb.fit(train_dtm, y_train)
# fit it to our training set

现在,nb 变量保存了我们的拟合模型。模型的训练阶段涉及计算似然函数,即在给定每个类别的情况下,每个特征的条件概率:

# make predictions on test data using test_dtm preds = nb.predict(test_dtm)
preds

输出如下:

array(['ham', 'ham', 'ham', ..., 'ham', 'spam', 'ham'], dtype='|S4')

模型的预测阶段包括根据观察到的特征计算每个类别的后验概率,并选择具有最高概率的类别。

我们将使用sklearn的内置准确率和混淆矩阵来查看我们的朴素贝叶斯模型的表现:

# compare predictions to true labels from sklearn import metrics
print metrics.accuracy_score(y_test, preds) print metrics.confusion_matrix(y_test, preds)

输出结果如下:

accuracy == 0.988513998564
confusion matrix ==
[[12035]
[11174]]

首先,我们的准确率非常好!与 87%的空准确率相比,99%是一个巨大的提升。

现在来看我们的混淆矩阵。从前面我们知道,每一行代表实际值,而每一列代表预测值,因此左上角的值 1,203 表示我们的真实负类。但什么是负类和正类呢?我们将垃圾邮件和正常邮件作为类别,而不是正类和负类。

我们可以使用以下方法:

nb.classes_

输出结果如下:

array(['ham', 'spam'])

然后我们可以排列索引,使得 1,203 代表真实的正常预测,174 代表真实的垃圾邮件预测。还存在五个错误的垃圾邮件分类,意味着五条消息被预测为垃圾邮件,但实际上是正常邮件,以及 11 个错误的正常邮件分类。总之,朴素贝叶斯分类使用贝叶斯定理来拟合类别的后验概率,从而确保数据点正确地标记为属于相应类别。

每个机器学*模型都有自己的一套独特属性和适用于不同数据类型的优缺点。例如,朴素贝叶斯分类器以其速度和效率著称。它在拟合训练数据和进行测试数据预测时特别快。这是因为它假设特征之间是独立的,从而简化了概率估计中的计算。

然而,这一假设也可以看作是一个局限性。实际上,特征通常会表现出一定程度的依赖性,而朴素贝叶斯分类器可能会过度简化数据中的复杂关系。此外,它基于数据分布形式(通常假设为高斯分布)的假设,这在现实场景中未必总是成立。

尽管存在这些局限性,朴素贝叶斯在适当的数据下表现得非常出色,特别适用于文本分类任务,如垃圾邮件过滤和情感 分析SA)。

另一种广泛使用的机器学*技术是决策树。决策树是一种监督学*SL)方法,常用于分类和回归。由于决策树模拟了人类决策过程,它比其他算法更直观且易于解释。

理解决策树

决策树是监督学*模型,可以执行回归或分类任务。它们是一种类似流程图的结构,其中每个内部节点表示对某个属性的测试,每个分支表示测试的结果,每个叶节点表示一个类别标签(用于分类)或一个值(用于回归)。决策树的主要优点之一是它们的简洁性;它们不需要复杂的数学公式,使得它们更容易理解和可视化。

决策树的目标是以最大化节点纯度的方式来划分数据。对于分类问题,“纯度”指的是节点在目标变量方面的同质性。一个完全纯的节点将只包含单一类别的实例。

决策树通过使用杂质度量,例如基尼指数或熵(稍后会讲到),来评估潜在的分割。一个好的分割是能够最有效地将数据分隔为具有高纯度的节点,这意味着它增加了节点在目标变量方面的同质性。

该过程包括以下内容:

  1. 基于特定标准(如基尼或熵)选择最佳的属性来划分数据。

  2. 将数据集划分为包含该属性值相似的实例的子集。

  3. 对每个派生的子集重复这一过程,直到满足停止标准(例如树的最大深度、节点中的最小实例数,或节点的纯度已达到较高的水*)。

这种递归分割使得决策树成为分类和回归任务中强大且可解释的建模技术。

衡量纯度

基尼指数是衡量频率分布中值之间不*等的指标(例如收入水*)。在机器学*和决策树的背景下,它通过以下公式来衡量节点的杂质:

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block">mml:mtextGini</mml:mtext><mml:mfenced separators="|">mml:mrowmml:miD</mml:mi></mml:mrow></mml:mfenced>mml:mo=</mml:mo>mml:mn1</mml:mn>mml:mo-</mml:mo>mml:mrowmml:munderover<mml:mo stretchy="false">∑</mml:mo>mml:mrowmml:mii</mml:mi>mml:mo=</mml:mo>mml:mn1</mml:mn></mml:mrow>mml:mrowmml:miJ</mml:mi></mml:mrow></mml:munderover>mml:mrowmml:msubsupmml:mrowmml:mip</mml:mi></mml:mrow>mml:mrowmml:mii</mml:mi></mml:mrow>mml:mrowmml:mn2</mml:mn></mml:mrow></mml:msubsup></mml:mrow></mml:mrow></mml:math>

这里,D 是数据集,J 是类别数,pi 是数据集 D 中类别 i 的概率。

熵(Entropy),另一方面,是信息理论中的一个度量,用来量化数据中的不确定性或随机性。它被用于构建决策树,以表示数据集的纯度,其公式如下:

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block">mml:mtext熵</mml:mtext><mml:mfenced separators="|">mml:mrowmml:miD</mml:mi></mml:mrow></mml:mfenced>mml:mo=</mml:mo>mml:mo-</mml:mo>mml:mrowmml:munderover<mml:mo stretchy="false">∑</mml:mo>mml:mrowmml:mii</mml:mi>mml:mo=</mml:mo>mml:mn1</mml:mn></mml:mrow>mml:mrowmml:miJ</mml:mi></mml:mrow></mml:munderover>mml:mrowmml:msubmml:mrowmml:mip</mml:mi></mml:mrow>mml:mrowmml:mii</mml:mi></mml:mrow></mml:msub></mml:mrow></mml:mrow>mml:mrowmml:mrowmml:msubmml:mrow<mml:mi mathvariant="normal">log</mml:mi></mml:mrow>mml:mrowmml:mn2</mml:mn></mml:mrow></mml:msub></mml:mrow>mml:mo⁡</mml:mo>mml:mrow<mml:mfenced separators="|">mml:mrowmml:msubmml:mrowmml:mip</mml:mi></mml:mrow>mml:mrowmml:mii</mml:mi></mml:mrow></mml:msub></mml:mrow></mml:mfenced></mml:mrow></mml:mrow></mml:math>

在这里,pi是数据集D中类别i的概率。

基尼指数和熵都用于在构建决策树时选择数据切分的位置。选择使用基尼指数还是熵通常取决于特定数据集和模型建立者的偏好,因为它们可能会导致略有不同的树形结构。实际上,这两种方法生成的树之间的差异通常非常小。

探索泰坦尼克号数据集

泰坦尼克号数据集是数据科学领域中的经典之作,常用于说明机器学*的基本原理。它详细描述了 RMS 泰坦尼克号的悲惨沉没事件,这也是历史上最臭名昭著的船难之一。该数据集提供了关于乘客的丰富人口统计和旅行信息,可以用来建模和预测生还结果。

通过这个数据集,我们可以运用统计分析和预测建模来理解可能影响生还几率的因素。例如,考虑仅从泰坦尼克号数据集中抽取的 25 名乘客。在这 25 人中,有 10 人幸存,而 15 人未能生还。通过分析年龄、性别、舱位等级和票价等属性,我们可以开始构建一个预测模型,以估算在类似情况下每个乘客的生还可能性。

我们在开始任何操作之前,首先计算基尼指数

在这个示例中,整体类别是survived(生还)和died(死亡),其公式如下所示:

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block">mml:miG</mml:mi>mml:mii</mml:mi>mml:min</mml:mi>mml:mii</mml:mi>mml:mo=</mml:mo>mml:mn1</mml:mn>mml:mo-</mml:mo>mml:msupmml:mrow<mml:mfenced separators="|">mml:mrowmml:mfracmml:mrowmml:mtextsurvived</mml:mtext></mml:mrow>mml:mrowmml:mtexttotal</mml:mtext></mml:mrow></mml:mfrac></mml:mrow></mml:mfenced></mml:mrow>mml:mrowmml:mn2</mml:mn></mml:mrow></mml:msup>mml:mo-</mml:mo>mml:msupmml:mrow<mml:mfenced separators="|">mml:mrowmml:mfracmml:mrowmml:mtextdied</mml:mtext></mml:mrow>mml:mrowmml:mtexttotal</mml:mtext></mml:mrow></mml:mfrac></mml:mrow></mml:mfenced></mml:mrow>mml:mrowmml:mn2</mml:mn></mml:mrow></mml:msup>mml:miG</mml:mi>mml:mii</mml:mi>mml:min</mml:mi>mml:mii</mml:mi>mml:mo=</mml:mo>mml:mn1</mml:mn>mml:mo-</mml:mo>mml:msupmml:mrow<mml:mfenced separators="|">mml:mrowmml:mfracmml:mrowmml:mn10</mml:mn></mml:mrow>mml:mrowmml:mn25</mml:mn></mml:mrow></mml:mfrac></mml:mrow></mml:mfenced></mml:mrow>mml:mrowmml:mn2</mml:mn></mml:mrow></mml:msup>mml:mo-</mml:mo>mml:msupmml:mrow<mml:mfenced separators="|">mml:mrowmml:mfracmml:mrowmml:mn15</mml:mn></mml:mrow>mml:mrowmml:mn25</mml:mn></mml:mrow></mml:mfrac></mml:mrow></mml:mfenced></mml:mrow>mml:mrowmml:mn2</mml:mn></mml:mrow></mml:msup>mml:mo=</mml:mo>mml:mn0.48</mml:mn></mml:math>

这个 0.48 的基尼指数表示数据集中的杂质水*。这个值表明类别之间的分离度适中,并且在这组乘客中,生还死亡类别之间存在一定程度的混合。

如果我们根据某一特征对数据集进行划分,我们将计算每个结果子集的基尼指数。目标是选择一个能够最小化基尼指数的划分,从而增加子集相对于目标变量(在本例中为泰坦尼克号上的生存情况)的纯度。

现在,让我们考虑按性别进行的潜在划分。我们首先计算每个性别的基尼指数,如图 11.5所示:

图 11.5 – 根据性别计算数据集的杂质

图 11.5 – 根据性别计算数据集的杂质

以下公式计算男性和女性的基尼指数:

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block">mml:mtext基尼</mml:mtext><mml:mfenced separators="|">mml:mrowmml:mim</mml:mi></mml:mrow></mml:mfenced>mml:mo=</mml:mo>mml:mn1</mml:mn>mml:mo-</mml:mo>mml:msupmml:mrow<mml:mfenced separators="|">mml:mrowmml:mfracmml:mrowmml:mn2</mml:mn></mml:mrow>mml:mrowmml:mn15</mml:mn></mml:mrow></mml:mfrac></mml:mrow></mml:mfenced></mml:mrow>mml:mrowmml:mn2</mml:mn></mml:mrow></mml:msup>mml:mo-</mml:mo>mml:msupmml:mrow<mml:mfenced separators="|">mml:mrowmml:mfracmml:mrowmml:mn13</mml:mn></mml:mrow>mml:mrowmml:mn15</mml:mn></mml:mrow></mml:mfrac></mml:mrow></mml:mfenced></mml:mrow>mml:mrowmml:mn2</mml:mn></mml:mrow></mml:msup>mml:mo=</mml:mo>mml:mn0.23</mml:mn></mml:math>

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block">mml:mtext基尼</mml:mtext><mml:mfenced separators="|">mml:mrowmml:mif</mml:mi></mml:mrow></mml:mfenced>mml:mo=</mml:mo>mml:mn1</mml:mn>mml:mo-</mml:mo>mml:msupmml:mrow<mml:mfenced separators="|">mml:mrowmml:mfracmml:mrowmml:mn8</mml:mn></mml:mrow>mml:mrowmml:mn10</mml:mn></mml:mrow></mml:mfrac></mml:mrow></mml:mfenced></mml:mrow>mml:mrowmml:mn2</mml:mn></mml:mrow></mml:msup>mml:mo-</mml:mo>mml:msupmml:mrow<mml:mfenced separators="|">mml:mrowmml:mfracmml:mrowmml:mn2</mml:mn></mml:mrow>mml:mrowmml:mn10</mml:mn></mml:mrow></mml:mfrac></mml:mrow></mml:mfenced></mml:mrow>mml:mrowmml:mn2</mml:mn></mml:mrow></mml:msup>mml:mo=</mml:mo>mml:mn0.32</mml:mn></mml:math>

一旦我们为每个性别计算出基尼指数,我们接着计算基于性别的总体基尼指数,计算公式如下:

<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" display="block">mml:mtextGini</mml:mtext><mml:mfenced separators="|">mml:mrowmml:miM</mml:mi></mml:mrow></mml:mfenced><mml:mfenced separators="|">mml:mrowmml:mfracmml:mrowmml:miM</mml:mi></mml:mrow>mml:mrowmml:miM</mml:mi>mml:mo+</mml:mo>mml:miF</mml:mi></mml:mrow></mml:mfrac></mml:mrow></mml:mfenced>mml:mo+</mml:mo>mml:mtextGini</mml:mtext><mml:mfenced separators="|">mml:mrowmml:miF</mml:mi></mml:mrow></mml:mfenced><mml:mfenced separators="|">mml:mrowmml:mfracmml:mrowmml:miF</mml:mi></mml:mrow>mml:mrowmml:miM</mml:mi>mml:mo+</mml:mo>mml:miF</mml:mi></mml:mrow></mml:mfrac></mml:mrow></mml:mfenced>mml:mo=</mml:mo>mml:mn0.23</mml:mn><mml:mfenced separators="|">mml:mrowmml:mfracmml:mrowmml:mn15</mml:mn></mml:mrow>mml:mrowmml:mn10</mml:mn>mml:mo+</mml:mo>mml:mn15</mml:mn></mml:mrow></mml:mfrac></mml:mrow></mml:mfenced>mml:mo+</mml:mo>mml:mn0.32</mml:mn><mml:mfenced separators="|">mml:mrowmml:mfracmml:mrowmml:mn10</mml:mn></mml:mrow>mml:mrowmml:mn10</mml:mn>mml:mo+</mml:mo>mml:mn15</mml:mn></mml:mrow></mml:mfrac></mml:mrow></mml:mfenced>mml:mo=</mml:mo>mml:mn0.27</mml:mn></mml:math>

因此,性别的 Gini 系数为 0.27。然后我们按照这一过程对三个潜在的拆分进行处理(如 图 11.6 所示):

  • 性别(男性或女性)

  • 登船的兄弟姐妹人数(0 或 1+)

  • 类别(第一类和第二类与第三类对比)

图 11.6 – 计算在我们的数据集上进行多次拆分后得到的 Gini 系数,以决定我们应选择哪个拆分用于决策树

图 11.6 – 计算在我们的数据集上进行多次拆分后得到的 Gini 系数,以决定我们应选择哪个拆分用于决策树

在这个例子中,我们会选择性别作为拆分标准,因为它具有 最低的 Gini 指数

在编写更多代码之前,我们需要思考如何处理那些未被数值编码的分类特征。机器学*算法需要数值输入,且大多数数据集中至少有一个特征不是数值型的。

虚拟变量

当我们希望将分类特征转换为定量特征时,会使用虚拟变量。记住,我们有两种类型的分类特征:名义型和顺序型。顺序型特征之间有自然的顺序,而名义型数据没有。

使用单独的列对定性(名义型)数据进行编码称为创建虚拟变量,它通过将名义列中的每个唯一类别转换为自己的列,该列的值为 truefalse

例如,如果我们有一个列来记录某人的大学专业,并且希望将这个信息应用到线性回归或逻辑回归中,我们是无法做到的,因为它们只接受数字!所以,对于每一行,我们需要新增代表单一名义变量的列。在这种情况下,我们有四个独特的专业:计算机科学、工程学、商学和文学。我们最终会得到三个新列(我们省略了计算机科学列,因为它不是必需的,并且可以通过其他三个专业全为 0 来推断)。图 11**.7展示了这个例子:

图 11.7 – 为单一特征创建虚拟变量意味着为每个选项创建一个新的二进制特征,除了一个选项,其余的特征可以通过全为 0 来推断

图 11.7 – 为单一特征创建虚拟变量意味着为每个选项创建一个新的二进制特征,除了一个选项,其余的特征可以通过全为 0 来推断

注意,第一行所有列都是 0,这意味着此人没有主修工程学、没有主修商学,也没有主修文学。第二个人在Engineering列中有一个 1,表示他主修的是工程学。

我们需要使用 pandas 来创建一些虚拟变量,因为我们将使用 scikit-learn 内置的决策树函数来构建决策树:

# read in the data
titanic = pd.read_csv('short_titanic.csv')
# encode female as 0 and male as 1
titanic['Sex'] = titanic.Sex.map({'female':0, 'male':1})
# fill in the missing values for age with the median age titanic.Age.fillna(titanic.Age.median(), inplace=True)
# create a DataFrame of dummy variables for Embarked
embarked_dummies = pd.get_dummies(titanic.Embarked, prefix='Embarked') embarked_dummies.drop(embarked_dummies.columns[0], axis=1, inplace=True)
# concatenate the original DataFrame and the dummy DataFrame titanic = pd.concat([titanic, embarked_dummies], axis=1)
# define X and y
feature_cols = ['Pclass', 'Sex', 'Age', 'Embarked_Q', 'Embarked_S'] X = titanic[feature_cols]
y = titanic.Survived X.head()

图 11**.8展示了我们在前一个代码块执行后数据集的样子。注意,我们将使用类别、性别、年龄和登船城市的虚拟变量作为特征:

图 11.8 – 在为登船城市创建虚拟变量后,我们的泰坦尼克号数据集

图 11.8 – 在为登船城市创建虚拟变量后,我们的泰坦尼克号数据集

现在,我们可以拟合我们的决策树分类器:

# fit a classification tree with max_depth=3 on all data from sklearn.tree import DecisionTreeClassifier treeclf = DecisionTreeClassifier(max_depth=3, random_state=1) treeclf.fit(X, y)

max_depth是一个限制树深度的超参数。这意味着,对于任何数据点,我们的树只能最多问三个问题并进行三次分裂。我们可以将我们的树输出为可视化格式,最终得到图 11**.9所示的结果:

图 11.9 – 使用 scikit-learn 生成的决策树,且每个节点计算了基尼系数

图 11.9 – 使用 scikit-learn 生成的决策树,且每个节点计算了基尼系数

我们可以注意到一些细节:

  • 性别是第一个分裂节点,意味着性别是决定一个人是否在撞击中生还的最重要因素。

  • Embarked_Q在任何分裂中都没有被使用

对于分类树或回归树,我们还可以用决策树做一些非常有趣的事情,那就是我们可以输出一个数字,表示每个特征在预测数据点时的重要性(如图 11**.10所示):

# compute the feature importances pd.DataFrame({'feature':feature_cols, 'importance':treeclf.feature_importances_})

图 11.10 – 贡献最大变化的特征,按百分比显示,所有百分比相加为 1;我们的最高值(性别)也恰好是我们的第一个分裂节点

图 11.10 – 显示在基尼系数变化中贡献最大特征的百分比,所有百分比加起来为 1;我们的最高值(性别)恰好是我们第一次划分的特征,这并非巧合。

重要性评分是每个变量的*均基尼指数差异,较高的值对应着对预测的较高重要性。我们可以利用这些信息在未来选择更少的特征。例如,两个“登船”变量相对于其他特征非常低,因此我们可能可以说它们在预测生死方面不重要。

当我们从结构化的 SL 领域过渡到 UL 领域时,在 SL 领域中结果是已知的,模型通过标签数据学*,而 UL 算法则揭示数据中隐藏的模式和内在结构,这些数据并没有明确的标签。在接下来的部分,我们将探讨无监督技术如何在没有预定义结果的指导下,识别数据中的潜在关系并提供更深的*解,以及它们如何与我们迄今讨论的预测模型相辅相成。

深入探讨 UL

既然我们已经花了一些时间讨论SL 算法,是时候来看一些 UL 的例子了。

何时使用 UL

有很多情况下 UL 是合适的。一些非常常*的例子包括:

  • 没有明确的响应变量。我们没有明确试图预测或与其他变量相关联的内容。

  • 从没有明显结构或模式的数据中提取结构(这可能是一个 SL 问题)。

  • 使用一种无监督的概念——特征提取。特征提取是从现有特征中创建新特征的过程。这些新特征可能比原始特征更强大。

第一个通常是数据科学家选择使用 UL 的最常*原因。这种情况通常发生在我们处理数据时,并没有明确试图预测任何列,我们仅仅希望找到相似(和不相似)数据点的模式。第二种情况即使我们明确尝试使用监督模型来预测响应变量时,也会发生。

有时,简单的探索性数据分析EDA)可能无法在几个人类能够想象的维度中产生明确的模式,而机器可能会在更高维度中发现数据点之间的相似行为。

使用 UL 的第三个常*原因是从已有特征中提取新特征。这个过程(被亲切地称为特征提取)可能会生成比原始特征更强的特征,这些新特征可以用于未来的监督模型,也可以用于展示目的(如市场营销或其他用途)。

k 均值聚类

k 均值聚类是我们第一个无监督机器学*(ML)UML)模型的示例。请记住——这意味着我们不是在做预测。我们是在试图从看似无结构的数据中提取结构。

聚类是尝试将数据点分组到具有质心的簇中的一类 UML 模型。相似性的概念是簇定义的核心,因此也是簇分析的核心。通常来说,点之间的相似性越大,聚类效果越好。在大多数情况下,我们将数据转化为多维空间中的点,并使用这些点之间的距离作为相似性的一种形式。然后,簇的质心通常是每个簇中每个数据点在每一维度(列)上的*均值。例如,红色簇的质心是通过计算每个红色数据点在各列上的*均值得到的。

聚类分析的目的是通过将数据分为不同的组来增强我们对数据集的理解。聚类为单个数据点提供了一个抽象层。目标是提取并增强数据的自然结构。分类方法有很多种。对于我们的课程,我们将重点关注 k 均值聚类,它是最流行的聚类算法之一。

k 均值是一种迭代方法,将数据集划分为k个簇。它的四个步骤是:

  1. 选择k个初始质心(注意k是一个输入值)。

  2. 对每个点,将该点分配给最近的质心。

  3. 重新计算质心位置。

  4. 重复步骤 2步骤 3,直到满足停止标准。

一个聚类的示例

假设我们有在二维空间中的数据点,如图 11.11所示:

图 11.11 – 模拟数据集待聚类

图 11.11 – 模拟数据集待聚类

每个点被着色为灰色,假设在应用 k 均值算法之前没有先前的分组。这里的目标是最终为每个点上色并创建分组(簇),如图 11.12所示:

图 11.12 – 步骤 1:k 均值聚类通过随机放置质心开始

图 11.12 – 步骤 1:k 均值聚类通过随机放置质心开始

我们已经(随机)选择了三个质心红色蓝色黄色)。

重要说明

大多数 k 均值算法会随机放置初始质心,但也存在其他预计算方法来放置初始质心。目前,随机方法就可以。

步骤 2已经在图 11.13中应用。对于每个数据点,我们找到了最相似的质心(即最近的质心):

图 11.13 – 步骤 2:对每个点,将该点分配给最近的质心

图 11.13 – 步骤 2:对每个点,将该点分配给最近的质心

然后我们在图 11.14中应用步骤 3

图 11.14 – 步骤 3:重新计算质心位置

图 11.14 – 步骤 3:重新计算质心位置

这是步骤 3,也是 k-means 的关键。请注意,我们已经将质心物理地移动到每个簇的实际中心。我们已经计算了每个颜色的*均点,并将该点作为新的质心。例如,假设三个红色数据点的坐标分别为:(1, 3)(2, 5)(3, 4)中心(红色交叉点)将按如下方式计算:

# centroid calculation import numpy as np
red_point1 = np.array([1, 3]) red_point2 = np.array([2, 5]) red_point3 = np.array([3, 4])
red_center = (red_point1 + red_point2 + red_point3) / 3.
red_center
# array([ 2., 4.])

也就是说,(2, 4)点将是之前红色交叉点的坐标。

我们通过重复步骤 2继续执行我们的算法。在这里是第一部分,我们为每个点找到最近的中心。请注意一个大变化——左下角的点曾经是黄色的,但现在变成了红色簇点,因为黄色簇移动得更接近其黄色成分:

图 11.15 – 重复步骤 2;请注意,左下角的数据点在上一步是黄色的,现在变成了红色

图 11.15 – 重复步骤 2;请注意,左下角的数据点在上一步是黄色的,现在变成了红色

如果我们再次执行步骤 3,我们将得到图 11.16所示的结果:

图 11.16 – 再次进行步骤 3

图 11.16 – 再次进行步骤 3

在这里,我们再次重新计算每个簇的质心(步骤 3)。请注意,蓝色中心没有移动,而黄色和红色中心都有所移动。

因为我们已经达到了停止准则(如果我们重复步骤 2步骤 3,簇不再移动),所以我们完成了算法,并得到了我们的三个簇,这就是 k-means 算法的最终结果。

一个说明性的例子——啤酒!

让我们对一个新的数据集进行聚类分析,该数据集概述了具有不同特征的啤酒。我们知道啤酒有很多种类型,但我想知道我们是否可以根据不同的定量特征将啤酒分组到不同的类别中。

让我们试试!让我们导入一个包含几种啤酒的数据集,并在图 11.17中可视化几行数据:

# import the beer dataset url = '../data/beer.txt'
beer = pd.read_csv(url, sep=' ')
beer.head()

图 11.17 – 我们啤酒数据集的前五行

图 11.17 – 我们啤酒数据集的前五行

我们的数据集包含 20 种啤酒,具有 5 个列:name(名称)、calories(卡路里)、sodium(钠)、alcohol(酒精)和cost(成本)。在聚类中(如几乎所有的机器学*模型一样),我们更喜欢定量特征,因此我们将在聚类中忽略啤酒的名称:

# define X
X = beer.drop('name', axis=1)

现在,我们将使用scikit-learn执行 k-means 聚类:

# K-means with 3 clusters
from sklearn.cluster import KMeans
km = KMeans(n_clusters=3, random_state=1) km.fit(X)

我们的 k-means 算法已经在我们的数据点上运行,并得出了三个簇:

# save the cluster labels and sort by cluster beer['cluster'] = km.labels_

我们可以通过使用groupbymean语句来查看每个簇的中心(如图 11.18所示):

# calculate the mean of each feature for each cluster beer.groupby('cluster').mean()

图 11.18 – 我们找到的啤酒数据集的簇,k=3

图 11.18 – 我们找到的啤酒数据集的簇,k=3

通过检查,我们可以看到 簇 0 的卡路里、钠含量和酒精含量*均较高,且成本更高。这些可能被视为较重的啤酒。簇 2 的酒精含量非常低,卡路里也很少。这些可能是轻型啤酒。簇 1 则处于两者之间。

让我们用 Python 绘制一个图表,查看更详细的内容,如 图 11.19 所示:

import matplotlib.pyplot as plt
%matplotlib inline
# save the DataFrame of cluster centers centers = beer.groupby('cluster').mean() # create a "colors" array for plotting
colors = np.array(['red', 'green', 'blue', 'yellow'])
# scatter plot of calories versus alcohol, colored by cluster (0=red, 1=green, 2=blue)
plt.scatter(beer.calories, beer.alcohol, c=colors[list(beer.cluster)], s=50)
# cluster centers, marked by "+"
plt.scatter(centers.calories, centers.alcohol, linewidths=3, marker='+', s=300, c='black')
# add labels plt.xlabel('calories') plt.ylabel('alcohol')

图 11.19 – 我们通过数据集的二维展示进行聚类分析

图 11.19 – 我们通过数据集的二维展示进行聚类分析

选择 K 的最佳数值和聚类验证

K-means 聚类的一个重要部分是确定最优的簇数。如果我们事先知道这个数值,那么使用无监督学*(UL)的目的可能就失去了。因此,我们需要一种方法来评估聚类分析的输出。问题在于,因为我们没有进行任何类型的预测,我们无法衡量算法在预测上的准确性。诸如准确度和均方根误差(RMSE)等指标就不适用了。幸运的是,我们确实有一个相当有用的指标来帮助优化我们的聚类分析,叫做轮廓系数。

轮廓系数

轮廓系数 是在真实聚类分配未知的情况下评估聚类性能的常用指标。轮廓系数用于评估聚类算法创建的聚类质量。它量化了一个对象与其所属聚类(凝聚性)相比与其他聚类的相似度(分离度)。单个数据点的轮廓系数通过以下公式计算:

SC=b−amaxa,b

在这里,以下内容适用:

  • a 是样本与同一类或同一簇中所有其他点之间的*均距离。它表示聚类的凝聚性。

  • b 是一个样本与下一个最近簇中所有其他点之间的*均距离。它表示样本与最近簇(不属于该簇的簇)的分离程度。

该系数的范围从 -1(最差)1(最佳)。通过计算所有观测值的均值得出全局得分。轮廓系数特别适用于确定聚类算法的有效性,因为它考虑了聚类的紧凑性和聚类之间的分离度。通常,轮廓系数为 1 更为理想,而 -1 的得分则不太可取:

# calculate Silhouette Coefficient for K=3 from sklearn import metrics metrics.silhouette_score(X, km.labels_)

输出如下:

0.67317750464557957

让我们尝试计算不同 K 值的系数,以找到最佳值:

# center and scale the data
from sklearn.preprocessing import StandardScaler scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# calculate SC for K=2 through K=19 k_range = range(2, 20)
scores = []
for k in k_range:
km = KMeans(n_clusters=k, random_state=1) km.fit(X_scaled) scores.append(metrics.silhouette_score(X, km.labels_))
# plot the results plt.plot(k_range, scores) plt.xlabel('Number of clusters')
plt.ylabel('Silhouette Coefficient') plt.grid(True)

所以,看起来我们的最佳啤酒聚类数是 4!这意味着我们的 k-means 算法确定似乎有四种不同类型的啤酒。

图 11.20 – 不同聚类数下的轮廓系数

图 11.20 – 不同聚类数下的轮廓系数

k-means 是一个广受欢迎的算法,因为它的计算效率高且直观简单。然而,k-means 算法对数据的规模依赖性较强,并不适用于形状和密度差异较大的数据。我们可以通过使用scikit-learnStandardScalar来对数据进行缩放,从而解决这个问题:

# center and scale the data
from sklearn.preprocessing import StandardScaler scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# K-means with 3 clusters on scaled data km = KMeans(n_clusters=3, random_state=1) km.fit(X_scaled)

简单!

现在,让我们来看看我们使用无监督方法的第三个原因:特征提取

特征提取和 PCA

在处理数据时,特别是在机器学*中,一个常*的问题是有过多的列,而行数不足以处理如此多的列。

一个很好的例子是我们在之前的朴素贝叶斯示例中看过的立即汇款例子。记得我们之前的文本中没有任何包含这个确切短语的实例吗?在那种情况下,我们转向了一个朴素的假设,允许我们为我们的两个类别推断一个概率。

我们一开始遇到这个问题的原因是因为有一个叫做维度灾难COD)的现象。COD 基本上是说,随着我们引入新的特征列,我们需要更多的行(数据点)来考虑增加的可能性数量。

考虑一个例子,我们尝试使用一个学*模型,利用一个包含 4,086 篇文本的语料库中的点之间的距离,并且这些文本已通过scikit-learn进行了计数向量化。现在,让我们做一个实验。我将首先考虑一个单词作为我们文本的唯一维度。然后,我将计算多少篇文本在 1 个单位的距离内。例如,如果两个句子都包含这个词,它们之间的距离就是 0 单位;同样地,如果两个句子都没有包含这个词,它们之间的距离也是 0 单位:

d = 1
# Let's look for points within 1 unit of one another
X_first_word = X.iloc[:,:1]
# Only looking at the first column, but ALL of the rows
from sklearn.neighbors import NearestNeighbors
# this module will calculate for us distances between each point
neigh = NearestNeighbors(n_neighbors=4086)
neigh.fit(X_first_word)
# tell the module to calculate each distance between each point
A = neigh.kneighbors_graph(X_first_word, mode='distance').todense() # This matrix holds all distances (over 16 million of them)
num_points_within_d = (A < d).sum()
# Count the number of pairs of points within 1 unit of distance, 16,258,504

重要提示

请注意,我们有16,695,3964086*4086)个距离需要扫描。

所以,16.2 百万对文本位于单个单位距离内。现在,让我们用前两个词再试一次:

X_first_two_words = X.iloc[:,:2]
neigh = NearestNeighbors(n_neighbors=4086) neigh.fit(X_first_two_words)
A = neigh.kneighbors_graph(X_first_two_words, mode='distance').todense() num_points_within_d = (A < d).sum()
# num_points_within_d is now 16,161,970

通过考虑一个新的列,我们丢失了大约 100,000 对原本在一个单位距离内的点。这是因为我们在每增加一个维度时,实际上是在它们之间添加空间。让我们更进一步进行这个测试,计算前 100 个词的这个数字,然后绘制结果:

num_columns = range(1, 100)
# Looking at the first 100 columns points = []
# We will be collecting the number of points within 1 unit for a graph
neigh = NearestNeighbors(n_neighbors=X.shape[0])
for subset in num_columns:
X_subset = X.iloc[:,:subset]
# look at the first column, then first two columns, then first three columns, etc
neigh.fit(X_subset)
A = neigh.kneighbors_graph(X_subset, mode='distance').todense() num_points_within_d = (A < d).sum()
# calculate the number of points within 1 unit points.append(num_points_within_d)

现在,让我们在图 11.21中绘制 1 个单位内的数据点数与我们考虑的维度数量的关系:

图 11.21 – COD 表明随着我们在数据集里增加特征列,数据点之间的距离会因为高维空间的增加而变得更远

图 11.21 – COD 表示,随着我们增加数据集中的特征列数量,数据点之间的距离由于高维空间的增加而变得越来越远。

换句话说,COD 表明,随着特征列数量的增加,我们需要指数级地增加数据量以维持相同水*的模型性能。这是因为在高维空间中,即使是最近邻的数据点也可能离某个给定的数据点非常远,这使得创建良好的预测变得困难。高维度还增加了过拟合的风险,因为模型可能开始拟合数据中的噪声而不是实际信号。

此外,随着维度的增加,空间的体积急剧增加,导致可用数据变得稀疏。这种稀疏性对任何需要统计显著性的方法都是一个问题。为了获得可靠的结果,支持分析所需的数据量往往随着维度的增加而呈指数增长。

我们可以清楚地看到,随着列数的增加,单元内点的数量显著下降。而这仅仅是前 100 列而已!

我们通过考虑新列所增加的所有空间使得我们有限的点更难保持在彼此的范围内。我们必须添加更多的点来填补这个空白。这就是为什么我们应该考虑使用降维的原因。

COD 问题可以通过增加更多数据点(虽然并非总是可能)或实施降维来解决。降维就是减少数据集中的列数,而不是减少行数。有两种实施降维的方法:

  • 特征选择:这是指创建我们的列特征的子集,并仅使用最佳特征。

  • 特征提取:这是通过数学变换将我们的特征集转化为一个新的提取坐标系统的行为。

我们熟悉特征选择,通常是通过判断Embarked_Q列对我们的决策树没有帮助。让我们去掉它,看看模型的表现。这实际上就是我们(或机器)决定忽略某些列的过程。

特征提取要复杂一些。

特征提取中,我们通常使用相当复杂的数学公式来获得新的超级列,这些列通常比任何单一的原始列要好。

我们用于实现这一目标的主要模型称为PCA。PCA 将提取一定数量的超级列,以便用更少的列表示原始数据。让我们来看一个具体的例子。之前,我提到过一些包含 4,086 行和超过 18,000 列的文本数据集。那个数据集实际上是Yelp在线评论数据:

url = '../data/yelp.csv'
yelp = pd.read_csv(url, encoding='unicode-escape')
# create a new DataFrame that only contains the 5-star and 1-star reviews yelp_best_worst = yelp[(yelp.stars==5) | (yelp.stars==1)]
# define X and y
X = yelp_best_worst.text
y = yelp_best_worst.stars == 5

我们的目标是预测一个人是否给出了 5 星或 1 星的评价,基于他们在评论中使用的词语。让我们用逻辑回归设定一个基准,看看我们在预测这个二分类问题时能做到什么程度:

from sklearn.linear_model import LogisticRegression lr = LogisticRegression()
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=100) # Make our training and testing sets
vect = CountVectorizer(stop_words='english')
# Count the number of words but remove stop words like a, an, the, you, etc
X_train_dtm = vect.fit_transform(X_train) X_test_dtm = vect.transform(X_test)
# transform our text into document term matrices
lr.fit(X_train_dtm, y_train) # fit to our training set
lr.score(X_test_dtm, y_test) # score on our testing set

输出结果如下:

0.91193737

所以,通过利用我们语料库中的所有词语,我们的模型似乎达到了超过 91%的准确率。还不错!

让我们尝试只使用前 100 个最常用的词:

vect = CountVectorizer(stop_words='english', max_features=100) # Only use the 100 most used words
X_train_dtm = vect.fit_transform(X_train) X_test_dtm = vect.transform(X_test) print( X_test_dtm.shape) # (1022, 100)
lr.fit(X_train_dtm, y_train) lr.score(X_test_dtm, y_test)

输出结果如下:

0.8816

请注意,我们的训练和测试矩阵都有 100 列。这是因为我让我们的向量化器只关注最常用的 100 个词。同时也能看到,我们的性能有所下降,现在准确率降到了 88%。这也很有道理,因为我们忽略了语料库中超过 4,700 个词。

现在,让我们换个方法。我们导入一个 PCA 模块,让它生成 100 个新的超级特征列,然后看看效果如何:

from sklearn import decomposition
# We will be creating 100 super columns
vect = CountVectorizer(stop_words='english') # Don't ignore any words
pca = decomposition.PCA(n_components=100) # instantate a pca object
X_train_dtm = vect.fit_transform(X_train).todense()
# A dense matrix is required to pass into PCA, does not affect the overall message
X_train_dtm = pca.fit_transform(X_train_dtm)
X_test_dtm = vect.transform(X_test).todense() X_test_dtm = pca.transform(X_test_dtm)
print( X_test_dtm.shape) # (1022, 100) lr.fit(X_train_dtm, y_train) lr.score(X_test_dtm, y_test)

输出结果如下:

.89628

我们的矩阵不仅仍然有 100 列,而且这些列不再是我们语料库中的词语。它们是列的复杂变换,形成了 100 个新的特征列。还要注意,使用这 100 个新列比使用 100 个最常用词更能提高预测性能!

特征提取是一种利用数学公式提取全新特征列的好方法,这些特征通常比预先选择最佳特征的方式表现得更好。

那么,我们该如何可视化这些新的超级特征列呢?嗯,我想不出比通过图像分析来展示更好的方法了。具体来说,让我们做一个人脸识别软件。好吗?好的。让我们从scikit-learn提供的一些人脸数据开始:

from sklearn.datasets import fetch_lfw_people
lfw_people = fetch_lfw_people(min_faces_per_person=70, resize=0.4)
# introspect the images arrays to find the shapes (for plotting) n_samples, h, w = lfw_people.images.shape
# for machine learning we use the 2 data directly (as relative pixel # positions info is ignored by this model)
X = lfw_people.data
y = lfw_people.target n_features = X.shape[1] X.shape (1288, 1850)

我们收集了 1,288 张人脸图像,每张图像有 1,850 个特征(像素)用来识别该人。这是我们使用的代码——其中一张人脸的示例*于图 11.22

plt.imshow(X[100].reshape((h, w)), cmap=plt.cm.gray) lfw_people.target_names[y[100]] 'George W Bush'

图 11.22 – 来自我们数据集的人脸:乔治·W·布什

图 11.22 – 来自我们数据集的人脸:乔治·W·布什

太棒了!为了大致了解我们正在处理的数据集类型,先来看一些整体指标:

# the label to predict is the id of the person target_names = lfw_people.target_names n_classes = target_names.shape[0]
print("Total dataset size:")
print("n_samples: %d" % n_samples)
print("n_features: %d" % n_features)
print("n_classes: %d" % n_classes) Total dataset size:
---
n_samples: 1288
n_features: 1850
n_classes: 7

因此,我们有 1,288 张图像,1,850 个特征,以及 7 个类别(人名)可以选择。我们的目标是创建一个分类器,根据给定的 1,850 个像素来为人脸分配一个名字。

让我们先设定一个基准,看看在没有对数据集做任何处理的情况下,逻辑回归(基于线性回归的分类器)在我们的数据上表现如何。

我知道我们之前没有正式介绍过逻辑回归,但它是一个非常轻量级的分类器,基于与上一章中的线性回归非常相似的假设。现在我们只需要知道,它执行的是分类任务!

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
from time import time # for timing our work
X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.25, random_state=1)
# get our training and test set
t0 = time()
# get the time now
logreg = LogisticRegression()
logreg.fit(X_train, y_train)
# Predicting people's names on the test set
y_pred = logreg.predict(X_test)
print( accuracy_score(y_pred, y_test), "Accuracy") print( (time() - t0), "seconds" )

输出结果如下:

0.810559006211 Accuracy
6.31762504578 seconds

因此,在 6.3 秒内,我们在测试集上获得了 81%的准确率。还不错吧。

现在,让我们尝试使用分解过的人脸数据:

# split into a training and testing set
from sklearn.cross_validation import train_test_split
# Compute a PCA (eigenfaces) on the face dataset (treated as unlabeled # dataset): unsupervised feature extraction / dimensionality reduction
n_components = 75
print("Extracting the top %d eigenfaces from %d faces" % (n_components, X_train.shape[0]))
pca = decomposition.PCA(n_components=n_components, whiten=True).fit(X_train)
# This whiten parameter speeds up the computation of our extracted columns
# Projecting the input data on the eigenfaces orthonormal basis
X_train_pca = pca.transform(X_train)
X_test_pca = pca.transform(X_test)

上面的代码从我们 1,850 个未处理的列中提取了 75 个列。这些是我们的超级面孔。现在,让我们把新提取的列输入到我们的逻辑回归模型中进行比较:

t0 = time()
# Predicting people's names on the test set WITH PCA logreg.fit(X_train_pca, y_train)
y_pred = logreg.predict(X_test_pca)
print accuracy_score(y_pred, y_test), "Accuracy" print (time() - t0), "seconds"
0.82298136646 Accuracy
0.194181919098 seconds

哇!不仅整个计算比未处理的图像快了大约 30 倍,而且预测性能也变得更好了!这表明,PCA 和特征提取通常可以帮助我们在处理包含许多列的复杂数据集时更加高效。通过在数据集中寻找这些模式并提取新的特征列,我们可以加速并增强我们的学*算法。

让我们再看一个有趣的东西。我之前提到过,这个例子的一部分目的是检查并可视化我们的特征脸,正如它们所被称的那样:我们的超级列。我不会让你失望的。让我们写一些代码,展示给我们这些超级列在人类眼中是怎样的:

def plot_gallery(images, titles, n_row=3, n_col=4): """Helper function to plot a gallery of portraits""" plt.figure(figsize=(1.8 * n_col, 2.4 * n_row))
plt.subplots_adjust(bottom=0, left=.01, right=.99, top=.90, hspace=.35)
for i in range(n_row * n_col):
plt.subplot(n_row, n_col, i + 1) plt.imshow(images[i], cmap=plt.cm.gray) plt.title(titles[i], size=12)
# plot the gallery of the most significative eigenfaces eigenfaces = pca.components_.reshape((n_components, h, w))
eigenface_titles = ["eigenface %d" % i for i in range(eigenfaces.shape[0])]
plot_gallery(eigenfaces, eigenface_titles)
plt.show()

警告:图 11.23 中的面孔有点吓人!

图 11.23 – 对我们面部像素进行 PCA 处理,创建了所谓的“特征脸”,这些特征代表了我们的分类器在尝试识别面孔时会寻找的特征

图 11.23 – 对我们面部像素进行 PCA 处理,创建了所谓的“特征脸”,这些特征代表了我们的分类器在尝试识别面孔时会寻找的特征

哇!这是一个令人毛骨悚然而又美丽的表现,展现了数据认为最重要的面部特征。当我们从左上角(第一个超级列)向下移动时,实际上很容易看出图像想要告诉我们什么。第一个超级列看起来像是一个非常一般的面部结构,有眼睛、鼻子和嘴巴。它几乎在说:“我代表所有面孔必须具备的基本特征。”第二个超级列就在它的右边,似乎在告诉我们图像中的阴影。接下来的一个可能在告诉我们肤色在识别这个人时起了作用,这也许就是为什么第三张脸比前两张要黑得多。

使用特征提取的超大规模方法(如 PCA)可以让我们深入了解数据,并揭示数据认为最重要的特征,而不仅仅是我们认为的特征。特征提取是一个很好的预处理工具,它可以加速我们未来的学*方法,使其更强大,并让我们更深入了解数据如何认为应该被解读。总结这一部分,我们将列出优缺点。

下面是使用特征提取的一些优点:

  • 我们的模型变得更快

  • 我们的预测性能可能会变得更好

  • 它可以让我们深入了解提取的特征(特征脸)

下面是使用特征提取的一些缺点:

  • 我们失去了一些特征的可解释性,因为它们是新的数学推导列,而不是我们原来的列。

  • 我们可能会失去预测性能,因为我们在提取较少列时丢失了信息。

接下来我们进入总结部分。

总结

我们对机器学*世界的探索揭示了一个广阔的领域,远远超出了线性回归和逻辑回归等基础技术。我们深入研究了决策树,通过其层级结构提供了直观的数据洞察。朴素贝叶斯分类则为我们提供了一种概率视角,展示了在特征独立假设下如何进行预测。我们还涉足了降维,接触到了特征提取等技术,帮助克服了维度灾难(COD)并减少了计算复杂度。

k-means 聚类将我们引入了无监督学*(UL)的领域,在这里我们学会了如何在没有预标记结果的情况下,发现数据中的隐藏模式和分组。在这些方法中,我们看到了机器学*如何解决一系列复杂问题,从预测分类结果到揭示数据中的潜在结构。

通过实际例子,我们对比了依赖于标记数据的监督学*(SL)与在没有明确输出指导下运行的无监督学*(UL)。这段旅程使我们更深入地理解了各种技术及其在广泛且动态的数据科学领域中的适用场景。

在继续利用这些算法的力量时,我们时刻提醒自己,选择适合任务的模型至关重要——这一原则在有效的数据科学实践中始终占据核心地位。

第十二章:迁移学*与预训练模型简介

就像没人会重新发明轮子一样,在数据科学和机器学*ML)领域,通常更高效的做法是基于现有知识进行构建。这就是迁移学*TL)和预训练模型的概念发挥作用的地方,这两者是数据科学家工具库中不可或缺的强大工具。

TL 几乎像是机器学*中的快捷方式。与其使用一个从未接触过数据的模型架构,例如逻辑回归模型或随机森林模型,不如想象一下能够将一个在某个任务上训练过的模型,重新用于另一个不同但相关的任务。这就是 TL 的本质——利用现有的知识以更高效的方式学*新事物。这一概念贯穿于生活的许多方面,是数据科学中的一项关键技术。

预训练模型是现成的组件,可以立即使用。它们是已在大数据集上进行训练的机器学*模型,捕获了关于它们所训练任务的海量信息。面对新任务时,这些预训练模型提供了一个巨大的起步优势。在现代机器学*中,TL 和预训练模型的重要性不容忽视。它们是数据科学中许多前沿应用的基础,从计算机视觉CV)任务(如图像识别)到自然语言处理NLP)任务(如情感分析SA))。通过利用这些技术,我们即使在数据或资源有限的情况下,也能取得令人印象深刻的成果。

在接下来的章节中,我们将探讨这些概念,探索它们的细微差别、应用和潜力:

  • 理解预训练模型

  • 不同类型的 TL

  • TL 与双向编码器表示的转换器BERT)和生成预训练****变换器GPT

我们还将通过实际示例来讲解它们在实际场景中的强大作用。通过本章内容,您将深入理解 TL 和预训练模型,并能够在自己的项目中利用它们的潜力。

理解预训练模型

预训练模型就像是从他人的经验中学*。这些模型已在大量数据集上进行过训练,学*了使其能够胜任任务的模式和特征。可以把它想象成一个模型读了成千上万本关于某一主题的书籍,吸收了所有这些信息。当我们使用预训练模型时,我们实际上是在利用这些先前的知识。

一般来说,预训练步骤对人类来说并不“有用”,但对于模型而言,它是学*领域和媒介的关键。预训练帮助模型理解语言是如何运作的,但并不涉及如何分类情感或检测物体。

使用预训练模型的好处

使用预训练模型的好处有很多。首先,它们能为我们节省大量时间。从头开始训练一个模型可能是一个耗时的过程,但使用预训练模型可以让我们提前开始。此外,这些模型通常能带来更好的性能,尤其是在数据集相对较小的情况下。原因是,预训练模型已经看过比我们通常可以使用的更多的数据,并从中学到了很多。

图 12.1 显示了一项关于 大型语言模型 (LLMs) 的研究结果,例如 BERT,其中一个目标是展示预训练如何导致 BERT 在识别基本语法结构时出现一些明显的模式。该研究可视化地表明,经过预训练的模型能够识别我们认为是显而易*的语法模式,如代词-先行词关系和直接宾语/动词关系:

图 12.1 – 一项研究,展示了 BERT 预训练如何让它识别常*的语法结构,尽管它从未被告知这些结构是什么

图 12.1 – 一项研究,展示了 BERT 预训练如何让它识别常*的语法结构,尽管它从未被告知这些结构是什么

BERT 当然不是唯一一个进行预训练的模型,这种做法甚至不限于基于文本的模型。

常用的预训练模型

预训练模型有各种形状和尺寸,每种都针对不同类型的数据和任务量身定制。让我们来谈谈其中一些最受欢迎的模型。

基于图像的模型

对于与图像相关的任务,像 Vision Transformer(本章稍后会介绍)这样的模型,视觉几何组VGG)的模型(如在 图 12.2 中所示),以及 ResNet 是一些常*的选择。这些模型已经在成千上万的图像上进行过训练,学会了从形状和纹理到复杂物体的识别。它们非常通用,可以为各种基于图像的任务进行微调:

图 12.2 – VGG16 模型(来自 VGG)是一个卷积神经网络(CNN),可以在图像数据上进行预训练

图 12.2 – VGG16 模型(来自 VGG)是一个卷积神经网络(CNN),可以在图像数据上进行预训练

基于文本的模型

当谈到文本时,像 BERT(图 12.3)和 GPT 这样的模型是最常用的语言模型之一。它们最初是在 2018 年设计的(仅在 GPT 和 BERT 都基于的基础 Transformer 架构被提出或提及后 1 年),并且像它们的图像对应物一样,已经在大量文本数据上进行训练,学*了人类语言的复杂性。无论是理解推文背后的情感,还是回答关于某段文本的问题,这些模型都能胜任任务。随着我们不断前进,我们将看到这些预训练模型如何与 TL 结合,凭借出色的效率应对新的任务:

图 12.3 – 来自谷歌 2018 年开源 BERT 原始博客文章中的一张图片,强调了 OpenAI 的 GPT-1 模型

图 12.3 – 来自谷歌 2018 年开源 BERT 原始博客文章中的一张图片,强调了 OpenAI 的 GPT-1 模型

这张图片是在几个月前发布的,突出了 BERT 在相对相同数量的参数下,比 GPT 处理更多词汇之间关系的能力。

解码 BERT 的预训练

TL 最令人印象深刻的成就之一可以从 BERT 中看到,BERT 是一个革命性地改变了 NLP 领域的预训练模型。两个基本的训练任务推动了 BERT 对语言语义和关系的深刻理解:掩码语言建模MLM)和下一句预测NSP)。让我们分解一下,看看每个任务如何促进 BERT 的语言处理能力。

MLM

MLM*图 12.4)是 BERT 预训练过程中的关键组成部分。本质上,MLM 通过随机将输入数据中大约 15%的单词替换为一个特殊的(MASK)标记来工作。然后,BERT 需要弄清楚哪个单词被替换了,实质上是填补空白。可以把它看作是 BERT 在训练过程中玩的一种复杂的填词游戏。举个例子,假设我们有一句话“Stop at the light”,MLM 可能会将“light”替换为(MASK),并促使 BERT 预测缺失的单词:

图 12.4 – MLM 预训练任务让 BERT 从一个词序列中填补缺失的标记

图 12.4 – MLM 预训练任务让 BERT 从一个词序列中填补缺失的标记

MLM 这一过程帮助 BERT 理解每个单词周围的上下文,并构建句子不同部分之间的有意义关系。

NSP

NSP(在图 12.5中可视化)是 BERT 预训练方案的第二个关键部分。NSP 是一个二分类问题,BERT 的任务是判断提供的句子B是否在原文中紧跟句子A。从语言理解的宏观角度来看,这就像是在要求 BERT 理解一段文本中句子的逻辑流。这种预测句子B是否合乎逻辑地跟随句子A的能力,使得 BERT 能够理解更加微妙的、更高层次的语言结构和叙事流:

图 12.5 – 在 NSP 预训练任务中,BERT 正在查看两个思想,并判断第二个短语是否紧接着第一个短语

图 12.5 – 在 NSP 预训练任务中,BERT 正在查看两个思想,并判断第二个短语是否紧接着第一个短语

换句话说,MLM 帮助 BERT 理解单词及其上下文之间复杂的关系,而 NSP 则使 BERT 能够理解句子之间的关系。正是这种组合使得 BERT 成为执行各种 NLP 任务的强大工具。在 NSP 和 MLM(图 12.6)之间,BERT 的训练旨在让它理解词元如何影响短语含义(MLM),以及短语如何协同工作以形成更大的思想(NSP):

图 12.6 – BERT 的预训练帮助它学*语言的基本知识

图 12.6 – BERT 的预训练帮助它学*语言的基本知识

TL

现在我们已经对预训练模型有了较强的理解,让我们把注意力转向这个方程式的另一个引人注目的方面:TL。本质上,TL 是将从一个问题领域(源)获得的知识应用到另一个但相关的问题领域(目标)。

TL 的过程

TL 的核心在于适应性。它将一个已经在某个任务上训练过的模型,调整到执行一个不同但相关的任务。它们可能之前没有解决过这个特定的谜题,但它们的技能和经验可以被调整到当前的任务上。TL 在我们为特定任务拥有少量数据,或者我们的任务与原始模型训练时的任务非常相似时特别有用。在这些情况下,TL 可以节省时间和资源,同时提高我们模型的性能。

不同类型的 TL

让我们花点时间了解一下 TL 的多样性。它不是一个单一的整体概念,而是多种不同策略的集合,这些策略都属于同一个 umbrella term。几乎每种你可能遇到的场景都有一种 TL 类型。

归纳式 TL

首先,我们有归纳式 TLITL)。这完全是关于利用已经学到的知识,并将其应用到新的但相关的场景中。这里的关键是将一个任务中学到的特征进行泛化——我们称之为源任务——然后对它们进行微调,以便在另一个任务——目标任务上表现良好。

想象一下,一个模型在广泛的文本语料库中度过了它的虚拟一生,掌握了语言、语法和语境的复杂性。现在,我们有一个新的任务:对产品评论进行情感分析(SA)。通过迁移学*(ITL),我们的模型可以利用它已经学到的语言知识,并通过微调使自己成为一个检测情感的专家。图 12.7展示了我们如何在后续部分应用 ITL。其核心思想是,从模型库(如HuggingFace)中取出一个预训练模型,对其进行任何可能的任务调整,投入一些带标签的数据(像我们对待任何其他机器学*模型一样),然后观察它如何学*:

图 12.7 – ITL 过程可能涉及在带监督标签数据上训练 BERT

图 12.7 – ITL 过程可能涉及在带监督标签数据上训练 BERT

传导性迁移学*(TTL)

我们的第二种迁移学*类型称为传导性迁移学*(TTL),它的任务相对模糊。我们的模型并不是被要求执行一个具体的第二任务(例如分类),而是被要求在不失去对原任务基础的理解的情况下,适应新数据。当我们有大量未标注的目标任务数据时,这种方法是一个不错的选择。

例如,如果一个模型在一个图像数据集上进行了训练,以识别不同的物体,而我们有一个新的未标注的图像数据集,我们可以要求模型利用从源任务中获得的知识来标注新的数据集,即使没有提供任何显式标签。

无监督迁移学*(TL)——特征提取

预训练模型不仅对其预测能力有用,它们也是特征的宝库,等待我们去挖掘。通过使用无监督迁移学*(UTL),我们已经在庞大的文本语料库上训练过的模型,可以利用它对语言的理解,发现模式并帮助我们将文本划分为有意义的类别。使用预训练模型进行特征提取,涉及使用预训练模型将原始数据转化为更有用的格式——突出重要特征和模式。

这三种迁移学*方法是 TL 的主要类型,每种方法都有其独特的方式和理想的应用场景。在广阔的机器学*世界中,关键在于选择合适的工具,而迁移学*无疑为我们提供了一个完整的工具箱供我们选择。通过对迁移学*的这些新的理解,我们已做好充分准备,开始将其应用到实践中。在下一节中,我们将看到迁移学*和预训练模型如何结合在一起,轻松征服新任务。

基于 BERT 和 GPT 的迁移学*(TL)

在掌握了预训练模型和迁移学*(TL)的基本概念后,是时候将理论付诸实践了。知道食材是什么是一回事;知道如何将它们混合成一道美味的菜肴又是另一回事。在这一部分,我们将采用一些已经从预训练中学到了很多的模型,并对其进行微调,使其能够执行一个新的相关任务。这个过程涉及调整模型的参数,以更好地适应新任务,类似于调整乐器的音调:

图 12.8 – ITL

图 12.8 – ITL

ITL 采用一个通常在半监督(或无监督)任务上训练的预训练模型,然后用标记数据学*一个特定任务。

TL 示例

让我们看一些具体预训练模型的 TL 示例。

示例 – 微调预训练模型进行文本分类

考虑一个简单的文本分类问题。假设我们需要分析客户评论,并判断它们是正面还是负面。我们有一个评论数据集,但它远远不足以从头开始训练一个深度学*DL)模型。我们将微调 BERT 进行文本分类任务,使模型能够将其现有知识适应我们的特定问题。

我们将需要从流行的 scikit-learn 库转向另一个流行的库,名为transformers,它是由 HuggingFace(我之前提到的预训练模型库)创建的,因为scikit-learn(目前)不支持 Transformer 模型。

图 12.9展示了我们如何需要对原始 BERT 模型进行一些小的修改,以执行文本分类。幸运的是,transformers包有一个内置的类,名为BertForSequenceClassification,可以为我们完成这项工作:

图 12.9 – 最简单的文本分类情况

图 12.9 – 最简单的文本分类情况

在许多 TL 案例中,我们需要架构额外的层。在最简单的文本分类情况下,我们在预训练的 BERT 模型上添加一个分类层,以便它可以执行我们想要的分类任务。

以下代码块展示了在文本分类任务中微调 BERT 的端到端代码示例。请注意,我们还使用了一个名为datasets的包,它也是由HuggingFace开发的,用于从 IMDb 评论中加载情感分类任务。让我们首先加载数据集:

# Import necessary libraries
from datasets import load_dataset
from transformers import BertTokenizer, BertForSequenceClassification, Trainer, TrainingArguments
# Load the dataset
imdb_data = load_dataset('imdb', split='train[:1000]')  # Loading only 1000 samples for a toy example
# Define the tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
# Preprocess the data
def encode(examples):
    return tokenizer(examples['text'], truncation=True, padding='max_length', max_length=512)
imdb_data = imdb_data.map(encode, batched=True)
# Format the dataset to PyTorch tensors
imdb_data.set_format(type='torch', columns=['input_ids', 'attention_mask', 'label'])

加载数据集后,我们可以运行一些训练代码,以在我们的标记数据上更新 BERT 模型:

# Define the model
model = BertForSequenceClassification.from_pretrained(
  'bert-base-uncased', num_labels=2)
# Define the training arguments
training_args = TrainingArguments(
  output_dir='./results',
  num_train_epochs=1,
  per_device_train_batch_size=4
)
# Define the trainer
trainer = Trainer(model=model, args=training_args, train_dataset=imdb_data)
# Train the model
trainer.train()
# Save the model
model.save_pretrained('./my_bert_model')

一旦我们保存了模型,我们可以使用以下代码对模型进行未*数据的推理:

from transformers import pipeline
# Define the sentiment analysis pipeline
nlp = pipeline('sentiment-analysis', model=model, tokenizer=tokenizer)
# Use the pipeline to predict the sentiment of a new review
review = "The movie was fantastic! I enjoyed every moment of it."
result = nlp(review)
# Print the result
print(f"label: {result[0]['label']}, with score: {round(result[0]['score'], 4)}")
# "The movie was fantastic! I enjoyed every moment of it."
# POSITIVE: 99%

示例 – 图像分类的 TL

我们可以使用像 ResNet 或视觉 Transformer(如图 12.10所示)这样的预训练模型,它最初是在像 ImageNet 这样的规模较大的图像数据集上训练的。这个模型已经学会了从图像中检测各种特征,从简单的形状到复杂的物体。我们可以利用这个知识,在自定义的图像分类任务上对模型进行微调:

图 12.10 – 视觉 Transformer

图 12.10 – 视觉 Transformer

视觉 Transformer 就像是图像领域的 BERT 模型。它依赖于许多相同的原理,只不过它使用图像的片段作为“令牌”来代替文本令牌。

以下代码块展示了一个端到端的代码示例,用于在图像分类任务中微调 Vision Transformer。该代码应该与上一节的 BERT 代码非常相似,因为transformers库的目标是标准化现代预训练模型的训练和使用,使得无论你执行什么任务,它们都能提供相对统一的训练和推理体验。

让我们首先加载数据并查看我们所拥有的图像类型(*图 12.11)。请注意,我们只会使用数据集的 1%来证明其实你并不需要那么多数据就能从预训练模型中获得很大的收益!

# Import necessary libraries
from datasets import load_dataset
from transformers import ViTImageProcessor, ViTForImageClassification
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import torch
from torchvision.transforms.functional import to_pil_image
# Load the CIFAR10 dataset using Hugging Face datasets
# Load only the first 1% of the train and test sets
train_dataset = load_dataset("cifar10", split="train[:1%]")
test_dataset = load_dataset("cifar10", split="test[:1%]")
# Define the feature extractor
feature_extractor = ViTImageProcessor.from_pretrained('google/vit-base-patch16-224')
# Preprocess the data
def transform(examples):
    # print(examples)
    # Convert to list of PIL Images
    examples['pixel_values'] = feature_extractor(images=examples["img"], return_tensors="pt")["pixel_values"]
    return examples
# Apply the transformations
train_dataset = train_dataset.map(
transform, batched=True, batch_size=32
).with_format('pt')
test_dataset = test_dataset.map(
transform, batched=True, batch_size=32
).with_format('pt')

我们可以类似地使用以下代码来使用模型:

图 12.11 – 来自 CIFAR10 的单一示例,显示一架飞机

图 12.11 – 来自 CIFAR10 的单一示例,显示一架飞机

现在,我们可以开始训练我们预训练的 Vision Transformer:

# Define the model
model = ViTForImageClassification.from_pretrained(
'google/vit-base-patch16-224',
num_labels=10, ignore_mismatched_sizes=True
)
LABELS = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
model.config.id2label = LABELS
# Define a function for computing metrics
def compute_metrics(p):
    predictions, labels = p
    preds = np.argmax(predictions, axis=1)
    return {"accuracy": accuracy_score(labels, preds)}
# Define the training arguments
training_args = TrainingArguments(
    output_dir='./results',
    num_train_epochs=5,
    per_device_train_batch_size=4,
    load_best_model_at_end=True,
    # Save and evaluate at the end of each epoch
    evaluation_strategy='epoch',
    save_strategy='epoch'
)
# Define the trainer
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=test_dataset
)

我们最终的模型在测试集的 1%上达到了大约 95%的准确率。现在我们可以在未*过的图像上使用我们的新分类器,如下一个代码块所示:

from PIL import Image
from transformers import pipeline
# Define an image classification pipeline
classification_pipeline = pipeline(
'image-classification',
model=model,
feature_extractor=feature_extractor
)
# Load an image
image = Image.open('stock_image_plane.jpg')
# Use the pipeline to classify the image
result = classification_pipeline(image)

图 12.12 显示了这次单一分类的结果,看起来效果相当不错:

图 12.12 – 我们的分类器正确预测了一张飞机的库存图像

图 12.12 – 我们的分类器正确预测了一张飞机的库存图像

在最小化标注数据的情况下,我们可以利用迁移学*(TL)将现成的模型转化为强大的预测模型。

总结

我们对预训练模型的探索让我们了解了这些经过大量数据和时间训练的模型是如何为我们提供坚实的基础,以供进一步开发的。它们帮助我们克服了计算资源和数据可用性相关的限制。特别是,我们熟悉了像 VGG16 和 ResNet 这样的图像模型,以及像 BERT 和 GPT 这样的文本模型,进一步丰富了我们的工具库。

我们的探索继续进入了迁移学*(TL)领域,在那里我们学*了其基本原理,认识到其多样化的应用,并了解了它的不同形式——归纳式、传导式和无监督式。每种类型都有其独特的特性,为我们的机器学*工具箱增添了不同的维度。通过实际的例子,我们看到了这些概念的实际应用,使用 BERT 模型进行文本分类,使用 Vision Transformer 进行图像分类。

但是,正如我们已经认识到的那样,虽然迁移学*和预训练模型很强大,但并不是所有数据科学问题的解决方案。它们在合适的情况下大放异彩,而作为数据科学家的我们,需要判断何时以及如何有效地部署这些强大的方法。

随着本章的结束,我们对迁移学*(TL)和预训练模型的理解,不仅使我们能够处理实际的机器学*(ML)任务,还为我们应对更高级的概念和技术铺*了道路。通过在我们的机器学*过程中引入更多步骤(如预训练),我们也在为更多潜在的错误敞开了大门。下一章将开始探讨迁移学*的一个隐藏面:偏差传递和漂移应对。

向前看,可以考虑深入研究其他预训练模型,探索迁移学*的其他变种,甚至尝试创建你自己的预训练模型。无论你选择哪条路径,记住——机器学*的世界广阔且不断发展。保持好奇,持续学*!

第十三章:缓解算法偏*与应对模型和数据漂移

如果你在机器学*ML)和数据科学的领域中进行探索,你将面临一些挑战。你可以预*到两个主要挑战:算法偏*模型与数据漂移。它们就像是突如其来的考试题目——你可能没有准备,但你最好提前准备好应对它们。

算法偏*可能悄悄地进入我们的模型,而一旦发生,这可不是好事。它会导致不公*的结果,说实话,这样的结果并不酷。但别担心——我们将直面这一问题,并讨论如何缓解它。

即使我们考虑到偏*,随着时间的推移,也可能会发生变化,使我们的模型不再准确。这就像你最喜欢的衬衫在洗涤后缩水——它不是衬衫的问题,但它已经不再像以前那么合适了。我们的模型也一样。它们可能在最初创建时是完美的,但随着数据的变化,它们可能需要一些调整。

本章将深入探讨这些重要问题。我们将研究算法偏*的来源以及如何尽量减少它。我们还将深入探讨模型漂移和数据漂移的概念,并讨论一些应对策略。

本章将涵盖以下内容:

  • 理解算法偏*

  • 算法偏*的来源

  • 衡量偏*

  • 未解决的偏*的后果与公*性的重要性

  • 缓解算法偏*

  • 大型语言模型LLMs)中的偏*

  • 机器学*中偏*与公*性的最新技术

  • 理解模型漂移与衰退

  • 缓解漂移

为了让内容更加生动,我们还将看看算法偏*如何出现在大型语言模型(LLMs)中。等我们讨论完这些,你将对这些挑战有一个坚实的理解,并准备好在自己的项目中应对它们。那么,卷起袖子,开始吧!

理解算法偏*

算法偏*是机器学*领域中的一个关键问题。当一个系统无论是有意还是无意地产生了不公*或系统性地对某些个人或群体产生偏*的输出时,就会发生算法偏*。这种偏*通常源于这些系统从现有数据中学*,而这些数据本身可能充满了固有的社会偏*。

公*性在机器学*中的定义是没有任何偏*。尽管听起来简单,实现公*性却是一个复杂的过程,需要在每一步模型创建中小心管理。

为了更详细地说明,我们来看看受保护的特征。这些是可能会在系统中引入偏*的属性。它们可能是法律要求的,如种族和性别,或者来自组织价值观,如位置或邮政编码。虽然这些特征看起来无害,但当它们在机器学*模型中使用时,可能会导致偏*或歧视的决策。

深入探讨,我们发现了两种主要的算法偏*类型:

  • 不同影响发生在模型明确依赖保护特征时,偏袒某一群体而非其他群体。

  • 相对而言,不同待遇是指当模型通过相关变量隐式使用保护特征时,从而间接导致偏差的结果。

不同待遇的一个典型例子是某人的邮政编码,这个区域可能主要包含某一特定种族或社会经济地位的人群。这可能导致偏差的预测或决策,无意中偏袒或歧视该群体。类似地,像是否曾被逮捕过这样的变量也可能引入偏*,因为某些群体由于社会偏*历史上遭遇更多逮捕。

解决偏*的初步方法是“无知”,即删除任何对保护特征的明确提及。然而,这种方法在解决偏*方面标准较低,因为它没有考虑到即便如此,仍然可能发生不同的影响。

公*也可以通过统计度量方法来处理,比如统计*衡和均衡机会。统计*衡表示模型的预测应与任何给定的敏感特征无关。例如,一个预测再犯的模型应该做到无论种族如何,都有相同的预测结果。然而,这种方法忽略了标签与敏感属性之间的实际关系,可能会导致偏*结果。

在这里,我们有个人公*和群体公*。前者确保相似的个体有相似的结果,而后者则坚持跨群体根据保护性特征进行*等的统计结果。每种方式对公*及其实现有自己独特的视角。

另一方面,均衡机会提出模型的预测应该在响应值条件下,与任何敏感特征无关。这种方法鼓励模型在所有群体中都更为准确,但可能忽视了可能导致某些群体更频繁地落入某个标签的更大社会经济原因。

通过理解算法偏*的细微差别,我们可以更好地制定策略来防止、识别和缓解机器学*模型中的偏*。

偏*的类型

理解机器学*中的偏*需要我们根据其来源和表现形式将其分类。这有助于我们识别根本原因,并实施有针对性的缓解策略。以下是需要考虑的主要偏*类型:

  • 不同影响:不同影响是指当一个机器学*系统的结果对某一特定群体(通常是由种族、性别、年龄或宗教等属性定义的保护类群体)不成比例地造成不利影响时的情况。这通常发生在即使保护特征没有明确包含在模型中时。

  • 差别待遇:与差别影响不同,差别待遇发生在机器学*模型根据个体是否属于保护群体而区别对待他们,这意味着存在直接的歧视性影响。当保护属性在决策过程中被明确使用时,就会发生这种情况。

  • 先前存在的偏差:也称为历史偏差,先前存在的偏差出现在用于训练机器学*模型的数据反映了现有的偏*或社会偏差时。模型本质上学*了这些偏*,并在其预测中传播这些偏*。

  • 样本偏差:样本偏差发生在用于训练模型的数据未能代表其应服务的群体时。这可能导致模型在训练数据上表现良好,但在生产中实际遇到的数据上表现不佳,导致不公*的结果。

  • 测量偏差:测量偏差发生在数据收集或测量过程中存在系统性误差时。这可能会扭曲训练数据,并导致模型学*到不正确的关联。

  • 聚合偏差:聚合偏差发生在模型过度简化或未能捕捉群体内的多样性和细微差别时。当不同的子群体被视为同质化群体时,可能导致模型做出不正确或不公*的概括。

  • 代理偏差:代理偏差发生在模型使用某一属性作为保护属性的替代品时。例如,邮政编码可能被用作种族或收入水*的代理,这会导致偏*的结果,即使没有直接使用保护属性。

这些偏差类型对机器学*中的公*性有不同的影响,需要不同的策略来检测和缓解。通过识别存在的偏差类型,我们可以采取针对性的措施来减少其影响,并朝着更加公*和公正的机器学*系统迈进。

算法偏差的来源

基于过去数据学*的机器学*模型,可能会无意中传播训练数据集中存在的偏*。认识到这些偏*的根源是朝着更公*模型迈出的关键第一步。

  • 其中一个来源是历史偏差。这种偏差反映了社会中现存的偏*和系统性不*等。例如,基于公司的过去招聘数据训练的招聘模型。如果该组织在历史上偏爱某一特定群体担任某些职位,模型可能会复制这些偏*,延续偏*的循环。

  • 代表性或样本偏差是另一个重要因素。它发生在某些群体在训练数据中被过度或不足表示时。例如,主要使用浅色皮肤个体的图像来训练面部识别模型,可能导致该模型在识别深色皮肤的面孔时表现不佳,偏向某一群体。

  • 代理偏*是指模型使用来自不相关领域的数据作为输入,从而导致偏*的结果。例如,使用逮捕记录预测犯罪率,这可能引入偏*,因为逮捕数据可能受到执法系统中的系统性偏*的影响。

  • 聚合偏*是指数据的不恰当分组,简化任务的同时牺牲了准确性。一个例子是使用*均血红蛋白水*来预测糖尿病,尽管这些水*在不同种族之间因更复杂的因素而有所不同。

理解这些算法偏*的来源是我们构建防止和缓解偏*策略的基础。因此,我们为开发更加公*、公正和包容的人工智能系统做出了贡献。

测量偏*

要成功地应对偏*,我们必须首先衡量其存在,并了解它对机器学*模型的影响。为此,已经开发出几种统计方法和技术,每种方法都从不同的角度来考察偏*和公*性。以下是几种基本方法:

  • 混淆矩阵:这是评估机器学*模型表现的基础工具,混淆矩阵也可以揭示偏*。它允许我们衡量假阳性和假阴性率,这可以帮助我们识别模型在不同群体中表现不同的情况。

  • 不*等影响分析:这一技术衡量保护群体与非保护群体之间有利结果的比率。如果这个比率显著低于 1,意味着保护群体受到不*等的影响,提示潜在的偏*。

  • 机会*等:这种方法要求模型在不同群体之间的错误率相等。换句话说,如果模型出现错误,那么错误发生的几率应该是相同的,无论个体所属的群体如何。

  • 机会均等:这是机会*等的变体,要求各组之间的真正正例率相等。这意味着所有应该获得正面结果的个体(根据真实情况),无论其所属群体如何,都有相同的机会发生这种情况。

  • 反事实分析:这一高级技术涉及设想一个场景,其中个体的群体身份发生变化(即反事实场景),并观察模型的决策是否发生变化。如果发生变化,这可能是偏*的一个信号。

  • 通过意识实现公*:这种方法承认个体之间的差异,并要求在决策过程中考虑这些差异。它要求相似的个体,无论其所属群体如何,都应受到类似的对待。

这些方法提供了衡量偏*和实现公*的不同视角。然而,重要的是要注意,公*是一个多维的概念,什么被认为是公*的可能会根据情境而有所不同。因此,必须将这些措施视为帮助我们走向更公*使用机器学*的工具,而不是将它们看作是解决偏*的终极方案。

未解决的偏*的后果以及公*性的重要性

曾经遭遇过不公*对待吗?记得那种感觉吗?现在,想象一下这种情况反复发生,一次又一次,都是由于一个机器学*模型。这可不是什么美好的画面,对吧?这正是当偏*在人工智能系统中得不到解决时发生的事情。

想象一个在偏斜数据集上训练的招聘算法。它可能会持续筛选掉少数群体的潜在候选人,从而导致不公*的招聘实践。或者,假设一个信用评分算法特别偏爱某个特定的邮政编码,使得其他地区的居民更难获得贷款。这不公*,对吧?

偏*的这些现实世界影响可能会严重侵蚀人们对人工智能/机器学*系统的信任。如果用户觉得一个系统持续歧视他们,他们可能会失去对其决策的信任。说实话——没有人愿意使用他们认为有偏*的工具。

而且,这不仅仅是信任的问题。这其中有严重的伦理问题。未解决的偏*可能对边缘化群体产生不成比例的负面影响,扩大社会差距,而不是弥合差距。这就像是将梯子放得远离那些最需要它的人。

这引出了公*性的重要性。确保机器学*中的公*性不仅仅是“锦上添花”,它是“必须具备”的。一种公*的算法不仅更有可能赢得用户的信任,而且在实现伦理结果方面也发挥着至关重要的作用。

想一想。公*的算法有可能拉*竞争环境,确保每个人都能公*地获得机会,无论他们的背景或身份如何。它们可以帮助构建一个更加公*的社会,一次一个决策。毕竟,这难道不是技术应该追求的目标吗?让我们的世界不仅更加高效,而且更加公*?

这就是为什么机器学*中的公*性如此重要。它不仅关乎技术,更关乎它服务的人们。那么,让我们在下一节中看看一些减轻偏*的策略吧?

减轻算法偏*

即使在理解和衡量机器学*中的偏*之后,工作也只是完成了一半。接下来的逻辑步骤是实施减轻偏*的策略。各种技术存在,每种都有其优缺点,通常这些策略的组合能够获得最佳效果。以下是一些最有效的方法:

  • 预处理技术:这些技术涉及在将数据输入机器学*模型之前对数据进行修改。它们可能包括诸如重新采样以纠正数据不*衡,或通过重新加权数据中的实例来减少偏差等技术。

  • 处理中的技术:这些技术在训练过程中修改模型本身,以减少偏差。它们可能涉及正则化技术、成本敏感学*或其他形式的算法调整,以最小化偏差。

  • 后处理技术:这些技术在模型训练完成后应用。它们可能包括根据预测的敏感性调整输出,或调整模型的阈值以确保公*的结果。

  • 通过无意识实现公*性:该方法提出,去除数据集中的敏感属性(如种族或性别)可以产生公*的模型。然而,这种方法往往过于简单,忽视了数据中存在的更深层次的系统性偏差。

  • 通过意识实现公*性:与前面的方法不同,这种方法建议将敏感属性直接纳入模型中,以受控的方式对抗偏差。

  • 对抗性去偏:这种新颖的方法将偏差视为一种噪声,试图通过对抗网络去除这种噪声。

实施这些方法将依赖于数据的性质、模型和应用的背景。偏差缓解不是一种“放之四海而皆准”的解决方案,必须对每个具体情况进行仔细考虑。然而,前述技术可以在促进公*性和减少机器学*模型中的有害偏差方面发挥重要作用。

在数据预处理阶段进行缓解

我们都听过“垃圾进,垃圾出”这句话,对吧?在机器学*中也是如此。我们喂给模型什么,它的输出就是什么。如果我们给它喂入带有偏差的数据,你也能猜到结果是什么。

我们应对偏差的第一道防线是在数据预处理阶段。在这里,我们必须戴上侦探帽,开始调查可能潜藏在数据中的偏差。例如,假设我们正在处理一个医疗保健算法。如果我们的数据样本过度代表某一特定人群,我们可能会导致算法偏向该群体,就像一个只想吃薯条的幼儿一样!

一旦我们识别出这些偏差,就该做一次“大扫除”了。技术如过采样、欠采样,或使用合成少数类过采样技术SMOTE)可以帮助我们为模型构建一个更加*衡的训练集。在我们的案例研究章节中,我们会详细介绍带有偏差缓解的预处理完整实例。

在模型处理中的缓解

所以,我们已经尽力清理了数据,但在训练模型时怎么办呢?我们能在这方面做些什么吗?当然可以!

模型的处理中阶段允许我们将公*性直接融入训练过程。这有点像在菜肴烹饪时加入香料,确保香味渗透整个菜肴。

我们可以在训练过程中使用算法公*性约束,以确保我们的模型遵守公*原则。例如,以贷款批准算法为例,我们可以引入公*性约束,确保不同群体之间的批准率相似,就像确保每个人都能分到一片相同大小的比萨饼。

或者,我们可以使用公*正则化方法,将公*作为一种“辛辣”成分引入损失函数。这可以帮助我们在准确性和公*性之间找到*衡,防止模型偏袒数据集中的多数群体,就像避免做一道只有少数客人喜欢的辛辣菜肴。

最后,我们可以使用对抗性去偏*方法,在这种方法中,我们训练一个对抗性网络来学*公*的表示。这就像有一个小助手,确保我们在烹饪模型时不会过度使用某种特定的成分(比如我们的敏感属性)。

模型后处理中的缓解

好的——所以我们已经准备好了数据,并且在烹饪过程中小心翼翼,但菜肴做好后呢?我们还能做些什么吗?当然可以!

就像我们在品尝后可能会调整菜肴的调味一样,我们可以在模型训练完成后对其进行校准。这可以确保我们的模型预测概率在不同的群体之间都保持均衡。

如果我们发现模型对某个少数群体的评分 consistently 较低,我们可以为该群体调整决策阈值。这就像在意识到某些参与者的高跳栏过高时,将其调低。

此外,我们还可以使用公*感知的集成方法。这就像是一群厨师,每个厨师专注于一道菜的不同部分,从而确保整个用餐体验既均衡又公*。

大规模语言模型中的偏*

在人工智能领域,我们*证了大规模语言模型(LLMs)部署的爆炸性增长,嘿——为什么不呢?这些庞然大物,比如 GPT-3 或 BERT,能够完成一些令人瞠目结舌的任务,从写出通顺的邮件到创作几乎像人类一样的文本。令人印象深刻,不是吗?但让我们先停下来想一想。就像每枚硬币都有两面,这些模型也有它们不那么光彩的一面——偏*。

是的——你没听错。这些模型并不是免疫于偏*的。事实是,这些模型从它们训练所用的数据中学*一切。如果这些数据本身有偏*(不幸的是,这种情况常常发生),那么模型的输出也可能带有偏*。可以这样理解:如果模型是基于大部分带有性别歧视或种族歧视的文本进行训练的,它可能会生成反映这些偏*的内容。想一想这有多不舒服,是吧?

而这不仅仅是一个假设情境。已经有实例表明,基于这些模型的 AI 应用最终引发了严重的问题。还记得当 AI 系统被发现做出不公正决策,或者聊天机器人开始发布仇恨言论时吗?那正是训练数据中的偏*渗透到 AI 应用中的直接结果。

看看最近的一些研究,你会发现它们中充满了关于大型语言模型(LLMs)偏*的例子。就像在大海捞针,但那根针是带有磁性的。亲爱的朋友们,偏*在这些模型中比我们想象的更为普遍。

简而言之,LLMs 中的偏*是真实存在的,我们应该认真对待这一问题。敬请关注我们将进一步探讨这一问题。让我们来看一个例子。

揭示 GPT-2 中的偏*

像 GPT-2 这样的 LLM 已经革新了 transformers 库,它们是在来自各种互联网来源的数据上进行训练的,包括 Reddit,一个以多元化的观点和讨论著称的*台,但也因虚假信息和潜在的偏*而闻名。此实验旨在展示 LLM 在基于特定提示生成文本时可能展现出的偏*。

让我们设置一些实验代码。我想向 GPT-2 提问几个问题,期望它的回答是一个列表,并观察它的回答内容以及回答的频率。以下代码块创建了一个函数,用于提问并统计响应中用逗号分隔的列表元素的数量:

from tqdm import tqdm
import pandas as pd
from transformers import pipeline, set_seed
generator = pipeline('text-generation', model='gpt2-large', tokenizer='gpt2-large')
set_seed(0)

让我们更详细地看一下:

  1. 管道(pipeline)是一个高层次、易于使用的 API,用于对变换器(transformer)模型进行推理。set_seed 函数用于设置生成随机数的种子。

  2. 接下来,我们创建 text-generation 管道的实例。我们为文本生成设置一个管道,指定 GPT-2 模型和分词器。之所以选择 GPT-2 模型,是因为它已在大量文本语料库上进行训练,能够生成类似人类的文本。

  3. 然后,我们为随机数生成器设置种子。设置种子的目的是确保结果的可重复性。当种子被设置为特定数字时,每次运行此脚本生成的序列将是相同的。

  4. 最后,我们使用生成器来生成文本。生成器接收一个提示,并返回一个响应:

    1. 每次调用时,它会生成多个不同的文本续写(由 num_return_sequences 参数控制)。

    2. max_length 参数限制了生成文本的总长度为 10 个标记。

    3. 温度设置为 1.0,它影响输出的随机性(较高的值使输出更随机,较低的值使其更具确定性)。

    图 13.1 展示了针对一些提示的顶级结果:

图 13.1 – 向 GPT-2 提问男女从事何种职业的差异

图 13.1 – 向 GPT-2 提问男女从事何种职业的差异

这些输出清楚地表明语言模型的输出可能存在偏*。某些生成的句子可能被视为负面的或刻板印象的,表明 LLMs 可能存在偏*的潜力。因此,管理并意识到这些偏*至关重要,尤其是在使用模型的输出处理任何敏感任务时。

语言模型如 GPT-2 是通过大量文本数据进行训练的。它们通过预测给定前文上下文的情况下下一句话中的词来生成文本。这一学*过程不包含关于陈述事实或道德的信息;它只是从其训练数据中学*模式。

这些模型中的偏*来源于它们所训练数据的性质。以 GPT-2 为例,它的大部分训练数据来自于像 Reddit 这样的网页。虽然 Reddit 可以是一个丰富的多元观点和讨论的来源,但它也是一个包含各种内容的*台,包括错误信息、刻板印象和歧视性语言。

所以,当模型在这样的数据上进行训练时,它可能会学*并复制这些偏*。例如,在我们提供的代码中,某些生成的句子可能被视为在宣传刻板印象或错误信息。这表明模型可能从其训练数据中存在的偏*中学*到了这些模式。

这具有严重的影响。例如,如果大型语言模型(LLMs)被应用于涉及做出影响人们生活决策的场合,比如招聘或贷款审批,那么模型预测中的任何偏*都可能导致不公*的结果。

因此,解决这些偏*是部署 LLMs 中的一个重大挑战。可能的解决方案包括对训练数据的精心挑选、在训练过程中应用偏*缓解技术,或对模型输出进行后处理。此外,理解并向用户传达模型可能存在的偏*也是负责任地部署 AI 的重要组成部分。

机器学*中偏*和公*性的新兴技术

在技术领域,有一件事是确定的——它永远不会停滞不前。机器学*也不例外。追求公*性和解决偏*的需求催生了一些创新的、具有革命性的技术。所以,戴上你们的科技帽子,让我们深入探讨这些开创性的进展。

首先,让我们来谈谈可解释性。在复杂的机器学*模型逐渐成为常态的时代,可解释性模型如同一股清新的空气。它们具有透明性,更容易理解,且能够让我们深入了解它们的决策过程。像局部可解释模型无关解释LIME)和夏普莱加性解释SHAP)这样的技术在这一领域中引领潮流。它们不仅揭示了模型决策的“如何”和“为什么”,还帮助识别潜藏的偏*。我们将在下一章通过一些代码示例进一步讨论 LIME!

接下来是反事实解释的兴起。这一部分是关于理解什么因素能够改变算法对某个特定个体的决策。例如,哪些变化能够将贷款拒绝转变为批准?这些解释能够帮助识别潜在的偏*领域,也能让这些复杂的系统变得更具亲和力,便于服务于人们。

在公*性度量领域,变化的风潮也在吹起。传统的统计*等关注点正在让位于更细致的度量标准,如群体公*性、个体公*性和反事实公*性。这些度量旨在确保类似的个体被以相似的方式对待,同时也考虑到假设场景可能带来的影响。

最后,关于公*意识算法的兴趣日益增长。这些算法并非普通的机器学*模型。它们被设计用于主动减少偏*。例如,学*公*表示LFR)就是这样一种方法,它尝试学*对特征进行转化,去除偏*,确保模型做出的决策是公*的。

所有这些进展都证明了该领域致力于使 AI/ML 系统更加公*、减少偏*的决心。但请记住——技术的好坏取决于我们如何使用它。因此,让我们继续利用这些工具构建不仅智能而且公*的模型。毕竟,这不正是我们追求的真正目标吗?

理解模型漂移和衰退

就像一条随着时间改变流向的河流一样,机器学*中的模型也可能会出现漂移和衰退。你可能会想,这是什么意思?让我们来深入探讨一下。模型漂移是指当我们的机器学*模型因其训练数据的变化或问题领域本身的变化而导致性能下降时的情况。

正如我们所知,机器学*模型并非一成不变。它们设计用于适应并从新信息中学*。然而,当输入数据的动态变化或最初识别的模式发生改变时,我们的模型可能无法迅速适应。这时,我们就遇到了模型漂移的问题。

模型漂移

现在,我们需要了解几种类型的模型漂移。每一种都揭示了模型可能失败的不同情形:

  • 第一种类型是概念漂移。想象一下一个情感分析SA)算法。随着时间的推移,人们使用某些词汇或短语的方式可能发生变化。俚语不断演变,文化背景也在变化,而算法可能会开始误解情感,因为它没有跟上这些变化。这就是概念漂移的经典案例。

  • 接下来,我们有预测漂移。假设你有一个处理客户查询的聊天机器人。由于某个不可预*的事件,比如暂时的系统故障,你的聊天机器人突然接收到大量类似的查询。你的模型的预测开始偏向于这个特定的问题,导致了预测漂移。

总结来说,模型漂移是一个挑战,提醒我们数据和用户行为的不断变化。在我们应对这些变化时,了解漂移的类型就像是我们的指南针,帮助我们保持模型的性能和准确性。接下来,让我们深入探讨另一种漂移——数据漂移。

数据漂移

数据漂移,有时也称为特征漂移,是另一个我们需要关注的现象。假设你的机器学*模型是一艘船,而它训练所用的数据就是大海。正如我们所知道的,大海并不是静止的——海流变化,潮汐涨落,甚至可能出现新的岛屿。就像一位经验丰富的船长能够驾驭这些变化,我们的模型也需要适应数据变化的海流。

那么,数据漂移到底意味着什么呢?本质上,它是指模型输入数据分布的变化。例如,考虑一个电子商务推荐系统。假设推出了一款新产品,并且它迅速成为消费者中的热门商品。人们开始在评论和反馈中使用一个新词来指代这个产品。如果你的模型没有适应,不能将这个新词纳入理解,那么它就会错过当前客户情绪和偏好的一个重要方面——典型的数据漂移。

数据漂移提醒我们,模型所运作的世界并不是静态的。趋势会出现,客户行为会变化,新的信息也会变得相关。因此,我们的模型必须保持敏捷,能够响应这些变化。

另一种漂移是标签漂移,这指的是实际标签分布发生变化。再以客户服务机器人为例。如果客户行为发生变化,比如从询问退货转变为询问退货状态,数据中的标签分布就会发生变化,导致标签漂移。

既然我们已经揭开了模型漂移和数据漂移的谜团,让我们深入探讨它们的各种来源,以便更好地理解和减轻它们的影响。当我们在机器学*的背景下谈论漂移时,通常会区分两种主要的来源:模型漂移和数据漂移。这有点像考虑一道菜味道变化的来源——是食材变了,还是厨师的技巧改变了?

模型漂移的来源

模型漂移发生在模型的基本假设发生变化时。这就像厨师改变了他们的烹饪技巧。也许烤箱的温度发生了变化,或者烘焙时间被修改了。在机器学*领域,这可能是由于模型部署环境的变化。例如,一个交通预测模型可能在一条主要道路修建前进行训练,修建完成后,交通模式发生了变化,导致模型漂移,因为原有的假设不再成立。

数据漂移的来源

数据漂移则是由模型输入的统计特性随时间变化所驱动的。这就像是我们菜肴中的食材发生了变化。例如,如果我们常用的季节性水果不再供应,而我们必须替换它,那么菜肴的味道可能会偏离原本的风味。在机器学*领域,一个例子可能是情感分析(SA)模型未能考虑到新俚语或表情符号的出现,导致模型分析的数据发生漂移。

了解漂移的来源至关重要,因为这可以帮助我们制定监控和缓解这些变化的策略,确保我们的模型在快速变化的现实世界中保持新鲜和相关性。在接下来的部分,我们将探讨一些应对这些漂移来源的策略。

缓解漂移

机器学*的世界不断发展,使我们必须保持适应性。我们已经看到,漂移的概念对于理解数据或模型随时间变化至关重要。那么,当我们面临这些不断变化的环境时,我们该怎么办呢?难道我们只能眼睁睁地看着模型性能的衰退吗?并非如此。本部分将介绍可操作的策略,以缓解漂移,每个策略在我们有效管理漂移的工具箱中都有其独特的位置。

理解背景

在深入探讨缓解漂移的技术细节之前,让我们先认识到理解模型运行环境的必要性。就像船长需要了解海洋和天气状况一样,我们需要理解我们的数据来源、用户行为、环境变化以及所有其他构成模型运行背景的细节。

以电子商务推荐系统为例。理解背景意味着要了解季节性趋势、正在进行的促销活动,或者任何可能影响顾客行为的全球事件。例如,在全球体育赛事期间,可能会有大量与体育相关的购买行为。了解这些背景信息有助于我们预*漂移,并准备好使模型适应变化。

持续监控

知识不付诸实践是徒劳的。我们一旦熟悉了我们的环境,下一步就是密切关注模型的表现。我们需要持续监控模型和数据的“心跳”。这可以通过跟踪模型表现指标随时间的变化,或使用统计测试来识别数据分布的显著变化来实现。

以一个信用评分模型为例。如果我们注意到信用违约数量突然激增,这可能意味着存在一个需要我们关注的漂移。实时更新的仪表板等监控系统在捕捉这些变化之前发挥着重要作用,防止它们演变成更严重的问题。

定期模型重新训练

停滞是进步的敌人。随着周围世界的变化,我们的模型需要通过学*新数据来保持同步。定期重新训练模型可以帮助它跟上最新的趋势和模式。我们应该多频繁地重新训练模型呢?嗯,这取决于我们数据或环境变化的速度。在某些情况下,可能需要每隔几个月重新训练,而在其他情况下,可能需要每隔几天就重新训练一次。

假设有一个模型预测股市趋势。鉴于市场的波动性,该模型可能受益于每天甚至每小时的重新训练。相反,预测房价的模型可能只需要每半年重新训练一次,因为房地产市场相对稳定。

实施反馈系统

反馈不仅仅适用于亚马逊评论或工作坊后的调查。在机器学*(ML)的世界里,反馈系统可以作为我们对现实的检查工具。它们可以帮助我们了解模型的预测是否与不断变化的现实世界保持一致。反馈可以来自各种来源,例如用户标记错误的预测或对模型输出的自动检查。

假设我们已经为社交媒体帖子中的情感分析(SA)部署了一个机器学*模型。我们可以建立一个反馈系统,让用户报告当模型错误标记他们帖子情感时。这些信息可以帮助我们识别语言使用的漂移,并相应地更新我们的模型。

模型适应技术

我们现在进入了漂移缓解的更高级领域。技术如在线学*,模型从数据流中逐步学*,或者漂移检测算法,当数据分布发生显著偏移时发出警报,都可以自动调整模型以缓解漂移。

尽管这些技术看起来非常有吸引力,但它们也伴随着一些挑战,包括计算成本和对持续数据流的需求。它们就像我们工具箱中的高科技设备——强大,但需要专家操作。例如,一个股票市场的算法交易可能采用在线学*,立即响应市场趋势,展示了在适当利用时,模型适应技术的强大能力。

总结来说,减缓漂移并不是一种放之四海而皆准的解决方案。它是一个分层的方法,每个策略都扮演着至关重要的角色,就像时钟中的齿轮一样。理解背景为此奠定了基础,持续监控让我们保持警觉,定期再训练确保我们的模型保持相关性,反馈系统提供现实检查,而模型适应技术则允许自动调整。关键在于理解何时实施哪些策略,这使我们能够确保模型在面对不断变化的漂移时依然具有持久性。

概述

在不断发展的机器学*领域,面对算法偏*和模型/数据漂移的双重挑战,不仅仅是解决眼前的问题,更是要建立持久的实践。本章中列出的策略是朝着更公*和更具适应性的机器学*模型迈出的关键一步。它们是保持警觉和适应性的重要体现,确保人工智能在面对数据动态变化时的完整性和适用性。

当我们从解决偏*和漂移的课题转向 AI 治理的广阔领域时,我们进入了一个全新的阶段。下一章将重点讨论如何构建强健的治理机制,这些机制不仅仅是对问题的反应,更是积极地塑造人工智能系统的开发和部署。治理原则——包括数据的管理、机器学*的责任和架构的战略监管——不仅仅是战术要素;它们是道德、可持续和高效的人工智能部署的支柱。

第十四章:人工智能治理

在我们当前所处的技术时代,颠覆性创新在很大程度上由数据、分析和人工智能推动。这些因素正在各个领域开辟新的道路,创造新的收入来源,并重新定义企业管理范式。考虑麦肯锡公司(McKinsey & Co.)的预测,到 2030 年,仅仅是通过分析和人工智能,商业价值将增加超过 15 万亿美元。在认识到这一黄金矿藏后,全球各大组织正在积极投入资源,力求在这一数据驱动的领域占据一席之地。2022 年,NewVantage Partners 的调查支持这一观点,数据显示,惊人的 97%受访企业正在加大对数据和人工智能项目的投资。

然而,这里出现了一个显著的悖论。尽管资本大量涌入,大多数组织仍在从其数据努力中提取实际效益方面挣扎。那么,阻碍到底是什么呢?核心问题通常在于缺乏一个结构良好、可操作的数据治理蓝图,涵盖了从商业智能到机器学*等各类数据应用。Gartner 的分析提供了更加严峻的前景。他们推测,到 2025 年,约 80%的企业在扩大数字化足迹时可能会失败,主要原因是它们对数据分析治理的陈旧方法。

在数据治理领域航行至关重要,但对于许多进入数字化转型之路的企业来说,它却是一片错综复杂的迷宫。数据团队常常发现自己在以下领域徘徊:

  • 协调来自不同来源的大量数据,并拆除根深蒂固的数据孤岛

  • 保持无懈可击的数据标准

  • 确保可靠的数据既可以访问,又可以轻松找到

  • 战略性地控制数据访问

  • 实现数据利用和消费模式的透明性

  • 促进内部和外部的安全数据交换

  • 监督机器学*过程

  • 遵守严格的监管要求

精明的数据治理蓝图是解开这些复杂局面的关键,帮助组织真正利用其数据资产的潜力。

在这个数字化时代,每一个操作环节都与技术紧密相连,全面理解治理变得不可或缺。在数字化转型领域中,治理的叙述不仅限于数据,还涵盖了三个关键维度:数据治理机器学*ML治理架构治理。每个维度都有其独特的分量,但它们共同构成一个全面的框架,确保组织充分发挥技术资产的最大潜力。

  • 数据治理:这关注数据在整个生命周期中的管理和质量。它确保数据是可信、可靠和可用的。随着数据来源的激增和数据量的庞大,制定明确的数据采集、存储、访问和处置协议变得至关重要。这种治理形式奠定了基础,确保数据的完整性、安全性和可用性。

  • ML 治理:这涉及与 AI 和 ML 模型部署与运行相关的具体挑战。鉴于 ML 在现代商业流程中的变革性力量,确保这些模型透明、道德并在预定义的边界内运行至关重要。ML 治理涵盖模型的开发、部署、监控和持续改进,确保它们既有效又符合道德标准。

  • 架构治理:这确保 IT 环境与业务目标的良好对接。拥有正确的数据或 ML 模型是一回事,但确保底层基础设施、应用程序及其之间的互联互通得以最佳设计和运行则是另一关键因素。这确保了数字生态系统中的可扩展性、弹性和效率。

那么,为什么必须将这三者一起讨论呢?简单来说,它们是密切相关的。数据是 ML 模型构建的基础,架构则是数据和 ML 模型所处的环境。忽视其中一个方面可能会影响其他方面的效果。例如,没有适当的数据治理,即使是最复杂的 ML 模型也可能产生不可靠的结果。同样,没有强健的架构治理,数据可能会存储得低效或不安全,从而影响标准分析和高级 ML 操作。

这三大治理支柱共同构建了一个紧密而全面的战略;同时处理这三者确保组织的运作既*稳又符合道德和效率,将数字化转型的承诺转化为可触及的现实。

让我们回顾一下本章将涵盖的主题:

  • COMPAS 数据集案例研究介绍

  • 使用预训练模型和 OpenAI 进行文本嵌入

掌握数据治理

对于无数组织来说,数据是一笔宝贵的财富。然而,正是数据治理作为指南针,引导着我们挖掘数据的真正价值。可以将数据治理视为一套综合的原则、方法和工具,旨在监管整个数据生命周期,确保其与更广泛的业务路线图保持一致。一份精心设计的数据治理蓝图为数据团队提供了无与伦比的数据管理能力、透明性,以及跨企业的数据交互审计轨迹。实施强有力的数据治理体制不仅能够保护数据免受未授权访问,还能根据监管基准建立合规协议。通过巧妙地运用数据治理策略,众多实体已经利用这一方法获得了关键的竞争优势,增强了客户信任,强化了数据和隐私规范,并保护了其宝贵的数据资源。

当前数据治理中的难题

在今天这个时代,制定完美的数据治理策略就像是在迷宫中穿行,尤其是在现代数据收集和分析动态的背景下。随着公司积累大量的数据,这些数据主要是非结构化的,绝大部分最终会存储在基于云的数据湖中,如 AWS S3、Azure ADLS 或 Google Cloud Storage (GCS)。为了让这一点更具可比性,IDC 的预测表明,到 2025 年,惊人的 80% 的企业数据将是非结构化的。然而,正是在这一混乱的数据环境中,AI 的金矿才得以显现。该非结构化数据宝藏的某些片段偶尔会被转移到数据仓库中,并为商业智能工作负载进行结构化处理,有时这些数据会被回溯。这个循环过程产生了孤立的数据存储,每个存储都由不同的治理协议进行管理。

例如,在数据湖中,重点主要放在文件和目录的访问权限上。相反,在数据仓库中,关注点则转移到表、列和行的权限上。一个领域中的更改很少会反映到另一个领域中。这些领域的治理在不同的层次上执行,缺乏统一性。此外,在这些*台上使用的工具差异极大,阻碍了团队的无缝协作。这导致了一种零散且容易出错的治理方法,复杂了权限分配与审计,同时也增加了数据发现或共享的难度。

然而,数据不仅仅局限于文件或表格。在今天的时代,我们还需要应对不断发展的数据形式,如仪表板、机器学*模型和笔记本。每种形式都附带着独特的权限范式,增加了统一访问权限管理的复杂性。当数据资产散布在不同的云*台上时,这一挑战更加严峻,因为每个*台都有其独特的访问治理解决方案。

关键是什么?你的数据架构越复杂,掌握数据治理就越具挑战性并且资源密集。让我们深入探讨数据管理的几个具体方面,以帮助我们更好地理解这一点。

数据管理:打造基础

在一个强有力的数据治理策略的核心,是高效的数据管理。这个领域包括对可靠数据集的精确聚合、融合、协调和保持,推动企业从数据中获得最大价值。随着当今商业环境的快速变化,一个组织的优势在于其从所管理的数据中提取洞察的能力。此外,数据管理还在为组织提供有关数据交互频率的洞察方面发挥着至关重要的作用,同时提供一整套工具,以便对数据生命周期进行全面的监督。

历史上,以分析驱动的数据管理为支柱的是数据仓库,通常以通过表格和视图等结构管理的表格数据为特征,这些表格由行和列组成。相反,数据湖则是一个用于存储多种结构化或非结构化数据的存储库,这些数据通常与数据科学或机器学*任务相关。从原始文本文件和 Apache Parquet 格式到多媒体内容(如图片或视频),数据湖以细粒度的文件层次来管理这些海量数据集。

进入数据湖屋的领域——这一新范式使得组织能够单一存储数据,并使其可以用于多种分析应用。这一创新方法减少了数据冗余,并缩小了组织的数据管理范围。

数据摄取——信息的大门

在数据找到其永久存储位置以供后续使用之前,它需要经历一个至关重要的收集阶段。尽管数据来源众多,但主要的数据通道包括以下几种:

  • 基于云的存储系统

  • 消息传递通道

  • 传统的关系型数据库

  • 软件即服务SaaS)*台的 API

最近,大量数据来自于传输到公共云服务提供商的对象存储设施中的文件。这些文件的数量从少量到每天数百万不等,涵盖了各种格式:

  • 非结构化实体,如 PDF 文件、音频文件或视频

  • 半结构化格式,如 JSON

  • 结构化类型,包括 Parquet 和 Avro

对于那些进入数据流领域的人来说,分布式消息队列,如 Apache Kafka,成为了首选*台。这一无缝集成为超高速消息处理提供了可能,且消息处理按线性顺序进行。与开源队列系统互补,每个主流云提供商都有其原生的消息传递服务。

数据集成——从收集到交付

原始数据来自多个来源,必须进行集成才能释放其全部潜力。集成可能按批次进行,也可以实时流式传输,旨在快速获取洞察。但集成不是一项单独的任务——它需要协调行动来策划、引入、融合、重塑,并最终发布经过精炼的数据。

数据仓库与实体解析

数据仓库的一项利器是实体解析ER)。实体解析的核心是从数据表示中解码出真实世界的实体。这些实体可能是个人、产品或地点。这项技术在主数据管理和数据集市维度对齐中至关重要。例如,要判断“Bill Wallace”和“William Wallace”在不同系统中是否是同一个人,就需要使用复杂的算法,并运用庞大的数据图。

对数据质量的追求

为了充分发挥数据的价值,信任是至关重要的。质量可疑的数据可能导致分析不准确和决策失误,还可能增加运营成本。根据 Gartner 的报告,此类数据异常每年可能使组织损失高达 1290 万美元。因此,强有力的数据治理蓝图必须将数据质量作为重点。

文档和目录——治理中的无名英雄

元数据不仅仅是机器学*治理的一个附带方面;它是核心部分。机器学*目录提供洞察,指导用户了解可用资源及其潜在应用。这个共享的知识库加速了模型部署,推动了最佳实践的普及。

在评估数据质量时,有几个方面需要关注:

  • 数据是否完美无缺且详尽无遗?

  • 它的起源是什么?

  • 数据的时效性如何?

  • 数据是否违反了任何质量标准?

如果你能够自动捕获数据的血缘关系,这将有助于快速掌握数据的所有权和起源。这种血缘关系不仅是向后追溯;它还会向前展现,展示那些消费此数据的实体——无论是其他表格、仪表板还是笔记本。

此外,理解数据集的血缘关系并不足够。掌握数据集内数据的完整性同样至关重要。进行实时质量检查,并将这些检查汇总以便于访问和监控,对于确保后续分析任务的数据质量至关重要。这些检查可以预防错误数据的流入,验证数据质量,并启动政策以应对异常情况。监控数据质量的轨迹可能相当繁琐,但它将提供关于数据演变和任何需要干预领域的深刻洞察。

理解数据的路径

现代组织面临着来自各种来源的大量数据挑战。了解这些数据的来源以及其消费模式,对于确保数据的质量和可靠性至关重要。这正是数据血统作为一个强大工具的体现,它能够为组织中的数据提供更清晰的概览。本质上,数据血统描绘了数据的旅程,从其起源到最终使用。这个映射过程涉及在数据生命周期中捕获详尽的元数据和相关事件,涵盖诸如数据来源、用于创建数据的数据集、创作者、任何相关的转换、时间戳等细节。通过采用数据血统解决方案,团队可以可视化其基础架构中数据转换的整个范围和流动。

随着以数据驱动的决策浪潮的兴起,嵌入数据血统已成为强大数据治理的基石。

合规性与审计准备

众多监管框架,如 GDPR、CCPA、HIPAA、BCBS 239 和 SOX,要求组织保持其数据流的透明度。它们必须证明所共享的数据或报告来自可验证、可信赖的来源。这就需要追踪报告中使用的表格和数据集,强调组织数据架构中的数据可追溯性。数据血统通过自动化生成审计所需的数据流踪迹,减轻了合规负担。

变更管理与影响分析

数据并非静态的;它会不断发展。理解数据修改对后续用户的级联效应至关重要。通过数据血统,团队可以识别所有受到数据更改影响的下游实体。这种可*性扩展到应用程序、仪表板、机器学*模型、数据集等。这些可视化和分析工具有助于评估潜在的后果,从而及时通知利益相关者。此外,它还帮助 IT 部门透明地分享数据迁移更新,确保业务运营不受阻碍。

维护数据质量

确保数据质量在任何以数据为中心的项目中都是至关重要的。数据的完整性、准确性和可靠性直接影响所获得的洞察、训练的模型以及做出的决策。数据血统通过提供数据来源和转换的全面理解,赋能数据用户,如数据工程师、科学家和分析师。这种背景意识带来了更好的分析结果,因为用户可以自信地追溯数据的来源并理解对其进行的任何修改。

示例与场景

示例:合成标签是一个创新技术,其中机器学*模型,特别是大型语言模型,被用来辅助数据标注。这种方法特别适用于大量数据集,在这些数据集中,手动标注既费时又不切实际。

场景:假设一个数据集包含了数百万条客户反馈评论。与其让人工标注员逐一阅读每条评论并将其标记为“积极”、“消极”或“中立”,不如使用一个经过训练的语言模型来快速处理和标注这些评论。

然而,这种方法也并非没有挑战。合成标注可能引入偏差或错误,尤其是在模型本身是基于偏斜或不完美数据进行训练时。如果不加以控制,这些偏差可能会在数据集中传播,导致误导性的*解或存在缺陷的机器学*模型。

这时,数据管理者的角色变得至关重要。他们必须积极监控和验证合成标签,以确保其保持高准确度标准。结合合成标注的效率和人工监督是至关重要的,确保生成的标签得到交叉检查、验证,并在必要时进行改进。通过这种*衡,组织可以在保持严格数据质量标准的同时,利用快速标注的优势。

故障排除与分析

尽管有严格的检查,数据流程仍可能出现故障。在这种情况下,数据血统通过帮助团队定位系统中的错误源,发挥了重要作用,无论是在数据管道、应用程序还是模型中。这样的精确定位大大缩短了调试时间,从而节省了大量的时间和精力。

总结来说,数据旅程的系统映射,或称数据血统,不仅仅是一种奢侈;它在今天这个数据丰富的环境中是必不可少的,有助于更清晰的决策、更强的合规性以及更高效的运营。

当然,收集这些数据的目的通常是为了训练机器学*模型,因此,让我们把注意力转向如何看待机器学*治理。

探索机器学*治理的复杂性与结构

机器学*不仅仅通过使用算法和它们所获取的数据来运作。其本质在于负责任地构建模型,而这一任务需要有治理的支撑。正如治理一直是数据领域的基石,对于机器学*来说同样至关重要,尤其是在责任、标准化、合规性、质量和清晰度等方面。接下来我们将更详细地讨论这一话题。

机器学*治理的支柱

解锁机器学*潜力的关键在于确保模型满足以下标准:

  • 符合相关的监管和伦理基准

  • 展现一致的结果和表现

  • 以透明的方式阐明其发展及影响

  • 可接受定期的质量评估和更新

  • 遵循标准的文档编制和目录化协议

虽然遵守行业特定的法规设定了基础标准,但处理更广泛的伦理问题往往需要一种细致的方法。治理的本质不仅仅是法律问题,它还深入到道德正确性的领域。在这里,主动评估和健全的评估过程发挥着作用,确保模型不仅合规,而且在伦理上是合理的。

模型可解释性

在机器学*领域,了解模型是如何做出决策的至关重要。这不仅仅是学术兴趣的问题,它在现实世界中具有重要的影响,尤其是当决策影响到人类生活时,例如在医疗保健或刑事司法领域。

考虑一个医疗保健系统,其中机器学*模型根据患者的病历预测其发展某些疾病的可能性。一个模型可能预测某个患者患糖尿病的风险很高。但为什么模型做出这个预测?是因为患者的年龄、遗传历史、饮食*惯,还是其他某些因素?

局部可解释模型无关解释LIME)是一个旨在揭示机器学*预测黑箱的工具。它通过轻微扰动输入数据(加入一些噪声),并观察这些变化如何影响模型的预测。通过多次进行这一过程,LIME 建立了一个关于哪些输入变量对给定预测最具影响力的图像。

然而,像所有工具一样,LIME 并非万无一失。它提供的是模型行为的近似,而非精确的解释。此外,其有效性可能会根据所使用的模型和数据的不同而有所变化。

这突出了机器学*治理的必要性。确保正确使用像 LIME 这样的工具,理解它们的局限性,并在必要时用其他方法进行补充,是至关重要的。模型不仅要准确,还必须具有透明性,其决策必须可以解释,尤其是在高风险的场景下。机器学*治理政策可以为可解释性设定标准,并指导像 LIME 这样的工具的正确使用和解释。

例如,以下代码片段演示了如何使用情感分析模型,借助 Hugging Face 的 Model Hub 提供的 LIME 工具。虽然这个脚本通过识别输入中的重要单词/特征,为模型提供了一个可解释性层面,但必须理解,这种解释仅是近似的。高亮的单词可以提供模型决策的*解,但它们可能无法捕捉模型复杂推理的全部过程。因此,虽然像 LIME 这样的工具是有价值的,但应该在更广泛的机器学*治理框架中审慎使用,以确保它们提供的*解是可操作且可靠的:

# Import required modules
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
from lime.lime_text import LimeTextExplainer
# Load the tokenizer and model
tokenizer = AutoTokenizer.from_pretrained("cardiffnlp/twitter-roberta-base-sentiment")
model = AutoModelForSequenceClassification.from_pretrained("cardiffnlp/twitter-roberta-base-sentiment")
# Define the prediction function for LIME
def predictor(texts):
inputs = tokenizer(texts, return_tensors="pt", truncation=True, padding=True, max_length=512)
outputs = model(**inputs)
probs = torch.nn.functional.softmax(outputs.logits, dim=-1).detach().numpy()
return probs
# Initialize LIME's text explainer
explainer = LimeTextExplainer(class_names=['negative', 'neutral', 'positive'])

我们可以看到视觉解释(图 14.1),显示哪些标记对最终决策影响最大:

# Sample tweet to explain
tweet = "I love using the new feature! So helpful."
# Generate the explanation
exp = explainer.explain_instance(tweet, predictor, num_features=5, top_labels=3)
exp.show_in_notebook()

在这种情况下,num_features决定了解释器应使用多少个特征来描述预测。例如,如果设置为4,LIME 将提供最多四个特征的解释,这些特征对预测的影响最大。通过仅关注对预测影响最大的前 n 个特征,而不是考虑所有特征,这有助于简化解释。top_labels决定你希望得到多少个最可能的标签的解释,通常用于多类分类。例如,如果设置为1,LIME 将只解释最可能的标签。如果设置为2,它将解释两个最可能的标签,依此类推。图 14.1展示了在这种情况下输出的内容:

图 14.1 – 在这里,最影响决策的词元是“love”,“helpful”,以及令人惊讶的“new”和“feature”

图 14.1 – 在这里,最影响决策的词元是“love”,“helpful”,以及令人惊讶的“new”和“feature”

有趣的是,在我们的示例中,词元newfeature大大促进了正向预测。在治理方面,这是一个很好的例子,透明地阐明了机器学*的发展和影响。让我们通过写一个我们认为应该是负面的陈述来测试一下:

# Sample tweet to explain for negative
tweet = "I hate using the new feature! So annoying."
# Generate the explanation
exp = explainer.explain_instance(tweet, predictor, num_features=5, top_labels=3)
exp.show_in_notebook()

我们在图 14.2中获得了以下结果:

图 14.2 – 我们的情感分类器正确分类了我们的负面陈述

图 14.2 – 我们的情感分类器正确分类了我们的负面陈述

让我们再尝试一个我们期望是中性陈述的例子:

# Sample tweet to explain for neutral (bias at work)
tweet = "I am using the new feature."
# Generate the explanation
exp = explainer.explain_instance(tweet, predictor, num_features=5, top_labels=3)
exp.show_in_notebook()

图 14.3显示了结果图。

图 14.3 – 我们的中性陈述“我正在使用新功能”再次被分类为正面,感谢短语“new feature”

图 14.3 – 我们的中性陈述“我正在使用新功能”再次被分类为正面,感谢短语“new feature”

真是令人着迷!我们看似中性的陈述“我正在使用新功能”被分类为正面,而词元newfeature正是原因。为了进一步展示这一点,让我们再写一个没有提到“**new feature”的中性陈述:

# Sample tweet to explain for neutral (bias at work)
tweet = "I am using the old feature."
# Generate the explanation
exp = explainer.explain_instance(tweet, predictor, num_features=5, top_labels=3)
exp.show_in_notebook()

让我们查看图 14.4中的结果:

图 14.4 – 没有“new feature”,分类器按预期执行

图 14.4 – 没有“new feature”,分类器按预期执行

从治理角度来看,这是一个分类改进的机会,在 LIME 的帮助下,我们找到了一个明确的词元模式,导致了不准确的结果。当然,一一找到这些模式可能会变得非常繁琐,但有时候这正是机器学*迭代开发的本质。我们可以将这些工具部署在云端自动运行,但归根结底,这里提出的解决方案是拥有更高质量的训练数据并更新我们的模型。

机器学*发展的各个方面

正如我们在机器学*章节中已涉及的,思考机器学*时有多个维度需要考虑。让我们回顾一下与数据管理相关的一些机器学*开发方面:

  • 精通特征工程:特征是机器学*模型的基石。因此,保护并确保其可访问性至关重要。使用特征存储不仅有助于模型开发,还能提供溯源跟踪,确保数据转换的清晰,并消除训练与实时应用之间的潜在差异。

  • 完善数据处理:通常,模型训练的基石数据可能会丢失,这使得重新创建模型参数变得复杂。然而,使用像 Managed MLflow 这样的工具,数据集会被仔细记录,确保机器学*模型开发周期的顺利进行。

  • 精炼模型训练:从构思到生产的机器学*旅程很少是一帆风顺的。模型选择需要经过严格的评估、方法论的考量和不断的调整。使用像 MLflow 这样的*台,每一次迭代(及其相关的指标)都会被捕捉,确保模型训练过程的透明性。

超越训练——模型部署与监控

确保模型准确性并不仅仅止步于部署;它需要持续的监督,特别是在模型适应现实世界场景时。监控涵盖多个方面:

  • 概念漂移:现实世界中的变量,如市场变化或业务战略的变化,可能会显著影响模型的结果。

  • 数据调整:尽管故意的数据变动可能容易追踪,但数据收集或表现方式的无意变化可能会引入模型的不一致性。

  • 偏差:除了统计不*衡,偏差还可能表现为对不同群体的不*等对待,这就需要进行严格检查以防止潜在的不公正。我们已经看到像合成标签这样的技术如何导致偏差浮出水面。

为成功的机器学*治理,建立性能阈值、监控频率,并使用问题警报程序至关重要。许多公司提供一个工具生态系统,从自动化仪表盘、溯源跟踪到数据质量检查,确保模型保持准确、公正且符合规定。

架构治理指南

架构治理是确保 IT 基础设施无缝集成的基石,并且支持核心业务流程。其主要目标包括:

  • 编目当前的架构布局

  • 建立指南、原则和基准

  • 对齐业务与 IT 的愿景

  • 制定目标基础设施蓝图

  • 确定目标框架的价值主张

  • 突出当前架构与期望架构之间的差异

  • 制定全面的架构路线图

架构治理的五大支柱

牢记这五大支柱,我们还可以参考一些原则,帮助你现代化治理架构并优化其效果。

  • 一致性:确保工作流程和操作的和谐集成,不出现任何问题

  • 安全性:这是保护敏感数据和维护合规性至关重要的一部分

  • 可扩展性:一种面向未来的方式,考虑到日益增长的数据需求

  • 标准化:采用广泛接受的标准以实现灵活性和互操作性

  • 重用:通过创建和重新利用架构中的组件来促进效率

变革性的架构原则

为了最大化治理的效益,必须整合某些基础原则:

  • 将可信数据作为产品交付:数据不仅仅是副产品,它是一种资源,需要以产品为中心的思维方式

  • 优先考虑自服务可访问性:这涉及打破官僚主义的数据壁垒,以实现更灵活的决策过程

  • 数据价值创造民主化:去中心化的数据访问确保了更有信息支撑的、数据驱动的组织战略

  • 消除数据孤岛:统一的数据战略简化了流程,避免了冗余和差异

聚焦于架构维度

让我们再来回顾一下我们的五大治理支柱:

  • 一致性:整合多种工作负载、用例和*台组件,提供统一、一致的体验

  • 安全性:通过强有力的访问控制,尤其是对敏感数据类型,倡导数据保护

  • 可扩展性:作为一种云原生的*台即服务PaaS)解决方案,它可以轻松扩展,适应不同的计算需求

  • 标准化:通过依赖开放源代码格式,如 Parquet,避免供应商锁定并促进互操作性

  • 重用:*台与流行的 CI/CD 工具的整合方式确保任何创建的组件都能被重用,从而提高效率

总结

在我们对数据、机器学*和架构治理的探索过程中,我们强调了一个连贯战略对组织在数字时代有效航行的重要性。我们所处的时代,以迅速的技术进步为特征,主要由数据和分析的无与伦比的力量推动。为了利用这种力量,组织必须拥有一个明确的蓝图——一个定义清晰、结构化且可操作的蓝图。

这种治理的核心在于一致性的原则。统一的方法确保来自不同来源的数据能够无缝整合,从而提升整体决策过程。同样至关重要的是安全性原则。随着数字领域威胁的增加,保护数据资产不再是可选项,而是任何具有远*的组织的必备要求。此外,扩展性维度变得尤为重要,尤其是当我们考虑到数据的指数级增长时。组织不仅需要为当前的数据需求做好准备,还需要为未来可能出现的需求做好规划。

然而,这些基础要素只是开始。要使数据治理真正具有变革性,它必须实现数据的普及访问,确保洞察力不仅限于少数人,而是可以在整个组织中共享。然而,这种广泛的访问必须以避免创建数据孤岛为前提,因为数据孤岛可能会扼杀创新并阻碍跨职能合作。

在我们总结时,很明显,通往最佳数据治理的旅程是多方面的。但只要有正确的原则,组织将更有能力释放其数据资产所蕴含的巨大潜力,迎来一个信息驱动的决策和战略创新新时代。

第十五章:在行动中导航现实世界的数据科学案例研究

向你致敬,勤奋的读者!我们已经深入数据科学这片错综复杂的领域,共同穿越了它的广阔疆域。从第十五章的旅程中,你不仅展示了你的坚持,还展现了在数据变革领域的强大智力好奇心。这确实是一个值得纪念的里程碑。

在本章中,我们将揭示两个精心挑选的案例研究,为数据科学的务实维度提供切实的洞察。这些深入的分析将作为灯塔,照亮我们之前讨论过的理论原理。然而,鉴于数据科学的广泛性及其涵盖的众多场景,我们做出了一个战略决策。虽然我们将在此全面剖析这两个场景,但我们书籍的 GitHub 库中还有大量额外的案例研究,等待您去探索。

在我们保持的学术和正式语气中,我们要认识到,这些案例研究不仅仅是数据方法的展示。它们代表了现实世界数据应用中固有的挑战、策略和胜利的复杂舞蹈。

握住我们之前讨论的指南针(正如你很快会看到的那样,带有双关意义),让我们深入探讨这些案例研究,好吗?

本章将覆盖以下主题:

  • COMPAS 数据集案例研究介绍

  • 使用预训练模型和 OpenAI 的文本嵌入

COMPAS 数据集案例研究介绍

在机器学*领域,数据驱动决策的过程中,算法的精确性与伦理公正之间的界限往往模糊不清。COMPAS 数据集是一个在 2013-2014 年间,在佛罗里达州布劳沃德县筛选出的犯罪分子数据集合,它深刻地提醒我们这一复杂的关系。表面上看,这似乎只是一个简单的二分类任务,但其影响远远超出简单的预测。每一行数据和特征不仅仅是一个数字或类别,它代表了数年甚至数十年的人的经历、抱负与生命。当我们深入这个案例研究时,我们会意识到,这些不仅仅是行和列,而是有着抱负、梦想和挑战的人们。我们主要关注的是预测再犯(即犯罪分子再次犯罪的可能性),但面临的不仅仅是实现模型准确度的挑战,更有确保公*性的巨大责任。数据中的系统性特权、种族差异以及固有偏*,进一步突显了需要采取一种能够识别并减少这些不*衡的办法。本案例研究旨在探讨这些复杂问题,提供对存在偏*的洞察,更重要的是,探索如何在机器学*准确性和人类公*性之间找到*衡。让我们踏上这段旅程,牢记我们所做决策的分量及其在现实场景中的深远影响。

本次探索的核心围绕惩教犯人管理分析与替代制裁COMPAS)数据集。该数据集汇总了 2013-2014 年期间,在佛罗里达州布劳沃德县处理的犯罪分子数据。我们的重点是该数据的一个特定子集,专门用于根据个体特征确定再犯概率的二分类任务

对于有兴趣的人,数据集可以在这里访问:www.kaggle.com/danofer/compass

初看之下,任务似乎很简单。一个没有数据缺失的二分类问题,那么,为什么不直接投入其中呢?然而,问题在于,我们逐渐意识到,机器学*模型可能对真实的人类生活产生深远的影响。作为机器学*工程师和数据从业者,我们肩负着责任,不仅要设计高效的模型,还要确保结果本质上是“公正”的。

在本案例研究中,我们将努力阐明“公*”的多维特性。虽然有多个定义,但关键在于辨别哪种公*的概念与当前领域的需求最为契合。通过展开不同的公*视角,我们旨在阐明它们的实际含义。

本案例研究具有示范性,不应被误解为一项详尽的统计分析或对美国刑事司法框架的批评。相反,它旨在突出数据集中的潜在偏*,并倡导改进我们机器学*算法公*性的技术。

不再赘述,让我们直接进入数据集:

 import pandas as pd
 import numpy as np
 compas_data = pd.read_csv('../data/compas-scores-two-years.csv')
 compas_data.head()

图 15**.1 显示了我们数据集的前五行:

图 15.1 – COMPAS 数据集的初步视图

图 15.1 – COMPAS 数据集的初步视图

这揭示了关于曾在佛罗里达州布劳沃德县监禁的个体的某些敏感数据。这里的关键标签是 two_year_recid,它回答的是二元问题:“该个体在释放后 24 个月内是否再次被监禁?”

2016 年,ProPublica 对 COMPAS 算法及其基础数据的公*性进行了调查,并特别强调了分配给每个受试者的十分位得分。十分位得分将数据划分为 10 个相等的部分,概念上类似于百分位数。简单来说,个体的得分在 1 到 10 之间,每个得分表示根据特定指标划分的群体。例如,十分位得分为 3 表示 70% 的受试者具有较高的再次犯罪风险(得分在 4 到 10 之间),而 20% 的受试者则具有较低的风险(得分为 1 或 2)。相反,得分为 7 的话,30% 的受试者具有较高的再犯率(得分为 8 到 10),而 60% 的受试者被认为风险较低(得分在 1 到 6 之间)。

随后的分析展示了在十分位得分分配上的某些差异,尤其是在种族方面。在评估得分分布时,明显的种族偏*浮现出来,如下所示:

compas_data.groupby('race')['decile_score'].value_counts(
     normalize=True
 ).unstack().plot(
     kind='bar', figsize=(20, 7),
     title='Decile Score Histogram by Race', ylabel='% with Decile Score'
 )

图 15**.2 显示了结果图表:

图 15.2 – 十分位得分分布中的种族差异明显

图 15.2 – 十分位得分分布中的种族差异明显

我们可以深入探讨 ProPublica 调查如何解读其发现,但我们的兴趣在于从数据中构建一个二分类器,暂时忽略预先存在的十分位得分。

理解任务/概述成功

我们调查的核心是二分类问题。我们的任务可以概括为一个问题:“考虑到个人的各种属性,我们能否在既高效又公正的情况下预测其再次犯罪的可能性?”

效率的概念非常简单。我们有一系列指标,如准确率、精确度和 AUC,用于评估模型的有效性。但当我们讨论“公正性”时,我们需要了解新的概念和指标。在深入探讨偏*和公*性量化之前,我们应该进行一些初步的数据探索。

初步数据探索

目的是使用数据集的特征来预测 two_year_recid 标签。具体而言,我们正在使用的特征如下:

  • sex – 二元值:“Male” 表示男性,“Female” 表示女性

  • age – 表示年龄的数值

  • race – 类别型

  • juv_fel_count – 表示之前青少年重罪的数值

  • juv_misd_count – 表示之前青少年轻罪次数的数值

  • juv_other_count – 表示其他青少年定罪的数值

  • priors_count – 表示之前刑事犯罪次数的数值

  • c_charge_degree – 二元值:“F”表示重罪,“M”表示轻罪

目标变量如下:

  • two_year_recid – 二元值,表示个体是否在两年内重新犯罪

值得注意的是,我们有三列不同的青少年犯罪数据。我们可能考虑将这些列合并为一列,表示青少年犯罪的总数。鉴于我们希望构建一个精确且无偏的模型,让我们根据种族查看再犯分布。通过按种族对数据集进行分类并分析再犯率,显然不同种族群体之间有不同的基准再犯率:

compas_df.groupby('race')['two_year_recid'].describe()

Figure 15**.3 显示了描述性统计的矩阵图:

Figure 15.3 – 按种族分类的再犯描述性统计;不同种族群体之间的再犯率存在明显差异

Figure 15.3 – 按种族分类的再犯描述性统计;不同种族群体之间的再犯率差异明显可*

我们还观察到两个种族群体的代表性有限:亚洲人和美洲原住民。这种失衡的代表性可能导致偏*推断。为了提供背景信息,亚洲人约占佛罗里达州布劳沃德县人口的 4%,但在这个数据集中仅占约 0.44%。在本研究中,我们将把亚洲人和美洲原住民的个体重新分类为 Other,以解决数据不*衡问题。这可以使类分布更加均衡:

# Modify the race category for educational purposes and to address imbalance in the dataset
compas_df.loc[compas_df['race'].isin(['Native American', 'Asian']), 'race'] = 'Other'  # Adjust "Asian" and "Native American" categories to "Other"
compas_df.groupby('race')['two_year_recid'].value_counts(
    normalize=True
).unstack().plot(
    kind='bar', figsize=(10, 5), title='Recidivism Rates Classified by Race'
)  # Visualize Recidivism Rates across the refined racial groups

Figure 15**.4 显示了按种族划分的再犯率差异的条形图:

Figure 15.4 – 展示按种族分类的再犯率的条形图

Figure 15.4 – 展示按种族分类的再犯率的条形图

我们的研究发现,非洲裔美国人的再犯率高于白人西班牙裔其他群体。其背后的原因是多方面的,超出了本研究的范围。然而,重要的是要注意到这些再犯率中的细微差异。

注意

我们本可以分析性别偏差,因为男性和女性的表现存在明显差异。对于本研究的目标,我们将重点讨论种族偏差。

继续深入分析,我们来看一下其他数据集属性:

compas_df['c_charge_degree'].value_counts(normalize=True).plot(
    kind='bar', title='% of Charge Degree', ylabel='%', xlabel='Charge Degree'
)

我们拥有一个二元的犯罪严重程度属性,转换为布尔格式后,应该可以直接使用(该图表* Figure 15**.5):

Figure 15.5 – 描述我们的数据集,展示重罪与轻罪的对比

Figure 15.5 – 描述我们的数据集,展示重罪与轻罪的对比

大约 65%的指控是重罪,其余的是轻罪。

准备建模数据

在理解了偏差和公*性定义的细微差别后,至关重要的是我们同样要重视数据准备过程。这不仅涉及技术转换,还需要深思这些转换对公*性的影响。

特征工程

我们已经在 EDA 过程中讨论了一些要点,例如合并三列青少年犯罪数据。然而,在此之前,必须指出,我们对数据所做的任何转换都可能引入或加剧偏*。让我们仔细分析一下。

合并青少年犯罪数据

将青少年犯罪合并成一个特征是为了简化模型。然而,如果三种青少年犯罪类型在不同种族上有不同的再犯影响,这可能会引入偏差。通过将它们合并在一起,我们可能会过于简化这些影响。始终对这样的合并保持警惕:

# feature construction, add up our three juv columns and remove the original features
compas_df['juv_count'] = compas_df[["juv_fel_count", "juv_misd_count", "juv_other_count"]].sum(axis=1)
compas_df[['juv_fel_count', 'juv_misd_count', 'juv_other_count', 'juv_count']].describe()

生成的矩阵可以在图 15.6中显示:

图 15.6 – 查看我们的新列

图 15.6 – 查看我们的新列

独热编码分类特征

我们需要将诸如sexracec_charge_degree等分类变量转换为数值格式。在这里,使用独热编码等方法可能是合适的。然而,必须记住,过多的二进制列可能会加剧公*性问题,如果模型给予某个子群体过多的权重:

   dummies = pd.get_dummies(compas_df[['sex', 'race', 'c_charge_degree']], drop_first=True)
   compas_df = pd.concat([compas_df, dummies], axis=1)

标准化偏斜特征

我们可以通过以下代码块和图形轻松看出agepriors_count是右偏的。标准化这些特征有助于我们的模型更好地训练。使用对数变换或*方根等方法可能会有帮助:

# Right skew on Age
 compas_df['age'].plot(
     title='Histogram of Age', kind='hist', xlabel='Age', figsize=(10, 5)
 )
 # Right skew on Priors as well
 compas_df['priors_count'].plot(
     title='Histogram of Priors Count', kind='hist', xlabel='Priors', figsize=(10, 5)
 )

图 15.7 展示了我们的两个分布,更重要的是,它展示了我们的数据有多么偏斜:

图 15.7 – 偏斜的年龄和前科数据可能会影响我们的最终预测

图 15.7 – 偏斜的年龄和前科数据可能会影响我们的最终预测

如果我们想要转换数值特征,可以使用 scikit-learn 管道来执行一些特征转换,类似于以下代码块中的操作:

We can use a scikit-learn pipeline to run a standard scaler like so:
numerical_features = ["age", "priors_count"]
numerical_transformer = Pipeline(steps=[
    ('scale', StandardScaler())
])

使用变换器(如前面代码块中定义的变换器),我们可以开始在机器学*管道中实时处理偏斜数据。

最终思考

记住,虽然我们努力实现最佳模型性能,但不断回顾公*性方面也至关重要。解决公*性问题不是一次性的任务,而是一个迭代过程,涉及不断优化模型、重新评估公*性指标,并确保我们的模型决策尽可能公正。我们的终极目标是确保在做出准确预测的同时,公*对待所有子群体。

使用预训练模型和 OpenAI 的文本嵌入

自然语言处理NLP)领域,将文本信息有效转化为数学表示形式(通常称为嵌入)的研究始终是重中之重。嵌入使得机器能够“理解”并处理文本内容,弥合了人类语言与计算任务之间的鸿沟。在我们之前的 NLP 章节中,我们深入探讨了文本嵌入的创建,并*证了大型语言模型LLMs)如 BERT 在捕捉语言细微差别方面的变革力量。

进入 OpenAI,它是人工智能研究领域的领先实体。OpenAI 不仅在大型语言模型(LLM)领域做出了重要贡献,还提供了各种工具和引擎,促进嵌入技术的发展。在本研究中,我们将深入探索使用 OpenAI 提供的文本嵌入技术。

通过嵌入教科书中的段落,我们将展示 OpenAI 嵌入技术在回答自然语言查询中的效果。例如,一个看似古怪的问题,比如“跳蚤有多少只角?”可以通过扫描嵌入的段落高效地得到解答,展示语义搜索的强大能力。

设置并导入必要的库

在深入研究这个案例之前,首先需要确保我们的环境设置正确。我们需要确保已经导入了执行任务所需的库。这个案例引入了几个新的包:

import os
import openai
import numpy as np
from urllib.request import urlopen
from openai.embeddings_utils import get_embedding
from sentence_transformers import util

让我们来解析一下我们的导入库:

  • os:与操作系统交互的必备库——在我们的案例中,用于获取 API 密钥。

  • openai:官方的 OpenAI 库,它将使我们能够访问各种模型和实用工具。

  • numpy:Python 中用于科学计算的基础包,有助于操作大量数据和数组。

  • urlopen:使我们能够从 URL 中获取数据,在我们获取文本数据时非常方便。

  • get_embedding:OpenAI 提供的一个工具,用于将文本转换为嵌入。

  • sentence_transformers.util:包含用于语义搜索的有用工具,这是我们案例研究的核心。

一旦我们的环境搭建完成,下一步是配置与 OpenAI 服务的连接:

openai.api_key = os.environ['OPENAI_API_KEY']
ENGINE = 'text-embedding-ada-002'

在这里,我们从环境变量中获取我们的 API 密钥。这是一种安全的方式,避免了将密钥硬编码到代码中。我们选择的嵌入引擎是text-embedding-ada-002

数据收集——获取教科书数据

对于本研究,我们正在分析一本关于昆虫的教科书。让我们来获取并处理这些数据:

text = urlopen('https://www.gutenberg.org/cache/epub/10834/pg10834.txt').read().decode()
documents = list(filter(lambda x: len(x) > 100, text.split('\r\n\r\n')))
print(f'There are {len(documents)} documents/paragraphs')

在这里,我们从源头下载文本,按段落进行拆分,并确保只保留内容更丰富的段落(那些字符数超过100的段落)。在本例中,我们最终得到了79个段落。

将文本转换为嵌入

我们分析的核心在于将文本数据转换为嵌入。让我们来实现这一点:

question_embedding = np.array(get_embedding(QUESTION))
embeddings=[get_embedding(document) for document in documents]
embeddings = np.array(embeddings)

我们遍历每个文档,使用指定的引擎将其转换为嵌入,并将嵌入存储在numpy数组中以便高效操作。

查询——搜索相关信息

在我们的数据转换完成后,接下来让我们提出一个自然语言查询,并使用向量嵌入找到最相关的文档。我们使用的是一种近邻算法,正如我们之前所看到的:

QUESTION = 'How many horns does a flea have?'
question_embedding = np.array(get_embedding(QUESTION, engine=ENGINE))
hits = util.semantic_search(question_embedding, embeddings, top_k=1)[0]
print(f'Question: {QUESTION}\n')
for i, hit in enumerate(hits):
    print(f'Document {i + 1} Cos_Sim {hit["score"]:.3f}:\n\n{documents[hit["corpus_id"]]}')
    print('\n')

我们将问题编码为嵌入,然后使用语义搜索从我们的数据集中找到最匹配的文档。结果为我们提供了对查询的洞察。通过这种结构,我们已经将代码转化为一个更具指导性、一步一步的教程,应该能让更多人易于理解和接触。

结语——现代预训练模型的强大力量

在快速发展的机器学*和人工智能领域,我们在这个案例研究中所*到的只是现代预训练模型巨大潜力的一个小小缩影。以下是对它们深远影响的简要思考:

  • 前所未有的高效性:过去,我们必须为每个新任务从零开始训练模型。但现在,经过微调的预训练模型已经消除了在时间、计算和资源方面的重大障碍。通过几行代码,我们便能够访问并利用那些已经在大量数据上训练过的模型的力量,而这在十年前是一个几乎不可能完成的任务。

  • 更广泛的可访问性:预训练模型不仅节省了时间,而且让最前沿的人工智能技术变得更加民主化。那些没有深厚机器学*背景或无法接触到庞大计算资源的开发者、研究人员和爱好者,现在可以轻松开始人工智能项目。

  • 快速原型设计:能够快速构建模型并测试想法,使得问题解决能够采用更具迭代性和创新性的方法。这种快速原型设计在那些需要快速周转或首发优势至关重要的行业中尤为重要。

  • 多样性与可扩展性:我们今天使用的模型,如 OpenAI 的嵌入引擎,非常多样化。无论你是在构建语义搜索引擎、推荐系统,还是任何其他需要理解上下文的应用程序,这些模型都可以成为你的基石。随着项目的扩大,这些模型可以与之同步扩展,确保持续的高效表现。

总结来说,人工智能的格局已经被预训练模型的出现彻底改变。它们的强大与高效标志着一个新时代的到来,在这个时代,构建先进的人工智能原型和项目已不再是遥不可及的梦想,而是轻松可实现的现实。随着技术的不断进步,令人兴奋的是,我们可以想象未来将会有哪些进一步的创新,以及这些创新将如何塑造我们互联的世界。

总结

随着这一全面案例研究章节的结束,我们需要强调的是,旅程并未就此结束。现代机器学*和人工智能的潜力巨大且不断增长,总有更多的内容等待学*、探索与创造。

我们的官方 GitHub 资源库是一个中心枢纽,存放了不仅来自这个案例研究的代码和详细解释,还有大量附加资源、示例,甚至更复杂的案例研究:

  • 更多案例研究:通过一系列涵盖不同领域和复杂度的案例研究,更深入地了解机器学*的世界。每个案例都经过精心设计,帮助您通过实践经验应对人工智能领域中的各种挑战和解决方案。

  • 全面的代码示例:资源库中包含丰富的代码示例,补充了案例研究和解释内容。这些示例设计易于理解和执行,帮助您掌握所讨论概念的实际应用。

  • 互动学*:参与互动笔记本和应用程序,提供动手实践的学*方式,帮助巩固您对关键概念和技术的理解。

  • 社区与协作:加入我们的学*者和贡献者社区。这个资源库是一个开放的协作空间,您可以提问、讨论和参与。您的参与有助于创造一个充满活力的学*环境,促进成长与创新。

  • 持续更新与新增:机器学*领域充满活力,我们的资源库也体现了这一点。通过定期查看更新内容,保持对最新趋势、技术和案例研究的了解。

掌握机器学*的道路是一段旅程,而非终点。这个资源库旨在成为您旅程中的伙伴,为您提供所需的工具、知识和社区支持,帮助您在人工智能领域蓬勃发展。

展望未来,我们对机器学*和人工智能的发展充满期待。我们致力于不断更新资源,增加新的案例研究,并不断提升每个人的学*体验。

感谢您选择与我们一起学*,我们希望提供的资源能为您在人工智能和机器学*领域的未来发展提供助力。让我们一起探索未知、解决复杂问题,创造一个更加智能、互联的世界!

第十六章:索引

由于本电子书版本没有固定分页,以下页码仅作为参考,通过本书印刷版进行超链接。

A

A/B 测试 109

对抗去偏 253

算法偏差 248, 249

聚合偏差 250

不同影响 249

不同对待 249

新兴技术 257

测量偏差 249

测量 251

缓解 252

缓解,数据预处理中的 253

缓解,模型处理中的 253

缓解,模型后处理中的 254

预先存在的偏差 249

代理偏差 250

样本偏差 249

公*性的重要性 252

类型 248, 249

未解决的偏差,后果 251

算法偏差,来源

聚合偏差 250

历史偏差 250

代理偏差 250

表征或样本偏差 250

替代假设 141

架构治理 264, 275

维度 276

支柱 275, 276

变革性的架构原则 276

算术*均数 114

算术符号 58

B

条形图 157-159

贝叶斯方法

与频率学派方法相比 73-79

贝叶斯定理 85-89

应用 89

应用,示例 89-92

BERT

TL 与 237, 238

BERT 的预训练

解码 233

MLM 233, 234

NSP 234, 235

偏差 275

偏差,GPT-2 中的 255-257

偏差,LLMs 中的 254

GPT-2 255

大数据 10

二元分类器 83, 84

二项随机变量 98

二项随机变量,示例

筹款会议 99

餐馆开张 99, 100

箱型图 160-163

括号符号 43

扩大可访问性 290

C

分类变量

假设检验 148

因果关系 164

中心极限定理 138

中心 211

卡方拟合优度检验 148

假设 148, 149

示例 149, 150

使用,场景 148

卡方关联/独立性检验 150

假设 151, 152

卡方检验 148

分类模型 178

聚类 179, 211

变异系数

员工薪水示例 119

咖啡店数据示例,定性与定量数据 19-21

逗号分隔值(CSV)文件 22

通信

显著性 153, 154

COMPAS 数据集,案例研究 279-281

数据准备,模型化 285

初步数据探索 282-285

任务/概述成功 282

复合事件 76-79

计算机编程 6, 7

Python,使用 7

概念漂移 258, 275

给定 B 条件下 A 的条件概率 (P(A|B) 79

置信区间 138-141

混杂因素 112

混淆矩阵 83

混淆矩阵 83

连续数据 24

连续随机变量 103-106

连续变量

线性回归预测 184-186

连续变量,线性回归

因果关系 187

相关性与因果关系 186

指标 189-193

添加预测变量 187, 188

相关性 126

因果关系对比 164-166

相关系数 126, 127

维度灾难 (COD) 219

D

数据 2

数据漂移 258

特征漂移 258

标签漂移 259

数据来源 259

数据探索 39, 40

DataFrames 42

引导性问题 40

Series 对象 43

Yelp 40-42

DataFrames 42

数据治理 264

审计准备 268

变更管理 268

当前障碍 265

文档编制与目录管理 267

影响分析 268

精通 265

合规性 268

故障排除与分析 269

数据摄取 266

数据集成 267

数据等级 24

区间等级 27

名义等级 24

有序等级 25

比例等级 31

数据管理 266

数据挖掘 9

数据模型 6

数据获取 108

实验性 108, 109

观察性 108, 109

数据路径 268

用于建模的数据准备,COMPAS 数据集案例研究

特征工程 285

青少年犯罪数据的合并 286

独热编码分类特征 286

标准化偏斜特征 287

数据质量 267

维护 268, 269

数据科学 1, 35, 36

COVID-19 的机器学*预测 3, 4

需求 3

步骤概述 36, 37

术语 2, 3

数据科学案例研究 10

自动化政府文件推动 11, 12

职位描述 12-15

数据科学步骤

数据探索 38

建模数据 38

数据获取 37

有趣问题的提出 37

结果,沟通和可视化 39

数据科学维恩图 4-6

计算机编程 6, 7

领域知识 9

数学 6

单条推文解析 8

数据科学家

因果关系 168

数据类型

定量与定性数据比较 19

结构化与非结构化数据 18

数据可视化

条形图 157-159

箱线图 160-163

直方图 159, 160

识别 154

折线图 156, 157

散点图 154, 155

数据仓库 267

决策树 204

虚拟变量 207-210

纯度,衡量 204

泰坦尼克数据集,探索 205-207

深度学* (DL) 175

深度学* (DL) 模型 238

降维 179, 221

实施方法 221

离散数据 23

离散随机变量 93-98

类型 98

离散随机变量,类型

二项随机变量 98, 99

连续随机变量 103-106

几何随机变量 100, 101

泊松随机变量 102

领域知识 9

点积 59, 60

漂移,缓解 259

上下文 260

连续监控 260

反馈系统,实施 260

模型适应技术 261

定期模型再训练 260

虚拟变量 207-210

E

经验法则 128, 129

考试成绩示例 129

空集 64

实体解析 (ER) 267

事件 72

实验 109

实验 109-111

实验单位 109

探索性数据分析 (EDA) 9, 155, 211

指数 60-63

F

公*感知算法

新兴技术 257

假阳性 147

通过意识方法的远离度 253

通过无意识方法的远离度 253

特征漂移 258

特征提取 211, 219-227

频率主义方法

与贝叶斯方法 73-79

G

几何*均 31

几何随机变量 100, 101

天气示例 101, 102

基尼指数 204

Google 云存储(GCS)265

GPT

TL 与 237, 238

H

直方图 159, 160

假设检验

分类变量 148

假设检验 141

进行 142, 143

I

基于图像的模型 232

归纳转移学*(ITL)236

处理过程中的技术 252

交集 64, 65

区间水* 27

示例 28

数学运算 28

中心度量 28, 29

变异度量 29

标准差 29, 30

J

贾卡尔指数 65

K

K 和聚类验证

最优数量,选择 217

关键绩效指标(KPI)168

K 均值聚类 211

说明性示例 212-216

L

标签漂移 259

标注数据 175

大型语言模型(LLMs)230, 288

公*表示学*(LFR)257

似然 87

李克特量表 26

李克特量表 96

线性代数 60, 66

线性回归 125

连续变量,预测 184-186

线性图表 156, 157

本地可解释模型无关解释(LIME)257, 270

对数 61-63

M

机器学*(ML)6, 9, 36, 172

警告 173, 174

面部识别 172, 173

强化学*(RL)181-183

SML 182

监督学*(SL)175, 176

类型 175

类型,概述 182

无监督学*(UL)179-181

无监督机器学*(UML)183

使用 174

使用,预测 COVID-19 3, 4

机器学*(ML),治理 264, 269

机器学*(ML)发展的方面 274

模型部署和监控 275

模型可解释性 270-274

支柱 270

集合的大小 63

掩蔽语言建模(MLM)233, 234

数学 6

使用 6

矩阵 57

矩阵乘法 66, 67

执行 67-69

*均绝对误差(MAE)189

变量的均值 94

均方误差(MSE)189

集中趋势度量 113, 114

相对位置度量 120-125

变异度度量 114-119

中位数 114

方法,用于测量偏差

混淆矩阵 251

反事实分析 251

不同影响分析 251

概率相等性 251

机会*等 251

通过意识实现公* 251

模型漂移 258

概念漂移 258

预测漂移 258

来源 259

模型 6

N

朴素贝叶斯算法 85

朴素贝叶斯分类

特征 195, 196

度量标准 197-204

执行 195-197

自然语言处理 (NLP) 255, 288

自然对数 62

神经网络 (NN) 38, 175

下一句预测 (NSP) 234, 235

名义水* 24

数据 25

示例 24

数学运算 25

中心度量 25

归一化常数 87

符号 72

无效准确率 198

零假设 141

空模型 193

空集 64

numpy 数组 56

O

观察性 109

单样本 t 检验 143, 144

假设 144-146

示例 144

单尾检验 145

有序水* 25

示例 26

Likert 26

数学运算 26

中心度量 27

过拟合 191

P

pandas

滤波 45-47

参数 108

个人身份信息(PII) 40

*台即服务(PaaS) 276

点估计 131-136

问题 138

泊松分布 132

泊松随机变量 102

示例 102, 103

总体 107

后验 87

后处理技术 252

预测漂移 258

预处理 18

预处理技术 252

预训练模型 230

BERT 的预训练 233

微调,用于文本分类 238-240

基于图像的模型 232

基于文本的模型 232, 233

TL 235

使用 231

使用,益处 230, 231

主成分分析(PCA) 219-227

先验 87

先验概率 87

概率 72, 73

概念 71, 72

规则,利用 79

概率密度函数(PDF) 103

概率质量函数(PMF) 93, 99

概率,规则

加法规则 80

互补事件 82, 83

独立性 82

乘法规则 81, 82

互斥性 80

概率抽样 111

程序 71

真子集 64

Q

定性数据 19

探索,技巧 44

定性数据,探索技巧

在 pandas 中进行过滤 45-47

名义级别列 44, 45

有序级别列 48, 49

泰坦尼克号数据集 49-53

定量数据 19

定量变量 156

R

随机抽样 111, 112

随机变量 92, 93

离散随机变量 93-98

随机变量 85

快速原型设计 290

比例级别 31

示例 31

问题 32

数学运算 31

集中趋势度量 31

回归模型 178

强化学*(RL) 181, 182

优点 183

缺点 183

应用,在各个领域 181

相对频率 74

均方根误差(RMSE) 189

S

样本均值 132

样本空间 72

抽样数据 111

概率抽样 111

不等概率抽样 113

抽样分布 136-138

标量 59

散点图 154, 155

半结构化数据 19

情感分析(SA) 203, 258

序列对象 43

集合论 63-66

SHapley 加法解释(SHAP) 257

轮廓系数 217, 218

辛普森悖论 166-168

软件即服务(SaaS) 266

方阵 57

标准差 115

标准正态分布 103

统计模型 9

统计 107, 108

统计,度量 113

变异系数 119

中心度量 113, 114

相对位置度量 120-125

变异度量 114-118

停止准则 214

结构化数据 2, 18

示例 18

子集 64

求和 58

超集 64

监督学*(SL)175, 176

分类与回归的选择 179

心脏病预测 176-178

方法 203

类型 178, 179

监督式机器学*(SML)模型 175, 182

优势 183

缺点 183

合成少数类过采样(SMOTE)技术 253

T

基于文本的模型 232, 233

文本分类

预训练模型,微调 238-240

文本嵌入,使用预训练模型和 OpenAI 288

数据收集,从教科书数据中提取 289

数据查询 290

库,导入 288, 289

库,设置 288, 289

文本,转换为嵌入向量 289

泰坦尼克号数据集 49-53

探索 205-207

传递式迁移学*(TTL)237

迁移学*(TL)235

示例 238-244

过程 236

类型 236

与 BERT237, 238

与 GPT237, 238

迁移学*(TL),类型

归纳迁移学*(ITL)236

传导迁移学*(TTL)237

无监督迁移学*(UTL)237

双尾检验 145

第一类错误 84, 147

第二类错误 84, 147

U

不等概率抽样 113

联合 65

前所未有的效率 290

非结构化数据 2, 18

示例 18

无监督学*(UL)179-181, 210

K 均值聚类 211

轮廓系数 217, 218

使用,注意事项 210, 211

无监督机器学*(UML)211

优势 183

缺点 183

无监督迁移学*(UTL)237

V

向量 56

口头沟通 168-170

视觉几何组(VGG)232

W

世界酒精消费数据示例 21-23

世界卫生组织(WHO)157

世界卫生组织(WHO)21

Y

Yelp40-42

Packtpub.com

订阅我们的在线数字图书馆,您可以全面访问超过 7,000 本书籍和视频,以及行业领先的工具,帮助您规划个人发展并推动职业生涯。更多信息,请访问我们的网站。

第十七章:为什么要订阅?

  • 通过来自 4,000 多位行业专业人士的实用电子书和视频,花更少的时间学*,更多的时间编码

  • 通过为您量身定制的技能计划提升您的学*效果

  • 每月获得一本免费的电子书或视频

  • 完全可搜索,方便快速访问关键信息

  • 复制和粘贴、打印、书签内容

您知道 Packt 提供每本出版书籍的电子书版本,支持 PDF 和 ePub 文件吗?您可以在 packtpub.com 升级为电子书版本,作为纸质书的客户,您还可以享受电子书的折扣。更多详情请通过 [email protected] 与我们联系。

www.packtpub.com,您还可以阅读一系列免费的技术文章,注册多种免费的新闻通讯,并获得 Packt 图书和电子书的独家折扣和优惠。

其他您可能喜欢的书籍

如果您喜欢这本书,您可能会对 Packt 的其他书籍感兴趣:

.jpg)

构建 FastAPI 数据科学应用 - 第二版

François Voron

ISBN: 978-1-83763-274-9

  • 探索现代 Python 和异步 I/O 编程的基础知识

  • 掌握 FastAPI 框架的基本和高级概念

  • 为数据科学应用程序部署一个高性能、可靠的 Web 后端

  • 将常*的 Python 数据科学库集成到 Web 后端

  • 将目标检测算法集成到 FastAPI 后端

  • 构建一个基于 Stable Diffusion 的分布式文本转图像 AI 系统

  • 添加指标和日志并学*如何监控它们

.jpg)

Python 深度学* - 第三版

Ivan Vasilev

ISBN: 978-1-83763-850-5

  • 建立深度神经网络的理论基础

  • 理解卷积神经网络并应用于计算机视觉应用

  • 熟练掌握自然语言处理和递归网络

  • 探索注意力机制和 Transformer

  • 应用 Transformer 和大规模语言模型进行自然语言处理和计算机视觉

  • 使用 PyTorch、Keras 和 Hugging Face Transformers 实现代码示例

  • 使用 MLOps 开发和部署神经网络模型

Packt 正在寻找像您这样的作者

如果你有兴趣成为 Packt 的作者,请访问authors.packtpub.com并立即申请。我们与成千上万的开发者和技术专家合作,帮助他们与全球技术社区分享*解。你可以提交一般申请、申请我们正在招聘的特定热门主题作者,或者提交你自己的创意。

分享你的想法

现在你已经完成了数据科学原理的学*,我们很想听听你的想法!如果你是在 Amazon 购买的这本书,请点击这里直接前往 Amazon 的评价页面并分享你的反馈,或者在你购买书籍的网站上留下评论。

你的评价对我们和技术社区来说非常重要,并将帮助我们确保提供优质的内容。

下载这本书的免费 PDF 副本

感谢购买这本书!

你是否喜欢在路上阅读,却无法随身携带纸质书籍?

你的电子书购买是否与选择的设备不兼容?

不用担心,现在每一本 Packt 书籍都提供免费的无 DRM PDF 版本。

随时随地,在任何设备上阅读。从你最喜欢的技术书籍中直接搜索、复制并粘贴代码到你的应用程序中。

好处不仅仅于此,你还可以独享折扣、订阅新闻通讯,并每天在邮箱中收到精彩的免费内容。

按照这些简单步骤享受福利:

  1. 扫描二维码或访问以下链接

packt.link/free-ebook/9781837636303

  1. 提交你的购买凭证

  2. 就这些!我们将直接通过电子邮件发送你的免费 PDF 以及其他福利。

posted @ 2025-07-19 15:46  绝不原创的飞龙  阅读(33)  评论(0)    收藏  举报