什么是超几何分布
原文:
towardsdatascience.com/understanding-the-hypergeometric-distribution-e6540c7fec3c
解析数据科学中一个较不知名的分布
·发布于 Towards Data Science ·5 分钟阅读·2023 年 6 月 21 日
–
图片由 Roth Melinda 提供,Unsplash
背景
二项分布 是一个在数据科学内外都非常著名的分布。然而,你听说过它的较不知名的“亲戚”超几何分布 吗?如果没有,这篇文章将为你详细解释它是什么以及它为何对我们数据科学家有用。
补充视频。
直觉
超几何分布衡量在n次试验(样本)中成功的k的概率,没有替换,给定有关总体的一些信息。这与二项分布非常相似,但有一个关键区别是没有替换。因此,每次抽取/试验的成功(或结果)的概率会变化,而在二项分布中,成功(和失败)的概率是固定的。
一个易于理解的例子是确定从一副标准扑克牌中随机抽取 20 张牌中抽到全部 4 张国王的概率。如果我们抽到一张国王,那么抽到下一张国王的概率将与第一次不同,因为总体构成已经改变。因此,成功的概率是动态的。
理论
概率质量函数(PMF) 的超几何分布如下所示:
作者的 LaTeX 公式。
其中:
-
n 是试验的次数
-
k 是成功的数量。
-
N 是总体规模。
-
K 是总体中的成功总数。
-
X 是来自超几何分布的随机变量。
感兴趣的读者可以在这里找到 PMF 的推导。
类似括号的符号指的是二项系数:
作者提供的 LaTeX 公式。
阶乘 表明我们在处理组合和排列。你可以在我之前的博客中阅读更多内容:
关于组合和排列及其区别的简要描述。
towardsdatascience.com](/a-primer-on-combinations-and-permutations-d654aacea292?source=post_page-----e6540c7fec3c--------------------------------)
分布的均值由以下公式给出:
作者提供的 LaTeX 公式。
示例
让我们回到之前的例子,从一副普通的 52 张牌中随机抽取 20 张卡片,计算其中 4 张国王的情况。我们拥有的信息是:
-
N = 52:牌堆中的卡片数量。
-
n = 20:我们抽样的卡片数量。
-
k = 4:我们希望得到的国王数量(成功)。
-
K = 4:牌堆中的国王数量。
将这些数字代入 PMF:
作者提供的 LaTeX 公式。
因此,概率非常低。这是合理的,因为从一副牌中抽到一张国王的概率是 ~0.077 (1/13), 所以在样本更小的情况下,如我们上面所示,这个概率会进一步降低。
如果你想玩一些数字和不同的情境,我在这里链接了一个超几何分布计算器。
代码与图表
上述例子展示了超几何分布应用的有用示范。然而,我们可以通过将 PMF 绘制为成功数量 k 的函数来获得更全面的视角。
以下是一个 Python 绘制的图示,展示了在我们期望的国王数量 k 变化时的例子:
作者的 GitHub Gist。
作者在 Python 中生成的图表。
正如我们所见,从 20 张卡片样本中得到 5 张国王的概率为 0,因为牌堆中没有五张国王!我们得到的最可能的国王数量是 1。
现在让我们考虑一个新问题。随机抽取 30 张牌样本中,黑桃的超几何分布是多少?
作者提供的 GitHub Gist。
由作者用 Python 生成的图。
我们在 30 张牌的样本中得到 8 张黑桃的可能性最大。正如图示,很难在样本中得到没有黑桃的情况。
应用
超几何分布涉及到许多领域:
-
扑克牌中赢得一手的概率
-
投票人群分析
-
制造业中的质量控制
-
种群中的遗传变异
因此,超几何分布是你在数据科学职业生涯中最有可能遇到的内容,因此值得了解。
总结与进一步思考
在这篇文章中,我们讨论了超几何分布。这与二项分布非常相似,但成功的概率会变化,因为我们是在不放回的情况下抽样。这个分布在数据科学中非常强大,广泛应用于质量控制和赌博行业。因此,作为数据科学家,了解它非常值得。
完整代码可以在我的 GitHub 上找到:
[## Medium-Articles/Statistics/Distributions/hypergeometric.py at main · egorhowell/Medium-Articles
在我的中等博客/文章中使用的代码。通过创建账户来贡献 egorhowell/Medium-Articles 的开发…
github.com](https://github.com/egorhowell/Medium-Articles/blob/main/Statistics/Distributions/hypergeometric.py?source=post_page-----e6540c7fec3c--------------------------------)
另一个事项!
我有一份免费的通讯,Dishing the Data,我在其中每周分享成为更好数据科学家的技巧。没有“废话”或“点击诱饵”,只有来自实践数据科学家的纯粹可操作见解。
[## Dishing The Data | Egor Howell | Substack
如何成为更好的数据科学家。点击阅读由 Egor Howell 撰写的《Dishing The Data》,一份 Substack 出版物,内容包括…
newsletter.egorhowell.com](https://newsletter.egorhowell.com/?source=post_page-----e6540c7fec3c--------------------------------)
与我联系!
参考文献
-
关于超几何分布的更多信息:
brilliant.org/wiki/hypergeometric-distribution/
理解集成学习中多样性的重要性
原文:
towardsdatascience.com/understanding-the-importance-of-diversity-in-ensemble-learning-34fb58fd2ed0
多样性在提升集成性能中的作用
·发布于 Towards Data Science ·阅读时长 9 分钟·2023 年 1 月 2 日
–
集成学习是一种将多个模型结合以获得更好结果的机器学习技术。只有近年来,随着计算速度的提高,集成学习才成为应对机器学习领域困难问题的最有效技术之一。这种方法在大多数机器学习竞赛的获胜解决方案中使用,奖金高达 10 万美元。
我们如何选择最佳的集成成员,将它们组合成一个更强大的模型?在本文中,我们将探讨如何选择模型进行集成以及如何对它们进行聚合。
什么是多样性?
集成学习基于结合多个模型的概念,这些模型被称为弱学习者。这个名字来源于这样一个想法:个体集成成员不需要非常准确。只要它们比随机模型稍微好一点,结合它们就会有益。多样性是集成学习中的一个重要概念,指的是集成中的个体模型的预测应该尽可能地彼此不同。这是因为不同的模型可能会犯不同类型的错误。通过结合多样化模型的预测,我们可以减少整体的集成误差。
我们如何增加多样性?
多年来,开发了几种集成学习算法。每种算法都有独特的方式来实现多样性。这些方法包括:
在不同的训练数据子集上训练每个模型。当样本是有放回地抽取时,称为自助法。当抽样是无放回地进行时,称为粘贴法。这也有助于减少集成预测的方差,从而改善其泛化能力。自助法中的多样性理念在随机森林和极端随机树中得到了进一步的发展。前者通过选择每次分裂时树可用的随机特征数量来实现多样性,而后者通过进行随机分裂来降低树之间的相关性。
使用不同特征集训练每个模型。可以使用可用特征的不同组合或原始特征的不同转换来训练集成。这有助于捕捉数据的不同方面,从而提高性能。
使用不同类型的算法训练每个模型。这是投票和堆叠元模型中使用的方法。通过使用不同的算法,集成中的个别模型可以捕捉数据中的不同模式并产生不同类型的错误。
投票集成。包括多样化的集成成员是很重要的。[1]
然而,多样化的预测并不总是更好。
良好的和不良的多样性
在多数投票集成中,浪费 投票的概念可以用来理解集成的多样性。如果集成已经是正确的,分类器之间的低分歧表明已经有一些投票被浪费了,因为无论个别分类器的投票结果如何,都会做出相同的正确决策。这被称为良好的多样性,它测量了集成已经正确时的分歧。在这种情况下,更多的分歧意味着更少的浪费投票。相反,不良的多样性测量集成不正确时的分歧。在这种情况下,任何分歧都代表着浪费的投票,因为个别分类器没有为集成做出的正确决策做出贡献。为了最大化集成的效率,重要的是增加良好的多样性并减少不良的多样性,这可以通过减少浪费投票的数量来实现。
从准确性角度来看,另一种思考方式是这样的。例如,假设决策树模型在识别狗方面表现很好,但在识别猫时有困难。另一方面,逻辑回归模型在识别猫方面表现更好,但在识别狗时有困难。这些算法各有优缺点,会对数据产生不同类型的错误。通过结合这两个模型的预测,我们可以创建一个总体准确性高于任何单个模型的集成。这就是良好多样性的一个例子。我们可以再添加一个第三个模型,该模型在分类猫和狗方面都很差。第三个模型将增加集成的多样性,但不会带来任何好处。这被认为是差的多样性。[2]
我们如何测量多样性?
让我们称f₁, f₂, … fₙ为集成中不同模型的预测。
多样性度量有两种类型:成对和全局。成对度量需要对每一对fᵢ, fⱼ进行计算。最终,你会得到一个nxn**矩阵。全局度量是在整个预测矩阵上计算的,它们由单一值表示。以下列表绝非详尽,你可以在 Kuncheva 的论文中找到更多度量,参考文献中有列出。
在公式中,0 表示预测错误,1 表示预测正确。例如,N⁰¹表示第一个分类器正确而第二个错误的次数。
皮尔逊相关系数 计算集成中相似性的最简单方法是使用皮尔逊相关系数。如果两个模型的预测高度相关,这意味着它们非常相似。当ρᵢⱼ = 0 时获得最大多样性,而两个产生完全相同输出的分类器将有ρᵢⱼ = 1。即使在软投票集成中,当预测是概率时,也可以计算相关系数。
以下指标用于二元类别预测。
分歧 正如名字所示,它表示预测之间的分歧程度。通过将预测不同的次数除以总的预测次数来计算分歧。分歧值介于 0(没有预测不同)和 1(每个预测都不同)之间。
分歧度量。[2]
Yule 的 Q Yule 的 Q 值介于-1 和 1 之间。这个度量提供了重要的信息:正值表示模型在正确分类相同对象,而在不同对象上的错误将导致负 Q 值。0 的值表明预测是独立的。
Yule 的 Q。[2]
熵 以下度量是一个全局指标。它在整个预测矩阵上计算。熵基于这样一个思想:当不一致性达到最大时,一半的预测是零,而另一半是 1。在以下公式中,L 是集成模型的总成员数,l是正确分类实例 zⱼ的分类器总数。
二元预测的熵。[2]
Python 实现
def coefficients(preds):
A = np.asarray(preds[:, 0], dtype=bool)
B = np.asarray(preds[:, 1], dtype=bool)
a = np.sum(A * B) # A right, B right
b = np.sum(~A * B) # A wrong, B right
c = np.sum(A * ~B) # A right, B wrong
d = np.sum(~A * ~B) # A wrong, B wrong
return a, b, c, d
def disagreement(preds, i,j):
L = preds.shape[1]
a, b, c, d = coefficients(preds[:, [i, j]])
return float(b + c) / (a + b + c + d)
def paired_q(preds, i, j):
L = preds.shape[1]
# div = np.zeros((L * (L - 1)) // 2)
a, b, c, d = coefficients(preds[:, [i, j]])
return float(a * d - b * c) / ((a * d + b * c) + 10e-24)
def entropy(preds):
L = preds.shape[1]
tmp = np.sum(preds, axis=1)
tmp = np.minimum(tmp, L - tmp)
ent = np.mean((1.0 / (L - np.ceil(0.5 * L))) * tmp)
return ent
构建投票分类器
现在让我们比较两个集成模型,以评估其成员的多样性。在以下示例中,我使用了pycaML,它允许用几行代码训练和比较模型及集成模型。
我为第一个集成模型选择了 9 个模型,为第二个集成模型选择了 10 个模型。每个模型都使用默认参数进行训练。数据集可在UCI 存储库中获得。
模型比较 为了有一个基准来比较集成模型,下面的图展示了表现最好的 10 个模型。我们将重点关注 F1 得分。
from pmlb import fetch_data
from pycaML import ClassificationExperiment
name = 'diabetes'
df = fetch_data(name, return_X_y=False)
df.head()
df.to_csv(f'{name}.csv', index=False)
from pycaML import ClassificationExperiment
exp = ClassificationExperiment(name = name)
exp.load_data(f'{name}.csv', target='target')
exp.start()
exp.result.loc[:, exp.result.columns.str.contains('Test')].head(10)
在糖尿病数据集上表现最好的 10 个模型,由 pycaML 生成。图像由作者提供。
预测矩阵可以如下计算。最终,我们将得到一个 154x19 的矩阵,每一行是测试集中的一个实例,每一列是一个模型。得到矩阵后,我们可以将其拆分为两个集成模型进行比较。第一个包含奇数索引的模型,而第二个包含偶数索引的模型。
preds = pd.DataFrame()
for preds in exp.result.index:
preds[preds] = exp.predict(preds)
preds.shape
# (154, 19)
ensemble_1 = preds.iloc[:, 1::2]
ensemble_2 = preds.iloc[:, ::2]
def disagreement_matrix(preds):
res = np.zeros((preds.shape[1], preds.shape[1]))
for i in range(preds.shape[1]):
for j in range(i, preds.shape[1]):
res[i, j] = disagreement(preds, i, j)
res[j, i] = res[i, j]
return res
sns.heatmap(d, annot=True, cmap='coolwarm', vmin=0, vmax=1, cbar = False)
查看集成模型 1 的不一致矩阵,似乎 Perceptron 是与其他模型不一致最多的模型。另一方面,提升模型之间的不一致较少。
集成模型 1 的不一致矩阵。图像由作者提供。
集成模型 2 包含不同的模型。总体来看,不一致性相当低。与其他模型不一致最多的是 Passive Aggressive Classifier。
集成模型 2 的不一致矩阵。图像由作者提供。
由于集成模型 1 具有更高的熵,这意味着其多样性更高。
熵得分。图像由作者提供。
现在让我们通过投票来建立最终模型,这种元模型被称为硬投票。两个集成模型使用与之前相同的指标进行评估,即 F1 得分。
preds_voting = preds.mode(axis=1)[0]
print(f'F1 score for {model}: {f1_score(preds_voting, y_test, average="macro"):.3f})
模型评估后的结果。图像由作者提供。
最终,我们展示了更高的多样性(集成模型 1)导致了更好的集成模型。比较中找到的最佳模型是 Extra Trees,其 F1 得分为 0.71。最终模型的表现比在数据集上训练的最佳模型高出不少,达到了 0.764 的得分。
结论
集成学习是一种强大的机器学习技术,它涉及训练多个模型并结合它们的预测以实现更好的性能。我们发现,当集成中的模型多样性增加时,集成的性能也会提高。
这一发现突显了集成学习中多样性的价值。在构建集成模型时,考虑模型之间的多样性对于实现最佳性能至关重要。
因此,下次你在进行机器学习项目并考虑使用集成学习时,记得优先考虑模型之间的多样性。这可能会对模型的最终性能产生重要影响。
喜欢这篇文章吗?通过订阅我的通讯, 数据面试,每周获取数据科学面试问题。
此外,你还可以在 LinkedIn上找到我。
参考文献
[1] Géron, A. (2019). 实用机器学习与 Scikit-Learn、Keras 和 TensorFlow: 构建智能系统的概念、工具和技术(第 2 版)。O’Reilly.
[2] Kuncheva, L.I., Whitaker, C.J. 分类器集成中的多样性测量及其与集成准确性的关系。 机器学习 51, 181–207 (2003). doi.org/10.1023/A:1022859003006
理解推动产品的多层数据管理
产品领导者需要知道如何通过数据解决阻碍
·发表于 Towards Data Science ·阅读时间 11 分钟·2023 年 12 月 19 日
–
图片由 American Heritage Chocolate 提供,来源于 Unsplash
当我看到上面那层层叠叠的蛋糕时,我立刻想吃它,但我也意识到,这些层次与推动现代数据产品的数据管理层次之间有许多有用的相似之处。
就像每一层蛋糕都涉及巧克力一样,每一层数据管理也涉及数据。这一点并不意外。
但每一层都是独特的。蛋糕有黑巧克力、牛奶巧克力和白巧克力层。成功制作蛋糕的每一层需要不同的成分,正如成功执行我们数据管理流程的每一层也需要不同的成分一样。
因此,我们来到了本文的重点:实现产品愿景需要在每个开发层次上具备正确的成分。对于数据密集型产品来说,不理解实现产品所需的数据管理不同层次,就像不知道我们要烘焙的蛋糕的层次一样令人无助。
如果我们(产品领导者)仅仅理解最上层(白巧克力),当团队在其他层次遇到困难时,我们将难以解锁团队的进展。我们可能会失败地理解为什么我们一开始会被阻塞,并错过为团队设立成功的机会。
产品领导层负责协商路线图、交付物和时间表。当我们不理解成功实现产品愿景所需的内容时,我们就把产品的交付置于风险之中。
在此基础上,让我们深入探讨数据管理的不同层次,产品领导者应当了解这些层次,以及我们如何解锁在数据管理堆栈的任何层次上遇到摩擦的团队。
有关更多数据工程建议和反思 在 Substack 上找到我!并收听我的 YouTube 频道 以获取实际操作教程。
标记数据产品中的数据管理层次
这里是我在现代分析堆栈中遇到的层次,我与包括娱乐、能源、金融、零售、医疗保健和广告在内的多个行业的团队合作:
-
原始数据,通常非常详细且未经精炼/处理
-
衍生数据,由处理和转化原始数据生成
-
汇总数据,即原始和/或衍生数据的聚合
-
整理后的产品数据,结合了衍生和/或汇总的数据
在某些环境中,这些层次之间的界限可能会变得模糊。通常,数据产品生命周期开始时观察到的“原始数据”也包含了大量的软件工程。但是为了简化概念,我们将“原始数据”视为存在于最细粒度和原始格式的数据,这些数据对数据工程方面的操作是可访问的。
对产品领导者的快速热点评:数据 就是 你的产品
Cullan Smith 在 Unsplash 上的照片
我的整个职业生涯都在策略、设计、工程和解释分析产品。不是每个项目都成功,多年来我观察到,最容易失败的方式是将数据及其周围的流程/管道视为简单商品。
数据起初正是如此:一种商品。但通过业务逻辑、背景和产品/设计权衡的应用,你的数据变成了一个产品。这个产品对其他内部团队和外部应用程序都有价值。
这就是为什么我如此坚定地帮助产品领导者理解数据的不同层次。在现代技术环境中,我们需要了解原始数据如何转变为产品,以便有效地管理交付结果的各个方面。
产品领导者,请听我说:数据不再是你插入前端应用的简单、未过滤的资源。数据应在到达应用层之前,已经 预加载了产品价值。
原始数据:你需要了解的内容
原始数据的简单解释是,它是你的数据团队可以访问的最详细和最细粒度的信息。例如:
-
原始交易详情
-
原始客户信息
-
原始事件/仪器数据
-
来自第三方系统的混乱数据
在这层面,产品领导者应主要关注两件事:
-
我们是否拥有所需的所有原料?
-
原料是否高质量?
如果这两个问题的答案都是肯定的,那么我们就处于良好的状态,我们的工程师和分析师更接近于成功。
如果其中一个问题的答案是否定的,那么我们需要做一些工作来确保团队的成功。这通常需要负责:
-
与领导者和利益相关者设定期望。
-
在可用原料和产品愿景之间进行权衡。
-
与其他团队合作,规划引入新的(或改进现有的)原料的工作。
我们可能会在“原始数据质量”的话题中迷失,因此目前不妨沿用老话:垃圾进,垃圾出。即使在产品领导层的最高级别,倡导数据质量也应该是优先考虑的。
原始数据层对于非技术领导者来说可能有点抽象,并且在规划路线图和交付时间表时不常被考虑。具有讽刺意味的是,这一层的数据不足会大幅降低将创新理念推向市场的能力。当我们缺乏成功的关键原料时,想法往往难以转化为产品。
派生数据:将上下文应用于原始数据
想象一下你去餐厅点餐。如果服务员把一堆原始蔬菜扔到你桌子上,根部还沾着泥土,你会有什么感觉?当业务用户连接到“自助分析”时,接触到脏乱的原始数据,可能会有类似的感觉。
照片由Markus Spiske提供,来源于Unsplash
派生数据层是将原始数据提炼和转换为推动产品的燃料的地方。这涉及到清理数据,并且通常还涉及应用对下游过程有重大影响的业务逻辑和假设。确保在这一层实施的业务逻辑正确且一致是确保产品成功的重要部分。
这是产品领导者需要了解的关于派生数据层的内容:
-
存在提取、转换和加载(ETL)数据的过程。
-
你的团队负责其中的一些 ETL 过程。
-
你的团队可能依赖于其他团队拥有的 ETL 过程。
-
在这一层中业务逻辑和假设是不可避免的。
为产品领导者提炼出“所以怎样”:
-
在 ETL(提取、转换、加载)过程将原始数据转换为更具业务相关性的格式时,会应用业务逻辑和假设。
-
如果任何业务逻辑或假设是不完整或不正确的,这将对你的产品产生负面影响。
-
如果任何不完整/不正确的定义由其他团队负责,我们需要进行跨团队的外交以解决问题。
-
在可用原料和产品愿景之间做出权衡,是工程和产品之间的舞蹈。
如果你曾经看到过两个产品(或仪表板)在一个重要指标上不一致,那么根本原因很可能存在于数据管理的这一层。
表面上看,这似乎是一个纯粹的数据工程问题,但请记住:数据就是产品。这绝对是产品领导的关注点,产品领导可以通过帮助高级管理人员理解消除摩擦的价值主张,从而积极推动事情向前发展,为产品的生命线(数据)创造成功的条件。
照片由 Iván Díaz 提供,发布在 Unsplash 上
在这一层投资资源往往被高级产品领导视为纯粹的成本(投资回报率低或没有),这里的项目经常被“全新”功能开发所搁置。总是有压力去开拓新领域并推出创新产品。在这种对话中,进行有效沟通的关键在于为非技术领导者框定叙事,以便他们理解消除各种技术流程中的摩擦如何将产生的价值倍增。
驾驶一个漏气的轮胎会减慢行程,并可能造成进一步的损坏,导致更高的成本和错过的截止日期。投资于修复漏气的轮胎在帮助你的团队和产品到达目的地方面是价值的倍增器。这项投资还可能导致发现新的产品机会,因为你的团队能够灵活应变,并在开发有价值的功能时迅速行动。
汇总数据:既是福音也是诅咒
当我刚进入分析领域时,我很少看到分析师参与到衍生数据层。数据由独立的数据工程团队汇总并交付到汇总表格中,然后更接近业务背景的分析师会从那里开始。流程如下:
-
选择你喜欢的商业智能(BI)工具
-
寻找数据分析师或商业智能分析师
-
连接到包含汇总表格的数据库
-
根据需要应用 SQL 以连接和交付数据到 BI 工具
-
连接到汇总表格/视图并构建仪表板
这种模式今天仍然非常相关,如果你认识到这种模式,那么你可能会遇到这些问题:
-
“我们为什么无法获取 XYZ 指标的可见性?”
-
“我们为什么无法深入到特定交易/产品/客户?”
-
“我们如何从仪表板中获取更多可操作的洞察?”
-
“为什么我们的前两个仪表板显示不同的数字?”
这些问题驱使许多现代分析师深入工程堆栈,承担派生数据层的责任。随着数据生成量的增加,更多的工程流程正在开发以管理大规模数据。几十年前,我们有“IT”团队,这些团队中的一部分分叉成为数据工程,之后又分叉成数据平台工程、分析工程等。
照片由Jens Lelie提供,来源于Unsplash
角色仍在不断发展,但有一点很明确:数据团队的工作范围和责任正在扩大。而在管道中注入产品专业知识的时机和地点的重要性也在增加。
产品领导者需要了解以下内容,以跟上时代的步伐:
-
并非所有摘要都是相同的。
-
摘要表可能会轻易过度简化你所寻求的价值点。
-
没有产品意图的数据总结会导致洞察力浅薄。
-
产品领导者应意识到粒度与性能之间的反向关系,以及这如何影响产品权衡。
当数据工程师与业务脱节但负责为分析师构建摘要表时,挑战会在摘要层显现。当业务背景未融入数据工程工作中时,数据工程师通常会倾向于总结尽可能多的数据,以便商业智能工具(或其他下游系统)能够处理这些数据。分析师则使用这些数据,因为这是他们所拥有的,但他们难以深入挖掘以提取业务同事所需的信息。
多年来,我观察到团队在面对上述挑战时,会执行一个或多个以下操作:
-
引入更多商业敏锐的工程师(分析工程师),以弥合商业与工程之间的差距。
-
更少依赖试图在一个地方总结所有内容的通用非规范化表(宽广的覆盖面,浅显的洞察深度)。
-
更多依赖临时分析,虽然能深入数据,但不适用于通用的摘要表。
-
开发针对特定产品需求的目标摘要表,紧密结合产品目标。
上述第四点引出了数据管理产品领导者需要了解的最终层级:精心策划的产品数据。
精心策划的产品数据:一台运转良好的机器
照片由Tim Mossholder提供,来源于Unsplash
并非所有的摘要数据都准备好接受大舞台的考验。好的产品设计不是偶然的,好的数据架构也不是。
每个组织都有“自下而上”的数据管道。也就是说,管道从大量数据开始,经过转换和汇总,直到可被下游系统(如仪表板)使用。我们并不总是知道结果数据将如何使用,但我们知道在使用之前它需要被汇总。
这种方法对于各种工作仍然很有用,从自动化工作流到自助分析。然而,仅仅对汇总数据进行表面上的了解往往无法满足产品需求。它会留下大量未被利用的价值,难以从详细的数据中提取深层次的见解。
我们经常需要对详细数据应用特定的业务逻辑和假设,以生成能够为我们的产品提供有洞察力、针对性信息的数据源。
这需要一种“自上而下”的方法:
-
从产品创意开始,或从具有产品洞察力的工程师开始,他们能够看到数据中存在的产品潜力。
-
确定能够支持这些创意的数据。
-
协作(工程 + 产品)以发现利用现有资源可以实现的目标。
-
展示产品/技术权衡并定义最小可行产品数据层。
-
设计数据架构;从产品层的需求开始,向后工作,经过其他数据管理层。
这确实是一个过程。它需要能够与工程师沟通的产品人员,并且需要能够与产品沟通的工程师。理想情况下,它涉及具有产品和工程双重思维的高级工程师。数据专业人员在两者之间保持良好的平衡是非常宝贵的。投资于这种价值可以开辟创新的数据产品;不投资于这种价值则往往导致乏善可陈的“自下而上”的分析。
产品/工程的平衡非常重要。完全由工程人员组成的数据团队,如果没有产品意识,注定会产生提供了数据难以获取的部分值的庞大表格。完全由产品人员组成且缺乏技术意识的领导者可能会在错误的地方施加压力,困惑于为什么他们的团队无法让数据发挥作用。
成功的现代分析团队拥有产品愿景和技术技能,能够开发精心策划的产品层,能够从最细粒度的数据中开辟价值路径。实现这一目标的技能既包括工程,也包括产品。我称之为产品工程。
知道就是胜利的一半
由Greg Rakozy拍摄,来源于Unsplash
在这篇文章中,我们深入探讨了数据产品团队幕后发生的一个方面。还有很多内容需要讨论,因为成功地应对现实世界的产品交付涉及更多的工程,识别用户需求,搞清楚这些用户真正需要什么,并了解那些可能影响资源供应链的办公室政治,这些因素都可能决定团队的成功与否。
我的目标是让数据密集型产品团队的领导者考虑如何为他们的团队铺平成功的道路。当产品期望与工程现实不一致时,所有涉事人员都可能感到压力和沮丧。希望本文中讨论的话题能促使读者考虑数据工程期望和责任在其组织内部的变化。
让我们再一次提到影响数据密集型产品交付的数据管理层面。牢记这些要点可以使工程和产品之间的对话更加富有成效:
-
原始数据层
-
派生数据层
-
汇总数据层
-
精心策划的产品数据层
成功确定产品需求范围、在组织内协商优先级、与高级领导设定期望,并解决数据团队的问题,都是交付数据产品的重要因素。
提高对幕后各种数据管理层的认识将有助于你实现团队的成功,并提供更一致的结果。数据确实重要,也许与数据本身同样重要的是你开发的那些使数据成为推动产品的宝贵资源的过程。
了解时间序列趋势
原文:
towardsdatascience.com/understanding-time-series-trend-addfd9d7764e
确定性趋势与随机趋势,以及如何处理它们
·发表于Towards Data Science ·阅读时间 6 分钟·2023 年 3 月 14 日
–
图片由Ali Abdul Rahman拍摄,来源于Unsplash
检测和处理趋势是时间序列建模的关键步骤。
在本文中,我们将:
-
描述时间序列的趋势及其不同特征;
-
探索如何检测它;
-
讨论处理趋势的方法;
了解趋势
趋势作为时间序列的构建块
在任何给定时间,时间序列可以分解为三个部分:趋势、季节性和剩余部分。
时间序列的加法分解
趋势表示时间序列水平的长期变化。这种变化可以是向上(水平上升)或向下(水平下降)。如果变化在一个方向上是系统性的,那么趋势就是单调的。
美国 GDP 时间序列具有向上和单调的趋势。数据来源于参考文献[1]。作者提供的图片。
趋势作为非平稳性的原因
如果时间序列的统计属性不变,则该时间序列是平稳的。这包括时间序列的水平,在平稳条件下保持不变。
因此,当时间序列表现出趋势时,平稳性假设并未得到满足。建模非平稳时间序列具有挑战性。如果不处理,统计测试和预测可能会产生误导。这就是为什么在建模时间序列之前检测和处理趋势很重要。
对趋势的适当描述会影响建模决策。这进一步影响预测性能。
确定性趋势
趋势可以是确定性的,也可以是随机的。
确定性趋势可以用一个明确的数学函数建模。这意味着时间序列的长期行为是可预测的。任何偏离趋势线的情况只是暂时的。
在大多数情况下,确定性趋势是线性的,可以写成如下形式:
线性趋势的方程。系数b 是连续期间趋势的预期变化。系数 a 是截距。
但,趋势也可以遵循指数或多项式形式。
指数趋势方程。通过对两边取对数,这一趋势可以变为线性。
在经济学中,有几个时间序列的例子呈指数增长,例如 GDP:
美国 GDP 时间序列。原始趋势是指数的,但在对数变换后变为线性。数据来源于参考文献[1]。作者提供的图像。
具有确定性趋势的时间序列称为趋势平稳。这意味着去除趋势成分后,序列变得平稳。
线性趋势也可以通过将时间作为解释变量来建模。以下是你可以这样做的一个例子:
import numpy as np
import pandas as pd
from statsmodels.tsa.arima.model import ARIMA
# https://github.com/vcerqueira/blog/blob/main/data/gdp-countries.csv
series = pd.read_csv('data/gdp-countries.csv')['United States']
series.index = pd.date_range(start='12/31/1959', periods=len(series), freq='Y')
log_gdp = np.log(series)
linear_trend = np.arange(1, len(log_gdp) + 1)
model = ARIMA(endog=log_gdp, order=(1, 0, 0), exog=linear_trend)
result = model.fit()
随机趋势
随机趋势可以随机变化,这使得它们的行为难以预测。
随机游走是具有随机趋势的时间序列的一个例子:
rw = np.cumsum(np.random.choice([-1, 1], size=1000))
随机游走时间序列,其趋势突然和不可预测地变化。作者提供的图像。
随机趋势与单位根、积分和差分有关。
具有随机趋势的时间序列被称为差分平稳。这意味着通过差分操作可以使时间序列变得平稳。差分指的是取连续值之间的差异。
差分平稳时间序列也称为积分时间序列。例如,ARIMA(自回归积分滑动平均)模型包含一个特定的术语(I)用于积分时间序列。这个术语涉及应用差分步骤直到序列变得平稳。
最终,差分平稳或积分时间序列的特征是单位根。不深入数学细节,单位根是非平稳时间序列的一个特征。
预测含义
确定性和随机趋势对预测有不同的影响。
确定性趋势在整个时间范围内具有恒定的方差。在线性趋势的情况下,这意味着斜率不会改变。但现实世界中的时间序列展示了复杂的动态,趋势在长期内发生变化。因此,使用确定性趋势模型进行长期预测可能会导致性能较差。假设方差恒定会导致预测区间过窄,从而低估了不确定性。
随机游走的多个实现。作者提供的图像。
随机趋势假定随着时间变化。因此,时间序列的方差会随着时间增加。这使得随机趋势在长期预测中更具优势,因为它们提供了更合理的不确定性估计。
如何检测趋势
随机趋势可以通过单位根测试来检测。例如,扩展的迪基-福勒测试或 KPSS 测试。
扩展的迪基-福勒(ADF)测试
ADF 测试检查自回归模型是否包含单位根。测试的假设包括:
-
原假设:存在单位根(时间序列不是平稳的);
-
备择假设:不存在单位根。
这个测试在statsmodels中可用:
from statsmodels.tsa.stattools import adfuller
pvalue_adf = adfuller(x=log_gdp, regression='ct')[1]
print(pvalue_adf)
# 1.0
参数*regression=‘ct’*用于在模型中包括一个常数项和确定性趋势。正如你可以在文档中检查的那样,这个参数有四个可能的备选值:
-
c: 包括一个常数项(默认值);
-
ct: 常数项加线性趋势;
-
ctt: 常数项加线性和二次趋势;
-
n: 无常数或趋势。
选择应包括哪些项非常重要。错误的包含或排除某项可能会显著降低测试的效能。在我们的案例中,我们使用了ct选项,因为对数 GDP 系列显示出线性确定性趋势行为。
KPSS 测试
KPSS 测试也可以用来检测随机趋势。测试假设相对于 ADF 是相反的:
原假设:时间序列是趋势平稳的;
备择假设:存在单位根。
from statsmodels.tsa.stattools import kpss
pvalue_kpss = kpss(x=log_gdp, regression='ct')[1]
print(pvalue_kpss)
# 0.01
KPSS 拒绝了原假设,而 ADF 没有。因此,两种测试都表明存在单位根。请注意,一个时间序列可以具有包含确定性和随机成分的趋势。
那么,你如何处理单位根呢?
如何处理趋势
我们已经探讨了如何使用时间作为解释变量来考虑线性趋势。
处理趋势的另一种方法是差分。不是处理绝对值,而是对时间序列在连续期间的变化建模。
单次差分操作通常足以实现平稳性。然而,有时你需要进行多次差分。你可以使用 ADF 或 KPSS 来估计所需的差分步骤数。pmdarima库在ndiffs函数中封装了这个过程:
from pmdarima.arima import ndiffs
# how many differencing steps are needed for stationarity?
ndiffs(log_gdp, test='adf')
# 2
在这种情况下,对数 GDP 系列需要 2 次差分步骤才能实现平稳性:
diff_log_gdp = log_gdp.diff().diff()
对数 GDP 时间序列的二次差分。图像由作者提供。
关键要点
-
趋势是时间序列的一个关键组成部分;
-
展现趋势的时间序列是非平稳的,因为均值水平不是恒定的;
-
时间序列的趋势可以是确定性或随机的。确定性趋势用明确的数学函数建模,而随机趋势可以随机变化;
-
可以使用单位根检验如 ADF 或 KPSS 来检测随机趋势;
-
差分操作用于从时间序列中去除随机趋势。
感谢阅读,下次故事见!
参考文献
[1] 各种国家的 GDP 时间序列数据(许可证:CC0:公共领域)
[3] 随机游走——介绍及其性质 由 Ben Lambert 制作
了解我们失去的东西
我们如何应对大型语言模型中的灾难性遗忘
·
关注 发表在 Towards Data Science ·8 min read·2023 年 5 月 23 日
–
图 1:遗忘的共享体验。 图像由 OpenAI 开发的 DALL·E 生成。
遗忘是人类经历的固有部分。我们都会丢失钥匙,忘记一个熟悉的名字,或者记不起几天前晚餐吃了什么。但这种明显的记忆缺失不一定是缺陷。相反,它突显了一个复杂的认知机制,使我们的大脑能够优先排序、筛选和管理大量信息。遗忘,悖论般地,证明了我们学习和记忆的能力。
就像人们会遗忘一样,机器学习模型也会遗忘——特别是大型语言模型。这些模型通过调整内部参数以响应数据暴露来进行学习。然而,如果新数据与模型之前学到的内容相反,它可能会覆盖或削弱旧信息。即使是相符的数据也可能会干扰并调整本来很好的学习权重。这种现象被称为“灾难性遗忘”,是训练稳定和多才多艺的人工智能系统中的一个重大挑战。
LLM 中遗忘的机制
核心是,LLM 的记忆存在于其权重中。在神经网络中,每个权重本质上构成了网络高维权重空间中的一个维度。随着学习过程的展开,网络在这个空间中导航,依靠选择的梯度下降,旨在最小化损失函数。
这个损失函数通常是 LLM 分类任务中的交叉熵损失形式,它将模型的输出分布与目标分布进行比较。从数学角度看,对于目标分布 y 和模型输出 ŷ,交叉熵损失可以表示为:
在训练过程中,网络调整其权重以最小化这个损失。现在,决定一个权重应该改变多少的核心因素是学习率。在随机梯度下降更新规则中:
η 是学习率。然而,选择这个学习率可能很棘手,并且对灾难性遗忘有影响。如果 η 较高,模型具有很高的适应性,能够快速学习新任务,但可能会丧失先前的知识。较小的 η 能保持旧知识,但可能会影响新任务的学习。
此外,当我们意识到权重更新并不是独立时,复杂性就上升了。调整与某一特征相关的权重可能会无意中影响其他特征的表现,导致复杂的依赖关系网络。
我们还必须考虑训练过程中任务或数据的课程顺序。顺序引入任务可能会导致后续任务的主导地位,使模型偏向最新学习的任务,这是灾难性遗忘的直接表现。
应对灾难性遗忘的策略
我们希望我们的 LLM 记住的信息超出我们自身的记忆。因此,我们努力构建在内存使用上高效的系统,但不一定受限于我们的生物标准。在与 LLM 中的灾难性遗忘作斗争的过程中,研究人员开发了几种创新策略。最突出的三种策略包括弹性权重巩固、渐进神经网络和优化固定扩展层。每种技术都采用了独特的数学方法来缓解遗忘问题。
弹性权重巩固(EWC):记住每个权重的重要性
EWC 受到神经科学和贝叶斯推理的启发,其目标是量化每个权重对模型之前学习的任务的重要性。基本思想是,在遇到新数据时,对先前任务关键的权重应进行较少的调整。
图 2:EWC 示意参数空间, www.pnas.org/doi/full/10.1073/pnas.1611835114
在图 2 中,我们可以清楚地看到弹性权重巩固(Elastic Weight Consolidation)在训练任务 B 时防止灾难性遗忘所起的关键作用,而不会丧失我们从任务 A 中获得的知识。该图显示了参数空间,其中灰色区域表示任务 A 的最佳表现,奶油色区域表示任务 B 的良好表现。在我们学习任务 A 之后,我们的参数值标记为θ*A。
如果我们只集中于任务 B,并沿其梯度方向采取步骤(如蓝色箭头所示),我们将最小化任务 B 的损失,但可能会抹去我们对任务 A 的知识——这就是灾难性遗忘的问题。另一方面,如果我们用相同的系数约束所有权重(如绿色箭头所示),我们会施加一个严厉的限制,使我们保留任务 A 的记忆,但使得学习任务 B 变得困难。
这是 EWC 发挥作用的地方——它通过为任务 B(由红色箭头指示)找到一个不会对任务 A 的知识产生重大影响的解决方案,从而找到最佳平衡点。它通过具体确定每个权重相对于任务 A 的重要性来实现这一点。
EWC 在损失函数中引入了一个二次惩罚,限制重要权重的修改。这个惩罚项与当前权重值和初始权重值之间的差异的平方成正比,由重要性因子进行缩放。这个重要性因子通过费舍尔信息矩阵(Fisher Information Matrix)计算,作为权重对先前学习任务的意义的启发式指标。
在弹性权重巩固中,首先对神经网络进行任务 A 的训练,然后计算并保存费舍尔信息矩阵(Fisher Information Matrix)及学得的权重。在对网络进行任务 B 的训练时,EWC 修改损失函数以包括一个惩罚项,该惩罚项使用保存的 FIM 和权重计算,阻止对任务 A 关键权重的剧烈变化,从而在学习新任务与保留先前任务的知识之间取得平衡。惩罚的二次性质确保了从初始权重的较大偏差会产生更高的惩罚。通过对对先前任务贡献更大的权重赋予更大的惩罚,EWC 旨在在适应新信息的同时保留其学习的知识。
渐进神经网络(ProgNet):构建神经网络塔
ProgNets 引入了一种新架构,使网络在遇到新任务时能够扩展。它不是改变单个网络的权重,而是为每个任务添加一个新的网络(或列),将这些列堆叠起来,类似于建造一座塔。每个新列连接到所有以前添加的列,但反向不连接,从而保留旧列中的知识。
在 ProgNet 背后,每个任务由一个独立的列学习,输出是所有先前和当前列输入的函数。先前列的权重保持不变,防止任何灾难性遗忘,而新列的权重正常训练。
图 3 : 基于块的 ProgNet 模型, arxiv.org/abs/1606.04671
设想渐进神经网络为一组独立的处理单元,每个单元能够识别并利用与其任务相关的最重要输入。以图 3 为例,输出₃不仅与直接连接的隐藏层 h₂ 进行交互,还与之前列的 h₂ 层进行接口,通过其独特的横向参数修改这些层的输出。这个输出₃ 单元扫描并评估可用数据,战略性地忽略不必要的输入。例如,如果 h₂¹ 包含所有需要的信息,输出₃ 可能选择忽略其他信息。另一方面,如果 h₂² 和 h₂³ 包含有价值的信息,输出₃ 可能会优先关注这些,而忽略 h₂¹。这些侧向连接使网络能够有效管理跨任务的信息流,同时排除无关数据。
优化固定扩展层(OFELs):每个任务的新房间
OFELs 的概念就像为每个新家庭成员在房子里建一个新房间。在神经网络的背景下,OFELs 为 LLM 遇到的每个任务添加一个新层。这种层的扩展允许网络容纳新信息,而不会干扰已学到的内容。
图 4 : OFEL 图示, www.mdpi.com/2073-4425/10/7/553
OFELs 涉及到修改网络本身的架构。在这里,对于每个新任务,会在神经网络中添加一个新层,而不是重新训练整个网络。这种架构修改有助于在特定层中封装所需的知识,从而最小化对旧层已有权重的影响。
其中 g 是激活函数。OFELs 的架构设计使其能够加入一个专门用于新任务的新层,这意味着网络可以独立处理新输入(x_new
)与旧输入(x_old
)。本质上,虽然方程式呈现了架构中底层过程的全面视图,但在推理或预测新任务时,我们通常只会使用x_new
而不需要x_old
。
通过有选择地优化新层,OFELs 在获取与新任务相关的知识和保留之前学到的信息之间取得了微妙的平衡。这一精细的优化过程使模型能够适应新挑战,同时保持利用先前知识的能力,从而实现更强大和多样化的学习。
前瞻性学习
遗忘——无论是在人的大脑还是 LLMs 中——都是一个迷人的悖论。一方面,它可能成为持续学习和适应能力的障碍。另一方面,它是我们大脑和 AI 模型管理和优先处理信息的固有部分。应对灾难性遗忘的策略——弹性权重巩固、渐进神经网络和优化固定扩展层——提供了富有洞察力但又各不相同的方法,以保留大型语言模型的保留能力。它们各自提供独特的解决方案,反映了人工智能领域必须持续体现的机智和适应性。然而,至关重要的是要理解,灾难性遗忘的问题尚未完全解决;在这一领域仍有待开发的途径,需要深入探索、创新和创造力。
解决灾难性遗忘的挑战不仅推动我们朝着更高效的 AI 系统前进,而且朝着对学习和遗忘的更深入理解——这是人类和机器共同拥有的认知功能。因此,对于研究人员、科学家、从业者以及任何对智能工作原理感兴趣的人来说,参与这一持续对话变得至关重要。驯服灾难性遗忘现象的探索不仅仅是学术追求,而是一段承诺重新定义我们关系理解的旅程。
使用 Pytest 对 PySpark 代码进行单元测试
原文:
towardsdatascience.com/unit-testing-pyspark-code-using-pytest-b5ab2fd54415
·发表于Towards Data Science ·10 分钟阅读·2023 年 1 月 16 日
–
我非常喜欢单元测试。
阅读了两本书—— 《务实程序员》 和 《重构》 ——彻底改变了我对单元测试的看法。
“测试不是为了找出漏洞。
我们相信,测试的主要好处发生在你思考和编写测试的时候,而不是运行它们的时候。”
— 《务实程序员》,大卫·托马斯和安德鲁·亨特
我不再把测试视为在完成数据管道后需要完成的繁琐工作,而是将其视为一个强大的工具,用于改善代码设计,减少耦合,更快速地迭代,并与工作中的其他人建立信任。
然而,为数据应用程序编写良好的测试可能很困难。
与传统的软件应用程序具有相对明确的输入不同,生产环境中的数据应用程序依赖于大量且不断变化的输入数据。
在测试环境中准确地表示这些数据,以足够的细节覆盖所有边界情况,可能是非常具有挑战性的。有些人认为对数据管道进行单元测试没有什么意义,而是更关注数据验证技术。
我坚信在数据管道中同时实现单元测试和数据验证。单元测试不仅仅是为了找出漏洞,更是为了创建设计更好的代码,并与同事和最终用户建立信任。
如果你能养成编写测试的习惯,你将编写出设计更好的代码,节省长远的时间,并减少管道失败或在生产中给出不正确结果的痛苦。
单元测试 PySpark 代码的挑战
一个好的单元测试应具备以下特征:
-
专注。每个测试应测试一个单一的行为/功能。
-
快速。允许你快速迭代并获得反馈。
-
孤立。每个测试应该负责测试特定的功能,并且不依赖于外部因素以便成功运行。
-
简洁。创建测试不应包含大量的样板代码来模拟/创建复杂对象以便测试运行。
当涉及到专门为 PySpark 管道编写单元测试时,编写专注、快速、孤立和简洁的测试可能是一个挑战。
你可能会遇到一些障碍……
首先编写可测试的代码
PySpark 管道往往被编写成一个负责多个转换的巨型函数。
例如:
def main(spark, country):
"""Example function for processing data received from different countries"""
# fetch input data
...
# preprocess based on individual country requirements
if country == 'US':
# preprocessing for US
elif country == 'GB':
# preprocessing for UK
else:
# more preprocessing etc..
# join dataframes together
...
# run some calculations/aggregate
...
# save results
...
从这个角度考虑转换是合理的,并且在很多方面更容易理解和阅读。
但是,当你开始尝试为这个函数编写测试时,你会很快意识到,编写一个覆盖所有功能的测试是非常困难的。
这是因为函数高度耦合,并且函数可能有许多不同的路径。
即使你编写了一个测试来验证输入数据和输出数据是否符合预期。如果测试因任何原因失败,将很难理解长函数的哪个部分出现了问题。
相反,你应该将你的转换拆分为负责单一任务的可重用函数块。然后你可以为每个单独的函数(任务)编写单元测试。当这些单元测试都通过时,你可以更有信心地将所有函数组合在一起,得到最终管道的输出。
编写测试是一种良好的实践,并迫使你考虑设计原则。如果测试你的代码很困难,那么你可能需要重新考虑你的代码设计。
速度
Spark 被优化用于处理非常大的数据,计算也被优化以分布在多台机器上。
这在大型集群中表现得很好,但实际上在你可能用于单元测试的单台机器上会严重影响性能。
当你运行 PySpark 管道时,spark 会评估整个管道并计算一个优化的‘计划’以在分布式集群上执行计算。
计划带来了显著的开销。当你在分布式集群上处理数 TB 的数据时,这是合理的。但在单台机器上处理小数据集时,速度可能会出奇地慢。尤其是与 Pandas 的体验相比。
如果不优化你的 SparkSession 配置参数,你的单元测试将会运行得非常缓慢。
依赖 Spark Session
要在你的单元测试中运行 PySpark 代码,你需要一个 SparkSession。
如上所述,理想情况下,每个测试应与其他测试隔离,并且不需要复杂的外部对象。不幸的是,不能避免为单元测试启动 Spark 会话的要求。
创建 Spark 会话是编写 PySpark 管道单元测试时要克服的第一个障碍。
你应该如何为测试创建 SparkSession?
为每个测试初始化一个新的 Spark 会话会显著增加运行测试的时间,并给测试引入大量样板代码。
高效地创建和共享 SparkSession 对于保持测试性能在可接受的水平至关重要。
数据
你的测试将需要输入数据。
创建大数据管道的示例数据存在两个主要问题。
首先是大小。显然,你不能在生产中使用的完整数据集上运行测试。你必须使用一个更小的子集。
但是,通过使用一个小数据集,你会遇到第二个问题,即提供足够的测试数据来覆盖你想要处理的所有边界情况。
真正很难为测试模拟现实数据。虽然对此没有太多办法,但你可以使用更小、目标明确的数据集进行测试。
使用 Pytest 对 PySpark 代码进行单元测试的步骤
让我们通过一个使用PyTest为 PySpark 管道编写单元测试的示例来进行实践。
💻 完整代码可在此 GitHub 存储库 中获取
示例代码
这是一个处理银行交易的 PySpark 管道示例。在这个场景中,我们希望将原始交易分类为借记账户或信用账户交易,通过将其与一些参考数据连接起来。
每个交易记录都附带一个账户 ID。我们将使用这个账户 ID 连接到包含该账户 ID 是借记账户还是信用账户的信息的账户信息表。
import pyspark.sql.functions as F
from pyspark.sql import DataFrame
def classify_debit_credit_transactions(
transactionsDf: DataFrame, accountDf: DataFrame
) -> DataFrame:
"""Join transactions with account information and classify as debit/credit"""
# normalise strings
transactionsDf = transactionsDf.withColumn(
"transaction_information_cleaned",
F.regexp_replace(F.col("transaction_information"), r"[^A-Z0-9]+", ""),
)
# join on customer account using first 9 characters
transactions_accountsDf = transactionsDf.join(
accountDf,
on=F.substring(F.col("transaction_information_cleaned"), 1, 9)
== F.col("account_number"),
how="inner",
)
# classify transactions as from debit or credit account customers
credit_account_ids = ["100", "101", "102"]
debit_account_ids = ["200", "201", "202"]
transactions_accountsDf = transactions_accountsDf.withColumn(
"business_line",
F.when(F.col("business_line_id").isin(credit_account_ids), F.lit("credit"))
.when(F.col("business_line_id").isin(debit_account_ids), F.lit("debit"))
.otherwise(F.lit("other")),
)
return transactions_accountsDf
这个示例管道存在几个问题:
-
难以阅读。一个地方包含了大量复杂的逻辑。例如,正则表达式替换、按子字符串连接等。
-
难以测试。单个函数负责多个操作
-
难以重用。借记/信用分类是业务逻辑,无法轻松在项目中重用
步骤 1:将代码重构为更小的逻辑单元
让我们首先将代码重构为单独的函数,然后将这些函数组合成主classify_debit_credit_transactions
函数。
然后,我们可以为每个单独的函数编写测试,以确保其按预期行为。
虽然这增加了代码的总体行数,但测试起来更容易,我们现在可以在项目的其他部分重用这些函数。
import pyspark.sql.functions as F
from pyspark.sql import DataFrame
def classify_debit_credit_transactions(
transactionsDf: DataFrame, accountsDf: DataFrame
) -> DataFrame:
"""Join transactions with account information and classify as debit/credit"""
transactionsDf = normalise_transaction_information(transactionsDf)
transactions_accountsDf = join_transactionsDf_to_accountsDf(
transactionsDf, accountsDf
)
transactions_accountsDf = apply_debit_credit_business_classification(
transactions_accountsDf
)
return transactions_accountsDf
def normalise_transaction_information(transactionsDf: DataFrame) -> DataFrame:
"""Remove special characters from transaction information"""
return transactionsDf.withColumn(
"transaction_information_cleaned",
F.regexp_replace(F.col("transaction_information"), r"[^A-Z0-9]+", ""),
)
def join_transactionsDf_to_accountsDf(
transactionsDf: DataFrame, accountsDf: DataFrame
) -> DataFrame:
"""Join transactions to accounts information"""
return transactionsDf.join(
accountsDf,
on=F.substring(F.col("transaction_information_cleaned"), 1, 9)
== F.col("account_number"),
how="inner",
)
def apply_debit_credit_business_classification(
transactions_accountsDf: DataFrame,
) -> DataFrame:
"""Classify transactions as coming from debit or credit account customers"""
CREDIT_ACCOUNT_IDS = ["101", "102", "103"]
DEBIT_ACCOUNT_IDS = ["202", "202", "203"]
return transactions_accountsDf.withColumn(
"business_line",
F.when(F.col("business_line_id").isin(CREDIT_ACCOUNT_IDS), F.lit("credit"))
.when(F.col("business_line_id").isin(DEBIT_ACCOUNT_IDS), F.lit("debit"))
.otherwise(F.lit("other")),
)
3. 使用 Fixtures 创建可重用的 SparkSession
在编写单元测试之前,我们需要创建一个可以在所有测试中重用的 SparkSession。
为此,我们在 conftest.py
文件中创建一个 PyTest fixture。
Pytest fixtures 是一次创建然后在多个测试中重复使用的对象。这对于像 SparkSession 这样的复杂对象特别有用,因为创建它们的开销很大。
# conftest.py
from pyspark.sql import SparkSession
import pytest
@pytest.fixture(scope="session")
def spark():
spark = (
SparkSession.builder.master("local[1]")
.appName("local-tests")
.config("spark.executor.cores", "1")
.config("spark.executor.instances", "1")
.config("spark.sql.shuffle.partitions", "1")
.config("spark.driver.bindAddress", "127.0.0.1")
.getOrCreate()
)
yield spark
spark.stop()
设置一些配置参数以优化 SparkSession,用于在单台机器上处理小数据进行测试是很重要的:
-
master = local[1]
– 指定 spark 在一台本地机器上运行,并且使用一个线程 -
spark.executor.cores = 1
– 设置核心数为一个 -
spark.executor.instances = 1
- 设置执行器为一个 -
spark.sql.shuffle.partitions = 1
- 设置最大分区数为 1 -
spark.driver.bindAddress = 127.0.0.1
– (可选)显式指定驱动程序绑定地址。如果你的机器也与远程集群有活动连接,这将很有用。
这些配置参数本质上告诉 spark 你在单台机器上处理数据,并且 spark 不应该尝试分发计算。这将大大节省管道执行和计算本身的时间。
注意,建议使用 yield
而不是 return
来返回 spark session。有关更多信息,请阅读 PyTest 文档。使用 yield
还允许你在测试运行后执行任何清理操作(例如,删除本地临时目录、数据库或表等)。
4. 为代码创建单元测试
现在让我们为我们的代码编写一些测试。
我发现最有效的方式是用以下结构组织我的 PySpark 单元测试:
-
创建输入 DataFrame
-
使用我们要测试的函数创建输出 DataFrame
-
指定预期的输出值
-
比较结果
我还尽量确保测试覆盖正面测试用例和至少一个负面测试用例。
from src.data_processing import (
apply_debit_credit_business_classification,
classify_debit_credit_transactions,
join_transactionsDf_to_accountsDf,
normalise_transaction_information,
)
def test_classify_debit_credit_transactions(spark):
# create input test dataframes
transactionsDf = spark.createDataFrame(
data=[
("1", 1000.00, "123-456-789"),
("3", 3000.00, "222222222EUR"),
],
schema=["transaction_id", "amount", "transaction_information"],
)
accountsDf = spark.createDataFrame(
data=[
("123456789", "101"),
("222222222", "202"),
("000000000", "302"),
],
schema=["account_number", "business_line_id"],
)
# output dataframe after applying function
output = classify_debit_credit_transactions(transactionsDf, accountsDf)
# expected outputs in the target column
expected_classifications = ["credit", "debit"]
# assert results are as expected
assert output.count() == 2
assert [row.business_line for row in output.collect()] == expected_classifications
def test_normalise_transaction_information(spark):
data = ["123-456-789", "123456789", "123456789EUR", "TEXT*?WITH.*CHARACTERS"]
test_df = spark.createDataFrame(data, "string").toDF("transaction_information")
expected = ["123456789", "123456789", "123456789EUR", "TEXTWITHCHARACTERS"]
output = normalise_transaction_information(test_df)
assert [row.transaction_information_cleaned for row in output.collect()] == expected
def test_join_transactionsDf_to_accountsDf(spark):
data = ["123456789", "222222222EUR"]
transactionsDf = spark.createDataFrame(data, "string").toDF(
"transaction_information_cleaned"
)
data = [
"123456789", # match
"222222222", # match
"000000000", # no-match
]
accountsDf = spark.createDataFrame(data, "string").toDF("account_number")
output = join_transactionsDf_to_accountsDf(transactionsDf, accountsDf)
assert output.count() == 2
def test_apply_debit_credit_business_classification(spark):
data = [
"101", # credit
"202", # debit
"000", # other
]
df = spark.createDataFrame(data, "string").toDF("business_line_id")
output = apply_debit_credit_business_classification(df)
expected = ["credit", "debit", "other"]
assert [row.business_line for row in output.collect()] == expected
我们现在为 PySpark 管道中的每个组件都有了单元测试。
由于每个测试都重用相同的 SparkSession,运行多个测试的开销大大减少。
PySpark 代码单元测试的进一步提示
创建包含最少所需信息的测试 DataFrame
在创建测试数据的 DataFrame 时,只创建与转换相关的列。
你只需要创建函数所需的列的数据。你不需要所有可能存在于生产数据中的其他列。
这有助于编写简洁的函数,并且更具可读性,因为可以清楚地知道哪些列是函数所需的以及受函数影响的。如果你发现需要一个包含许多列的大 DataFrame 来执行转换,你可能一次尝试做得太多了。
这只是一个指导方针,你自己的用例可能需要更复杂的测试数据,但如果可能,保持数据小巧、简洁,并局限于测试范围。
记得调用一个“操作”以触发 PySpark 计算
PySpark 使用惰性计算。你需要在测试期间调用一个‘action’(例如 collect
、count
等),以计算一个可以与预期输出进行比较的结果。
如果不需要的话,不要运行所有的 PySpark 测试
PySpark 测试通常比正常单元测试运行时间更长,因为有计算计划和执行的开销。
在开发过程中,利用 Pytest 的一些功能,例如 -k
标志来运行单个测试或仅运行单个文件中的测试。然后,仅在提交代码之前运行完整的测试套件。
保持单元测试的隔离性
注意不要在测试期间修改你的 spark 会话(例如,创建一个表,但之后不删除它)。
表格将在所有测试中持续存在,这可能会干扰预期行为。
尽量将数据的创建保持在使用它的地方附近。
你可以使用 Pytest fixtures 在多个测试之间共享数据框,甚至从 CSV 文件等中加载测试数据。
然而,根据我的经验,为每个单独的测试创建所需的数据更容易且更具可读性。
测试正面和负面结果
例如,在测试连接条件时,你应该包括一些不满足连接条件的数据。这有助于确保你既排除了错误的数据,又包含了正确的数据。
本博客最初发布在 engineeringfordatascience.com
如果你使用 Pytest 进行单元测试,可以查看我另一篇包含 Pytest 使用技巧的文章:
13 Tips for using PyTest [## 13 Tips for using PyTest
单元测试是软件开发中非常重要的技能。有一些很棒的 Python 库可以帮助我们……
单变量离散分布:易于理解的解释
数学和视觉上理解单变量离散分布
·发表于 Towards Data Science ·阅读时间 9 分钟·2023 年 10 月 20 日
–
图片来源 unDraw
你是否有这样的感觉?你想学习新的东西,但不知道从哪里开始。这就是我们在想要数学理解分布时的感受。是的,我们的教授向我们解释了所有这些分布,但只是用公式的数学方式!我们的教授没有用易于理解的视觉方式来解释。
这就是为什么我们写这篇文章来讲解最重要的单变量离散分布。我们想以数学和视觉的方式向你解释这些分布。我们的目标是让你理解数学和分布图之间的关系。此外,我们还为每个分布提供了一个例子。
作为数据科学家,了解分布如何工作是非常重要的。分布假设是一些机器学习算法的基础,并且对于解决统计问题至关重要,例如在保险行业中。
我们将讨论以下分布:
-
伯努利分布
-
二项分布
-
几何分布
-
泊松分布
-
均匀分布
让我们深入了解吧!
伯努利分布是什么?
如果一个随机变量 X 只有两个可能的结果(标记为 0 或 1),则存在一个参数为 p 的伯努利分布。X=1(成功)发生的概率是 p,而 X=0(失败)发生的概率是 1-p。
让我们来看一个例子。
示例:
X 代表一次掷硬币的结果,其中 X=1(正面)和 X=0(反面)。p 是硬币落在正面的概率。
接下来,你将看到伯努利分布的视觉和数学解释。首先,我们从数学开始!
数学描述:
伯努利分布具有以下要求:
随机变量 X 是伯努利分布的。概率 p 的值范围在 0 和 1 之间。
概率质量函数(PMF)是
公式显示,当 k=0 时,概率为 1-p。另一方面,当 k=1 时,概率为 p。
该分布的均值和方差是
现在,我们理解了数学原理。接下来,我们看一下视觉解释。
视觉解释:
伯努利分布的视觉解释(图像来源于作者)
在左侧的视觉图中,你可以看到随机变量 X 有两个可能的结果(k = 1 或 k = 0)。在右侧,你看到 k = 1(概率 p = 0.7)和 k=0(概率 p=1–0.7=0.3)的示例。
以下代码显示了如何生成右侧的图表:
import plotly.graph_objects as go
fig = go.Figure(data=[go.Bar(
x=[0, 1],
y=[0.3, 0.7],
width=[0.8, 0.8],
marker_color=['#165AA7', '#165AA7']
)],
layout = {
'xaxis': {'title': 'k', 'tick0': '0', 'dtick': '1'},
'yaxis': {'title': 'Probability', 'range': [0, 1]},
'title': 'p=0.7',
'barmode': 'relative',
}
)
fig.show()
伯努利分布 (n=1) 是二项分布的特例。所以现在让我们看看二项分布吧!
什么是二项分布?
如果一个随机变量 X 等于 n 个具有参数 p 的伯努利分布随机变量的总和,则存在一个具有参数 n 和 p 的二项分布:
X 代表在参数 p 的 n 次伯努利试验中的成功总数(1 的数量)。
具有二项分布的随机变量是 n 个伯努利随机变量的总和!
接下来,我们看一下这种分布的一个示例。
示例:
例如,X 代表 n 次抛硬币中的正面总数。得到正面的概率是 p。
下面,你可以看到二项分布的数学解释。
数学描述:
我们有以下要求:
随机变量 X 是二项分布的。概率 p 的值范围在 0 和 1 之间。n 的值是 1, 2, … 。
概率质量函数(PMF)是
该分布的均值和方差是
现在,我们知道了所有的公式。接下来,我们看看二项分布的视觉解释。
视觉解释:
二项分布的视觉解释(图像来源于作者)
在这个示例中,我们选择 n=6 和 p=0.5。我们可以用 PMF 计算概率(y 轴)的值。计算所有值后,我们得到右侧的图表。k=3(1 的数量)的概率是 31.25%。最高概率是 X=1 发生三次。
假设我们掷一枚硬币六次。那么,硬币正面出现三次的概率是 31.25%。
你可以在下面查看右侧图形的代码:
import plotly.graph_objects as go
fig = go.Figure(data=[go.Bar(
x=[0, 1, 2, 3, 4, 5, 6],
y=[0.015625, 0.09375, 0.234375, 0.3125, 0.234375, 0.09375, 0.015625],
width=[0.8, 0.8],
marker_color=['#165AA7', '#165AA7', '#165AA7', '#165AA7', '#165AA7', '#165AA7', '#165AA7']
)],
layout = {
'xaxis': {'title': 'k', 'tick0': '0', 'dtick': '1'},
'yaxis': {'title': 'Probability', 'range': [0, 0.35]},
'title': 'n=6, p=0.5',
'barmode': 'relative',
}
)
fig.show()
我们使用二项分布的 PMF 手动计算了 y 值。试着在代码中重现这些值。
什么是几何分布?
我们有一个具有参数p的伯努利试验序列。一个具有参数p的几何分布随机变量X表示在第一次成功之前发生的失败试验(0 的数量)。
示例:
你可以使用几何分布来确定设备和组件的寿命,即等待第一次故障或等待第一次成功。
下面,你可以看到几何分布的数学解释。
数学描述:
我们有以下要求:
随机变量X是几何分布的。概率p的值范围在 0 到 1 之间。
概率质量函数(PMF)为
该分布的均值和方差为
现在,我们知道了这个分布背后的数学原理。让我们更直观地看一下它。
视觉解释:
几何分布的视觉解释(图片由作者提供)
X表示失败的数量(0 的数量)。我们假设有一个电子组件,其故障概率为 50%(p=0.5)。对于第一个组件,错误概率为 50%。之后,概率会降低。
在下面,你可以看到右侧图形的代码:
import plotly.graph_objects as go
fig = go.Figure(data=[go.Bar(
x=[0, 1, 2, 3, 4, 5, 6, 7],
y=[0.5, 0.25, 0.125, 0.0625, 0.03125, 0.015625, 1/128, 1/256],
width=[0.8, 0.8],
marker_color=['#165AA7', '#165AA7', '#165AA7', '#165AA7', '#165AA7', '#165AA7', '#165AA7', '#165AA7']
)],
layout = {
'xaxis': {'title': 'k (number of 0s)', 'tick0': '0', 'dtick': '1'},
'yaxis': {'title': 'Probability', 'range': [0, 0.55]},
'title': 'p=0.5',
'barmode': 'relative',
}
)
fig.show()
我们使用 PMF 来计算图形的 y 值。试着自己动手做做看!
什么是泊松分布?
泊松分布是二项分布在试验次数n趋于无穷大且p趋于零时的极限情况。乘积np=λ保持不变。此外,参数λ是分布的均值和方差。λ表示成功事件的平均数量。
泊松分布对二项分布的近似规则:
n >= 50 和 p <= 0.05
泊松分布也被称为稀有事件分布。
示例:
例如,一家汽车保险公司需要计算一年内发生 1、2 或 3 起车祸的概率。
在下面,你可以看到泊松分布的数学解释。
数学描述:
泊松分布有以下要求:
随机变量X是具有参数λ的泊松分布。参数λ的值范围大于 0。
概率质量函数(PMF)为
该分布的均值和方差为
现在,我们知道了泊松分布的公式。是时候从视觉上看看它了。
视觉解释:
泊松分布的视觉解释(图片由作者提供)
我们假设一个保险公司想要知道一年内发生十次车祸的概率。此外,我们知道平均事故率为五。因此,我们可以查看红色图表,看到 k = 10 的概率非常小。这样,你就能得到所有的概率。
接下来,你可以看到右侧图表的代码。
import math
import plotly.express as px
df = pd.DataFrame(columns = ['Parameter', 'k', 'Probability'])
for _lambda in [1, 5, 10]:
for k in range(24):
poisson_formula = (pow(_lambda,k) * math.exp(-_lambda))/math.factorial(k)
df = df.append({'Parameter' : "λ=" + str(_lambda), 'k' : k, 'Probability' : poisson_formula}, ignore_index = True)
fig = px.line(df, x='k', y='Probability', color='Parameter', markers=True)
fig.show()
我们绘制了 λ=1、λ=5 和 λ=10 的图表。此外,我们使用上述 PMF 计算概率。最后,我们绘制了一个折线图,显示 k 值和概率。
什么是均匀分布?
随机变量 X 具有离散均匀分布(参数 a 和 b),可以以相等的概率取 a 到 b 之间的任何整数。在均匀分布中,所有结果的发生概率相同。
示例:
例如,掷一次骰子。随机变量 X 的可能值为:
每个数字的概率是相同的。
接下来,我们将介绍此分布背后的数学。
数学描述:
我们有以下要求:
概率质量函数(PMF)是
此分布的均值和方差为
现在,我们知道如何从数学上定义分布。让我们看看视觉解释。
视觉解释:
均匀分布的视觉解释(图片由作者提供)
在这个例子中,我们设置 a=1 和 b=6。我们可以看到所有 k 的值具有相同的概率。
接下来,你可以看到上面图表的代码。
import plotly.graph_objects as go
fig = go.Figure(data=[go.Bar(
x=[1, 2, 3, 4, 5, 6],
y=[1/6, 1/6, 1/6, 1/6, 1/6, 1/6],
width=[0.8, 0.8],
marker_color=['#165AA7', '#165AA7', '#165AA7', '#165AA7', '#165AA7', '#165AA7']
)],
layout = {
'xaxis': {'title': 'k', 'tick0': '0', 'dtick': '1'},
'yaxis': {'title': 'Probability', 'range': [0, 0.25]},
'title': 'a=1, b=6',
'barmode': 'relative',
}
)
fig.show()
我们使用均匀分布的概率质量函数(PMF)并绘制 x 轴和 y 轴的值。
结论
恭喜你,了解了最重要的单变量离散分布的工作原理。在每个解释中,我们首先给出了直观的示例。然后,我们介绍了分布背后的数学,并将这些数学内容放入程序代码中。程序代码为每个分布生成了一个图表。之后,我们基于图表和图形解释了分布的视觉效果。
希望你对单变量离散分布的理解比以前更好。感谢你的关注。
👉🏽 加入我们免费的每周 Magic AI 新闻通讯,获取最新的 AI 更新!
免费订阅 以在我们发布新故事时收到通知:
[## 每当 Janik 和 Patrick Tinz 发布内容时,您将收到一封电子邮件。
每当 Janik 和 Patrick Tinz 发布内容时,您将收到一封电子邮件。通过注册,如果您还没有创建 Medium 帐户,您将创建一个…
在我们的 关于页面 了解更多关于我们的信息。不要忘记关注我们在 X 上。非常感谢您的阅读。如果您喜欢这篇文章,随时分享。祝您度过愉快的一天!
使用 我们的链接 注册 Medium 会员,以阅读无限制的 Medium 故事。
释放隐藏模式:无监督机器学习文章推荐系统指南
构建一个基于无监督机器学习的文章推荐系统,并生成有助于推荐的特征和模式
·
关注 发表在 Towards Data Science ·8 分钟阅读·2023 年 3 月 7 日
[]
照片由 Salomé Watel 提供,图片来源于 Unsplash
最近有很多关于人工智能和机器学习令人难以置信的能力的讨论。随着我们看到机器学习应用的各种前沿领域,这一转变所产生的更高价值的可能性也在不断增长。像谷歌、微软和英伟达这样的公司正在推动人工智能和机器学习的边界,以利用技术进步建设一个伟大的社会。
现在,人工智能的炒作非常多,一个常被忽视的话题是无监督机器学习。我敢打赌,人们可能已经有机会享受一些最先进的流媒体服务,如Netflix和Amazon Videos,它们拥有最先进的推荐系统。然而,对于作为无监督机器学习一部分的推荐系统的惊人能力却鲜有讨论。
在本文中,我们将主要关注基于用户之前阅读的文章构建文章推荐系统,为他们提供积极的体验,并促使他们阅读更多类似类型的文章。这属于无监督机器学习,因为我们没有关于特定文章是否与其他文章相关的标签。相反,我们仅提供文本信息,我们必须在向用户推荐项目之前突出和可视化相似性。
最初,我们从数据开始,并在继续推荐文章的努力之前了解它们的类型。在浏览数据后,我们将可视化数据中的关键见解。最后,我们根据文章与所有文章列表之间的余弦相似度分数构建推荐系统以获取推荐。
阅读数据
第一步是读取数据。以下是读取数据的代码。读取数据后,我们将进行探索性数据分析。这里是数据的代码和表示。
注意: 数据集来自 CI&T DeskDrop 文章分享与阅读 | Kaggle,根据 数据库内容许可证 (DbCL) v1.0 — 开放数据公约:开放数据的法律工具 许可证
输入数据(作者提供的图片)
在我们的数据中,标题、文本和语言特征是最重要的。其他特征,如内容类型和作者用户代理,对于构建推荐系统的任务并不十分适用。我们将采取步骤去除这些特征,仅考虑标题、文本和语言等特征。请注意,图中未显示所有特征,因为这将成为一张大图。
数据信息(作者提供的图片)
我们获取数据的信息,并确定数据集中存在的非空值的数量。我们发现有些特征如 authorSessionID 和 authorUserAgent 包含超过**50%**的缺失值。我们将删除这些特征以及上面讨论的其他特征,专注于标题、文本和语言特征。
探索性数据分析(EDA)
现在让我们通过使用视觉效果和图表来探索数据集并理解它。第一步是探索数据中存在的文本内容类型。
内容类型的计数图(作者提供的图片)
从图中观察,大部分内容以HTML 链接的形式存在,用于推荐。这些 HTML 链接包含实际的文本数据和标题。我们在这里的重点是从文本中提取有意义的见解,而不是使用其他格式,如‘视频’和‘富媒体’格式。因此,我们可以从项目中删除这些类别。
语言的计数图(作者提供的图片)
我们的大部分文本和标题都是英文标题。由于我们只关注英语语言,我们可以采取措施删除其他类别,以提高我们的推荐模型效率。
标题词云(作者提供的图片)
词云提供了对我们文本语料库中最常出现的词汇的良好理解。词汇在语料库中的出现频率越高,词云图中的文本大小就越大。从图中可以看出,包含‘Google’、‘机器学习’和‘Apple’等词汇的标题在我们的标题空间中经常出现。
文本词云(作者提供的图片)
现在我们已经探索了标题并找出了最常见的词汇,是时候在我们的文章列表中探索文本本身,寻找有趣的趋势和模式了。大量文章包含‘数据’、‘用户’和‘时间’等词汇。
方差解释百分比(作者提供的图片)
将给定的一组标题转换为TFIDF格式并应用主成分分析,上述图表展示了每个组件解释的方差累计百分比。值得注意的是,随着组件数量的增加,解释的方差也在增加,因为更多的组件信息导致了更多的解释和信息。
方差解释百分比(作者提供的图片)
此图显示了 tfidf 组件对整个文本解释的方差,而不仅仅是考虑标题。这个图与之前的图大相径庭,因为只有少数主成分集能够解释数据方差的大部分。因此,这可以帮助降维,因为更少的特征能够解释大部分方差。
K-means 聚类图(图片来源:作者)
聚类是一种将相似的数据点分组在一起的技术,以寻找有趣的模式和共同点。通过这种方式,基于数据点所在的簇,可以给出推荐。
为了确定正确的簇数,使用k-means 聚类模型并遵循肘部法则来找到最佳簇数是很方便的。在我们的案例中,最佳值k 为 11,因为它符合肘部的形状。
PCA 与聚类 2D 图(图片来源:作者)
在执行 PCA 和基于最佳簇数的聚类后,到了可视化聚类结果的时刻。根据上述图,聚类效果相当不错,因为在簇中发现了模式。
PCA 与聚类 3D 图(图片来源:作者)
让我们确定3D 图的外观,以发现潜在的模式。可以看到,有很多空间可以分隔簇,因此可以指导我们的推荐系统根据之前阅读的文本给出好的建议。
还有其他降维技术,如TSNE和Kernal PCA。逐一了解它们以确定最佳聚类对于推荐系统将很有帮助。必须采取步骤可视化文本数据点并生成有趣的模式。
TSNE 与聚类 2D 图(图片来源:作者)
使用 TSNE 降维并以 11 个簇可视化表示,可以看到数据点分布得很好。因此,相似的文章在同一簇中的机会较小。结果,PCA 在聚类和确定最佳簇数方面表现良好。我们还将使用 3D 可视化来指导我们的思考和理解聚类机制。
TSNE 与聚类 3D 图(图片来源:作者)
在执行聚类任务后,可以看到许多文本的数据嵌入非常接近。因此,当数据点没有在各个方向上分布时,准确执行聚类可能具有挑战性。因此,我们可能会寻找替代的聚类方法和降维技术。
核 PCA 与聚类 2D 图(作者提供的图片)
核 PCA 是另一种用于维度缩减的流行方法。如所见,数据表示的分布良好,有助于确定和使用聚类的数量。总体而言,算法很好地减少了维度并分离了数据点。让我们也来看一下使用这种技术进行维度缩减后的3D 表示的聚类结果。
核 PCA 与聚类 3D 图(作者提供的图片)
在用核 PCA 绘制了 3D 生成的表示后,聚类效果相当好,点分布也相当均匀。因此,这种方法可以帮助基于聚类方法生成推荐。
比如,用户访问一个特定的网站并阅读了一段有趣的文本。在这一步之后,基于核主成分分析(PCA)表示的簇中的项目将推荐给用户。用户可能会觉得这些文章很吸引人,从而促进业务增长。
在执行了之前的步骤后,我们定义了一个函数,生成一组有用的特征,这些特征可以被各种推荐系统模型用于进行推荐。以下函数中使用的特征是重要的,能够很好地表示文本的类型及其内容和可读性。
我们将这个函数应用于数据框,并生成一组新的特征,这些特征被推荐系统所使用。最后,余弦相似度在确定感兴趣的文本与所有其他可能的文本和文章列表之间的距离时被考虑在内。
在根据生成的特征和核 PCA 表示获取当前文本的余弦差异后,将其与现有文章列表进行比较,以确定距离簇最小的文章。结果是,这些文章被推荐给用户,使他们的阅读体验变得引人入胜和有趣。
我们导入了各种用于测量余弦相似度和生成结构和内容相似的文章推荐的有用库。
结论
在阅读了这篇详尽的文章之后,你应该对推荐系统在文章上的具体实现细节有了较好的了解。进行维度缩减可以确保我们减少计算资源的消耗,同时在进行预测或提供推荐时减少异常值的影响。感谢你花时间阅读这篇文章。
以下是你可以联系我或查看我工作的方式。
GitHub:suhasmaddali (Suhas Maddali ) (github.com)
YouTube:https://www.youtube.com/channel/UCymdyoyJBC_i7QVfbrIs-4Q
LinkedIn:(1) Suhas Maddali,东北大学,数据科学 | LinkedIn
Medium: Suhas Maddali — Medium
Kaggle:Suhas Maddali | 贡献者 | Kaggle
释放 GPT-3 的力量:超级英雄描述的微调
GPT-3 微调的逐步指南
·
关注 发表在 Towards Data Science ·11 min 阅读·2023 年 2 月 18 日
–
自 2022 年底以来,OpenAI 推出的 ChatGPT 被许多人认为是人工智能的 iPhone 时刻。然而,OpenAI 的聊天机器人并不是第一个生成式 AI 文本机器学习模型,它跟随的是两年前推出的 GPT-3。
OpenAI 为我们提供了一个现成的 GPT-3 训练模型。此外,特定任务可以在较小的数据集上进行微调。例如,假设你想创建一个针对你公司特定的电子邮件回复生成器。首先,你必须收集大量有关你特定业务领域的数据,如客户电子邮件咨询和回复。然后,你可以使用这些数据来微调 GPT-3,以学习你公司特定的语言模式和短语。通过微调 GPT-3,可以创建一个高度定制和专业化的电子邮件回复生成器,专门针对特定业务领域使用的语言模式和词汇。
在这篇博客文章中,我将向你展示如何微调 GPT-3。我们将使用 python 代码进行操作,并且不假设对 GPT-3 有任何先验知识。
微调需要什么?
与当前在 Hugging Face 上提供的 GPT-2 模型不同(撰写本文时),我们没有直接访问 GPT-3 模型的权限。因此,你首先需要从 OpenAI 获取一个 API 密钥,并安装 Python 包 openai,可以通过 pip 快速完成。
获取 OpenAI 的 API 密钥:
-
创建一个账户,
-
点击‘创建新密钥’,然后
-
复制密钥。
关键是以‘sk-’开头的一长串字符。确保你保密!一旦你拥有了你的密钥,一个简单的获取密钥的方法是在终端中执行以下操作:(个人而言,为了简便,我将其放在我的*.bachrc*中):
export OPENAI_API_KEY=sk-t59pgejhtrff5(...)
使用 GPT-3 模型是有成本的。我们需要积分。撰写本文时,当你创建一个新账户时,你会获得免费的积分来尝试这个工具。我不知道这种情况是否会持续…
现在我们有了密钥和 Python 包,是时候考虑我们需要微调的数据了。首先,我们需要一个用于微调的示例文件,其中每个示例都是一个 prompt,后跟相应的 completion。
一个超级英雄描述生成工具
来自 DALL-E 2 的超级英雄
我们将为这个演示构建一个工具,以创建虚构超级英雄的描述。最终,工具将接收超级英雄的年龄、性别和能力,并自动生成超级英雄的描述。
在以下示例中,经过模型微调后,我们只需输入’40, woman, Healing ->',模型将自动生成一个描述。
这就是一切的关键!! 😃
创建一个合成的数据集用于微调
在某些情况下,你可能有一个数据集想要用于微调。但由于我没有现成的数据集,我们来看看如何直接从 GPT-3 创建一个合成数据集,用于超级英雄的描述。以下代码将给我一个包含 prompts 和相应 completions 的 CSV 文件。
import os
import openai
import pandas as pd
openai.api_key = os.getenv("OPENAI_API_KEY")
l_age = ['18', '20', '30', '40', '50', '60', '90']
l_gender = ['man', 'woman']
l_power = ['invisibility', 'read in the thoughts', 'turning lead into gold', 'immortality', 'telepathy', 'teleport', 'flight']
f_prompt = "Imagine a complete and detailed description of a {age}-year-old {gender} fictional character who has the superpower of {power}. Write out the entire description in a maximum of 100 words in great detail:"
f_sub_prompt = "{age}, {gender}, {power}"
df = pd.DataFrame()
for age in l_age:
for gender in l_gender:
for power in l_power:
for i in range(3): ## 3 times each
prompt = f_prompt.format(age=age, gender=gender, power=power)
sub_prompt = f_sub_prompt.format(age=age, gender=gender, power=power)
print(sub_prompt)
response = openai.Completion.create(
model="text-davinci-003",
prompt=prompt,
temperature=1,
max_tokens=500,
top_p=1,
frequency_penalty=0,
presence_penalty=0
)
finish_reason = response['choices'][0]['finish_reason']
response_txt = response['choices'][0]['text']
new_row = {
'age':age,
'gender':gender,
'power':power,
'prompt':prompt,
'sub_prompt':sub_prompt,
'response_txt':response_txt,
'finish_reason':finish_reason}
new_row = pd.DataFrame([new_row])
df = pd.concat([df, new_row], axis=0, ignore_index=True)
df.to_csv("out_openai_completion.csv")
让我们深入了解这段代码是如何工作的 🧐。
变量f_prompt包含以下句子,其中*{age}、{gender}和{power}*是缺失的。
想象一下一个详细描述的{age}岁{gender}虚构角色,他拥有{power}的超级能力。用最多 100 个字写出整个详细描述:
在代码的前三个for循环中,我们遍历不同的{age}、{gender}和{power}值。在每一步循环中,我们用不同的值替换 3 个缺失的变量。然后,我们使用openai.Completion.create函数请求 GPT 生成对我们提示的响应。
这个函数的最重要参数是
-
model:用于生成响应的模型。OpenAI 提供了四种标准 GPT-3 模型(
ada
、babbage
、curie
或davinci
),它们在规模和使用价格上有所不同。这里使用的是davinci——最大的模型。 -
prompt:我们希望 GPT-3 完成的提示。
-
temperature:温度是一个介于 0 和 1 之间的数字,控制输出的随机性。我们将温度设置为最大值,以便模型在生成响应时尽可能具有创造力。
-
max_tokens:定义响应的最大长度。
在这个脚本的最后,我们有一个存储在文件out_openai_completion.csv中的 Pandas 表格。这个表格中我们感兴趣的两个主要列是sub_prompt和response_txt。
-
sub_prompt例如会是‘18, man, invisibility’。它包含了三个用逗号分隔的被替换的值。
-
response_txt包含 GPT 模型的输出。
微调 GPT 模型
以下代码检索之前创建的文件out_openai_completion.csv,并使用openai对 GPT-3 模型进行微调。
import pandas as pd
import openai
import subprocess
df = pd.read_csv("out_openai_completion.csv")
prepared_data = df.loc[:,['sub_prompt','response_txt']]
prepared_data.rename(columns={'sub_prompt':'prompt', 'response_txt':'completion'}, inplace=True)
prepared_data.to_csv('prepared_data.csv',index=False)
## prepared_data.csv --> prepared_data_prepared.json
subprocess.run('openai tools fine_tunes.prepare_data --file prepared_data.csv --quiet'.split())
## Start fine-tuning
subprocess.run('openai api fine_tunes.create --training_file prepared_data_prepared.jsonl --model davinci --suffix "SuperHero"'.split())
让我们花时间来理解这段代码 🤓!
首先,文件out_openai_completion.csv的内容被加载到数据框df中。为了提醒一下,在我们的任务中,如果用户输入’40, female, healing’,我们希望得到一个关于 40 岁女性角色拥有治愈能力的描述。为了进行微调,需要向 GPT 提供用户可能输入的示例以及相应的期望响应。在数据框df中,sub_prompt和response_txt列包含了输入示例和对应的期望响应。在上面的代码中,我们首先提取这两列,然后将其分别重命名为prompt和completion。结果数据框被存储在一个新的文件prepared_data.csv中,仅包含这两列。
这个文件prepared_data.csv的内容如下:
prompt,completion
"18, man, invisibility","He looks to be in his late teens, with dark eyes and unruly black hair which reach his shoulders. His build is athletic and strong, but not muscular. He often wears clothing that makes him blend in with his surroundings, in order to stay as inconspicuous as possible.He has the ability of invisibility, which he can use to become an invisible observer or to make himself or objects around him disappear from view. He has honed his skills to make himself undetectable, able to even make sounds vanish, allowing him to go unnoticed. His invisibility power is his greatest asset, and he uses it to protect those he cares about."
"18, man, invisibility","18 year-old Jackson is a powerful young man with the incredible power of invisibility. He stands at 6'2 and has a strong athletic frame with wavy brown hair, startling blue eyes and a warm and gentle smile. His strength and agility make him especially good at using his ability. He can become invisible in an instant, moving through walls and other obstacles without being detected. What's more, he can move objects with his mind and use his power to protect those he loves. His power is a blessing and a curse, as it can be abused to do harm. Jackson is a brave and noble person who uses his power to do good and make the world a better place."
"18, man, invisibility","Brandon is an 18-year-old of average build, standing roughly 5'10 with an inquisitive look. He has naturally wavy chestnut brown hair and bright blue eyes. His demeanor is usually quite serious, but he also has an easy and gentle smile. He has a natural gift of invisibility, which he uses to protect himself and his family from harm. He's an inquisitive and creative young man who uses his gift to explore the world, observe people, and uncover the truth. His consistent practice of mindfulness helps him remain unseen, even when his emotions are visible. His intelligence and persistent passion for truth drives him to help those in need."
"18, man, read in the thoughts","This 18-year-old man has a muscular stature and long, ash blonde hair. His bright blue eyes are full of knowledge and power, hinting at the strange ability he has - he can read minds. Now and then, strange sparks of electricity streak from his eyes when he concentrates hard enough. He is often calm and collected, but when provoked has the ability to blend his learning of your thoughts with his immense strength. His wit, intelligence and empathy are weapons that are often underestimated - yet, very lethal."
"18, man, read in the thoughts","He's an 18-year-old man with short, cropped dark hair and bright green eyes. His usual outfit consists of an olive green hoodie, blue jeans and purple trainers. He's relaxed, confident and always reads the atmosphere well. One of his great skills is read in the thoughts of others. By doing so, he is able to understand people more easily and relate to their concerns out of the blue. He always uses his superpower with the utmost dignity and respect, never using it to manipulate. He uses it to seek the truth, identify people's emotions and gain insight into their perspectives. He's the sort of person that can make anyone feel comfortable and is never afraid of a challenge. His superpower is merely an extension of his already charismatic disposition."
(...)
Python 的*subprocess.run()*函数将命令作为子进程运行。它通常用于执行外部程序,就像在终端中运行一样。
我们使用 subprocess.run() 执行‘openai tools fine_tunes.prepare_data’。这个函数接受输入文件 prepared_data.csv,检查数据是否正确,并生成一个名为 prepared_data_prepared.jsonl 的 JSONL 文件。JSONL 文件是一种将每个 JSON 对象存储在单独一行的格式。JSONL 文件包含一系列 JSON 对象,每个对象之间用换行符分隔。
注意,我们已添加选项“- - quiet”以自动接受‘openai tools fine_tunes.prepare_data’所做的所有建议。例如,它建议在所有提示的末尾添加‘–>’,并在每个响应的末尾添加一个END标记。
这个 JSONL 文件的前几行看起来像这样:
{"prompt":"18, man, invisibility ->","completion":" \n\nHe looks to be in his late teens, with dark eyes and unruly black hair which reach his shoulders. His build is athletic and strong, but not muscular. He often wears clothing that makes him blend in with his surroundings, in order to stay as inconspicuous as possible.\n\nHe has the ability of invisibility, which he can use to become an invisible observer or to make himself or objects around him disappear from view. He has honed his skills to make himself undetectable, able to even make sounds vanish, allowing him to go unnoticed. His invisibility power is his greatest asset, and he uses it to protect those he cares about. END"}
{"prompt":"18, man, invisibility ->","completion":" \n\n18 year-old Jackson is a powerful young man with the incredible power of invisibility. He stands at 6'2 and has a strong athletic frame with wavy brown hair, startling blue eyes and a warm and gentle smile. His strength and agility make him especially good at using his ability. He can become invisible in an instant, moving through walls and other obstacles without being detected. What's more, he can move objects with his mind and use his power to protect those he loves. His power is a blessing and a curse, as it can be abused to do harm. Jackson is a brave and noble person who uses his power to do good and make the world a better place. END"}
(...)
GPT-3 模型的微调实际上是在第二个 subprocess.run() 中实现的,其中执行了 openai api fine_tunes.create。在这个函数中,我们首先提供刚刚创建的 JSONL 文件的名称。然后你需要选择要微调的模型。OpenAI 提供了四个主要模型,具有不同的性能水平,适用于各种任务。Davinci 是最强大的模型,而 Ada 是最快的。Davinci 也是最昂贵的模型 😨。
由于我模型的目的是创建超级英雄的描述,因此我们给我的新模型添加了后缀“超级英雄”。
就这样😉 几分钟后,你将拥有一个可以使用的微调模型🌟。
现在是时候测试你的新模型了。
使用模型进行补全有不同的方法。主要通过 OpenAI 提供的 Playground 或者通过像 Python 这样的编程语言。
最简单的方法可能是使用 playground。
-
点击‘模型’并搜索带有后缀“超级英雄”的模型。
-
在‘停止序列’中添加标记‘END’。
现在是时候要求我们的模型进行新的预测了。我们将要求描述一个 18 岁的男性角色,他真的有一个不必要的能力😉 我们将要求描述一个拥有‘吃很多’能力的角色……看看会发生什么……😆
不错 😅
你想用 Python 来做吗?很简单!点击屏幕右上角的‘查看代码’。
在我们的案例中,在‘查看代码’中我们有:
import os
import openai
openai.api_key = os.getenv("OPENAI_API_KEY")
response = openai.Completion.create(
model="davinci:ft-klhm:superhero-2023-02-01-14-56-48",
prompt="18, Man, can eat a lot ->\n",
temperature=0.7,
max_tokens=256,
top_p=1,
frequency_penalty=0,
presence_penalty=0,
stop=["END"]
)
你只需要复制并粘贴即可👍。
总结
在这个博客中,我们已经了解了如何生成合成数据以优化我们的模型以及如何进行微调。我们使用了创建超级英雄的用例,但相同的方法也可以用于你可能拥有的任何用例。最重要的是要有足够的质量示例,包含提示和期望的响应。
感谢阅读!
如果你希望保持最新的出版物并增加这个博客的可见性,请考虑关注我。
释放 MLflow 的力量
原文:
towardsdatascience.com/unleashing-the-power-of-mlflow-36c17a693033
机器学习生命周期管理的快速导览
·发表于Towards Data Science ·6 分钟阅读·2023 年 5 月 30 日
–
图片由Stephen Dawson提供,来源于Unsplash
你是否曾经发现自己花了几个小时训练模型、调整超参数和选择特征,却意识到你已经有一组不错的参数,但却忘记跟踪或保存模型?我知道我有过,可能比我愿意承认的还要多。在你打开一个电子表格,开始记录模型使用的 alpha 值或邻居数量之前,我想向你介绍 MLflow。
MLflow 是一个多功能的开源平台,旨在管理端到端的机器学习生命周期,由 Databricks 开发。它为机器学习从业者、数据科学家和开发人员提供了一系列好处,使实验、重现性和 ML 模型的部署变得更加简便。所以,让我们来探索一下它能为你做些什么吧!
MLflow 的主要组件
在我们深入探讨使用 MLflow 的细节之前,了解 MLflow 是什么以及它在当今机器学习领域中为何是一个关键工具是至关重要的。
MLflow 帮助管理机器学习生命周期,包括实验、重现性和部署。它兼容任何(Python)机器学习库。它为最常见的库提供了现成的接口,使其具有高度的灵活性,满足你所有的开发需求。
MLflow 由四个主要组件组成:
-
MLflow Tracking:主要 API,记录和组织机器学习实验。它记录参数、指标和工件(如模型、笔记本和脚本),使你能够跟踪实验运行和结果。它配备了一个 UI,你可以通过 localhost 访问来查看、可视化和管理你的实验。
-
MLflow Projects:一种用于重复性和共享的代码打包格式。它定义了 ML 代码的标准结构,使你更容易理解、重用和与他人协作。
-
MLflow 模型:一种用于打包多种 ML 框架的标准格式,以及一个用于存储和共享模型的库。它简化了跨平台的模型部署。
-
MLflow 模型注册表:一个集中管理的模型库,具备模型谱系、版本控制、阶段转换和注释功能。它在需要与团队成员比较和组合模型的协作环境中特别有用。
MLflow 的优势在于其简化和优化 ML 生命周期的能力,使你能够轻松跟踪和比较实验、重现代码、管理模型,并相对容易地部署解决方案。
深入了解 MLflow Tracking:管理你的 ML 实验
让我们通过MLflow Tracking开始这次实践环节。API 和 UI 用于记录和管理你所有的 ML 实验。
你可以在简单的脚本中使用它,也可以将其扩展到大规模训练环境中。我将通过一个简单的代码片段,使用 Scikit-learn 中的 ElasticNet 回归来演示基本用法。
import mlflow
import mlflow.sklearn
from sklearn.linear_model import ElasticNet
from sklearn.metrics import mean_squared_error
import numpy as np
# Initiate a new MLflow run
with mlflow.start_run():
# Train and fit the model
model = ElasticNet(alpha=0.5, l1_ratio=0.5)
model.fit(X_train, y_train)
# Make predictions and calculate the RMSE
predictions = model.predict(X_test)
rmse = np.sqrt(mean_squared_error(y_test, predictions))
# Log parameters
mlflow.log_param("alpha", 0.5)
mlflow.log_param("l1_ratio", 0.5)
# Log metric
mlflow.log_metric("rmse", rmse)
# Log model (artifact)
mlflow.sklearn.log_model(model, "model")
运行此脚本后,你可以在 MLflow Tracking UI 中查看记录的运行详细信息。用户友好的界面允许你根据不同参数和指标过滤和排序运行,从而进行比较分析。
MLflow UI 示例
MLflow 模型注册表:简化你的模型管理
在开始使用 MLflow Tracking 后,你可能也想开始组织你的模型。这就是 MLflow 模型注册表的用武之地,它是一个与 MLflow Tracking 紧密集成的集中模型库。它是个人和团队审查、共享和协作 ML 模型的绝佳工具。
模型注册表简化了模型从实验到生产的过渡。它通过允许模型谱系跟踪、模型版本控制、阶段转换和模型注释来实现这一点。
你可以这样使用模型注册表:
- 从 MLflow Tracking 记录模型:
mlflow.sklearn.log_model(lr_model, "model")
2. 在注册表中注册已记录的模型:
result = mlflow.register_model(
"runs:/d16076a3ec534311817565e6527539c0/model",
"ElasticNetWineModel"
)
模型是通过其运行 ID 注册的(d16076a3ec534311817565e6527539c0
在示例中)。
3. 列出所有注册的模型:
你可以通过列出所有注册的模型来检查你的模型是否已成功注册:
mlflow.search_runs()
4. 从注册表中加载模型:
你可以从注册表中加载模型进行预测或评分:
model_uri = "models:/ElasticNetWineModel/1"
model = mlflow.pyfunc.load_model(model_uri)
5. 在阶段之间转换模型版本:
模型注册表允许模型阶段的转换。你可以将模型从“无”状态转换为“暂存”、“生产”或“归档”阶段:
client = mlflow.tracking.MlflowClient()
client.transition_model_version_stage(
name="ElasticNetWineModel",
version=1,
stage="Production",
)
在这个例子中,我们将模型的版本 1 转移到“生产”阶段以供后续使用。
了解 MLflow 模型
MLflow 如何保存你的模型?
MLflow 模型提供了一种标准格式来打包你的机器学习模型,这些模型可以在各种下游工具中使用。例如,通过 REST API 进行实时服务,Spark 集群上的批量推断等。
MLflow 模型使用一种简单的约定来打包模型,每个模型都保存为一个目录,其中包含必要的文件和一个描述符文件,列出模型可以使用的几种“风味”。
一种风味定义了模型可以运行的特定格式或库。对于最重要的库有不同的风味。例如,TensorFlow 模型可以加载为 TensorFlow SavedModel 格式或作为一个 Python 函数应用于输入数据。
如果你有来自自定义库的模型或未内置的模型,你仍然可以在通用 Python 函数风味中使用它:
import mlflow.pyfunc
class ModelWrapper(mlflow.pyfunc.PythonModel):
def load_context(self, context):
# Initialization logic
pass
def predict(self, context, model_input):
# Prediction logic
pass
# Saving the model
mlflow.pyfunc.save_model(path="model_path", python_model=ModelWrapper())
当你准备好提供你的模型(例如用于 API)时,你可以使用mlflow models serve
命令:
mlflow models serve -m models:/ElasticNetWineModel/1 -p 1234
此命令将在本地主机的端口1234
上提供指定版本的模型(此处为1
)。
探索 MLflow 项目:简化你的代码打包
如上所述,MLflow Projects 是一种以可重用和可复现的方式打包你的代码的格式。它主要帮助你在团队之间共享项目,并使其在不同平台上运行。每个项目仅仅是一个包含你的代码和名为MLproject
的描述符文件的目录或库。
MLproject
文件定义了项目的结构,包括依赖项、入口点和代码的参数。你的MLproject
文件可能如下所示:
name: My_Project
conda_env: conda.yaml
entry_points:
main:
parameters:
alpha: float # no default value
l1_ratio: {type: float, default: 0.5} # default value
command: "python main.py {alpha} {l1_ratio}" # run script with params
在这个例子中,main.py
是项目的入口点。alpha
和l1_ratio
是这个脚本的参数。conda.yaml
文件列出了项目的 Python 依赖项。
你可以通过mlflow run
命令运行项目的入口点:
mlflow run . -P alpha=0.42
该命令获取必要的依赖项,使用给定的和/或默认参数运行你的代码,并将结果记录到跟踪服务器。
总结
这就结束了我们对 MLflow 的快速浏览。
我们现在已经涵盖了 MLflow 的四个组件:MLflow Tracking、MLflow Projects、MLflow Models 和 MLflow Model Registry。这些工具结合在一起,为你提供了一个全面的平台,用于管理机器学习生命周期,促进更好的协作、代码重用和实验跟踪。
无论你是在单独工作还是在多个团队中工作,处理简单模型还是复杂的 ML 管道,我希望你发现 MLflow 是一个值得整合到你的工作流中的工具。
记住,我这里只是简单地触及了 MLflow 功能的表面。要深入了解高级功能、示例和使用方法,请查看官方 MLflow 文档或可能的后续帖子。
和往常一样,我也希望你学到了一些对你有帮助的东西。享受在你的机器学习项目中探索和利用 MLflow 的力量吧!
-Merlin
[## 使用我的推荐链接加入 Medium - Merlin Schäfer
阅读 Merlin Schäfer(以及 Medium 上成千上万其他作家的)每一个故事。你的会员费直接支持…
ms101196.medium.com](https://ms101196.medium.com/membership?source=post_page-----36c17a693033--------------------------------)
来源:
[1] https://mlflow.org/docs/latest/index.html
发掘数据科学家的提示工程潜力
原文:
towardsdatascience.com/unleashing-the-power-of-prompt-engineering-for-data-scientists-16b6d1f2bf85
如何以及为什么在数据工作中编写有效的提示
·发布于数据科学之路 ·18 分钟阅读·2023 年 6 月 7 日
–
图片由Gerd Altmann提供,来源于Pixabay
多亏了 GPT 模型,提示工程正成为数据科学中的一个重要领域。最初,我们看到世界各地许多好奇的人在测试 ChatGPT 以尝试欺骗它。然后,虽然这种趋势(终于!)结束了,但使用它来自动化无聊的任务或帮助处理一般任务的人数稳步增加。
开发人员和数据科学家从使用像 ChatGPT 这样的提示系统中受益良多。因此,在本文中,我们将概述提示工程及其如何为数据科学家编写高效提示。
我知道你从 ChatGPT 中受益良多,对吧?!但事实是,有时候我们作为数据科学家并不能完全从中获得我们想要的结果。所以,让我们看看如何通过一些简单的预防措施来提高我们的提示技能。
这篇文章的内容如下:
**Table of Contents:**
The importance of prompt engineering today
How prompt engineering can affect Data Scientists
Examples of effective prompts for Data Scientists
今天提示工程的重要性
过去 150 年的关键词可能是“自动化”。事实上,世界已经从手工制作的产品演变为生产线。虽然手工艺仍然(高度)有价值,但“批量生产”已成为与“自动化”相伴的词汇。
工作的机械化和自动化在不断增加,这种趋势渗透到了各个领域,不仅限于直接涉及生产商品的领域,例如制造业或农业。
如果我们以软件为例,第一个应该看到的就是自动化。当我大约三年前学习 Python 时,一位导师在审查我的第一个项目时告诉我:“Federico,开发软件意味着自动化事物!” 如果你在问,是的:我的第一个项目是一团糟(就像我们做的所有第一次的事情一样!)。
无论如何,真相是:人类进化的明确目标是:自动化事物。这可以与自动化无聊的事情或“艰苦的工作”相关。无论如何,关键在于朝着自动化的方向前进。
在这种情况下,提示工程只是最新的帮助我们自动化事物的工具。在“代码视角”下,这意味着自动化可自动化的事物:软件开发本质上是自动化,使用提示工程意味着进一步推动自动化。
事实上,即使在软件开发中也存在无聊的任务,即使,例如,我们已经创建了可以导入的类(但需要稍作修改)。
想一想:作为一个数据科学家,你每周开发多少个原型?你有多少时间来开发它们?
你创建一个原型,然后呢?项目的规格发生变化,客户改变主意,你的老板不满意……好吧,你自己说吧。
那么,为什么我们要花费大量精力在低价值但耗时的任务上,而不是自动化它们呢?在我看来,这里是提示工程的核心概念。
那么,让我们看看提示工程如何影响数据科学家,然后看看我们如何创建有用且高效的提示。
提示工程如何影响数据科学家
每项新技术都有其优点和缺点,提示工程也不例外。首先,让我们看看优点,然后是缺点。
数据科学家的提示工程:优点
-
更快的学习。如果你是数据科学领域(以及软件开发领域)初学者,你会发现像 ChatGPT 这样的工具非常有益,因为它就像是 24/7 随时可用的高级开发人员。不过,仍然不应将其视为万能的,主要因为它仍然会出现一些错误。如果你有兴趣,我写了一篇关于如何在 ChatGPT 时代有效开始编程的专门文章,点击这里。
-
更快的原型。在我看来,数据科学家工作中最重要的部分之一是原型开发。事实上,我们经常需要根据数据(通常是可用数据很少,且很脏的数据)快速给出回答。因此,原型可以给出客户需要的回答感,使我们有时间:a) 请求/获取更多数据,b) 请求/获取更多规格,c) 清洗数据,d) 进行必要的研究。
-
更快的调试和错误管理。我们必须诚实地说:在软件开发中,调试和错误管理更多的是一种诅咒而不是一种乐趣。对于机器学习/深度学习算法的软件开发也是如此。ChatGPT 是一个很好的调试和错误管理工具:通过正确的提示,它可以在几秒钟内发现错误和漏洞,节省大量时间和精力。只要提醒一下:由于 ChatGPT(以及类似工具)在云端工作,并且可能使用我们的提示来训练它们的算法,所以要记住不要写包含敏感信息的代码,因为在数据泄露的情况下可能会给你带来麻烦。
-
更快的研究。数据科学家的工作中一个重要部分就是进行研究。我们绝对需要大量的研究来解决问题,例如:特定库及其使用信息、与我们面临的问题领域知识相关的信息等等。好的提示通常对获取所需的信息非常有用。唯一需要记住的是,我们始终需要通过在互联网或书籍上深入验证输出的正确性。特别是对于代码,阅读文档始终很重要:否则,风险就是复制和粘贴代码而实际上不理解它。
数据科学家的提示工程:缺点
-
可能会失去工作。是的,我们必须说:AI 工具可能会使我们失去工作。这似乎是一种矛盾:市场对数据专业人员的需求在这些月里不断增加,但像 ChatGPT 这样的工具可能会取代我们。好吧,实话实说:这个可能性在目前还很遥远,因为 AI 工具需要专家的监督,就像我们在优点中讨论的那样。当然,你可以请求一些代码和数据分析,但如果你不知道如何使用这些代码,你怎么办?所以,是的:提示工程可能会导致一些数据专业人员失去工作,但这是一件几年后才会发生的事情,而不是几个月。
-
可能会忘记如何编程。这是一个实际问题。如果我们过于依赖提示工程而不是自己编写代码,我们可能会忘记如何编程。你知道的:编程是一个需要练习的过程,需要每天的练习。当然,就像骑自行车一样:你永远不会忘记如何骑。但你知道:过于依赖提示而不是编写代码会导致你的技能萎缩,因为你变得过于舒适。因此,使用像 ChatGPT 这样的工具,但不要仅仅依赖于这些工具:尽可能多地努力编写代码。因为我知道你喜欢编程,所以不要过多地依赖机器。
-
可能无法学习新知识。从事 IT 工作,尤其是在数据领域,令人兴奋的一点是新话题和技术几乎每天都在诞生。这也是我转行从事 IT 的原因之一:因为我喜欢不断学习新事物,我希望这被视为一件好事(是的,有些领域/公司认为自我提升不是好事)。但如果你只是依赖提示得到的答案并复制粘贴代码(或信息),你不会学到新知识。在代码方面,至少你需要请求解释,然后阅读文档。接着,如果可能的话,在不同的场景中尝试代码,以便你能熟悉你收到的代码。这也是我们在请求一般信息时需要采取的相同方法:始终通过阅读其他来源来深入了解(就像它应该总是那样:但让我们记住这一点!)。
-
可能出现“冗长的代码”。 代码能正常工作并不是保留它的好理由,你知道的。代码质量对未来的读者(可能是你)也很重要。有时,由于各种因素(通常是提示不正确),像 ChatGPT 这样的工具可能会给我们提供带有不必要部分的“冗长代码”。这就需要专业人士的帮助:不要盲目复制和粘贴。如果你复制和粘贴了它,在验证它有效后,尽量将其改进到最佳状态。
数据科学家的有效提示示例
现在,考虑到这些优缺点,让我们看看我们可以在 ChatGPT 中创建的一些有效提示,以改善和加速我们作为数据科学家的工作。
示例 1:原型
假设我们正在研究一个二分类示例。我们想用学习曲线比较三种 ML 模型,但我们实际上不记得使用的代码。此外,我们的代码库有点乱,我们不想浪费太多时间。
为了加快我们在这种情况下的工作,我们可以向 ChatGPT 提出如下提示:“用 Python 创建一个二分类问题,使用合成数据。使用标准化器对数据进行标准化,然后将数据拆分为训练集和测试集。然后,使用随机森林、KNN 和 SVM 分类器拟合训练集。最后,绘制 ROC 曲线以比较模型。” 这些是结果:
当然!我可以帮助你使用 Python 创建一个二分类问题,使用标准化器对数据进行标准化。然后,我们可以将数据拆分为训练集和测试集。最后,我们将使用三种分类器来拟合训练集:随机森林、K-最近邻(KNN)和支持向量机(SVM),并绘制 ROC 曲线以比较模型。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.metrics import roc_curve, auc
# Generate synthetic data for binary classification
X, y = make_classification(n_samples=1000, n_features=20, n_informative=10,
n_redundant=10, random_state=42)
# Standardize the data using StandardScaler
scaler = StandardScaler()
X = scaler.fit_transform(X)
# Split the data into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,
random_state=42)
# Fit the train set with classifiers
rf_classifier = RandomForestClassifier(random_state=42)
knn_classifier = KNeighborsClassifier()
svm_classifier = SVC(probability=True, random_state=42)
rf_classifier.fit(X_train, y_train)
knn_classifier.fit(X_train, y_train)
svm_classifier.fit(X_train, y_train)
# Generate predictions for the test set
rf_probs = rf_classifier.predict_proba(X_test)[:, 1]
knn_probs = knn_classifier.predict_proba(X_test)[:, 1]
svm_probs = svm_classifier.predict_proba(X_test)[:, 1]
# Calculate false positive rate, true positive rate,
# and area under the curve for ROC curve
rf_fpr, rf_tpr, _ = roc_curve(y_test, rf_probs)
rf_auc = auc(rf_fpr, rf_tpr)
knn_fpr, knn_tpr, _ = roc_curve(y_test, knn_probs)
knn_auc = auc(knn_fpr, knn_tpr)
svm_fpr, svm_tpr, _ = roc_curve(y_test, svm_probs)
svm_auc = auc(svm_fpr, svm_tpr)
# Plot the ROC curve
plt.figure()
plt.plot(rf_fpr, rf_tpr, label=f'Random Forest (AUC = {rf_auc:.2f})')
plt.plot(knn_fpr, knn_tpr, label=f'KNN (AUC = {knn_auc:.2f})')
plt.plot(svm_fpr, svm_tpr, label=f'SVM (AUC = {svm_auc:.2f})')
plt.plot([0, 1], [0, 1], 'k--')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic')
plt.legend(loc='lower right')
plt.show()
提供的代码使用 scikit-learn 的
make_classification
函数生成一个合成的二分类问题。然后应用StandardScaler
对数据进行标准化,并将数据拆分为训练集和测试集。然后在训练集上训练三个分类器:随机森林、K-近邻(KNN)和支持向量机(SVM)。为了评估,分类器使用
predict_proba
方法为测试集生成概率。下一步涉及绘制接收者操作特征(ROC)曲线,以比较分类器的性能。使用
roc_curve
函数计算假正例率(FPR)和真实正例率(TPR),使用auc
函数计算曲线下面积(AUC)。最终,ROC 曲线使用 matplotlib 绘制,每个分类器的 AUC 显示在图例中。该曲线表示每个分类器在真实正例率和假正例率之间的权衡,允许对它们的性能进行可视化比较。
通过检查 ROC 曲线并比较 AUC 值,你可以评估随机森林、KNN 和 SVM 分类器在给定二分类问题上的相对性能。
现在,如果我们将这段代码复制并粘贴到 Jupyter Notebook 中,我们会得到:
上述代码生成的 ROC 曲线。图片由 Federico Trotta 提供。
我们得到了想要的结果,所以让我们分析一下这个提示:
-
它很具体。我指定了:a) 语言(Python),b) 问题类型(二分类),c) 需要遵循的过程(数据创建、数据标准化、数据拆分),和 d) 使用的 ML 模型。
-
它包含了所需的一切。没有额外的词语可能会造成混淆。提示直截了当。
-
它很简短。仅有 3 行。较长的提示可能导致错误的输出或使模型添加我们可能不需要的信息。如果你必须插入“长提示”,我的建议是将“大任务”分解成较小的任务,并输入不同的短提示,这些短提示可以是连续的。明确来说,上述提示可以分解成连续的对话,如:a) “使用 Python 创建一个二分类数据集,对数据进行标准化,并将其拆分为训练集和测试集”,b) “用随机森林、SVM 和 KNN 分类器拟合训练集,然后绘制 ROC 曲线”
示例 n°2:研究
假设我们知道多项式回归通常会过拟合,但我们想要证明,因为我们从未见过。提示可能是这样的:“我知道多项式回归模型通常会过拟合:你能展示一些东西吗,通过在 Python 中创建一些数据?”
结果如下:
当然!我可以生成一些随机数据并演示多项式回归如何导致过拟合。这是一个 Python 示例:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
# Generate random data
np.random.seed(0)
X = np.linspace(-3, 3, 100)
y = 0.5 * X**2 + X + np.random.normal(0, 1, 100)
# Reshape the input data
X = X.reshape(-1, 1)
# Plot the original data
plt.scatter(X, y, color='b', label='Data')
# Fit polynomial regression models of different degrees
degrees = [1, 4, 15]
colors = ['r', 'g', 'm']
for degree, color in zip(degrees, colors):
# Create polynomial features
polynomial_features = PolynomialFeatures(degree=degree)
X_poly = polynomial_features.fit_transform(X)
# Fit the polynomial regression model
model = LinearRegression()
model.fit(X_poly, y)
# Predict the values
y_pred = model.predict(X_poly)
# Plot the fitted curve
plt.plot(X, y_pred, color=color, linewidth=2, label=f'Degree {degree}')
# Add labels and title to the plot
plt.xlabel('X')
plt.ylabel('y')
plt.title('Polynomial Regression - Overfitting Example')
plt.legend(loc='upper left')
# Display the plot
plt.show()
这段代码生成具有二次关系的随机数据点,并向 y 值添加一些噪声。然后,它将不同度数(在这个例子中为 1、4 和 15)的多项式回归模型拟合到数据上,并绘制出结果曲线。
你会观察到,随着多项式的度数增加,模型开始拟合数据中的噪声而不是潜在的趋势。这是过拟合的一个例子。线性模型(度数为 1)过于简单,无法捕捉潜在的二次关系,而高次多项式(度数为 15)试图精确拟合每一个数据点,从而导致曲线剧烈震荡。
需要注意的是,我提供的这个例子只是多项式回归中过拟合的简单说明。在实践中,你通常会使用更现实和复杂的数据集来评估和解决过拟合问题。
现在,让我们看看在 Jupyter Notebook 中得到什么:
下面的图显示了用上述代码创建的数据拟合差异。图片来自 Federico Trotta。
因此,这个图表显示了,给定一些二次分布的数据:
-
线性模型无法抓住路径。
-
4 度和 15 度的多项式“过于贴合路径”。
现在告诉我:这是否意味着这两个多项式过拟合了?
停下来思考一下。
是的和不是,但主要是“不是”。实际上,正如我们所知,过拟合发生在:
-
我们定义了评估数据的指标。
-
指标在训练集和测试集上都进行了计算,我们发现它们之间有很大的数值差异。
因此,这个提示不够令人满意,我们需要提出更多问题。例如,我们可以问:“好,那就用相同的数据和相同的多项式。然后,归一化数据,将数据集拆分为训练集和测试集,并计算两个集合的 R²”。
我们得到:
当然!这是一个更新版的代码,它归一化数据,将其分为训练集和测试集,并计算这两个集的 R²分数:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score
# Generate random data
np.random.seed(0)
X = np.linspace(-3, 3, 100)
y = 0.5 * X**2 + X + np.random.normal(0, 1, 100)
# Reshape the input data
X = X.reshape(-1, 1)
# Normalize the data
X_normalized = (X - np.mean(X)) / np.std(X)
y_normalized = (y - np.mean(y)) / np.std(y)
# Split the data into train and test sets
X_train, X_test, y_train, y_test = train_test_split(
X_normalized, y_normalized, test_size=0.2, random_state=0
)
# Plot the original data
plt.scatter(X_normalized, y_normalized, color='b', label='Data')
# Fit polynomial regression models of different degrees
degrees = [1, 4, 15]
colors = ['r', 'g', 'm']
for degree, color in zip(degrees, colors):
# Create polynomial features
polynomial_features = PolynomialFeatures(degree=degree)
X_poly_train = polynomial_features.fit_transform(X_train)
X_poly_test = polynomial_features.transform(X_test)
# Fit the polynomial regression model
model = LinearRegression()
model.fit(X_poly_train, y_train)
# Predict the values for train and test sets
y_pred_train = model.predict(X_poly_train)
y_pred_test = model.predict(X_poly_test)
# Calculate R² scores
r2_train = r2_score(y_train, y_pred_train)
r2_test = r2_score(y_test, y_pred_test)
# Plot the fitted curve
plt.plot(X_normalized, model.predict(polynomial_features.transform(X_normalized)), color=color, linewidth=2,
label=f'Degree {degree} (Train R²={r2_train:.2f}, Test R²={r2_test:.2f})')
# Add labels and title to the plot
plt.xlabel('X')
plt.ylabel('y')
plt.title('Polynomial Regression - Overfitting Example (Normalized)')
plt.legend(loc='upper left')
# Display the plot
plt.show()
在这个更新的代码中,数据首先通过减去均值并除以标准差进行归一化。然后,使用 scikit-learn 的
train_test_split()
将数据拆分为训练集(80%的数据)和测试集(20%的数据)。多项式回归模型使用归一化的训练数据进行拟合,然后使用来自 scikit-learn 的
r2_score()
计算训练集和测试集的 R²分数。这些 R²分数随后显示在每条拟合曲线的图例中。通过归一化数据并计算训练集和测试集上的 R²分数,你可以评估多项式回归模型的表现,并判断它们是否过拟合或对未见数据的泛化能力如何。
在这种情况下,我们得到:
下面的图显示了用上述代码创建的数据拟合差异。图片来自 Federico Trotta。
那么,我们得到了什么?嗯……我们看不到明显的过拟合迹象,因为测试集上的 4 度多项式 R² 稍微增加,而 15 度多项式的 R² 稍微下降。
这里的错误在于我们继续了开始的聊天。让我们记住,LLM 是神经网络,它们有过拟合的倾向。因此,有时候,如果你继续提示,你会得到相同的答案(或非常相似的东西),即使它返回了一些“对不起,我犯了个错误”的信息。
此外,我不需要任何图表。因此,在这种情况下,我们最好创建另一个聊天。现在让我们尝试更具体一些:“在 Python 中创建一个 2 度数据集。对数据进行标准化,并将其分割为训练集和测试集。用:线性回归、4 度多项式和 15 度多项式模型来拟合数据。打印每个模型在训练集和测试集上的 R 平方值。”
然后我们得到:
要在 Python 中创建一个 2 度的数据集,对其进行标准化,并将其分割为训练集和测试集,你可以使用
numpy
和sklearn
库。以下是你如何实现这一点的示例:
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
# Create the dataset
X = np.random.rand(100, 1) # Independent variable
y = 3 * X**2 + np.random.randn(100, 1) # Dependent variable (quadratic relationship)
# Normalize the data
X_normalized = (X - np.mean(X)) / np.std(X)
y_normalized = (y - np.mean(y)) / np.std(y)
# Split the data into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X_normalized, y_normalized, test_size=0.2, random_state=42)
# Fit the linear regression model
linear_regression = LinearRegression()
linear_regression.fit(X_train, y_train)
# Fit the 4-degree polynomial regression model
poly_features = PolynomialFeatures(degree=4)
X_poly_train = poly_features.fit_transform(X_train)
X_poly_test = poly_features.transform(X_test)
poly_regression = LinearRegression()
poly_regression.fit(X_poly_train, y_train)
# Fit the 15-degree polynomial regression model
poly_features = PolynomialFeatures(degree=15)
X_poly_train = poly_features.fit_transform(X_train)
X_poly_test = poly_features.transform(X_test)
poly_regression_15 = LinearRegression()
poly_regression_15.fit(X_poly_train, y_train)
# Calculate R-squared for train and test sets
linear_train_r2 = linear_regression.score(X_train, y_train)
linear_test_r2 = linear_regression.score(X_test, y_test)
poly_train_r2 = poly_regression.score(X_poly_train, y_train)
poly_test_r2 = poly_regression.score(X_poly_test, y_test)
poly_15_train_r2 = poly_regression_15.score(X_poly_train, y_train)
poly_15_test_r2 = poly_regression_15.score(X_poly_test, y_test)
# Print the results
print("Linear Regression R-squared (Train):", linear_train_r2)
print("Linear Regression R-squared (Test):", linear_test_r2)
print("4-Degree Polynomial Regression R-squared (Train):", poly_train_r2)
print("4-Degree Polynomial Regression R-squared (Test):", poly_test_r2)
print("15-Degree Polynomial Regression R-squared (Train):", poly_15_train_r2)
print("15-Degree Polynomial Regression R-squared (Test):", poly_15_test_r2)
在这个例子中,我们生成了一个具有独立变量
X
和依赖变量y
之间的二次关系的随机数据集。然后,使用 z-score 标准化对数据进行归一化。接下来,使用来自sklearn.model_selection
模块的train_test_split
将数据集分割为训练集(80%)和测试集(20%)。然后,我们拟合了三种不同的模型:线性回归、4 度多项式回归和 15 度多项式回归。对于多项式回归,我们使用来自
sklearn.preprocessing
的PolynomialFeatures
类来创建高达所需度数的多项式特征。使用归一化的训练数据来拟合模型。最后,我们使用各自模型的
score
方法计算每个模型在训练集和测试集上的 R 平方值。R 平方值表示依赖变量中方差的比例,这部分方差可以通过
好的,我们得到了我们想要的结果,并附带了一些解释。现在,如果我们尝试一下,我们得到:
ValueError: X has 16 features, but LinearRegression
is expecting 5 features as input.
哎呀!正如我们所说,出现一些错误是很正常的。所以,在这种情况下,我们可以简单地继续聊天,例如:“我得到:ValueError: X 有 16 个特征,但 LinearRegression 期望输入 5 个特征。”
现在它返回:
对于错误表示歉意。似乎多项式特征转换出现了问题。要解决这个问题,我们需要确保在归一化的
X
数据上应用多项式转换,而不是在原始X
数据上。以下是修正后的代码:
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
# Create the dataset
X = np.random.rand(100, 1) # Independent variable
y = 3 * X**2 + np.random.randn(100, 1) # Dependent variable (quadratic relationship)
# Normalize the data
X_normalized = (X - np.mean(X)) / np.std(X)
y_normalized = (y - np.mean(y)) / np.std(y)
# Split the data into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X_normalized, y_normalized, test_size=0.2, random_state=42)
# Fit the linear regression model
linear_regression = LinearRegression()
linear_regression.fit(X_train, y_train)
# Fit the 4-degree polynomial regression model
poly_features = PolynomialFeatures(degree=4)
X_poly_train = poly_features.fit_transform(X_train)
X_poly_test = poly_features.transform(X_test)
poly_regression = LinearRegression()
poly_regression.fit(X_poly_train, y_train)
# Fit the 15-degree polynomial regression model
poly_features_15 = PolynomialFeatures(degree=15)
X_poly_train_15 = poly_features_15.fit_transform(X_train)
X_poly_test_15 = poly_features_15.transform(X_test)
poly_regression_15 = LinearRegression()
poly_regression_15.fit(X_poly_train_15, y_train)
# Calculate R-squared for train and test sets
linear_train_r2 = linear_regression.score(X_train, y_train)
linear_test_r2 = linear_regression.score(X_test, y_test)
poly_train_r2 = poly_regression.score(X_poly_train, y_train)
poly_test_r2 = poly_regression.score(X_poly_test, y_test)
poly_15_train_r2 = poly_regression_15.score(X_poly_train_15, y_train)
poly_15_test_r2 = poly_regression_15.score(X_poly_test_15, y_test)
# Print the results
print("Linear Regression R-squared (Train):", linear_train_r2)
print("Linear Regression R-squared (Test):", linear_test_r2)
print("4-Degree Polynomial Regression R-squared (Train):", poly_train_r2)
print("4-Degree Polynomial Regression R-squared (Test):", poly_test_r2)
print("15-Degree Polynomial Regression R-squared (Train):", poly_15_train_r2)
print("15-Degree Polynomial Regression R-squared (Test):", poly_15_test_r2)
现在代码应该可以正确工作,对归一化数据应用多项式转换,并相应地拟合模型。
现在,如果我们测试一下,我们得到:
Linear Regression R-squared (Train): 0.5287297254229243
Linear Regression R-squared (Test): 0.4627146369316367
4-Degree Polynomial Regression R-squared (Train): 0.5895312356444804
4-Degree Polynomial Regression R-squared (Test): 0.4601670183046247
15-Degree Polynomial Regression R-squared (Train): 0.6256705123172766
15-Degree Polynomial Regression R-squared (Test): 0.26972190687190367
在这里,我们可以看到 4 度模型的轻微过拟合和 15 度模型的明显过拟合。实际上,随着多项式的次数增加,它过拟合数据的概率也会增加。我们来考虑一下:我们有一个 2 度的数据分布,而我们正试图用 15 度的多项式来拟合它。因此,15 度的多项式很容易捕捉到数据中的每一个小的变化,从而导致过拟合。
结论
在这篇文章中,我们已经看到提示工程的重要性,它如何影响数据科学家,以及一些针对数据科学家的提示示例。
记住,提示应该简洁明了,我们始终需要验证答案。但正如我们所展示的,收益是巨大的。
免费 Python 电子书:
刚开始学习 Python 数据科学却感到困难?订阅我的通讯并获取免费的电子书:这将为您提供正确的学习路径,以便通过实践经验学习 Python 数据科学。
享受这个故事吗?通过我的推荐链接成为 Medium 会员,费用为 5$/月:我将获得一小笔佣金,而您无需支付额外费用。
## 通过我的推荐链接加入 Medium - Federico Trotta