TowardsDataScience 2023 博客中文翻译(二百零三)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

偏差和方差之间是否总有权衡?

原文:towardsdatascience.com/is-there-always-a-tradeoff-between-bias-and-variance-5ca44398a552

不拘一格的揭秘者

偏差-方差权衡,第一部分,共 3 部分

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Cassie Kozyrkov

·发表于 Towards Data Science ·阅读时间 5 分钟·2023 年 2 月 15 日

你应该阅读这篇文章吗?如果你理解下一部分中的所有单词,那么不。如果你不在乎理解它们,那么也不。如果你想了解粗体部分的解释,那么是。

偏差-方差权衡

“偏差-方差权衡” 是一个在 ML/AI 语境中常听到的流行词汇。如果你是 统计学家,你可能认为它是对这个公式的总结:

MSE = 偏差² + 方差

不是的。

嗯,它有点相关,但这个短语实际上指的是如何选择模型的复杂度甜点实际方法。当你在调优正则化 超参数时,它最有用。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者插图。

注意: 如果你从未听说过 MSE,你可能需要对一些术语有所了解。当你遇到新术语并想要更详细的解释时,你可以跟随链接到我的其他文章中,我会介绍我使用的词汇。

理解基础知识

均方误差(MSE)是模型的损失函数中最流行(且最基础)的选择,通常是你首先被教到的。你可能会上很多统计课程,才会有人告诉你可以选择最小化其他损失函数。(但说实话:抛物线很容易优化。记得 d/dx 吗? 2x。这种便利足以让大多数人忠于 MSE。)

一旦你了解了 MSE,通常在几分钟内就会有人提到偏差和方差公式:

MSE = 偏差² + 方差

我也做到了,像一个普通的数据科学狂人一样,把证明留给了感兴趣的读者

让我们做些补救——如果你希望我在推导过程中做些讽刺评论,可以稍微绕道这里。如果你选择跳过数学内容,那么你只能接受我的手势,并相信我的话。

只有积极的氛围

想让我直言不讳地告诉你关键点吗?请注意,这个公式由两个不能为负的项组成。

你在调整你的预测机器学习/人工智能模型时试图优化的数量(MSE)可以分解为始终为正的项,这些项只涉及偏差和方差。

MSE = 偏差² + 方差 = (偏差)² + (标准差)²

更直接一点?好吧,当然可以。

更好的模型具有更低的 MSE。E 代表误差,误差越少越好,所以最佳模型的 MSE 为零:它不会犯错。这也意味着它没有偏差没有方差

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

劳拉·克劳拍摄于Unsplash

与其追求完美模型,不如看从好到更好的过程。如果你真的能够改善你的模型(在 MSE 方面),就不需要在偏差和方差之间做权衡。 如果你变得更好的弓箭手,你就变得更好的弓箭手了。没有权衡。(你可能需要更多的练习——数据!——才能做到这一点。)

正如托尔斯泰所说,所有完美的模型都是相似的,但每个不满意的模型都可以以自己的方式不满意。

所有完美的模型都是相似的

正如托尔斯泰所说,所有完美的模型都是相似的,但每个不满意的模型(在给定的 MSE 下)都可以以自己的方式不满意。你可以得到两个同样糟糕但不同的模型,MSE 相同:一个模型可能有很好的(低)偏差但高方差,而另一个模型可能有很好的(低)方差但高偏差,但两者的 MSE(整体得分)却相同。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果我们用 MSE 来衡量弓箭手的表现,那就意味着减少弓箭手的标准差和减少偏差是等值的。我们说我们对这两者无所谓。(等等,如果对它们并不无所谓呢?那么 MSE 可能不是你最好的选择。如果你不喜欢 MSE 的表现评分方式?没关系。自己制定一个损失函数。)

现在我们已经铺好了桌子,前往第二部分,我们将深入探讨问题的核心:是否存在实际的权衡?(是的!但可能不是你想象的那样。)以及过拟合与此有何关系?(提示:一切都有关!)

感谢阅读!怎么样,来一门课程吧?

如果你在这里感到有趣,且正在寻找一个既能吸引人工智能初学者又能打动专家的领导力课程,这里有一门我为你准备的小课程

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

课程链接:bit.ly/funaicourse

想提升你的决策能力而不是仅仅增强你的人工智能技能?你可以通过这个链接访问我的免费课程:

## 你生活的方向盘——决策智能视频教程 | LinkedIn Learning…

决策能力是你可以学习的最宝贵的技能。你的人生归结为两件事:你生活的质量和…

决策课程

P.S. 你是否尝试过在 Medium 上多次点击鼓掌按钮看看会发生什么? ❤️

喜欢作者吗?与 Cassie Kozyrkov 联系

让我们成为朋友吧!你可以在TwitterYouTubeSubstackLinkedIn上找到我。对让我在你的活动中发言感兴趣?请使用这个表单与我联系。

## 加入 Medium

阅读 Cassie Kozyrkov 的每一个故事(以及 Medium 上成千上万的其他作家的故事)。你的会员费直接支持…

kozyrkov.medium.com

这是否是解决 P-hacking 的方案?

原文:towardsdatascience.com/is-this-the-solution-to-p-hacking-a04e6ed2b6a7

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

E 是新的 P 吗?图像由作者使用 Dall·E 创建。

e-值,一种优于 p-值的替代方案

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Hennie de Harder

·发表于 Towards Data Science ·阅读时间 11 分钟·2023 年 11 月 16 日

在科学研究中,数据操控和结果窥探一直是存在的问题。研究人员经常为了发表文章而追求显著的 p-值,这可能导致提前停止数据收集或操控数据的诱惑。这种做法被称为 p-hacking,是 我之前的帖子****的重点。如果研究人员决定故意更改数据值或伪造完整的数据集,我们也无能为力。然而,对于某些 p-hacking 的情况,可能存在解决方案!

在这篇文章中,我们深入探讨了安全测试的话题。安全测试相比于旧的(当前的)假设测试方法具有一些强大的优势。例如,这种测试方法允许将多项研究的结果进行结合。另一个优势是你可以在任何时间选择性地停止实验。为了说明安全测试,我们将使用由提出该理论的研究人员开发的 R 包 safestats。首先,我们将介绍 e-值并解释它们能解决的问题。由于其优势,e-值已经被 Netflix 和 Amazon 等公司使用。

我不会深入探讨理论的证明;相反,这篇文章采用了更实际的方法,展示了如何在自己的测试中使用 e-值。对于证明和安全测试的详细解释,原始论文是一个很好的资源。

e-值简介

在假设检验中,你可以在这里刷新相关知识,你评估是否保留原假设或接受备择假设。通常情况下,使用 p 值。如果 p 值小于预定的显著性水平 alpha,你就接受备择假设。

e 值的功能与 p 值不同,但有关联。e 值的最简单解释如下:假设你在对抗原假设进行赌博。你投资 1 美元,回报值等于 E 美元。如果 e 值 E 在 0 到 1 之间,你输了,原假设成立。另一方面,如果 e 值高于 1,你赢了!原假设输掉了比赛。 一个适中的 E 为 1.1 表示对原假设的证据有限,而一个巨大的 E,例如 1000,则表示压倒性的证据。

需要了解的一些 e 值要点:

  • e 值可以取任何正值,你可以在假设检验中将 e 值作为p 值的替代方案使用。

  • 一个 e 值 E 可以通过关系 1/E = p 解释为传统的 p 值 p。注意:它不会给你和标准 p 值一样的结果,但你可以像解释 p 值那样解释它。

  • 在传统的测试中,你有 alpha,也就是显著性水平。通常这个值为 0.05。e 值的工作方式略有不同,你可以将它们看作是对原假设的证据。e 值越高,对原假设的证据越多。

  • 在使用 e 值的情况下,你可以在任何时候 (!) 停止数据收集并在测试期间得出结论。这被称为e 过程,使用 e 过程可以确保在可选停止下的有效性,并允许对统计证据进行连续更新。

有趣的事实:e 值并不像你想象的那么‘新’。关于它的第一篇论文是 1976 年写的。当时这些值并没有被称为 e 值。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

研究人员在对抗…一个假设?!图像由作者使用 Dall·E 3 创建。

我为什么要关心 e 值?

这是一个有效的问题。传统的 p 值有什么问题?是否需要用 e 值来替代它们?如果当前的测试方式没有问题,为什么要学习新的东西?

其实,p 值确实存在一些问题。传统 p 值受到大量批评。一些统计学家(超过 800 人)想要完全放弃 p 值

让我们用一个经典的例子来说明原因。

想象一下你是一个制药公司的初级研究员。你需要测试公司开发的药物的有效性。你寻找测试候选人,其中一半人接受药物,而另一半则服用安慰剂。你确定了得出结论所需的测试候选人数量。

实验开始了,你在寻找新参与者时有些困难。你面临时间压力,你的老板经常问:“你有结果了吗?我们想把这个产品推向市场!”由于压力,你决定偷看结果并计算 p 值,尽管你还没有达到最小测试候选人数!看着 p 值,现在有两个选择:

  • p 值是 不显著的。这意味着你不能证明药物有效。显然,你不会分享这些结果!你多等一会儿,希望 p 值会变得显著…

  • 是的!你发现了一个 显著 的 p 值!但你的下一步是什么?你停止实验吗?你继续直到达到正确的测试候选人数量吗?你将结果分享给你的老板吗?

一旦你查看了数据,可能会很诱人地想要更频繁地查看。你计算 p 值,有时它显著,有时则不显著… 这样做可能看起来无害,但实际上你是在破坏整个过程。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

显著还是不显著?图像由作者使用 Dall·E 3 创建。

为什么在实验正式结束之前只查看数据和相应的 p 值几次是错误的?一个简单而直观的原因是,因为如果你对其他结果做了什么(例如,如果你发现显著的 p 值你就停止实验),你就是在干扰整个过程。

从理论角度来看:你违反了第一类错误保证。第一类错误保证是指你有多大的把握不会错误地拒绝一个真实的零假设(= 发现显著结果)。这就像是对你在没有狼的情况下哭狼的频率的承诺。这种情况发生的风险是 ≤ alpha。但这仅适用于一个实验!如果你更频繁地查看数据,你就不能再相信这个值了:第一类错误的风险会变得更高。

这涉及到 多重比较问题。如果你做了多个独立的测试来证明相同的假设,你应该纠正 alpha 值,以保持第一类错误的风险较低。解决这个问题有不同的方法,比如 Bonferroni 校正Tukey 的范围检验Scheffé 方法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

多个独立测试的家庭错误率。对于一个测试,它等于 alpha。注意,对于 10 个测试,错误率增加到了 40%,而对于 60 个测试,它达到了 95%。图像来源:作者。

总结:p 值可以使用,但研究人员可能会在样本量达到之前查看数据。这是错误的,会增加 I 型错误的风险。为了保证实验的质量和稳健性,e 值是更好的选择。由于 e 值的特性,你不需要怀疑这些实验(或者至少少一些,研究人员总是可以选择伪造数据 😢)。

使用 e 值的好处

如前所述,我们可以像使用 p 值一样使用 e 值。一个主要的区别在于,大的 e 值与低的 p 值是可以比较的。回想一下 1/E = p。如果你想像使用 p 值一样使用 e 值,并且使用显著性水平 0.05,则当 e 值高于 20(1/0.05)时,你可以拒绝原假设。

当然,e 值还有更多的使用案例和好处!如果有多个实验测试同一假设,我们可以将这些测试的 e 值相乘,得到一个新的 e 值,用于测试。这在 p 值中是无法做到的。但对于 e 值,这种方法是有效的。

你也可以在实验期间查看数据和结果。如果你想停止测试,因为结果看起来不够有希望,那也是可以的。另一个可能性是,如果结果看起来有希望,可以继续进行测试。

我们还可以使用 e 值创建任何时候有效的置信区间。这是什么意思?这意味着置信区间对任何样本大小(整个实验期间)都有效。它们会比常规置信区间稍宽,但好处是你可以在任何时候相信它们。

使用 safestats 包

在帖子最后一部分,我们将更加实际。让我们计算自己的 e 值。为此,我们使用 R 包 safestats。要安装和加载它,请运行:

install.packages("safestats")
library(safestats)

我们将解决的案例是经典的:我们将比较网站的不同版本。如果一个人购买了东西,我们记录成功(1);如果一个人什么也没有购买,我们记录失败(0)。我们将旧版网站展示给 50% 的访客(A 组),将新版网站展示给另外 50%(B 组)。在这个用例中,我们将关注可能发生的不同情况。可能出现原假设为真的情况(网站之间没有差异或旧版网站更好),有时也可能是备择假设为真的情况(新版网站更好)。

创建安全测试的第一步是设定设计目标。在这个变量中,你需要为 alphabetadelta 指定值:

designObj <- designSafeTwoProportions(
  na = 1,
  nb = 1,        # na and nb are of equal size so 1:1
  alpha = 0.05,  # significance level
  beta = 0.2,    # risk of type II error
  delta = 0.05,  # minimal effect we like to detect
)

designObj

在许多情况下,delta 设置为较高的数字。但对于比较具有大量流量的网站的不同版本,将其设置为较小的值是合理的,因为容易获得许多观察值。

输出如下:

 Safe Test of Two Proportions Design

 na±2se, nb±2se, nBlocksPlan±2se = 1±0, 1±0, 4355±180.1204
              minimal difference = 0.05
                     alternative = twoSided
         alternative restriction = none
                 power: 1 - beta = 0.8
 parameter: Beta hyperparameters = standard, REGRET optimal
                           alpha = 0.05
decision rule: e-value > 1/alpha = 20

Timestamp: 2023-11-15 10:58:37 CET

Note: Optimality of hyperparameters only verified for equal group sizes (na = nb = 1)

你可以识别我们选择的值,但包还计算了 nBlocksPlan 参数。这是我们需要观察的数据点(块)的数量,它基于 delta 和 beta 参数。还需检查基于 alpha 值的决策规则。如果 e-value 大于 20(1 除以 0.05),我们拒绝原假设。

测试案例:备择假设为真

现在,让我们生成一些虚假数据:

set.seed(10)
successProbA = 0.05  # success probability for A 5%
successProbB = 0.08  # success probability for B 8%
nTotal = designObj[["nPlan"]]["nBlocksPlan"]  # use the nBlocksPlan value as sample size
ya <- rbinom(n = nTotal, size = 1, prob = successProbA)
yb <- rbinom(n = nTotal, size = 1, prob = successProbB)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

分布 A 和 B 的成功概率分别为 0.05 和 0.08。图像由作者提供。

现在是进行我们第一次安全测试的时候了!

safe.prop.test(ya=ya, yb=yb, designObj=designObj)

输出为:

 Safe Test of Two Proportions

data:  ya and yb. nObsA = 4355, nObsB = 4355

test: Beta hyperparameters = standard, REGRET optimal
e-value = 77658 > 1/alpha = 20 : TRUE
alternative hypothesis: true difference between proportions in group a and b is not equal to 0 

design: the test was designed with alpha = 0.05
for experiments with na = 1, nb = 1, nBlocksPlan = 4355
to guarantee a power = 0.8 (beta = 0.2)
for minimal relevant difference = 0.05 (twoSided) 

e-value 等于 77658,这意味着我们可以拒绝原假设。足够的证据来拒绝它!

可能会出现的问题是:“我们是否可以早点停止?”这就是 e-values 的一个好处。在达到计划样本量之前,允许查看数据,因此你可以随时决定停止或继续实验。我们可以绘制 e-values,例如每 50 个新样本的累计 e-values。前 40 个 e-values 图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

开始时没有反对原假设的证据,对应于低 e-values。但随着样本的增加,证据开始显现:e-values 超过了 20 的决策边界。图像由作者提供。

完整图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以确定:原假设应该被拒绝。除了最后一个 e-value。图像由作者提供。

测试案例:原假设为真

如果我们更改虚假数据,使得两个版本的成功概率相等(A 和 B 版本的成功概率均为 0.05),我们应该检测不到显著的 e- 或 p-值。版本 A 和 B 的分布看起来类似,原假设为真。这在 e-values 图中体现了出来:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

无效果。图像由作者提供。

但如果我们将其与 p-values 进行比较呢?我们会多频繁地拒绝原假设,尽管实际上我们不应该?让我们测试一下。我们将实验重复 1000 次,看看在多少情况下我们拒绝了原假设,无论是对于 p-values 还是 e-values。

R 代码:

pValuesRejected <- c()
eValuesRejected <- c()
alpha <- 0.05
ealpha <- 1/alpha

# repeat the experiment 1000 times, calculate the p-value and the e-value
for (i in seq(1, 1000, by = 1)) {
  # create data, use the same value of nTotal as before (4355)
  set.seed(i)
  ya <- rbinom(n = nTotal, size = 1, prob = 0.05)
  yb <- rbinom(n = nTotal, size = 1, prob = 0.05)

  # calculate the p-value, H0 rejected if it's smaller than alpha
  testresultp <- prop.test(c(sum(ya), sum(yb)), n=c(nTotal, nTotal))
  if (testresultp$p.value < alpha){
    pValuesRejected <- c(pValuesRejected, 1)
  } else{
    pValuesRejected <- c(pValuesRejected, 0)
  }

  # calculate the e-value, H0 rejected if it's bigger than 1/alpha
  testresulte <- safe.prop.test(ya=ya, yb=yb, designObj=designObj)
  if (testresulte[["eValue"]] > ealpha){
    eValuesRejected <- c(eValuesRejected, 1)
  } else{
    eValuesRejected <- c(eValuesRejected, 0)
  }
}

如果我们将 pValuesRejectedeValuesRejected 相加,输出为:

> sum(pValuesRejected)
[1] 48
> sum(eValuesRejected)
[1] 0

p-value 在 48 个案例中显著(约 5%,这是我们期望的 alpha 为 0.05 时的结果)。另一方面,e-value 做得很好:它从未拒绝原假设。如果你之前还不确定是否使用 e-values,现在希望你相信了!

如果你对其他示例感兴趣,我可以推荐safestats 包中的小插曲

结论

E-values 提供了一种比传统 p-values 更具吸引力的替代方案,具有多个优点。它们提供了在任何阶段继续或停止实验的灵活性。此外,它们的可组合性是一个优势,任何时候审查实验结果的自由也是一个大优点。p-values 和 e-values 的比较表明,e-values 更可靠;p-values 在没有显著差异时更容易错误地识别出显著差异。safestats R 包是实施这些稳健测试的有用工具。

我相信 e-values 的优点,并期待一个支持其实现的 Python 包的开发!😄

相关

## 隐秘的科学:数据挖掘曝光

深入探讨 P-hacking 的动机和后果

towardsdatascience.com ## 如何有效地比较机器学习解决方案?

提高将模型投入生产的机会

towardsdatascience.com ## 简化你的机器学习项目

为什么在复杂模型上花费大量时间和精力是个坏主意,应该怎么做

towardsdatascience.com

旅游是否恢复到 COVID 危机前的水平?

原文:towardsdatascience.com/is-tourism-back-to-its-pre-covid-crisis-level-27ed32604fa6

数据分析

进行全程分析,回答一个社会经济问题并附上漂亮的图表

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Marie Lefevre

·发布于 Towards Data Science ·5 分钟阅读·2023 年 2 月 18 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 Alexandra Luniel 提供,来自 Unsplash

你在圣诞节期间休假吗?你计划在三月底休息几天吗?或者你可能更喜欢在一年中的其他时间比如夏天利用机会环游世界?

无论你是什么类型的假期旅行者, chances are that you will take some holidays this year. 现在 COVID-19 危机的最糟糕阶段(祈祷不要再恶化)已经过去,你有没有想过旅游是否已经恢复到危机前的水平?

这是我最近问自己的一个问题,我想给出答案。我发现,对专业领域以外的话题进行分析是一种很好的练习,可以保持你“在数据分析的游戏中”。无论你是初级还是更高级的数据分析师,我认为使用新的数据集进行训练总是很有帮助的。

让我带你一起走过从最初构想到最终输出的全程分析旅程吧!

步骤 1:找出一个相关的问题进行处理

在这篇文章中,我想借此机会回顾一下今年的旅行,并调查旅游是否已经恢复到危机前的水平。这可以从全球范围来看,也可以缩小到特定的地理区域。由于我住在欧洲,我特别感兴趣的是分析 COVID 危机对欧洲旅游的影响。

我思考的另一个方面是:我如何衡量“旅游”?这一概念包含了各种领域,如交通(飞机、火车、汽车等)、旅游景点(博物馆、活动等)、住宿(酒店、露营地、家庭住宿等)。为了更精确地解决我在这里要分析的问题,让我们专注于住宿

步骤 2:获取数据以进行分析

在这种情况下,我没有公司数据或预定义的数据集可用,所以让我们浏览互联网以寻找与我的主题相关的开放数据。

Eurostat 提供了可以在线可视化和以多种格式下载的开放数据集。Eurostat 的数据可以免费使用,只要提到 Eurostat 是数据来源即可。在这里,我将使用这个关于在旅游住宿设施中度过的夜晚的数据集:TOUR_OCC_NIM,其来源是 Eurostat。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

原始数据集(由 Eurostat 提供,查看版权声明

步骤 3:绘制最终输出

为了回答我的初始问题,我想将欧洲的全球旅游发展与各国的旅游发展进行比较。通过这样做,我应该能够看到旅游是否、何时以及在哪个国家恢复到危机前的水平。

为此,我需要两个图表:一个显示每月在旅游住宿设施中度过的总夜晚数,第二个显示按国家划分的相同指标。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

期望输出(使用 Excalidraw 绘制)

步骤 4:转换数据

手头有原始数据集(步骤 2)并且心中有目标输出(步骤 3),我现在已经准备好进行分析。你注意到实际上进入“数据分析模式”是在步骤 4,而不是更早吗?这是因为数据分析更多的是关于你为何需要进行分析,而不是实际进行分析

要绘制第一个图表,我必须按月对值(在旅游住宿设施中度过的夜晚数量)进行分组。对于第二个图表,我必须按月和国家进行分组,如下所示:

SELECT 
  TIME_PERIOD AS month,
  geo AS country,
  SUM(OBS_VALUE) AS nb_nights_spent,
FROM my_dataset.raw_data
GROUP BY 1,2
ORDER BY 1,2

步骤 5:可视化数据

代码片段的输出是数据表,而不是(还不是)图表。要将这些表格输出转换为漂亮的图表,让我们使用数据可视化工具。在这里,我使用了第 4 步的 Google BigQuery 和第 5 步的 Looker Studio(以前称为 DataStudio)的组合。

由于我们之前绘制了目标输出,我们已经知道最终图表应该是什么样的。这节省了很多时间,因为我只需配置工具,将正确的维度放在正确的位置。这将给我这些图表:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

输出图表(在 Looker Studio 中构建)

步骤 6:提取见解

那么现在该怎么办?制作图表很棒,但没有人脑来解释这些输出结果,这些图表就相当无用。让我们回到最初的问题:根据住宿数据,欧洲的旅游是否已经恢复到危机前的水平? 我们希望尽可能清晰地回答这个问题。

如果我们看全球的发展趋势,欧洲旅游似乎正回到恢复到危机前水平的轨道上。虽然 2022 年的夏季季节未能完全达到 2019 年的水平(2022 年 7 月至 8 月比 2019 年 7 月至 8 月减少了 15%),但与 2020 年和 2021 年相比,趋势表现出积极的变化。下一年 2023 年夏季结束时进行相同的分析将是很有趣的。

如果我们仔细查看每个国家的发展情况,这个普遍的评论并不总是适用。例如,2022 年西班牙的值与 2019 年非常接近(仅 7 月至 8 月减少了 4%),而其他国家 2022 年远低于 2019 年(捷克为-26%)。

结论

在解读结果时,另一个重要的因素是偏差。首先,数据收集方式可能存在偏差:当我们比较不同国家时,每个国家可能会采用不同的方法来计算在旅游住宿机构过夜的数量。

其次,仅基于一种指标分析就得出旅游几乎恢复到危机前水平的结论是片面的。要准确评估欧洲旅游的状态,应当分析多个指标,比较这些分析结果,并整合从中获得的经验教训。

简而言之:进行分析时要遵循这 6 个步骤,并注意偏差。

如果你想获取这篇文章的视觉总结,你可以在这里免费下载 <<

您的 LLM 应用程序准备好公开了吗?

原文:towardsdatascience.com/is-your-llm-application-ready-for-the-public-55fdcce99888?source=collection_archive---------17-----------------------#2023-06-20

将基于 LLM 的应用程序投入生产时的关键关注点

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Itai Bar Sinai

·

关注 发表在 Towards Data Science ·6 分钟阅读·2023 年 6 月 20 日

大型语言模型(LLMs)正成为现代自然语言处理(NLP)应用的基本组成部分,并且在许多方面取代了各种更为专业的工具,例如命名实体识别模型、问答模型和文本分类器。因此,很难想象一个不以某种方式使用 LLM 的 NLP 产品。虽然 LLM 带来了诸如个性化和创意对话生成等诸多好处,但在将这些模型集成到服务最终用户的软件产品中时,了解其陷阱以及如何应对这些问题是很重要的。事实证明,监控能够很好地解决这些挑战,并且是任何与 LLM 合作的企业工具箱中的一个重要部分。

数据、隐私和提示注入

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 TheDigitalArtist 提供,来源于 Pixabay

数据与隐私

隐私和数据使用是现代消费者的主要关注点之一,尤其是在剑桥分析等著名数据共享丑闻的影响下,消费者越来越不愿使用那些可能危及个人隐私的服务和产品。虽然 LLM(大型语言模型)为用户提供了极高的个性化,但了解它们所带来的风险也很重要。与所有机器学习模型一样,LLM 容易受到针对性的攻击,这些攻击旨在揭示训练数据,由于其生成性质,LLM 尤其容易受到风险,甚至在进行自由形式生成时可能会意外泄露数据。例如,在2020 年博客文章中,谷歌大脑的研究科学家尼古拉斯·卡尔尼讨论了如何通过提示 LLM(如 GPT)来泄露个人身份信息,如姓名、地址和电子邮件,这些信息包含在模型的训练数据中。这表明,针对客户数据对 LLM 进行微调的企业可能会引发类似的隐私风险。同样,来自微软研究人员的论文也证实了这些说法,并建议了一些具体的缓解策略,这些策略利用差分隐私技术来训练 LLM,同时减少数据泄露的担忧。不幸的是,由于许多企业使用的 LLM API 不允许他们控制微调过程,因此无法利用这些技术。这些公司的解决方案在于插入一个监控步骤,在将结果返回给最终用户之前验证和限制模型的输出。通过这种方式,企业可以在隐私违规实际发生之前识别并标记潜在的训练数据泄露实例。例如,监控工具可以应用诸如命名实体识别和正则表达式过滤等技术来识别模型生成的姓名、地址、电子邮件及其他敏感信息,防止这些信息落入不良分子之手。这对于在隐私受限领域如医疗或金融工作的组织尤其重要,这些领域涉及 HIPAA 和 FTC/FDIC 等严格法规。即使是仅仅在国际上运营的企业也面临违反复杂的地域特定法规,如欧盟的 GDPR。

提示注入

提示注入(Prompt injection)指的是设计 LLM 提示的过程,通常是恶意的,通过某种方式“欺骗”或混淆系统,从而产生有害的输出。例如,最近的一篇文章展示了精心设计的提示注入攻击如何使得 OpenAI 的 GPT-4 模型被颠覆,从而提供事实错误的信息甚至推广阴谋论。可以想象到更多邪恶的场景,例如用户提示 LLM 提供如何制造炸弹的建议、如何最佳自杀的细节,或生成可用于感染其他计算机的代码。提示注入攻击的脆弱性是不幸的副作用,由于 LLM 的训练方式,很难在前端做任何事情来防止所有可能的提示注入攻击。即使是最强大和最新的 LLM,如 OpenAI 的 ChatGPT——专门为安全而调整——也证明了对提示注入的脆弱性。

由于提示注入可能表现出的方式千差万别,因此几乎不可能防范所有可能性。因此,监控 LLM 生成的输出至关重要,因为它提供了一种识别和标记虚假信息以及明显有害生成的机制。监控可以使用简单的 NLP 启发式方法或额外的 ML 分类器来标记包含有害内容的模型响应,并在返回给用户之前拦截它们。类似地,对提示本身的监控可以在提示被传递给模型之前捕捉到一些有害的提示。

幻觉

幻觉(hallucination)指的是 LLM 偶尔“编造”那些实际上不符合现实的输出的倾向。提示注入和幻觉可以表现为同一枚硬币的两面,尽管提示注入中的虚假生成是用户的故意行为,而幻觉则是 LLM 训练目标的无意副作用。由于 LLM 被训练为在每个时间步骤中预测序列中下一个最可能的词,因此它们能够生成高度逼真的文本。因此,幻觉是最可能的情况并不总是真的简单结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源:Matheus Bertelli via Pexels

最新一代的大型语言模型(LLM),例如 GPT-3 和 GPT-4,采用了一种叫做人类反馈强化学习(RLHF)的算法进行优化,以匹配人类对什么构成良好响应的主观判断。虽然这使得 LLM 能够达到更高的对话流畅度,但有时也会导致它们在回应时表现得过于自信。例如,询问 ChatGPT 一个问题时,它可能会自信地给出一个初看似乎合理的回答,但经过进一步检查,结果却是客观上错误的。赋予 LLM 提供不确定性量化的能力仍然是一个活跃的研究问题,并且不太可能很快得到解决。因此,LLM 基于的产品的开发者应该考虑监控和分析输出,以试图检测幻觉,并提供比 LLM 模型开箱即用的更细致的回应。这在 LLM 的输出可能指导某些下游过程的情况下尤为重要。例如,如果一个 LLM 聊天机器人在帮助用户提供产品推荐并协助在零售商网站上下订单时,应该实施监控程序,以确保模型不会建议购买在该零售商网站上实际上并未销售的产品。

不受控制的成本

由于 LLM 通过 API 正变得越来越商品化,企业在将这些模型集成到其产品中时,制定防止成本无限增加的计划非常重要。如果没有保护措施,用户生成数千个 API 调用并发出数千个令牌的提示(例如,用户将一份极长的文档粘贴到输入中,并要求 LLM 分析它)是很容易的。由于 LLM API 通常根据调用次数和令牌计数(包括提示和模型的回应)进行计量,因此成本迅速失控并不困难。因此,企业在制定定价结构时需要小心,以抵消这些成本。此外,企业应该有监控程序,以了解使用激增如何影响成本,并通过施加使用上限或采取其他补救措施来缓解这些激增。

结论

每个将大型语言模型(LLMs)应用于其产品的企业,都应确保将监控功能整合到其系统中,以避免并解决 LLMs 的众多陷阱。此外,使用的监控解决方案应专门针对 LLMs 应用,允许用户识别潜在的隐私侵犯,防止和/或修复提示注入,标记幻觉,并诊断不断上升的成本。最佳的监控解决方案将解决所有这些问题,并为企业提供一个框架,以确保其基于 LLM 的应用准备好对公众发布。通过预约演示来查看 Mona 的全面监控能力,确保你的 LLM 应用经过充分优化并按预期运行。

依赖 GridSearchCV 的最佳模型是一个错误

原文:towardsdatascience.com/its-a-mistake-to-trust-the-best-model-of-a-gridsearchcv-536a73e835ad

通过四个例子解释了“最佳模型”实际上并不是最佳模型的情况

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Tomer Gabay

·发布于Towards Data Science ·6 分钟阅读·2023 年 1 月 4 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源:Choong Deng XiangUnsplash

Scikit-learn 的GridSearchCV是一个常用的工具,用于优化机器学习模型的超参数。不幸的是,并不是每个人都能彻底分析它的输出,很多人只是使用GridSearchCV的最佳估计器。这意味着在很多情况下,你可能没有使用到真正的最佳估计器。让我们首先确定如何运行网格搜索,并检测它选择哪个估计器作为最佳。

一个非常基础的GridSearchCV可能看起来是这样的:

import pandas as pd
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.tree import DecisionTreeRegressor

df = pd.read_csv('data.csv')
X_train, X_test, y_train, y_test = train_test_split(df.drop('target'),
                                                    df['target'],
                                                    random_state=42)

model = DecisionTreeRegressor()
gridsearch = GridSearchCV(model, 
                          param_grid={'max_depth': [None, 2, 50]},
                          scoring='mean_absolute_error')

gridsearch.fit(X_train, y_train)
gridsearch.train(X_train, y_train)

# get the results. This code outputs the tables we'll see in the article
results = pd.DataFrame(gridsearch.cv_results_)

# what many data scientists do, while they shouldn't without analyzing first
Y_pred = gridsearch.best_estimator_.predict(X_test) 

在上面代码的底部,我提醒不要仅仅使用best_estimator_。因此,了解scikit-learn如何选择GridSearchCV的最佳估计器非常重要。我们只需要查看由cv_results_生成的results数据框中的一列,以确定为什么一个模型被选择为最佳:mean_test_score

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(作者提供的表格)

mean_test_score显示了该模型在测试集上的平均得分;例如,默认参数cv=5表示mean_test_score是模型在 5 个测试集上的测试得分的平均值。如果将cv改为 4,你会得到如下的训练-测试数据划分:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用 cv=4 的交叉验证(图由作者提供)

默认情况下,GridSearchCV 选择 mean_test_score 最高的模型,并将其分配一个 rank_test_score 为 1. 这也意味着当你通过 gs.best_estimator_ 访问 GridSearchCV 的最佳估算器时,你将使用 rank_test_score 为 1 的模型。然而,有许多情况下 rank_test_score 为 1 的模型并不一定是最佳模型。让我们通过四个示例来说明‘最佳’模型并不是最优模型,并看看我们如何确定实际的最佳模型。

示例 #1:测试分数的标准差

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果我们查看上面的表格,我们可以看到‘最佳’模型的 mean_test_score 为 -100,std_test_score 为 21,这些分数以 均方误差 表示。因为这是一个误差分数,所以值越接近零,得分越好。‘第二最佳’模型的 mean_test_score 为 -102,std_test_score 为 5. std_test_score 代表模型在测试集上的分数的标准差,这是一个绝对值,因此,离 0 越近,模型的表现越一致。尽管 #1 模型的均值稍好,但其标准差要大得多。这意味着模型的性能不够一致和可靠,并且通常这并不是理想的。这就是为什么在这个例子中我会选择模型 #2 而不是模型 #1,并且在大多数使用情况下你也应该这样做。

示例 #2:训练分数

现在让我们考虑两个在测试集上表现几乎相同的模型:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

两个看似相同的决策树(作者提供的图片)

尽管这些模型的性能看起来几乎相同,但它们并不是相同的。为了检测这一点,我们必须将 GridSearchCVreturn_train_score 参数从默认值 False 设置为 True

from sklearn.model_selection import GridSearchCV
from sklearn.tree import DecisionTreeClassifier

model = DecisionTreeClassifier()
param_grid = {...}

gridsearch = GridSearchCV(model, 
                          param_grid, 
                          scoring="mean_absolute_error", 
                          return_train_score=True)

现在如果我们在拟合和训练后运行 gridsearch.cv_results_,我们会注意到添加了多个列,其中一个列显示如下:mean_train_score

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

带有训练分数的 cv_results_(作者提供的图片)

我们现在可以注意到,尽管两个模型在测试集上的表现似乎相同,但第二个模型在训练集上的表现实际上明显优于第一个模型。当然,这在某些情况下可能意味着过拟合,但在这里,均值训练分数与均值测试分数更一致,因此似乎没有暗示过拟合。一般来说,当两个模型在测试集上的表现相似时,考虑到训练分数时表现更一致的模型应该被优先选择。

示例 #3:模型复杂度

让我们考虑以下网格搜索结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

cv_results_(作者提供的图片)

上述两个模型的测试和训练分数几乎相同。然而,在确认第一个模型是最佳模型之前,还有更多重要的信息需要考虑。在最后一列中,我们看到 param_tree__max_depth,它显示了每个模型决策树的树深度。‘最佳’模型的决策树深度为 50,而‘第二最佳’决策树的深度仅为 2。在这种情况下,我们可以选择第二个模型作为最佳模型,因为这个决策树更容易解释。例如,我们可以使用 [sklearn.tree.plot_tree](https://scikit-learn.org/stable/modules/generated/sklearn.tree.plot_tree.html) 绘制决策树,并查看一个非常简单且易于解释的树。一个深度为 50 的树几乎不可读且难以解释。作为经验法则,当两个模型在测试集和训练集上的表现相当时,应优先选择更简单的模型。

示例 #4:模型速度性能

我想展示的最后一个例子是关于速度性能的。假设我们有与示例 #3 相同的网格搜索结果,但添加了一个新列:mean_score_time

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在,另一个原因出现了,我们可能会更喜欢第二个模型而不是第一个模型。第一个模型的运行时间是第二个模型的 6 倍以上,这可能是由于其额外的复杂性。当然,由于 mean_score_time 表示模型预测验证集的平均时间,这种差异似乎可以忽略不计。然而,想象一下一个需要基于每台机器数百个传感器预测机器故障的模型。对于这样的模型,如果需要在大量数据上进行实时预测,那么 n 个观测值上的 0.02 秒与 n 个观测值上的 0.003 秒之间的差异可能会产生显著影响。

结论

在这篇文章中,我们看到了四个示例,展示了为什么你不应该盲目相信 scikit-learnGridSearchCV 的最佳估计器。我们不仅要依赖平均测试分数,还应该考虑交叉验证结果中的其他列,以确定哪个模型最好,尤其是在顶级模型的测试分数相似时。

如果你想了解更多关于机器学习和/或网格搜索的内容,请务必阅读这些相关文章:

[## 使用这些参数显著提高你的网格搜索结果

使用 EstimatorSwitch 对任何机器学习管道步骤进行网格搜索。

通过这些参数显著提高网格搜索结果 一款备受期待的时间序列交叉验证器终于到来 [## 一款备受期待的时间序列交叉验证器终于到来

不均匀分布的时间序列数据不再是交叉验证的问题。

一款备受期待的时间序列交叉验证器终于到来

是时候提升数据分析师的角色了

原文:towardsdatascience.com/its-about-time-we-elevate-the-data-analyst-role-e2845d038233?source=collection_archive---------7-----------------------#2023-01-02

意见

数据分析师应成为依靠数据的可信顾问

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 João António Sousa

·

关注 发表在 Towards Data Science ·8 分钟阅读·2023 年 1 月 2 日

注意:大多数数据和分析角色定义模糊。本文重点讨论数据分析师角色,通常被描述为 BI 分析师。根据组织的不同,业务分析师和数据科学家的角色也有些重叠。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 Freepik 提供

数据分析师的角色在过去几年中迅速演变。随着数据复杂性和业务期望的爆炸式增长,他们面临着许多挑战。

在早期和分析成熟度较低的公司中,数据分析师是一个通才角色。它负责几乎整个数据与分析马拉松。从准备和可视化,到分析和见解沟通。

数据分析领域的增长和创新改变了数据驱动的含义以及对数据分析师角色的要求。这导致了对专业角色的需求增加,并对最后一公里给予了强烈关注。这样,数据分析师更多地关注于分析、见解和决策。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

数据分析师角色的演变 —— 作者图片

与此相关,Cassie Kozyrkov 将数据分析师的关键角色定义为“查找事实并为你提供灵感”,并通过指出“分析游戏完全是关于优化每分钟的灵感”来强调工作的高要求/节奏。

我通常将这一概念定义为“行动见解的速度”。

行动见解的速度在提升分析价值和帮助数据实现承诺方面起着关键作用。因此,它应该被定义为数据分析师的北极星指标。(了解更多关于加速见解速度的内容,请访问这里.)

然而,由于各种挑战,大多数公司还远未达到这一点。让我们深入探讨是什么阻碍了数据分析师的进步。

数据分析师的挑战

数据复杂性和业务期望都在上升。同时,传统的 BI 工具在过去 20 年中没有太大进展,造成了许多数据团队的差距。以下是今天的数据分析师面临的主要挑战:

定义不清的角色

在数据与分析领域普遍存在角色划分的问题。这不仅限于数据分析师。它的角色与数据科学家和 BI 分析师(有时也包括业务分析师)有显著的重叠。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

数据角色的复杂格局 —— 作者图片

如果你查看大多数职位描述,它们会强调数据分析师的使命是提供见解。这本身非常模糊——这个角色是成为自助服务的推动者(即,回应业务请求和构建仪表板),还是提供建议?组织对数据分析师的角色有不同的愿景,从自助服务推动者(即,回应业务请求和构建仪表板)到数据驱动的可信业务顾问。

价值误解

数据分析师常被视为“二等公民”,感到在“技术”专长上被数据科学对手甩在了后面。数据科学角色与更高的薪酬和地位相关,这加剧了这一差距。

大部分时间用于应急处理和低附加值的任务

数据分析师花费大部分时间在低附加值的任务上,通常以非常被动的方式进行(例如,修复损坏的仪表板)。许多人主要作为“仪表板/报告工厂”运作,与业务脱节。

现状

这些挑战使数据与分析团队停滞不前,因为分析师产生了大量的输出,但业务结果却很少。在实践中,大多数数据分析师将大部分时间花在低附加值的任务上,主要关注描述性分析,仅呈现发生了什么。

以下是我所见的三种最常见的数据分析师状态的详细说明:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

数据分析师角色的状态 — 作者提供的图像

状态 I — 数据整理员

你可以将“数据整理员”视为陷入数据分析师角色初始通才定义的数据分析师。他/她负责处理与数据相关的所有事务,包括从不同资源中提取数据、生成报告、清理数据和准备仪表板。因此,他们几乎没有时间专注于挖掘洞察,他们提供的价值被稀释了。

结果: 团队停留在“什么”上,只能识别业务表现中的变化,却无法回答“为什么”。分析的价值不明确,导致数据文化薄弱。

状态 II — 仪表板构建者

尽管“仪表板构建者”状态比“数据整理员”状态稍微发展了一些,但这些分析师仍未专注于可操作的洞察。他们的大部分时间用于可视化、构建和管理仪表板。他们被动地寻找洞察,以回答业务团队的问题或解决紧急情况。

结果: 分析师的价值未被充分发掘。决策过程存在显著偏差,阻碍了真实洞察的发现,同时分析师团队被临时请求所压倒。

状态 III — 分析师

这是一个成熟的状态,其中分析师的角色定义更为明确,公司数据文化也已较为强大。分析师的重点在于提供洞察,但他们无法跟上业务的节奏来发现这些洞察。他们通常还需要处理大量非分析师的工作。因此,需要增强分析工作流,以释放他们的时间,让他们主要集中在分析的最后阶段。

结果: 在重复任务上花费过多时间(例如,在仪表板上切割和分类)会阻碍行动洞察和业务影响的速度,导致机会的错失。

未来的数据分析师 — 业务顾问

与一些最具数据前瞻性的公司合作时,我见证了数据分析师能够带来的业务影响。这些进展从根本上改变了数据分析师在公司中的职能。他们参加每周的业务会议,呈现发生了什么,为什么会发生,以及所以呢,分享主动的业务解决方案。

数据分析师的主要目标是快速浏览庞大的数据集,与业务利益相关者联系,并挖掘潜在洞察。速度是他们的最高美德。

结果:公司能够把握自身动态,并以业务速度发现真实洞察。这激发了决策者为数据科学家选择最有价值的任务。我已识别出使数据分析师成为可信赖商业顾问的三个主要特征:

  • 合作伙伴关系 — 最优秀的团队将数据分析师嵌入到业务职能中,并与业务利益相关者非常紧密地合作。

  • 主动性 — 数据分析师通过在诊断分析上花费大量时间来分享主动的业务建议,而不仅仅是展示/描述发生了什么。他们会参加日常/每周/每月的业务回顾,解释关键指标变化的原因及潜在建议。

  • 领域/业务专业知识 — 理解业务和领域(例如,产品),以便将各方面连接起来并推动可操作的洞察。

所需条件

这种转变需要通过增强技术来实现,同时需要文化和人员的变革。这些是主要要求:

  • 增强 — 这是推动这一转变的主要因素。以业务速度提供可操作的洞察需要对传统 BI 工作流程进行增强,以消除速度与全面性的权衡 (了解更多)。此外,为了成为可信赖的顾问,分析师不应花费大量时间在仪表盘上切割和处理数据。相反,他们应该专注于寻找真实洞察,利用 ML 进行统计测试,指引业务团队查找相关信息,并与他们紧密合作。这是征服分析最后一公里的唯一途径:提炼洞察,正确传达,并推动行动。

  • 业务和领域专业知识 — 数据分析师需要对所工作的业务和领域有很好的理解。因此,分析师在一个业务职能(例如,市场分析师)上专业化,并嵌入到相应职能(例如,市场团队)中越来越常见(阅读更多关于领域专业知识的重要性这篇文章由 Randy Au 撰写)。

  • 软技能 — 要成为可信赖的顾问,数据分析师需要超越 SQL 语言等技术技能。商业头脑、沟通能力、倾听技能、好奇心、同理心和讲故事能力对于成为战略合作伙伴、发展领域专业知识以及推动建议和行动至关重要。

  • 数据文化——合适的数据文化是关键,其中业务利益相关者将数据视为关键资产,并愿意与数据分析师紧密合作。推动这种文化需要数据团队通过关注业务价值来赢得对方的信任。

如何弥合差距。

这种演变需要心态、技能和文化的转变,这些都需要合适的流程和工具来支持。以下是几个具体的最佳实践:

工具——增强分析作为一种推动者。

  • 增强现有工作流程,使分析师能够以业务速度获取详细洞察,并有足够的时间处理分析马拉松的最后一公里。

人员——发展专业知识,使分析师成为值得信赖的顾问。

  • 将你的分析师嵌入业务职能中。

  • 促进不同分析师之间的知识交流(包括跨功能)。

  • 为技术和非技术事项(例如,如何进行根本原因分析,如何呈现洞察)制定最佳实践。

文化——发展你的数据文化以最大化价值。

  • 促进一个以业务影响为中心的文化——开发一个衡量价值的框架,推广成功案例等。

  • 通过提供背景和创建对齐,确保分析师发展领域和业务专长。

过程——支持整个转型。

  • 开发一套流程,以促进业务和数据团队之间的知识转移(例如,数据研讨会、业务深入探讨)。

  • 设立每周或每月的业务审查绩效,并让你的分析师参与其中。

提升你的数据分析师以最大化业务影响。

现在是数据与分析团队征服分析马拉松最后一公里的时候了。公司已经在数据收集、准备和可视化上投入了大量资源。现在是时候专注于终点线——数据分析、洞察沟通和更好的决策。提升你的数据分析师来征服这一最后一公里,并实现预期(和投资回报)。

想法?请联系 João Sousa Kausa 担任增长总监。请关注更多关于如何掌握诊断分析并提高数据和分析价值的帖子。

终于是时候告别 “git checkout” 了

原文:towardsdatascience.com/its-finally-time-to-say-goodbye-to-git-checkout-fe95182c6100

“Git switch”和“git restore”将会长期存在

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Tomer Gabay

·发表于 Towards Data Science ·4 分钟阅读·2023 年 5 月 1 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 Dim Hou 提供,来源于 Unsplash

Git 是开发人员最广泛使用的版本控制系统。Git 中最常用的命令之一是 git checkout,它允许用户在分支之间切换并将文件恢复到先前的状态。

然而,在 2019 年,随着 Git 2.23 的发布,两个新命令被引入以取代 git checkout,以实现更直观和简化的工作流:git switchgit restore。尽管它们发布已有近四年,开发人员仍然难以放弃使用 git checkout(我猜老习惯很难改)。

在这篇文章中,我们将探讨 Git 团队通过引入 git switchgit restore 解决了 git checkout 的哪些缺点,以及为什么这应该导致不再使用 git checkout

git checkout 的问题

git checkout 是一个具有两个核心功能的命令:

  • 在分支之间切换

  • 将文件恢复到先前状态

然而,这两个功能在命令语法中没有明确区分,这可能导致混淆和错误。例如,如果你不小心输入 git checkout <commit> 而不是 git checkout <branch>,你将进入所谓的“分离的 HEAD” 状态,这意味着你所做的任何新提交将不会与任何分支相关联。当你在分离的 HEAD 中做了更改后切换到新分支时,这些更改将会丢失,且无法恢复。

引入 git switch 和 git restore

为了解决git checkout的一些问题,Git 团队在 Git 版本 2.23 中引入了两个新命令:git switchgit restore。这两个命令将git checkout之前提到的两个核心功能拆分为两个独立的命令。

git switch仅用于在分支间切换。语法很简单:git switch <branch>。如果你尝试使用git switch切换到一个提交,Git 会抛出一个错误,而不是将你置于脱离的 HEAD 状态。因此,你不太可能进行与任何分支无关的提交。

另一方面,git restore只能用于将文件恢复到之前的状态。它的语法也很简单:git restore <file>。如果你不小心尝试切换到一个分支而不是文件,Git 将会抛出一个错误。

使用git switchgit restore的好处

git switchgit restore的一个关键好处是提高了安全性。由于两个功能被分离成两个不同的命令,你无意中应用错误功能的机会减少了。这使得因为丢失工作而发生错误和挫败感的可能性降低了。

git switchgit restore的另一个好处是语法更直接,因为你不需要记住git checkout的不同用法。即使在使用 Git 多年之后,许多开发者仍然无法完全理解git checkout的工作原理和语法。

此外,git switchgit restore的命令更直观且准确。当你想创建一个新分支时,可以使用git switch -c <branch>,其中-c标志表示创建。在使用checkout时,你必须使用git checkout -b <branch>,其中-b标志表示分支。然而,你也可以使用git checkout <branch>在分支间切换,因此-b标志仅用于创建新分支,而一个名为branch的标志并不能很好地表明这是关于创建新分支而不是切换到一个已有的分支。因此,git switchgit restore的命令更直观,因此应当被优先使用。

总而言之

虽然git checkout多年来一直是 Git 用户的首选命令,但随着git switchgit restore的引入,已经有了更好的替代方案。将git checkout的两个核心功能拆分成两个独立的命令后,Git 使得在分支间切换和恢复文件变得更加容易,而不会不小心使用错误的命令。如果你仍在使用git checkout而不是git switchgit restore,现在正是切换的最佳时机!

资源

[## Git

Git 易于学习,体积小,性能极快。它超越了像 Subversion 这样的 SCM 工具…

Git 2.23 发布,新增两个命令 ‘git switch’ 和 ‘git restore’,还有更多内容!

上周,Git 团队发布了包含实验性命令、向后兼容等更多功能的版本…

Hub PacktPub [## git switch 和 git checkout 有什么区别

switch 命令确实与 checkout 做了相同的事情,但仅限于切换分支的用法。在…

Stack Overflow

这不仅仅关乎得分

原文:towardsdatascience.com/its-not-all-about-scores-1a5d97ea981d?source=collection_archive---------22-----------------------#2023-03-27

在模型选择过程中,你应考虑的其他标准

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Dorian Drost

·

关注 发表在Towards Data Science ·8 分钟阅读·2023 年 3 月 27 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

模型选择和甜甜圈选择之间的区别:在模型选择中,你只能选择一个。照片由ELISA KERSCHBAUMER提供,拍摄于Unsplash

作为数据科学家或机器学习工程师,你的大部分时间都在通过创建新特征、比较不同类型的模型、尝试新的模型架构等方式来提升模型的性能。最终,决定性的是测试集上的得分,所以在选择模型时,你会将重点放在得分上。然而,尽管模型性能非常重要,但还有其他一些次要标准你不应忽视。

如果你的 MLOps 部门无法托管一个几乎完美评分的模型,你能得到什么?如果预测准确但获取时间极长,用户会有什么感觉?如果你的模型在你训练它的数据上表现良好,但随着时间的推移变得越来越差且无法适应新数据,你该怎么办?

这些是选择模型或算法时需要考虑的一些因素。在本文中,我想引起你对一些经常被忽视的次要标准的关注,尽管这些标准对用户体验有着巨大的影响。

1) 推理速度

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 George Brynzan 提供,来源于 Unsplash

根据你的模型使用情况,速度可能很重要。如果模型的预测在后台异步进行,花费几秒钟多一点也无所谓,但在用户主动等待预测的情况下,每一秒钟都可能极大地影响用户体验。在机器学习中,我们经常考虑训练模型所需的时间,但推理的计算时间常常被忽视。

在许多连续执行的简单任务中,如果能更快地获得预测,纠正多出的一些错误可能是值得的。假设你的工作是标记呈现给你的图像中的所有企鹅。幸运的是,你不必从零开始,因为有一种 AI 已经检测到一些企鹅并画出边框,所以你需要验证它们,添加缺失的边框,或修正不准确的部分。在这种情况下,速度很重要。虽然准确性得分少了几分,但你需要更频繁地纠正 AI 的预测,但这本来就是你的工作。然而,如果你需要为每张图像等待几秒钟才能获得 AI 的预测,那将会大大拖慢你的速度。

另一方面,当然并不总是值得为了速度而牺牲准确性。如果你是一名医生,得到一种能够检测 X 射线图像中恶性部位的 AI 支持,那么即使预测多花几秒钟也无关紧要。你一天只需要预测几次,这时准确性是最重要的方面。作为患者,你肯定不希望因为速度快而得到错误的诊断,对吧?

2) 重新训练

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 Danil Shostak 提供,来源于 Unsplash

大多数情况下,你的模型评分仅仅是特定静态测试集上的瞬时快照。尽管这个测试集上的评分在一定程度上可以推断模型在真实数据上的表现,但你应该始终意识到,外部世界可能会随着时间变化。在这种情况下,你需要能够作出相应的反应。即使你能够收集新数据,重新训练深度神经网络也可能非常耗时和资源。在某些情况下,如果你每晚或每周重新训练一次,更简单的模型可能是更好的选择。

重新训练或其他方式调整模型行为在你处理的数据不完整或非常异质时变得重要,因为你无法达到模型在新数据上良好泛化的水平。如果你创建一个 AI 来以给定作者的风格写故事,这可能对它知道的作者效果很好。然而,如果它从未读过简·奥斯汀的任何故事,它怎么可能以她的风格写小说呢?随着新作者的出现,你可能需要不时重新训练模型。模型参数越多,这个过程就越困难。

甚至有些情况需要训练多个模型,例如,每个客户一个模型。当数据如此多样时,用客户 X 的数据进行训练可能会损害客户 Y 的模型表现。如果你的 AI 为公司制定商业计划,你可能会发现这些公司的数据差异非常大。一些公司的收入受到全球经济形势的严重影响,而另一些公司则相对稳定。一些公司已经在市场上存在了很长时间,希望实现可持续发展,而另一些公司则非常年轻,目标是快速增长。由于数据如此多样,为新客户接入可能需要在其公司数据上重新训练或微调模型。也可能因为法律原因,你不能使用客户 X 的数据为客户 Y 制定预测,因此你需要为每个客户训练一个新模型。模型训练所需时间越长,每个新客户所花费的时间和金钱就越多。

3) 可部署性

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Ian Taylor 提供,来源于 Unsplash

如果一个模型不能被任何人使用,那么再好的模型也没有意义。有多种方式可以将模型交付给最终用户,从在云服务器上托管服务到在用户设备上安装程序。选择哪种方式取决于你的使用场景,因此你决定的模型需要适应所需的部署方式。

更大的模型需要更长时间来加载,并且需要更多的资源。一个需要解压神经网络及相关包的 Docker 容器,可能轻松达到几个 GB 的大小。在云提供商上,多出几个 GB 需要花费一些钱,你可以决定是否值得,但如果用户需要下载模型,问题可能会变得更大。在现代笔记本电脑上,安装几个 GB 可能不是问题,但如果你的主要目标群体生活在发展中国家,并使用几年前的手机,内存和计算能力可能会成为问题。

如果你训练一个 AI 来检测主要用于非洲或南美洲的作物中的害虫或疾病,你应该预期你的用户没有稳定的互联网连接。因此,应用程序应该能够离线运行,这意味着用户需要下载模型,并在可能已经有几年历史且资源有限的智能手机上计算预测。在这种情况下,即使深度神经网络有更好的评分,更小、更简单的模型可能是更好的选择。

如上所述,甚至有需要托管多个模型的情况(例如,每个客户一个)。在这种情况下,模型的大小变得尤为关键。部署一个神经网络可能是可行的,但你是否能托管一百个不同的模型?如果不能,小型模型可能是更好的选择。

4) 可解释性

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由Crissy Jarvis提供,来自Unsplash

出于充分的理由,可解释的人工智能在当前研究中变得越来越重要。大多数时候,我们只是接受机器学习方法通常是黑箱的这一现实,即模型给出预测而无法解释模型为何以某种方式行为。然而,许多使用案例中,这种可解释性有助于获得用户的信任或提升他们的体验。对于医生来说,如果人工智能做出一个令人惊讶的诊断而没有解释,那么很难决定如何继续。而金融投资者可能不会遵循模型的预测,如果他们无法理解其中的逻辑。健康或金融相关任务中的关键或敏感决策,可能需要比其他较不关键任务更多的解释。如果 AI 无法找到你手机上的图像,你可能不会在意原因。在这种情况下,更好的模型性能将大大提升你的体验。

关于可解释性,简单的模型通常比复杂的模型更容易理解。在线性回归中,权重有明确的含义,而在深度神经网络中,它们则完全形成了一个黑箱。然而,也有越来越多的努力使深度学习模型变得可理解,ELI5就是其中一个最著名的例子。对用户而言,模型或算法是否看起来更值得信任可能会更常被使用,即使它的分数低于竞争模型。你不会仅仅因为一个 AI 告诉你这么做而不解释原因就投资钱,对吧?

总结

我刚刚展示了一些可能与模型选择相关的标准,尽管它们常常被忽视。当然,这些标准本身并不是最重要的标准,模型的分数也不是。最终,你必须根据你的使用案例权衡所有标准。与其仅仅看分数,你可以问问自己是否也适当地考虑了其他标准:

  • 模型的预测速度是否仍然足够快?我的用户会主动等待模型预测吗?如果会,推理时间是否合适?

  • 我是否能够托管这个模型?我是否有足够的资源?分数的小幅提升是否真的值得拥有一个更大的模型?

  • 我是否期望我的数据随时间变化?如果是,我能否定期重新训练我的模型?

  • 我是否仍然理解我的模型?使用黑箱模型是否可以,或者在我的情况下可解释性是否比准确性更重要?

还有许多其他标准,对于不同的使用场景可能很重要,所以在选择模型时要开始考虑这些标准。评分并不是全部!

喜欢这篇文章吗? 关注我 以便接收我未来的帖子。

在 Docker 中运行 Jaffle Shop dbt 项目

原文:towardsdatascience.com/jaffle-shop-dbt-docker-93a9b14532a4

流行的 Jaffle Shop dbt 项目的容器化版本

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Giorgos Myrianthous

·发布于 Towards Data Science ·8 分钟阅读·2023 年 4 月 28 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Ryan Howerter的照片,来自Unsplash

如果你是数据构建工具(dbt)的新手,你可能已经遇到过所谓的 Jaffle Shop,这是一个用于测试的项目。

jaffle_shop 是一个虚构的电子商务商店。这个 dbt 项目将应用数据库中的原始数据转换成一个适合分析的客户和订单模型。

我观察到的 Jaffle Shop 项目的一个根本问题是,它期望用户(可能是 dbt 的新手)配置并托管一个本地数据库,以便 dbt 模型能够生成。

在本教程中,我将演示如何使用 Docker 创建项目的容器化版本。这将使我们能够部署一个 Postgres 实例,并将 dbt 项目配置为从该数据库读取和写入。我还会提供一个我创建的 GitHub 项目链接,它将帮助你迅速启动所有服务。

订阅数据管道,这是一个专注于数据工程的新闻通讯

创建 Dockerfile 和 docker-compose.yml

让我们开始定义我们想要通过 Docker 运行的服务。首先,我们将创建一个[docker-compose.yml](https://github.com/gmyrianthous/jaffle_shop/blob/main/docker-compose.yml)文件,在其中定义两个服务。第一个服务是 Postgres 数据库,第二个服务是我们将在下一步中使用 Dockerfile 创建的自定义服务。

# docker-compose.yml

version: "3.9"

services:
  postgres:
    container_name: postgres
    image: postgres:15.2-alpine
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
    ports:
      - 5432
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
  dbt:
    container_name: dbt
    build: .
    image: dbt-jaffle-shop
    volumes:
      - ./:/usr/src/dbt
    depends_on:
      postgres:
        condition: service_healthy

该文件指定了使用的 Docker Compose 版本(3.9 版)。它定义了两个服务,postgresdbt,每个服务都有自己的设置。

postgres服务基于官方的postgres Docker 镜像版本 15.2-alpine。它将容器名称设置为postgres,将端口 5432(Postgres 的默认端口)映射到主机,并为 Postgres 用户和密码设置环境变量。healthcheck部分指定了一个命令来测试容器是否健康,并设置了检查的超时时间和重试次数。

dbt服务指定了一个dbt容器,该容器使用当前目录的 Docker 镜像(使用 Dockerfile)。它将当前目录挂载为容器中的一个卷,并指定它依赖于postgres服务,并且只有在postgres服务健康时才会启动。

为了容器化 Jaffle Shop 项目,我们需要创建一个[Dockerfile](https://github.com/gmyrianthous/jaffle_shop/blob/main/Dockerfile),该文件安装 Python 和 dbt 所需的依赖项,并确保环境设置完毕后容器保持活动状态。

# Dockerfile

FROM --platform=linux/amd64 python:3.10-slim-buster

RUN apt-get update \
    && apt-get install -y --no-install-recommends

WORKDIR /usr/src/dbt

# Install the dbt Postgres adapter. This step will also install dbt-core
RUN pip install --upgrade pip
RUN pip install dbt-postgres==1.2.0
RUN pip install pytz

# Install dbt dependencies (as specified in packages.yml file)
# Build seeds, models and snapshots (and run tests wherever applicable)
CMD dbt deps && dbt build --profiles-dir ./profiles && sleep infinity

配置 Postgres 与 dbt

要与 dbt 交互,我们将使用 dbt 命令行界面(CLI)。包含[dbt_project.yml](https://github.com/gmyrianthous/jaffle_shop/blob/main/dbt_project.yml)文件的目录被 dbt CLI 视为一个 dbt 项目。

我们将创建一个并指定一些基本配置,如 dbt 项目名称和要使用的profile(我们将在下一步中创建)。此外,我们将指定包含各种 dbt 实体的路径,并提供关于它们的物化配置。

# dbt_project.yml

name: 'jaffle_shop'

config-version: 2
version: '0.1'

profile: 'jaffle_shop'

model-paths: ["models"]
seed-paths: ["seeds"]
test-paths: ["tests"]
analysis-paths: ["analysis"]
macro-paths: ["macros"]

target-path: "target"
clean-targets:
    - "target"
    - "dbt_modules"
    - "logs"

require-dbt-version: [">=1.0.0", "<2.0.0"]

models:
  jaffle_shop:
      materialized: table
      staging:
        materialized: view

现在[profiles.yml](https://github.com/gmyrianthous/jaffle_shop/blob/main/profiles/profiles.yml)文件用于存储 dbt 配置文件。一个配置文件包含多个目标,每个目标指定了数据库或数据仓库的连接详细信息和凭据。

# profiles.yml

jaffle_shop:
  target: dev
  outputs:
    dev:
      type: postgres
      host: postgres
      user: postgres
      password: postgres
      port: 5432
      dbname: postgres
      schema: public
      threads: 1

该文件定义了一个名为jaffle_shop的配置文件,指定了运行在名为postgres的 Docker 容器上的 Postgres 数据库的连接详细信息。

  • jaffle_shop:这是配置文件的名称。它是用户为了识别配置文件而选择的任意名称。

  • target: dev:这指定了配置文件的默认目标,在这种情况下名为dev

  • outputs:这一部分列出了配置文件的输出配置,默认的输出配置名为dev

  • dev:这指定了dev目标的连接详细信息,该目标使用 Postgres 数据库。

  • type: postgres:这指定了输出的类型,在这种情况下是 Postgres 数据库。

  • host: postgres:这指定了 Postgres 数据库服务器的主机名或 IP 地址。

  • user: postgres:这指定了用于连接到 Postgres 数据库的用户名。

  • password: postgres:这指定了用于认证 Postgres 数据库的密码。

  • port: 5432:这指定了 Postgres 数据库监听的端口号。

  • dbname: postgres:这指定了要连接的 Postgres 数据库的名称。

  • schema: public:这指定了在对数据库执行查询时要使用的模式名称。

  • threads: 1:这指定了在运行 dbt 任务时要使用的线程数。

Jaffle Shop dbt 模型和 seeds

Jaffle Shop 项目的源数据包括客户、支付和订单的 csv 文件。在 dbt 中,我们可以通过 seeds 将这些数据加载到数据库中。然后,我们利用这些源数据在其基础上构建 dbt 模型

这是一个示例模型,它生成一些 客户的指标

with customers as (

    select * from {{ ref('stg_customers') }}

),

orders as (

    select * from {{ ref('stg_orders') }}

),

payments as (

    select * from {{ ref('stg_payments') }}

),

customer_orders as (

        select
        customer_id,

        min(order_date) as first_order,
        max(order_date) as most_recent_order,
        count(order_id) as number_of_orders
    from orders

    group by customer_id

),

customer_payments as (

    select
        orders.customer_id,
        sum(amount) as total_amount

    from payments

    left join orders on
         payments.order_id = orders.order_id

    group by orders.customer_id

),

final as (

    select
        customers.customer_id,
        customers.first_name,
        customers.last_name,
        customer_orders.first_order,
        customer_orders.most_recent_order,
        customer_orders.number_of_orders,
        customer_payments.total_amount as customer_lifetime_value

    from customers

    left join customer_orders
        on customers.customer_id = customer_orders.customer_id

    left join customer_payments
        on  customers.customer_id = customer_payments.customer_id

)

select * from final

通过 Docker 运行服务

现在让我们构建并启动我们的 Docker 服务。为此,我们只需运行以下命令:

$ docker-compose build
$ docker-compose up

上述命令将运行一个 Postgres 实例,然后构建 Jaffle Shop 的 dbt 资源,如仓库中所述。这些容器将保持运行,以便你可以:

  • 查询 Postgres 数据库及从 dbt 模型创建的表

  • 通过 dbt CLI 运行进一步的 dbt 命令

通过 CLI 运行 dbt 命令

dbt 容器已经构建了指定的模型。然而,我们仍然可以访问容器并通过 dbt CLI 运行 dbt 命令,无论是对于新模型还是修改过的模型。为此,我们需要首先访问容器。

以下命令将列出所有活动的容器:

$ docker ps

复制 dbt 容器的 id,然后在运行下一个命令时输入它:

$ docker exec -it <container-id> /bin/bash

上面的命令将基本上为你提供对容器的 bash 访问权限,这意味着你现在可以运行 dbt 命令。

# Install dbt deps (might not required as long as you have no -or empty- `dbt_packages.yml` file)
dbt deps

# Build seeds
dbt seeds --profiles-dir profiles

# Build data models
dbt run --profiles-dir profiles

# Build snapshots
dbt snapshot --profiles-dir profiles

# Run tests
dbt test --profiles-dir profiles

请注意,由于我们已将本地目录挂载到正在运行的容器中,因此本地目录中的任何更改将立即反映到容器中。这意味着你也可以创建新的模型或修改现有的模型,然后进入正在运行的容器并构建模型、运行测试等。

查询 Postgres 数据库中的 dbt 模型

你还可以查询 postgres 数据库及其上创建的 dbt 模型或快照。同样,我们将需要进入正在运行的 postgres 容器,以便直接查询数据库。

# Get the container id for `postgres` service
$ docker ps

# Then copy the container id to the following command to enter the 
# running container
$ docker exec -it <container-id> /bin/bash

然后我们将使用 psql,这是一个基于终端的 PostgreSQL 界面,允许我们查询数据库:

$ psql -U postgres

以下两个命令分别用于列出表和视图:

postgres=# \dt
             List of relations
 Schema |     Name      | Type  |  Owner   
--------+---------------+-------+----------
 public | customers     | table | postgres
 public | orders        | table | postgres
 public | raw_customers | table | postgres
 public | raw_orders    | table | postgres
 public | raw_payments  | table | postgres
(5 rows)

postgres=# \dv
            List of relations
 Schema |     Name      | Type |  Owner   
--------+---------------+------+----------
 public | stg_customers | view | postgres
 public | stg_orders    | view | postgres
 public | stg_payments  | view | postgres
(3 rows)

你现在可以通过 SELECT 查询来查询 dbt 模型:

SELECT * FROM <table_or_view_name>;

获取完整代码

我已经创建了一个 GitHub 仓库,你可以在本地机器上克隆它,并快速运行容器化的 Jaffle Shop dbt 项目。你可以在以下链接中找到项目及本教程中共享的代码。

[## GitHub - gmyrianthous/jaffle_shop:这是 Jaffle Shop dbt 项目的容器化版本

这是由 dbt Labs 发布的流行 Jaffle Shop dbt 项目的容器化版本。你可以使用这个项目…

github.com

最后的思考

数据构建工具 (dbt) 是现代数据技术栈中快速增长的技术之一。如果你刚开始学习如何使用 dbt,我强烈推荐尝试 Jaffle Shop 项目。这是一个由 dbt Labs 创建的自包含项目,用于测试和实验目的。

dbt 是数据分析师和分析工程师(除了数据工程师)常用的工具,它需要连接到数据库或数据仓库。然而,许多分析师可能不习惯配置和初始化本地数据库。

在这篇文章中,我们演示了如何开始使用 dbt 并运行所有必要的服务,以在本地 Postgres 数据库上实现 dbt 模型。我希望这个教程能帮助你尽快启动你的 dbt 项目和数据库。如果在运行项目时遇到任何问题,请在评论中告知我,我会尽力帮助你调试代码和配置。

👉 订阅 Data Pipeline,这是一个专注于数据工程的新闻通讯

👇相关的文章你也可能喜欢 👇

## dbt CLI 的模型选择

运行 dbt 命令时选择特定模型的完整备忘单

towardsdatascience.com ## ETL 与 ELT:有什么区别?

数据工程背景下的 ETL 与 ELT 比较

towardsdatascience.com

《发现者简:利用大语言模型增强因果发现(因果 Python)》

原文:towardsdatascience.com/jane-the-discoverer-enhancing-causal-discovery-with-large-language-models-causal-python-564a63425c93

一份实用指南,介绍如何利用大语言模型增强因果发现,减少幻觉风险(附 Python 代码)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Aleksander Molak

·发布于 Towards Data Science ·18 分钟阅读·2023 年 10 月 22 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 Artem Podrez 提供,来源于 Pexels.com

“世界即一切存在的事物。”

路德维希·维特根斯坦 — 《逻辑哲学论》(1922 年)

婴儿在出生时无法理解运动的本质。

我们——人类——以及许多其他非人类动物,都天生拥有帮助我们了解环境的系统,但在我们出生时对环境本身知之甚少¹。

我们需要学习。

在这方面,我们与机器学习系统有相似之处。

早期心理学和*(原始)*神经科学对人类和其他非人类动物学习的发现,激发了构建可以从经验中学习的人工系统的灵感。

20 世纪 10 年代机器学习革命的最成功学习范式之一是监督学习。

经过监督训练的神经网络使我们在图像分类或机器翻译等长期存在的问题上取得了几十年的进展。

人类和其他非人类动物具备一种学习机制(在概念上²,但不一定在实施上)类似于监督学习³。

然而,婴儿和监督算法的学习方式之间也存在根本性差异。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1. 一幅 Purkinje 神经元的图画。这些是人脑中最大的神经元之一,可以在小脑中找到。图画由 Santiago Ramón y Cajal 绘制,约 1900 年。来源:commons.wikimedia.org/wiki/Category:Santiago_Ram%C3%B3n_y_Cajal

扔物体*(但不要*对着你)

你有没有见过父母试图说服孩子停止乱扔玩具?一些父母倾向于将这种行为解读为粗鲁、破坏性或攻击性,但婴儿往往有一套非常不同的动机(Molak, 2023)。

他们正在进行系统实验,允许他们学习

物理定律和社会互动规则(Gopnik, 2009)。

得益于这些行动,婴儿不仅可以通过观察静态数据(他们也可以这样做)来学习,还可以通过与世界互动来学习。

超越观察

与世界互动使孩子们能够建立世界模型。

世界模型是关于数据生成过程的假设。

一个足够丰富的世界模型可以帮助我们回答干预性(哪个决定将导致最佳结果?)和反事实(如果我做了 X 而不是 Y,会发生什么?)查询。

监督式机器学习模型(及其他基于关联原理的模型)不能直接回答这些问题⁴。

我们可以教机器回答因果查询吗?

简短的回答是“可以”。

有几种方法可以做到这一点。在这篇文章中,我们将仅限于机器不直接与环境互动的情况。

一个流行的因果查询是:“如果我对一组具有某些特征 X 的个体进行治疗 T ,那么治疗的平均效果会是什么”。

这被称为异质处理效应条件平均处理效应CATE)。

为了回答这种类型的查询,我们需要提供因果模型以及关于数据生成过程结构的额外信息。

后者通常以有向无环图DAG)⁵的形式编码。

传统上,DAG(有向无环图)是(1)基于相关专家知识手动创建的,(2)使用自动化因果发现⁶,或(3)两者结合的过程,有时称为人机交互因果发现

存在各种因果发现算法,每种算法利用的假设略有不同。请参见这篇文章以了解介绍:

## Causal Python — 提升你的 Python 因果发现技能(2023)

…并解锁 Python 中最佳因果发现包的潜力!

[towardsdatascience.com

雨中舞蹈

构建一个表示我们感兴趣问题的有向无环图(DAG)的任务有时可能很具挑战性,尤其是当我们处理复杂且开放的系统时⁷。

企业在构建因果图时可能面临的一个挑战是收集专家知识的挑战。

收集专家知识可能代价昂贵,尤其是当我们打算以系统化的方式进行时。专家可能不容易获得,而且他们的时间可能很宝贵。

我想要大型语言模型

一些作者(例如 Kıcıman et al., 2023)提出,大型语言模型LLMs)可能有潜力用于因果发现。

使用通用模型来获得因果结构的理解的前景令人心动——它为快速且具有成本效益的过程开辟了道路。

不幸的是,现成的语言模型(LLMs)在因果发现中的有效性似乎有限,因为它们表现出不可预测的失败模式、幻觉,并且无法区分变量之间的因果关系和非因果关系(Kıcıman et al., 2023; Zečević et al., 2023)。

因果强盗播客这一集里,我们讨论了 LLMs 和因果推理中的问题。仅音频版本可在这里获取。

一种最近越来越受欢迎的应对 LLMs 幻觉和失败模式的方法是检索增强生成RAG)。

RAG 是一组技术,它为模型提供外部知识来源(如文档或语料库),模型基于这些来源生成响应。

这种方法已被证明能减少幻觉并帮助模型保持相关性。

要了解更多关于 RAG 及其重要性的内容,请参阅我同事Anthony Alcaraz的这篇文章:

## 为什么检索增强对企业 LLMs 至关重要,以及选择哪个当前模型?

大型语言模型(LLMs)展示了在企业环境中彻底改变工作流程的巨大潜力。

ai.plainenglish.io

两种视角

LLMs 和传统因果发现算法在寻找因果结构的任务中存在根本差异。

传统的因果发现算法利用观测、干预或混合数据的组合以及以假设形式编码的归纳偏差,从数据中推断生成这些数据的过程的结构信息。

另一方面,LLM 完全不看(测量)数据。它们利用概念的学习语义表示并进行转换,以回答关于这些概念之间可能的因果关系的查询。

在这种情况下,LLM 的一个视角是,它们利用因果事实的相关性(有关详细信息,请参见 Zečević et al., 2023),以确定两个或多个实体之间的因果关系。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2。因果事实的文本和测量表示的上下文等效性。来源:Zečević et al., 2023

我们在这里讨论事实的相关性,因为 LLM 是以联想的方式进行训练的,因此通常无法期望它们基于因果合理的表示进行操作⁸。

好消息是 RAG 可以通过将模型响应集中于提供的语料库的上下文,减少失败模式和幻觉的风险。

这为 LLM 在因果发现过程中的更有意义的角色打开了道路。

文档和测量

让我们假设我们想为一个感兴趣的问题建立一个因果图。

我们在一段时间内测量了一组与此问题相关的变量。我们还有存储在多个文档中的关于该问题的专家知识。

我们可以简单地在测量数据上运行因果发现算法,但如果无法保证算法的假设得到满足,这样做是有风险的。这也意味着抛弃我们手头上宝贵的专家知识。

另一方面,我们可以使用 RAG-LLM 查询语料库并解析模型输出以获取图形。

这可能仍然有风险,因为即使有 RAG,我们也不能保证模型会正确回答查询。此外,语料库中的知识可能不完整,这意味着数据中存在的一些因果关系可能在语料库中没有反映出来。

想成为朋友吗?

一种替代方法可能是结合两种方法,三角测量语义和基于测量的视角。

一种实现方法是通过查询 RAG-LLM 系统从文本表示中提取因果关系,然后使用获得的信息作为专家知识,注入到仅在测量数据上操作的因果发现算法中。

想尝试一下吗?

Mehendretex 能帮助登山者吗?

让我们将刚刚描述的想法付诸实践,看看它如何运作。

我使用了一个关于高度和温度关系的经典例子作为我们练习的灵感。

为了使任务更加有趣,我们将向问题中添加更多变量,并使其中一个完全陌生于 LLM(模型本身和文档语料库中都没有关于它的先验知识)。

我们将关注找到一组变量之间的因果结构,这些变量可能影响登山者的死亡风险。

为了找到结构,我们将使用合成测量数据⁹,并利用维基百科中提供的一些变量的先验知识(我们的语料库)。

问题陈述中的变量有:

  • 海拔

  • 温度

  • 氧气密度

  • 死亡风险

  • Mehendretex 的使用(我们虚构的处理方法)

后者变量——Mehendretex(现实中不存在类似的东西)——的功能是对我们系统中的 LLM 部分进行压力测试。

我们预计,在相关提示下,一个 RAG-LLM 会告诉我们,它不能对 Mehendretex 和任何其他变量之间的因果关系说任何话。

图 3 展示了我们问题的真实结构 DAG,以图形(左)和邻接矩阵(右)的形式表示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3. 我们问题的真实结构表示为图形(左)和邻接矩阵(右)。矩阵中的黑色区域表示一条边,白色区域表示没有边。例如,坐标为 (4, 3) 的黑色方块表示从节点 4 到节点 3 有一条边。

我的 Python 在哪里?

让我们编写代码解决这个问题。

GitHub 仓库的链接见文章末尾。

我们将使用numpyscipy生成数据,gCastle 将作为我们的因果发现库,OpenAI 的GPT-4 作为 LLM,LangChain 作为框架,帮助我们将相关的维基百科文章提供给 GPT-4。

在此基础上,我们将导入os来管理 OpenAI 密钥,itertools.combinations来帮助我们生成 RAG-LLM 代理的变量对。

设置

让我们导入库:

import os
from itertools import combinations

import numpy as np
from scipy import linalg 
from scipy import stats 

import matplotlib.pyplot as plt

from langchain.agents import load_tools, initialize_agent
from langchain.agents import AgentType

from langchain.chat_models import ChatOpenAI

from castle.common import GraphDAG
from castle.metrics import MetricsDAG
from castle.algorithms import PC

from castle.common.priori_knowledge import PrioriKnowledge

让我们设置 OpenAI 密钥:

with open(r'my_folder/my_openai_key.dat') as f:
    key = f.read()

os.environ['OPENAI_API_KEY'] = key

数据生成

现在,让我们生成数据。

注意,我们仅生成虚构的测量数据。我们将使用的文本数据是实际的维基百科文章。

从这个意义上讲,我们在这个例子中使用半合成数据。

让我们从定义变量名称和索引之间的映射开始。这将帮助我们在后续中以矩阵格式编码从 LLM 获得的专家知识:

all_vars = {
    'altitude': 0,
    'oxygen_density': 1,
    'temperature': 2,
    'risk_of_death': 3,
    'mehendretex': 4
}

接下来,我们设置样本大小,并根据图 3中展示的结构生成变量的合成测量数据:

SAMPLE_SIZE = 1000

altitude = stats.halfnorm.rvs(scale=2000, size=SAMPLE_SIZE)
temperature = 25 - altitude / 100 + stats.norm.rvs(
    loc=0,
    scale=2,
    size=SAMPLE_SIZE
)

mehendretex = stats.halfnorm.rvs(size=SAMPLE_SIZE)

oxygen_density = np.clip(
    1 - altitude / 8000 
    - temperature / 50 
    + stats.norm.rvs(size=SAMPLE_SIZE) / 20,
    0, 
    1)

risk_of_death = np.clip(
    altitude / 20000 
    + np.abs(temperature) / 100 
    - oxygen_density / 5 
    - mehendretex / 5
    + stats.norm.rvs(size=SAMPLE_SIZE) / 10,
    0,
    1
)

请注意,为了使数据集更加逼真,我们使用了正态分布和半正态分布的组合,并与截断相结合。

这也将使因果发现算法的工作更加困难。

让我们将所有变量堆叠到一个矩阵中:

dataset = np.stack(
    [
        altitude,
        oxygen_density,
        temperature,
        risk_of_death,
        mehendretex
    ]
).T

…并以矩阵形式定义图 3中的结构:

true_dag = np.array(
    [
        [0, 1, 1, 1, 0],
        [0, 0, 0, 1, 0],
        [0, 1, 0, 1, 0],
        [0, 0, 0, 0, 0],
        [0, 0, 0, 1, 0]
    ]
)

算法因果发现

让我们通过将经典的 PC 算法应用于我们的测量数据来开始发现过程。

我们将使用 gCastle 实现的 PC 稳定变体:

# PC discovery without LLM assist
pc = PC(variant='stable')
pc.learn(dataset)

# Vizualize
GraphDAG(
    est_dag=pc.causal_matrix, 
    true_dag=true_dag)

plt.show()

# Compute metrics
metrics = MetricsDAG(
    B_est=pc.causal_matrix, 
    B_true=true_dag)

print(metrics.metrics)

结果展示在图 4中:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4:将 PC 算法应用于我们的测量数据的结果。右侧是实际邻接矩阵;左侧是 PC 算法生成的矩阵。矩阵中的黑色“区域”表示一条边,白色区域表示没有边。例如,坐标为(2, 1)的黑色方块表示从节点 2 到节点 1 有一条边。

如我们所见,结果并不理想。算法正确识别了一些边(例如,(4, 3)(0, 1)(0, 2)),但许多边仍然未定向。

比如,在左侧的矩阵中,我们有一个边(4, 3),但也有(3, 4),这表明模型能够识别节点 3 和 4 之间的连接,但无法确定这一连接的方向。

了解我们数据集的基础结构,这并不令人惊讶,因为 PC 算法依赖于条件独立性测试,并且在某些结构配置下无法确定边的方向。

想了解更多关于 PC 的信息及其原因,请查看这篇博客文章进行介绍或我最近的因果建模书籍的第三部分以获得更详细的处理。

当我们查看指标时,这张图的 F1 分数等于 0.53¹⁰。

定义一个 LLM 代理

让我们看看当我们将 RAG-LLM 代理添加到这个场景中会发生什么。

首先,我们将定义一个先验知识对象,然后用它来存储我们的代理提取的知识。

# Instantiate the priori knowledge object
priori_knowledge = PrioriKnowledge(n_nodes=len(all_vars))

接下来,让我们实例化代理。

llm = ChatOpenAI(
    temperature=0, 
    model='gpt-4')

我们将选择 GPT-4 作为我们的模型。我们将温度设置为0,以使模型输出尽可能保守。

让我们添加 LangChain 的工具,使模型能够访问维基百科:

# Load tools
tools = load_tools(
    [
        "wikipedia"
    ], 
    llm=llm)

最后,让我们实例化代理:

# Instantiate the agent
agent = initialize_agent(
    tools, 
    llm, 
    agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION,
    handle_parsing_errors=True,
    verbose=False)

我们将使用代理浏览维基百科页面,以寻找与我们问题相关的变量之间的因果关系信息。

从这个代理获得的发现将被传递给另一个 GPT-4 实例。

这个第二个 GPT-4 实例将负责解释初始输出,并将其转换为与我们之前的知识对象兼容的格式。

为了使代码更具可重用性,我们将其封装在一个函数中:

def get_llm_info(llm, agent, var_1, var_2):

    out = agent(f"Does {var_1} cause {var_2} or the other way around?\
    We assume the following definition of causation:\
    if we change A, B will also change.\
    The relationship does not have to be linear or monotonic.\
    We are interested in all types of causal relationships, including\
    partial and indirect relationships, given that our definition holds.\
    ")

    print(out)

    pred = llm.predict(f'We assume the following definition of causation:\
    if we change A, B will also change.\
    Based on the following information: {out["output"]},\
    print (0,1) if {var_1} causes {var_2},\
    print (1, 0) if {var_2} causes {var_1}, print (0,0)\
    if there is no causal relationship between {var_1} and {var_2}.\
    Finally, print (-1, -1) if you don\'t know. Importantly, don\'t try to\
    make up an answer if you don\'t know.')

    print(pred)

    return pred

注意我们如何首先调用代理,使用一个提示,然后将这个代理的输出传递给另一个(非代理)LLM 实例,该实例使用另一个提示进行初始化。

在我的实验中,这种分层方法比仅使用单一代理获得了更好、更一致的结果。

我还注意到,重复向两个代理解释因果关系对改善结果有帮助。

在这两个提示中,我们使用 Pearl 的干预因果定义。此外,第一个提示指定我们对广泛的因果效应感兴趣,只要它们符合基本定义。

在实验中,后者在某些情况下帮助模型变得更加果断。

函数返回:

  • 如果模型判断存在从var_1var_2的因果边,则为(0,1)

  • 如果模型判断因果方向是从var_2var_1,则为(1,0)

  • 如果模型判断var_1var_2之间没有因果关系,则为(0,0)

  • 如果模型不知道答案(请注意我们在提示中强调了模型如果不知道答案就不应给出答案),则为(-1,-1)

LLM 增强的因果发现

我们现在准备使用我们的分层双提示系统从维基百科收集专家知识。

我们将遍历所有可能的变量组合,记录每个组合的模型回答,并将其存储在先验知识对象中:

for var_1, var_2 in combinations(all_vars.keys(), r=2):
    print(var_1, var_2)
    out = get_llm_info(llm, agent, var_1, var_2)
    if out=='(0,1)':
        priori_knowledge.add_required_edges(
            [(all_vars[var_1], all_vars[var_2])]
        )

        priori_knowledge.add_forbidden_edges(
            [(all_vars[var_2], all_vars[var_1])]
        )

    elif out=='(1,0)':
        priori_knowledge.add_required_edges(
            [(all_vars[var_2], all_vars[var_1])]
        )
        priori_knowledge.add_forbidden_edges(
            [(all_vars[var_1], all_vars[var_2])]
        )

print('\nLLM knowledge vs true DAG')
priori_dag = np.clip(priori_knowledge.matrix, 0, 1)

GraphDAG(
    est_dag=priori_dag, 
    true_dag=true_dag)

plt.show()

请注意,我们仅在模型判断变量之间存在因果关系(输出为(0,1)(1,0))时,将信息从模型传递到先验知识对象,但当模型声称变量之间没有因果关系时,我们会跳过这一步。

这个决定基于观察到模型在声称存在因果影响时通常过于谨慎。

通过忽略模型的空结果,我们实质上允许因果发现算法在这种情况下决定是否存在关系。

让我们看看模型能够学到什么。

图 5 显示了我们的 LLM 代理与真实情况的对比结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5. 我们的 LLM 代理检索的先验知识(左)和真实情况图(右)。

正如我们所见,模型能够学到一些有用的知识,但未能重建整个图。这部分是因为维基百科没有包含我们设想的处理—Mehendretex 的信息。

让我们快速查看一下我们模型中涉及 Mehendretex 的一个日志:

{'input': 'Does risk_of_death cause mehendretex or the other way around?    
We assume the following definition of causation:    
if we change A, B will also change.    
The relationship does not have to be linear or monotonic.    
We are interested in all types of causal relationships, including    
partial and indirect relationships, given that our definition holds.', 
'output': 'I\'m sorry, but I couldn\'t 
find any information on "mehendretex". 
Therefore, it\'s impossible to determine a causal relationship 
between "risk_of_death" and "mehendretex". 
Could you please provide more context or 
check if "mehendretex" is spelled correctly?'}
(-1, -1)

似乎模型正确回应了包含不在模型使用的语料库中的术语的查询(见上框中的粗体文本)。

我们现在准备将收集到的知识传递给因果发现算法:

print('\nRunning PC')
# Instantiate the model with expert knowledge
pc_priori = PC(
    priori_knowledge=priori_knowledge,
    variant='stable'
)

# Learn
pc_priori.learn(dataset)

GraphDAG(
    est_dag=pc_priori.causal_matrix, 
    true_dag=true_dag)

plt.show()

# Compute metrics
metrics = MetricsDAG(
    B_est=pc_priori.causal_matrix, 
    B_true=true_dag)

print(metrics.metrics)

请注意,我们正在初始化 PC 算法的新实例,并将包含 LLM 收集到的知识的priori_knowledge对象传递给构造函数。

PC 现在将重新计算所有条件独立性测试的结果,但在确定边的方向时还会考虑先验知识。

准备好查看结果了吗?

图 6 显示了我们 RAG-LLM 诱导的 PC 算法的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6. 使用基于 RAG-LLM 的先验知识的 PC 算法结果(左)与真实情况(右)。

这看起来非常好!

只有一条边保持无向:索引为02(海拔和温度)的节点之间的边,所有其他边都被正确定向。

该输出的 F1 分数为 0.93,比没有 LLM 帮助的 PC 提高了 40 个百分点。

总结结果

通过将传统的因果发现方法与 RAG-LLM 结合,我们能够获得比任何单一方法更好的结果。

图 7 总结了每个步骤的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 7 模型输出在每个阶段的邻接矩阵(左列)与真实值(右列)以及相应的 F1 分数值。

摘要

使用大型语言模型作为“知识解析器”,将文本信息转换为与传统因果发现算法兼容的格式,并扩展后者的能力,这一想法非常强大。

限制

我们在这篇博客文章中讨论的系统主要限制之一是 LLM 输出的不稳定性。我运行了大约 20 次这里呈现的过程,LLM 的决策在不同运行间并不总是保持一致(即使温度设置为零)。

PC 算法有一套理论保证,但这些保证仅适用于无限样本量,这在实际中当然是不可能的。此外,我们在这篇文章中使用的默认条件独立性测试并不十分强大,可能在更复杂的情况下表现不佳。

PC 算法要求数据中没有隐藏的混淆——这是一个不总是可以保证的条件,特别是在开放复杂系统中。

对于较大的数据集,运行 RAG-LLM 部分可能会迅速变得昂贵,因为系统可能在幕后对每对变量进行多个 API 调用。如果你决定为自己的用例运行代码,要注意潜在的高 API 费用

我的建议是在你的 OpenAI 账户中设定一个限制,在开始实验之前。

进一步改进

自然地,这样的系统不必局限于维基百科作为外部知识来源,任何文档语料库都可以使用。

一些大型语言模型(LLM)输出的不稳定性可以通过创建更好的提示来潜在解决。

通过使用更通用的基于核的方法进行条件独立性测试,PC 算法的性能可以得到改善。如果数据适合,其他因果发现算法也可以替代 PC。

尤其是当无法排除隐藏混淆时,像快速因果发现FCI)这样的算法可能是更好的选择。

对每对可能的变量进行 LLM 查询不可扩展,对于较大的数据集可能非常昂贵。一个潜在的预选择机制可以基于常规 LLM,评估两个变量是否有因果连接的可能性。在单个查询中结合多个变量可能有助于降低成本。

这个解决方案的缺点是它伴随着更高的幻觉风险,但在处理常见变量和关系时可能是有效的。

结论

Jane the Discoverer 是一个概念验证的想法。我认为它是一个因果助手,适用于小型到中型图。

出于教育目的,博客中的代码是以建议自主使用的方式编写的,但我发现人机交互范式更为有效。

恭喜你阅读完毕!

我们如何进一步改进 Jane?

在下面的评论区分享你的想法和建议,保持因果联系!

如果你喜欢这篇文章,你可能也会喜欢:

🔸 因果 Bandits 播客

🔸因果 Python 每周通讯

笔记本与本文的完整代码可以在 GitHub 上找到:

[## blogs-code/Jane the Discoverer at main · AlxndrMlk/blogs-code

博客文章的代码。通过在 GitHub 上创建帐户来贡献 AlxndrMlk/blogs-code 的开发。

github.com](https://github.com/AlxndrMlk/blogs-code/tree/main/Jane%20the%20Discoverer?source=post_page-----564a63425c93--------------------------------)

脚注

¹ 我们可以争辩说,在出生的那一天,我们实际上对世界有所了解,因为许多研究表明胎儿可以学习和习惯(例如,James 等,2022)

² 联想学习在各个物种中普遍存在。即使在像C. elegans这样的简单生物中也有出现。注意,在本文中我们使用术语联想学习来描述任何基于关联的学习,这不包括系统干预。这一定义可能与心理学和/或生物学中使用的一些定义不一致。

³ 我们可以在这里讨论在联想学习背景下动物与监督机器学习算法之间的实现差异及潜在的样本效率差异。附带说明,我认为样本效率的话题非常有趣。同时,我相信相关的答案比“一个孩子可以通过两个例子了解什么是狗,而 CNN 或 Transformer 架构需要数百万个例子”这样的流行说法要复杂得多。

⁴ 这也包括自监督学习和一些强化学习模型。

⁵ 通过使用更先进或更通用的识别方法,我们可以将其扩展到部分有向或循环图。

⁶ 因果发现是一系列方法,旨在从观察性、干预性或混合数据中恢复数据生成过程的结构。

为生产线建立一个文档完善且实际隔离于外部影响的图形通常比为公共政策优化建立图形要容易一些。在这两种极端情况之间还有许多其他情况。

⁸ 也就是说,如果我们可以在测试时进行干预,LLMs 可以学习主动(因果)策略。有关详细信息,请参见 Lampinen 等(2023)

⁹ 我们使用合成测量,以确保我们知道真实情况。

¹⁰ F1 分数不一定是比较图形结构时最具信息量的度量指标。然而,我决定在这里使用它,因为它是数据科学社区中相对被理解且非常流行的度量指标。我希望使用它(而不是像 SHD 这样的更专业的度量指标)能使这篇文章对更广泛的受众更具可及性。

参考文献

Gopnik, A. (2009). 哲学婴儿:儿童的思维告诉我们关于真理、爱和生活意义的东西. 纽约:Farrar, Straus and Giroux.

Kıcıman, E., Ness, R., Sharma, A., & Tan, C. (2023). 因果推理与大型语言模型:为因果关系开辟新领域。 ArXiv.

Lampinen, A. K., Chan, S. C. Y., Dasgupta, I., Nam, A. J., & Wang, J. X. (2023). 代理和语言模型中的主动因果策略的被动学习. ArXiv.

Molak, A. (2023). Python 中的因果推断与发现:利用 DoWhy、EconML、PyTorch 等解锁现代因果机器学习的秘密. Packt Publishing.

Stanovich, K., & West, R. (2000). 个人推理差异:对理性辩论的影响?行为与脑科学, 23(5), 645–665. doi:10.1017/S0140525X00003435

Zečević, M., Willig, M., Dhami, D. S., & Kersting, K. (2023). 因果鹦鹉:大型语言模型可能谈论因果关系但不具备因果能力. ArXiv.

这篇文章可能包含书籍的联盟链接。如果你通过这些链接购买书籍,作者将获得少量佣金。

一月刊:成为更好的学习者

原文:towardsdatascience.com/january-edition-becoming-better-learners-a595626554d6?source=collection_archive---------23-----------------------#2023-01-04

月刊

让你的 2023 年数据科学之旅从一开始就顺利起步

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 TDS Editors

·

关注 发表在 Towards Data Science · 4 min read · 2023 年 1 月 4 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源:little plantUnsplash

每年年初看到如此多的人加入我们的社区总是令人兴奋,无论是第一次加入还是经过长时间的 hiatus。欢迎!(或欢迎回来!)

对我们 TDS 团队来说,参与您的学习之旅是一种极大的荣幸。在 2023 年初,我们希望帮助每个人——无论您是新年决心者还是循序渐进的习惯养成者——以正确的步伐开始今年的旅程。

为此,我们汇总了一些关于学习和提升数据科学及相关技能的优质文章。我们的许多精选内容面向全新或有意从事数据工作的专业人士,但更有经验的专家也会找到许多宝贵的见解和实用技巧。

在我们让您继续阅读之前,最后提一点:如果您希望支持我们作者在 2023 年及以后继续创作,请考虑成为 Medium 会员

新年快乐——我们期待一起探索和学习!

TDS 编辑

TDS 编辑精选

  • 2023 年指导自学数据科学家的每日、每周、每月和每年目标建议(2022 年 12 月,11 分钟)

    一个好的计划是实现学习目标的关键,Madison Hunter提供了一个既雄心勃勃又可持续的详细路线图来帮助您。

  • 如何作为高中生探索机器学习和自然语言处理(2022 年 7 月,12 分钟)

    Carolyn Wang的这份有用指南可能围绕她作为高中生的个人经历展开,但对于各年龄层的有志从业者来说,它是机器学习和自然语言处理的有益入门。

  • 数据科学初学者需要知道的简单事项(2022 年 12 月,11 分钟)

    Ken Jee最近的资源是一个便于理解的最新数据科学入门指南,适合今年初涉数据科学的任何人。

  • 我是一名自学成才的数据科学家。以下是我对新手的 3 点建议(2022 年 4 月,5 分钟)

    对于所有选择不遵循既定课程的独立学习者,Soner Yıldırım基于他作为自学数据专业人士的经验,提供了一些关键见解。

  • 神经网络简要介绍:回归问题(2022 年 12 月,12 分钟)

    你如何从头开始学习一个复杂的技术主题?Chayma Zatout 最近的文章提供了一个强大的蓝图(这也是任何新手学习神经网络的绝佳起点)。

  • 有抱负的数据科学家如何找到并参与现实世界项目?(2022 年 12 月,7 分钟)

    在完成了一些在线课程和阅读了几本书(以及 TDS 文章!)之后,你可能会问自己:接下来怎么办?Arunn Thevapalan 提出了一些将理论知识转化为实际项目的想法。

  • 如何在初学者阶段开始你的第一个数据科学项目(2022 年 6 月,6 分钟)

    从不同的角度解决类似问题,Rashi Desai 提出了一个五步流程,帮助你从新手成长为自信的实验者和探索者。

  • 没有计算机科学学位的任何数据科学初学者最佳编程技巧(2022 年 5 月,7 分钟)

    人们从各种背景过渡到数据科学职业;如果你的过去职业生涯中没有扎实的计算机科学基础,Hennie de Harder 分享了一些有用的技巧,帮助你编写高效、干净的代码。

  • 如果可以重新开始,我会如何学习数据科学(四年后)(2022 年 11 月,7 分钟)

    数据科学领域在过去几年中发展迅速,进入这个领域的方法也在不断变化。Terence Shin 现在已经是一名四年的资深人士,他的深思熟虑的文章反映了他对作为初学者掌握基础知识最佳方法的最新思考。

原始特性

我们最新的问答选择和阅读推荐。

  • “在任何行业中产生影响,领域知识至关重要。” 不要错过我们最新的作者专访:我们与 Abiodun Olaoye 进行了交谈,他讨论了自己在数据科学和可再生能源交汇处的职业路径。

  • 探索我们最佳深度分析的季节。为了您的阅读乐趣,我们汇总了一些近期最优秀的长篇文章。

热门文章

如果您错过了,以下是上个月 TDS 上一些最受欢迎的帖子。

  • 我使用 ChatGPT 在 AWS 上创建了一个完整的 AI 应用程序 作者:Heiko Hotz

  • 5 个 Python 库助你开启数据科学职业生涯 作者:Federico Trotta

  • 使用 Bokeh 在 Python 中创建数据可视化的 8 个技巧 作者:Payal Patel

  • 每位数据科学家都应该知道的数学优化启发式方法 作者:Hennie de Harder

  • OpenAI 的 ChatGPT 是世界上最好的聊天机器人 作者:Alberto Romero

  • 数据科学家的日常 作者:Leah Berg 和 Ray McLendon

我们很高兴在 12 月迎来了最后一批新作者;请加入我们,欢迎 Mohamad AboufoulPaul KnulstClement WangMichael ParkesDaniel JustusPhilip TsangJoel HodgsonDonato RiccioJake SchmidtSalehaRyan KearnsThomas EllyattAdam KohanMaybritt SchillingerSekhar MShreshth SharmaHajime TakedaQitian WuPatrick HoeflerOmar AlkousaArliJonte DanckerAvraGosia KomorSairam Sundaresan等。 如果你有有趣的项目或想法想要与我们分享,我们期待你的来信!

直到下个月…

Java 和数据工程

原文:towardsdatascience.com/java-and-data-engineering-f0e0a145cb52

数据工程

Java Juggernaut: 数据工程掌握的关键

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Naser Tamimi

·发布于 Towards Data Science ·阅读时间 4 分钟·2023 年 11 月 11 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 Zhen H 提供,来源于 Unsplash

数据工程和编程技能

当我们想到数据工程时,首先想到的编程技能通常是 SQL 和 Python。SQL 是一种用于查询数据的著名语言,深深根植于数据和管道的世界。另一方面,Python 在数据科学中变得相当强大,并且现在在不断发展的数据工程领域中也开始展现其影响力。但是,这种普遍的看法是否准确?SQL 和 Python 真的就是数据工程师最重要的编程技能吗?在本文中,我将分享我在这一领域的经验,旨在帮助年轻专业人士找出最佳技能,以充分利用他们的时间和精力。

为什么选择 Java 和 Scala?

在今天的数据工程中,我们处理大量的数据。主要的工作是搞清楚如何每天、每小时甚至实时地收集、改变和存储这些海量数据。更棘手的是,要确保不同的数据服务能够在各种系统上顺利运行,而不用担心底层发生了什么。

在过去的 15 年里,聪明的人们提出了分布式计算框架来应对数据过载问题。Hadoop 和 Spark 是这个领域的两个大名鼎鼎的名字。由于这两个框架主要使用 JVM(Java 虚拟机)语言构建(Hadoop 使用 Java,而 Spark 使用 Scala),许多数据和软件专家认为 Java 和 Scala 是数据工程的未来方向。

此外,JVM 应用程序的可移植性使它们成为跨各种系统和环境操作的数据应用的优秀选择。你可以开发无缝运行于各种云端和本地环境的数据管道,从而可以在不担心底层基础设施的情况下,按需扩展系统。

在基于 JVM 的应用程序中,数据管道是什么样的?

现在我们已经探讨了 Java 和 Scala,或者更广泛地说,基于 JVM 的数据应用在处理大数据方面的好处,接下来的逻辑问题是:这些应用程序,或者简单的数据管道,看起来是什么样的?本节旨在概述这些应用程序的架构。

首先,必须在 Java 或 Scala 中开发一个数据管道。通常,多个相关的数据管道可以共存于同一个 Java 或 Scala 项目中。为了有效的项目管理,可以使用像Apache Maven这样的工具。Maven 简化了 Java 应用程序的创建、管理和构建过程,使这一过程更加高效和可靠。

在这些项目中,数据管道通常包括一个或多个 Java 或 Scala 类。Spark 通常集成到这些类中,用于读取(或提取)、转换和写入(或加载)数据。尽管数据可以从各种来源读取和写入,但 Hive 表通常是自然选择。标准转换被封装在常用类中,使其可以在不同管道之间重用。

这段代码展示了一个基本的 Spark Scala 数据管道。

import org.apache.spark.sql.{SparkSession, DataFrame}
import org.apache.spark.sql.functions._

class MyExampleDataPipeline {

  val spark: SparkSession = SparkSession.builder
    .appName("DataFrame Transformation Example")
    .master("local[*]")
    .enableHiveSupport()
    .getOrCreate()

  def main(inputTableName: String, outputTableName: String): Unit = {
    val inputDataFrame: DataFrame = spark.table(inputTableName)

    val transformedDataFrame: DataFrame = inputDataFrame.SOME_TRANSFORMATIONS

    transformedDataFrame.write.mode("overwrite").format("parquet").saveAsTable(outputTableName)
  }
}

最终,目标是构建一个通常以 jar 文件形式存在的 Java 应用程序。这个 jar 文件以及适当的参数,可以通过像Apache Airflow这样的作业和工作流管理系统进行调用。这使得在计划的时间间隔执行特定的数据管道成为可能,从而促进有序和自动化的数据处理工作流。

这是一个简单的示例,展示如何从命令行执行一个作为类表示的数据管道。

spark-submit --class MyExampleDataPipeline --master local[*] yourJarFile.jar your_input_hive_table_name your_output_hive_table_name

更高级的实践

如前所述,数据管道现在被封装在 jar 文件中,通常需要定时执行,特别是用于批处理,或者基于触发事件激活,通常用于实时处理。Apache Airflow 作为一个强大的解决方案,用于编排这些 jar 文件和任务类,促进定期执行作业。或者,可以使用像 AWS Lambda 这样的工具触发 jar 文件,以应对不规则的时间表和实时处理。

这是一个 Apache Airflow DAG 的示例,旨在每天执行一个 Java 类。

from datetime import datetime, timedelta
from airflow import DAG
from airflow.operators.bash_operator import BashOperator
from airflow.operators.dummy_operator import DummyOperator

jar_file_path = "/path/to/yourJarFile.jar"

input_table_name = "your_input_hive_table_name"
output_table_name = "your_output_hive_table_name"

default_args = {
    'owner': 'airflow',
    'start_date': datetime(2023, 1, 1),
    'depends_on_past': False,
    'retries': 1,
    'retry_delay': timedelta(minutes=5),
}

dag = DAG(
    'my_data_pipeline_dag',
    default_args=default_args,
    description='DAG to run the MyExampleDataPipeline class',
    schedule_interval='@daily',  # Adjust the schedule as needed
)

start_task = DummyOperator(task_id='start', dag=dag)
end_task = DummyOperator(task_id='end', dag=dag)

spark_submit_command = f"spark-submit --class MyExampleDataPipeline --master local[*] {jar_file_path} {input_table_name} {output_table_name}"

run_data_pipeline_task = BashOperator(
    task_id='run_data_pipeline',
    bash_command=spark_submit_command,
    dag=dag,
)

start_task >> run_data_pipeline_task >> end_task

此外,持续集成/持续部署(CI/CD)工具,包括 Jenkins、GitHub Actions、Spinnaker 等,提供了一种无缝的方式来开发和部署跨各种环境的数据管道,从开发到测试和生产环境。这确保了管道在整个开发生命周期中的平滑和自动化过渡。

在最后…

我们探索了数据工程不断变化的格局以及该领域所需的基本编程技能。虽然 SQL 和 Python 传统上与数据工程相关,但重点正转向 Java 和 Scala,特别是在通过像 Hadoop 和 Spark 这样的分布式计算框架处理大量数据的背景下。

我们再次强调了 JVM(Java 虚拟机)语言的重要性,因为它们的可移植性使其适合开发可以在各种系统和环境中无缝运行的数据应用程序。它深入探讨了基于 JVM 的数据应用程序的架构,展示了如何使用 Java 或 Scala 开发数据管道,同时 Apache Maven 帮助项目管理。

爵士和弦解析与变压器

原文:towardsdatascience.com/jazz-chords-parsing-with-transformers-d75031a976f2

基于数据的树形音乐分析方法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Francesco Foscarin

·发布在 Towards Data Science ·阅读时间 11 分钟·2023 年 8 月 1 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这篇文章中,我总结了我的研究论文“使用基于图的神经解码器预测音乐层级”的一部分,该论文提出了一种能够解析爵士和弦序列的数据驱动系统。

这项研究是由于我对基于语法的解析系统感到挫败而激发的(这些系统是处理音乐数据的唯一选择):

  • 语法构建阶段需要大量的领域知识

  • 解析器在面对一些未见过的配置或嘈杂数据时会失败

  • 在单一语法规则中考虑多个音乐维度是具有挑战性的。

  • 目前没有一个得到广泛支持的活跃 Python 框架来帮助开发

我的方法(受自然语言处理领域类似工作的启发),不依赖于任何语法对嘈杂输入产生部分结果可以轻松处理多个音乐维度,并且在 PyTorch 中实现

如果你不熟悉解析和语法,或者只是需要刷新你的知识,我现在会退后一步。

什么是“解析”?

解析一词指的是预测/推断出一棵树(数学结构),其叶子节点是序列中的元素。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

好吧,但我们为什么需要树呢?

让我们从以下的爵士和弦序列(《Take the A Train》的 A 部分)开始。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在爵士音乐中,和弦通过复杂的感知关系系统连接。例如,Dm7 是对主和弦 G7 的预备。这意味着 Dm7 的作用不如 G7 重要,它可以在不同的和声重构中被省略。类似地,D7 是一个次级主和弦(主和弦的主和弦),也指向 G7。

这种和声关系可以用树来表达,并且对音乐分析或执行诸如再和声等任务非常有用。然而,由于音乐作品中的和弦大多以序列形式存在,我们希望一个能够自动构建这种树结构的系统

组成树与依赖树

在继续之前,我们需要区分两种类型的树。

音乐学家倾向于使用所谓的组成树,你可以在下面的图片中看到。组成树包含叶子(蓝色和弦——输入序列的元素),以及内部节点(橙色和弦——子叶的简化)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

而在这项工作中,我们考虑另一种树,称为依赖树。这种树没有内部节点,只有连接序列元素的有向弧。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以通过一些稍后讨论的算法从组成树生成依赖树。

数据集

由于这是数据驱动的方法,我们需要一个和弦序列的数据集(输入数据),以及一个树的数据集(真实标签)用于训练和测试。我们使用的是 Jazz Treebank¹,公开在这个 GitHub 仓库中(它可以自由用于非商业应用,并且我获得了作者的许可在本文中使用它)。特别是,他们提供了一个包含所有和弦和注释的 JSON 文件。

我们将输入到系统中的每个和弦建模为三个特征:

  1. 根节点,是[0…11]中的一个整数,其中 C -> 0,C# ->1,依此类推……

  2. 基本形式是[0…5]中的一个整数,用于选择大调、小调、增和弦、半减和弦、减和弦和挂留和弦(sus)。

  3. 扩展,是[0,1,2]中的一个整数,用于选择 6、次要 7 或大调 7。

要从和弦标签(一个字符串)中生成和弦特征,我们可以使用如下的正则表达式(注意,这段代码适用于这个数据集,因为在其他和弦数据集中格式可能会有所不同)。

def parse_chord_label(chord_label):
  # Define a regex pattern for chord symbols
  pattern = r"([A-G][#b]?)(m|\+|%|o|sus)?(6|7|\⁷)?"
  # Match the pattern with the input chord
  match = re.match(pattern, chord_label)
  if match:
    # Extract the root, basic chord form and extension from the match obj
    root = match.group(1)
    form = match.group(2) or "M"
    ext = match.group(3) or ""
    return root, form, ext
  else:
    # Return None if the input is not a valid chord symbol
    raise ValueError("Invalid chord symbol: {}".format(chord_label))

最后,我们需要生成依赖树。JHT 数据集只包含组成树,以嵌套字典形式编码。我们导入它们,并通过递归函数将它们转换为依赖树。我们函数的机制可以描述如下。

我们从一个完全形成的组成树和一个没有任何依赖弧的依赖树开始,这个依赖树仅由标记为序列元素的节点组成。算法将所有内部树节点与其主要子节点(它们都具有相同的标签)进行分组,并使用每个组中所有次要子节点关系来创建组标签和次要子节点标签之间的依赖弧。

def parse_jht_to_dep_tree(jht_dict):
    """Parse the python jazz harmony tree dict to a list of dependencies and a list of chord in the leaves.
    """
    all_leaves = []

    def _iterative_parse_jht(dict_elem):
        """Iterative function to parse the python jazz harmony tree dict to a list of dependencies."""
        children = dict_elem["children"]
        if children == []:  # recursion ending condition
            out = (
                [],
                {"index": len(all_leaves), "label": dict_elem["label"]},
            )
            # add the label of the current node to the global list of leaves
            all_leaves.append(dict_elem["label"])
            return out
        else:  # recursive call
            assert len(children) == 2 
            current_label = noast(dict_elem["label"])
            out_list = []  # dependency list
            iterative_result_left = _iterative_parse_jht(children[0])
            iterative_result_right = _iterative_parse_jht(children[1])
            # merge the dependencies lists computed deeper
            out_list.extend(iterative_result_left[0])
            out_list.extend(iterative_result_right[0])
            # check if the label correspond to the left or right children and return the corresponding result
            if iterative_result_right[1]["label"] == current_label: # default if both children are equal is to go left-right arch
                # append the dependency for the current node
                out_list.append((iterative_result_right[1]["index"], iterative_result_left[1]["index"]))
                return out_list, iterative_result_right[1]
            elif iterative_result_left[1]["label"] == current_label: 
                # print("right-left arc on label", current_label)
                # append the dependency for the current node
                out_list.append((iterative_result_left[1]["index"], iterative_result_right[1]["index"]))
                return out_list, iterative_result_left[1]
            else:
                raise ValueError("Something went wrong with label", current_label)

    dep_arcs, root = _iterative_parse_jht(jht_dict)
    dep_arcs.append((-1,root["index"])) # add connection to the root, with index -1
    # add self loop to the root
    dep_arcs.append((-1,-1)) # add loop connection to the root, with index -1
    return dep_arcs, all_leaves

依赖解析模型

我们的解析模型的工作机制相当简单:我们考虑所有可能的弧,并使用弧预测器(一个简单的二分类器)来预测该弧是否应该成为树的一部分。

然而,仅根据我们试图连接的两个和弦来做出这个选择是相当困难的。我们需要一些上下文。我们通过构建一个变换器编码器来提供这样的上下文。

总结而言,我们的解析模型分为两个步骤:

  1. 输入序列通过变换器编码器传递,以丰富其上下文信息;

  2. 一个二分类器评估所有可能的依赖弧图,以过滤掉不需要的弧。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

变换器编码器遵循标准架构。我们使用可学习的嵌入层将每个分类输入特征映射到一个连续的多维空间中的点。然后将所有嵌入加在一起,因此网络可以“决定”每个特征使用的维度。

import torch.nn as nn

class TransformerEncoder(nn.Module):
    def __init__(
        self,
        input_dim,
        hidden_dim,
        encoder_depth,
        n_heads = 4,
        dropout=0,
        embedding_dim = 8,
        activation = "gelu",
    ):
        super().__init__()
        self.input_dim = input_dim
        self.positional_encoder = PositionalEncoding(
            d_model=input_dim, dropout=dropout, max_len=200
        )
        encoder_layer = nn.TransformerEncoderLayer(d_model=input_dim, dim_feedforward=hidden_dim, nhead=n_heads, dropout =dropout, activation=activation)
        encoder_norm = nn.LayerNorm(input_dim)
        self.transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=encoder_depth, norm=encoder_norm)
        self.embeddings = nn.ModuleDict({
                        "root": nn.Embedding(12, embedding_dim),
                        "form": nn.Embedding(len(CHORD_FORM), embedding_dim),
                        "ext": nn.Embedding(len(CHORD_EXTENSION), embedding_dim),
                        "duration": nn.Embedding(len(JTB_DURATION), embedding_dim,
                        "metrical": nn.Embedding(METRICAL_LEVELS, embedding_dim)
                    })

    def forward(self, sequence):
        root = sequence[:,0]
        form = sequence[:,1]
        ext = sequence[:,2]
        duration = sequence[:,3]
        metrical = sequence[:,4]
        # transform categorical features to embedding
        root = self.embeddings"root")
        form = self.embeddings"form")
        ext = self.embeddings"ext")
        duration = self.embeddings"duration")
        metrical = self.embeddings"metrical")
        # sum all embeddings
        z = root + form + ext + duration + metrical
        # add positional encoding
        z = self.positional_encoder(z)
        # reshape to (seq_len, batch = 1, input_dim)
        z = torch.unsqueeze(z,dim= 1)
        # run transformer encoder
        z = self.transformer_encoder(src=z, mask=src_mask)
        # remove batch dim
        z = torch.squeeze(z, dim=1)
        return z, ""

class PositionalEncoding(nn.Module):
    def __init__(self, d_model: int, dropout: float = 0.1, max_len: int = 500):
        super().__init__()
        self.dropout = nn.Dropout(p=dropout)
        position = torch.arange(max_len).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, d_model, 2) * (-np.log(10000.0) / d_model))
        pe = torch.zeros(max_len, d_model)
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        self.register_buffer('pe', pe)
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = x + self.pe[:x.size(0)]
        return self.dropout(x)

弧预测器只是一个线性层,它的输入是两个和弦的隐藏特征的连接。由于矩阵乘法的强大功能,所有弧的分类步骤都是并行完成的。

class ArcPredictor(nn.Module):
    def __init__(self, hidden_channels, activation=F.gelu, dropout=0.3):
        super().__init__()
        self.activation = activation
        self.root_linear = nn.Linear(1, hidden_channels) # linear to produce root features
        self.lin1 = nn.Linear(2*hidden_channels, hidden_channels)
        self.lin2 = nn.Linear(hidden_channels, 1)
        self.dropout = nn.Dropout(dropout)
        self.norm = nn.LayerNorm(hidden_channels)

    def forward(self, z, pot_arcs):
        # add column for the root element
        root_feat = self.root_linear(torch.ones((1,1), device=z.device))
        z = torch.vstack((root_feat,z))
        # proceed with the computation
        z = self.norm(z)
        # concat the embeddings of the two nodes, shape (num_pot_arcs, 2*hidden_channels)
        z = torch.cat([z[pot_arcs[:, 0]], z[pot_arcs[:, 1]]], dim=-1)
        # pass through a linear layer, shape (num_pot_arcs, hidden_channels)
        z = self.lin1(z)
        # pass through activation, shape (num_pot_arcs, hidden_channels)
        z = self.activation(z)
        # normalize
        z = self.norm(z)
        # dropout
        z = self.dropout(z)
        # pass through another linear layer, shape (num_pot_arcs, 1)
        z = self.lin2(z)
        # return a vector of shape (num_pot_arcs,)
        return z.view(-1)

我们可以将变换器编码器和弧预测器放在一个单独的 PyTorch 模块中,以简化其使用。

class ChordParser(nn.Module):
    def __init__(self, input_dim, hidden_dim, num_layers, dropout=0.2, embedding_dim = 8, use_embedding = True, n_heads = 4):
        super().__init__()
        self.activation = nn.functional.gelu
        # initialize the encoder
        self.encoder = NotesEncoder(input_dim, hidden_dim, num_layers, dropout, embedding_dim, n_heads=n_heads)
        # initialize the decoder
        self.decoder = ArcDecoder(input_dim, dropout=dropout)

    def forward(self, note_features, pot_arcs, mask=None):
        z = self.encoder(note_features)
        return self.decoder(z, pot_arcs)

损失函数

作为损失函数,我们使用两个损失的和:

  • 二元交叉熵损失:这个思想是将我们的问题视为一个二分类问题,每个弧都可以被预测或不被预测。

  • 交叉熵损失:这个思想是将我们的问题视为一个多分类问题,在一个头 → 依赖弧中,我们需要预测所有其他和弦中哪个是正确的依赖。

loss_bce = torch.nn.BCEWithLogitsLoss()
loss_ce = torch.nn.CrossEntropyLoss(ignore_index=-1)
total_loss = loss_bce + loss_ce

后处理

有一个问题我们仍然需要解决。在训练过程中,并没有强制要求预测的弧必须形成树结构。因此,我们可能会遇到像弧循环这样的无效配置。幸运的是,我们可以使用一种算法来确保这种情况不会发生:Eisner 算法。²

我们不再只是简单地假设弧存在的概率大于 0.5,而是将所有预测结果保存在一个大小为(和弦数,和弦数)的方形矩阵(邻接矩阵)中,然后在该矩阵上运行 Eisner 算法。

# Adapted from https://github.com/HMJW/biaffine-parser
def eisner(scores, return_probs = False):
    """Parse using Eisner's algorithm.
    The matrix follows the following convention:
        scores[i][j] = p(i=head, j=dep) = p(i --> j)
    """
    rows, collumns = scores.shape
    assert rows == collumns, 'scores matrix must be square'
    num_words = rows - 1  # Number of words (excluding root).
    # Initialize CKY table.
    complete = np.zeros([num_words+1, num_words+1, 2])  # s, t, direction (right=1).
    incomplete = np.zeros([num_words+1, num_words+1, 2])  # s, t, direction (right=1).
    complete_backtrack = -np.ones([num_words+1, num_words+1, 2], dtype=int)  # s, t, direction (right=1).
    incomplete_backtrack = -np.ones([num_words+1, num_words+1, 2], dtype=int)  # s, t, direction (right=1).
    incomplete[0, :, 0] -= np.inf
    # Loop from smaller items to larger items.
    for k in range(1, num_words+1):
        for s in range(num_words-k+1):
            t = s + k
            # First, create incomplete items.
            # left tree
            incomplete_vals0 = complete[s, s:t, 1] + complete[(s+1):(t+1), t, 0] + scores[t, s]
            incomplete[s, t, 0] = np.max(incomplete_vals0)
            incomplete_backtrack[s, t, 0] = s + np.argmax(incomplete_vals0)
            # right tree
            incomplete_vals1 = complete[s, s:t, 1] + complete[(s+1):(t+1), t, 0] + scores[s, t]
            incomplete[s, t, 1] = np.max(incomplete_vals1)
            incomplete_backtrack[s, t, 1] = s + np.argmax(incomplete_vals1)
            # Second, create complete items.
            # left tree
            complete_vals0 = complete[s, s:t, 0] + incomplete[s:t, t, 0]
            complete[s, t, 0] = np.max(complete_vals0)
            complete_backtrack[s, t, 0] = s + np.argmax(complete_vals0)
            # right tree
            complete_vals1 = incomplete[s, (s+1):(t+1), 1] + complete[(s+1):(t+1), t, 1]
            complete[s, t, 1] = np.max(complete_vals1)
            complete_backtrack[s, t, 1] = s + 1 + np.argmax(complete_vals1)
    value = complete[0][num_words][1]
    heads = -np.ones(num_words + 1, dtype=int)
    backtrack_eisner(incomplete_backtrack, complete_backtrack, 0, num_words, 1, 1, heads)
    value_proj = 0.0
    for m in range(1, num_words+1):
        h = heads[m]
        value_proj += scores[h, m]
    if return_probs:
        return heads, value_proj
    else:
        return heads

def backtrack_eisner(incomplete_backtrack, complete_backtrack, s, t, direction, complete, heads):
    """
    Backtracking step in Eisner's algorithm.
    - incomplete_backtrack is a (NW+1)-by-(NW+1) numpy array indexed by a start position,
    an end position, and a direction flag (0 means left, 1 means right). This array contains
    the arg-maxes of each step in the Eisner algorithm when building *incomplete* spans.
    - complete_backtrack is a (NW+1)-by-(NW+1) numpy array indexed by a start position,
    an end position, and a direction flag (0 means left, 1 means right). This array contains
    the arg-maxes of each step in the Eisner algorithm when building *complete* spans.
    - s is the current start of the span
    - t is the current end of the span
    - direction is 0 (left attachment) or 1 (right attachment)
    - complete is 1 if the current span is complete, and 0 otherwise
    - heads is a (NW+1)-sized numpy array of integers which is a placeholder for storing the
    head of each word.
    """
    if s == t:
        return
    if complete:
        r = complete_backtrack[s][t][direction]
        if direction == 0:
            backtrack_eisner(incomplete_backtrack, complete_backtrack, s, r, 0, 1, heads)
            backtrack_eisner(incomplete_backtrack, complete_backtrack, r, t, 0, 0, heads)
            return
        else:
            backtrack_eisner(incomplete_backtrack, complete_backtrack, s, r, 1, 0, heads)
            backtrack_eisner(incomplete_backtrack, complete_backtrack, r, t, 1, 1, heads)
            return
    else:
        r = incomplete_backtrack[s][t][direction]
        if direction == 0:
            heads[s] = t
            backtrack_eisner(incomplete_backtrack, complete_backtrack, s, r, 1, 1, heads)
            backtrack_eisner(incomplete_backtrack, complete_backtrack, r+1, t, 0, 1, heads)
            return
        else:
            heads[t] = s
            backtrack_eisner(incomplete_backtrack, complete_backtrack, s, r, 1, 1, heads)
            backtrack_eisner(incomplete_backtrack, complete_backtrack, r+1, t, 0, 1, heads)
            return

结论

我提出了一种用于和弦序列依赖解析的系统,该系统使用变换器构建上下文和弦隐藏表示,并使用分类器来选择两个和弦是否应该通过弧连接。

相较于竞争系统,主要的优点在于该方法不依赖于任何特定的符号语法,因此它可以同时考虑多种音乐特征,利用序列上下文信息,并对噪声输入产生部分结果。

为了保持文章的适当长度,解释和代码都集中在系统中最有趣的部分。你可以在这篇科学文章中找到更完整的解释,所有代码都可以在这个 GitHub 仓库中找到。

(所有图片均由作者提供。)

参考文献

  1. D. Harasim, C. Finkensiep, P. Ericson, T. J. O’Donnell, 和 M. Rohrmeier,“爵士和声树库”,载于国际音乐信息检索会议(ISMIR)论文集,2020 年,第 207–215 页。

  2. J. M. Eisner,“依存解析的三种新概率模型:一种探索”,载于国际计算语言学大会(COLING)论文集,1996 年。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值