数据科学的数学基础(一)
原文:
zh.annas-archive.org/md5/773ab81070b00a4b488f78c66a458af9译者:飞龙
序言
在过去的 10 年左右,人们对将数学和统计应用于我们的日常工作和生活中越来越感兴趣。为什么会这样?这与哈佛商业评论称之为“21 世纪最性感的工作”的“数据科学”引起的加速兴趣有关吗?还是机器学习和“人工智能”的承诺正在改变我们的生活?是因为新闻头条充斥着研究、民意调查和研究结果,但不确定如何审查这些说法吗?还是因为“自动驾驶”汽车和机器人将在不久的将来自动化工作?
我将提出这样的论点,即数学和统计学的学科之所以引起主流兴趣,是因为数据的日益增加,我们需要数学、统计学和机器学习来理解它。是的,我们确实有科学工具、机器学习和其他自动化工具在呼唤我们,就像塞壬一样。我们盲目地信任这些“黑匣子”、设备和软件;我们不理解它们,但我们还是在使用它们。
尽管很容易相信计算机比我们聪明(这种想法经常被宣传),但现实可能恰恰相反。这种脱节在许多层面上都可能很危险。你真的想让一个算法或人工智能来执行刑事判决或驾驶车辆,但包括开发者在内没有人能解释为什么会做出特定决定吗?可解释性是统计计算和人工智能的下一个前沿。这只有在我们打开黑匣子并揭示数学时才能开始。
你可能还会问,一个开发者怎么可能不知道他们自己的算法是如何工作的?当我们讨论机器学习技术并强调为什么我们需要理解我们构建的黑匣子背后的数学时,我们将在本书的后半部分谈论这个问题。
另一个观点是,大规模收集数据的原因很大程度上是由于连接设备及其在我们日常生活中的存在。我们不再仅仅在台式机或笔记本电脑上使用互联网。我们现在随身携带智能手机、汽车和家用设备。这在过去的二十年里悄然实现了过渡。数据现在已经从一个操作工具演变为为不太明确的目标而收集和分析的东西。智能手表不断收集我们的心率、呼吸、步行距离和其他指标的数据。然后它将这些数据上传到云端,与其他用户的数据一起进行分析。我们的驾驶习惯被计算机化的汽车收集,并被制造商用来收集数据并实现自动驾驶车辆。甚至“智能牙刷”也正在进入药店,跟踪刷牙习惯并将数据存储在云端。智能牙刷数据是否有用和必要是另一个讨论的话题!
所有这些数据收集正在渗透到我们生活的每个角落。这可能令人不知所措,整本书都可以写关于隐私问题和伦理问题。但是,数据的可用性也为我们提供了利用数学和统计学以新方式创造更多曝光机会的机会,超越学术环境。我们可以更多地了解人类经验,改进产品设计和应用,并优化商业策略。如果您理解本书中提出的观点,您将能够释放我们数据积累基础中所蕴藏的价值。这并不意味着数据和统计工具是解决世界所有问题的灵丹妙药,但它们为我们提供了新的工具。有时认识到某些数据项目是兔子洞,意识到努力在其他地方更值得花费也同样有价值。
数据的不断增加使得数据科学和机器学习成为备受需求的职业。我们将基本数学定义为接触概率、线性代数、统计学和机器学习。如果您希望在数据科学、机器学习或工程领域寻求职业,这些主题是必要的。我将提供足够的大学数学、微积分和统计学知识,以更好地理解您将遇到的黑匣子库中的内容。
通过这本书,我旨在向读者介绍不同的数学、统计和机器学习领域,这些领域将适用于实际问题。前四章涵盖了基础数学概念,包括实用微积分、概率、线性代数和统计学。最后三章将过渡到机器学习。教授机器学习的最终目的是整合我们所学的一切,并展示在使用机器学习和统计库时的实际见解,超越对黑匣子理解。
跟随示例所需的唯一工具是 Windows/Mac/Linux 计算机和您选择的 Python 3 环境。我们将需要的主要 Python 库是numpy、scipy、sympy和sklearn。如果您对 Python 不熟悉,它是一种友好且易于使用的编程语言,背后有大量的学习资源。这里是我推荐的一些资源:
《从零开始的数据科学,第二版》作者:Joel Grus(O’Reilly)
本书的第二章是我遇到的最好的 Python 速成课程。即使您以前从未编写过代码,Joel 在最短的时间内有效地让您开始使用 Python。这也是一本放在书架上并应用您的数学知识的好书!
《Python for the Busy Java Developer》作者:Deepak Sarda(Apress)
如果你是一名从静态类型、面向对象编程背景转变而来的软件工程师,这本书是值得一读的。作为一个从 Java 开始编程的人,我深深欣赏 Deepak 是如何分享 Python 特性并将其与 Java 开发人员联系起来的。如果你有.NET、C++或其他类 C 语言的经验,你也很可能从这本书中有效地学习 Python。
这本书不会让你成为专家或获得博士知识。我尽力避免使用希腊符号的数学表达式,而是努力用简单的英语代替。但这本书将使你更加自如地谈论数学和统计学,为你成功地应对这些领域提供基本知识。我相信成功的最广泛途径不是在一个主题上拥有深入的专业知识,而是在几个主题上有暴露和实用知识。这本书的目标就是这样,你将学到足够多的知识,足以引起注意并提出那些曾经难以捉摸的关键问题。
那么让我们开始吧!
本书使用的约定
本书使用以下印刷约定:
斜体
指示新术语、URL、电子邮件地址、文件名和文件扩展名。
固定宽度
用于程序清单,以及在段落中引用程序元素,如变量或函数名、数据库、数据类型、环境变量、语句和关键字。
固定宽度粗体
显示用户应该按照字面意义输入的命令或其他文本。
固定宽度斜体
显示应该用用户提供的值或上下文确定的值替换的文本。
提示
这个元素表示提示或建议。
注意
这个元素表示一般说明。
警告
这个元素表示警告或注意。
使用代码示例
补充材料(代码示例、练习等)可在https://github.com/thomasnield/machine-learning-demo-data下载。
如果您有技术问题或在使用代码示例时遇到问题,请发送电子邮件至bookquestions@oreilly.com。
这本书旨在帮助你完成工作。一般来说,如果本书提供示例代码,你可以在你的程序和文档中使用它。除非你要复制代码的大部分内容,否则不需要联系我们请求许可。例如,编写一个使用本书中几个代码块的程序不需要许可。出售或分发 O'Reilly 图书中的示例需要许可。通过引用本书回答问题并引用示例代码不需要许可。将本书中大量示例代码合并到产品文档中需要许可。
我们感谢,但通常不需要归属。归属通常包括标题、作者、出版商和 ISBN。例如:“Essential Math for Data Science by Thomas Nield (O’Reilly). Copyright 2022 Thomas Nield, 978-1-098-10293-7.”
如果您觉得您对代码示例的使用超出了合理使用范围或上述许可,请随时与我们联系permissions@oreilly.com。
O’Reilly 在线学习
注意
40 多年来,O’Reilly Media提供技术和商业培训、知识和见解,帮助公司取得成功。
我们独特的专家和创新者网络通过书籍、文章和我们的在线学习平台分享他们的知识和专长。O’Reilly 的在线学习平台为您提供按需访问现场培训课程、深入学习路径、交互式编码环境以及来自 O’Reilly 和其他 200 多家出版商的大量文本和视频。欲了解更多信息,请访问https://oreilly.com。
如何联系我们
请将有关本书的评论和问题发送至出版商:
-
O’Reilly Media, Inc.
-
1005 Gravenstein Highway North
-
Sebastopol, CA 95472
-
800-998-9938(美国或加拿大)
-
707-829-0515(国际或本地)
-
707-829-0104(传真)
我们为这本书创建了一个网页,列出勘误、示例和任何其他信息。您可以在https://oreil.ly/essentialMathDataSci访问此页面。
发送电子邮件至bookquestions@oreilly.com以评论或询问有关本书的技术问题。
有关我们的书籍和课程的新闻和信息,请访问https://oreilly.com。
在 LinkedIn 上找到我们:https://linkedin.com/company/oreilly-media
在 Twitter 上关注我们:https://twitter.com/oreillymedia
在 YouTube 上观看我们:https://youtube.com/oreillymedia
致谢
这本书是许多人一年多努力的成果。首先,我要感谢我的妻子 Kimberly 在我写这本书时的支持,特别是在我们抚养儿子 Wyatt 到他一岁生日时。Kimberly 是一位了不起的妻子和母亲,我现在所做的一切都是为了我儿子和我们家庭更美好的未来。
我要感谢我的父母教会我克服极限,永不放弃。考虑到本书的主题,我很高兴他们鼓励我在高中和大学认真学习微积分,没有人可以在不经常走出舒适区的情况下写书。
我要感谢 O’Reilly 出色的编辑和工作人员团队,自 2015 年我写了第一本关于 SQL 的书以来,他们一直在为我打开大门。Jill 和 Jess 在帮助我完成并出版这本书方面表现出色,我很感激 Jess 在提出这个主题时想到了我。
我要感谢南加州大学航空安全与保障项目的同事们。有机会开创人工智能系统安全概念教给我很少人有的见解,我期待我们在未来能继续取得的成就。Arch,你仍然让我感到惊讶,我担心你退休的那一天世界将停止运转。
最后,我要感谢我的兄弟 Dwight Nield 和我的朋友 Jon Ostrower,他们是我创业公司 Yawman Flight 的合作伙伴。创办一家初创公司很困难,他们的帮助让我有宝贵的时间来写这本书。Jon 让我加入南加州大学,他在航空新闻界的不懈成就简直令人瞩目(去搜索一下他吧!)。他们和我一样对我在车库里开始的发明充满热情,我觉得如果没有他们,我无法将它带给世界。
对于我可能遗漏的任何人,感谢你们所做的大小事情。往往我因为好奇和提问而受益。我不会视而不见。正如泰德·拉索所说,“保持好奇,不要带有偏见。”
第一章:基础数学和微积分复习
我们将开始第一章,介绍数字是什么,以及变量和函数在笛卡尔坐标系上是如何运作的。然后我们将讨论指数和对数。之后,我们将学习微积分的两个基本运算:导数和积分。
在我们深入探讨基本数学的应用领域,如概率、线性代数、统计学和机器学习之前,我们可能需要复习一些基本数学和微积分概念。在您放下这本书并尖叫逃跑之前,请不要担心!我将以一种您可能在大学里没有学到的方式呈现如何计算函数的导数和积分。我们有 Python 在我们这边,而不是铅笔和纸。即使您对导数和积分不熟悉,您也不需要担心。
我将尽可能紧凑和实用地讨论这些主题,只关注对我们以后章节有帮助的内容和属于“基本数学”范畴的内容。
这不是一个完整的数学速成课程!
这绝不是高中和大学数学的全面复习。如果您想要这样的内容,可以查看伊万·萨沃夫(请原谅我的法语)的《数学和物理无废话指南》。前几章包含了我见过的最好的高中和大学数学速成课程。理查德·埃尔韦斯博士的《数学 1001》也有一些很棒的内容,并且解释得很简洁。
数论
数字是什么?我承诺在这本书中不会太过哲学,但数字不是我们定义的构造吗?为什么我们有 0 到 9 这些数字,而不是更多的数字?为什么我们有分数和小数,而不只是整数?我们思考数字以及为什么以某种方式设计它们的数学领域被称为数论。
数论可以追溯到古代,当时数学家研究不同的数字系统,它解释了为什么我们今天接受它们的方式。以下是您可能认识的不同数字系统:
自然数
这些是数字 1、2、3、4、5……等等。这里只包括正数,它们是已知最早的系统。自然数如此古老,以至于原始人在骨头和洞穴墙壁上刻划记号来记录。
整数
除了自然数,后来还接受了“0”的概念;我们称这些为“整数”。巴比伦人还开发了有用的空“列”占位符的概念,用于大于 9 的数字,比如“10”、“1,000”或“1,090”。这些零表示该列没有值。
整数
整数包括正整数、负整数以及 0。我们可能认为它们理所当然,但古代数学家深深不信任负数的概念。但是当你从 3 中减去 5 时,你得到-2。这在财务方面特别有用,我们在衡量利润和损失时使用。公元 628 年,一位名叫布拉马古普塔的印度数学家展示了为什么负数对于算术的进展是必要的,因此整数得到了认可。
有理数
任何可以表示为分数的数字,比如 2/3,都是有理数。这包括所有有限小数和整数,因为它们也可以表示为分数,比如 687/100 = 6.87 和 2/1 = 2。它们被称为有理数,因为它们是比率。有理数很快被认为是必要的,因为时间、资源和其他数量并非总是可以用离散单位来衡量。牛奶并非总是以加仑为单位。我们可能需要将其测量为加仑的部分。如果我跑了 12 分钟,实际上跑了 9/10 英里,我就不能被迫以整英里为单位来衡量。
无理数
无理数不能表示为分数。这包括著名的π,某些数字的平方根,如2,以及欧拉数e,我们稍后会学习到。这些数字有无限多的小数位,比如 3.141592653589793238462…
无理数背后有一个有趣的历史。希腊数学家毕达哥拉斯相信所有数字都是有理数。他如此坚信这一点,以至于他创立了一个崇拜数字 10 的宗教。“祝福我们,神圣的数字,你生成了神和人!”他和他的追随者会这样祈祷(为什么“10”如此特别,我不知道)。有一个传说,他的一个追随者希帕索斯通过演示根号 2 证明了并非所有数字都是有理数。这严重干扰了毕达哥拉斯的信仰体系,他回应是将希帕索斯溺死在海中。
无论如何,我们现在知道并非所有数字都是有理数。
实数
实数包括有理数和无理数。在实际操作中,当你进行任何数据科学工作时,你可以将你处理的任何小数视为实数。
复数和虚数
当你对负数取平方根时,就会遇到这种数字类型。虽然虚数和复数在某些类型的问题中具有相关性,但我们大多数时候会避开它们。
在数据科学中,你会发现你的大部分(如果不是全部)工作将使用整数、自然数、整数和实数。虚数可能会在更高级的用例中遇到,比如矩阵分解,我们将在第四章中涉及。
复数和虚数
如果你想了解虚数,YouTube 上有一个很棒的播放列表虚数是真实的,链接在YouTube。
运算顺序
希望你熟悉运算顺序,这是你解决数学表达式中每个部分的顺序。简要回顾一下,记住你要先计算括号中的部分,然后是指数,接着是乘法、除法、加法和减法。你可以通过 PEMDAS(请原谅我亲爱的阿姨莎莉)这个记忆法来记住运算顺序,对应的是括号、指数、乘法、除法、加法和减法的顺序。
举个例子,考虑这个表达式:
2 × (3+2) 2 5 - 4
首先我们计算括号中的部分(3 + 2),结果为 5:
2 × (5) 2 5 - 4
接下来我们解决指数,我们可以看到是对刚刚求和的 5 进行平方。结果是 25:
2 × 25 5 - 4
接下来是乘法和除法。这两者的顺序可以交换,因为除法也是乘法(使用分数)。让我们继续将 2 乘以25 5,得到50 5:
50 5 - 4
接下来我们将进行除法运算,将 50 除以 5,得到 10:
10 - 4
最后,我们进行任何加法和减法。当然,10 - 4 将给我们 6:
10 - 4 = 6
毫无疑问,如果我们要在 Python 中表示这个,我们将打印一个值6.0,如示例 1-1 所示。
示例 1-1. 在 Python 中解决一个表达式
my_value = 2 * (3 + 2)**2 / 5 - 4
print(my_value) # prints 6.0
这可能很基础,但仍然至关重要。在代码中,即使在没有它们的情况下得到正确的结果,也是一个很好的实践,在复杂表达式中大量使用括号,这样你就能控制评估顺序。
这里我将表达式的分数部分用括号括起来,有助于将其与示例 1-2 中的其余部分区分开。
示例 1-2. 在 Python 中利用括号来提高清晰度
my_value = 2 * ((3 + 2)**2 / 5) - 4
print(my_value) # prints 6.0
虽然这两个示例在技术上都是正确的,但后者对于我们这些容易混淆的人来说更清晰。如果你或其他人对你的代码进行更改,括号提供了一个易于参考的操作顺序,以防止更改引起错误。
变量
如果你用 Python 或其他编程语言做过一些脚本编写,你就知道什么是变量。在数学中,变量是一个未指定或未知数字的命名占位符。
你可能有一个表示任意实数的变量x,你可以将该变量乘以 3 而不声明它是什么。在示例 1-3 中,我们从用户那里获取一个变量输入x,并将其乘以 3。
示例 1-3. 在 Python 中一个变量,然后进行乘法
x = int(input("Please input a number\n"))
product = 3 * x
print(product)
对于某些变量类型,有一些标准变量名。 如果这些变量名和概念对您不熟悉,不用担心! 但是一些读者可能会认识到我们使用 theta θ表示角度,beta β表示线性回归中的参数。 希腊符号在 Python 中会使变量名尴尬,所以我们可能会在 Python 中将这些变量命名为theta和beta,如示例 1-4 所示。
示例 1-4. Python 中的希腊变量名
beta = 1.75
theta = 30.0
还要注意,变量名可以被标为下标,以便可以使用多个变量名的实例。 对于实际目的,只需将这些视为单独的变量。 如果你遇到变量x[1],x[2]和x[3],只需将它们视为示例 1-5 中所示的三个单独的变量。
示例 1-5. 在 Python 中表示带下标的变量
x1 = 3 # or x_1 = 3
x2 = 10 # or x_2 = 10
x3 = 44 # or x_3 = 44
函数
函数是定义两个或更多变量之间关系的表达式。 更具体地说,函数接受输入变量(也称为定义域变量或自变量),将它们插入表达式中,然后产生一个输出变量(也称为因变量)。
考虑这个简单的线性函数:
y = 2 x + 1
对于任何给定的 x 值,我们使用该x解决表达式以找到y。 当x = 1 时,y = 3。 当x = 2 时,y = 5。 当x = 3 时,y = 7 等等,如表 1-1 所示。
表 1-1. y = 2x + 1 的不同值
| x | 2x + 1 | y |
|---|---|---|
| 0 | 2(0) + 1 | 1 |
| 1 | 2(1) + 1 | 3 |
| 2 | 2(2) + 1 | 5 |
| 3 | 2(3) + 1 | 7 |
函数很有用,因为它们模拟了变量之间的可预测关系,比如在x温度下我们可以期待多少火灾y。 我们将使用线性函数在第五章中执行线性回归。
你可能会看到的另一个约定是将因变量y明确标记为x的函数,例如f ( x )。 因此,我们可以将一个函数表达为y = 2 x + 1,也可以表达为:
f ( x ) = 2 x + 1
示例 1-6 展示了我们如何在 Python 中声明一个数学函数并进行迭代。
示例 1-6. 在 Python 中声明线性函数
def f(x):
return 2 * x + 1
x_values = [0, 1, 2, 3]
for x in x_values:
y = f(x)
print(y)
当处理实数时,函数的一个微妙但重要的特征是它们通常具有无限多个 x 值和相应的 y 值。 问问自己:我们可以通过函数y = 2 x + 1放入多少个 x 值? 不仅仅是 0、1、2、3…为什么不是 0、0.5、1、1.5、2、2.5、3,如表 1-2 所示?
表 1-2. y = 2x + 1 的不同值
| x | 2x + 1 | y |
|---|---|---|
| 0.0 | 2(0) + 1 | 1 |
| 0.5 | 2(.5) + 1 | 2 |
| 1.0 | 2(1) + 1 | 3 |
| 1.5 | 2(1.5) + 1 | 4 |
| 2.0 | 2(2) + 1 | 5 |
| 2.5 | 2(2.5) + 1 | 6 |
| 3.0 | 2(3) + 1 | 7 |
或者为什么不对x进行四分之一步?或者十分之一的步长?我们可以使这些步长无限小,有效地显示y = 2 x + 1是一个连续函数,对于每个可能的x值,都有一个y值。这使我们可以很好地将我们的函数可视化为一条线,如图 1-1 所示。
图 1-1. 函数 y = 2x + 1 的图形
当我们在二维平面上绘制两个数轴(每个变量一个)时,这被称为笛卡尔平面、x-y 平面或坐标平面。我们追踪给定的 x 值,然后查找相应的 y 值,并将交点绘制为一条线。请注意,由于实数(或小数,如果您喜欢)的性质,存在无限多个x值。这就是为什么当我们绘制函数f(x)时,我们得到一条没有中断的连续线。这条线上有无限多个点,或者该线的任何部分上都有无限多个点。
如果您想使用 Python 绘制这个函数,有许多绘图库可供选择,从 Plotly 到 matplotlib。在本书中,我们将使用 SymPy 来执行许多任务,而我们将首先使用它来绘制函数。SymPy 使用 matplotlib,因此请确保您已安装该软件包。否则,它将在控制台上打印一个丑陋的基于文本的图形。之后,只需使用symbols()将x变量声明给 SymPy,声明您的函数,然后像示例 1-7 和图 1-2 中所示那样绘制它。
示例 1-7. 使用 SymPy 在 Python 中绘制线性函数
from sympy import *
x = symbols('x')
f = 2*x + 1
plot(f)
图 1-2. 使用 SymPy 绘制线性函数
示例 1-8 和图 1-3 是另一个示例,展示了函数f ( x ) = x 2 + 1。
示例 1-8. 绘制指数函数
from sympy import *
x = symbols('x')
f = x**2 + 1
plot(f)
注意在图 1-3 中,我们得到的不是一条直线,而是一个光滑、对称的曲线,称为抛物线。它是连续的,但不是线性的,因为它不会产生直线上的数值。像这样的曲线函数在数学上更难处理,但我们将学习一些技巧,使其变得不那么糟糕。
曲线函数
当一个函数是连续的但是曲线的,而不是线性的和直的时,我们称之为曲线函数。
图 1-3. 使用 SymPy 绘制指数函数
注意函数利用多个输入变量,而不仅仅是一个。例如,我们可以有一个具有独立变量x和y的函数。注意y不像之前的例子中那样是依赖的。
f ( x , y ) = 2 x + 3 y
由于我们有两个独立变量(x和y)和一个因变量(f(x,y)的输出),我们需要在三维上绘制这个图形,以产生一组值的平面,而不是一条线,如示例 1-9 和图 1-4 所示。
示例 1-9。在 Python 中声明具有两个独立变量的函数
from sympy import *
from sympy.plotting import plot3d
x, y = symbols('x y')
f = 2*x + 3*y
plot3d(f)
图 1-4。使用 SymPy 绘制三维函数
无论你有多少个独立变量,你的函数通常只会输出一个因变量。当你解决多个因变量时,你可能会为每个因变量使用单独的函数。
求和
我承诺在这本书中不使用充满希腊符号的方程。然而,有一个是如此常见和有用,我不涵盖它会疏忽大意。一个求和被表示为一个 sigmaΣ,并将元素相加。
例如,如果我想迭代数字 1 到 5,将每个数字乘以 2,然后求和,这里是我如何使用求和表达这个过程。示例 1-10 展示了如何在 Python 中执行这个过程。
∑ i=1 5 2 i = ( 2 ) 1 + ( 2 ) 2 + ( 2 ) 3 + ( 2 ) 4 + ( 2 ) 5 = 30
示例 1-10。在 Python 中执行求和
summation = sum(2*i for i in range(1,6))
print(summation)
请注意,i是一个占位符变量,代表我们在循环中迭代的每个连续索引值,我们将其乘以 2,然后全部求和。当你迭代数据时,你可能会看到像x i这样的变量,表示在索引i处的集合中的元素。
range()函数
回想一下,在 Python 中,range()函数是以结束为准的,也就是说,如果你调用range(1,4),它将迭代数字 1、2 和 3。它排除 4 作为上限。
n也常用来表示集合中的项数,比如数据集中的记录数。这里有一个例子,我们迭代一个大小为n的数字集合,将每个数字乘以 10,然后求和:
∑ i=1 n 10 x i
在示例 1-11 中,我们使用 Python 在四个数字的集合上执行这个表达式。请注意,在 Python(以及大多数编程语言)中,我们通常从索引 0 开始引用项目,而在数学中我们从索引 1 开始。因此,在我们的range()中通过从 0 开始迭代来相应地进行移位。
示例 1-11。在 Python 中对元素求和
x = [1, 4, 6, 2]
n = len(x)
summation = sum(10*x[i] for i in range(0,n))
print(summation)
这就是求和的要点。简而言之,求和Σ表示“将一堆东西加在一起”,并使用索引i和最大值n来表达每次迭代输入到总和中。我们将在本书中看到这些。
指数
指数将一个数字乘以自身指定次数。当你将 2 提升到三次方(用 3 作为上标表示为 2³),这就是将三个 2 相乘:
2 3 = 2 2 2 = 8
底数是我们正在进行指数运算的变量或值,指数是我们将基值相乘的次数。对于表达式2 3,2 是底数,3 是指数。
指数具有一些有趣的性质。假设我们将x 2和x 3相乘。观察当我用简单的乘法展开指数然后合并为单个指数时会发生什么:
x 2 x 3 = ( x x ) ( x x x ) = x 2+3 = x 5
当我们将具有相同底数的指数相乘在一起时,我们简单地将指数相加,这被称为乘法法则。请注意,所有相乘指数的底数必须相同才能应用乘法法则。
接下来让我们探讨除法。当我们将x 2除以x 5时会发生什么?
x 2 x 5 xx xxxxx 1 xx*x 1 x 3 = x -3
如您所见,当我们将x 2除以x 5时,我们可以在分子和分母中消去两个x,留下1 x 3。当一个因子同时存在于分子和分母中时,我们可以消去该因子。
那么x -3呢?这是一个引入负指数的好时机,这是在分数的分母中表示指数运算的另一种方式。举例来说,1 x 3 就等同于x -3:
1 x 3 = x -3
将乘法法则联系起来,我们可以看到它也适用于负指数。为了理解这一点,让我们用另一种方式来解决这个问题。我们可以通过将x 5 的“5”指数变为负数,然后与x 2 相乘来表示这两个指数的除法。当你加上一个负数时,实际上是在执行减法。因此,指数乘法法则将这些相乘的指数相加仍然成立,如下所示:
x 2 x 5 = x 2 1 x 5 = x 2 x -5 = x 2+-5 = x -3
最后但同样重要的是,你能想明白为什么任何底数的指数为 0 时结果是 1 吗?
x 0 = 1
获得这种直觉的最好方法是推理任何数除以自己都是 1。如果你有x 3 x 3,代数上显然可以简化为 1。但是这个表达式也等于x 0:
1 = x 3 x 3 = x 3 x -3 = x 3+-3 = x 0
根据传递性质,即如果a = b且b = c,则a = c,我们知道x 0 = 1。
现在,对于分数指数怎么办?它们是表示根的另一种方式,比如平方根。简要回顾一下,4 问“什么数乘以自己会得到 4?”当然是 2。请注意,4 1/2 和4 是相同的:
4 1/2 = 4 = 2
立方根类似于平方根,但它们寻找一个数,使其乘以自身三次得到一个结果。8 的立方根表示为 8 3 ,问“什么数乘以自身三次得到 8?” 这个数将是 2,因为 2 2 2 = 8 。在指数中,立方根表示为一个分数指数,8 3 可以重新表示为 8 1/3 :
8 1/3 = 8 3 = 2
为了将其完整地回到原点,当你将 8 的立方根乘三次时会发生什么?这将撤销立方根并产生 8. 或者,如果我们将立方根表示为分数指数 8 1/3 ,清楚地表明我们将指数相加以获得指数 1. 这也撤销了立方根:
8 3 8 3 8 3 = 8 1 3 × 8 1 3 × 8 1 3 = 8 1 3+1 3+1 3 = 8 1 = 8
最后一个性质:一个指数的指数将把指数相乘。这被称为幂规则。因此 (8 3 ) 2 将简化为 8 6 :
(8 3 ) 2 = 8 3×2 = 8 6
如果你对这个为什么持怀疑态度,尝试展开它,你会发现总和规则变得清晰:
(8 3 ) 2 = 8 3 8 3 = 8 3+3 = 8 6
最后,当我们有一个分数指数的分子不是 1 时,比如 8 2 3 ,那意味着什么?那是取 8 的立方根然后平方。看一下:
8 2 3 = (8 1 3 ) 2 = 2 2 = 4
是的,无理数可以作为指数,如 8 π ,这是 687.2913. 这可能感觉不直观,这是可以理解的!为了节约时间,我们不会深入探讨这一点,因为这需要一些微积分。但基本上,我们可以通过用有理数近似来计算无理指数。这实际上是计算机所做的,因为它们只能计算到有限的小数位数。
例如 π 有无限多位小数。但如果我们取前 11 位数字,3.1415926535,我们可以将 π 近似为一个有理数 31415926535 / 10000000000. 确实,这给出了大约 687.2913,这应该与任何计算器大致匹配:
8 π ≈ 8 31415926535 10000000000 ≈ 687. 2913
对数
对数是一个数学函数,它找到一个特定数字和底数的幂。起初听起来可能不那么有趣,但实际上它有许多应用。从测量地震到调节立体声音量,对数随处可见。它也经常出现在机器学习和数据科学中。事实上,对数将成为第六章中逻辑回归的关键部分。
通过问“2 的多少次方等于 8?”来开始你的思考。数学上表达这个问题的一种方式是使用x作为指数:
2 x = 8
我们直观地知道答案,x = 3,但我们需要一种更优雅的方式来表达这个常见的数学运算。这就是l o g ( )函数的作用。
l o g 2 8 = x
正如在前面的对数表达式中所看到的,我们有一个底数为 2,正在寻找一个幂以给出 8。更一般地,我们可以将一个变量指数重新表达为一个对数:
a x = b l o g a b = x
代数上讲,这是一种隔离x的方法,这对于解出x很重要。示例 1-12 展示了我们如何在 Python 中计算这个对数。
示例 1-12. 在 Python 中使用对数函数
from math import log
# 2 raised to what power gives me 8?
x = log(8, 2)
print(x) # prints 3.0
当你在像 Python 这样的平台上的log()函数中不提供一个底数参数时,它通常会有一个默认的底数。在一些领域,比如地震测量中,对数的默认底数是 10。但在数据科学中,对数的默认底数是自然常数e。Python 使用后者,我们很快会谈到e。
就像指数一样,对数在乘法、除法、指数运算等方面有几个性质。为了节省时间和专注力,我将在表 1-3 中简要介绍这一点。关键的思想是对数找到一个给定底数的指数,使其结果为某个特定数字。
如果你需要深入了解对数的性质,表 1-3 显示了指数和对数的行为,你可以用作参考。
表 1-3. 指数和对数的性质
| 运算符 | 指数性质 | 对数性质 |
|---|---|---|
| 乘法 | x m × x n = x m+n | l o g ( a × b ) = l o g ( a ) + l o g ( b ) |
| 除法 | x m x n = x m-n | l o g ( a b ) = l o g ( a ) - l o g ( b ) |
| 指数运算 | (x m ) n = x mn | l o g ( a n ) = n × l o g ( a ) |
| 零指数 | x 0 = 1 | l o g ( 1 ) = 0 |
| 倒数 | x -1 = 1 x | l o g ( x -1 ) = l o g ( 1 x ) = - l o g ( x ) |
欧拉数和自然对数
在数学中经常出现的一个特殊数字叫做欧拉数e。它类似于圆周率π,约为 2.71828。e被广泛使用,因为它在数学上简化了许多问题。我们将在指数和对数的背景下讨论e。
欧拉数
在高中时,我的微积分老师在几个指数问题中展示了欧拉数。最后我问道,“Nowe 先生,e到底是什么?它从哪里来?”我记得对涉及兔子种群和其他自然现象的解释从未完全满足过我。我希望在这里给出一个更令人满意的解释。
这是我喜欢发现欧拉数的方式。假设你向某人借出$100,年利率为 20%。通常,利息将每月复利,因此每月的利息将为. 20 / 12 = . 01666。两年后贷款余额将是多少?为了简单起见,假设贷款在这两年内不需要还款(也没有还款)。
将我们迄今学到的指数概念汇总(或者可能翻出一本金融教科书),我们可以得出一个计算利息的公式。它包括了起始投资P的余额A,利率r,时间跨度t(年数),以及期数n(每年的月数)。以下是公式:
A = P × (1+r n) nt
如果我们每个月复利,贷款将增长到$148.69,如下所计算:
A = P × (1+r n) nt 100 × (1+.20 12) 12×2 = 148.6914618
如果你想在 Python 中尝试这个,请使用示例 1-13 中的代码。
示例 1-13。在 Python 中计算复利
from math import exp
p = 100
r = .20
t = 2.0
n = 12
a = p * (1 + (r/n))**(n * t)
print(a) # prints 148.69146179463576
但如果我们每天复利呢?那时会发生什么?将n更改为 365:
A = P × (1+r n) nt 100 × (1+.20 365) 365×2 = 149. 1661279
哎呀!如果我们每天复利而不是每月,我们将在两年后多赚 47.4666 美分。如果我们贪心,为什么不每小时复利呢?下面将显示这样做是否会给我们更多?一年有 8,760 小时,所以将n设为该值:
A = P × (1+r n) nt 100 × (1+.20 8760) 8760×2 = 149. 1817886
啊,我们多挤出了大约 2 美分的利息!但我们是否正在经历收益递减?让我们尝试每分钟复利一次!请注意,一年有 525,600 分钟,所以让我们将该值设为n:
A = P × (1+r n) nt 100 × (1+.20 525600) 525600×2 = 149. 1824584
好的,我们只是在越来越频繁地复利时获得了越来越小的一分钱。所以,如果我继续使这些周期无限小,直到连续复利,这会导致什么?
让我向你介绍欧拉数 e ,约为 2.71828。这是连续复利的公式,“连续”意味着我们不停地复利:
A = P × e rt
回到我们的例子,让我们计算在连续复利两年后我们贷款的余额:
A = P × e rt A = 100 × e .20×2 = 149. 1824698
考虑到每分钟复利使我们得到了 149.1824584 的余额,这并不太令人惊讶。当我们连续复利时,这使我们非常接近 149.1824698 的值。
通常在 Python、Excel 和其他平台中使用 exp() 函数时,你会将 e 作为指数的底数。你会发现 e 如此常用,它是指数和对数函数的默认底数。
示例 1-14 使用 exp() 函数在 Python 中计算连续利息。
示例 1-14. 在 Python 中计算连续利息
from math import exp
p = 100 # principal, starting amount
r = .20 # interest rate, by year
t = 2.0 # time, number of years
a = p * exp(r*t)
print(a) # prints 149.18246976412703
那么我们从哪里得到这个常数 e?比较复利利息公式和连续利息公式。它们在结构上看起来相似,但有一些差异:
A = P × (1+r n) nt A = P × e rt
更具体地说,e 是表达式 (1+1 n) n 随着 n 不断增大而趋近于无穷大的结果。尝试使用越来越大的 n 值进行实验。通过使其变得越来越大,你会注意到一些事情:
(1+1 n) n (1+1 100) 100 = 2.70481382942 (1+1 1000) 1000 = 2.71692393224 (1+1 10000) 10000 = 2.71814592682 (1+1 10000000) 10000000 = 2.71828169413
随着n的增大,收益递减,最终收敛到约为 2.71828 的值,这就是我们的值e。您会发现这个e不仅用于研究人口及其增长,还在数学的许多领域中发挥着关键作用。
本书后面将使用欧拉数来构建第三章中的正态分布和第六章中的逻辑回归。
自然对数
当我们以e作为对数的底数时,我们称之为自然对数。根据平台的不同,我们可能会使用ln()来表示自然对数,而不是log()。因此,为了找到幂次为e的 10,我们不再表达自然对数为l o g e 10,而是简写为l n ( 10 ):
l o g e 10 = l n ( 10 )
然而,在 Python 中,自然对数由log()函数指定。正如前面讨论的,log()函数的默认底数是e。只需将底数的第二个参数留空,它将默认使用e作为底数,如示例 1-15 所示。
示例 1-15。在 Python 中计算 10 的自然对数
from math import log
# e raised to what power gives us 10?
x = log(10)
print(x) # prints 2.302585092994046
我们将在本书中的许多地方使用e。请随意使用 Excel、Python、Desmos.com 或您选择的任何其他计算平台进行指数和对数的实验。制作图表,熟悉这些函数的外观。
极限
正如我们在欧拉数中看到的,当我们永远增加或减少一个输入变量时,输出变量不断接近一个值但永远不会达到它时,一些有趣的想法会出现。让我们正式探索这个想法。
看看这个函数,在图 1-5 中绘制:
f ( x ) = 1 x
图 1-5。一个永远接近 0 但永远不会达到 0 的函数
我们只考虑正x值。注意随着x不断增加,*f(x)*越来越接近 0。有趣的是,*f(x)*实际上永远不会达到 0。它只是永远不断地接近。
因此,这个函数的命运是,当x永远延伸到无穷大时,它将不断接近 0 但永远不会达到 0。我们表达一个永远被接近但永远不被达到的值的方式是通过一个极限:
lim x→∞ 1 x = 0
我们读这个的方式是“当 x 趋近于无穷大时,函数 1/x 趋近于 0(但永远不会达到 0)。”你会经常看到这种“趋近但永远不触及”的行为,特别是当我们深入研究导数和积分时。
使用 SymPy,我们可以计算当x趋近于无穷大时,f ( x ) = 1 x会趋近于什么值(示例 1-16)。注意,∞在 SymPy 中用oo巧妙表示。
示例 1-16。使用 SymPy 计算极限
from sympy import *
x = symbols('x')
f = 1 / x
result = limit(f, x, oo)
print(result) # 0
正如你所见,我们也是通过这种方式发现了欧拉数e。这是将n永远延伸到无穷大的函数的结果:
lim n→∞ (1+1 n) n = e = 2. 71828169413 ...
有趣的是,当我们在 SymPy 中使用极限计算欧拉数(如下面的代码所示)时,SymPy 立即将其识别为欧拉数。我们可以调用evalf()以便实际显示它作为一个数字:
from sympy import *
n = symbols('n')
f = (1 + (1/n))**n
result = limit(f, n, oo)
print(result) # E
print(result.evalf()) # 2.71828182845905
导数
让我们回到谈论函数,并从微积分的角度来看待它们,首先是导数。导数告诉我们函数的斜率,它有助于衡量函数在任意点的变化率。
我们为什么关心导数?它们经常在机器学习和其他数学算法中使用,特别是在梯度下降中。当斜率为 0 时,这意味着我们处于输出变量的最小值或最大值。当我们进行线性回归(第五章)、逻辑回归(第六章)和神经网络(第七章)时,这个概念将会很有用。
让我们从一个简单的例子开始。让我们看看图 1-6 中的函数f ( x ) = x 2。在x = 2处曲线有多“陡峭”?
请注意,我们可以在曲线的任意点测量“陡峭度”,并且可以用切线来可视化这一点。将tangent line想象成“刚好触及”曲线的直线在给定点。它还提供了给定点的斜率。您可以通过创建一条与该 x 值和函数上的一个非常接近的相邻 x 值相交的线来粗略估计给定 x 值处的切线。
取x = 2 和附近值x = 2.1,当传递给函数f ( x ) = x 2时,将得到f(2) = 4 和f(2.1) = 4.41,如图 1-7 所示。通过这两点的结果线的斜率为 4.1。
图 1-6. 观察函数某一部分的陡峭程度
图 1-7. 计算斜率的粗略方法
使用简单的上升-下降公式,您可以快速计算两点之间的斜率m:
m = y 2 -y 1 x 2 -x 1 m = 4.41-4.0 2.1-2.0 m = 4.1
如果我将两点之间的x步长变得更小,比如x = 2 和x = 2.00001,这将导致f(2) = 4 和f(2.00001) = 4.00004,这将非常接近实际斜率 4。因此,步长越小,接近函数中给定点的斜率值就越接近。就像数学中许多重要概念一样,当我们接近无限大或无限小的值时,我们会发现一些有意义的东西。
示例 1-17 展示了 Python 中实现的导数计算器。
示例 1-17. Python 中的导数计算器
def derivative_x(f, x, step_size):
m = (f(x + step_size) - f(x)) / ((x + step_size) - x)
return m
def my_function(x):
return x**2
slope_at_2 = derivative_x(my_function, 2, .00001)
print(slope_at_2) # prints 4.000010000000827
现在好消息是有一种更干净的方法可以计算函数上任何位置的斜率。我们已经在使用 SymPy 绘制图形,但我将向您展示它如何使用符号计算的魔力来执行导数等任务。
当遇到类似f ( x ) = x 2的指数函数时,导数函数将使指数成为乘数,然后将指数减 1,留下导数d dx x 2 = 2 x。 d dx表示关于 x 的导数,这意味着我们正在构建一个以 x 值为目标的导数,以获得其斜率。因此,如果我们想要在x = 2 处找到斜率,并且我们有导数函数,我们只需将该 x 值代入即可获得斜率:
f ( x ) = x 2 d dx f ( x ) = d dx x 2 = 2 x d dx f ( 2 ) = 2 ( 2 ) = 4
如果你打算学习这些规则来手动计算导数,那么有很多关于微积分的书籍可供参考。但也有一些很好的工具可以为您符号性地计算导数。Python 库 SymPy 是免费且开源的,很好地适应了 Python 语法。示例 1-18 展示了如何在 SymPy 中计算f ( x ) = x 2的导数。
示例 1-18. 在 SymPy 中计算导数
from sympy import *
# Declare 'x' to SymPy
x = symbols('x')
# Now just use Python syntax to declare function
f = x**2
# Calculate the derivative of the function
dx_f = diff(f)
print(dx_f) # prints 2*x
哇!通过在 SymPy 中使用symbols()函数声明变量,然后可以继续使用普通的 Python 语法声明函数。之后可以使用diff()来计算导数函数。在示例 1-19 中,我们可以将导数函数转换回普通的 Python,简单地将其声明为另一个函数。
示例 1-19. Python 中的导数计算器
def f(x):
return x**2
def dx_f(x):
return 2*x
slope_at_2 = dx_f(2.0)
print(slope_at_2) # prints 4.0
如果你想继续使用 SymPy,你可以调用subs()函数,将x变量与值2交换,如示例 1-20 所示。
示例 1-20. 使用 SymPy 中的替换功能
# Calculate the slope at x = 2
print(dx_f.subs(x,2)) # prints 4
偏导数
在本书中,我们将遇到另一个概念偏导数,我们将在第 5、6 和 7 章中使用它们。这些是对具有多个输入变量的函数的导数。
这样想吧。与其在一维函数上找到斜率,我们有多个方向上关于多个变量的斜率。对于每个给定变量的导数,我们假设其他变量保持不变。看一下f ( x , y ) = 2 x 3 + 3 y 3 的三维图,你会看到我们有两个变量的两个方向上的斜率。
让我们看看函数f ( x , y ) = 2 x 3 + 3 y 3 。x和y变量分别得到它们自己的导数d dx 和 d dy 。这些代表在多维表面上关于每个变量的斜率值。在处理多个维度时,我们在技术上将这些称为“斜率”梯度。这些是x和y的导数,接着是用 SymPy 计算这些导数的代码:
f ( x , y ) = 2 x 3 + 3 y 3 d dx 2 x 3 + 3 y 3 = 6 x 2 d dy 2 x 3 + 3 y 3 = 9 y 2
示例 1-21 和 图 1-8 展示了我们如何分别使用 SymPy 计算 x 和 y 的偏导数。
示例 1-21. 使用 SymPy 计算偏导数
from sympy import *
from sympy.plotting import plot3d
# Declare x and y to SymPy
x,y = symbols('x y')
# Now just use Python syntax to declare function
f = 2*x**3 + 3*y**3
# Calculate the partial derivatives for x and y
dx_f = diff(f, x)
dy_f = diff(f, y)
print(dx_f) # prints 6*x**2
print(dy_f) # prints 9*y**2
# plot the function
plot3d(f)
图 1-8. 绘制三维指数函数
因此对于 (x,y) 值(1,2),相对于 x 的斜率为 6 ( 1 ) = 6,相对于 y 的斜率为 9 (2) 2 = 36。
链式法则
在 第七章 中,当我们构建神经网络时,我们将需要一种特殊的数学技巧,称为链式法则。当我们组合神经网络层时,我们将不得不解开每一层的导数。但现在让我们通过一个简单的代数示例来学习链式法则。假设你有两个函数:
y = x 2 + 1 z = y 3 - 2
注意这两个函数是相互关联的,因为第一个函数中的 y 是输出变量,但在第二个函数中是输入变量。这意味着我们可以将第一个函数的 y 替换为第二个函数的 z,如下所示:
z = (x 2 +1) 3 - 2
那么 z 对 x 的导数是多少呢?我们已经有了用 x 表示 z 的替换。让我们使用 SymPy 在 示例 1-24 中计算出来。
示例 1-24. 求 z 对 x 的导数
from sympy import *
z = (x**2 + 1)**3 - 2
dz_dx = diff(z, x)
print(dz_dx)
# 6*x*(x**2 + 1)**2
因此,我们对z关于x的导数是6 x (x 2 +1) 2:
dz dx ( (x 2 +1) 3 - 2 ) = 6 x (x 2 +1) 2
但是看这个。让我们重新开始,采用不同的方法。如果我们分别对y和z函数求导,然后将它们相乘,也会得到z关于x的导数!让我们试试:
dy dx ( x 2 + 1 ) = 2 xdz dy ( y 3 - 2 ) = 3 y 2dz dx = ( 2 x ) ( 3 y 2 ) = 6 x y 2
好的,6 x y 2看起来可能不像6 x (x 2 +1) 2,但那只是因为我们还没有替换y函数。这样做,整个dz dx导数将用x而不用y来表示。
dz dx = 6 x y 2 = 6 x (x 2 +1) 2
现在我们看到我们得到了相同的导数函数6 x (x 2 +1) 2!
这就是链式法则,它表示对于给定的函数y(具有输入变量x)组合到另一个函数z(具有输入变量y)中,我们可以通过将两个相应的导数相乘来找到z关于x的导数:
dz dx = dz dy × dy dx
示例 1-25 展示了 SymPy 代码,进行了这种比较,显示链式法则的导数等于替换函数的导数。
示例 1-25. 使用链式法则计算导数 dz/dx,但仍然得到相同的答案
from sympy import *
x, y = symbols('x y')
# derivative for first function
# need to underscore y to prevent variable clash
_y = x**2 + 1
dy_dx = diff(_y)
# derivative for second function
z = y**3 - 2
dz_dy = diff(z)
# Calculate derivative with and without
# chain rule, substitute y function
dz_dx_chain = (dy_dx * dz_dy).subs(y, _y)
dz_dx_no_chain = diff(z.subs(y, _y))
# Prove chain rule by showing both are equal
print(dz_dx_chain) # 6*x*(x**2 + 1)**2
print(dz_dx_no_chain) # 6*x*(x**2 + 1)**2
链式法则是训练神经网络的关键部分,通过适当的权重和偏差。我们可以跨越每个节点乘以导数,而不是以嵌套的洋葱方式解开每个节点的导数,这在数学上要容易得多。
积分
导数的相反是积分,它找到给定范围下曲线下的面积。在第二章和第三章,我们将找到概率分布下的面积。虽然我们不会直接使用积分,而是使用已经被积分的累积密度函数,但了解积分如何找到曲线下的面积是很好的。附录 A 包含了在概率分布上使用这种方法的示例。
我想采用一种直观的方法来学习积分,称为黎曼和,这种方法可以灵活适应任何连续函数。首先,让我们指出,在一条直线下的范围内找到面积是很容易的。假设我有一个函数f ( x ) = 2 x,我想找到在 0 和 1 之间线下的面积,如图 1-9 中所阴影部分所示。
图 1-9. 计算线性函数下的面积
请注意,我正在计算线和 x 轴之间的面积,在 x 范围为 0.0 到 1.0。如果你记得基本几何公式,一个三角形的面积A是A = 1 2 b h,其中b是底边的长度,h是高度。我们可以直观地看到b = 1和h = 2。因此,根据公式计算,我们得到了我们的面积为 1.0:
A = 1 2 b h A = 1 2 1 2 A = 1
那还不错,对吧?但是让我们看一个难以计算面积的函数:f ( x ) = x 2 + 1。在 0 到 1 之间的阴影部分的面积是多少?参见图 1-10。
图 1-10。计算非线性函数下的面积并不那么直接
再次,我们只对 x 范围在 0 到 1 之间的曲线下方和 x 轴上方的面积感兴趣。这里的曲线不给我们一个清晰的几何公式来计算面积,但是这里有一个聪明的小技巧可以做。
如果我们在曲线下方放置五个相等长度的长方形,如图 1-11 所示,其中每个长方形的高度从 x 轴延伸到中点触及曲线的位置,会发生什么?
长方形的面积是A = length × width,因此我们可以轻松地计算长方形的面积。这样做会给我们一个好的曲线下面积的近似值吗?如果我们放置 100 个长方形呢?1,000 个?100,000 个?随着长方形数量的增加和宽度的减小,我们是否会越来越接近曲线下的面积?是的,我们会,这又是一个我们将某物增加/减少至无穷以接近实际值的情况。
图 1-11。放置长方形以近似曲线下的面积
让我们在 Python 中试一试。首先,我们需要一个近似积分的函数,我们将其称为approximate_integral()。参数a和b将分别指定x范围的最小值和最大值。n将是要包含的矩形数,f将是我们要积分的函数。我们在示例 1-26 中实现该函数,然后使用它来积分我们的函数f ( x ) = x 2 + 1,使用五个矩形,在 0.0 和 1.0 之间。
示例 1-26。Python 中的积分近似
def approximate_integral(a, b, n, f):
delta_x = (b - a) / n
total_sum = 0
for i in range(1, n + 1):
midpoint = 0.5 * (2 * a + delta_x * (2 * i - 1))
total_sum += f(midpoint)
return total_sum * delta_x
def my_function(x):
return x**2 + 1
area = approximate_integral(a=0, b=1, n=5, f=my_function)
print(area) # prints 1.33
所以我们得到了一个面积为 1.33。如果我们使用 1,000 个矩形会发生什么?让我们在示例 1-27 中试一试。
示例 1-27。Python 中的另一个积分近似
area = approximate_integral(a=0, b=1, n=1000, f=my_function)
print(area) # prints 1.333333250000001
好的,我们在这里得到了更多的精度,并获得了更多的小数位数。在示例 1-28 中展示了一百万个矩形的情况?
示例 1-28。Python 中的另一个积分近似
area = approximate_integral(a=0, b=1, n=1_000_000, f=my_function)
print(area) # prints 1.3333333333332733
好的,我认为我们在这里得到了一个递减的回报,并收敛于值1. 333 ¯,其中“.333”部分是永远重复的。如果这是一个有理数,很可能是 4/3 = 1. 333 ¯。随着矩形数量的增加,近似值开始在越来越小的小数处达到其极限。
现在我们对我们试图实现的目标和原因有了一些直觉,让我们用 SymPy 做一个更精确的方法,它恰好支持有理数,在示例 1-29 中进行。
示例 1-29。使用 SymPy 执行积分
from sympy import *
# Declare 'x' to SymPy
x = symbols('x')
# Now just use Python syntax to declare function
f = x**2 + 1
# Calculate the integral of the function with respect to x
# for the area between x = 0 and 1
area = integrate(f, (x, 0, 1))
print(area) # prints 4/3
太棒了!所以实际面积是 4/3,这正是我们之前的方法收敛的地方。不幸的是,普通的 Python(以及许多编程语言)只支持小数,但像 SymPy 这样的计算代数系统给我们提供了精确的有理数。我们将在第二章和第三章中使用积分来找到曲线下的面积,尽管我们将让 scikit-learn 来为我们完成这项工作。
结论
在本章中,我们介绍了一些我们将在本书中其余部分中使用的基础知识。从数论到对数和微积分积分,我们强调了一些与数据科学、机器学习和分析相关的重要数学概念。您可能会对为什么这些概念有用有疑问。接下来会解释!
在我们继续讨论概率之前,花点时间再浏览一下这些概念,然后做以下练习。当您在阅读本书的过程中逐步应用这些数学思想时,您可以随时回顾本章并根据需要进行刷新。
练习
-
值 62.6738 是有理数还是无理数?为什么?
-
计算表达式:10 7 10 -5
-
计算表达式:81 1 2
-
计算表达式:25 3 2
-
假设没有还款,一个$1,000 的贷款在 5%的利率下每月复利 3 年后价值多少?
-
假设没有还款,一个$1,000 的贷款在 5%的利率下连续复利 3 年后价值多少?
-
对于函数f ( x ) = 3 x 2 + 1在x = 3 处的斜率是多少?
-
对于函数f ( x ) = 3 x 2 + 1在x在 0 和 2 之间的曲线下的面积是多少?
答案在附录 B 中。
第二章:概率
当你想到概率时,你会想到什么图像?也许你会想到与赌博相关的例子,比如中彩票的概率或者用两个骰子得到一对的概率。也许是预测股票表现、政治选举结果,或者你的航班是否会准时到达。我们的世界充满了我们想要衡量的不确定性。
或许这是我们应该关注的词:不确定性。我们如何衡量我们不确定的事物?
最终,概率是理论研究事件发生的确定性的学科。它是统计学、假设检验、机器学习以及本书中其他主题的基础学科。很多人认为概率理所当然,并假设他们理解它。然而,它比大多数人想象的更加微妙和复杂。虽然概率的定理和思想在数学上是正确的,但当我们引入数据并涉足统计学时,情况就变得更加复杂。我们将在第四章中讨论统计学和假设检验。
在本章中,我们将讨论什么是概率。然后我们将涵盖概率数学概念、贝叶斯定理、二项分布和贝塔分布。
理解概率
概率是我们相信事件发生的强度,通常以百分比表示。以下是一些可能需要概率回答的问题:
-
在 10 次公平抛硬币中,我得到 7 次正面的可能性有多大?
-
我赢得选举的机会有多大?
-
我的航班会晚点吗?
-
我有多大把握说一个产品有瑕疵?
表达概率最流行的方式是以百分比形式,“我的航班晚点的可能性是 70%”。我们将这种概率称为P ( X ),其中X是感兴趣的事件。然而,在处理概率时,你更可能看到它以小数形式表示(在这种情况下为 0.70),它必须介于 0.0 和 1.0 之间:
P ( X ) = .70
可能性类似于概率,很容易混淆这两者(许多词典也是如此)。在日常对话中,你可以随意使用“概率”和“可能性”这两个词。然而,我们应该明确这些区别。概率是关于量化尚未发生事件的预测,而可能性是衡量已经发生事件的频率。在统计学和机器学习中,我们经常使用可能性(过去)的形式来预测概率(未来)。
需要注意的是,事件发生的概率必须严格在 0%和 100%之间,或者 0.0 和 1.0 之间。逻辑上,这意味着事件不发生的概率是通过从 1.0 中减去事件发生的概率来计算的:
P ( X ) = .70 P ( not X ) = 1 - .70 = .30
这是概率和可能性之间的另一个区别。事件的所有可能互斥结果的概率(意味着只能发生一个结果,而不是多个)必须总和为 1.0 或 100%。然而,可能性不受此规则约束。
或者,概率可以表示为赔率O ( X ),例如 7:3,7/3 或2. 333 ¯。
要将赔率O ( X )转换为比例概率P ( X ),请使用以下公式:
P ( X ) = O(X) 1+O(X)
因此,如果我有一个赔率为 7/3,我可以像这样将其转换为比例概率:
P ( X ) = O(X) 1+O(X) P ( X ) = 7 3 1+7 3 P ( X ) = .7
相反,您可以通过将事件发生的概率除以它不会发生的概率来将赔率转换为概率:
O ( X ) = P(X) 1-P(X) O ( X ) = .70 1-.70 O ( X ) = 7 3
概率与统计
有时人们会将概率和统计这两个术语混为一谈,虽然混淆这两个学科是可以理解的,但它们确实有区别。概率纯粹是关于事件发生的可能性,不需要数据。另一方面,统计则无法在没有数据的情况下存在,并利用数据来发现概率,并提供描述数据的工具。
想象一下预测骰子掷出 4 的结果(这是骰子的单数形式)。用纯粹的概率思维方法来解决这个问题,一个人简单地说骰子有六个面。我们假设每个面都是等可能的,所以掷出 4 的概率是 1/6,或 16.666%。
然而,一个狂热的统计学家可能会说:“不!我们需要掷骰子来获取数据。如果我们能掷出 30 次或更多次,而且我们掷得越多,就越好,那么我们才能有数据来确定掷出 4 的概率。” 如果我们假设骰子是公平的,这种方法可能看起来很愚蠢,但如果不是呢?如果是这样,收集数据是发现掷出 4 的概率的唯一方法。我们将在第三章中讨论假设检验。
概率数学
当我们处理一个事件的单一概率P ( X ),即所谓的边际概率时,这个概念是相当直观的,如前所述。但当我们开始结合不同事件的概率时,就变得不那么直观了。
联合概率
假设你有一枚公平的硬币和一枚公平的六面骰子。你想找出硬币翻转为正面和骰子掷出 6 的概率。这是两个不同事件的两个单独概率,但我们想找出这两个事件同时发生的概率。这被称为联合概率。
将联合概率视为一个 AND 运算符。我想找出翻转为正面和掷出 6 的概率。我们希望这两个事件同时发生,那么我们如何计算这个概率?
一枚硬币有两面,一个骰子有六面,所以正面的概率是 1/2,六的概率是 1/6。两个事件同时发生的概率(假设它们是独立的,稍后会详细讨论!)就是简单地将两者相乘:
P ( A AND B ) = P ( A ) × P ( B ) P ( heads ) = 1 2 P ( 6 ) = 1 6 P ( heads AND 6 ) = 1 2 × 1 6 = 1 12 = .08 333 ¯
这很容易理解,但为什么会这样呢?许多概率规则可以通过生成所有可能的事件组合来发现,这源自离散数学中的排列组合知识领域。对于这种情况,生成硬币和骰子之间的每种可能结果,将正面(H)和反面(T)与数字 1 到 6 配对。请注意,我在我们得到正面和一个 6 的结果周围放了星号“*”:
H1 H2 H3 H4 H5 *H6* T1 T2 T3 T4 T5 T6
抛硬币和掷骰子时有 12 种可能的结果。我们感兴趣的只有一个,即“H6”,即得到一个正面和一个 6。因为只有一个结果符合我们的条件,而有 12 种可能的结果,所以得到一个正面和一个 6 的概率是 1/12。
与其生成所有可能的组合并计算我们感兴趣的组合,我们可以再次使用乘法作为找到联合概率的快捷方式。这被称为乘法规则:
P ( A AND B ) = P ( A ) × P ( B ) P ( 正面 AND 6 ) = 1 2 × 1 6 = 1 12 = .08 333 ¯
并集概率
我们讨论了联合概率,即两个或多个事件同时发生的概率。但是如果是事件 A 或 B 发生的概率呢?当我们处理概率的 OR 运算时,这被称为并集概率。
让我们从互斥事件开始,即不能同时发生的事件。例如,如果我掷一个骰子,我不能同时得到 4 和 6。我只能得到一个结果。对于这些情况的并集概率很容易。我只需将它们相加即可。如果我想找到掷骰子得到 4 或 6 的概率,那将是 2/6 = 1/3:
P ( 4 ) = 1 6 P ( 6 ) = 1 6 P ( 4 OR 6 ) = 1 6 + 1 6 = 1 3
非互斥事件又是什么呢,即可以同时发生的事件?让我们回到抛硬币和掷骰子的例子。得到正面或 6 的概率是多少?在你尝试将这些概率相加之前,让我们再次生成所有可能的结果,并突出显示我们感兴趣的结果:
*H1* *H2* *H3* *H4* *H5* *H6* T1 T2 T3 T4 T5 *T6*
这里我们感兴趣的是所有正面的结果以及 6 的结果。如果我们将我们感兴趣的 12 个结果中的 7 个进行比例,7/12,我们得到一个正确的概率为. 58 333 ¯。
但是如果我们将正面和 6 的概率相加会发生什么?我们得到一个不同(并且错误!)的答案:. 666 ¯:
P ( h e a d s ) = 1 2 P ( 6 ) = 1 6 P ( heads OR 6 ) = 1 2 + 1 6 = 4 6 = .666 ¯
为什么会这样?再次研究硬币翻转和骰子结果的组合,看看是否能找到一些可疑之处。注意当我们将概率相加时,我们在“H6”和“T6”中都重复计算了得到 6 的概率!如果这不清楚,请尝试找出得到正面或掷骰子得到 1 至 5 的概率:
P ( h e a d s ) = 1 2 P ( 1 through 5 ) = 5 6 P ( heads OR 1 through 5 ) = 1 2 + 5 6 = 8 6 = 1. 333 ¯
我们得到了 133.333%的概率,这显然是不正确的,因为概率必须不超过 100%或 1.0。问题在于我们重复计算结果。
如果你思考足够长时间,你可能会意识到在并集概率中消除重复计数的逻辑方法是减去联合概率。这被称为概率求和规则,确保每个联合事件只计算一次:
P ( A OR B ) = P ( A ) + P ( B ) - P ( A AND B ) P ( A OR B ) = P ( A ) + P ( B ) - P ( A ) × P ( B )
回到我们计算抛硬币或掷骰子得到正面或 6 的概率的例子,我们需要从并集概率中减去得到正面或 6 的联合概率:
P ( h e a d s ) = 1 2 P ( 6 ) = 1 6 P ( A OR B ) = P ( A ) + P ( B ) - P ( A ) × P ( B ) P ( heads OR 6 ) = 1 2 + 1 6 - ( 1 2 × 1 6 ) = .58 333 ¯
请注意,这个公式也适用于互斥事件。如果事件是互斥的,只允许发生一个结果A或B,那么联合概率P(A AND B)将为 0,因此从公式中消除。然后你只需简单地对事件求和,就像我们之前做的那样。
总之,当你有两个或更多不是互斥的事件之间的并集概率时,请确保减去联合概率,以免概率被重复计算。
条件概率和贝叶斯定理
一个容易让人困惑的概率主题是条件概率的概念,即在事件 B 发生的情况下事件 A 发生的概率。通常表示为P ( A GIVEN B )或P ( A|B )。
假设一项研究声称 85%的癌症患者喝咖啡。你对这个说法有何反应?这是否让你感到惊慌,想要放弃你最喜爱的早晨饮料?让我们首先将其定义为条件概率P ( Coffee given Cancer )或P ( Coffee|Cancer )。这代表了患癌症的人中喝咖啡的概率。
在美国,让我们将这与被诊断患有癌症的人的百分比(根据cancer.gov为 0.5%)和喝咖啡的人的百分比(根据statista.com为 65%)进行比较:
P ( Coffee ) = .65 P ( Cancer ) = .005 P ( Coffee|Cancer ) = .85
嗯……稍微研究一下这些数字,问问咖啡是否真的是问题所在。再次注意,任何时候只有 0.5%的人口患有癌症。然而,65%的人口定期喝咖啡。如果咖啡会导致癌症,我们难道不应该有比 0.5%更高的癌症数字吗?难道不应该接近 65%吗?
这就是比例数字的狡猾之处。它们在没有任何给定上下文的情况下可能看起来很重要,而媒体头条肯定可以利用这一点来获取点击量:“新研究揭示 85%的癌症患者喝咖啡”可能会这样写。当然,这很荒谬,因为我们将一个常见属性(喝咖啡)与一个不常见属性(患癌症)联系起来。
人们很容易被条件概率搞混,因为条件的方向很重要,而且两个条件被混淆为相等。"在你是咖啡饮用者的情况下患癌症的概率"不同于"在你患癌症的情况下是咖啡饮用者的概率"。简单来说:很少有咖啡饮用者患癌症,但很多癌症患者喝咖啡。
如果我们对研究咖啡是否会导致癌症感兴趣,我们实际上对第一个条件概率感兴趣:即咖啡饮用者患癌症的概率。
P ( 咖啡|癌症 ) = .85 P ( 癌症|咖啡 ) = ?
我们如何翻转条件?有一个强大的小公式叫做贝叶斯定理,我们可以用它来翻转条件概率:
P ( A|B ) = P(B|A)P(A) P(B)
如果我们将已有的信息代入这个公式,我们就可以解出咖啡饮用者患癌症的概率:
P ( A|B ) = P(B|A)P(A) P(B) P ( 癌症|咖啡 ) = P(咖啡|癌症)P(咖啡) P(癌症) P ( 癌症|咖啡 ) = .85*.005 .65 = .0065
如果你想在 Python 中计算这个问题,请查看示例 2-1。
示例 2-1. 在 Python 中使用贝叶斯定理
p_coffee_drinker = .65
p_cancer = .005
p_coffee_drinker_given_cancer = .85
p_cancer_given_coffee_drinker = p_coffee_drinker_given_cancer *
p_cancer / p_coffee_drinker
# prints 0.006538461538461539
print(p_cancer_given_coffee_drinker)
因此,某人在是咖啡饮用者的情况下患癌症的概率仅为 0.65%!这个数字与某人在患癌症的情况下是咖啡饮用者的概率(85%)非常不同。现在你明白为什么条件的方向很重要了吗?贝叶斯定理就是为了这个原因而有用。它还可以用于将多个条件概率链接在一起,根据新信息不断更新我们的信念。
如果你想更深入地探讨贝叶斯定理背后的直觉,请参考附录 A。现在只需知道它帮助我们翻转条件概率。接下来让我们讨论条件概率如何与联合和并集操作相互作用。
朴素贝叶斯
贝叶斯定理在一种常见的机器学习算法——朴素贝叶斯中扮演着核心角色。乔尔·格鲁斯在他的书《从零开始的数据科学》(O'Reilly)中对此进行了介绍。
联合和条件概率
让我们重新审视联合概率以及它们与条件概率的相互作用。我想找出某人是咖啡饮用者且患有癌症的概率。我应该将P ( 咖啡 )和P ( 癌症 )相乘吗?还是应该使用P ( 咖啡|癌症 )代替P ( 咖啡 )?我应该使用哪一个?
选项 1: P ( 咖啡 ) × P ( 癌症 ) = .65 × .005 = .00325 选项 2: P ( 咖啡|癌症 ) × P ( 癌症 ) = .85 × .005 = .00425
如果我们已经确定我们的概率仅适用于患癌症的人群,那么使用P ( Coffee|Cancer ) 而不是P ( Coffee ) 是不是更有意义?前者更具体,适用于已经建立的条件。因此,我们应该使用P ( Coffee|Cancer ),因为P ( Cancer ) 已经是我们联合概率的一部分。这意味着某人患癌症并且是咖啡饮用者的概率为 0.425%:
P ( Coffee and Cancer ) = P ( Coffee|Cancer ) × P ( Cancer ) = .85 × .005 = .00425
这种联合概率也适用于另一个方向。我可以通过将P ( Cancer|Coffee ) 和P ( Coffee ) 相乘来找到某人是咖啡饮用者且患癌症的概率。正如你所看到的,我得到了相同的答案:
P ( Cancer|Coffee ) × P ( Coffee ) = .0065 × .65 = .00425
如果我们没有任何条件概率可用,那么我们能做的最好就是将P ( Coffee Drinker ) 和P ( Cancer ) 相乘,如下所示:
P ( Coffee Drinker ) × P ( Cancer ) = .65 × .005 = .00325
现在想想这个:如果事件 A 对事件 B 没有影响,那么这对条件概率P(B|A)意味着什么?这意味着P(B|A) = P(B),也就是说事件 A 的发生不会影响事件 B 发生的可能性。因此,我们可以更新我们的联合概率公式,无论这两个事件是否相关,都可以是:
P ( A AND B ) = P ( B ) × P ( A|B )
最后让我们谈谈并集和条件概率。如果我想计算发生事件 A 或事件 B 的概率,但事件 A 可能会影响事件 B 的概率,我们更新我们的求和规则如下:
P ( A OR B ) = P ( A ) + P ( B ) - P ( A|B ) × P ( B )
作为提醒,这也适用于互斥事件。如果事件 A 和事件 B 不能同时发生,则求和规则P(A|B) × P(B)将得到 0。
二项分布
在本章的其余部分,我们将学习两种概率分布:二项分布和贝塔分布。虽然我们在本书的其余部分不会使用它们,但它们本身是有用的工具,对于理解在一定数量的试验中事件发生的方式至关重要。它们也将是理解我们在第三章中大量使用的概率分布的好起点。让我们探讨一个可能在现实场景中发生的用例。
假设你正在研发一种新的涡轮喷气发动机,并进行了 10 次测试。结果显示有 8 次成功和 2 次失败:
- ✓ ✓ ✓ ✓ ✓ ✘ ✓ ✘ ✓ ✓
你原本希望获得 90%的成功率,但根据这些数据,你得出结论你的测试只有 80%的成功率。每次测试都耗时且昂贵,因此你决定是时候回到起点重新设计了。
然而,你的一位工程师坚持认为应该进行更多的测试。“唯一确定的方法是进行更多的测试,”她辩称。“如果更多的测试产生了 90%或更高的成功率呢?毕竟,如果你抛硬币 10 次得到 8 次正面,这并不意味着硬币固定在 80%。”
你稍微考虑了工程师的论点,并意识到她有道理。即使是一个公平的硬币翻转也不会总是有相同的结果,尤其是只有 10 次翻转。你最有可能得到五次正面,但也可能得到三、四、六或七次正面。你甚至可能得到 10 次正面,尽管这极不可能。那么,如何确定在底层概率为 90%的情况下 80%成功的可能性?
一个在这里可能相关的工具是二项分布,它衡量了在n次试验中,k次成功可能发生的概率,给定了p的概率。
从视觉上看,二项分布看起来像图 2-1。
在这里,我们看到了在 10 次试验中每个柱子代表的k次成功的概率。这个二项分布假设了一个 90%的概率p,意味着成功发生的概率为 0.90(或 90%)。如果这是真的,那么我们在 10 次试验中获得 8 次成功的概率为 0.1937。在 10 次试验中获得 1 次成功的概率极低,为 0.000000008999,这就是为什么柱子几乎看不见。
我们还可以通过将八次或更少成功的柱子相加来计算八次或更少成功的概率。这将给我们八次或更少成功的概率为 0.2639。
图 2-1. 一个二项分布
那么我们如何实现二项分布呢?我们可以相对容易地从头开始实现(如在附录 A 中分享的),或者我们可以使用像 SciPy 这样的库。示例 2-2 展示了我们如何使用 SciPy 的binom.pmf()函数(PMF代表“概率质量函数”)来打印从 0 到 10 次成功的二项分布的所有 11 个概率。
示例 2-2. 使用 SciPy 进行二项分布
from scipy.stats import binom
n = 10
p = 0.9
for k in range(n + 1):
probability = binom.pmf(k, n, p)
print("{0} - {1}".format(k, probability))
# OUTPUT:
# 0 - 9.99999999999996e-11
# 1 - 8.999999999999996e-09
# 2 - 3.644999999999996e-07
# 3 - 8.748000000000003e-06
# 4 - 0.0001377809999999999
# 5 - 0.0014880347999999988
# 6 - 0.011160260999999996
# 7 - 0.05739562800000001
# 8 - 0.19371024449999993
# 9 - 0.38742048900000037
# 10 - 0.34867844010000004
正如你所看到的,我们提供n作为试验次数,p作为每次试验成功的概率,k作为我们想要查找概率的成功次数。我们迭代每个成功次数x与我们将看到该数量成功的相应概率。正如我们在输出中看到的,最可能的成功次数是九。
但是,如果我们将八个或更少成功的概率相加,我们会得到 0.2639。这意味着即使基础成功率为 90%,我们也有 26.39%的机会看到八个或更少的成功。所以也许工程师是对的:26.39%的机会并非没有,而且确实可能。
然而,在我们的模型中我们确实做了一个假设,接下来我们将讨论与 beta 分布相关的内容。
从头开始的二项分布
转到附录 A 了解如何在没有 scikit-learn 的情况下从头开始构建二项分布。
Beta 分布
我在使用二项分布的引擎测试模型时做了什么假设?我是否假设了一个参数为真,然后围绕它构建了整个模型?仔细思考并继续阅读。
我的二项分布可能存在问题的地方在于我假设了基础成功率为 90%。这并不是说我的模型毫无价值。我只是展示了如果基础成功率为 90%,那么在 10 次试验中看到 8 个或更少的成功的概率为 26.39%。所以工程师当然没有错,可能存在一个基础成功率为 90%的情况。
但让我们反过来思考这个问题:如果除了 90%之外还有其他基础成功率可以产生 8/10 的成功呢?我们能否用基础成功率 80%?70%?30%?来看到 8/10 的成功?当我们固定 8/10 的成功时,我们能探索概率的概率吗?
与其创建无数个二项分布来回答这个问题,我们可以使用一个工具。Beta 分布允许我们看到在给定alpha成功和beta失败的情况下事件发生的不同基础概率的可能性。
给出八次成功和两次失败的 beta 分布图表如图 2-2 所示。
图 2-2. Beta 分布
Desmos 上的 Beta 分布
如果你想与 beta 分布互动,可以在这里找到 Desmos 图表。
请注意,x 轴表示从 0.0 到 1.0(0%到 100%)的所有基础成功率,y 轴表示在八次成功和两次失败的情况下给定该概率的可能性。换句话说,beta 分布允许我们看到在 8/10 成功的情况下概率的概率。把它看作是一种元概率,所以花点时间理解这个概念!
还要注意,贝塔分布是一个连续函数,这意味着它形成了一个十进制值的连续曲线(与二项分布中整洁且离散的整数相对)。这将使得贝塔分布的数学运算稍微困难,因为 y 轴上的给定密度值不是概率。我们通过曲线下的面积来找到概率。
贝塔分布是一种概率分布,这意味着整个曲线下的面积为 1.0,或者 100%。要找到概率,我们需要找到一个范围内的面积。例如,如果我们想评估 8/10 次成功将产生 90%或更高成功率的概率,我们需要找到 0.9 和 1.0 之间的面积,即 0.225,如图 2-3 中所阴影部分所示。
图 2-3. 90%到 100%之间的面积,为 22.5%
就像我们使用二项分布一样,我们可以使用 SciPy 来实现贝塔分布。每个连续概率分布都有一个累积密度函数(CDF),用于计算给定 x 值之前的面积。假设我想计算到 90%(0.0 到 0.90)的面积,如图 2-4 中所阴影部分所示。
图 2-4. 计算到 90%(0.0 到 0.90)的面积
使用 SciPy 的beta.cdf()函数非常简单,我需要提供的唯一参数是 x 值,成功次数a和失败次数b,如示例 2-3 所示。
示例 2-3. 使用 SciPy 进行贝塔分布
from scipy.stats import beta
a = 8
b = 2
p = beta.cdf(.90, a, b)
# 0.7748409780000001
print(p)
根据我们的计算,底层成功概率为 90%或更低的概率为 77.48%。
我们如何计算成功概率为 90%或更高的概率,如图 2-5 中所阴影部分所示?
图 2-5. 成功概率为 90%或更高的情况
我们的累积密度函数只计算边界左侧的面积,而不是右侧。想想我们的概率规则,对于概率分布,曲线下的总面积为 1.0。如果我们想找到一个事件的相反概率(大于 0.90 而不是小于 0.90),只需从 1.0 中减去小于 0.90 的概率,剩下的概率将捕获大于 0.90 的情况。图 2-6 说明了我们如何进行这种减法。
图 2-6. 找到成功概率大于 90%的概率
示例 2-4 展示了我们如何在 Python 中计算这种减法操作。
示例 2-4. 在贝塔分布中进行减法以获得正确的面积
from scipy.stats import beta
a = 8
b = 2
p = 1.0 - beta.cdf(.90, a, b)
# 0.22515902199999993
print(p)
这意味着在 8/10 次成功的引擎测试中,成功率为 90% 或更高的概率只有 22.5%。但成功率低于 90% 的概率约为 77.5%。我们在这里的胜算不高,但如果我们感觉幸运,我们可以通过更多的测试来赌那 22.5% 的机会。如果我们的首席财务官为 26 次额外测试提供资金,结果是 30 次成功和 6 次失败,我们的贝塔分布将如 图 2-7 所示。
图 2-7. 30 次成功和 6 次失败后的贝塔分布
注意我们的分布变得更窄,因此更有信心地认为成功率在一个更小的范围内。不幸的是,我们达到 90% 成功率最低的概率已经减少,从 22.5% 降至 13.16%,如 例子 2-5 所示。
例子 2-5. 具有更多试验的贝塔分布
from scipy.stats import beta
a = 30
b = 6
p = 1.0 - beta.cdf(.90, a, b)
# 0.13163577484183708
print(p)
此时,最好离开并停止做测试,除非你想继续赌博对抗那 13.16% 的机会,并希望峰值向右移动。
最后,我们如何计算中间的面积?如果我想找到成功率在 80% 和 90% 之间的概率,如 图 2-8 所示,该怎么办?
图 2-8. 成功率在 80% 和 90% 之间的概率
仔细考虑一下你可能如何处理这个问题。如果我们像在 图 2-9 中那样,从 .80 后面的面积中减去 .90 后面的面积会怎样?
图 2-9. 获取 .80 和 .90 之间的面积
这会给我们提供 .80 和 .90 之间的面积吗?是的,它会,并且会产生一个 .3386 或 33.86% 的概率。这是我们如何在 Python 中计算它的方法(例子 2-6)。
例子 2-6. 使用 SciPy 计算贝塔分布中间区域
from scipy.stats import beta
a = 8
b = 2
p = beta.cdf(.90, a, b) - beta.cdf(.80, a, b)
# 0.33863336200000016
print(p)
贝塔分布是一种迷人的工具,用于根据有限的观察来衡量事件发生与不发生的概率。它使我们能够推理关于概率的概率,并且我们可以在获得新数据时更新它。我们也可以将其用于假设检验,但我们将更加强调使用正态分布和 T 分布来进行这种目的,如 第三章 中所述。
从头开始的贝塔分布
要了解如何从头开始实现贝塔分布,请参考 附录 A。
结论
在本章中,我们涵盖了很多内容!我们不仅讨论了概率的基础知识、逻辑运算符和贝叶斯定理,还介绍了概率分布,包括二项式分布和贝塔分布。在下一章中,我们将讨论更著名的分布之一,正态分布,以及它与假设检验的关系。
如果你想了解更多关于贝叶斯概率和统计的知识,一本很棒的书是Bayesian Statistics the Fun Way,作者是 Will Kurt(No Starch Press)。还可以在 O’Reilly 平台上找到互动的Katacoda 场景。
练习
-
今天有 30%的降雨概率,你的雨伞订单准时到达的概率为 40%。你渴望今天在雨中散步,但没有雨伞你无法做到!
下雨的概率和你的雨伞到达的概率是多少?
-
今天有 30%的降雨概率,你的雨伞订单准时到达的概率为 40%。
只有在不下雨或者你的雨伞到达时,你才能出门办事。
不下雨或者你的雨伞到达的概率是多少?
-
今天有 30%的降雨概率,你的雨伞订单准时到达的概率为 40%。
然而,你发现如果下雨,你的雨伞准时到达的概率只有 20%。
下雨的概率和你的雨伞准时到达的概率是多少?
-
你从拉斯维加斯飞往达拉斯的航班上有 137 名乘客预订了座位。然而,这是拉斯维加斯的一个星期天早上,你估计每位乘客有 40%的可能性不会出现。
你正在努力计算要超售多少座位,以免飞机空飞。
至少有 50 名乘客不会出现的概率有多大?
-
你抛了一枚硬币 19 次,其中 15 次是正面,4 次是反面。
你认为这枚硬币有可能是公平的吗?为什么?
答案在附录 B 中。
第三章:描述性和推断性统计
统计学是收集和分析数据以发现有用发现或预测导致这些发现发生的原因的实践。概率在统计学中经常起着重要作用,因为我们使用数据来估计事件发生的可能性。
统计学可能并不总是受到赞誉,但它是许多数据驱动创新的核心。机器学习本身就是一种统计工具,寻找可能的假设来相关不同变量之间的关系。然而,即使对于专业统计学家来说,统计学也存在许多盲点。我们很容易陷入数据所说的内容,而忘记了要问数据来自何处。随着大数据、数据挖掘和机器学习加速推动统计算法的自动化,这些问题变得更加重要。因此,拥有扎实的统计学和假设检验基础非常重要,这样你就不会把这些自动化处理当作黑匣子。
在本节中,我们将介绍统计学和假设检验的基础知识。从描述性统计开始,我们将学习总结数据的常见方法。之后,我们将进入推断统计,试图根据样本揭示总体属性。
什么是数据?
定义“数据”可能看起来有些奇怪,因为我们都使用并认为理所当然。但我认为有必要这样做。如果你问任何人数据是什么,他们可能会回答类似于“你知道的...数据!就是...你知道的...信息!”而不会深入探讨。现在它似乎被宣传为至关重要的事物。不仅是真相的来源...还有智慧!这是人工智能的燃料,人们相信你拥有的数据越多,你就拥有的真相就越多。因此,你永远不可能拥有足够的数据。它将揭示重新定义你的业务策略所需的秘密,甚至可能创造人工通用智能。但让我提供一个关于数据是什么的实用观点。数据本身并不重要。数据的分析(以及它是如何产生的)是所有这些创新和解决方案的驱动力。
想象一下,如果你拿到一张一个家庭的照片。你能根据这张照片揭示这个家庭的故事吗?如果你有 20 张照片呢?200 张照片?2,000 张照片?你需要多少张照片才能了解他们的故事?你需要他们在不同情况下的照片吗?一个人和一起的照片?和亲戚朋友在一起的照片?在家里和工作中的照片?
数据就像照片一样;它提供了故事的快照。连续的现实和背景并没有完全捕捉到,也没有驱动这个故事的无限数量的变量。正如我们将讨论的,数据可能存在偏见。它可能存在缺口,缺少相关变量。理想情况下,我们希望有无限量的数据捕捉无限数量的变量,有如此之多的细节,我们几乎可以重新创造现实并构建替代现实!但这可能吗?目前,不可能。即使将全球最强大的超级计算机组合在一起,也无法接近以数据形式捕捉整个世界的全部内容。
因此,我们必须缩小范围,使我们的目标变得可行。父亲打高尔夫球的几张战略照片可以很容易地告诉我们他是否擅长高尔夫。但是仅凭照片来解读他的整个人生故事?那可能是不可能的。有很多东西是无法在快照中捕捉到的。这些实际问题也应该在处理数据项目时应用,因为数据实际上只是捕捉特定时间的快照,只捕捉到了它所针对的内容(就像相机一样)。我们需要保持我们的目标集中,这有助于收集相关和完整的数据。如果我们的目标过于宽泛和开放,我们可能会遇到虚假发现和不完整数据集的问题。这种实践,被称为数据挖掘,有其时机和地点,但必须小心进行。我们将在本章末重新讨论这个问题。
即使目标明确,我们仍然可能在数据方面遇到问题。让我们回到确定几张战略照片是否能告诉我们父亲是否擅长高尔夫的问题。也许如果你有一张他挥杆中的照片,你就能看出他的动作是否正确。或者也许如果你看到他在一个洞位上欢呼和击掌,你可以推断他得了一个好成绩。也许你只需拍一张他的记分卡的照片!但重要的是要注意所有这些情况都可能是伪造的或脱离了上下文。也许他在为别人欢呼,或者记分卡不是他的,甚至是伪造的。就像这些照片一样,数据并不能捕捉到背景或解释。这是一个非常重要的观点,因为数据提供线索,而不是真相。这些线索可以引导我们找到真相,也可能误导我们得出错误的结论。
这就是为什么对数据来源感到好奇是如此重要的技能。询问数据是如何创建的,由谁创建的,以及数据未捕捉到什么。很容易陷入数据所说的内容而忘记询问数据来自何处。更糟糕的是,有广泛的观点认为可以将数据填入机器学习算法中,并期望计算机解决所有问题。但正如谚语所说,“垃圾进,垃圾出”。难怪根据 VentureBeat 的数据,只有13%的机器学习项目成功。成功的机器学习项目对数据进行了思考和分析,以及产生数据的过程。
描述性统计与推断性统计
当你听到“统计学”这个词时,你会想到什么?是计算平均值、中位数、众数、图表、钟形曲线和其他用于描述数据的工具吗?这是统计学最常被理解的部分,称为描述性统计,我们用它来总结数据。毕竟,浏览百万条数据记录还是对其进行总结更有意义?我们将首先涵盖统计学的这一领域。
推断性统计试图揭示关于更大总体的属性,通常基于样本。它经常被误解,比描述性统计更难理解。通常我们对研究一个太大以至无法观察的群体感兴趣(例如,北美地区青少年的平均身高),我们必须仅使用该群体的少数成员来推断关于他们的结论。正如你所猜测的那样,这并不容易做到。毕竟,我们试图用一个可能不具代表性的样本来代表一个总体。我们将在探讨过程中探讨这些警告。
总体、样本和偏差
在我们深入研究描述性和推断性统计之前,将一些定义和与之相关的具体示例联系起来可能是一个好主意。
总体是我们想要研究的特定感兴趣的群体,比如“北美地区所有 65 岁以上的老年人”,“苏格兰所有金毛猎犬”,或者“洛斯阿尔托斯高中目前的高中二年级学生”。请注意我们对定义总体的边界。有些边界很宽泛,涵盖了广阔地理区域或年龄组的大群体。其他则非常具体和小,比如洛斯阿尔托斯高中的高中二年级学生。如何确定总体的定义取决于你对研究感兴趣的内容。
样本是总体的一个理想随机和无偏子集,我们用它来推断总体的属性。我们经常不得不研究样本,因为调查整个总体并不总是可能的。当然,如果人口小且易接触,那么获取一些人口是更容易的。但是测量北美地区所有 65 岁以上的老年人?那不太可能实际可行!
总体可以是抽象的!
需要注意的是,人口可以是理论的,而不是实际可触及的。在这些情况下,我们的人口更像是从抽象事物中抽取的样本。这里是我最喜欢的例子:我们对一个机场在下午 2 点到 3 点之间起飞的航班感兴趣,但在那个时间段内的航班数量不足以可靠地预测这些航班的延误情况。因此,我们可能将这个人口视为从所有在下午 2 点到 3 点之间起飞的理论航班中抽取的样本。
这类问题是为什么许多研究人员借助模拟生成数据的原因。模拟可能是有用的,但很少是准确的,因为模拟只捕捉了有限的变量,并且内置了假设。
如果我们要根据样本推断人口的属性,那么样本尽可能随机是很重要的,这样我们才不会扭曲我们的结论。这里举个例子。假设我是亚利桑那州立大学的一名大学生。我想找出美国大学生每周观看电视的平均小时数。我走出宿舍,开始随机询问路过的学生,几个小时后完成了数据收集。问题在哪里?
问题在于我们的学生样本可能存在偏见,这意味着它通过过度代表某一群体而牺牲其他群体来扭曲我们的发现。我的研究将人口定义为“美国的大学生”,而不是“亚利桑那州立大学的大学生”。我只是在一个特定大学对学生进行调查,代表整个美国的所有大学生!这真的公平吗?
不太可能全国各地的大学都具有相同的学生属性。如果亚利桑那州立大学的学生比其他大学的学生观看电视时间更长怎么办?使用他们来代表整个国家难道不会扭曲结果吗?也许这是可能的,因为在亚利桑那州的坦佩市通常太热了,所以看电视是一种常见的消遣(据说,我会知道;我在凤凰城住了很多年)。其他气候较温和的地方的大学生可能会进行更多的户外活动,看电视时间更少。
这只是一个可能的变量,说明用一个大学的学生样本来代表整个美国的大学生是一个不好的主意。理想情况下,我应该随机调查全国各地不同大学的大学生。这样我就有了更具代表性的样本。
然而,偏见并不总是地理性的。假设我竭尽全力在全美各地调查学生。我策划了一个社交媒体活动,在 Twitter 和 Facebook 上让各大学分享调查,这样他们的学生就会看到并填写。我收到了数百份关于全国学生电视习惯的回复,觉得我已经征服了偏见的野兽...或者我真的做到了吗?
如果那些足够在社交媒体上看到投票的学生也更有可能看更多电视呢?如果他们在社交媒体上花很多时间,他们可能不介意娱乐性的屏幕时间。很容易想象他们已经准备好在另一个标签上流媒体 Netflix 和 Hulu!这种特定类型的偏见,即特定群体更有可能加入样本的偏见,被称为自我选择偏差。
糟糕!你就是赢不了,是吗?如果你足够长时间地考虑,数据偏差似乎是不可避免的!而且通常确实如此。许多混杂变量,或者我们没有考虑到的因素,都会影响我们的研究。数据偏差问题昂贵且难以克服,而机器学习尤其容易受到影响。
克服这个问题的方法是真正随机地从整个人口中选择学生,他们不能自愿地加入或退出样本。这是减轻偏见的最有效方法,但正如你所想象的那样,这需要大量协调的资源。
好了,关于人口、样本和偏差的讨论就到此为止。让我们继续进行一些数学和描述性统计。只要记住,数学和计算机不会意识到数据中的偏差。这取决于你作为一名优秀的数据科学专业人员来检测!始终要问关于数据获取方式的问题,然后仔细审查该过程可能如何使数据产生偏差。
机器学习中的样本和偏差
这些与抽样和偏差有关的问题也延伸到机器学习领域。无论是线性回归、逻辑回归还是神经网络,都会使用数据样本来推断预测。如果数据存在偏差,那么它将引导机器学习算法做出有偏见的结论。
这方面有许多记录的案例。刑事司法一直是机器学习的一个棘手应用,因为它一再显示出在每个意义上都存在偏见,由于数据集中存在少数族群,导致对少数族群进行歧视。2017 年,沃尔沃测试了训练过捕捉鹿、麋鹿和驯鹿数据集的自动驾驶汽车。然而,它在澳大利亚没有驾驶数据,因此无法识别袋鼠,更不用说理解它们的跳跃动作了!这两个都是有偏见数据的例子。
描述性统计
描述性统计是大多数人熟悉的领域。我们将介绍一些基础知识,如均值、中位数和众数,然后是方差、标准差和正态分布。
均值和加权均值
均值是一组值的平均值。这个操作很简单:将值相加,然后除以值的数量。均值很有用,因为它显示了观察到的一组值的“重心”在哪里。
均值的计算方式对于人口和样本是相同的。示例 3-1 展示了八个值的样本以及如何在 Python 中计算它们的均值。
示例 3-1. 在 Python 中计算均值
# Number of pets each person owns
sample = [1, 3, 2, 5, 7, 0, 2, 3]
mean = sum(sample) / len(sample)
print(mean) # prints 2.875
正如你所看到的,我们对八个人关于他们拥有的宠物数量进行了调查。样本的总和为 23,样本中的项目数为 8,因此这给我们一个均值为 2.875,因为 23/8 = 2.875。
你将看到两个版本的均值:样本均值x ¯和总体均值μ如此表达:
x ¯ = x 1 +x 2 +x 3 +...+x n n = ∑ x i n μ = x 1 +x 2 +x 3 +...+x n N = ∑ x i N
回想一下求和符号∑表示将所有项目相加。n和N分别代表样本和总体大小,但在数学上它们表示相同的东西:项目的数量。对于称为样本均值x ¯(“x-bar”)和总体均值μ(“mu”)的命名也是如此。无论是x ¯还是μ都是相同的计算,只是根据我们处理的是样本还是总体而有不同的名称。
均值可能对你来说很熟悉,但关于均值有一些不太为人知的东西:均值实际上是一种称为加权均值的加权平均值。我们通常使用的均值给予每个值相同的重要性。但我们可以操纵均值,给每个项目赋予不同的权重:
weighted mean = (x 1 ·w 1 )+(x 2 ·w 2 )+(x 3 ·w 3 )+...(x n ·w n ) w 1 +w 2 +w 3 +...+w n
当我们希望某些值对均值的贡献大于其他值时,这将会很有帮助。一个常见的例子是对学术考试进行加权以得出最终成绩。如果你有三次考试和一次期末考试,我们给予每次考试 20%的权重,期末考试 40%的权重,我们如何表达它在示例 3-2 中。
示例 3-2. 在 Python 中计算加权均值
# Three exams of .20 weight each and final exam of .40 weight
sample = [90, 80, 63, 87]
weights = [.20, .20, .20, .40]
weighted_mean = sum(s * w for s,w in zip(sample, weights)) / sum(weights)
print(weighted_mean) # prints 81.4
我们通过相应的乘法对每个考试分数进行加权,而不是通过值计数进行除法,而是通过权重总和进行除法。权重不必是百分比,因为用于权重的任何数字最终都将被比例化。在示例 3-3 中,我们对每个考试赋予“1”的权重,但对最终考试赋予“2”的权重,使其比考试的权重大一倍。我们仍然会得到相同的答案 81.4,因为这些值仍然会被比例化。
示例 3-3. 在 Python 中计算加权平均值
# Three exams of .20 weight each and final exam of .40 weight
sample = [90, 80, 63, 87]
weights = [1.0, 1.0, 1.0, 2.0]
weighted_mean = sum(s * w for s,w in zip(sample, weights)) / sum(weights)
print(weighted_mean) # prints 81.4
中位数
中位数是一组有序值中间最靠近的值。您按顺序排列值,中位数将是中间的值。如果您有偶数个值,您将平均两个中间值。我们可以看到在示例 3-4 中,我们样本中拥有的宠物数量的中位数为 7:
0, 1, 5, *7*, 9, 10, 14
示例 3-4. 在 Python 中计算中位数
# Number of pets each person owns
sample = [0, 1, 5, 7, 9, 10, 14]
def median(values):
ordered = sorted(values)
print(ordered)
n = len(ordered)
mid = int(n / 2) - 1 if n % 2 == 0 else int(n/2)
if n % 2 == 0:
return (ordered[mid] + ordered[mid+1]) / 2.0
else:
return ordered[mid]
print(median(sample)) # prints 7
当数据被异常值或与其他值相比极端大和小的值所扭曲时,中位数可以成为均值的有用替代。这里有一个有趣的轶事来理解为什么。1986 年,北卡罗来纳大学教堂山分校地理学毕业生的年起薪平均值为22,000。哇,UNC-CH 一定有一个了不起的地理学项目!
但实际上,为什么北卡罗来纳大学的地理学项目如此赚钱呢?嗯...迈克尔·乔丹是他们的毕业生之一。确实,有史以来最著名的 NBA 球员之一,确实是从北卡罗来纳大学获得地理学学位的。然而,他开始他的职业生涯是打篮球,而不是学习地图。显然,这是一个造成了巨大异常值的混杂变量,它严重扭曲了收入平均值。
这就是为什么在受异常值影响较大的情况下(例如与收入相关的数据)中,中位数可能比均值更可取。它对异常值不太敏感,并严格根据它们的相对顺序将数据划分到中间,而不是准确地落在数字线上。当您的中位数与均值非常不同时,这意味着您的数据集具有异常值。
众数
众数是出现频率最高的一组值。当您的数据重复时,想要找出哪些值出现最频繁时,它就变得很有用。
当没有任何值出现超过一次时,就没有众数。当两个值出现的频率相等时,数据集被认为是双峰的。在示例 3-5 中,我们计算了我们的宠物数据集的众数,果然我们看到这是双峰的,因为 2 和 3 都出现最多(并且相等)。
示例 3-5. 在 Python 中计算众数
# Number of pets each person owns
from collections import defaultdict
sample = [1, 3, 2, 5, 7, 0, 2, 3]
def mode(values):
counts = defaultdict(lambda: 0)
for s in values:
counts[s] += 1
max_count = max(counts.values())
modes = [v for v in set(values) if counts[v] == max_count]
return modes
print(mode(sample)) # [2, 3]
在实际应用中,除非您的数据重复,否则众数不经常使用。这通常在整数、类别和其他离散变量中遇到。
方差和标准差
当我们开始讨论方差和标准差时,这就变得有趣起来了。方差和标准差让人们感到困惑的一点是,对于样本和总体有一些计算差异。我们将尽力清楚地解释这些差异。
总体方差和标准差
在描述数据时,我们经常对测量平均值和每个数据点之间的差异感兴趣。这让我们对数据的“分布”有了一种感觉。
假设我对研究我的工作人员拥有的宠物数量感兴趣(请注意,我将这定义为我的总体,而不是样本)。我的工作人员有七个人。
我取得他们拥有的所有宠物数量的平均值,我得到 6.571。让我们从每个值中减去这个平均值。这将展示给我们每个值距离平均值有多远,如表 3-1 所示。
表 3-1. 我的员工拥有的宠物数量
| Value | Mean | Difference |
|---|---|---|
| 0 | 6.571 | -6.571 |
| 1 | 6.571 | -5.571 |
| 5 | 6.571 | -1.571 |
| 7 | 6.571 | 0.429 |
| 9 | 6.571 | 2.429 |
| 10 | 6.571 | 3.429 |
| 14 | 6.571 | 7.429 |
让我们在数轴上用“X”表示平均值来可视化这一点,见图 3-1。
图 3-1. 可视化我们数据的分布,其中“X”是平均值
嗯...现在考虑为什么这些信息有用。这些差异让我们了解数据的分布情况以及值距离平均值有多远。有没有一种方法可以将这些差异合并成一个数字,快速描述数据的分布情况?
你可能会想要取这些差异的平均值,但当它们相加时,负数和正数会互相抵消。我们可以对绝对值求和(去掉负号并使所有值为正)。一个更好的方法是在求和之前对这些差异进行平方。这不仅消除了负值(因为平方一个负数会使其变为正数),而且放大了较大的差异,并且在数学上更容易处理(绝对值的导数不直观)。之后,对平方差异取平均值。这将给我们方差,一个衡量数据分布范围的指标。
这里是一个数学公式,展示如何计算方差:
population variance = (x 1 -mean) 2 +(x 2 -mean) 2 +...+(x n -mean) 2 N
更正式地,这是总体的方差:
σ 2 = ∑(x i -μ) 2 N
在 Python 中计算我们宠物示例的总体方差在示例 3-6 中展示。
示例 3-6. 在 Python 中计算方差
# Number of pets each person owns
data = [0, 1, 5, 7, 9, 10, 14]
def variance(values):
mean = sum(values) / len(values)
_variance = sum((v - mean) ** 2 for v in values) / len(values)
return _variance
print(variance(data)) # prints 21.387755102040813
因此,我的办公室员工拥有的宠物数量的方差为 21.387755。好的,但这到底意味着什么?可以合理地得出结论,更高的方差意味着更广泛的分布,但我们如何将其与我们的数据联系起来?这个数字比我们的任何观察结果都要大,因为我们进行了大量的平方和求和,将其放在完全不同的度量标准上。那么我们如何将其压缩回原来的尺度?
平方的反面是平方根,所以让我们取方差的平方根,得到标准差。这是将方差缩放为以“宠物数量”表示的数字,这使得它更有意义:
σ = ∑(x i -μ) 2 N
要在 Python 中实现,我们可以重用variance()函数并对其结果进行sqrt()。现在我们有了一个std_dev()函数,如示例 3-7 所示。
示例 3-7. 在 Python 中计算标准差
from math import sqrt
# Number of pets each person owns
data = [0, 1, 5, 7, 9, 10, 14]
def variance(values):
mean = sum(values) / len(values)
_variance = sum((v - mean) ** 2 for v in values) / len(values)
return _variance
def std_dev(values):
return sqrt(variance(values))
print(std_dev(data)) # prints 4.624689730353898
运行示例 3-7 中的代码,你会看到我们的标准差约为 4.62 只宠物。因此,我们可以用我们开始的尺度来表示我们的扩散,这使得我们的方差更容易解释。我们将在第五章中看到标准差的一些重要应用。
为什么是平方?
关于方差,如果σ 2中的指数让你感到困扰,那是因为它提示你对其进行平方根运算以获得标准差。这是一个小小的提醒,告诉你正在处理需要进行平方根运算的平方值。
样本方差和标准差
在前一节中,我们讨论了总体的方差和标准差。然而,当我们为样本计算这两个公式时,我们需要应用一个重要的调整:
s 2 = ∑(x i -x ¯) 2 n-1 s = ∑(x i -x ¯) 2 n-1
你注意到了区别吗?当我们对平方差值求平均时,我们除以n-1 而不是总项目数n。为什么要这样做?我们这样做是为了减少样本中的任何偏差,不低估基于我们的样本的总体方差。通过在除数中计算少一个项目的值,我们增加了方差,因此捕捉到了样本中更大的不确定性。
如果我们的宠物数据是一个样本,而不是一个总体,我们应相应地进行调整。在示例 3-8 中,我修改了之前的variance()和std_dev() Python 代码,可选择提供一个参数is_sample,如果为True,则从方差的除数中减去 1。
示例 3-8. 计算样本的标准差
from math import sqrt
# Number of pets each person owns
data = [0, 1, 5, 7, 9, 10, 14]
def variance(values, is_sample: bool = False):
mean = sum(values) / len(values)
_variance = sum((v - mean) ** 2 for v in values) /
(len(values) - (1 if is_sample else 0))
return _variance
def std_dev(values, is_sample: bool = False):
return sqrt(variance(values, is_sample))
print("VARIANCE = {}".format(variance(data, is_sample=True))) # 24.95238095238095
print("STD DEV = {}".format(std_dev(data, is_sample=True))) # 4.99523582550223
请注意,在示例 3-8 中,我的方差和标准差与之前将它们视为总体而不是样本的示例相比有所增加。回想一下,在示例 3-7 中,将其视为总体时,标准差约为 4.62。但是在这里将其视为样本(通过从方差分母中减去 1),我们得到的值约为 4.99。这是正确的,因为样本可能存在偏差,不完全代表总体。因此,我们增加方差(从而增加标准差)以增加对值分布范围的估计。较大的方差/标准差显示出较大范围的不确定性。
就像均值(样本为 x ¯,总体为 μ),你经常会看到一些符号表示方差和标准差。样本和均值的标准差分别由s和σ指定。这里再次是样本和总体标准差的公式:
s = ∑(x i -x ¯) 2 n-1 σ = ∑(x i -μ) 2 N
方差将是这两个公式的平方,撤销平方根。因此,样本和总体的方差分别为s 2和σ 2:
s 2 = ∑(x i -x ¯) 2 n-1 σ 2 = ∑(x i -μ) 2 N
再次,平方有助于暗示应该取平方根以获得标准差。
正态分布
在上一章中我们提到了概率分布,特别是二项分布和贝塔分布。然而,最著名的分布是正态分布。正态分布,也称为高斯分布,是一种对称的钟形分布,大部分质量集中在均值附近,其传播程度由标准差定义。两侧的“尾巴”随着远离均值而变得更细。
图 3-2 是金毛犬体重的正态分布。请注意,大部分质量集中在 64.43 磅的均值附近。
图 3-2. 一个正态分布
发现正态分布
正态分布在自然界、工程、科学和其他领域中经常出现。我们如何发现它呢?假设我们对 50 只成年金毛犬的体重进行抽样,并将它们绘制在数轴上,如图 3-3 所示。
图 3-3. 50 只金毛犬体重的样本
请注意,我们在中心附近有更多的值,但随着向左或向右移动,我们看到的值越来越少。根据我们的样本,看起来我们很不可能看到体重为 57 或 71 磅的金毛犬。但体重为 64 或 65 磅?是的,这显然很可能。
有没有更好的方法来可视化这种可能性,以查看我们更有可能从人群中抽样到哪些金毛犬的体重?我们可以尝试创建一个直方图,它根据相等长度的数值范围将值分组(或“箱”),然后使用柱状图显示每个范围内的值的数量。在图 3-4 中,我们创建了一个将值分组为 0.5 磅范围的直方图。
这个直方图并没有显示出数据的任何有意义的形状。原因是因为我们的箱太小了。我们没有足够大或无限的数据量来有意义地在每个箱中有足够的点。因此,我们必须使我们的箱更大。让我们使每个箱的长度为三磅,如图 3-5 所示。
图 3-4. 一张金毛犬体重的直方图
图 3-5. 一个更有意义的直方图
现在我们有了进展!正如你所看到的,如果我们将箱的大小调整得恰到好处(在本例中,每个箱的范围为三磅),我们开始在数据中看到一个有意义的钟形。这不是一个完美的钟形,因为我们的样本永远不会完全代表人群,但这很可能是我们的样本遵循正态分布的证据。如果我们使用适当的箱大小拟合一个直方图,并将其缩放为面积为 1.0(这是概率分布所需的),我们会看到一个粗略的钟形曲线代表我们的样本。让我们在图 3-6 中将其与我们的原始数据点一起展示。
看着这个钟形曲线,我们可以合理地期望金毛寻回犬的体重最有可能在 64.43(均值)左右,但在 55 或 73 时不太可能。比这更极端的情况变得非常不太可能。
图 3-6。拟合到数据点的正态分布
正态分布的性质
正态分布具有几个重要的属性,使其有用:
-
它是对称的;两侧在均值处完全镜像,这就是中心。
-
大部分质量在均值周围的中心。
-
它具有由标准偏差指定的传播(狭窄或宽广)。
-
“尾部”是最不可能的结果,并且无限接近于零,但永远不会触及零。
-
它类似于自然和日常生活中的许多现象,甚至由于中心极限定理而概括了非正态问题,我们很快会谈论到。
概率密度函数(PDF)
标准偏差在正态分布中起着重要作用,因为它定义了它的“扩散程度”。实际上,它是与均值一起的参数之一。创建正态分布的*概率密度函数(PDF)*如下:
f ( x ) = 1 σ 2 π e -1 2(x-μ σ 2 )
哇,这真是一个冗长的话题,不是吗?我们甚至在第一章中看到我们的朋友欧拉数e和一些疯狂的指数。这是我们如何在 Python 中表达它的示例 3-9。
示例 3-9。Python 中的正态分布函数
# normal distribution, returns likelihood
def normal_pdf(x: float, mean: float, std_dev: float) -> float:
return (1.0 / (2.0 * math.pi * std_dev ** 2) ** 0.5) *
math.exp(-1.0 * ((x - mean) ** 2 / (2.0 * std_dev ** 2)))
这个公式中有很多要解开的内容,但重要的是它接受均值和标准偏差作为参数,以及一个 x 值,这样你就可以查找给定值的可能性。
就像第二章中的贝塔分布一样,正态分布是连续的。这意味着要获取一个概率,我们需要积分一系列x值以找到一个区域。
实际上,我们将使用 SciPy 来为我们进行这些计算。
累积分布函数(CDF)
对于正态分布,垂直轴不是概率,而是数据的可能性。要找到概率,我们需要查看一个给定范围,然后找到该范围下曲线的面积。假设我想找到金毛寻回犬体重在 62 到 66 磅之间的概率。图 3-7 显示了我们要找到面积的范围。
图 3-7。测量 62 到 66 磅之间概率的 CDF
我们已经在第二章中用贝塔分布完成了这个任务,就像贝塔分布一样,这里也有一个累积密度函数(CDF)。让我们遵循这种方法。
正如我们在上一章中学到的,CDF 提供了给定分布的给定 x 值的面积直到。让我们看看我们的金毛寻回犬正态分布的 CDF 是什么样子,并将其与 PDF 放在图 3-8 中进行参考。
图 3-8. PDF 与其 CDF 并排
注意两个图之间的关系。CDF 是一个 S 形曲线(称为 Sigmoid 曲线),它在 PDF 中投影到该范围的面积。观察图 3-9,当我们捕捉从负无穷到 64.43(均值)的面积时,我们的 CDF 显示的值恰好为.5 或 50%!
图 3-9. 金毛寻回犬体重的 PDF 和 CDF,测量均值的概率
这个区域的面积为 0.5 或 50%,这是由于我们正态分布的对称性,我们可以期望钟形曲线的另一侧也有 50%的面积。
要在 Python 中使用 SciPy 计算到 64.43 的面积,使用norm.cdf()函数,如示例 3-10 所示。
示例 3-10. Python 中的正态分布 CDF
from scipy.stats import norm
mean = 64.43
std_dev = 2.99
x = norm.cdf(64.43, mean, std_dev)
print(x) # prints 0.5
就像我们在第二章中所做的那样,我们可以通过减去面积来演绎地找到中间范围的面积。如果我们想找到 62 到 66 磅之间观察到金毛寻回犬的概率,我们将计算到 66 的面积并减去到 62 的面积,如图 3-10 所示。
图 3-10. 寻找中间范围的概率
在 Python 中使用 SciPy 进行这个操作就像在示例 3-11 中所示的两个 CDF 操作相减一样简单。
示例 3-11. 使用 CDF 获取中间范围概率
from scipy.stats import norm
mean = 64.43
std_dev = 2.99
x = norm.cdf(66, mean, std_dev) - norm.cdf(62, mean, std_dev)
print(x) # prints 0.4920450147062894
你应该发现在 62 到 66 磅之间观察到金毛寻回犬的概率为 0.4920,或者大约为 49.2%。
逆 CDF
当我们在本章后面开始进行假设检验时,我们会遇到需要查找 CDF 上的一个区域然后返回相应 x 值的情况。当然,这是对 CDF 的反向使用,所以我们需要使用逆 CDF,它会翻转轴,如图 3-11 所示。
这样,我们现在可以查找一个概率然后返回相应的 x 值,在 SciPy 中我们会使用norm.ppf()函数。例如,我想找到 95%的金毛寻回犬体重在以下。当我使用示例 3-12 中的逆 CDF 时,这很容易做到。
图 3-11. 逆 CDF,也称为 PPF 或分位数函数
示例 3-12. 在 Python 中使用逆 CDF(称为ppf())
from scipy.stats import norm
x = norm.ppf(.95, loc=64.43, scale=2.99)
print(x) # 69.3481123445849
我发现 95%的金毛寻回犬体重为 69.348 磅或更少。
你也可以使用逆 CDF 生成遵循正态分布的随机数。如果我想创建一个模拟,生成一千个真实的金毛寻回犬体重,我只需生成一个介于 0.0 和 1.0 之间的随机值,传递给逆 CDF,然后返回体重值,如示例 3-13 所示。
示例 3-13. 从正态分布生成随机数
import random
from scipy.stats import norm
for i in range(0,1000):
random_p = random.uniform(0.0, 1.0)
random_weight = norm.ppf(random_p, loc=64.43, scale=2.99)
print(random_weight)
当然,NumPy 和其他库可以为您生成分布的随机值,但这突出了逆 CDF 很方便的一个用例。
从头开始实现 CDF 和逆 CDF
要学习如何在 Python 中从头开始实现 CDF 和逆 CDF,请参考附录 A。
Z 分数
将正态分布重新缩放,使均值为 0,标准差为 1 是很常见的,这被称为标准正态分布。这样可以轻松比较一个正态分布的扩展与另一个正态分布,即使它们具有不同的均值和方差。
特别重要的是,标准正态分布将所有 x 值表示为标准差,称为Z 分数。将 x 值转换为 Z 分数使用基本的缩放公式:
z = x-μ σ
这是一个例子。我们有两个来自两个不同社区的房屋。社区 A 的平均房屋价值为3,000。社区 B 的平均房屋价值为10,000。
μ A = 140,000 μ B = 800,000 σ A = 3,000 σ B = 10,000
现在我们有两个来自每个社区的房屋。社区 A 的房屋价值为815,000。哪个房屋相对于其社区的平均房屋更昂贵?
x A = 150,000 x B = 815,000
如果我们将这两个值用标准差表示,我们可以相对于每个社区的均值进行比较。使用 Z 分数公式:
z = x-mean standarddeviation z A = 150000-140000 3000 = 3. 333 ¯ z B = 815000-800000 10000 = 1.5
因此,A 社区的房屋实际上相对于其社区要昂贵得多,因为它们的 Z 分数分别为3. 333 ¯和 1.5。
下面是我们如何将来自具有均值和标准差的给定分布的 x 值转换为 Z 分数,反之亦然,如示例 3-14 所示。
示例 3-14. 将 Z 分数转换为 x 值,反之亦然
def z_score(x, mean, std):
return (x - mean) / std
def z_to_x(z, mean, std):
return (z * std) + mean
mean = 140000
std_dev = 3000
x = 150000
# Convert to Z-score and then back to X
z = z_score(x, mean, std_dev)
back_to_x = z_to_x(z, mean, std_dev)
print("Z-Score: {}".format(z)) # Z-Score: 3.333
print("Back to X: {}".format(back_to_x)) # Back to X: 150000.0
z_score()函数将接受一个 x 值,并根据均值和标准差将其按标准偏差进行缩放。z_to_x()函数将接受一个 Z 分数,并将其转换回 x 值。研究这两个函数,你可以看到它们的代数关系,一个解决 Z 分数,另一个解决 x 值。然后,我们将一个 x 值为 8.0 转换为3. 333 ¯的 Z 分数,然后将该 Z 分数转换回 x 值。
推断统计学
我们迄今为止所涵盖的描述性统计学是常见的。然而,当我们涉及推断统计学时,样本和总体之间的抽象关系得以充分发挥。这些抽象的微妙之处不是你想要匆忙通过的,而是要花时间仔细吸收。正如前面所述,我们作为人类有偏见,很快就会得出结论。成为一名优秀的数据科学专业人员需要你抑制那种原始的欲望,并考虑其他解释可能存在的可能性。推测没有任何解释,某一发现只是巧合和随机的也是可以接受的(也许甚至是开明的)。
首先让我们从为所有推断统计学奠定基础的定理开始。
中心极限定理
正态分布之所以有用的原因之一是因为它在自然界中经常出现,比如成年金毛犬的体重。然而,在自然人口之外的更迷人的背景下,它也会出现。当我们开始从人口中抽取足够大的样本时,即使该人口不遵循正态分布,正态分布仍然会出现。
假设我正在测量一个真正随机且均匀的人口。0.0 到 1.0 之间的任何值都是同等可能的,没有任何值有任何偏好。但当我们从这个人口中抽取越来越大的样本,计算每个样本的平均值,然后将它们绘制成直方图时,会发生一些有趣的事情。在示例 3-15 中运行这段 Python 代码,并观察图 3-12 中的图表。
示例 3-15. 在 Python 中探索中心极限定理
# Samples of the uniform distribution will average out to a normal distribution.
import random
import plotly.express as px
sample_size = 31
sample_count = 1000
# Central limit theorem, 1000 samples each with 31
# random numbers between 0.0 and 1.0
x_values = [(sum([random.uniform(0.0, 1.0) for i in range(sample_size)]) / \
sample_size)
for _ in range(sample_count)]
y_values = [1 for _ in range(sample_count)]
px.histogram(x=x_values, y = y_values, nbins=20).show()
图 3-12. 取样本均值(每个大小为 31)并绘制它们
等等,当作为 31 个一组的均匀随机数取样然后求平均时,为什么会大致形成正态分布?任何数字都是等可能的,对吧?分布不应该是平坦的而不是钟形曲线吗?
发生了什么呢?样本中的单个数字本身不会产生正态分布。分布将是平坦的,其中任何数字都是等可能的(称为均匀分布)。但当我们将它们作为样本分组并求平均时,它们形成一个正态分布。
这是因为中心极限定理,它表明当我们对一个人口取足够大的样本,计算每个样本的均值,并将它们作为一个分布绘制时,会发生有趣的事情:
-
样本均值的平均值等于总体均值。
-
如果总体是正态的,那么样本均值将是正态的。
-
如果总体不是正态分布,但样本量大于 30,样本均值仍然会大致形成正态分布。
-
样本均值的标准差等于总体标准差除以n的平方根:
sample standard deviation = populationstandarddeviation samplesize
为什么上述所有内容很重要?这些行为使我们能够根据样本推断出关于人口的有用信息,即使对于非正态分布的人口也是如此。如果你修改前面的代码并尝试较小的样本量,比如 1 或 2,你将看不到正态分布出现。但当你接近 31 或更多时,你会看到我们收敛到一个正态分布,如图 3-13 所示。
图 3-13。较大的样本量接近正态分布
三十一是统计学中的教科书数字,因为这时我们的样本分布通常会收敛到总体分布,特别是当我们测量样本均值或其他参数时。当你的样本少于 31 个时,这时你必须依赖 T 分布而不是正态分布,随着样本量的减小,正态分布的尾部会变得越来越厚。我们稍后会简要讨论这一点,但首先让我们假设在谈论置信区间和检验时,我们的样本至少有 31 个。
置信区间
你可能听过“置信区间”这个术语,这经常让统计新手和学生感到困惑。置信区间是一个范围计算,显示我们有多大信心相信样本均值(或其他参数)落在总体均值的范围内。
基于 31 只金毛猎犬的样本,样本均值为 64.408,样本标准差为 2.05,我有 95%的信心总体均值在 63.686 和 65.1296 之间。 我怎么知道这个?让我告诉你,如果你感到困惑,回到这段话,记住我们试图实现什么。我有意突出了它!
我首先选择一个置信水平(LOC),这将包含所需概率的总体均值范围。我希望有 95%的信心,我的样本均值落在我将计算的总体均值范围内。这就是我的 LOC。我们可以利用中心极限定理并推断出总体均值的这个范围是什么。首先,我需要关键 z 值,这是在标准正态分布中给出 95%概率的对称范围,如图 3-14 中所示。
我们如何计算包含 0.95 面积的对称范围?这作为一个概念比作为一个计算更容易理解。你可能本能地想使用 CDF,但随后你可能会意识到这里有更多的变动部分。
首先,您需要利用逆 CDF。从逻辑上讲,为了获得中心对称区域的 95%面积,我们将切掉剩余 5%面积的尾部。将剩余的 5%面积分成两半,每个尾部将给我们 2.5%的面积。因此,我们想要查找 x 值的面积是 0.025 和 0.975,如图 3-15 所示。
图 3-14。标准正态分布中心的 95%对称概率
图 3-15。我们想要给出面积为 0.025 和 0.975 的 x 值
我们可以查找面积为 0.025 和面积为 0.975 的 x 值,这将给出包含 95%面积的中心范围。然后我们将返回包含这个区域的相应下限和上限 z 值。请记住,我们在这里使用标准正态分布,因此它们除了是正/负之外是相同的。让我们按照 Python 中示例 3-16 中所示的方式计算这个。
示例 3-16。检索关键的 z 值
from scipy.stats import norm
def critical_z_value(p):
norm_dist = norm(loc=0.0, scale=1.0)
left_tail_area = (1.0 - p) / 2.0
upper_area = 1.0 - ((1.0 - p) / 2.0)
return norm_dist.ppf(left_tail_area), norm_dist.ppf(upper_area)
print(critical_z_value(p=.95))
# (-1.959963984540054, 1.959963984540054)
好的,所以我们得到了±1.95996,这是我们在标准正态分布中心捕获 95%概率的关键 z 值。接下来,我将利用中心极限定理来产生误差边界(E),这是样本均值周围包含在该置信水平下的总体均值的范围。回想一下,我们的 31 只金毛猎犬样本的均值为 64.408,标准差为 2.05。计算这个误差边界的公式是:
E = ± z c s n E = ± 1.95996 * 2.05 31 E = ± 0.72164
如果我们将这个误差边界应用于样本均值,最终我们就得到了置信区间!
95% confidence interval = 64.408 ± 0.72164
这里是如何在 Python 中从头到尾计算这个置信区间的,详见示例 3-17。
示例 3-17. 在 Python 中计算置信区间
from math import sqrt
from scipy.stats import norm
def critical_z_value(p):
norm_dist = norm(loc=0.0, scale=1.0)
left_tail_area = (1.0 - p) / 2.0
upper_area = 1.0 - ((1.0 - p) / 2.0)
return norm_dist.ppf(left_tail_area), norm_dist.ppf(upper_area)
def confidence_interval(p, sample_mean, sample_std, n):
# Sample size must be greater than 30
lower, upper = critical_z_value(p)
lower_ci = lower * (sample_std / sqrt(n))
upper_ci = upper * (sample_std / sqrt(n))
return sample_mean + lower_ci, sample_mean + upper_ci
print(confidence_interval(p=.95, sample_mean=64.408, sample_std=2.05, n=31))
# (63.68635915701992, 65.12964084298008)
因此,解释这个的方式是“基于我的 31 只金毛犬体重样本,样本均值为 64.408,样本标准差为 2.05,我有 95%的信心总体均值在 63.686 和 65.1296 之间。” 这就是我们描述置信区间的方式。
这里还有一件有趣的事情要注意,就是在我们的误差边界公式中,n 变大时,我们的置信区间变得更窄!这是有道理的,因为如果我们有一个更大的样本,我们对于总体均值落在更小范围内的信心就更大,这就是为什么它被称为置信区间。
这里需要注意的一个警告是,为了使这个工作起效,我们的样本大小必须至少为 31 个项目。这回到了中心极限定理。如果我们想将置信区间应用于更小的样本,我们需要使用方差更高的分布(更多不确定性的更胖尾巴)。这就是 T 分布的用途,我们将在本章末讨论它。
在第五章中,我们将继续使用线性回归的置信区间。
理解 P 值
当我们说某事是统计显著时,我们指的是什么?我们经常听到这个词被随意地使用,但在数学上它意味着什么?从技术上讲,这与一个叫做 p 值的东西有关,这对许多人来说是一个难以理解的概念。但我认为当你将 p 值的概念追溯到它的发明时,它会更有意义。虽然这只是一个不完美的例子,但它传达了一些重要的思想。
1925 年,数学家罗纳德·费舍尔在一个聚会上。他的同事穆里尔·布里斯托尔声称她能通过品尝来区分是先倒茶还是先倒牛奶。对这一说法感到好奇,罗纳德当场设置了一个实验。
他准备了八杯茶。四杯先倒牛奶,另外四杯先倒茶。然后他把它们呈现给他的鉴赏家同事,并要求她识别每一杯的倒入顺序。令人惊讶的是,她全部正确识别了,这种情况发生的概率是 70 分之 1,或者 0.01428571。
这个 1.4%的概率就是我们所谓的p 值,即某事发生的概率是偶然而非因为一个假设的解释。不深入组合数学的兔子洞,穆里尔完全猜对杯子的概率是 1.4%。这到底告诉了你什么?
当我们设计一个实验,无论是确定有机甜甜圈是否导致体重增加,还是住在电力线附近是否导致癌症,我们总是要考虑到随机运气起了作用的可能性。就像穆里尔仅仅通过猜测正确识别茶杯的概率为 1.4%一样,总是有一种可能性,即随机性给了我们一个好手牌,就像老虎机一样。这有助于我们构建我们的零假设(H[0]),即所研究的变量对实验没有影响,任何积极的结果只是随机运气。备择假设(H[1]) 提出所研究的变量(称为控制变量) 导致了积极的结果。
传统上,统计显著性的阈值是 5%或更低的 p 值,即 0.05。由于 0.014 小于 0.05,这意味着我们可以拒绝零假设,即穆里尔是随机猜测的。然后我们可以提出备择假设,即穆里尔有特殊能力判断是先倒茶还是牛奶。
现在,这个茶会的例子没有捕捉到的一点是,当我们计算 p 值时,我们捕捉到了所有该事件或更罕见事件的概率。当我们深入研究下一个使用正态分布的例子时,我们将解决这个问题。
假设检验
过去的研究表明,感冒的平均恢复时间为 18 天,标准偏差为 1.5 天,并且符合正态分布。
这意味着在 15 到 21 天之间恢复的概率约为 95%,如图 3-16 和示例 3-18 所示。
图 3-16。在 15 到 21 天之间有 95%的恢复机会。
示例 3-18。计算在 15 到 21 天之间恢复的概率
from scipy.stats import norm
# Cold has 18 day mean recovery, 1.5 std dev
mean = 18
std_dev = 1.5
# 95% probability recovery time takes between 15 and 21 days.
x = norm.cdf(21, mean, std_dev) - norm.cdf(15, mean, std_dev)
print(x) # 0.9544997361036416
我们可以从剩下的 5%概率推断出,恢复需要超过 21 天的概率为 2.5%,而少于 15 天的概率也为 2.5%。记住这一点,因为这将在后面至关重要!这驱动了我们的 p 值。
现在假设一个实验性新药物被给予了一个由 40 人组成的测试组,他们从感冒中恢复需要平均 16 天,如图 3-17 所示。
图 3-17。服用药物的一组人恢复需要 16 天。
这种药物有影响吗?如果你思考足够长时间,你可能会意识到我们所问的是:这种药物是否显示出统计上显著的结果?或者这种药物没有起作用,16 天的恢复只是与测试组的巧合?第一个问题构成了我们的备择假设,而第二个问题构成了我们的零假设。
我们可以通过两种方式来计算这个问题:单尾检验和双尾检验。我们将从单尾检验开始。
单尾检验
当我们进行单尾检验时,通常使用不等式来构建零假设和备择假设。我们假设围绕着总体均值,并说它要么大于/等于 18(零假设 H 0 ),要么小于 18(备择假设 H 1 ):
H 0 : 总体 均值 ≥ 18 H 1 : 总体 均值 < 18
要拒绝我们的零假设,我们需要表明服用药物的患者的样本均值不太可能是巧合的。由于传统上认为小于或等于.05 的 p 值在统计上是显著的,我们将使用这个作为我们的阈值(图 3-17)。当我们在 Python 中使用逆 CDF 计算时,如图 3-18 和示例 3-19 所示,我们发现大约 15.53 是给我们左尾部分.05 面积的恢复天数。
图 3-18. 获取其后面 5%面积的 x 值
示例 3-19. 获取其后面 5%面积的 x 值的 Python 代码
from scipy.stats import norm
# Cold has 18 day mean recovery, 1.5 std dev
mean = 18
std_dev = 1.5
# What x-value has 5% of area behind it?
x = norm.ppf(.05, mean, std_dev)
print(x) # 15.53271955957279
因此,如果我们在样本组中实现平均 15.53 天或更少的恢复时间,我们的药物被认为在统计上足够显著地显示了影响。然而,我们的恢复时间的样本均值实际上是 16 天,不在这个零假设拒绝区域内。因此,如图 3-19 所示,统计显著性测试失败了。
图 3-19. 我们未能证明我们的药物测试结果在统计上是显著的
到达那个 16 天标记的面积是我们的 p 值,为.0912,并且我们在 Python 中计算如示例 3-20 所示。
示例 3-20. 计算单尾 p 值
from scipy.stats import norm
# Cold has 18 day mean recovery, 1.5 std dev
mean = 18
std_dev = 1.5
# Probability of 16 or less days
p_value = norm.cdf(16, mean, std_dev)
print(p_value) # 0.09121121972586788
由于.0912 的 p 值大于我们的统计显著性阈值.05,我们不认为药物试验是成功的,并且未能拒绝我们的零假设。
双尾检验
我们之前进行的测试称为单尾检验,因为它只在一个尾部寻找统计显著性。然而,通常更安全和更好的做法是使用双尾检验。我们将详细说明原因,但首先让我们计算它。
要进行双尾检验,我们将零假设和备择假设构建在“相等”和“不相等”的结构中。在我们的药物测试中,我们会说零假设的平均恢复时间为 18 天。但我们的备择假设是平均恢复时间不是 18 天,这要归功于新药物:
H 0 : 总体 均值 = 18 H 1 : 总体 均值 ≠ 18
这有一个重要的含义。我们构建我们的备择假设不是测试药物是否改善感冒恢复时间,而是是否有任何影响。这包括测试它是否增加了感冒的持续时间。这有帮助吗?记住这个想法。
自然地,这意味着我们将 p 值的统计显著性阈值扩展到两个尾部,而不仅仅是一个。如果我们正在测试 5% 的统计显著性,那么我们将其分割,并将每个尾部分配 2.5%。如果我们的药物的平均恢复时间落在任一区域内,我们的测试将成功,并拒绝零假设(图 3-20)。
图 3-20. 双尾检验
下尾和上尾的 x 值分别为 15.06 和 20.93,这意味着如果我们低于或高于这些值,我们将拒绝零假设。这两个值是使用图 3-21 和示例 3-21 中显示的逆 CDF 计算的。记住,为了得到上尾,我们取 .95 然后加上显著性阈值的 .025 部分,得到 .975。
图 3-21. 计算正态分布中心 95% 的区域
示例 3-21. 计算 5% 统计显著性范围
from scipy.stats import norm
# Cold has 18 day mean recovery, 1.5 std dev
mean = 18
std_dev = 1.5
# What x-value has 2.5% of area behind it?
x1 = norm.ppf(.025, mean, std_dev)
# What x-value has 97.5% of area behind it
x2 = norm.ppf(.975, mean, std_dev)
print(x1) # 15.060054023189918
print(x2) # 20.93994597681008
药物测试组的样本均值为 16,16 既不小于 15.06 也不大于 20.9399。因此,就像单尾检验一样,我们仍然未能拒绝零假设。我们的药物仍然没有显示出任何统计显著性,也没有任何影响,如图 3-22 所示。
图 3-22. 双尾检验未能证明统计显著性
但是 p 值是什么?这就是双尾检验变得有趣的地方。我们的 p 值将捕捉不仅仅是 16 左侧的区域,还将捕捉右尾的对称等效区域。由于 16 比均值低 4 天,我们还将捕捉高于 20 的区域,即比均值高 4 天(图 3-23)。这是捕捉事件或更罕见事件的概率,位于钟形曲线的两侧。
图 3-23. p 值添加对称侧以获得统计显著性
当我们将这两个区域相加时,我们得到一个 p 值为 .1824。这远大于 .05,因此绝对不符合我们的 p 值阈值为 .05(示例 3-22)。
示例 3-22. 计算双尾 p 值
from scipy.stats import norm
# Cold has 18 day mean recovery, 1.5 std dev
mean = 18
std_dev = 1.5
# Probability of 16 or less days
p1 = norm.cdf(16, mean, std_dev)
# Probability of 20 or more days
p2 = 1.0 - norm.cdf(20, mean, std_dev)
# P-value of both tails
p_value = p1 + p2
print(p_value) # 0.18242243945173575
那么为什么在双尾检验中还要添加对称区域的相反侧?这可能不是最直观的概念,但首先记住我们如何构建我们的假设:
H 0 : 总体 均值 = 18 H 1 : 总体 均值 ≠ 18
如果我们在“等于 18”与“不等于 18”的情况下进行测试,我们必须捕捉两侧任何等于或小于的概率。毕竟,我们试图证明显著性,这包括任何同样或更不可能发生的事情。我们在只使用“大于/小于”逻辑的单尾检验中没有这种特殊考虑。但当我们处理“等于/不等于”时,我们的兴趣范围向两个方向延伸。
那么双尾检验有什么实际影响?它如何影响我们是否拒绝零假设?问问自己:哪一个设定了更高的阈值?你会注意到,即使我们的目标是表明我们可能减少了某些东西(使用药物减少感冒恢复时间),重新构建我们的假设以显示任何影响(更大或更小)会创建一个更高的显著性阈值。如果我们的显著性阈值是小于或等于 0.05 的 p 值,我们的单尾检验在 p 值 0.0912 时更接近接受,而双尾检验则是大约两倍的 p 值 0.182。
这意味着双尾检验更难拒绝零假设,并需要更强的证据来通过测试。还要考虑这一点:如果我们的药物可能加重感冒并使其持续时间更长呢?捕捉这种可能性并考虑该方向的变化可能会有所帮助。这就是为什么在大多数情况下双尾检验更可取。它们往往更可靠,不会偏向于某一个方向的假设。
我们将在第五章和第六章再次使用假设检验和 p 值。
T 分布:处理小样本
让我们简要讨论如何处理 30 个或更少的较小样本;当我们在第五章进行线性回归时,我们将需要这个。无论是计算置信区间还是进行假设检验,如果样本中有 30 个或更少的项目,我们会选择使用 T 分布而不是正态分布。T 分布类似于正态分布,但尾部更胖,以反映更多的方差和不确定性。图 3-24 显示了一个正态分布(虚线)和一个自由度为 1 的 T 分布(实线)。
图 3-24。T 分布与正态分布并列;请注意更胖的尾部。
样本大小越小,T 分布的尾部就越胖。但有趣的是,在接近 31 个项目后,T 分布几乎无法与正态分布区分开来,这很好地反映了中心极限定理的思想。
示例 3-23 展示了如何找到 95%置信度的临界 t 值。当样本量为 30 或更少时,您可以在置信区间和假设检验中使用这个值。概念上与关键 z 值相同,但我们使用 T 分布而不是正态分布来反映更大的不确定性。样本量越小,范围越大,反映了更大的不确定性。
示例 3-23. 用 T 分布获取临界值范围
from scipy.stats import t
# get critical value range for 95% confidence
# with a sample size of 25
n = 25
lower = t.ppf(.025, df=n-1)
upper = t.ppf(.975, df=n-1)
print(lower, upper)
-2.063898561628021 2.0638985616280205
请注意,df是“自由度”参数,正如前面所述,它应该比样本量少一个。
大数据考虑和得克萨斯神枪手谬误
在我们结束本章之前,最后一个想法。正如我们所讨论的,随机性在验证我们的发现中起着如此重要的作用,我们总是要考虑到它的可能性。不幸的是,随着大数据、机器学习和其他数据挖掘工具的出现,科学方法突然变成了一种倒退的实践。这可能是危险的;请允许我演示一下,从加里·史密斯的书标准偏差(Overlook Press)中借鉴一个例子。
假设我从一副公平的牌中抽取四张牌。这里没有游戏或目标,只是抽取四张牌并观察它们。我得到两个 10,一个 3 和一个 2。我说:“这很有趣,我得到两个 10,一个 3 和一个 2。这有意义吗?接下来我抽的四张牌也会是两个连续的数字和一对吗?这里的潜在模型是什么?”
看到我做了什么吗?我拿了完全随机的东西,不仅寻找了模式,而且试图从中建立一个预测模型。这里微妙发生的是,我从未把得到这四张具有特定模式的牌作为我的目标。我是在它们发生之后观察到它们。
这正是数据挖掘每天都会遇到的问题:在随机事件中找到巧合的模式。随着大量数据和快速算法寻找模式,很容易找到看起来有意义但实际上只是随机巧合的东西。
这也类似于我朝墙壁开枪。然后我在弹孔周围画一个靶子,然后邀请朋友过来炫耀我的惊人射击技巧。荒谬,对吧?嗯,很多数据科学家每天都在比喻性地做这件事,这就是得克萨斯神枪手谬误。他们开始行动而没有目标,偶然发现了一些罕见的东西,然后指出他们发现的东西在某种程度上具有预测价值。
问题在于大数定律表明罕见事件很可能会发生;我们只是不知道是哪些。当我们遇到罕见事件时,我们会突出显示甚至推测可能导致它们的原因。问题在于:某个特定人赢得彩票的概率极低,但总会有人赢得彩票。当有人赢得彩票时,我们为什么会感到惊讶?除非有人预测了赢家,否则除了一个随机的人走运之外,没有发生任何有意义的事情。
这也适用于相关性,我们将在第五章中学习。在具有数千个变量的庞大数据集中,很容易找到具有 0.05 p 值的统计显著性发现吗?当然!我会找到成千上万个这样的发现。我甚至会展示证据表明尼古拉斯·凯奇电影的数量与一年中溺水的人数相关。
因此,为了防止得到德克萨斯神枪手谬误并成为大数据谬论的受害者,请尝试使用结构化的假设检验并为此目标收集数据。如果您使用数据挖掘,请尝试获取新鲜数据以查看您的发现是否仍然成立。最后,始终考虑事情可能是巧合的可能性;如果没有常识性的解释,那么很可能是巧合。
我们学会了在收集数据之前进行假设,但数据挖掘是先收集数据,然后进行假设。具有讽刺意味的是,我们在开始时更客观,因为我们会寻找数据来有意识地证明和反驳我们的假设。
结论
我们在本章学到了很多,你应该为走到这一步感到自豪。这可能是本书中较难的主题之一!我们不仅学习了从平均值到正态分布的描述性统计,还解决了置信区间和假设检验的问题。
希望你能稍微不同地看待数据。数据是某种东西的快照,而不是对现实的完全捕捉。单独的数据并不是很有用,我们需要背景、好奇心和分析数据来源的地方,然后我们才能对其进行有意义的洞察。我们讨论了如何描述数据以及根据样本推断更大人口的属性。最后,我们解决了一些数据挖掘中可能遇到的谬论,以及如何通过新鲜数据和常识来纠正这些谬论。
如果您需要回顾本章内容,不要感到难过,因为需要消化的内容很多。如果您想在数据科学和机器学习职业中取得成功,进入假设检验的思维模式也很重要。很少有从业者将统计和假设检验概念与机器学习联系起来,这是不幸的。
理解性和可解释性是机器学习的下一个前沿,因此在阅读本书的其余部分和职业生涯中继续学习和整合这些想法。
练习
-
你购买了一卷 1.75 毫米的线材用于你的 3D 打印机。你想测量线材直径与 1.75 毫米的实际接近程度。你使用卡钳工具在卷轴上抽取了五次直径值:
1.78, 1.75, 1.72, 1.74, 1.77
计算这组数值的均值和标准差。
-
一家制造商表示 Z-Phone 智能手机的平均使用寿命为 42 个月,标准差为 8 个月。假设服从正态分布,一个随机的 Z-Phone 使用寿命在 20 到 30 个月之间的概率是多少?
-
我怀疑我的 3D 打印机线材的平均直径不是广告中宣传的 1.75 毫米。我用我的工具抽取了 34 个测量值。样本均值为 1.715588,样本标准差为 0.029252。
我整个线轴的均值的 99% 置信区间是多少?
-
你的营销部门开始了一项新的广告活动,并想知道它是否影响了销售。过去销售额平均为每天 552。新的广告活动持续了 45 天,平均销售额为 $11,641。
这次广告活动是否影响了销售?为什么?(使用双尾检验以获得更可靠的显著性。)
答案在附录 B 中。