攻读数据科学硕士学位前你必须考虑的 8 件事
别急着用你的 Pinterest 板块:回到大学对你来说是否是正确的选择?
·发布在 Towards Data Science ·10 分钟阅读·2023 年 4 月 14 日
–
近年来,数据科学相关的硕士项目数量激增。根据 FindAMasters.com 的数据[1],全球范围内的大学目前提供超过 3,500 个与数据科学和人工智能相关的硕士学位。
显然,这一领域有很大的需求,大学擅长利用这一点。几乎每天我都会被互联网的神灵用一些华丽的、营销到位的大学广告轰炸,谈论“数据是新的石油”。
如果你在考虑攻读该领域的硕士学位,理性评估入学的利弊可能会非常困难。显然,大学会说这是一个好主意:他们希望得到你的钱!但这真的是一个好主意吗?尤其对你来说,这是否真的是一个好主意?
我写这篇文章是因为我想帮助你做出明智的决定,并考虑所有重要的问题。我自己确实拥有数据科学硕士学位,虽然我认为这对我来说是值得的时间和金钱投资,但我也相信硕士学位并不适合所有人。我的希望是这些 8 个考虑因素能帮助你判断这是否对你来说是正确的选择。
你为什么想攻读硕士学位?
首先,你需要搞清楚是什么驱动你考虑攻读数据科学硕士学位。
对,我说的是你:不是来自科罗拉多州的二年级学生鲍勃,他在 r/datascience 上写过帖子;也不是来自班加罗尔的阿尼鲁德,他在 Quora 上写过关于从软件工程转行的帖子。你。每个人的理由都不同,那么你的理由是什么?
我从这一点开始,是因为你的动机会极大地影响你应听取的建议。如果你没有明确的动机,就无法过滤掉不相关的建议,最终会阅读大量无法解决你特定问题的垃圾信息。
从我所见,最常见的动机有:
-
职业相关:例如,获得(潜在)雇主的信誉,提高获得工作或晋升的机会。
-
学习相关:你希望尽可能快地学习更多内容。也许你尝试过在晚上和周末学习,但进展缓慢让你感到沮丧。对你来说,主要的动力是能够“全力以赴”,快速学习。
-
智力好奇心:也许你已经有了 AI 方面的基础知识,但有一个特定的领域或话题你非常想探索。对你而言,硕士学位的主要吸引力是有机会从生活中抽身,专注于探索自己的兴趣。
-
责任感:你知道自我驱动学习并不总是一致的,所以你需要一个切实的目标(比如硕士学位),这会让你在学习中保持责任感和一致性。
-
冒名顶替综合症:你已经在该行业有了一份工作,但没有正式的数据科学背景,想要通过官方资格来获得验证。
这些感觉与你产生共鸣吗?类似的想法是否曾经出现在你的脑海中?在你对自己的驱动力有明确的了解之前,很难公平地评估是否应该报名。
对我而言,我的动机是职业和学习相关原因的结合。在决定报名之前,我与许多高级数据科学家交谈,他们都告诉我,获得硕士学位对于从事数据科学职业并非客观上必要。我也已经有了良好的本科学位(因此我不需要向雇主证明我的学术能力),而且我看到有足够的在线数据科学短期课程可以让我自学硕士课程中的内容。然而,作为一个完全没有 AI 背景的人(我之前的工作是在销售和市场营销领域),我知道自己会从一个密集的学习阶段中受益,感觉来牛津大学是一个难得的机会。
这是必要的吗?
一旦你确定了为什么考虑攻读硕士学位,你需要问自己:硕士学位是否真的对实现我的目标是必要的?
例如,假设你攻读硕士学位的主要动机是获得领域内的信誉,并增加获得工作/晋升的机会。你能否确定硕士学位会对此有所帮助?我知道它可能(理论上)会,但是否真的会?
很可能,答案不像你想象的那么简单:这要看情况。例如,如果你已经有了一个与 STEM 相关的学位,那么获得硕士学位可能对提升你的就业机会帮助不大。这在你已经拥有如硕士或博士这样的高层次资格时尤为明显。这些资格已经展示了你对数字的能力,而另一个学位可能不会在潜在雇主眼中增加太多的价值。相反,你职业发展的限制因素可能仅仅是你在数据/分析方面缺乏商业经验。在这种情况下,你不如尝试弥补简历中的“经验”空白,例如获得实习或较低级别的工作。
我的观点是,你不应该假设获得硕士学位会显著帮助你的职业发展。判断它是否必要的简单方法是与公司/行业中你特别感兴趣的人员交谈。试试看:联系一些你希望工作公司的高级/首席数据科学家,询问他们是否认为硕士学位是进入这些公司工作的前提条件。
正如我之前讨论的那样,在我看来,获得硕士学位是有帮助的,但我不会说它是必要的。这对你来说是否也是如此?
这是实现你目标的最有效方式吗?
接下来的问题是,你是否将这个学位视为时间的有效利用。一开始,这可能看起来是个奇怪的问题。我们认为,全日制学习是快速学习大量知识的最佳方式?然而,问题是,尽管硕士学位似乎是实现这一目标的明显途径,但它并不是唯一的方式,甚至可能不是最有效的。在我的硕士期间,例如,必须修读许多与数据科学不直接相关的课程(例如,调查设计、互联网社会学等)。不要误解我的意思:这些课程非常有趣,为我的作品集提供了很好的素材,我喜欢这种在大学之外难以获得的广泛学术体验。但如果你有非常具体的目标(例如,成为 NLP 领域的专家),那么像硕士这样的广泛资格可能不是一个很有效的时间投资方式。
机会成本是什么?
考虑以下问题:如果你不攻读硕士学位,你可以做什么替代的事情?而且,关键是,那些替代方案在帮助你实现领域内长期目标方面是否实际上更有帮助?
图片由Raquel Martínez提供,来源于Unsplash
考虑机会成本是评估不同机会利弊的一个非常有用的经验法则。具有讽刺意味的是,我在完成硕士学位时遇到了这个例子,当时我们的一个教授鼓励我们考虑将学习延续到博士学位。他的论点大致是这样的:雇主对博士学位的评价远高于硕士学位,所以如果我们想获得顶级职位,我们应该强烈考虑将学习延续几年,争取博士学位。
然而,这实际上并不公平,因为如果博士学位需要 4 年时间完成,那么我们实际上应该把博士学位与硕士学位加 3 年数据科学工作经验进行比较。突然之间,博士学位是否是一个好主意变得不那么明显,因为 3 年的工作经验在人工智能领域中对候选人简历的提升是巨大的。
不要误解我的意思;博士学位可能仍然是某些工作的前提条件。我想表达的是,你需要考虑所有选项并公平比较。一个简单的方法是把你的时间视作一种可以投资的资源。简单问自己:我如何投资这项资源以获得最大的投资回报?
你能负担得起吗?
不要假设你通过获得更高薪资的工作来“弥补”硕士学位的成本。我知道很多做了数据科学相关硕士的人仍在寻找工作。尽管你可能会在网上读到不同的意见,但并不能保证你会找到一份高薪工作,所以不要假设花在硕士学位上的钱会通过工作得到报销(至少在短期内)。
图片由 Towfiqu barbhuiya 提供,来源于 Unsplash。
如果资金是个问题,有很多方法可以解决这个问题。你可以选择兼职学习(即,同时工作),申请奖学金,或者选择攻读资助的博士学位,或者让雇主赞助你的学习;我知道几个人就是这样做的。
在我的情况下,我采取了几种不同的方法。首先,我申请了尽可能多的奖学金,并获得了一个部分奖学金,覆盖了我费用的一大部分。我在攻读硕士学位之前也工作了几年,以便有时间积攒学费。由于大额的财务支出,我也专注于找工作,确保在年初就开始申请,并尽量缩短完成学位后的“失业”时间。
这是一个好的课程吗?
不要以为仅仅因为它是知名大学,课程就一定好。密切关注具体的模块和授课教师。了解课程的学生满意度评分,并查看毕业生的就业情况。如果你找不到关于这些内容的官方统计数据,可以看看是否有开放日或在线体验活动。考虑我的硕士课程时,我向课程协调员提出了一些问题。我认识的其他人则通过 LinkedIn 联系了在读学生和校友以获取他们的意见。你问谁并不重要,但问题越具体,效果越好。
这个课程适合你吗?
数据科学课程的内容和风格差异很大,特别是在编码先决条件方面。有些课程针对新手编码者,而另一些则期望有更高水平的编码经验。
重要的是找到适合你水平的课程。例如,我在牛津大学的硕士课程尝试(在我看来,成功了!)满足各种学术背景的人,但这意味着最初的几周有点像“编码速成课程”,如果你已经有大量的编码经验,那么这可能会很无聊,甚至有些浪费时间。
还要考虑其他因素,比如课程是授课型硕士还是研究型硕士,是否提供工作实习机会,是否在线/远程,是否全日制或兼职。所有这些因素都会影响它是否适合你。在我的情况下,我有一点编码经验,但真的想要一个全面涵盖数据科学教育各个方面的课程。鉴于我有社会科学和商业背景,我还希望选择一个专注于将人工智能应用于经济学和商业问题的课程,而不是一个纯理论的课程,这样的课程会忽略人工智能的商业和社会方面。
你确定你真的想做数据科学吗?
图片由 Vladislav Babienko 提供,来自 Unsplash
如果你对是否想从事数据科学/人工智能工作犹豫不决,那么硕士学位并不是了解的最佳方式。当然,它是一种方式。但它也是最昂贵和耗时的方式之一。
我认为很多人攻读硕士学位是为了推迟关于未来的决定。不要这样做!不如选择一个间隔年,获得一些实际工作经验,以了解你是否喜欢日常工作。
最终,数据科学并不适合每个人,考虑到数据科学可能不是你合适的路径,这一点很重要。你可能更适合其他相关领域,比如产品管理、软件工程、设计、人机交互、研究、统计学、数据可视化、分析翻译等。相关的职业有很多,你不应该因为对它们了解不多而排除那些不那么知名或不那么热门的选项。在决定攻读硕士学位之前,我尝试了几份不同的工作,我认为这些经历确实帮助我明确了自己想进入人工智能领域。此外,一旦你完成了硕士学位,拥有一些商业经验将有助于你顺利进入下一份工作。
结论
我真的希望这篇文章能为你的思考带来一些清晰。如果你喜欢这篇文章,关注我将对我非常重要,因为这有助于支持我的写作。如果你有任何反馈,请在评论中告诉我!
来源
[1] FindAMasters.com www.findamasters.com/masters-degrees/?Keywords=data+science
[访问日期:2023 年 4 月 12 日]
高效数据可视化的 8 个技巧
原文:
towardsdatascience.com/8-tips-for-effective-data-visualisation-f00e711b164a
数据 | 可视化 | 分析
一份正确向观众展示你的见解和观察的指南
·发表于 Towards Data Science ·10 分钟阅读·2023 年 4 月 25 日
–
图片由 Luke Chesser 提供,来源于 Unsplash
当我们讨论数据科学时,我们往往过于关注数据清理和机器学习方面的过程。
讨论的主要点似乎是如何最佳地准备我们的数据集以进行建模,我们需要工程化并包含哪些特征在我们的训练中,我们会首先尝试哪种机器学习技术,以及我们将如何评估它?
虽然这些都是有效且重要的问题需要询问和规划,但作为数据科学家,我们常常忘记优先考虑任何项目的最大卖点之一:可视化。
每一个数据科学项目至少涉及两个方面:技术方面(即数据科学家)和非技术方面(即利益相关者,可能是一些经理或高层主管)。
我们需要记住,数据科学的根本目的是提升业务价值。大多数人不理解数据。我们必须展示给他们。
当做得有效时,数据可视化可以帮助我们发现洞察、识别趋势并传达复杂的想法。
在多年的经验中,我发现这一领域是许多专业人士的短板——尤其是那些初级职位的人员(包括我自己!)。
创建优秀的数据可视化是另一项独立的技能。数据可视化很容易造成更多的混淆而不是清晰。
在这篇文章中,我们将讨论 8 个关于如何生成美观、易于解释和有效的数据可视化的技巧。
提示 #1:选择合适的图表类型
迄今为止,最难掌握的技能是选择合适的可视化类型的直觉。
我们有柱状图、折线图、饼图、散点图、热图和小提琴图——仅举几例。很容易迷失方向并感到不知所措。前往 seaborn 画廊,你会立即开始理解这一决策有多么广泛。
编辑描述
seaborn.pydata.org](https://seaborn.pydata.org/examples/index.html?source=post_page-----f00e711b164a--------------------------------)
正如预期的那样,这可能是我经常看到的最常见错误之一。使用不正确的数据可视化图表。
选择正确的图表类型至关重要,这直接与我们展示的数据类型和想要传达的信息相关。
假设我们有一个小数据集,显示了一个商店本月售出了多少苹果、香蕉和橙子。
# Example data
data = {'apples': 10, 'bananas': 5, 'oranges': 7}
让我们深入探讨不同图表类型如何传达信息。
在所有情况下,我们都需要导入以下包:
import matplotlib.pyplot as plt
import pandas as pd
柱状图
# Bar chart
plt.bar(data.keys(), data.values())
plt.title('Fruit Sales')
plt.xlabel('Fruit')
plt.ylabel('Number of Sales')
plt.show()
作者提供的图像
柱状图在显示每个类别(在我们的例子中是水果类型)的值方面做得非常好。这个图表清楚地显示出最畅销的水果是苹果,最不畅销的是香蕉。
折线图
# Line chart
df = pd.DataFrame(data, index=[0])
df.plot.line()
plt.title('Fruit Sales')
plt.xlabel('Fruit')
plt.ylabel('Number of Sales')
plt.show()
作者提供的图像
如果我们尝试将相同的数据可视化为折线图,我们会得到上面显示的——一个空图表。折线图通常用于显示随时间变化的趋势。因此,我们需要监控某种‘移动’变量。在这种情况下,可以是每月的销售数据,跨多个不同的月份。
散点图
我们还可以将相同的水果类别映射到一个数字,并将其可视化为散点图。假设我们有 5 个类别及其各自的值。
# Scatter plot
x = [1, 2, 3, 4, 5]
y = [10, 5, 8, 3, 6]
plt.scatter(x, y)
plt.title('Data Points')
plt.xlabel('X')
plt.ylabel('Y')
plt.show()
作者提供的图像
正如我们所见,虽然散点图确实显示了不同类别之间的某种差异,并帮助指示它们的表现,但信息仍然没有清晰呈现。
对于这种特定的洞察交付和使用场景,我认为我们都可以同意柱状图可能是最合适的选择。
Infogram 有一篇关于此主题的好文章。
如果你有数据需要可视化,确保使用正确的图表。虽然你的数据可能适用于多个图表……
infogram.com](https://infogram.com/page/choose-the-right-chart-data-visualization?source=post_page-----f00e711b164a--------------------------------)
提示 #2: 有效使用颜色
我不能再强调了——颜色是你在可视化中的最佳朋友。
使用颜色来突出主要的(或值得注意的)见解。
使用颜色来分隔群体。
使用颜色将观众的注意力转移到你希望他们看到的区域。
使用颜色来控制观众的注意力。
为了美观——选择一种与数据和观众相配的令人愉悦的颜色调色板。例如,如果我们展示水果,橙子可能是橙色,香蕉则是黄色。这些小细节是区分好图表和优秀图表的差异。你的观众不应该努力理解图表——而是应该让它对他们说话,告诉他们他们需要知道的一切!
## 选择颜色调色板 - seaborn 0.12.2 文档
由于我们眼睛的工作方式,特定颜色可以通过三个组件来定义。我们通常编程颜色…
额外提示——一旦选择了颜色调色板,请保持一致。在所有图表中使用相同的颜色调色板。尤其是在演示过程中。不要让观众困惑。如果在第一个图表中苹果是红色的,就不要在下一个图表中将它们标记为黄色。
回想一下我们之前展示的条形图示例。让我们用一些颜色来装饰它。
import matplotlib.pyplot as plt
import pandas as pd
data = {'apples': 10, 'bananas': 5, 'oranges': 7}
# create a figure with two subplots
fig, axs = plt.subplots(ncols=2, figsize=(10, 4))
# plot the first chart on the left subplot
axs[0].bar(data.keys(), data.values())
axs[0].set_title('Fruit Sales')
axs[0].set_xlabel('Fruit')
axs[0].set_ylabel('Number of Sales')
# Custom color palette
colors = ['#C5283D', '#E9724C', '#FFC857']
# plot the second chart on the right subplot
axs[1].bar(data.keys(), data.values(), color=colors)
axs[1].set_title('Fruit Sales')
axs[1].set_xlabel('Fruit')
axs[1].set_ylabel('Number of Sales')
# adjust the spacing between the subplots
fig.tight_layout()
# show the plot
plt.show()
作者提供的图片
提示 #3: 保持简单
和生活中的大多数事物一样,越简单越好。
如果某些元素或样式对图表没有增加任何价值,就不要包括它们。
同时记住,你的目标是尽可能清晰和高效地向观众展示发现。没有人会在乎你的花哨图形。
额外的内容只会有一个目的:分散观众的注意力。
假设有一个数据集,包含三种不同产品——A、B 和 C 的总销售额。我们想创建一个图表来显示销售趋势随时间的变化。
import matplotlib.pyplot as plt
import numpy as np
# Generate some fake data
months = np.arange(1, 7)
sales_a = np.array([100, 120, 90, 110, 130, 95])
sales_b = np.array([80, 90, 100, 110, 120, 130])
sales_c = np.array([70, 80, 90, 100, 110, 120])
# Create the chart
fig, axs = plt.subplots(3, sharex=True, sharey=True)
axs[0].plot(months, sales_a, color='red')
axs[0].set_title('Product A')
axs[1].plot(months, sales_b, color='green')
axs[1].set_title('Product B')
axs[2].plot(months, sales_c, color='blue')
axs[2].set_title('Product C')
fig.suptitle('Sales by Product')
plt.show()
作者提供的图片
上面的图表完成了任务——但效果相当糟糕。我们有多个图表,每个图表都有自己的刻度、标题和颜色。很难跟踪和比较。
让我们简化一下,好吗?
import matplotlib.pyplot as plt
import numpy as np
# Generate some fake data
months = np.arange(1, 7)
sales_a = np.array([100, 120, 90, 110, 130, 95])
sales_b = np.array([80, 90, 100, 110, 120, 130])
sales_c = np.array([70, 80, 90, 100, 110, 120])
# Create the chart
plt.plot(months, sales_a, color='red', label='Product A')
plt.plot(months, sales_b, color='green', label='Product B')
plt.plot(months, sales_c, color='blue', label='Product C')
plt.title('Sales by Product')
plt.legend()
plt.show()
作者提供的图片
好多了,不是吗?
我们现在可以轻松比较趋势。
当然,这只是一个示例。在生成图表时,请记住,添加到图表中的任何东西都必须具有价值。
提示 #4: 提供背景信息
我不能过分强调这一点——提供背景信息!
令人惊讶的是,我经常看到许多图表没有标题或标签轴。你的观众不是心灵感应者。让他们知道他们在看什么!
在必要时添加标签、标题、图例、数据来源和注释。
这是一个没有任何背景的糟糕图表(左侧)和一个具有背景的优秀图表(右侧)的示例。
作者提供的图像
提示 #5: 使用比例表示
我们有时需要用不同的尺度或范围可视化多个数据。确保我们处理和表示所有变量时使用相同的尺度,并关注有趣的数据范围,是非常重要的。
小心不要误表述你的数据。
例如,考虑下面的图表:
import matplotlib.pyplot as plt
import pandas as pd
# Example data
data = {'apples': 10, 'bananas': 5, 'oranges': 7}
# First chart: bar chart with proportional representation and inconsistent y-axis
plt.subplot(1, 2, 1)
plt.bar(data.keys(), data.values())
plt.ylim(0, 500)
plt.title('Fruit Sales')
plt.xlabel('Fruit')
plt.ylabel('Quantity Sold')
# Second chart: bar chart with proportional representation and consistent y-axis
plt.subplot(1, 2, 2)
plt.bar(data.keys(), data.values())
plt.ylim(0, 12)
plt.title('Fruit Sales')
plt.xlabel('Fruit')
plt.ylabel('Quantity Sold')
# Adjust the spacing between the charts
plt.subplots_adjust(wspace=0.3)
# Display the charts
plt.show()
作者提供的图像
我们非常欣赏两个图表之间的差异。左侧的图表完全失去了比例——使得评估和比较图表变得非常困难。
另一方面,右侧的图表清楚地显示了差异。
提示 #6: 讲述一个故事
我们必须尽量使图表易于理解。
我们还必须尽量使图表尽可能有趣。
优秀的图表传达直接的信息。它们挑选一个有趣的观察或见解并讲述其故事。图表应该用来支持论点。
import matplotlib.pyplot as plt
import pandas as pd
# Example data
year = [2015, 2016, 2017, 2018, 2019, 2020]
sales = [100, 150, 200, 180, 250, 300]
# Line chart with a narrative
plt.plot(year, sales)
plt.title('Sales Growth')
plt.xlabel('Year')
plt.ylabel('Number of Sales')
plt.text(2016.5, 160, 'First year of rapid growth')
plt.text(2018.2, 195, 'Sales decline due to recession')
plt.text(2019.7, 265, 'Sales pick up after the recession')
plt.show()
作者提供的图像
提示 #7: 考虑你的观众
图表应满足观众的需求。
图表应该帮助你的观众更快地得出自己的结论。
不同的观众有不同的需求。
例如,如果我们刚刚训练了预测模型,并且我们将结果呈现给一些高管,我们可能会想要关注财务方面。我们可能会想要突出不同的 KPI 以及该模型如何提高收入。
如果我们向更技术性的观众展示模型,比如其他数据科学家或工程师,我们可能会想要重点关注模型性能方面。我们会想要突出学习曲线或关注评估指标。
观众将影响我们的图表。我们需要使用观众知道和理解的概念和语言。
提示 #8: 使其具有互动性
有时,我们的数据本质上很复杂,而让观众容易理解的唯一方法就是使其具有互动性。
这将允许我们的观众手动探索数据并得出自己的见解。
我们可以通过添加其他互动组件,如工具提示、过滤器和缩放,来帮助他们尽可能参与。
Plotly 是生成互动图表的绝佳工具。
Dash Enterprise 是构建、扩展和部署 Python 数据应用的首选平台。构建数据应用…
plotly.com](https://plotly.com/?source=post_page-----f00e711b164a--------------------------------)
import plotly.graph_objs as go
import numpy as np
# Generate random data
x = np.random.rand(100)
y = np.random.rand(100)
# Create a Plotly trace object
trace = go.Scatter(
x = x,
y = y,
mode = 'markers'
)
# Create a Plotly layout object
layout = go.Layout(
title = 'Interactive Scatter Plot',
xaxis = dict(title = 'X Axis'),
yaxis = dict(title = 'Y Axis'),
hovermode = 'closest'
)
# Create a Plotly figure object that combines the trace and layout
fig = go.Figure(data=[trace], layout=layout)
# Display the interactive plot in the Jupyter Notebook
fig.show()
总结
设计美观且有效的数据可视化需要仔细考虑。我们必须记住多种因素,这些因素不仅影响我们展示什么,也影响我们如何展示。我们还必须记住,生成优秀的图表可能需要大量的测试和迭代,以找出最佳的呈现方式。
通过遵循这 8 个数据可视化技巧,你将能够创建更好的可视化图表,有效且清晰地传达你的发现。
你喜欢这篇文章吗?只需每月$5,你就可以成为会员,解锁 Medium 上的无限访问权限。你将直接支持我以及你所有其他喜欢的 Medium 作者。非常感谢!
[## 通过我的推荐链接加入 Medium - David Farrugia
获得对我所有 ⚡优质⚡ 内容的独家访问权限,并在 Medium 上无限制地浏览。通过购买我一杯咖啡来支持我的工作…
想要联系我吗?
我很想听听你对这个话题的看法,或者对人工智能和数据的任何想法。
如果你希望联系我,可以发邮件至 davidfarrugia53@gmail.com。
在大学 HPC 集群上训练模型的 9 个技巧
原文:
towardsdatascience.com/9-tips-for-training-models-on-your-universitys-hpc-cluster-a703eb87f3d6
如何在资源受限的环境中有效运行和调试代码
·发表于Towards Data Science ·6 分钟阅读·2023 年 3 月 28 日
–
图片由Martijn Baudoin拍摄,刊登在Unsplash
排队作业,等待 24 小时,cuda runtime error: 内存不足
排队作业,等待 24 小时,FileNotFoundError: 没有这样的文件或目录
排队作业,等待 24 小时,RuntimeError: 堆栈期望每个张量……
啊啊啊啊啊!!!
在高性能计算(HPC)集群上调试代码可能非常令人沮丧。更糟糕的是,在大学里你将与其他学生共享资源。作业将被添加到队列中。你可能要等待几个小时才能知道代码是否有错误。
最近我在大学的 HPC 上训练模型。我学到了一些东西(是艰难的方式)。我想分享这些技巧,希望能让你的体验更顺利。我会保持通用,以便你可以将它们应用到任何系统。
尽可能在个人机器上进行开发
HPC 集群有一个目标——处理数据。没有华丽的 IDE,没有调试器,也没有副驾驶。你真的想用 vim 编写整个项目吗?
为了避免发疯(把那些留给你的论文吧),你应该在自己的机器上开发代码。用小样本训练模型至少1 个周期。包含测试以确保数据正确加载并且所有结果都已保存。训练模型 50 个周期却发现忘记保存最佳结果是毫无乐趣的(是的,我确实做过这个)。
保持代码简单
你在代码中包含的任何额外步骤都会增加运行失败的可能性。你应该只运行需要大量计算能力的进程。例如,模型训练完成后,你会想要对其进行评估。这可以在本地机器上完成。如果你的测试集足够小,甚至 CPU 也可以使用。
不要硬编码任何路径或变量
当将代码从本地机器移动到 HPC 时,某些事情不可避免地需要更改。例如,加载数据和保存结果的文件路径。我是在 Mac 上开发的,但 HPC 使用的是 Linux 操作系统。这意味着我需要将设备从“mps”更改为“cuda”。
图 1:待更新变量示例(来源:作者)
不要硬编码任何需要更改的内容。使用变量并在脚本顶部定义它们。这使得它们易于更改,并且你不会忘记任何东西。相信我!你不想用 vim 滚动查看代码行。
增加批量大小
在上面的图 1中,你可以看到我包含的一个变量是batch_size。这是每次迭代中加载和用于更新模型参数的样本数量。较大的批量大小意味着你可以在 GPU 上并行处理更多样本,从而加快训练时间。
在本地机器上增加这一点很快会导致“cuda runtime error: out of memory”错误。相比之下,HPC 可以处理更大的批量大小。然而,重要的是不要增加过多,因为这可能会对模型准确性产生负面影响。我只是将批量大小从 32 加倍到 64。
对任何实验使用系统参数
尽可能避免在 HPC 上编辑代码。同时,为了训练最佳模型,你需要进行实验。为了解决这个问题,使用系统参数。如图 2所示,这些参数允许你在命令行中更新脚本中的变量。
图 2:系统参数示例(来源:作者)
第一个变量允许我更新最终模型的保存路径(model_name)。其他变量允许我以各种方式采样、缩放和清理数据(见图 3)。你甚至可以更新模型架构。例如,通过传递一个列表[x,y,z]来定义神经网络隐藏层中的节点数量。
图 3:用 4 个数据清理实验训练 U-Net(来源:作者)
包含一个示例参数
sample 系统参数特别有用。我上面包含的这个是一个二进制标志。如果设置为 true,建模代码将使用 1000 个样本的子集运行。你也可以传递实际的样本数量作为整数。
我通常会在两个作业之间安排紧接着的运行——一个是采样数据集运行,另一个是完整数据集运行。采样运行通常会在几分钟内完成(除非有人占用了所有的 GPU!!)。这有助于指出那些烦人的错误。如果出现问题,我有机会在一天结束前停止完整数据集的运行,进行修正,然后重新启动。
详尽说明
不,我不是在谈论你那个主修金融的朋友。你的代码应该尽可能地告诉你更多信息。这将帮助修复任何出现的错误。使用那些打印语句!我发现每次运行时打印的一些有用信息包括:
-
系统设备(即 cuda 或 CPU)
-
数据集的长度、训练集和验证集
-
数据在任何转换前后的样本
-
每个 epoch 和 batch 的训练损失和验证损失
-
模型架构
-
任何系统参数的值
你永远不知道会出什么问题。对于机器学习来说,逻辑错误可能很难被识别。你的代码可能运行得很好,但产生的模型却是完全无用的。拥有一个过程记录将帮助你追溯错误的来源。如果你打算进行多次实验,这一点尤其重要。
使用 diff 比较工具
事情会出错的。非常糟糕。我打破了自己的规则,在 HPC 上对脚本进行了几处修改。好吧,我做了很多修改。我失去了对自己所做更改的追踪,代码无法正常运行。在这种情况下,diff 比较工具就派上了用场。
它会逐行比较一个文档中的文本与另一个文档中的文本。我用它来比较 HPC 上的脚本与我本地机器上的一个脚本。这指出了我所做的更改,我可以立即识别出问题。
图 4:指出 HPC 和本地脚本中的差异(来源:diff 比较工具)
对你的研究保持现实
目前的提示还缺乏细节。你可以做很多事情来加快特定模型包(例如 XGBoost)的训练速度。更好地跟踪你的数据和模型也可以帮助避免错误和混乱。但实际上,你能做的也有限。我的最终建议是对你在可用资源下能实现的目标保持现实。
在 LLM 时代,这可能会令人沮丧。所有主要的突破似乎都是通过增加计算能力来实现的。你必须考虑到,一个像 GPT-3 这样大小的模型训练成本估计为 45 万美元。这与一些部门的整个预算相当!你根本无法竞争。
然而,您仍然可以做出有价值的贡献。微调模型不需要那么多资源。收集和标注数据往往比在基准测试中追求 SOTA 更为重要。每个应用机器学习的子领域都会带来自己的挑战。通常,资源的匮乏会促使我们对这些挑战提出更具创新性的解决方案。所以要感谢那些漫长的工作队列……
哦,我在自欺欺人!!有谁愿意买给我一块 GPU 吗?
希望您喜欢这篇文章!您可以通过成为我的 推荐会员 😃 来支持我
[## 通过我的推荐链接加入 Medium — Conor O’Sullivan]
作为 Medium 会员,您的会员费的一部分会分配给您阅读的作者,您可以全面访问每个故事……
| Twitter | YouTube | Newsletter — 免费注册获取 Python SHAP 课程
参考文献
Michael Galarnyk 如何加速 XGBoost 模型训练 medium.com/towards-data-science/how-to-speed-up-xgboost-model-training-fcf4dc5dbe5f
Leonie Monigatti MLOps 简介:数据和模型版本管理 medium.com/@iamleonie/intro-to-mlops-data-and-model-versioning-fa623c220966
Kevin Shen 批量大小对训练动态的影响 medium.com/mini-distill/effect-of-batch-size-on-training-dynamics-21c14f7a716e
91% 的机器学习模型随着时间的推移会退化
随着时间的推移和数据分布的变化,机器学习模型的性能会退化。
·
关注 发表在 Towards Data Science ·9 min read·2023 年 4 月 11 日
–
模型老化图表展示了机器学习模型随着时间推移的性能退化情况。图片摘自原始论文,由作者注释。
最近来自麻省理工学院、哈佛大学、蒙特雷大学和剑桥大学的研究显示,91% 的机器学习模型随着时间推移会退化。这是同类研究中的首个之一,研究人员关注于机器学习模型在部署后的行为及其性能如何随未见数据的发展而变化。
“虽然已经对各种类型和时间数据漂移的标记进行了大量研究,但尚未有关于模型自身如何应对这些漂移的全面研究。”
本博客文章将回顾研究的关键部分,突出其结果,并强调这些结果的重要性,特别是对机器学习行业的意义。
介绍
如果你以前接触过像协变量偏移或概念漂移这样的概念,你可能知道生产数据分布的变化可能会影响模型的性能。这种现象是维护生产环境下机器学习模型的一大挑战。
根据定义,机器学习模型依赖于其训练的数据,这意味着如果生产数据的分布开始变化,模型可能不再像以前那样表现良好。随着时间的推移,模型的性能可能会越来越差。作者称这种现象为*“AI 老化。”* 我喜欢称之为模型性能退化,具体的性能下降幅度可能让我们将其视为模型失败。
为了更好地理解这种现象,作者开发了一个识别时间性模型退化的框架。他们将该框架应用于来自四个行业的 32 个数据集,使用四种标准机器学习模型,并调查了在数据的最小漂移下,时间性模型退化如何发展。
模型和数据
为了避免任何模型偏差,作者选择了四种不同的标准机器学习方法(线性回归、随机森林回归、XGBoost 和多层感知器神经网络)。这些方法代表了从数据中学习的不同数学方法。通过选择不同的模型类型,他们能够调查不同模型在相同数据上的老化方式的相似性和差异。
同样,为了避免领域偏差,他们选择了来自四个行业(医疗保健、天气、机场交通和金融)的 32 个数据集。
另一个关键决定是,他们只调查了初始性能良好的模型-数据集对。这一决定至关重要,因为研究一个初始适配差的模型的退化是不值得的。
时间退化实验中使用的原始数据示例。时间轴在横轴上,每个数据集的目标变量在纵轴上。当每天收集了多个数据点时,它们会用背景颜色和移动的日均曲线来显示。突出标题的颜色将在博客文章中用于轻松识别每个数据集的行业。图片来自原始论文,由作者标注。
提议的框架
为了识别时间性模型性能退化,作者设计了一个模拟典型生产机器学习模型的框架,并按照该框架进行了多个数据集-模型实验。
对于每个实验,他们做了四件事:
-
随机选择一年的历史数据作为训练数据。
-
选择一个机器学习模型。
-
随机选择一个未来的日期时间点来测试模型。
-
计算模型性能的变化
为了更好地理解框架,我们需要几个定义。训练数据中最新的点定义为t_0。在t_0和测试模型的未来时间点之间的天数定义为dT,它象征着模型的年龄。
例如,一个天气预报模型使用 2022 年 1 月 1 日至 12 月 31 日的数据进行训练。然后在 2023 年 2 月 1 日,我们要求它进行天气预报。
在这种情况下
-
t_0 = 2022 年 12 月 31 日,因为这是训练数据中最新的点。
-
dT = 32 天(从 12 月 31 日到 2 月 1 日的天数)。这是模型的年龄。
下图总结了他们如何执行每次“历史-未来”模拟。我们添加了注释以便于理解。
AI 时间降解实验的图示。图片取自原始论文,由作者标注。
为了量化模型性能的变化,他们测量了在时间t_0时的均方误差(MSE)为MSE(t_0),以及在模型评估时的MSE(t_1)。
由于*MSE(t_0)应该较低(每个模型在接近训练日期时表现良好)。可以将相对性能误差测量为MSE(t_0)和MSE(t_1)*之间的比率。
E_rel = MSE(t_1)/MSE(t_0)
研究人员为每个数据集-模型对进行了 20,000 次这种类型的实验!其中t_0和dT是从均匀分布中随机抽取的。
在进行所有这些实验之后,他们为每个数据集-模型对报告了一个老化模型图表。该图表包含 20,000 个紫色点,每个点代表训练后dT天获得的相对性能误差E_rel。
财务数据集和神经网络模型的模型老化图表。每个小点代表一次单独的时间降解实验结果。图片取自原始论文,由作者标注。
图表总结了随着模型年龄的增加,模型性能的变化。关键要点:
-
误差随时间增加: 随着时间的推移,模型的性能越来越差。这可能是由于模型的任何特征存在漂移或由于概念漂移造成的。
-
误差变异性随时间增加: 随着模型的老化,最佳和最差情况之间的差距增加。当一个机器学习模型具有高误差变异性时,意味着它有时表现良好,有时表现不佳。模型性能不仅仅是退化,还存在不稳定行为。
合理较低的中位数模型误差可能仍会产生准确模型性能的错觉,而实际结果变得越来越不确定。
主要降解模式
在对所有 4(模型)x 32(数据集)= 128(模型、数据集)对进行实验后,91%的情况下观察到了时间上的模型性能下降。接下来,我们将探讨四种最常见的性能下降模式及其对 ML 模型实现的影响。
渐进或无降级
尽管在下面的两个示例中没有观察到强烈的性能下降,但这些结果仍然提出了挑战。查看原始的患者和天气数据集,我们可以看到患者数据中的延迟变量有很多异常值。相比之下,天气数据中的温度变量则有季节性变化。但即便在这些目标变量的行为下,两种模型似乎随着时间的推移表现准确。
渐进的 ML 模型降级模式,相对模型错误随时间的增加不超过线性。图片来源于原始论文,由作者注释。
作者声称,这些及类似结果表明,单凭数据漂移无法解释模型失败或触发模型质量检查和再训练。
我们在实践中也观察到了这一点。数据漂移并不一定意味着模型性能下降。这就是为什么在我们的ML 监控工作流程中,我们关注于性能监控,仅使用数据漂移检测工具来调查性能下降问题的合理解释,因为仅凭数据漂移不应触发模型质量检查。
爆炸性降级
模型性能下降也可能非常突然。查看下图,我们可以看到两种模型在第一年表现良好。但在某个时刻,它们开始以爆炸性的速度下降。作者声称,这些性能下降不能仅通过数据中的特定漂移来解释。
爆炸性 ML 模型老化模式。图片来源于原始论文,由作者注释。
比较两个使用相同数据集但不同 ML 模型的模型老化图。左侧是爆炸性降级模式,而右侧几乎没有降级。两种模型最初表现良好,但神经网络似乎比线性回归(标记为 RV 模型)更快地降级。
爆炸性与无降级对比。图片来源于原始论文,由作者注释。
鉴于此及类似结果,作者总结认为时间性模型质量取决于 ML 模型的选择及其在特定数据集上的稳定性。
在实践中,我们可以通过持续监控估计的模型性能来应对这种现象。这使我们能够在发现爆炸性降级之前解决性能问题。
错误变异性增加
尽管黄色(第 25 百分位数)和黑色(中位数)线保持在相对较低的误差水平,但它们与红色线(第 75 百分位数)之间的差距随着时间显著增加。如前所述,这可能会造成准确模型性能的错觉,而实际模型结果却变得越来越不确定。
增加不可预测的人工智能模型老化模式。图像取自原始论文,由作者标注。
单凭数据或模型本身都无法保证一致的预测质量。相反,时间模型的质量取决于在特定时间对特定数据应用的特定模型的稳定性。
潜在解决方案
一旦我们找到了模型老化问题的根本原因,我们可以寻找最佳的解决技术。适当的解决方案依赖于具体情境,因此没有一种简单的修复方法适用于所有问题。
每当我们看到模型性能下降时,我们应该调查问题并理解其原因。自动修复几乎不可能针对每种情况进行概括,因为多个原因可能导致退化问题。
在论文中,作者提出了一个可能的解决方案来应对时间退化问题。该方案集中于机器学习模型的再训练,并假设我们可以获得新的标注数据,数据质量没有问题,并且不存在概念漂移。为了使这个解决方案在实际中可行,他们提到需要以下条件:
1. 当你的模型需要再训练时发出警报。
当模型的性能出现退化时发出警报并非易事。需要访问最新的真实数据或能够估计模型的性能。像 DLE 和 CBPE 这样的解决方案来自 NannyML 可以提供帮助。例如,DLE(直接预测估计)和 CBPE(基于置信度的性能估计)使用概率方法来估计模型的性能,即使目标缺失时也能进行估计。他们监控估计的性能,并在模型退化时发出警报。
图表取自 NannyML
2. 开发一个高效且稳健的自动模型再训练机制。
如果我们知道没有数据质量问题或概念漂移,频繁使用最新标注数据再训练机器学习模型可能有帮助。然而,这可能会带来新的挑战,例如模型收敛不足、训练参数的次优变化以及 “灾难性遗忘”,即人工神经网络在学习新信息时突然忘记以前学习的信息的倾向。
3. 持续访问最新的真实数据。
最新的真实数据将允许我们重新训练机器学习模型并计算实际性能。问题在于,实际上,真实数据往往会延迟,或者获取新标记数据既昂贵又耗时。
当重新训练非常昂贵时,一个潜在的解决方案是拥有一个模型目录,然后利用估计的性能选择预期表现最佳的模型。这可以解决在相同数据集上不同模型老化速度不同的问题。
行业内其他流行的解决方案包括将模型恢复到之前的检查点,修复下游问题,或改变业务流程。要了解每种解决方案的最佳应用时机,请查看我们之前关于如何应对数据分布变化的博客文章。
结论
Vela 等人的研究显示,尽管机器学习模型在部署时可能达到高准确率,但其性能不会保持静态。即使在相同数据集上训练,不同的机器学习模型也会以不同的速度老化。另一个相关的观察是,并非所有的时间漂移都会导致性能下降。因此,模型的选择及其稳定性也成为处理性能时间退化的最关键因素之一。
这些结果为为什么监控解决方案对机器学习行业至关重要提供了理论支持。此外,它显示了机器学习模型性能容易退化。这就是为什么每个生产中的机器学习模型都必须进行监控,否则模型可能在没有警示的情况下失败。
参考文献
Vela, D., Sharp, A., Zhang, R., et al. 时间质量退化在 AI 模型中。Sci Rep 12, 11654 (2022). doi.org/10.1038/s41598-022-15245-z
我在Twitter和LinkedIn上不断撰写关于数据科学和机器学习的内容。如果你想和我一起在公开场合继续学习,请关注我 😃
评估检索增强生成(RAG)的 3 步法
停止随机选择你的 RAG 参数
·发表于Towards Data Science ·9 分钟阅读·2023 年 11 月 23 日
–
照片由Adi Goldstein拍摄,来源于Unsplash
调整你的 RAG 以获得最佳性能需要时间,因为这取决于各种相互关联的参数:块大小、重叠、检索的前 K 个文档、嵌入模型、LLM 等。
最佳组合通常依赖于你的数据和使用案例:你不能仅仅套用上一个项目中的设置来期望得到相同的结果。
大多数人没有妥善解决这个问题,而是几乎随机地选择参数。虽然有些人对此方法感到舒适,但我决定用数值方法来解决这个问题。
这就是评估你的 RAG 的地方。
在这篇文章中,我将展示一个快速的 3 步法,你可以用来高效、快速地评估你的 RAG 在两个任务中的表现。
-
检索
-
生成
通过掌握这个评估流程,你可以进行迭代,执行多次实验,用指标进行比较,并且希望找到最佳配置
让我们看看这个如何运作 👇。
附注:在每一节中,都提供了代码片段以帮助你开始实现这些想法。
如果你对提高构建 ML 系统生产力的实用技巧感兴趣,可以随时订阅我的通讯: The Tech Buffet。
我每周发送编程和系统设计的见解,以帮助你更快地发布 AI 产品。
1 — 创建一个合成数据集
评估 LLM 通常需要手动标注测试集。这需要时间、领域专业知识,并且容易出错。
希望 LLM 能帮助我们完成这项任务。
从你的数据中抽取 N 个块。对于每个块,指示 LLM 生成 K 个问题和答案的元组。
生成完成后,你将获得一个包含 N*K 元组的数据集,每个元组都有(问题,答案,上下文)。
附注:这里的上下文是原始块及其元数据
在以下示例中,我们将考虑一个提到艾萨克·牛顿的段落。
艾萨克·牛顿因其关于引力定律的理论而闻名,但他的《自然哲学的数学原理》(1686 年)
通过其三大运动定律极大地影响了欧洲的启蒙时代。生于 1643 年
在英格兰的伍尔索普,艾萨克·牛顿爵士开始发展他的理论
关于光、微积分和天体力学,同时在剑桥大学休假期间。
我们将指示 LLM 从中生成三对问题和答案。
图片由作者提供
这可以使用这个提示完成。(改编自我的一个项目)
Create exactly {num_questions} questions using the context and make
sure each question doesn’t reference
terms like "this study", "this research", or anything that’s
not available to the reader.
End each question with a ‘?’ character and then in a newline
write the answer to that question using only
the context provided.
Separate each question/answer pair by "XXX"
Each question must start with "question:".
Each answer must start with "answer:".
CONTEXT = {context}
我们将进一步了解这个合成数据集的局限性,但现在,它是评估我们实验的一个快速解决方案。
代码 💻
在接下来的部分中,我将展示如何使用 LangChain 创建你的合成评估数据集。
我们首先定义提示和 LLM:我正在使用 GCP,所以我将使用 VertexAI。
from random import sample
from langchain.llms import VertexAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
SYNTHETIC_DATASET_SIZE = 70
NUM_QUESTIONS_TO_GENERATE = 3
sampled_chunks = sample(splits, k=SYNTHETIC_DATASET_SIZE)
prompt = """
Create exactly {num_questions} questions using the context and make sure each question doesn't reference
terms like "this study", "this research", or anything that's not available to the reader.
End each question with a '?' character and then in a newline write the answer to that question using only
the context provided.
Separate each question/answer pair by "XXX"
Each question must start with "question:".
Each answer must start with "answer:".
CONTEXT = {context}
"""
llm = VertexAI(
model_name="text-bison",
max_output_tokens=256,
temperature=0,
top_p=0.95,
top_k=40,
verbose=True,
)
prompt_template = PromptTemplate(
template=prompt,
input_variables=[
"num_questions",
"context",
],
)
chain = LLMChain(llm=llm, prompt=prompt_template)
然后,我们从 BQ 加载一些数据并将其拆分成块。块将是生成问题和答案所需的上下文。
from langchain.document_loaders.bigquery import BigQueryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
def load_data_from_bq(query, content_cols, metadata_cols):
loader = BigQueryLoader(
query=query, page_content_columns=content_cols, metadata_columns=metadata_cols
)
return loader.load()
query = ... # the SQL query to
content_cols = ... # the content columns of your table
metadata_cols = ... # the metadata columns of your
loaded_data = load_data_from_bq(
query,
content_cols=content_cols,
metadata_cols=metadata,
)
CHUNK_SIZE = 700
CHUNK_OVERLAP = 100
text_splitter = RecursiveCharacterTextSplitter(
separators=["."],
chunk_size=CHUNK_SIZE,
chunk_overlap=CHUNK_OVERLAP,
)
splits = text_splitter.split_documents(loaded_data)
一旦数据加载并拆分完毕,你需要从中抽取一些块以生成问题/答案对。
这在以下循环中完成。
import pandas as pd
SYNTHETIC_DATASET_SIZE = 70
NUM_QUESTIONS_TO_GENERATE = 3
sampled_chunks = sample(splits, k=SYNTHETIC_DATASET_SIZE)
synthetic_dataset = []
for sampled_chunk in tqdm(sampled_chunks):
prediction = chain.invoke(
{
"num_questions": NUM_QUESTIONS_TO_GENERATE,
"context": sampled_chunk.page_content,
}
)
output = prediction["text"]
try:
questions_and_answers = parse_output(output)
for question_and_answer in questions_and_answers:
synthetic_dataset.append(
{
**question_and_answer,
"context": sampled_chunk.page_content,
"source": sampled_chunk.metadata["source"],
}
)
except:
pass
synthetic_dataset_df = pd.DataFrame(synthetic_dataset)
2 — 对每个合成问题运行你的 RAG
一旦合成数据集构建完成,你可以使用你的 RAG 对每个问题进行预测。
这将生成基于一组检索来源的答案。
对于每个问题,请确保你提取了检索到的文档:这些文档将用于进行评估。
图片由作者提供
代码 💻
在接下来的部分中,我们将看到如何构建 RAG 并对评估数据进行预测。
我们首先需要创建一个向量数据库来索引这些块。
from langchain.embeddings import VertexAIEmbeddings
from langchain.vectorstores import Chroma
db = Chroma.from_documents(
documents=splits,
embedding=VertexAIEmbeddings(),
persist_directory="./db",
)
LangChain 中默认的 RAG 实现使用了一个特定的提示模板。在我们的案例中,我们将稍微修改它以防止产生幻觉。
RAG_TEMPLATE = """Use the following pieces of context to answer the question at the end.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
{context}
Question: {question}
Helpful Answer:"""
然后,我们将我们的 RAG 实现包装在一个 Python 类中。
class RAG:
def __init__(self, vectorstore, rag_template=RAG_TEMPLATE):
self.vectorstore = vectorstore
self.rag_prompt = PromptTemplate.from_template(rag_template)
self.chain = LLMChain(llm=llm, prompt=self.rag_prompt)
def _format_context(self, docs):
context = [doc.page_content for doc, score in docs]
context = "\n---\n".join(context)
return context
def _format_source_documents(self, docs):
source_documents = []
for doc, score in docs:
doc.metadata["score"] = score
source_documents.append(doc)
return source_documents
def predict(self, question, k=4, score_threshold=0.6):
relevant_documents = self.vectorstore.similarity_search_with_relevance_scores(
query=question,
k=k,
score_threshold=score_threshold,
)
source_documents = self._format_source_documents(relevant_documents)
context = self._format_context(relevant_documents)
answer = self.chain.predict(question=question, context=context)
output = {
"question": question,
"answer": answer,
"source_documents": source_documents,
}
return output
# Create the RAG:
rag = RAG(vectorstore=db)
一旦 RAG 构建和初始化完成,你可以进行预测:
evaluations = []
for i, row in tqdm(synthetic_dataset_df.iterrows(), total=len(synthetic_dataset_df)):
question = row["question"]
ground_truth_answer = row["answer"]
ground_truth_context = row["context"]
ground_truth_source = row["source"]
evaluation = {
"question": question,
"ground_truth_answer": ground_truth_answer,
"ground_truth_source": ground_truth_source,
}
prediction = rag.predict(question)
predicted_answer = prediction["answer"]
predicted_contexts = [
source_document.page_content
for source_document in prediction["source_documents"]
]
predicted_sources = [
source_document.metadata["source"]
for source_document in prediction["source_documents"]
]
evaluation["predicted_answer"] = predicted_answer
evaluation["predicted_contexts"] = predicted_contexts
evaluation["predicted_sources"] = predicted_sources
evaluation["relevance_scores"] = [
source_document.metadata["score"]
for source_document in prediction["source_documents"]
]
evaluations.append(evaluation)
df_evaluations = pd.DataFrame(evaluations)
完成后,你将大致得到以下结果:
截图由作者提供
3 — 计算两个评估指标
现在,你可以对你为每个问题做出的预测计算两个评估指标。
-
一个检索分数用于评估检索到的文档的相关性
这个分数可以是二进制的(每个预测为 1/0),并且表示每个问题的真实来源是否在检索到的来源列表中。你可以把这个分数看作是召回率。
-
一个质量评分用于评估生成的答案,给定问题和真实答案。再次地,可以使用 LLM 在此任务中提供 5 分评价。
这是我用来要求 LLM 评分生成答案的提示。
Your job is to rate the quality of a generated answer
given a query and a reference answer.
QUERY = {query}
GENERATED ANSWER = {generated_answer}
REFERENCE ANSWER = {reference_answer}
Your score has to be between 1 and 5.
You must return your response in a line with only the score.
Do not return answers in any other format.
On a separate line provide your reasoning for the score as well.
在获得每个问题的分数后,对数据集进行平均以获得最终的两个指标。
作者图片
代码 💻
在这一部分中,我们从df_evaluations
数据框开始并计算不同的指标。
1 — 检索分数
我们可以通过计算 top_k 指标开始,这些指标表明真实来源是否在前 k 个预测来源中。
df_evaluations["top_1"] = df_evaluations.apply(
lambda row: row["ground_truth_source"] in row["predicted_sources"][:1], axis=1
)
df_evaluations["top_2"] = df_evaluations.apply(
lambda row: row["ground_truth_source"] in row["predicted_sources"][:2], axis=1
)
df_evaluations["top_3"] = df_evaluations.apply(
lambda row: row["ground_truth_source"] in row["predicted_sources"][:3], axis=1
)
df_evaluations["top_4"] = df_evaluations.apply(
lambda row: row["ground_truth_source"] in row["predicted_sources"][:4], axis=1
)
对所有文档进行平均,将给我们每个 k 的检索分数。
作者截图
如何解读这些分数:
示例:真实来源在前四个检索来源中的概率为 0.37。
2 — 生成分数
为了评估生成答案的质量,我们将使用一个 LLM,它将提供其评分和推理。
EVAL_TEMPLATE = """Your job is to rate the quality of a generated answer
given a query and a reference answer.
QUERY = {query}
GENERATED ANSWER = {generated_answer}
REFERENCE ANSWER = {reference_answer}
Your score has to be between 1 and 5.
You must return your response in a line with only the score.
Do not return answers in any other format.
On a separate line provide your reasoning for the score as well."""
prompt_template_eval = PromptTemplate.from_template(EVAL_TEMPLATE)
llm_eval_chain = LLMChain(llm=llm, prompt=prompt_template_eval)
在这个函数中,我们指导这个 LLM 并获取其评分和推理:
-
查询
-
预测的答案
-
参考答案
def evaluate_answer(query, generated_answer, reference_answer):
answer = llm_eval_chain.invoke(
{
"query": query,
"generated_answer": generated_answer,
"reference_answer": reference_answer,
}
)
answer = answer["text"]
try:
rating, reasoning = answer.split("\n")
rating = rating.strip()
reasoning = reasoning.strip()
evaluation = {
"rating": rating,
"reasoning": reasoning,
}
return evaluation
except:
return {
"rating": None,
"reasoning": None,
}
然后,我们对评估数据运行这个函数:
answer_quality_evaluations = []
for i, row in tqdm(df_evaluations.iterrows(), total=len(df_evaluations)):
evaluation = evaluate_answer(
row["question"],
row["predicted_answer"],
row["ground_truth_answer"],
)
answer_quality_evaluations.append(evaluation)
生成评分后,我们得到的分布如下:
作者截图
根据评分过滤,以下是一些 RAG 未能生成答案的问题:
作者截图
这里是一些其他成功的例子。
作者截图
分析这些数据有助于诊断和解决 RAG 出现的错误。
这种评估方法的局限性
在为制药行业构建多个 RAG 时,我应用了这种评估方法。
虽然它快速提供了一个端到端的管道并避免了手动标记数据,但我注意到它有两个主要问题:
-
一些生成的问题过于具体,对于没有阅读上下文的人来说毫无意义。因此,它们对评估指标造成了很大的负面影响,因为它们是不可能回答的。
例如:“这项研究讨论的主要问题是什么?”
-
另一方面,一些其他问题过于简单,或者只是原始片段的简单改述。
结论
如果你想了解更多关于这种评估方法(也称为冷启动),可以查看这个指南。
如果你是 RAG 的新手并想深入了解,你也可以查看我之前的一些帖子以获取更多信息。
你知道其他在构建 RAG 时被证明有效的评估方法吗?请告诉我。
如果你也在研究 RAG 的最佳性能,我也很愿意讨论这个话题。
今天就到这里。下次再见!👋
使用策略梯度强化学习进行 A/B 优化
关于策略梯度方法的逐步视觉解释
·
关注 发表在 Towards Data Science ·10 分钟阅读·2023 年 5 月 23 日
–
明智选择!(图像由作者借助 Stable Diffusion Online 创建)
在这篇文章中,我们将探讨如何将策略梯度强化学习应用于 A/B 优化。这是一个简单的演示,以观察策略梯度方法,在这里我们将深入理解其基础机制,并逐步可视化学习过程。
介绍
强化学习是机器学习中的一个基本概念,与监督学习、自监督学习和无监督学习一样。在强化学习中,智能体试图在环境中找到最佳的一组动作以最大化奖励。强化学习因其与神经网络结合后作为高度灵活的智能体能够击败围棋和象棋的顶级玩家而广为人知。
用作智能体的神经网络通过最大化获得的奖励逐步优化策略。已经开发出几种更新神经网络参数的策略,如策略梯度、Q 学习或演员评论学习。策略梯度方法最接近于反向传播,这在神经网络的监督学习和自监督学习中常常使用。然而,在强化学习中,我们不会像在监督学习中那样直接评估每个动作,而是尝试最大化总奖励,并让神经网络决定采取的具体动作。这些动作从概率分布中选择,这为探索提供了高度的灵活性。在优化初期,动作是随机选择的,智能体探索不同的策略。随着时间的推移,一些动作证明比其他动作更有用,概率分布会收敛到明确的决策上。与其他强化学习方法不同,用户不需要控制探索与利用之间的平衡,最佳平衡由梯度策略方法本身找到。
通常,为了最大化奖励,最佳策略是通过一系列动作实现的,每个动作都会导致环境的新状态。然而,梯度策略方法也可以用来找到统计上奖励最高的最佳动作。这种情况通常出现在进行 A/B 优化时,这是一种选择两个选项中较好者的常见技术。例如,在营销中,A/B 测试用于选择能够带来更高销售的广告方案。你更愿意点击哪个广告?选项 A:“*充分利用你的数据:*我是一名专业的数据科学家——我可以帮助你分析数据”还是选项 B:“*数据处理遇到困难?*专业数据分析师有空帮助你自动化数据分析”?
两种广告创意选项。你更愿意点击哪一个?(图片由作者创作)
A/B 优化的难点在于点击率是变量。例如,在网站上看到广告后,每个用户可能有不同的偏好、处于不同的心情,因此反应也会不同。由于这种变异性,我们需要统计技术来选择更好的广告方案。比较 A 和 B 选项的常见方法是假设检验,如 t 检验。要进行 t 检验,必须展示广告的两个潜在版本一段时间,以从用户那里收集点击率。为了获得对首选广告方案的显著评估,需要相当长时间的探索,这会带来潜在的收入损失,因为在探索期间,好的和差的广告会随机展示得一样频繁。尽快通过更频繁地展示更好的广告来最大化点击率是有利的。通过使用梯度策略方法进行 A/B 优化,代理首先会随机探索 A 和 B 两个变体,但对点击率更高的广告给予更高的奖励,因此代理会迅速学会更频繁地向用户展示更好的广告,从而最大化点击率和收入。
示例
在我们的例子中,我们有两个广告创意选项,假设选项 A 的点击概率为 30%,选项 B 的点击概率为 40%。我们运行一个包含 1000 次广告展示的广告活动。如果我们仅进行探索,并且两个选项展示的频率相同,我们可以期望平均点击率为 35%,总点击数为 350 次。如果我们知道 B 的点击率会更高,我们只会展示 B 并获得平均 400 次点击。然而,如果我们运气不好,选择只展示 A,我们将获得平均仅 300 次点击。使用政策梯度方法,我们可以实现平均 391 次点击,这清楚地表明,快速应用学习到的策略可以获得几乎与首次选择更好的 B 选项一样多的点击数。
如何运作——逐步说明
我们使用 TensorFlow 库在一个小型神经网络上运行 A/B 优化,采用梯度策略方法。首先,我们需要进行一些导入。
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
神经网络仅包含一层单个神经元,该神经元决定展示哪个广告。由于我们没有关于用户偏好、位置、时间或其他任何信息,因此决策基于对神经网络的零输入,我们不需要大神经网络所实现的非线性。训练是通过调整该神经元的偏置来实现的。
model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Dense(1, activation="sigmoid", input_shape=(1,)))
model.summary()
使用神经网络的函数来选择动作,显示选项 A 或选项 B。该函数装饰为 tf.function(),这会创建一个静态计算图,使其运行速度比在急切模式下快得多。使用 TensorFlow 的 GradientTape 函数,我们在广告选择过程中收集梯度。每当用户进入网站时,神经网络会产生一个输出,这个输出被视为选择展示给用户的广告变体 A 或变体 B 的概率。由于我们只有一个带有 sigmoid 激活的神经元,输出是介于 0 和 1 之间的单一数字。如果输出为 0.5,则广告 B 和广告 A 的展示机会各为 50%。如果输出为 0.8,则广告 B 的展示机会为 80%,广告 A 的展示机会为 20%。通过将神经网络的输出与介于 0 和 1 之间的均匀分布的随机数进行比较来选择动作。如果随机数小于输出,则动作为 True(1),选择广告 B;如果随机数大于输出,则动作为 False(0),选择广告 A。损失值使用 binary_crossentropy_loss 来衡量神经网络输出与所选动作之间的差异。然后我们创建损失相对于模型参数的梯度。
@tf.function()
def action_selection(model):
with tf.GradientTape() as tape:
output = model(np.array([[0.0]])) # [0 ... 1]
action = (tf.random.uniform((1, 1)) < output) # [0 or 1]
loss = tf.reduce_mean(tf.keras.losses.binary_crossentropy(action, output))
grads = tape.gradient(loss, model.trainable_variables)
return output, action, loss, grads
我们在 1000 次广告展示中进行训练。在每一步中,广告被展示一次,新的用户有机会点击广告。为了评估学习过程,我们计算这一阶段的点击总数。学习率定义为 0.5。我们将稍后讨论学习率对点击总数的影响。
STEPS = 1000
LR = 0.5
现在,让我们运行广告活动。神经网络将随着时间的推移改进其预测。通过强化学习,训练和应用同时进行。在实践中,所选广告现在会显示在网站上,我们需要等待并查看用户是否点击了广告或离开了网站而没有点击。在代码中,我们只是模拟用户是否点击。正如上面所述,广告 A 有 30%的点击机会,而广告 B 有 40%的点击机会。点击可以直接作为奖励来训练神经网络。奖励用于修改梯度。如果用户点击了广告,该动作的梯度保持不变;但如果用户没有点击广告,梯度则会被反转。最后,梯度下降通过为神经网络分配新的权重和偏置值来更新神经网络的参数。
for step in range(STEPS):
output, action, loss, grads = action_selection(model)
if action == False: # Action A
reward = float(np.random.random() < 0.3)
if action == True: # Action B
reward = float(np.random.random() < 0.4)
grads_adjusted = []
for var_index in range(len(model.trainable_variables)):
grads_adjusted.append((reward-0.5)*2 * grads[var_index])
model.trainable_variables[0].assign(model.trainable_variables[0]-LR*grads_adjusted[0])
model.trainable_variables[1].assign(model.trainable_variables[1]-LR*grads_adjusted[1])
下图总结了学习过程的演变。
使用策略梯度强化学习进行 A/B 优化的学习过程演变。(图片由作者创建)
在前图显示的 1000 次广告展示的活动中,总共得到了 393 次点击,这与我们如果仅选择更好的广告 B 预期的 400 次点击非常接近。我们首先通过观察所有图表在初始步骤 = 1 来回顾学习过程。我们观察到神经网络输出从 0.5 开始,导致广告 B 和广告 A 的选择概率均为 50%。binary_crossentropy_loss 测量模型输出与实际采取的行动之间的差异。由于行动要么是 0,要么是 1,初始损失值为模型输出 0.5 的负对数,大约为 0.7。由于我们在神经网络中只有一个神经元,梯度包含该神经元的权重和偏差的两个标量值。如果选择了广告 A,偏差的梯度是正数;如果选择了广告 B,偏差的梯度是负数。权重参数的梯度始终为零,因为神经网络的输入为零。奖励是高度随机的,因为广告点击的概率只有 30%-40%。如果广告被点击,我们得到奖励,梯度保持不变,否则我们会反转梯度。调整后的梯度乘以学习率并从神经网络的初始参数中减去。我们可以看到,偏差值从零开始,当应用正的调整梯度时变得更负,而当应用负的调整梯度时变得更正。
在广告活动期间,神经网络的输出趋向于一,从而增加了选择广告 B 的机会。然而,即使模型输出已经接近一,广告 A 仍有小概率被展示。当模型输出接近一时,如果选择了动作 B,会有小的损失值和小的负梯度,但在选择广告 A 的少数情况下,会得到较大的损失值,表现为偶尔的峰值和较大的正梯度。在收集奖励后,观察到一些正峰值在调整梯度中被反转,因为这些动作没有带来点击。由于广告 B 的点击概率较高,因此小的负调整梯度比广告 A 点击产生的正梯度更频繁地应用。因此,模型的偏差值逐步增加,而在稀有的情况下,如果广告 A 被选择并点击,偏差值会减少。模型的输出由应用于模型偏差值的 sigmoid 函数提供。
学习率的影响
在本演示中,我们观察到神经网络可以学习选择两个选项中的较优者,并更频繁地应用该选项以最大化奖励。在这种设置下,平均将获得 391 次点击,其中广告 A 的点击概率为 30%,广告 B 的点击概率为 40%。在实际操作中,这些概率会低得多,而且它们之间的差异可能会更小,使得神经网络更难探索更好的选项。策略梯度方法的优点在于自动调整探索与利用之间的平衡。然而,这一平衡会受到学习率的影响。较高的学习率将导致较短的探索阶段和更快地应用所学策略,如下图所示,学习率从 0.01 增加到 10. 模型输出在 100 个单独广告活动中平均随着学习率的增加而更快地增长,直到学习率为 1. 然而,在较高的学习率下,存在适应错误行动的风险,这种行动可能仅在短暂的探索期内显得更好。在高学习率下,模型输出调整得过快,导致决策不稳定。
学习率对神经网络输出的影响。(图像由作者创建)
因此,存在一个最佳学习率需要选择,这在实践中可能很难找到,因为事先不知道点击概率。将学习率从 0.01 变化到 10.0 可以看到,在 0.1 到 2.0 之间的学习率可以获得点击总数的广泛最大值。较高的学习率明显增加了标准偏差,显示了学习过程的不稳定性,同时也导致了点击总数的减少。
学习率对广告活动期间获得的总点击数的影响。(图像由作者创建)
摘要
本演示展示了如何将强化学习用于 A/B 优化。它是一个简单的例子,用于说明策略梯度方法的基本过程。我们已经学习了神经网络如何根据调整后的梯度更新其参数,具体取决于所选择的广告是否被点击。应用所学策略可以迅速最大化点击率。然而,选择最佳学习率在实际操作中可能比较困难。
你可以在 huggingface.co 上找到完整的代码和一个 streamlit 演示:huggingface.co/spaces/Bernd-Ebenhoch/AB_optimization
像专家一样进行 A/B 测试:掌握统计测试选择的艺术
现实世界中的数据可能很棘手!使用 Python 选择和应用正确统计测试的指南
·
Follow 发布于 Towards Data Science ·15 min read·2023 年 12 月 29 日
–
A/B 测试是强大的工具,但选择错误的统计测试可能导致误导性结果。本指南将帮助你为数据选择完美的测试,以确保可靠的分析并做出自信的推荐。
Jason Dent 拍摄的照片,刊登于 Unsplash
刚刚完成了你的 A/B 测试?激动人心的时刻还未结束!真正的魔力在于深入数据,发掘有价值的见解。本指南为你,数据分析师或数据科学家,提供了一种系统的方法来分析你的 A/B 测试结果。
分析的一个重要部分涉及理解数据以及选择正确测试的复杂统计基础。这一步骤通常被忽视,因为 跳过实施直接开始分析的诱惑,可能会错过关键的见解。
根据你正在分析的内容,需要做出不同的一组假设,因此选择的测试也不同。本文将指导你如何为你的数据选择‘正确’的测试。
指南
直接进入正题。这张表集中于典型移动应用程序的指标,这些指标是 A/B 测试的典型目标,尽管这些原则和假设适用于所有情况。
指标概述及推荐的图表(作者提供)
跳到下面必要的部分,我将描述每种指标类型,如何决定最佳测试,并使用 Python 进行计算!
第一部分:每用户平均指标
第二部分:分类指标
第三部分:联合指标
在开始之前,基本定义:
零假设:
每个假设检验由一个“理论”组成,这被称为 零假设 (H₀)。我们分析的目标是尝试自信地证明这个假设是否正确。
零假设 (H₀) 假定两个组之间没有差异,即该特征没有影响。
例如 H₀**😗* μ1 = μ2
HA**😗* μ1 ≠ μ2
显著性水平 (Alpha):
显著性水平,或 alpha (α),是用于决定测试结果是否具有统计学显著性的一个指标。这是运行测试之前做出的基本假设的一部分。
简而言之,显著性水平帮助你确定你的发现是否可靠,而不仅仅是偶然事件。它充当了一个阈值,用于说“嘿,这可能是真的,而不仅仅是巧合。”
P 值:
p 值或概率值,是一种用于统计学中帮助确定对零假设的证据强度的度量。
简而言之,p 值与获得假阳性结果的概率有关,即我们得到的数据由于偶然性而发生,我们犯了第一类错误的概率。
如果 p 值很高,我们不能相信数据,因为假阳性结果的可能性很高。如果数据不可靠(即 p 值很高),我们不能自信地证明或否定 H₀。
P 值 < 显著性水平 → 拒绝 H₀。 我们有足够的数据来得出两个组之间存在显著差异的结论。
P 值 >显著性水平 → 不拒绝 H₀。 我们没有足够的数据得出两个组有显著差异的结论
选择要分析的指标
在开始之前,理解你从 A/B 测试中想要分析的内容是很重要的。
例如,如果你在重新设计首次用户体验(FTUE),你可能会关注用户保留或转化等指标。这些通常涉及是/否(1 或 0)的结果,使得“两个比例 Z 检验”成为一个不错的选择。
我们将探索上面指南中提到的不同类型的指标,解释为什么选择它们以及如何确保你的数据符合测试要求。
设置数据
我假设你已经拥有数据,并且数据格式为包含 3 列的表格:唯一 ID、变体、指标。
#Separate the data into 2
group_A = df[df['Variant'] == "A"]["Metric"]
group_B = df[df['Variant'] == "B"]["Metric"]
#Change "A" and "B" to the relevant groups in your dataset
#Change "Metric" to the relevant column name with the values
第一部分:每用户平均指标
这是最常见的指标分析类型,涉及数据的独立样本。
在大多数实际情况下,每用户平均指标,如每用户平均收入或每用户平均花费时间,都是非常常见的分析对象。如果样本量足够大,你可以直接跳过这一步,选择 Welch 的 t 检验。
选择适合每个用户指标的测试流程图(作者提供)
我将逐一讲解每个流程,并描述决定使用哪条路径的步骤 -
流程 1:大样本或正态分布:
上述流程图中的流程 1(作者提供)
我对“大样本”的一般假设通常是个体样本数量大于 10,000,尽管这个定义是相对的,可能会因具体研究领域、进行的分析类型和需要检测的效应大小而有所不同。
如果 n 相对较小,则执行正态性检验以确定是否选择 T 检验。有几种方法可以检验正态性。最简单的方法是通过创建直方图并目视检查数据。如果数据大致呈现正态分布,则继续。如果仍不确定,最好进行更具统计性的检验,例如 Shapiro-Wilkes 正态性检验。这里有一篇关于各种正态性检验方法的好文章。请记住,每种统计检验通常对数据有不同的假设,因此在选择正态性检验之前请牢记这一点。
请参见下面的代码片段,展示如何通过创建直方图进行视觉检查或使用名为 Shapiro-Wilkes 检验的统计检验来测试数据的正态性。
正态性检验
import pandas as pd
import matplotlib.pyplot as plt
import scipy.stats as stats
alpha=0.05 #We assume an alpha here of 0.05
# Creating Histograms
grouped = df.groupby('Variant')
#Variant column identifies the group of the user - group_A / group_B etc
# Plotting histograms for each group to visually inspect the shape of the data
for name, group in grouped:
plt.hist(group['Metric'])
plt.title(name)
plt.show()
# Shapiro-Wilkes Test to statistically test for normality
for name, group in grouped:
shapiro_test = stats.shapiro(group['Value'])
print(f"Shapiro-Wilk Test for Group {name}: W = {shapiro_test[0]}, p-value = {shapiro_test[1]}")
if p_value < alpha:
print("Reject the null hypothesis. There is a significant difference between the groups.")
else:
print("Fail to reject the null hypothesis. There is no significant difference between the groups.")
流程 2a:方差是否相等?
上述流程图中的流程 2a(作者提供)
接着上面“每用户平均”流程图 —
如果你已经确认数据符合正态分布,那么下一步是检查数据集是否具有相等的方差。这将决定你应该使用 Welch 的 t 检验还是 Student 的 t 检验。
Welch 的 t 检验和 Student 的 t 检验之间的主要区别在于自由度和样本方差估计。Student 的检验假设两个样本具有相同的方差,而 Welch 的检验则不要求。
在比较大样本量(n > 10,000)时使用 Welch 的 t 检验或 Student 的 t 检验进行假设检验时,两者之间的显著性水平差异通常可以忽略不计。这是因为在处理大样本量时,Student 的 t 检验对方差相等假设的依赖对检验准确性的影响非常小。
即使方差相等的假设被违反,Student 的 t 检验仍然相对稳健,这意味着它能产生准确的 p 值,并保持所需的 I 型错误率(拒绝真实零假设的概率)。这种稳健性源于中心极限定理,该定理指出,随着样本量的增加,样本均值的分布趋近于正态分布,而不管基础总体分布如何。
相比之下,Welch 的 t 检验专门设计用于处理不等方差的情况,使其在方差相等的假设存在疑问时更为合适。然而,对于大样本量而言,Welch 的 t 检验与 Student 的 t 检验之间的显著性水平差异通常很小。
如果你担心存在不等方差的可能性,Welch 的 t 检验是一个更安全的选择。然而,如果你想最大化效能,并且对样本量足够大的信心很足,那么可以使用 Student 的 t 检验。
参见下面的代码片段,展示如何使用 Bartlett 检验来测试数据的方差是否相等。Bartlett 检验是一种非常稳健的检验,但它要求数据符合正态分布。如果你更倾向于使用一个不那么稳健的检验,那么Levene 检验可能更为合适。
方差相等性检验
from scipy.stats import bartlett, levene
# Perform Bartlett's test for equal variances (works best on data that conforms to normality)
statistic, p_value = bartlett(group_A, group_B)
# Perform Levene's test for equal variances (less sensitive to Normality assumption)
statistic, p_value = levene(group_A, group_B)
# Display test results
print(f"Test statistic: {statistic}")
print(f"P-value: {p_value}")
if p_value < alpha:
print("Reject the null hypothesis. There is a significant difference in variances between the groups.")
else:
print("Fail to reject the null hypothesis. There is no significant difference in variances between the groups.")
流程 2b:中位数还是均值?
上述流程图中的流程 2b(作者提供)
继续以上的用户平均值流程图 —
如果你对数据集是否符合正态分布有合理的疑虑,那么可能需要使用另一种统计方法来分析数据集。下一步是决定数据的均值还是中位数更为有用。
在 A/B 测试中考虑中位数而不是均值在特定情况下可能有益,尤其是当数据可能受到异常值或极度偏斜的非正态分布影响时。
-
结果沟通: 使用中位数可以提供更清晰和直观的中心趋势解释,特别是在描述典型或每用户的行为时。这可能对利益相关者或非技术观众更具关联性。
-
偏态分布: 如果你的数据高度偏斜或不遵循正态分布,均值可能无法准确代表典型值。在这种情况下,中位数提供了一个更稳健的中心趋势估计,因为它不容易受到极端值或分布形状的影响。
-
离群值敏感性: 均值对数据集中的离群值或极端值高度敏感。即使少量的离群值也能显著影响均值,使其无法代表中心趋势。相比之下,中位数不容易受到极端值的影响,因为它代表了按升序排列的数据集中的中间值。
这两种测量方法各有优点,选择其中一种应该与数据的性质、离群值的影响以及你希望从 A/B 测试中得出的具体见解相一致。在得出结论时,你应该考虑均值和中位数。
这是关于曼-惠特尼 U 检验的非常有用的指南。一如既往,在开始之前最好先做一些研究,彻底理解每个检验方法!
统计检验
如果你遵循了上面的每用户平均值流程图,你现在应该已经决定了什么是确定两组指标是否具有统计显著性差异的最佳检验方法。请参见下面如何进行这些检验。
请参考上述指南和每用户平均值流程图。
学生 t 检验
import scipy.stats
# Student's t-test - This test requires Normality and Equal Variances
t_statistic, p_value = stats.ttest_ind(group_A, group_B)
print(f"Student's t-test: t = {t_statistic}, p-value = {p_value}")
if p_value < alpha:
print("Reject the null hypothesis. There is a significant difference between the groups.")
else:
print("Fail to reject the null hypothesis. There is no significant difference between the groups.")
Welch 检验
import scipy.stats
# Welch's t-test - This test requires Normality
t_statistic, p_value = stats.ttest_ind(group_A, group_B, equal_var=False)
print(f"Welch's t-test: t = {t_statistic}, p-value = {p_value}")
if p_value < alpha:
print("Reject the null hypothesis. There is a significant difference between the groups.")
else:
print("Fail to reject the null hypothesis. There is no significant difference between the groups.")
曼-惠特尼 U 检验
# Mann-Whitney U-test - No statistical assumptions, Median preferred over Mean
u_statistic, p_value = stats.mannwhitneyu(group_A, group_B)
print(f"Mann-Whitney U-test: U = {u_statistic}, p-value = {p_value}")
if p_value < alpha:
print("Reject the null hypothesis. There is a significant difference between the groups.")
else:
print("Fail to reject the null hypothesis. There is no significant difference between the groups.")
自助法
#Bootstrapping - forNon-Normal data/Small sample sizes, and Mean is preferred
# Calculate observed difference in means
observed_diff = np.mean(group_B) - np.mean(group_A)
# Combined data
combined_data = np.concatenate((group_A, group_B))
# Number of bootstrap samples
num_samples = 10000 # You can adjust this number based on computational resources
# Bootstrap resampling
bootstrap_diffs = []
for _ in range(num_samples):
# Resample with replacement
bootstrap_sample = np.random.choice(combined_data, size=len(combined_data), replace=True)
# Calculate difference in means for each bootstrap sample
bootstrap_mean_A = np.mean(bootstrap_sample[len(group_A):])
bootstrap_mean_B = np.mean(bootstrap_sample[:len(group_A)])
bootstrap_diff = bootstrap_mean_B - bootstrap_mean_A
bootstrap_diffs.append(bootstrap_diff)
# Calculate p-value (significance level)
p_value = np.mean(np.abs(bootstrap_diffs) >= np.abs(observed_diff))
print(f"P-value: {p_value}")
if p_value < alpha:
print("Reject the null hypothesis. There is a significant difference between the groups.")
else:
print("Fail to reject the null hypothesis. There is no significant difference between the groups.")
第二部分:分类变量
选择适合的分类数据检验的流程图(作者提供)
在这一部分,我们将探讨分类指标。这些指标可以是离散的,例如点击/未点击,或者在多变量检验的情况下是连续的。
遵循上面的流程图选择最适合你数据的检验方法。
2 组
两比例 Z 检验**(二元指标)**
这些指标包括留存、转化、点击等。
二项变量的两样本 z 检验用于比较两个组之间的二元结果比例。从统计角度来看,随着 N 的增大,二项分布趋向于正态分布。这一假设通常适用,因此使用 z 检验是合理的。
H0: μ1 — μ2 = 0
HA: μ1 — μ2 ≠ 0
两比例 Z 检验
from statsmodels.stats.weightstats import ztest
# Calculate the z-statistic and p-value. This assumes binomially distributed and i.i.d. variables.
z_stat, p_value = ztest(group_A, group_B)
print(f"Two Sample z-test: t = {z_stat}, p-value = {p_value}")
if p_value < alpha:
print("Reject the null hypothesis. There is a significant difference between the groups.")
else:
print("Fail to reject the null hypothesis. There is no significant difference between the groups.")
3 组及以上
离散变量
皮尔逊卡方检验
卡方检验是另一个强大的工具,特别是在 A/B 测试中有多个组(除了对照组)时,尤其有效。
它允许你同时比较多个变体,而无需假设任何分布特性。该方法也适用于类似的二元变量,但使用多个组而不是仅有的 2 个。由于这些组可能会将样本量分割成更小的组,因此必须确保每个组的样本量保持相对较大。
皮尔逊卡方检验用于确定在多个组中观察到的分类数据频率与期望频率之间是否存在显著关联或差异。因此,零假设假定组间没有差异。
由于数据是离散的,我们创建一个列联表来汇总每个变体的计数。然后由 stats.chi2_contingency 函数进行解释。
皮尔逊卡方检验
# Create a contingency table
contingency_table = pd.crosstab(df['Variant'], df["metric"])
# Perform the chi-squared test
chi2, p_value, dof, expected = stats.chi2_contingency(contingency_table)
if p_value < alpha:
print("There is a statistically significant difference in the distribution of the metric across groups.")
else:
print("There is no statistically significant difference in the distribution of the metric across groups.")
连续变量
ANOVA 检验
ANOVA 是一种用于比较三个或更多组均值的统计检验。它评估组均值之间观察到的差异是否可能是偶然的,还是实际上代表了它们底层总体中的显著差异。它在发布多个不同变体时很有用,可以将结果相互比较,以节省时间,而不是部署单独的 A/B 测试。
ANOVA 对正态性和方差齐性假设的违反相对鲁棒,特别是当样本量相对较大时。
ANOVA 检验
# Group the counts for the various groups in the data
grouped_data = [df[df['Variant'] == cat]['Metric'] for cat in df['Variant'].unique()]
# Perform ANOVA test
f_statistic, p_value = stats.f_oneway(*grouped_data)
if p_value < alpha:
print("There is a statistically significant difference in the means of the metric across groups.")
else:
print("There is no statistically significant difference in the means of the metric across groups.")
该检验确定组是否具有统计显著性。我们在这里遇到的一个问题是尝试识别出一个显著优于其他组的特定组。虽然总体 ANOVA 检验突出了普遍的偏差,但需要更深入的调查来识别展示统计差异的特定组。
幸运的是,有一个简洁的函数使用了 Tukey 的范围检验,提供了一种结构化的方法。它生成了一个全面的成对比较表,揭示了各组之间的统计显著差异。
然而,由于 Tukey 的范围检验可能违反了基本假设,必须谨慎使用。该检验应主要用于识别不同组,仅作为组间比较的辅助工具。这个视频非常有用,展示了如何执行和使用。
参见下面的代码片段,应作为上述 ANOVA 检验的辅助工具,用于识别优于其他组的特定组。
Tukey 的 HSD 检验
import statsmodels.api as sm
from statsmodels.stats.multicomp import pairwise_tukeyhsd
# Perform Tukey's HSD test for post-hoc analysis
tukey = pairwise_tukeyhsd(df["metric"], df['Variant'])
# Print pairwise comparison results
print(tukey.summary())
第三部分:联合指标
根据上述指南,如果你确定你的指标实际上是两个或更多变量的联合指标,那么你可能需要采取额外步骤来有效确定各组之间的统计差异。这是因为上述其他检验假设你测试的指标彼此独立。
Delta t 检验
Delta t 检验是一种统计方法,用于评估两个独立组之间均值的差异,同时考虑形成联合分布的两个随机变量的比率。在 A/B 测试领域,常常会遇到度量本身可能不独立的情况。
联合分布示例(作者提供)
一个例子是广告点击率(Ad Click-Through-Rate)。一个人可能会多次查看同一个广告,但只点击一次。
使用标准 t 检验的问题在于,我们分析的是两个独立的随机变量,它们的比率形成了一个联合分布。虽然对象本身是独立的,但它们的联合比率却不是。这违反了学生 t 检验和 Welch t 检验的独立性假设。
相反,我们使用 Delta 方法来估计方差;
方差估计公式(见下文参考)
参见下面的代码片段,展示了如何计算这个新的方差公式,并使用这个新方差创建 t 检验函数。
Delta t 检验
# create the new Variance function as described above
def var_ratio(metric1,metric2):
mean_x = np.mean(metric1)
mean_y = np.mean(metric2)
var_x = np.var(metric1,ddof=1)
var_y = np.var(metric2,ddof=1)
cov_xy = np.cov(metric1,metric2,ddof=1)[0][1]
result = (mean_x**2 / mean_y**2) * (var_x/mean_x**2 - 2*cov_xy/(mean_x*mean_y) + var_y/mean_y**2)
return result
# create this new ttest function, using the new Variances above. This is a standard t-test function.
def delta_ttest(mean_c,mean_t,var_c,var_t, alpha = 0.05):
mean_diff = mean_t - mean_c
var = var_c + var_t
std_e = stats.norm.ppf(1 - alpha/2) * np.sqrt(var)
lower_ci = mean_diff - std_e
upper_ci = mean_diff + std_e
z = mean_diff/np.sqrt(var)
p_val = stats.norm.sf(abs(z))*2
return z, p_val, upper_ci, lower_ci
#Eg. Here we calculate the significance of the CTR for a control and treatment group.
var_c = var_ratio(control['click'],control['view']) #Calculates the delta variance for the control group
var_t = var_ratio(treatment['click'],treatment['view']) #Calculates the delta variance for the treatment group
mean_c = control['click'].sum()/control['view'].sum()
mean_t= treatment['click'].sum()/treatment['view'].sum()
z, p_value, upper_ci, lower_ci = delta_ttest(mean_c,mean_t,var_c,var_t,alpha) #Applies the ttestusing these new delta variances
if p_value < alpha:
print("Reject the null hypothesis. There is a significant difference between the groups.")
else:
print("Fail to reject the null hypothesis. There is no significant difference between the groups.")
结论:
总结来说,虽然 A/B 测试对于实验和优化至关重要,但选择正确的统计检验方法至关重要。为了获得稳健和可靠的结果,数据科学家应仔细考虑特征和检验假设。
记住,A/B 测试是强大的工具,但选择错误的统计检验方法可能会导致误导性的结果!
参考文献:
1. Sivasai Yadav Mudugandla 的正态性检验
3. Ricardo Lara Jácome 的 Mann Whitney U 检验
4. Delta 方法:arxiv.org/pdf/1803.06336.pdf
5. Ahmad Nur Aziz 的 Delta T 检验
使用 R 和 brms 对学校毕业生结果进行贝叶斯比较
原文:
towardsdatascience.com/a-bayesian-comparison-of-school-leaver-outcomes-with-r-and-brms-4da9ae5f9895
ANOVA — 贝叶斯风格
·发表于 Towards Data Science ·9 分钟阅读·2023 年 9 月 2 日
–
我们在离开学校时常常会思考我们想做什么。作为小孩子时,我们被问及长大后想做什么,然后在 13 年的前高等教育中度过。在公共政策中,政府、天主教和独立学校系统之间的差异被广泛讨论,特别是在非政府学校的公共资助方面,以及如何分配资源。
给定学校部门,学生结果之间是否存在实际差异?
在澳大利亚维多利亚州,州政府每年进行一次调查,以评估高中毕业后的结果 (On Track Survey)。该 数据集 (在 CC BY 4.0 下提供)覆盖了多个年份,我们将只查看本文编写时最新的 2021 年数据。
本文将应用贝叶斯分析方法,使用 R 包 brms 来回答这些问题。
图片由 Good Free Photos 提供,来源于 Unsplash
加载库和数据集
以下是我们加载所需包的数据集,该数据集以宽格式报告呈现,包含许多合并单元格。R 不喜欢这种格式,因此我们需要做一些硬编码来重新标记向量,并创建一个整洁的数据框。
library(tidyverse) #Tidyverse meta package
library(brms) #Bayesian Modelling Package
library(tidybayes) #Tidy Helper Functions and Visualisation Geoms for Bayesian Models
library(readxl)
df <- read_excel("DestinationData2022.xlsx",
sheet = "SCHOOL PUBLICATION TABLE 2022",
skip = 3)
colnames(df) <- c('vcaa_code',
'school_name',
'sector',
'locality',
'total_completed_year12',
'on_track_consenters',
'respondants',
'bachelors',
'deferred',
'tafe',
'apprentice_trainee',
'employed',
'looking_for_work',
'other')
df <- drop_na(df)
下面是我们数据集的一个示例。
初始数据框的示例(作者提供的图片)
数据集有 14 个字段。
-
VCAA 代码 — 每个代码的行政 ID
-
学校名称
-
部门 — G = 政府,C = 天主教,I = 独立
-
地域或郊区
-
完成年级 12 的总学生数
-
跟踪同意者
-
受访者
-
其他列表示每个结果的受访者百分比
对于我们的横截面研究,我们对按部门划分的结果比例感兴趣,因此我们需要将这个宽格式数据框转换为长格式。
df_long <- df |>
mutate(across(5:14, ~ as.numeric(.x)), #convert all numeric fields captured as characters
across(8:14, ~ .x/100 * respondants), #calculate counts from proportions
across(8:14, ~ round(.x, 0)), #round to whole integers
respondants = bachelors + deferred + tafe + apprentice_trainee + employed + looking_for_work + other) |> #recalculate total respondents |>
filter(sector != 'A') |> #Low volume
select(sector, school_name, 7:14) |>
pivot_longer(c(-1, -2, -3), names_to = 'outcome', values_to = 'proportion') |>
mutate(proportion = proportion/respondants)
具有兴趣特征的长格式数据框(作者提供的图片)
探索性数据分析
让我们简要地可视化并总结一下我们的数据集。政府部门有 157 所,独立学校有 57 所,天主教学校有 50 所。
df |>
mutate(sector = fct_infreq(sector)) |>
ggplot(aes(sector)) +
geom_bar(aes(fill = sector)) +
geom_label(aes(label = after_stat(count)), stat = 'count', vjust = 1.5) +
labs(x = 'Sector', y = 'Count', title = 'Count of Schools by Sector', fill = 'Sector') +
scale_fill_viridis_d(begin = 0.2, end = 0.8) +
theme_ggdist()
按部门分类的学校数量(作者提供的图片)
df_long |>
ggplot(aes(sector, proportion, fill = outcome)) +
geom_boxplot(alpha = 0.8) +
geom_point(position = position_dodge(width=0.75), alpha = 0.1, color = 'grey', aes(group = outcome)) +
labs(x = 'Sector', y = 'Proportion', fill = 'Outcome', title = 'Boxplot of Respondant Proportions by Sector and Outcome') +
scale_fill_viridis_d(begin = 0.2, end = 0.8) +
theme_ggdist()
按部门和结果分布的比例(作者提供的图片)
分布情况讲述了一个有趣的故事。学士结果在所有部门中变异性最大,独立学校的学生选择攻读本科教育的中位比例最高。值得注意的是,政府学校在高中毕业后就业的学生比例中位数最高。所有其他结果的变化不大 — 我们会很快重新审视这一点。
统计建模 — Beta 似然回归
我们关注的是按学校划分的学生比例及其高中毕业后的结果。在这些情况下,beta 似然是我们最好的选择。brms 是由Buerkner开发的一个出色的包,用于构建贝叶斯模型。我们的目标是对按结果和部门划分的比例进行建模。
Beta 回归模型有两个参数,μ 和 φ。μ 代表平均比例,φ 是精度(或方差)的度量。
我们的第一个模型如下所示,请注意我们已经开始考虑部门和结果之间的交互,因为这是我们希望模型回答的问题,因此这是一个 ANOVA 模型。
我们要求模型为每个部门和结果的组合创建单独的 Beta 项,并使用一个汇总的 φ 项,或使用相同方差的不同比例均值。我们期望这些比例中的 50% 位于 logit 规模上的(-3, 1)之间或比例上的(0.01, 0.73)之间。这是足够宽泛但有信息的先验。全球 Phi 估计值是一个正数,因此我们使用一个足够宽泛的 gamma 分布。
模型 m3a 的数学形式 — 作者提供的图片
# Modelling Proportion with Sector:Outcome Interaction term using Beta - Pooled Phi term
m3a <- brm(
proportion ~ sector:outcome + 0,
family = Beta,
data = df_long |> mutate(proportion = proportion + 0.01), # Beta regression can't take outcomes that are 0 so we fudge here by adding tiny increment
prior = c(prior(normal(-1, 2), class = 'b'),
prior(gamma(6, 1), class = 'phi')),
iter = 8000, warmup = 1000, cores = 4, chains = 4, seed = 246, save_pars = save_pars(all = T),
control = list(adapt_delta = 0.99, max_treedepth = 15)
) |>
add_criterion(c('waic', 'loo'), moment_match = T)
summary(m3a)
模型 m3a 的输出摘要 — 作者提供的图片
注意在模型设置中,我们对所有比例增加了 1% — 这是因为 Beta 回归无法处理零值。我们尝试使用零膨胀 Beta 来建模,但收敛所需时间更长。
同样地,我们可以在没有合并 phi 的情况下建模,这在考虑到我们在上述分布中观察到的情况时直观上是有意义的,因为每个部门和结果组合中存在变异。模型定义如下。
m3b <- brm(
bf(proportion ~ sector:outcome + 0,
phi ~ sector:outcome + 0),
family = Beta,
data = df_long |> mutate(proportion = proportion + 0.01),
prior = c(prior(normal(-1, 2), class = 'b')),
iter = 8000, warmup = 1000, cores = 4, chains = 4, seed = 246, save_pars = save_pars(all = T),
control = list(adapt_delta = 0.99)
) |>
add_criterion(c('waic', 'loo'), moment_match = T)
summary(m3b)
m3b 的输出总结 — 作者提供的图片
模型诊断与比较
手中有两个模型后,我们将比较它们在样本外的预测准确性,使用贝叶斯 LOO 估计的期望对数逐点预测密度 (elpd_loo
)。
comp <- loo_compare(m3a, m3b)
print(comp, simplify = F)
模型 m3a 和 m3b 的 LOO 比较 — 作者提供的图片
简而言之,预期的对数逐点留一值越高,对未见数据的预测准确性越大。这为我们提供了模型之间的相对准确性度量。我们可以进一步通过完成后验预测检查来验证这一点,即观察值和模拟值的比较。在我们的案例中,模型 m3b 更好地模拟了观察数据。
alt_df <- df_long |>
select(sector, outcome, proportion) |>
rename(value = proportion) |>
mutate(y = 'y',
draw = 99) |>
select(sector, outcome, draw, value, y)
sim_df <- expand_grid(sector = c('C', 'I', 'G'),
outcome = unique(df_long$outcome)) |>
add_predicted_draws(m3b, ndraws = 1200) |>
rename(value = .prediction) |>
ungroup() |>
mutate(y = 'y_rep',
draw = rep(seq(from = 1, to = 50, length.out = 50), times = 504)) |>
select(sector, outcome, draw, value, y) |>
bind_rows(alt_df)
sim_df |>
ggplot(aes(value, group = draw)) +
geom_density(aes(color = y)) +
facet_grid(outcome ~ sector, scales = 'free_y') +
scale_color_manual(name = '',
values = c('y' = "darkblue",
'y_rep' = "grey")) +
theme_ggdist() +
labs(y = 'Density', x = 'y', title = 'Distribution of Observed and Replicated Proportions by Sector and Outcome')
模型 m3a 的后验预测检查 — 作者提供的图片
模型 m3b 的后验预测检查 — 作者提供的图片
模型 m3b 的非合并方差或 phi 项能够更好地捕捉按部门和结果分布的变异。
ANOVA — 贝叶斯风格
记住,我们的研究问题是关于了解部门间的结果差异及其程度。在频率统计学中,我们可能使用 ANOVA,即组间均值差异的方法。这种方法的弱点在于结果提供了一个估计值和置信区间,但没有关于这些估计的不确定性,并且提供了一个反直觉的 p 值来说明均值差异是否具有统计显著性。不,谢谢。
以下,我们生成每个部门和结果组合交互的期望值集合,然后使用优秀的 tidybayes::compare_levels()
函数进行繁重的计算。它计算了每个结果的部门间后验均值差异。为了简洁起见,我们排除了“其他”结果。
new_df <- expand_grid(sector = c('I', 'G', 'C'),
outcome = c('apprentice_trainee', 'bachelors', 'deferred', 'employed', 'looking_for_work', 'tafe'))
epred_draws(m3b, newdata = new_df) |>
compare_levels(.epred, by = sector, comparison = rlang::exprs(C - G, I - G, C - I)) |>
mutate(sector = fct_inorder(sector),
sector = fct_shift(sector, -1),
sector = fct_rev(sector)) |>
ggplot(aes(x = .epred, y = sector, fill = sector)) +
stat_halfeye() +
geom_vline(xintercept = 0, lty = 2) +
facet_wrap(~ outcome, scales = 'free_x') +
theme_ggdist() +
theme(legend.position = 'bottom') +
scale_fill_viridis_d(begin = 0.4, end = 0.8) +
labs(x = 'Proportional Difference', y = 'Sector', title = 'Differences in Posterior Means by Sector and Outcome', fill = 'Sector')
贝叶斯 ANOVA — 作者提供的图片
或者,我们可以用一个整洁的表格总结所有这些分布,以便更容易解释和一个 89% 的可信区间。
marg_gt <- epred_draws(m3b, newdata = new_df) |>
compare_levels(.epred, by = sector, comparison = rlang::exprs(C - G, I - G, C - I)) |>
median_qi(.width = .89) |>
mutate(across(where(is.numeric), ~round(.x, 3))) |>
select(-c(.point, .interval, .width)) |>
arrange(outcome, sector) |>
rename(diff_in_means = .epred,
Q5.5 = .lower,
Q94.5 = .upper) |>
group_by(outcome) |>
gt() |>
tab_header(title = 'Sector Marginal Distributions by Outcome') |>
#tab_stubhead(label = 'Sector Comparison') |>
fmt_percent() |>
gtExtras::gt_theme_pff()
按部门和结果的后验均值差异总结表及 89% 可信区间 — 作者提供的图片
例如,如果我们要在正式报告中总结比较,我们可能会写以下内容。
政府学校的学生在完成 VCE 后开始本科学位的可能性低于天主教和独立学校的同学。
平均而言,42.5%(介于 39.5%和 45.6%之间)的政府学校学生,53.2%(介于 47.7%和 58.4%之间)的天主教学校学生和 65%(介于 60.1%和 69.7%之间)的独立学校学生在完成 12 年级后开始接受本科教育。
在 89%的后验概率下,天主教学校与政府学校学生本科入学的差异在 5.6%到 15.7%之间,平均值为 10.7%。此外,独立学校与政府学校学生本科入学的差异在 17.8%到 27%之间,平均值为 22.5%。
这些差异是显著的,差异不为零的概率为 100%。
总结与结论
在本文中,我们展示了如何使用贝塔似然函数和贝叶斯建模来建模比例数据,然后执行贝叶斯方差分析(ANOVA)以探索不同部门之间比例结果的差异。
我们没有试图创建这些差异的因果理解。可以想象,有几个因素影响个体学生的表现,包括社会经济背景、父母的教育水平,以及学校层面的影响、资源等。这是一个极其复杂的公共政策领域,常常被零和博弈的 rhetoric 所困扰。
就个人而言,我是我大家庭中第一个接受并完成高等教育的人。我上了一所中等水平的公立高中,并取得了相当不错的成绩,顺利进入了我首选的学校。我的父母鼓励我继续接受教育,他们在 16 岁时选择了辍学。虽然本文提供了政府学校和非政府学校之间差异的证据,但它本质上只是描述性的。
贝叶斯选择餐厅的方法
·
关注 发表在 Towards Data Science ·3 分钟阅读·2023 年 10 月 12 日
–
最近我在寻找一家新的好餐厅。Google Maps 给了我两个选项:餐厅 A 有 10 条评论全是 5 星,而餐厅 B 有 200 条评论,平均评分为 4 星。我倾向于选择餐厅 A,但评论数量少让我感到担忧。另一方面,餐厅 B 的许多评论让我对其 4 星评级有信心,但没有承诺任何卓越。因此,我想比较这些餐厅,并选择最好的,考虑评论或缺少评论的情况。感谢贝叶斯,我们有办法做到这一点。
图片由作者制作。
贝叶斯框架允许我们对评分的初始分布做出假设,然后根据观察到的数据更新最初的信念。
设定初始信念 / 先验
-
起初,我们对每个评分(从 1 到 5 星)的概率一无所知。因此,在任何评论之前,所有评分的可能性是相等的。这意味着我们从均匀分布开始,这可以表示为Dirichlet 分布(Beta 分布的推广)。
-
我们的平均评分将是 (1+2+3+4+5)/5 = 3,这是概率集中最多的地方。
# prior prob. estimates sampling from uniform
sample_size = 10000
p_a = np.random.dirichlet(np.ones(5), size=sample_size)
p_b = np.random.dirichlet(np.ones(5), size=sample_size)
# prior ratings' means based on sampled probs
ratings_support = np.array([1, 2, 3, 4, 5])
prior_reviews_mean_a = np.dot(p_a, ratings_support)
prior_reviews_mean_b = np.dot(p_b, ratings_support)
图片由作者制作。
更新信念
-
要更新初始信念,我们需要将先验信念乘以观察数据的似然性,再乘以先验信念。
-
观察到的数据自然地用Multinomial 分布(Binomial 的推广)来描述。
-
事实证明,Dirichlet 是 Multinomial 似然的共轭先验。换句话说,我们的后验分布也是 Dirichlet 分布,参数包含了观测数据。
图片由作者制作。
# observed data
reviews_a = np.array([0, 0, 0, 0, 10])
reviews_b= np.array([21, 5, 10, 79, 85])
# posterior estimates of ratings probabilities based on observed
sample_size = 10000
p_a = np.random.dirichlet(reviews_a+1, size=sample_size)
p_b = np.random.dirichlet(reviews_b+1, size=sample_size)
# calculate posterior ratings' means
posterior_reviews_mean_a = np.dot(p_a, ratings_support)
posterior_reviews_mean_b = np.dot(p_b, ratings_support)
- 现在,A 的后验平均评分介于先验 3 和观察到的 5 之间。但 B 的平均评分变化不大,因为大量的评论超出了初始信念的影响。
图片由作者制作。
那么,哪个更好呢?
-
回到我们最初的问题,“更好”意味着A 的平均评分大于 B 的平均评分的概率,即 P(E(A|data)>E(B|data))。
-
在我的情况下,我得到了 85%的概率,即餐厅 A 比餐厅 B 更好。
# P(E(A)-E(B)>0)
posterior_rating_diff = posterior_reviews_mean_a-posterior_reviews_mean_b
p_posterior_better = sum(posterior_rating_diff>0)/len(posterior_rating_diff)
图片由作者制作。
贝叶斯更新允许我们结合先验信念,这在评论数量较少的情况下特别有价值。然而,当评论数量很大时,初始信念对后验信念的影响不大。
代码可在我的 GitHub上找到,我打算去餐厅 A。
面向初学者的应用科学介绍
原文:
towardsdatascience.com/a-beginner-friendly-introduction-to-applied-science-dd60741a9b17
学习特征和统计分析的基础知识
·发表于 Towards Data Science ·阅读时间 12 分钟·2023 年 6 月 6 日
–
照片由 Edge2Edge Media 提供,来源于 Unsplash
介绍
经验主义可能是形成对任何事物的现实世界理解最重要的方面。虽然轶事观察可能对更基础的事物有很大帮助,但我们实际希望被证明并接受为事实的事物必须通过经验主义来处理。这种经验主义的支柱当然是数据。数据非常重要,因为它为我们提供了对周围世界的洞察。然而,数据的一个大缺陷是,数据必须被解释,而这种解释确实需要一定程度的专业知识——这意味着可能有很多人无法根据数据得出一些经验性结论。将这些人和能够做出经验性结论的人区分开的当然是统计学专业知识。
统计学让我们能够分解数据中的不同内容,并对这些数据的不同方面做出观察。统计学应用于数据可以非常强大,常常推动全球范围内每天做出的最重要的决策。考虑到这一点,很容易理解为什么这样的技能可能是令人向往的、重要的,甚至可能是令人难以承受的。幸运的是,数学就是数学——数字就是数字——事情可能会变得混乱,但总有解释。当涉及到统计学时,一些相对基础的知识实际上可以走很远。所有这些对于许多现代学科都非常重要;从数据科学到分析和营销,简单的统计学通常是一个突出的练习,因此了解这些领域的统计学也可以非常有价值。
特征
在我们真正理解统计学、数据科学、机器学习或类似的领域之前,我们首先需要理解数据。数据由特征和它们的后续观察组成,这些构成了一个总体。总体通常指的是“我们所有的干净数据”,与样本相对,后者可能暗示我们在假设检验或类似背景下使用一些数据。这可能听起来有些混乱,但很简单记住:你的总体是你所有的数据,而样本是你的一部分数据。特征只是这些数据的一列,观察则是一行。例如,一个人的特征是头发颜色,而观察是金发。我们的特征是数据结构和测量内容的轮廓,而观察是对应于该轮廓的实际数据。
特征主要有三种类型:
-
标签
-
类别
-
连续
标签
标签是所有特征中使用最少的一种,但经常可以用来区分不同的观察值。这些标签可以在机器学习或统计学背景下使用,但很少被使用。一个在统计学背景下作为特征使用的例子是,如果我们想测试某个年份是否具有统计学意义。例如,如果我们想测试 2021 年的人是否比 2001 年的人高,我们将从两个年份中各取样本,并测试统计学意义。在机器学习背景下,确实有一些实例中日期或名字可能被用来预测某些特征,我也见过这种情况。我曾经有过的一个实际例子是训练机器学习算法来预测不同物品在不同日期的市场价值。
类别
类别数据与标签数据很相似。虽然标签数据并没有提供太多关于观察值的信息,但类别数据往往确实代表了实际的测量。在某些方面,类别数据只是连续空间的一部分标签——这一点在深入了解类别数据时会更加明了……但为了理解我的意思,我需要引用一个例子。
我们可以认为,体重超过 300 磅的人被认为非常超重,体重在 200–300 磅之间的人相当超重,100–200 磅通常为平均水平,而低于 100 磅在特定人群中则为偏瘦。这些都是放在连续空间上的类别标签,但在许多方面可以提供更多关于数据的信息,同时简化了这一特征——使其更易于解释和简化。如果我们的统计只需要知道一个人是否超重,那么我们可能不需要知道确切的体重。这有助于缓解一定程度的偏差,防止模型拟合问题,并且有助于隐藏个人信息。
连续
连续特征是最具代表性的特征类型。这些特征可以是评级、仪器测量、温度等任何数值型数据。每一个连续特征似乎都形成了其自身的数值空间。随之而来的是均值、标准差,以及我们传统上用于统计分析的所有其他数据。对分类数据也可以做同样的处理,但在某些情况下,这些特征必须转化为统计学和计算机能够理解的语言——即数字。更常见的是,我们使用分类特征来区分样本中的不同观察。
在许多情况下,你将很可能需要对特征进行一些处理。这通常取决于你所处理的特征类型,这可能会非常棘手。如果你想了解如何处理这些不同类型的特征,这里有一些我写的相关文章:
一个关于如何更好地处理和理解特征的小指南
towardsdatascience.com [## 分类数据与连续数据:你需要了解的一切
对两种最常见的特征类型的概述,以及一些重要的注意事项。
chifi.dev](https://chifi.dev/categorical-data-vs-continuous-data-everything-you-need-to-know-36c2a0dbf6c?source=post_page-----dd60741a9b17--------------------------------) ## 编码器——如何编写,如何使用
通过这篇“从零开始”的指南,发现编码器在机器学习中的多种用途!
towardsdatascience.com
分析
现在我们对数据和可能遇到的不同特征有了基本了解,我们可以开始分析数据。为了使这一过程尽可能易于理解,我将使用一些数据来演示这些不同的概念。我将使用的数据是由佛罗里达州立大学分发的 trees.csv
数据,您可以在 这里 找到(GNU LGPL 许可证)。我将使用 Julia 编程语言,但这个过程在大多数不同的解决方案中通常是相同的,方法调用或技术可能会有所不同,但我们所做的技术和最终更改是相同的。我们将通过读取数据并进行一些基本处理来开始这个过程。这将需要一些 Julia 包:
using Pkg; Pkg.activate("ds")
Pkg.add("DataFrames")
Pkg.add("CSV")
Pkg.add("Statistics")
Pkg.add("HypothesisTests")
现在让我们导入数据,我将使用 sink 参数来读取我们的 DataFrame。sink 参数允许我们的数据格式模块推断如何将一些数据读取到给定的类型;在我们的案例中,就是 DataFrames。
using DataFrames
using Statistics
using HypothesisTests
在大多数情况下,这将是我们清理数据和删除任何缺失值的地方。幸运的是,这些数据非常干净,甚至没有缺失值。删除后形状保持不变:
size(df)
(31, 4)
size(dropmissing!(copy(df)))
(31, 4)
话虽如此,我们可以直接查看我们的特征。
我们有四个不同的特征,分别是 Index、Girth、Height 和 Volume。Girth、Height 和 Volume 都是连续特征。Index 只是行的表示,是一个标签特征。在某些情况下,可能会混淆我们是否在查看标签或特征。一个很好的方法是查看有多少个唯一值。特别是在这些唯一值的数量与数据长度相同的情况下,这是一个明显的标签指示器。
println(length(Set(df[!, :Index])))
println(length(df[!, :Index]))
31
31
这些数据中没有分类特征,但这给了我们一个很好的机会来创建一个!如前所述,分类特征通常只是对连续特征的测量。例如,我们可以按树的高度对其进行分类。通过一个简单的理解,我将创建一个新的特征,根据均值来确定一个类别:
df[!, Symbol("Height Class")] = [begin
if x > hmu
"taller"
elseif x < hmu
"shorter"
else
"average"
end
end for x in df[!, Symbol("Height (ft)")]
]
现在我们有了一个新特征!
31-element Vector{String}:
"shorter"
"shorter"
"shorter"
"shorter"
"taller"
"taller"
"shorter"
"shorter"
"taller"
"shorter"
"taller"
"average"
"average"
⋮
"shorter"
"taller"
"taller"
"shorter"
"shorter"
"taller"
"taller"
"taller"
"taller"
"taller"
"taller"
"taller"
现在我们与数据建立了基本关系,我们实际上可以发现一些见解。例如,查看高度是否与宽度相关可能会很有趣。为此,我们现在需要结构化一个测试。
假设检验
现在我们已经建立了特征并识别了数据的格式,我们可以开始制定假设。
根据我们的特征,我们将形成以下假设。
“如果一棵树的高度更高,那么它的胸围也可能更高。”
这是一个有趣的假设,因为答案并不立即显而易见。当然,树木之间存在大量遗传多样性,一些树木长得很高,而一些树木则长得很宽。这些数据也只有 31 个观察值,这可能意味着我们没有足够的普遍性来接受这个假设。不过,仍然可以进行检验!首先,我们将创建我们之前讨论的样本。我们将使用我们的新类别,将树木按照是否较高或较矮进行分类。使用 Julia,我喜欢为此创建一个干净的小调度,使条件掩码变得更加容易。当然,仍然可以使用 filter!
来完成。
import Base: getindex
function getindex(df::DataFrame, bv::Vector{Bool})
points::Vector{Int64} = findall(x -> x == 0, bv)
dfcopy::DataFrame = copy(df)
delete!(dfcopy, points)
dfcopy::DataFrame
end
现在我将分离出较高的 df:
mask = [x == "taller" for x in df[!, Symbol("Height Class")]]
tallerdf = df[mask]
现在我们可以进行检验,以检查这个新分离样本中的胸径是否具有统计显著性。如果是这样,我们可以接受或拒绝我们的零假设并得到答案。还有更多内容;我们对任何检验都有一定的置信水平,但在这个简要概述中,我们只会略微触及这一点。现在我将拿出我们的两个样本,注意它们需要具有相同的长度,所以我们将随机抽样到我们的样本长度。
grow_samp = tallerdf[!, Symbol("Girth (in)")]
samples = [begin
n = rand(1:length(grow))
grow_pop[n]
end for n in 1:length(grow_samp) ]
我们的 grow_samp
是我们分类为更高的组。我们的 samples
是来自我们总体的随机样本。利用这两个数据,我们可以确定被标记为较高的树木和未标记的树木在胸径上是否存在差异。为此,我将使用单样本 T 检验,也称为独立 T 检验。这是一种标准的假设检验,可能是最容易学习的。在统计学中,一切都始于抛物线。大多数数据都位于这个抛物线的中心。统计上显著的事物则位于异常区,远离其他数据,位于抛物线的两侧——即尾部。在正态分布的例子中,我们的均值位于中心,两侧各设置两个标准差。在任一方向——均值的负标准差或正标准差。
抛物线本身被称为分布,因为它描述了我们的数据是如何分布的。在大多数情况下,鉴于我们生活在 2023 年,我们很可能会使用已经为你计算了公式的软件。作为初学者,我建议你熟悉概率密度函数(PDFs)和累积分布函数(CDFs),但不一定要学习这些函数的公式。我们将详细介绍一些内容,但不多,这里有一些文章更深入地探讨了这些主题:
towardsdatascience.com ## 什么是累积分布函数?
CDF 的概述及其在数据科学中的应用
towardsdatascience.com
一个值得熟悉的 PDF 是正态分布的 PDF。这是一个很简单的公式,很有意义。为了激发思考,让我们从后往前推导;我们希望得到什么回报?
我们希望得到一个分布函数,这个函数会根据概率展示我们的数据,其中每个输入都以其与均值的标准差为比例。如果我们从某些数据中取出一个值,我们如何找到它距离均值多少标准差?我们需要 5 个填充花生来运送我们的箱子,我们有 200 个,但用了 125 个,我们还能装多少个箱子?这是一个类似的问题;首先我们得到值和均值之间的差异,然后看到它距离均值有多少标准差,就像我们减去已用花生的数量并除以每箱花生的数量,以查看我们可能装多少个箱子一样。
x̄-µ/σ
其中
-
xbar 是我们样本中的一个观测值,
-
mu 是样本均值,
-
小写希腊字母 sigma 是标准差。
如果你想了解这些符号在统计学中的含义,这里有一篇我写的文章:
熟悉每个希腊字母在统计学中的含义。
towardsdatascience.com
每当我们测试相关性时,我们测试的是我们的数据是否异常;是否落在距离均值两个标准差的尾部。重要的是要准确记住这些测试的工作原理;我们不是在测试因果关系,我们在测试相关性。这并不意味着这些树木因其高度而变宽,它们只是因为更高而变得更宽。另一个需要考虑的因素是我们的假设:
如果一棵树更高,那么它也可能有更大的胸围。
每当我们进行这个测试时,我们并不是在检验某件事是否为真。我们是在检验相反的情况是否为真;我们真正的问题是完全不同的。
如果一棵树更高,那么它的胸围仍然是正常的。
每当我们拒绝否定我们的假设时,我们实际上并不是在拒绝我们最初想要证明的东西;我们只是证明了对立的观点不成立。这被称为零假设,在实验过程中应当时刻记在脑海里。现在回到我们的实验。正如我简要提到的,不同分布的各种函数涉及大量数学。对于不同分布的测试,最易接近的可能是 T 分布,这种分布仍有相当复杂的 CDF,通常用于这种类型的一样本测试。也就是说,我们可以利用软件库和 Julia 生态系统来我们的优势。在像 Python 或 R 这样的语言中,也有类似的包能够执行这种测试。为此,我将使用来自HypothesisTests
的OneSampleTTest
。
OneSampleTTest(samples, grow_samp)
One sample t-test
-----------------
Population details:
parameter of interest: Mean
value under h_0: 0
point estimate: -1.24
95% confidence interval: (-3.338, 0.8581)
Test summary:
outcome with 95% confidence: fail to reject h_0
two-sided p-value: 0.2256
Details:
number of observations: 15
t-statistic: -1.2675959523435136
degrees of freedom: 14
empirical standard error: 0.9782296935450965
虽然我们在这里可用的观察数据不多,但从这些数据来看,这个假设的统计意义不大。我们发现,相反的情况可能会成立——虽然我们当然需要更多的数据来确定。我们的 P 值仅为 0.225。一般而言,P 值低于 0.05 被认为是具有统计学意义的。我们的值显然表明在这些数据中情况并非如此。
假设检验和应用科学不仅对数据科学家、程序员、分析师等人员具有宝贵的资产,还可以在生活中和理解你周围的世界中发挥重要作用。数学和科学是非常具体和严格的,但同时也常常非常有意义,并让我们通过简单的观察学习新事物。应用科学确实是一种超级力量,而统计在许多不同的情况下具有无限的价值。感谢阅读,希望这篇文章提供了大量有价值的信息!