TowardsDataScience 2023 博客中文翻译(三百三十七)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

两篇新论文详细分析了 AlphaFold 2 的 2 亿个模型揭示的蛋白质宇宙

原文:towardsdatascience.com/two-new-papers-analyze-in-detail-the-protein-universe-unveiled-by-alphafold-2s-200-million-models-bf5bd55e754a

他们不得不创建新的工具来处理如此大规模的蛋白质结构模型

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

·发表于Towards Data Science ·阅读时间 7 分钟·2023 年 9 月 21 日

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

在讨论的文章中展示的资源之一,uniprot3d.org/,描绘了一个现代化的宇宙视图,其中较亮的簇包含更多的成员。用户可以缩放查看相关蛋白质,点击特定节点会显示有关 Uniprot 中蛋白质家族的信息(此处仅显示结构模型)。图片由作者在浏览网站时制作。

DeepMind 的 AlphaFold 2 与欧洲生物信息学研究所合作发布了超过 2 亿个预测蛋白质结构,标志着蛋白质研究进入了一个新时代。在这里,我总结了本周在Nature上发表的两篇开创性论文的发现,这些论文深入探讨了这一蛋白质宇宙的深度。这些论文采用了创新的聚类算法、结构比较和现有工具的其他适配方法,以处理大数据量,从而揭示了前所未有的蛋白质结构多样性、进化关系和功能潜力。

蛋白质是生物学中的“工作马”,支配着从能量生成到细胞分裂的无数细胞过程。尽管随着基因组学的发展,蛋白质测序在近年来迅速增长,但由于缺乏可扩展的实验方法,它们的 3D 结构确定却滞后。然而,随着 DeepMind 开发的革命性 AI 系统 AlphaFold 2 的出现,蛋白质结构预测的格局发生了变化。AlphaFold 蛋白质结构数据库(AFDB)现在拥有惊人的 2 亿个预测蛋白质结构,标志着计算生物学的一个里程碑。

## 基于 AlphaFold 的数据库和全面成熟、易于使用的在线 AlphaFold 接口即将…

不仅是计算生物学,还有实验生物学。对生物学中数据科学领域未来的思考。

[towardsdatascience.com

事实上,就在本周,两组作者在自然杂志上报告了如何利用 AlphaFold 2 的蛋白质模型来揭示蛋白质宇宙的新见解。这些研究利用了现有工具的创新版本,这些工具被调整以适应 AFDB 中的大量数据;例如,现代版本的聚类算法和结构比较方法。通过这些调整后的工具,这些研究探讨了蛋白质结构的广阔领域、它们的进化起源以及它们的功能意义。

聚类

在任何涉及过多对象的研究中,这些对象中许多将紧密相关甚至非常相似,聚类有助于降低复杂性。蛋白质结构也不例外。

在第一篇文章中,Inigo Barrio-Hernandez 及其同事的《在已知蛋白质宇宙规模上的聚类预测结构》中,作者们面对的是将 AFDB 中的 2 亿个蛋白质结构进行聚类的巨大任务。他们介绍了一种基于结构对齐的高效聚类算法,称为 Foldseek cluster。这种新颖的算法能够根据蛋白质的结构相似性快速进行分组,这是理解蛋白质进化和功能的关键步骤。

这项研究的结果非常显著。作者在 AFDB 中识别出了令人惊讶的 230 万个非单例结构簇,其中 31%的簇缺乏注释,代表了之前未被特征化的蛋白质结构。这些未注释的簇虽然只占 AFDB 中所有蛋白质的 4%,却为蛋白质宇宙中尚未发现的领域提供了有趣的见解。进化分析表明,大多数这些簇具有古老的起源,而一部分则似乎是特定于物种的,可能标志着较低质量的预测或 de novo 基因出生的实例。

此外,研究展示了结构比较在预测结构域家族及其关系中的实用性。值得注意的是,作者识别出遥远的结构相似性,揭示了蛋白质之间隐藏的联系。这一新发现知识的一个重要应用是识别具有潜在遥远同源性的与人类免疫相关的蛋白质,展示了这一资源在揭示蛋白质功能和生命树上进化的巨大潜力。

## Clustering-predicted structures at the scale of the known protein universe - Nature

蛋白质是所有细胞过程的关键,其结构在理解其功能和……

www.nature.com

新的家族和结构

第二篇文章,“揭示自然蛋白质宇宙中的新家族和结构”,如本文的封面图所示,由 Janani Durairaj 及其合作者撰写,重点转向利用 AlphaFold 的预测探索蛋白质宇宙中的‘暗物质’。作者创建了一个互动的序列相似性网络,连接了 AFDB 中超过 5000 万种准确预测的蛋白质结构。这个网络作为揭示蛋白质多样性隐藏面貌的强大工具。

这项研究的一个突出发现是识别出了一个新型蛋白质结构,恰如其分地命名为‘Beta-flower’。这一以前未见的结构特征由类似花瓣的发夹型转折组成,类似于 Beta-barrel,为研究人员提供了一个令人兴奋的谜题待解。具有 Beta-flowers 的蛋白质虽关系较远,但其功能仍然未解,突显了未来研究的无限机遇。

此外,作者通过添加多个蛋白质家族扩展了 Pfam 数据库,强调了他们发现的实际应用。值得注意的是,他们实验验证了一种新的翻译靶向毒素-抗毒素系统超家族 TumE-TumA,突显了大规模识别和注释新蛋白质家族的巨大潜力。

[## 发现自然蛋白质宇宙中的新家族和折叠 - 自然

我们现在进入了一个蛋白质序列和结构注释的新纪元,数亿个预测的……

www.nature.com](https://www.nature.com/articles/s41586-023-06622-3?source=post_page-----bf5bd55e754a--------------------------------)

变革性和实用性

AlphaFold 2 无疑具有变革性,在大规模运行时为科学提供了工作。但如同这两篇新的Nature论文中所做的那样,分析大量的结构模型也是一项艰巨的任务,这次直接聚焦于将结构模型整理成可以被科学家轻松使用的形式。

确实,Durairaj 及其合作者的工作最终形成了他们称之为“蛋白质宇宙图谱”的网页资源,这个资源提供了蛋白质序列相似性网络的互动视图,提供有关蛋白质多样性、社区组织、功能注释和结构异常的见解。你可以访问该资源uniprot3d.org/来通过互动浏览和缩放,查询 UniProtKB 条目,列出组件和社区,查看蛋白质序列和结构模型等。该资源促进了蛋白质宇宙的探索,揭示了新的蛋白质家族、折叠和功能见解,从而直接促进了结构研究。它提供了一些数据可视化功能,例如通过对节点进行上色以增强数据可视化。正如任何这样的资源应该有的那样,数据下载也可用,并提供各种格式。

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

放大蛋白质宇宙中的“社区”并将其与实际的 Uniprot 条目和信息连接起来。作者截图。

Barrio-Hernandez 及其同事的工作最终形成了一个实用的网页资源(cluster.foldseek.com/),这次专注于浏览和展示有关已识别蛋白质簇的信息,并提供数据下载服务。

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

欢迎界面和某一条目的示例簇信息。作者截图。

这里讨论的两篇文章标志着蛋白质科学的一个变革性时刻。特别是,它们紧密相关,处理相同的输入数据(AlphaFold 模型),提供相关但互补的分析,并且互相超链接。是的,还一起发表在自然期刊上。

通过 AlphaFold 2 提供的 2 亿个预测蛋白质结构的可用性,催生了探索蛋白质宇宙的创新方法。通过对这些结构进行聚类和比较,科学家们正在发现蛋白质多样性、进化和功能方面前所未有的见解。新折叠和蛋白质家族的发现,以及远程结构相似性的揭示,有望彻底改变我们对生命分子机制的理解。

正如我在传播文章中已经写过几次的那样,结构生物学中没有比现在更激动人心的时刻了,而这一切都是由于其与计算机科学,特别是人工智能的交叉。AlphaFold 数据库凭借其丰富的结构信息,已经成为全球科学家们的强大资源。通过每一项研究,我们揭示了生命复杂画卷中的新层次,使我们更接近理解蛋白质宇宙的奥秘。

进一步阅读

如果你对结构生物学和结构生物信息学中的人工智能世界感兴趣,以及 DeepMind 通过 AlphaFold 2 启动的所有迷人科学,以下是(仅)一些我撰写的文章,旨在以易于理解但严谨的方式传达所有最新发展:

[## 这里是我所有关于蛋白质建模、CASP 和 AlphaFold 2 的同行评审和博客文章

我在这里汇总了所有经过同行评审的文章(包括一些论文、几篇综述、一篇观点文章)和关于…

lucianosphere.medium.com ## 蛋白质设计中的机器学习时代,概述为四种关键方法

由于这些基于人工智能的方法和工具,蛋白质生物技术迎来了如此激动人心的时刻。

towardsdatascience.com

版权声明

这里报道的论文和网站内容都在CC BY 4.0 许可协议下。图片由作者从这些网站的截图中合成,未使用论文中的任何图像。

www.lucianoabriata.com 我撰写和拍摄关于我广泛兴趣领域中的一切:自然、科学、技术、编程等。 订阅以获取我的新故事 通过电子邮件*。要* 咨询小型工作 请查看我的 服务页面在这里。你可以 在这里联系我

两个强大的 Python 特性,以简化你的代码并提高可读性

原文:towardsdatascience.com/two-powerful-python-features-to-streamline-your-code-and-make-it-more-readable-51240f11d1a

通过匹配语句和对象切片提升你的代码质量。

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

·发布在 Towards Data Science ·8 分钟阅读·2023 年 9 月 29 日

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

照片由 Kevin Ku 提供,来自 Unsplash

Python 在当前技术领域的流行程度如此广泛是有原因的。在现代编程语言中,它可能是对新手最为友好的。凭借这种可访问性,它也提供了强大的功能。网页开发、数据科学、科学计算——你可以用 Python 完成许多任务。

随着 Python 多年的发展,其开发者们付出了巨大的努力,以保持其可读性和简洁性。尽管许多特性可能需要额外的学习努力,但代码的清晰度和美观度是绝对值得的。

在这篇文章中,我们将深入探讨两个这样的特性:匹配语句和字符串/列表切片。我们将详细介绍每个特性的工作原理,并考虑一些示例,以加深对语法和语义的理解。

现在,让我们深入探讨一下吧。

匹配语句

匹配语句——从 Python 3.10 版本开始可用——是一种检查条件的相等性并根据条件执行某些操作的方法[1]。如果你来自其他语言如 C 或 JavaScript,你可能已经熟悉这种概念,它们被称为switch 语句。

原则上,匹配语句类似于条件语句,但它们确实提供了一些有用的优势。让我们首先通过与条件语句的比较来看其基本结构,然后再讨论这些优势。

你可能会写出以下条件语句来检查某人的银行账户名称:

name = "Yen"

if name == "Yen":
    print("This is your account.")
elif name == "Ben":
    print("This is your sister's account.")
else:
    print("Fraud attempt.")

转换为匹配语句后,它将如下所示:

name = "Yen"

match name:
    case "Yen":
        print("This is your account.")
    case "Ben":
        print("This is your sister's account.")
    case _:
        print("Fraud attempt.")

让我们逐行分析:

  • 第一行是一样的——我们只是定义了 name 变量。

  • 关键字 match 用于启动匹配语句。

  • 然后,对于每个条件,我们使用 case 语句来有效地进行模式匹配,而不是明确地检查等式。因此,你可以把 case "Yen" 看作是在检查 name,即我们正在匹配的内容,是否等于 "Yen"

  • 最后,最后一个 case 是通配符 case。它由下划线(_)指定,实际上是 else 情况。

现在,你可能会问——为什么使用这个而不是传统的条件语句?我最初也有同样的问题,甚至对人们使用匹配语句而不是标准的 if-else 语句感到恼火。然而,确实有一些优势。

第一件事是,它是实现相同目标的一种更简洁的方法。这看起来可能是个托词,但实际上相当重要。Python 的整个精神在于编写干净、简洁的代码(如果你不相信我,试着在你的 Python 解释器中输入import this并按下回车)。

尤其是当条件数量较多时,解析长链的 ifelif 语句可能会很繁琐。使用匹配语句可以清理代码,并使同事程序员更容易阅读——这是任何 Python 程序员值得追求的成就。

除此之外,匹配语句还可以直接解构某些对象,从而不再需要手动使用条件语句。实际上,这意味着两件事:

  • 你可以自动检查类型(省去了手动检查的需要)。

  • 你可以在每个 case 中自动访问对象的属性。

让我们看一个例子。假设我们有以下代码,定义了两种不同类型的汽车类:

# Online Python compiler (interpreter) to run Python online.
# Write Python 3 code in this online editor and run it.
class Honda:
    # See below for explanation of __match_args__
    __match_args__ = ("year", "model", "cost")
    def __init__(self, year, model, cost):
        self.year = year
        self.model = model
        self.cost = cost

class Subaru:
    __match_args__ = ("year", "model", "cost")
    def __init__(self, year, model, cost):
        self.year = year
        self.model = model
        self.cost = cost

car = Subaru(2021, "Outback", 18000) 

我们在上面定义了一个 Subaru 实例。现在,我们想编写代码来检查汽车的类型,并打印出其某些属性。使用传统的条件语句,我们可以这样做:

if isinstance(car, Honda):
    print("Honda " + car.model)
elif isinstance(car, Subaru):
    print("Subaru " + car.model)
else:
    print("Failure :(")

对于我们上面的 car 变量,这将打印出 "Subaru Outback"。如果我们将其转换为匹配语句,将得到以下简化的代码:

match car:
    case Honda(year, model, cost):
        print("Honda " + model)
    case Subaru(year, model, cost):
        print("Subaru " + model)
    case _:
        print("Failure")

匹配的模式匹配功能使 Python 能够在 case 语句中自动检查类型,并进一步使对象的属性可以直接访问。注意,这得益于在类定义中包含 __match_args__ 属性,它为 Python 命名了位置参数。Python 文档中的推荐是使模式在为 self 分配属性时模拟 __init__ 构造函数中使用的模式。

匹配版本的代码更容易阅读且编写起来不那么繁琐。这是一个相当小的例子,但随着情况变得更加复杂,条件语句的字符串可能变得越来越复杂 [2]。

说到这一点,请记住,这一功能仅在 Python 3.10 及其之后的版本中可用。因此,你需要确保你编写代码的系统、应用程序或项目不会在需要兼容旧版 Python 的代码库中存在。

只要满足那个条件,就考虑使用 match 语句。虽然这可能需要一点努力,但从长远来看,你的代码将会更好。

字符串和列表切片

你可能对这个功能有一些了解,但我敢打赌你还没有完全发挥它的潜力。让我们从快速回顾开始,然后深入了解一些更复杂的用法。

在最简单的形式中,切片指的是一种简洁的语法,让你可以在 Python [3] 中提取字符串或列表的一部分。这里有一个小例子:

>>> my_str = "hello"
>>> my_str[1:3]
'el'

语法要求使用包含起始和结束索引的方括号,并且索引之间用冒号分隔。请记住,Python 使用的是 0 索引,所以这里1对应于'e'。此外,切片不包括右索引,因此它到达3包括它,因此输出是'el'而不是'ell'

如果你只想从开始处开始或一直到字符串或列表的末尾,你可以将相应的索引留空:

>>> my_lst = ['apple', 'orange', 'blackcurrant', 'mango', 'pineapple']
>>> my_lst[:3]
['apple', 'orange', 'blackcurrant']
>>> my_lst[2:]
['blackcurrant', 'mango', 'pineapple']

留下两个索引为空会得到整个对象的副本:

>>> my_str[:]
'hello'
>>> my_lst[:]
['apple', 'orange', 'blackcurrant', 'mango', 'pineapple']

注意,在列表和字符串中,切片定义并返回一个全新的对象,这个对象与原始对象不同:

>>> new_lst = my_lst[2:]
>>> new_lst
['blackcurrant', 'mango', 'pineapple']
>>> my_lst
['apple', 'orange', 'blackcurrant', 'mango', 'pineapple']

现在,让我们进入重点。通过切片,你还可以使用负数索引。如果你不熟悉负数索引,它基本上允许你从列表或字符串的末尾开始计数。最后一个字母对应-1,倒数第二个字母对应-2,依此类推。

这可以通过省去手动计算长度来简化代码。例如,要获取字符串的所有内容但不包括最后一个字母,你可以这样做:

>>> my_str[:-1]
'hell'

最后,切片最被忽视的功能之一是你还可以指定第三个数字——这指定了一种“跳跃”。用一个例子来解释最简单:

>>> my_long_lst = ['apple', 'orange', 'blackcurrant', 'mango', 'pineapple', 'grapes', 'kiwi', 'papaya', 'coconut']
>>> my_long_lst[1:-1:2]
['orange', 'mango', 'grapes', 'papaya']

让我们分解一下上面的内容:

  • 为了清楚起见,我们定义了一个包含更多元素的列表,而不是我们之前的原始列表。

  • 在列表切片中,前两个数字是1-1。正如我们上面看到的,这会去掉被切片对象——在这种情况下是my_long_list的第一个和最后一个元素。

  • 最后,我们在额外的冒号后面放一个2作为最终数字。这告诉 Python 我们希望从开始到结束索引切片,但只保留每隔一个的项。放一个3会给我们每隔第三个项,放一个4会给我们每隔第四个项,依此类推。

将上述两点结合起来,我们还可以对列表进行切片以获得反向元素:

>>> my_long_lst[-1:1:-2]
['coconut', 'kiwi', 'pineapple', 'blackcurrant']

# To slice backwars successfully, the "jump" value must be negative
# Otherwise, we just get an empty list
>>> my_long_lst[-1:1:2]
[]

这就是了——关于列表切片的所有知识。当你对上述语法进行创意应用时,可以实现一些非常酷的行为。例如,以下是利用列表切片在 Python 中反转列表的最妙方式之一:

>>> my_lst
['apple', 'orange', 'blackcurrant', 'mango', 'pineapple']
>>> my_lst[::-1]
['pineapple', 'mango', 'blackcurrant', 'orange', 'apple']

你看到它是如何工作的了吗?作为一个练习,你应该复习上述列表切片的每个特性,并尝试自己分解代码。提示:看看当我们留空开始和结束索引时意味着什么。

那么,让我们谈谈为什么你应该学习这些内容。

作为数据科学家,这有什么用?

一般来说,在用 Python 编写代码时,考虑代码的可读性和整洁性很重要。使用上述特性将大有帮助。如我们所讨论的,匹配语句在这方面比条件语句有几个显著的优势。至于列表切片,它比尝试使用复杂循环实现相同行为要整洁得多。

但超越这些广泛的好处,让我们专门谈谈数据科学。

从实际角度看,如果你作为数据科学家工作,你的正式培训很可能不是计算机科学,而是统计学、数学,或者如果你有幸找到这样的项目,可能是数据科学本身。在这些项目中,计算机科学通常作为工具来教授。

重点是以一种教你足够知识以处理数据、进行分析和大规模构建模型的方式来学习编程基本原理。因此,没有大量时间去学习像“有用的 Python 特定语法特性”这样的主题。实际上,这些主题在纯计算机科学课程中也常常被忽视。

然而,使用这些特性可以将你的代码提升到一个新水平,帮助你在才华横溢的同事中脱颖而出,并为客户提供更好的结果。匹配语句和对象切片是两个强大的例子,但 Python 还有很多其他的特性可以提供,我鼓励你去探索。

愿代码永远对你有利——下次见,朋友们。

想要在 Python 中脱颖而出? 点击这里获取我简单易读的独家免费指南。想在 Medium 上阅读无限故事?使用下面的推荐链接注册吧!

[## 使用我的推荐链接加入 Medium - Murtaza Ali

作为 Medium 会员,你的会员费用的一部分将用于支持你阅读的作者,并且你可以完全访问每个故事……

medium.com

参考文献

[1] docs.python.org/3.10/whatsnew/3.10.html#syntax-and-operations

[2] peps.python.org/pep-0622/#rationale-and-goals

[3] docs.python.org/3/c-api/slice.html

推荐系统中的双塔网络和负采样

原文:towardsdatascience.com/two-tower-networks-and-negative-sampling-in-recommender-systems-fdc88411601b?source=collection_archive---------0-----------------------#2023-11-24

了解推动高级推荐引擎的关键元素

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

·

关注 发表于 Towards Data Science ·7 分钟阅读·2023 年 11 月 24 日

目前推荐系统中最重要的模型之一是双塔神经网络。它们的结构如下:神经网络的一个部分(塔)处理关于查询(用户、上下文)的所有信息,而另一个塔处理关于对象的信息。这些塔的输出是嵌入,然后将这些嵌入相乘(点积或余弦,如我们已经在这里讨论过)。双塔网络应用于推荐的最早提及之一可以在关于 YouTube 的一篇非常好的论文中找到。顺便提一下,我现在会将这篇文章称为经典且最适合进入推荐领域的文章。

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

来自论文 YouTube 推荐的深度神经网络

这种网络的特点是什么?它们与矩阵分解非常相似,实际上矩阵分解是一种特殊情况,仅以 user_iditem_id 作为输入。然而,如果我们将它们与任意网络进行比较,限制晚期交叉(不允许来自不同塔的输入在最后阶段之前融合)使得双塔网络在应用中极为高效。为了为单个用户构建推荐,我们只需要计算一次查询塔,然后将该嵌入与通常预先计算的文档嵌入相乘。这个过程非常快速。此外,这些预先计算的文档嵌入可以组织成一个 ANN 索引(例如,HNSW),以便快速找到好的候选项,而无需遍历整个数据库。

## 相似性搜索,第四部分:分层可导航小世界(HNSW)

分层可导航小世界(HNSW)是一种用于近似最近邻搜索的最先进算法…

towardsdatascience.com

我们可以通过以某种规律异步计算用户部分而不是对每个查询进行计算来实现更高的效率。然而,这意味着需要牺牲对实时历史和上下文的考虑。

塔本身可以相当复杂。例如,在用户部分,我们可以使用自注意力机制处理历史记录,从而实现个性化的变换器。但引入晚期交叉限制的代价是什么?自然,它影响质量。在相同的注意力机制中,我们不能使用当前希望推荐的项目。理想情况下,我们希望关注用户历史中的相似项目。因此,具有早期交叉的网络通常在排序的后期阶段使用,当只剩下几十个或几百个候选时,而具有晚期交叉(双塔)的网络则在早期阶段和候选生成中使用。

(然而,有一个纯理论的观点认为,任何合理的文档排名都可以通过足够维度的嵌入来编码。此外,NLP 中的解码器实际上也是基于相同的原理,只是对每个标记重新计算查询塔。)

损失函数和负样本采样

一个特别关注的点是用于训练双塔网络的损失函数。原则上,它们可以使用任何损失函数进行训练,针对不同的结果,甚至对不同的头部使用多个不同的损失函数(每个塔中有不同的嵌入)。然而,一个有趣的变体是使用批量内负样本上的 softmax 损失进行训练。对于数据集中每个查询-文档对,其他在同一小批次中的文档被用作 softmax 损失中的负样本。这种方法是一种高效的困难负样本挖掘形式。

但考虑这种损失函数的概率解释是很重要的,而这并不总是被很好地理解。在训练好的网络中,

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

得分的指数与给定查询的文档的先验概率成比例,而不是与特定于查询的 PMI(点对点互信息)成比例。更受欢迎的文档不一定会被这种模型更频繁地推荐,因为在训练过程中,它们作为负样本的出现频率相对较高。使用得分作为特征可能是有益的,但对于最终的排序和候选生成,这可能导致非常具体但质量较差的文档。

谷歌在一篇论文中建议通过训练中的 logQ 校正来应对这个问题。而我们则通常在应用阶段处理这个问题,而不是训练阶段,通过简单地乘以文档的先验概率 P(d)。然而,我们从未比较过这些方法,这确实是一个有趣的比较。

隐式正则化:连接 ALS 与现代神经网络

有一种协同过滤算法叫做隐式 ALS(IALS)。我已经提到过它。在神经网络时代之前,它无疑是最受欢迎的算法之一。其显著特点是有效的‘挖掘’负样本:所有没有互动历史的用户-对象对都被视为负样本(虽然权重低于实际互动)。此外,与实际挖掘不同,这些负样本没有被采样,而是在每次迭代中全部使用。这种方法被称为隐式正则化。

这怎么可能呢?考虑到合理的任务规模(用户和对象数量),应该有那么多负样本,甚至列出它们所需的时间都比整个训练过程还长。算法的美妙之处在于,通过使用 MSE 损失和最小二乘法,可以在每次完整迭代之前分别为所有用户和所有对象预先计算某些元素,这足以进行隐式正则化。这样,算法避免了二次大小。(有关更多细节,请参阅我当时最喜欢的论文之一)。

几年前,我考虑过是否可以将这个隐式正则化的奇妙想法与更先进的双塔神经网络技术结合起来。这是一个复杂的问题,因为有随机优化而不是全批处理,并且对回退到 MSE 损失(至少对于整个任务;对于正则化来说可能还好)有顾虑,因为这往往会产生较差的结果。

我思考了很久,最终想出了一个解决方案!有几周的时间,我兴奋不已,热切期待我们如何用这个方案替代批量负样本。

然后,当然(如同在这种情况下经常发生的那样),我在一篇论文中读到一切早在三年前就已经被想到过了。再次,它是谷歌。后来,在那篇关于 logQ 校正的论文中,他们展示了 softmax 损失与批量负样本的组合比隐式正则化效果更好。

就这样,我们能够节省时间而没有测试这个想法🙂

我们真的需要负样本采样用于推荐模型吗?

毕竟,我们有真实的推荐印象实例,如果用户没有与这些实例互动,这些可以作为强负样本使用。(这不考虑推荐服务尚未启动且没有印象的情况。)

这个问题的答案并不那么简单;它取决于我们打算如何应用训练好的模型:是用于最终排序、候选生成,还是仅仅作为输入到另一个模型的特征。

当我们仅在实际展示上训练模型时,会发生什么?会出现相当强的选择偏差,模型只学会在特定上下文中区分那些文档。对于未展示的文档(或者更准确地说,查询-文档对),模型的表现会差很多:它可能会对一些文档进行过度预测,对其他文档进行低估。当然,这种效果可以通过在排名中应用探索来缓解,但通常这只是部分解决方案。

如果候选生成器以这种方式训练,它可能会针对一个查询生成大量文档,这些文档在这样的上下文中它从未见过,并且其预测被高估。在这些文档中,常常会有完全无用的内容。如果最终排序模型足够好,它会过滤掉这些文档,并不会展示给用户。然而,我们仍然不必要地浪费候选配额在这些文档上(而且可能根本没有合适的文档)。因此,候选生成器应以一种理解大部分文档库质量较差并且不应被推荐(提名为候选)的方式进行训练。负采样是一个好的方法。

在这方面,最终排序模型与候选生成非常相似,但有一个重要的区别:它们从错误中学习。当模型通过对某些文档的预测过高而出错时,这些文档会展示给用户,并可能被纳入下一个训练数据集。我们可以在这个新数据集上重新训练模型,并再次推出给用户。新的假阳性会出现。数据集收集和重新训练过程可以重复,从而形成一种主动学习。实际上,只需几次重新训练迭代即可使过程收敛,并使模型停止推荐无用内容。当然,必须权衡随机推荐的危害,有时值得采取额外的预防措施。但总体而言,这里不需要负采样。相反,它可能会损害探索,使系统停留在局部最优。

如果模型用于将特征作为输入传递给另一个模型,那么相同的逻辑适用,但对随机候选文档的预测过高的伤害更不显著,因为其他特征可以帮助调整最终预测。(如果文档甚至没有进入候选列表,我们不会为其计算特征。)

曾经我们直接测试发现,作为特征的标准 ALS 比 IALS 表现更好,但不应用于候选生成。

总结而言,我们的探索强调了双塔网络在排序中的有效性,研究了损失函数和负采样在模型准确性中的重要性,通过隐式正则化弥合了与经典协同过滤的差距,并讨论了负采样在推荐系统中的核心作用。此次讨论突显了推荐系统技术的不断演变的复杂性和精密性。

R 中的双因素 ANOVA

原文:towardsdatascience.com/two-way-anova-in-r-c53d7353efe5

了解如何在 R 中进行双因素 ANOVA。你还将学习其目的、假设、假设条件以及如何解释结果。

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

·发布于数据科学前沿 ·23 分钟阅读·2023 年 6 月 19 日

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

图片来源:内森·杜姆劳

介绍

双因素 ANOVA(方差分析)是一种统计方法,允许评估两个 分类 变量对 定量连续 变量的同时影响

双因素 ANOVA 是单因素 ANOVA 的扩展,因为它允许评估两个分类变量对数值响应的影响。双因素 ANOVA 相对于单因素 ANOVA 的优势在于我们可以测试两个变量之间的关系,同时考虑第三个变量的影响。此外,它还允许包括两个分类变量对响应的可能交互作用

双因素 ANOVA 相对于单因素 ANOVA 的优势与多元线性回归相对于相关性的优势类似:

  • 相关性测量两个定量变量之间的关系。多元线性回归也测量两个变量之间的关系,但这次考虑了其他协变量的潜在影响。

  • 单因素 ANOVA 测试定量变量在各组之间是否存在差异。双因素 ANOVA 也测试定量变量在各组之间是否存在差异,但这次考虑了另一个定性变量的影响。

之前,我们讨论了 单因素方差分析在 R 中的应用。现在,我们展示了在 R 中执行两因素方差分析的时机、原因和方法。

在继续之前,我想提及并简要描述一些相关的统计方法和测试,以避免任何混淆:

学生 t 检验用于评估一个分类变量对定量连续变量的影响,当分类变量恰好有 2 个水平时:

  • 如果观察值是独立的(例如:比较女性和男性的年龄),则使用学生 t 检验 针对独立样本

  • 如果观察值是依赖的,即成对出现(例如,当相同的受试者在两个不同时间点进行两次测量时,前后测量),则使用学生 t 检验 针对配对样本

为了评估一个分类变量对定量变量的影响,当分类变量有 3 个或更多水平时:1

  • 单因素方差分析(通常简称为 ANOVA)如果组是独立的(例如,一个接受治疗 A 的患者组,一个接受治疗 B 的患者组,以及一个没有接受治疗或接受安慰剂的患者组)。

  • 重复测量方差分析 如果组是依赖的(例如,当相同的受试者在三个不同时间点进行三次测量时,治疗前、治疗中和治疗后)。

两因素方差分析用于评估 2 个分类变量(及其潜在交互作用)对定量连续变量的影响。这是本文的主题。

线性回归用于评估定量连续因变量与一个或多个自变量之间的关系:

  • 如果只有一个自变量(可以是定量或定性),则为简单线性回归。

  • 如果至少有两个自变量(可以是定量、定性或两者的混合),则为多元线性回归。

ANCOVA(协方差分析)用于评估分类变量对定量变量的影响,同时控制另一个定量变量(称为协变量)的影响。ANCOVA 实际上是多元线性回归的一种特殊情况,其中包含一个定性和一个定量自变量。

在这篇文章中,我们首先解释了何时以及为何两因素方差分析是有用的,然后进行一些初步的描述性分析,并展示如何在 R 中进行两因素方差分析。最后,我们展示了如何解释和可视化结果。我们还简要提及并说明如何验证基本假设。

数据

为了说明如何在 R 中进行双因素 ANOVA,我们使用 {palmerpenguins} 包提供的 penguins 数据集。

我们不需要 导入数据集,但我们需要先 加载包,然后调用数据集:

# install.packages("palmerpenguins")
library(palmerpenguins)
dat <- penguins # rename dataset
str(dat) # structure of dataset
## tibble [344 × 8] (S3: tbl_df/tbl/data.frame)
##  $ species          : Factor w/ 3 levels "Adelie","Chinstrap",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ island           : Factor w/ 3 levels "Biscoe","Dream",..: 3 3 3 3 3 3 3 3 3 3 ...
##  $ bill_length_mm   : num [1:344] 39.1 39.5 40.3 NA 36.7 39.3 38.9 39.2 34.1 42 ...
##  $ bill_depth_mm    : num [1:344] 18.7 17.4 18 NA 19.3 20.6 17.8 19.6 18.1 20.2 ...
##  $ flipper_length_mm: int [1:344] 181 186 195 NA 193 190 181 195 193 190 ...
##  $ body_mass_g      : int [1:344] 3750 3800 3250 NA 3450 3650 3625 4675 3475 4250 ...
##  $ sex              : Factor w/ 2 levels "female","male": 2 1 1 NA 1 2 1 2 NA NA ...
##  $ year             : int [1:344] 2007 2007 2007 2007 2007 2007 2007 2007 2007 2007 ...

数据集包含 344 只企鹅的 8 个变量,汇总如下:

summary(dat)
##       species          island    bill_length_mm  bill_depth_mm  
##  Adelie   :152   Biscoe   :168   Min.   :32.10   Min.   :13.10  
##  Chinstrap: 68   Dream    :124   1st Qu.:39.23   1st Qu.:15.60  
##  Gentoo   :124   Torgersen: 52   Median :44.45   Median :17.30  
##                                  Mean   :43.92   Mean   :17.15  
##                                  3rd Qu.:48.50   3rd Qu.:18.70  
##                                  Max.   :59.60   Max.   :21.50  
##                                  NA's   :2       NA's   :2      
##  flipper_length_mm  body_mass_g       sex           year     
##  Min.   :172.0     Min.   :2700   female:165   Min.   :2007  
##  1st Qu.:190.0     1st Qu.:3550   male  :168   1st Qu.:2007  
##  Median :197.0     Median :4050   NA's  : 11   Median :2008  
##  Mean   :200.9     Mean   :4202                Mean   :2008  
##  3rd Qu.:213.0     3rd Qu.:4750                3rd Qu.:2009  
##  Max.   :231.0     Max.   :6300                Max.   :2009  
##  NA's   :2         NA's   :2

在这篇文章中,我们将重点关注以下三个变量:

  • species: 企鹅的物种(Adelie,Chinstrap 或 Gentoo)

  • sex: 企鹅的性别(雌性和雄性)

  • body_mass_g: 企鹅的体重(以克为单位)

如果需要,可以通过在 R 中运行 ?penguins 来获取关于此数据集的更多信息。

body_mass_g 是定量连续变量,将作为因变量,而 speciessex 都是定性变量。

这两个最后的变量将是我们的独立变量,也称为因素。确保它们被 R 读作 factors。如果不是这种情况,它们需要被 转换为因素

双因素 ANOVA 的目标和假设

如上所述,双因素 ANOVA 用于同时评估两个分类变量对一个定量连续变量的影响

它被称为因素 ANOVA,因为我们比较的组是由两个独立的分类变量形成的。

在这里,我们想知道体重是否依赖于物种和/或性别。特别是,我们感兴趣的是:

  1. 测量和测试物种与体重之间的关系,

  2. 测量和测试性别与体重之间的关系,并且

  3. 潜在地检查物种与体重之间的关系是否对雌性和雄性不同(这等同于检查性别与体重之间的关系是否依赖于物种)

前两个关系被称为主要效果,而第三点被称为交互效应

主要效果测试是否至少有一个组与另一个组不同(同时控制其他独立变量)。另一方面,交互效应旨在测试两个变量之间的关系是否依赖于第三个变量的水平

在进行双因素 ANOVA 时,测试交互效应不是强制性的。然而,忽略交互效应可能会导致错误的结论,如果交互效应存在的话。

如果我们回到我们的例子,我们有以下 假设检验

性别对体重的主要影响:

  • H0: 女性和男性的平均体重相等

  • H1: 女性和男性的平均体重不同

物种对体重的主要影响:

  • H0: 所有 3 个物种的平均体重相等

  • H1:至少有一个物种的体重平均值不同。

性别和物种之间的交互作用:

  • H0:性别和物种之间没有交互作用,意味着物种和体重之间的关系对雌性和雄性相同(同样,性别和体重之间的关系对所有 3 种物种相同)。

  • H1:性别和物种之间存在交互作用,意味着物种和体重之间的关系对雌性和雄性不同(同样,性别和体重之间的关系依赖于物种)。

两因素方差分析的假设

大多数统计检验需要一些假设以确保结果有效,而两因素方差分析也不例外。

两因素方差分析的假设与单因素方差分析类似。总结如下:

  • 变量类型:依赖变量必须是定量连续的,而两个自变量必须是分类的(至少有两个水平)。

  • 独立性:观察值在组间和组内应相互独立。

  • 正态性

  • 对于小样本,数据应大致遵循正态分布

  • 对于大样本(通常每组/样本 n ≥ 30),不要求正态性(感谢中心极限定理)。

  • 方差齐性:各组之间的方差应相等。

  • 异常值:任何组中都不应有显著的异常值

关于这些假设的更多细节可以在单因素方差分析的假设中找到。

现在我们已经看到了两因素方差分析的基本假设,在应用测试和解释结果之前,我们会专门审查这些假设在我们的数据集中的适用性。

变量类型

依赖变量体重是定量连续的,而两个自变量性别和物种是定性变量(至少有 2 个水平)。

因此,这个假设得到满足。

独立性

独立性通常根据实验设计和数据收集方式来检查。

为了简单起见,观察通常是:

  • 独立的,如果每个实验单元(此处为企鹅)仅测量一次,且观察值来自于一个具有代表性和随机选择的样本部分,或者

  • 依赖的,如果每个实验单元至少被测量两次(例如,在医学领域,通常在同一受试者上进行两次测量;一次是在治疗前,一次是在治疗后)。

在我们的案例中,体重只在每只企鹅上测量一次,并且在一个具有代表性和随机的样本中测量,因此独立性假设得到满足。

正态性

我们在所有子组中都有一个大样本(两个因素水平的每种组合,称为单元):

table(dat$species, dat$sex)
##            
##             female male
##   Adelie        73   73
##   Chinstrap     34   34
##   Gentoo        58   61

所以正态性无需检查。

为了完整起见,我们仍然展示如何验证正态性,假如我们有一个小样本。

有几种方法可以测试正态性假设。最常见的方法包括:

  • 一个QQ 图按组或对残差进行,以及/或

  • 一个直方图按组或对残差进行,以及/或

  • 一个正态性检验(例如 Shapiro-Wilk 检验)按组或对残差进行。

最简单/最短的方式是通过残差的 QQ 图来验证正态性。要绘制此图,我们首先需要保存模型:

# save model
mod <- aov(body_mass_g ~ sex * species,
  data = dat
)

这段代码会进一步解释。

现在我们可以绘制残差的 QQ 图。我们展示两种方法,首先是使用plot()函数,其次是使用来自{car}包的qqPlot()函数:

# method 1
plot(mod, which = 2)

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

按作者绘图

# method 2
library(car)
qqPlot(mod$residuals,
  id = FALSE # remove point identification
)

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

按作者绘图

方法 1 的代码稍短,但缺少参考线周围的置信区间。

如果点沿直线(称为亨利线)分布并且落在置信带内,我们可以假设正态性。在这里是这种情况。

如果你更倾向于通过残差的直方图来验证正态性,这里是代码:

# histogram
hist(mod$residuals)

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

按作者绘图

残差的直方图显示了高斯分布,这与 QQ 图的结论一致。

虽然 QQ 图和直方图在验证正态性方面已基本足够,但如果你希望通过统计检验更正式地测试正态性,可以对残差应用 Shapiro-Wilk 检验:

# normality test
shapiro.test(mod$residuals)
## 
## 	Shapiro-Wilk normality test
## 
## data:  mod$residuals
## W = 0.99776, p-value = 0.9367

⇒ 我们不拒绝残差服从正态分布的原假设(p 值 = 0.937)。

从 QQ 图、直方图和 Shapiro-Wilk 检验中,我们得出结论,不拒绝残差正态性的原假设。

正态性假设因此得到验证,我们现在可以检查方差的相等性。2

方差的同质性

方差的同质性,也称为方差的均匀性或齐性,可以通过plot()函数直观地验证:

plot(mod, which = 3)

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

按作者绘图

由于残差的分布是恒定的,红色平滑线是水平和扁平的,因此看起来这里的恒定方差假设得到满足。

上述诊断图足够,但如果你愿意,也可以使用 Levene 检验(也来自{car}包)进行更正式的测试:3

leveneTest(mod)
## Levene's Test for Homogeneity of Variance (center = median)
##        Df F value Pr(>F)
## group   5  1.3908 0.2272
##       327

⇒ 我们未拒绝方差相等的原假设(p 值 = 0.227)。

视觉和正式方法得出了相同的结论;我们未拒绝方差齐性的假设。

异常值

检测异常值最简单和最常见的方法是通过组的箱线图进行视觉检查。

对于雌性和雄性:

library(ggplot2)
# boxplots by sex
ggplot(dat) +
  aes(x = sex, y = body_mass_g) +
  geom_boxplot()

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

作者绘制

对于三种物种:

# boxplots by species
ggplot(dat) +
  aes(x = species, y = body_mass_g) +
  geom_boxplot()

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

作者绘制

根据四分位距标准,物种 Chinstrap 有两个异常值。这些点的极端程度不足以偏倚结果。

因此,我们认为满足无显著异常值的假设。

双因素 ANOVA

我们已经展示了所有假设都得到满足,所以现在可以继续在 R 中实施双因素 ANOVA。

这将帮助我们回答以下研究问题:

  • 控制物种后,两个性别之间的体重是否存在显著差异?

  • 控制性别后,体重在至少一种物种中是否存在显著差异?

  • 物种与体重之间的关系在雌性和雄性企鹅中是否不同?

初步分析

在进行任何统计测试之前,进行一些描述性统计是一个好的做法,以便对数据有一个初步的了解,并且可能对预期结果有所了解。

这可以通过描述性统计或图形完成。

描述性统计

如果我们想保持简单,我们可以只计算每个子组的均值:

# mean by group
aggregate(body_mass_g ~ species + sex,
  data = dat,
  FUN = mean
)
##     species    sex body_mass_g
## 1    Adelie female    3368.836
## 2 Chinstrap female    3527.206
## 3    Gentoo female    4679.741
## 4    Adelie   male    4043.493
## 5 Chinstrap   male    3938.971
## 6    Gentoo   male    5484.836

或者最终,使用{dplyr}包计算每个子组的均值和标准差

# mean and sd by group
library(dplyr)
group_by(dat, sex, species) %>%
  summarise(
    mean = round(mean(body_mass_g, na.rm = TRUE)),
    sd = round(sd(body_mass_g, na.rm = TRUE))
  )
## # A tibble: 8 × 4
## # Groups:   sex [3]
##   sex    species    mean    sd
##   <fct>  <fct>     <dbl> <dbl>
## 1 female Adelie     3369   269
## 2 female Chinstrap  3527   285
## 3 female Gentoo     4680   282
## 4 male   Adelie     4043   347
## 5 male   Chinstrap  3939   362
## 6 male   Gentoo     5485   313
## 7 <NA>   Adelie     3540   477
## 8 <NA>   Gentoo     4588   338

图形

如果你是博客的常读者,你知道我喜欢绘制图形来可视化数据,然后再解释测试结果。

当我们有一个定量变量和两个定性变量时,最合适的图形是按组绘制的箱线图。这可以很容易地使用[{ggplot2}](https://statsandr.com/blog/graphics-in-r-with-ggplot2/) 制作:

# boxplot by group
library(ggplot2)
ggplot(dat) +
  aes(x = species, y = body_mass_g, fill = sex) +
  geom_boxplot()

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

作者绘制

性别的一些观察值缺失,我们可以将它们删除以获得更简洁的图形:

dat %>%
  filter(!is.na(sex)) %>%
  ggplot() +
  aes(x = species, y = body_mass_g, fill = sex) +
  geom_boxplot()

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

作者绘制

请注意,我们也可以绘制以下图形:

dat %>%
  filter(!is.na(sex)) %>%
  ggplot() +
  aes(x = sex, y = body_mass_g, fill = species) +
  geom_boxplot()

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

作者绘图

但为了获得更易读的图形,我倾向于将变量中层级最少的设置为颜色(实际上是aes()层中的fill参数),将类别最多的变量设置在 x 轴上(即aes()层中的x参数)。

从均值和子组的箱线图中,我们已经可以看到,在我们的样本中

  • 雌性企鹅的体重往往低于雄性,并且所有考虑的物种中都是如此,并且

  • 相比其他两种物种,振翅企鹅的体重更高。

请记住,这些结论仅在我们的样本内有效!要将这些结论推广到总体,我们需要进行双因素方差分析并检查解释变量的显著性。这是下一节的目标。

R 中的双因素方差分析

如前所述,在双因素方差分析中包含交互作用效应并非强制性的。然而,为了避免错误结论,建议首先检查交互作用是否显著,并根据结果决定是否包括它。

如果交互作用不显著,可以安全地将其从最终模型中移除。相反,如果交互作用显著,应将其包含在最终模型中以解释结果。

因此,我们首先建立一个包括两个主要效应(即性别和物种)及交互作用的模型:

# Two-way ANOVA with interaction
# save model
mod <- aov(body_mass_g ~ sex * species,
  data = dat
)
# print results
summary(mod)
##              Df    Sum Sq  Mean Sq F value   Pr(>F)    
## sex           1  38878897 38878897 406.145  < 2e-16 ***
## species       2 143401584 71700792 749.016  < 2e-16 ***
## sex:species   2   1676557   838278   8.757 0.000197 ***
## Residuals   327  31302628    95727                     
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 11 observations deleted due to missingness

平方和(Sum Sq列)显示物种解释了体重变化的大部分。这是解释这种变化的最重要因素。

p 值显示在上述输出的最后一列(Pr(>F))。从这些 p 值中,我们得出结论,在 5%的显著性水平下:

  • 在控制了物种的情况下,两个性别之间的体重显著不同,

  • 控制性别的情况下,至少有一种物种的体重显著不同,并且

  • 性别和物种之间的交互作用(在上述输出中的sex:species行显示)是显著的。

因此,从显著的交互作用效应中,我们刚刚看到体重与物种之间的关系在雄性和雌性之间是不同的。由于它是显著的,我们必须将其保留在模型中,并应解释该模型的结果。

如果相反,交互作用不显著(即 p 值≥0.05),我们将从模型中移除这个交互作用效应。为了说明,下面是一个没有交互作用的双因素方差分析代码,称为加性模型:

# Two-way ANOVA without interaction
aov(body_mass_g ~ sex + species,
  data = dat
)

对于习惯于在R 中进行线性回归的读者,你会注意到双因素方差分析的代码结构实际上是相似的:

  • 公式是 dependent variable ~ independent variables

  • + 符号用于包含没有交互作用的独立变量4

  • * 符号用于包含具有交互作用的独立变量

与线性回归的相似性并不令人惊讶,因为双因素方差分析,就像所有方差分析一样,实际上是一个线性模型。

注意以下代码也有效,并且给出相同的结果:

# method 2
mod2 <- lm(body_mass_g ~ sex * species,
  data = dat
)
Anova(mod2)
## Anova Table (Type II tests)
## 
## Response: body_mass_g
##                Sum Sq  Df F value    Pr(>F)    
## sex          37090262   1 387.460 < 2.2e-16 ***
## species     143401584   2 749.016 < 2.2e-16 ***
## sex:species   1676557   2   8.757 0.0001973 ***
## Residuals    31302628 327                      
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

注意 aov() 函数假设平衡设计,即在我们的独立分组变量的水平内样本大小相等。此外,aov() 使用类型 I 方差和平方和,因此当我们写 y ~ A * By ~ B * A 时可能会得到不同的 p 值。

对于不平衡设计,即每个子组中的受试者数量不相等,推荐的方法是:

  • 没有显著交互作用时使用类型 II 方差分析,这可以在 R 中使用 Anova(mod, type = "II") 来完成,5,并且

  • 当存在显著交互作用时使用类型 III 方差分析,这可以在 R 中使用 Anova(mod, type = "III") 来完成。

这超出了帖子范围,我们假设这里是平衡设计。对感兴趣的读者,参见 详细讨论 关于类型 I、类型 II 和类型 III 方差分析。

两两比较

通过两个主要效应显著,我们得出结论:

  • 控制物种时,体重在女性和男性之间有所不同,并且

  • 控制性别时,体重在至少一个物种中有所不同。

如果体重在两个性别之间不同,鉴于性别正好有两个,这必然是因为体重在女性和男性之间显著不同。

如果想知道哪个性别的体重最高,可以通过均值和/或子组箱线图来推测。这里,显然男性的体重显著高于女性。

然而,对于物种来说,情况并非如此简单。让我解释为什么这不像性别那样容易。

有三种物种(阿德利企鹅、刺胸企鹅和绵毛企鹅),因此有 3 对物种:

  1. 阿德利企鹅和刺胸企鹅

  2. 阿德利企鹅和绵毛企鹅

  3. 刺胸企鹅和绵毛企鹅

如果体重在至少一个物种中显著不同,可能是因为:

  • 体重在阿德利企鹅和刺胸企鹅之间显著不同,但在阿德利企鹅和绵毛企鹅之间没有显著不同,也在刺胸企鹅和绵毛企鹅之间没有显著不同,或者

  • 体重在阿德利企鹅和绵毛企鹅之间显著不同,但在阿德利企鹅和刺胸企鹅之间没有显著不同,也在刺胸企鹅和绵毛企鹅之间没有显著不同,或者

  • 体重在刺胸企鹅和绵毛企鹅之间显著不同,但在阿德利企鹅和刺胸企鹅之间没有显著不同,也在阿德利企鹅和绵毛企鹅之间没有显著不同。

或者,也可能是:

  • 体重在 Adelie 和 Chinstrap 之间、Adelie 和 Gentoo 之间显著不同,但在 Chinstrap 和 Gentoo 之间没有显著差异,或者

  • 体重在 Adelie 和 Chinstrap 之间、Chinstrap 和 Gentoo 之间显著不同,但在 Adelie 和 Gentoo 之间没有显著差异,或者

  • 体重在 Chinstrap 和 Gentoo 之间、Adelie 和 Gentoo 之间显著不同,但在 Adelie 和 Chinstrap 之间没有显著差异。

最后,体重也可能在所有物种之间存在显著差异。

至于单因素方差分析,在这个阶段,我们无法确切知道哪种物种在体重方面与其他物种不同。要知道这一点,我们需要通过事后检验(也称为成对比较)来两两比较每个物种。

有几种事后检验,最常见的是 Tukey HSD,它测试所有可能的组对。如前所述,这个检验只需要在物种变量上进行,因为性别只有两个水平。

至于单因素方差分析,Tukey HSD 可以在 R 中按如下方式进行:

# method 1
TukeyHSD(mod,
  which = "species"
)
##   Tukey multiple comparisons of means
##     95% family-wise confidence level
## 
## Fit: aov(formula = body_mass_g ~ sex * species, data = dat)
## 
## $species
##                        diff       lwr       upr     p adj
## Chinstrap-Adelie   26.92385  -80.0258  133.8735 0.8241288
## Gentoo-Adelie    1377.65816 1287.6926 1467.6237 0.0000000
## Gentoo-Chinstrap 1350.73431 1239.9964 1461.4722 0.0000000

或使用{multcomp}包:

# method 2
library(multcomp)
summary(glht(
  aov(body_mass_g ~ sex + species,
    data = dat
  ),
  linfct = mcp(species = "Tukey")
))
## 
## 	 Simultaneous Tests for General Linear Hypotheses
## 
## Multiple Comparisons of Means: Tukey Contrasts
## 
## 
## Fit: aov(formula = body_mass_g ~ sex + species, data = dat)
## 
## Linear Hypotheses:
##                         Estimate Std. Error t value Pr(>|t|)    
## Chinstrap - Adelie == 0    26.92      46.48   0.579     0.83    
## Gentoo - Adelie == 0     1377.86      39.10  35.236   <1e-05 ***
## Gentoo - Chinstrap == 0  1350.93      48.13  28.067   <1e-05 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## (Adjusted p values reported -- single-step method)

或使用pairwise.t.test()函数,使用你选择的 p 值调整方法:6

# method 3
pairwise.t.test(dat$body_mass_g, dat$species,
  p.adjust.method = "BH"
)
## 
## 	Pairwise comparisons using t tests with pooled SD 
## 
## data:  dat$body_mass_g and dat$species 
## 
##           Adelie Chinstrap
## Chinstrap 0.63   -        
## Gentoo    <2e-16 <2e-16   
## 
## P value adjustment method: BH

请注意,使用第二种方法时,需要在glht()函数中指定没有交互作用的模型,即使交互作用显著。此外,不要忘记在我的代码中将modspecies替换为你的模型名称和独立变量名称。

两种方法得到的结果相同,即:

  • Chinstrap 和 Adelie 之间的体重没有显著差异(调整后的 p 值 = 0.83),

  • 体重在 Gentoo 和 Adelie 之间(调整后的 p 值 < 0.001)显著不同,并且

  • 体重在 Gentoo 和 Chinstrap 之间(调整后的 p 值 < 0.001)显著不同。

记住报告的是调整后的p 值,以防止比较多个组对时出现的多重检验问题

如果你想比较所有组的组合,可以使用TukeyHSD()函数,并在which参数中指定交互作用:

# all combinations of sex and species
TukeyHSD(mod,
  which = "sex:species"
)
##   Tukey multiple comparisons of means
##     95% family-wise confidence level
## 
## Fit: aov(formula = body_mass_g ~ sex * species, data = dat)
## 
## $`sex:species`
##                                      diff       lwr       upr     p adj
## male:Adelie-female:Adelie        674.6575  527.8486  821.4664 0.0000000
## female:Chinstrap-female:Adelie   158.3703  -25.7874  342.5279 0.1376213
## male:Chinstrap-female:Adelie     570.1350  385.9773  754.2926 0.0000000
## female:Gentoo-female:Adelie     1310.9058 1154.8934 1466.9181 0.0000000
## male:Gentoo-female:Adelie       2116.0004 1962.1408 2269.8601 0.0000000
## female:Chinstrap-male:Adelie    -516.2873 -700.4449 -332.1296 0.0000000
## male:Chinstrap-male:Adelie      -104.5226 -288.6802   79.6351 0.5812048
## female:Gentoo-male:Adelie        636.2482  480.2359  792.2606 0.0000000
## male:Gentoo-male:Adelie         1441.3429 1287.4832 1595.2026 0.0000000
## male:Chinstrap-female:Chinstrap  411.7647  196.6479  626.8815 0.0000012
## female:Gentoo-female:Chinstrap  1152.5355  960.9603 1344.1107 0.0000000
## male:Gentoo-female:Chinstrap    1957.6302 1767.8040 2147.4564 0.0000000
## female:Gentoo-male:Chinstrap     740.7708  549.1956  932.3460 0.0000000
## male:Gentoo-male:Chinstrap      1545.8655 1356.0392 1735.6917 0.0000000
## male:Gentoo-female:Gentoo        805.0947  642.4300  967.7594 0.0000000

或者使用来自{agricolae}包的HSD.test()函数,该函数用相同的字母标记那些在统计上没有显著差异的子组:

library(agricolae)
HSD.test(mod,
  trt = c("sex", "species"),
  console = TRUE # print results
)
## 
## Study: mod ~ c("sex", "species")
## 
## HSD Test for body_mass_g 
## 
## Mean Square Error:  95726.69 
## 
## sex:species,  means
## 
##                  body_mass_g      std  r  Min  Max
## female:Adelie       3368.836 269.3801 73 2850 3900
## female:Chinstrap    3527.206 285.3339 34 2700 4150
## female:Gentoo       4679.741 281.5783 58 3950 5200
## male:Adelie         4043.493 346.8116 73 3325 4775
## male:Chinstrap      3938.971 362.1376 34 3250 4800
## male:Gentoo         5484.836 313.1586 61 4750 6300
## 
## Alpha: 0.05 ; DF Error: 327 
## Critical Value of Studentized Range: 4.054126 
## 
## Groups according to probability of means differences and alpha level( 0.05 )
## 
## Treatments with the same letter are not significantly different.
## 
##                  body_mass_g groups
## male:Gentoo         5484.836      a
## female:Gentoo       4679.741      b
## male:Adelie         4043.493      c
## male:Chinstrap      3938.971      c
## female:Chinstrap    3527.206      d
## female:Adelie       3368.836      d

如果你有多个组需要比较,绘图可能更容易解释:

# set axis margins so labels do not get cut off
par(mar = c(4.1, 13.5, 4.1, 2.1))
# create confidence interval for each comparison
plot(TukeyHSD(mod, which = "sex:species"),
  las = 2 # rotate x-axis ticks
)

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

按作者绘图

从上述输出和图中,我们得出结论,所有性别和物种的组合之间都有显著差异,除了雌性 Chinstrap 和雌性 Adelie(p 值 = 0.138)以及雄性 Chinstrap 和雄性 Adelie(p 值 = 0.581)。

这些结果与上面展示的箱型图一致,并将通过下面的可视化得到确认,这些结果总结了 R 中的二元方差分析。

可视化

如果您想以不同于初步分析中已经呈现的方式可视化结果,以下是一些有用的绘图思路。

首先,使用 {effects} 包中的 allEffects() 函数绘制每个子组的均值和标准误:

# method 1
library(effects)
plot(allEffects(mod))

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

作者绘制

或使用 {ggpubr} 包:

# method 2
library(ggpubr)
ggline(subset(dat, !is.na(sex)), # remove NA level for sex
  x = "species",
  y = "body_mass_g",
  color = "sex",
  add = c("mean_se") # add mean and standard error
) +
  labs(y = "Mean of body mass (g)")

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

作者绘制

另外,使用 {Rmisc}{ggplot2}

library(Rmisc)
# compute mean and standard error of the mean by subgroup
summary_stat <- summarySE(dat,
  measurevar = "body_mass_g",
  groupvars = c("species", "sex")
)# plot mean and standard error of the mean
ggplot(
  subset(summary_stat, !is.na(sex)), # remove NA level for sex
  aes(x = species, y = body_mass_g, colour = sex)
) +
  geom_errorbar(aes(ymin = body_mass_g - se, ymax = body_mass_g + se), # add error bars
    width = 0.1 # width of error bars
  ) +
  geom_point() +
  labs(y = "Mean of body mass (g)")

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

作者绘制

其次,如果您更喜欢仅绘制每个子组的均值:

with(
  dat,
  interaction.plot(species, sex, body_mass_g)
)

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

作者绘制

最后但同样重要的是,对于那些熟悉 GraphPad 的人,您可能已经熟悉如下方式绘制均值和误差条:

# plot mean and standard error of the mean as barplots
ggplot(
  subset(summary_stat, !is.na(sex)), # remove NA level for sex
  aes(x = species, y = body_mass_g, fill = sex)
) +
  geom_bar(position = position_dodge(), stat = "identity") +
  geom_errorbar(aes(ymin = body_mass_g - se, ymax = body_mass_g + se), # add error bars
    width = 0.25, # width of error bars
    position = position_dodge(.9)
  ) +
  labs(y = "Mean of body mass (g)")

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

结论

在这篇文章中,我们首先回顾了用于比较组间定量变量的不同检验方法。然后我们集中讨论了二元方差分析,从其目标和假设到在 R 中的实现,以及解释和一些可视化。我们还简要提到了其基本假设和一种事后检验,用于比较所有子组。

所有这些都使用 {palmerpenguins} 包中提供的 penguins 数据集进行了说明。

感谢阅读。

我希望这篇文章能帮助您用您的数据进行二元方差分析。

一如既往,如果您对本文讨论的主题有任何问题或建议,请在评论中添加,以便其他读者也能从讨论中受益。

  1. 理论上,一元方差分析也可以用于比较 2 个组,而不仅仅是 3 个或更多组。然而,在实际操作中,通常会使用学生 t 检验来比较 2 个组,而使用一元方差分析来比较 3 个或更多组。使用独立样本的学生 t 检验和 2 个组的一元方差分析得出的结论会相似。↩︎

  2. 请注意,如果正态性假设未满足,可以应用许多变换来改善这一点,其中最常见的是对数变换(log() 函数在 R 中)。↩︎

  3. 请注意,Bartlett 检验也适用于检验方差齐性假设。↩︎

  4. 加性模型假设两个解释变量是独立的;它们之间没有相互作用。↩︎

  5. 其中 mod 是您保存的模型的名称。↩︎

  6. 在这里,我们使用 Benjamini & Hochberg (1995)修正,但你可以选择多种方法。有关更多详情,请参见?p.adjust↩︎

相关文章

最初发表于 https://statsandr.com 于 2023 年 6 月 19 日。

双因素方差分析测试,使用 Python

原文:towardsdatascience.com/two-way-anova-test-with-python-a112e2396d78

完全初学者的双因素方差分析测试指南(附代码!)

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

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

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

图片由Sergey Pesterev拍摄,Unsplash提供

方差分析测试旨在检验三个或更多组之间均值的统计显著差异。常用的方差分析有两种类型,**单因素方差分析测试****双因素方差分析测试**。唯一的区别在于影响因变量的自变量的数量。

双因素方差分析

双因素方差分析是单因素方差分析的扩展,考察**两个不同的分类自变量或两个独立因素****一个连续因变量**的影响。

双因素方差分析不仅旨在测试每个独立因素的主要效应,还测试两个因素是否相互影响以影响因变量,即是否存在两个独立因素之间的相互作用。[2]

方差分析使用 F 检验,这是一种组间比较检验,用于检验统计显著性。它将每个组在不同因素(因素 A、因素 B、因素 A 与因素 B 之间的相互作用)下的均方差与因变量的总体方差进行比较。最后,基于 F 检验统计量做出结论。

平方和(SS)

在双因素方差分析表中:

变异性的总量来自四个可能的来源,即:

  1. 因素 A 下的组间变异,称为处理(A)

  2. 因素 B 下的组间变异,称为处理(B)

  3. 由于因素 A 和因素 B 之间的相互作用引起的平方和,称为相互作用(AB)

  4. 组内变异,称为误差(E)

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

图片 1. SS 和 d.f. 的示意图 作者提供

类似于平方和 (SS),d.f. (SSTO) = d.f. (SSA) + d.f. (SSB) + d.f. (SSAB) + d.f. (SSE)

SS 除以其 d.f.将得到均方 (MS)。

两因素 ANOVA 测试的假设与单因素 ANOVA 测试相同,即所有的参数检验假设,包括样本数据的随机性和独立性、正态性及方差齐性。如果你想了解更多细节,可以参考上一篇文章。

两因素 ANOVA 测试的简单概述

两因素 ANOVA 有三组假设

集 1:

H₀: μₐ₁= μₐ₂ = μₐ₃ = … = μₐ𝒸

H₁: 不是所有的μₐᵢ在因素 A 下都是相等的,其中 i = 1, 2, 3, …, c。

显著性水平 = α

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

图片 2. 用于测试因素 A 主效应的 F 检验统计量。作者提供的图片。

集 2:

H₀: μᵦ₁= μᵦ₂ = μᵦ₃ = … = μᵦᵣ

H₁: 不是所有的μᵦᵢ在因素 B 下都是相等的,其中 i = 1, 2, 3, …, r。

显著性水平 = α

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

图片 3. 用于测试因素 B 主效应的 F 检验统计量。作者提供的图片。

集 3:

H₀: 一个独立变量的效应不依赖于另一个独立变量的效应,即因素 A 和因素 B 之间没有交互作用

H₁: 因素 A 和因素 B 之间存在交互作用

显著性水平 = α

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

图片 4. 用于测试两个独立因素之间是否存在交互作用的 F 检验统计量。作者提供的图片。

如果你执行带有交互作用的两因素 ANOVA 测试,你需要测试上述提到的所有 3 组假设。但如果你执行无交互作用的测试,你只需要测试集 1 和集 2 的假设

最后,带有交互作用的两因素 ANOVA 表格如下所示:

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

表 1. 带交互作用的两因素 ANOVA 示例表。作者提供的图片。

两因素 ANOVA 表格(无交互作用)如下所示:

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

表 2. 无交互作用的两因素 ANOVA 示例表。作者提供的图片。

平衡设计与不平衡设计

平衡设计是指所有组合组的样本量相等的情况。在不平衡设计中,各组的样本量不相等。在两因素 ANOVA 中,如果组的样本量差异过大,普通的方差分析方法可能不够充分。对于不平衡设计,需要使用回归方法。另一种方法是尽力确保设计的平衡。

一个数据集,students.csv,包含 8239 行学生特征数据。每一行代表一个独特的学生。它包含与学生相关的 16 个特征,我们将只关注 3 个特征:专业、性别和薪资。

基于两个因素,专业和性别,是否存在不同性别和专业毕业生的年均薪资显著差异,以及性别和专业之间是否存在交互作用,显著性水平为 5%?

数据处理

从给定的数据集中,我们需要筛选出已毕业的学生并进行随机抽样。在这种情况下,它随机抽取了每组 40 名学生,即不同的(专业和性别)组合,以使其成为平衡设计。之后,选择关注的三个变量的数据集,即分类变量major, gender和数值变量salary

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

图 5. 数据处理以实现平衡设计。图像来源:作者。

假设检验

根据假设检验的五步过程:

集合 1:

H₀: μₐ₁= μₐ₂ = μₐ₃ = … = μₐ₆

H₁: 在不同专业下薪资均值不相等

集合 2:

H₀: μᵦ₁= μᵦ₂

H₁: 在不同性别下薪资均值不相等

集合 3:

H₀: 专业和性别之间没有交互作用

H₁: 专业和性别之间存在交互作用

α = 0.05

根据 F 检验统计量:

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

图 6. 具有交互作用的 ANOVA 表:方差分析的正常方法。图像来源:作者。

我们还可以使用statsmodels包得到相同的结果,它使用回归方法。由于statsmodels使用回归方法,它也适用于不平衡设计,即你无需做大量工作来确保平衡设计。

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

图 7. 具有交互作用的 ANOVA 表:回归方法。图像来源:作者。

以下显示了专业和性别对薪资的交互作用图:

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

图 8. 专业和性别对薪资的交互作用图。图像来源:作者

结论

对于集合 1 和集合 2:由于 F 值 > F 临界值或 p 值 < 0.05,零假设被拒绝。∴我们有足够的证据表明,不同研究科目或性别的毕业生的平均薪资不相同,显著性水平为 5%。

对于集合 3:未能拒绝零假设。∴我们没有足够的证据表明研究科目和性别之间存在交互作用,显著性水平为 5%。此外,从交互作用图[4]中可以看出,没有交互作用,主要效应即专业和性别效应都显著。例如,生物学专业的男性毕业生的平均薪资会显著更高。

推荐阅读

## ANOVA 测试,使用 Python

完全初学者指南:执行 ANOVA 测试(附代码!)

towardsdatascience.com ## 卡方检验,使用 Python

完全初学者指南:进行卡方检验(附代码!)

towardsdatascience.com ## McNemar 检验,使用 Python

完全初学者指南:进行 McNemar 检验(附代码!)

towardsdatascience.com [## 单样本假设检验,使用 Python

完全初学者指南:进行单样本假设检验(附代码!)

levelup.gitconnected.com](https://levelup.gitconnected.com/how-to-perform-one-sample-hypothesis-tests-with-python-308eae8789fc?source=post_page-----a112e2396d78--------------------------------) [## 双样本假设检验,使用 Python

完全初学者指南:进行双样本假设检验(附代码!)

levelup.gitconnected.com](https://levelup.gitconnected.com/two-sample-hypothesis-tests-with-python-43e1b8c52306?source=post_page-----a112e2396d78--------------------------------)

参考文献

[1] “单因素方差分析假设检验 • SOGA • 地球科学系。” [在线]. 可用:www.geo.fu-berlin.de/en/v/soga/Basics-of-statistics/ANOVA/One-way-ANOVA-Hypothesis-Test/index.html

[2] 双向方差分析 — 维基百科

[3] Kiernan, D. (2014). 第六章:双向方差分析。Open SUNY 教科书。

[4] 第七章 方差分析与交互 | STA 265 讲义(统计与数据科学方法)。 (无日期). 取自 2023 年 1 月 2 日,campus.murraystate.edu/academic/faculty/cmecklin/STA265/_book/anova-with-interaction.html#the-interactive-two-way-anova-model

两种本地下载和访问 Llama 2 的方法

原文:towardsdatascience.com/two-ways-to-download-and-access-llama-2-locally-8a432ed232a4

在你的 PC 上使用 Llama 2 的逐步指南

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

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

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

图片来源:作者(Dreamstudio)

动机

Meta 最新发布的 Llama 2 正在获得越来越多的关注,并且对各种使用场景都非常有趣。它提供了不同大小的预训练和微调的 Llama 2 语言模型,从 7B 到 70B 参数。Llama 2 在推理、编码、能力和知识基准等各种测试中表现良好,这使它非常有前景。

在本文中,我们将逐步指导你在 PC 上下载 Llama 2 的过程。你有两个选项:官方的 Meta AI 网站或 HuggingFace。我们还会展示如何访问它,以便你可以利用其强大的功能来支持你的项目。让我们开始吧!

前提条件

  • Jupyter Notebook

  • Nvidia T4 图形处理单元 (GPU)

  • 虚拟环境 (Virtualenv)

  • HuggingFace 账户、库以及 Llama 模型

  • Python 3.10

本地下载前需要考虑的事项

在将模型下载到本地机器之前,考虑一些事项。首先,确保你的计算机有足够的处理能力和存储空间(从 SSD 磁盘加载模型要快得多)。其次,准备进行一些初始设置以使模型运行。最后,如果你是出于工作需要使用此模型,请检查公司关于下载外部软件的政策。

为什么要本地下载 Llama 2?

你可能有几个很好的理由希望将模型下载到自己的计算机上,例如:

  • 减少延迟 通过在你的环境中托管 Llama 2,你可以将与外部服务器的 API 调用相关的延迟降到最低。

  • 数据隐私 你可以将私人和敏感信息保存在自己的生态系统中(本地或外部云提供商)。

  • 定制和控制 您对模型拥有更多控制权。您可以优化机器的配置,进行优化技术的工作,对模型进行微调,并进一步将其集成到您的生态系统中。

  • 离线访问 根据使用情况,模型可能托管在没有互联网连接的安全环境中。

选择获取“Llama 2”的来源

决定从哪里获取“Llama 2”是基于对您最合适的选择。以下是一些考虑因素,以帮助您做出选择。

Meta 的 GitHub:

当您从 Meta 的 GitHub 获取“Llama 2”时,您直接从源头获取。这使您可以访问最新的更新。然而,如果遇到问题,社区可能不会像 HuggingFace 那样反应迅速。文档很好,但尝试示例可能需要更多编码。

Hugging Face:

使用 Hugging Face 非常简单,因为它具有用户友好的平台和反应迅速且强大的社区支持。它兼容多个框架,使得将模型集成到现有技术栈中变得更加容易。

因此,如果您需要定制和见解,建议直接从 Meta 的 GitHub 获取模型;如果需要易用性、社区支持和与各种框架的兼容性,可以选择 Hugging Face。

1️⃣ 从 Meta 网站下载 Llama 2

步骤 1:请求下载

下载 Llama 2 模型权重和分词器的一个选项是Meta AI 网站。在下载模型权重和分词器之前,您必须阅读并同意许可协议,并通过提供您的电子邮件地址提交请求。填写以下信息并接受条款:

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

作者提供的图片

一旦您的请求被批准,您将通过电子邮件收到一个signed URL

提个小提醒!提供的下载模型权重和分词器的链接仅在 24 小时内有效,并且下载次数有限。因此,如果您看到诸如“403: Forbidden”的错误,请不要担心!您可以通过返回 Meta AI 网站请求一个新链接。

步骤 2:获取 download.sh 脚本

在继续之前,请确保您已经安装了wgetmd5sum。您可以在Meta 的 GitHub 仓库找到所需的 download.sh 脚本。克隆该仓库并按如下方式进入llama目录:

git clone https://github.com/facebookresearch/llama.git
cd llama 

通过输入以下命令确保您赋予脚本执行权限:

chmod +x download.sh

步骤 3:启动下载过程

要启动下载过程,您需要运行download.sh脚本。在此过程中,系统会提示您提供通过电子邮件发送的 URL 以及您希望下载的模型。

您可以选择下载两种不同类型的模型:

  • 预训练 — Llama-2–7b, Llama-2–13b, Llama-2–70b

  • 微调的聊天 — Llama-2–7b-chat,Llama-2–13b-chat,Llama-2–70b-chat

就我而言,我会获得 Llama-2–7b 和 Llama-2–7b-chat。

bash download.sh

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

如果下载成功,你应该能找到分词器和模型 llama-2–7b 及 llama-2–7b-chat。

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

作者提供的图片

步骤 4:准备本地环境

为了获得最佳隔离,建议建立一个全新的本地环境;我个人使用 Conda 环境管理系统。让我们开始创建新的 Conda 环境:

conda create --name yourenvname python=3.10

用你想要给环境的名称替换 yourenvname,用首选的 Python 版本替换 3.10。创建环境后,你可以用以下命令激活它:

conda activate yourenvname

然后,导航到克隆的仓库并安装 requirements.txt 中提到的所需库。

pip install -r requirements.txt 

还有一件事你需要做:以允许你更改代码并立即查看效果的方式安装项目包,而不必重新安装。要实现这一点,请运行以下命令:

pip install -e .

既然我们已经准备好了,让我们运行模型看看会发生什么。

4. 使用 torchrun 运行推理

Torchrun 是 PyTorch 中的一个工具,通过自动分配工作者、处理故障、支持弹性设置和提供超越 torch.distributed.launch 的功能(包括自定义入口点、参数传递和日志捕获)来简化分布式训练。

在克隆的仓库中,你应该看到两个示例:example_chat_completion.pyexample_text_completion.py

由于两个脚本都设计用于分布式训练,我们需要设置一些变量。你可以像下面这样简单地导出它们,或将它们添加到 .bashrc 中。

export RANK=1
export WORLD_SIZE=0
export MASTER_ADDR=localhost
export MASTER_PORT=12355
  • RANK:分布式训练组中当前进程的等级。

  • WORLD_SIZE:分布式组中的总进程数。

  • MASTER_ADDR:协调训练的主节点地址。

  • MASTER_PORT:用于与主节点通信的端口号。

要执行 torchrun,我们需要:

  • nproc-per-node 定义为可用的 GPU 数量,

  • 提供 script.py

  • 通过 ckpt_dir 指定模型检查点目录,

  • 使用 tokenizer_path 指定分词器的路径。

torchrun --nproc-per-node=NUM_GPUS_YOU_HAVE your_script.py \ 
         -- ckpt_dir /path/to/checkpoint \ 
         -- tokenizer_path /path/to/tokenizer

让我们运行 example_text_completion.py,其中初始提示为:

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

作者提供的图片

torchrun --nproc_per_node 1 example_text_completion.py \
    --ckpt_dir llama-2-7b/ \
    --tokenizer_path tokenizer.model \
    --max_seq_len 128 --max_batch_size 4

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

作者提供的图片

就这样。你成功了!现在你可以更改提示并尝试其他模型 😃。

简短回顾:

访问 Meta 官方网站 并申请下载权限。

访问 Llama 2 仓库 在 GitHub 上并下载 download.sh 脚本。

执行 download.sh 并提供通过电子邮件发送的签名 URL:

https://download.llamameta.net/*?YOUR_SIGNED_URL 并选择要下载的模型权重

准备环境

使用 torchrun 进行干预

2️⃣ 从 HuggingFace 下载 Llama 2

步骤 1:请求下载

首先,确保你在 Meta AI 网站 上用与你的 Hugging Face 帐户关联的确切电子邮件地址请求下载。接受许可条款可接受使用政策。完成后,你可以申请访问 Hugging Face 上的任何可用模型。

下面是当前可用模型的列表。

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

cc. Hugging Face

你将收到来自 HuggingFace 的确认访问许可的电子邮件。

步骤 2:从 HuggingFace 获取令牌

如果你还没有 HuggingFace 账户,你需要创建一个。创建账户后,登录 HuggingFace。登录后,找到右上角的Profile选项并选择Settings

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

图片由作者提供

选择Access Tokens选项并点击New token按钮以生成令牌。

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

图片由作者提供

只需复制令牌并返回到你的笔记本中。在下一步中,我们将看到如何访问和下载模型。

步骤 3:对 HuggingFace 进行身份验证

首先,安装 Hugging Face 开发的 huggingface_hub 模块,它使你能够与 Hugging Face Model Hub 进行交互。该中心托管各种预训练模型。请注意,huggingface_hub.login() 需要 ipywidgets 包。

!pip install huggingface_hub ipywidgets

然后,导入 huggingface_hub 并按如下方式登录 Hugging Face:

import huggingface_hub
huggingface_hub.login()

当你运行 huggingface_hub.login() 时,你会被要求提供你的 Hugging Face 身份验证令牌。成功认证后,你可以下载 llama 模型。粘贴你的令牌并点击登录。如果认证成功,你应该会看到以下消息。

在身份验证通过后,你可以继续下载其中一个 llama 模型。我会选择meta-llama/Llama-2–7b-chat-hf

步骤 4:下载 Llama 2 模型

首先安装所需的库。

你可以按如下方式检查可用的 GPU:

要检查你的 GPU 详细信息,如驱动版本、CUDA 版本、GPU 名称或使用指标,请在单元格中运行命令 !nvidia-smi

然后,为了下载模型,我们需要从 PyTorch 和 Hugging Face 的 Transformers 导入所有必要的库,初始化 Llama-2–7b 聊天模型及其标记器,并将它们保存到磁盘。请查看以下示例:

执行完单元格后,你应该会在huggingface目录下看到模型。

在进一步操作之前检查目录。里面应该有什么?

  • config.json:将其视为模型操作的手册。

  • pytorch_model.bin:这是你的模型在 PyTorch 格式中的“大脑”。

  • 必需的分词器文件:special_tokens_map.jsontokenizer_config.json 就像是你模型语言的词典。

  • tokenizer.model Llama 2 分词器

第 5 步:从磁盘加载 Llama 2 模型

如果你已经将 Llama 2 模型存储在磁盘上,你应该先加载它们。

为此,你需要:

  • LlamaForCausalLM 就像是 “Llama 2” 的大脑,

  • LlamaTokenizer 有助于 “Llama 2” 理解和分解单词。

  • 模型的路径

我们已经到了最后一步——测试干预。

第 6 步:使用 HuggingFace 管道进行干预

我们可以通过使用 HuggingFace transformers 的管道来评估干预。利用管道,你可以快速完成复杂任务。

  • text-generation:指定管道用于生成文本。

  • model:你用于文本生成的预训练模型。

  • tokenizer:用于处理输入文本和解码模型输出的分词器。

  • device_map=”auto”:这尝试在最佳可用设备上运行模型(例如,若可用则为 GPU,否则为 CPU)。

  • max_new_tokens=512:限制生成的输出为 512 个标记。

  • num_return_sequences=1:只请求一个生成的序列。

  • eos_token_id=tokenizer.eos_token_id:序列结束标记 ID。

以我的情况为例,我想要一些摇滚乐队的建议,结果非常好。

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

从 HuggingFace 下载 Llama 的简短回顾:

访问Meta 官方网站并申请下载权限。

从 HuggingFace 获取令牌

认证 HuggingFace

下载 Llama 2 模型

从磁盘加载 Llama 2 模型

使用 HuggingFace 管道进行干预

最后的思考:

在本教程中,我们已经看到如何将 Llama 2 模型下载到本地 PC。你还可以通过使用量化、蒸馏等方法进一步提升模型的性能,我将在后续文章中讨论这些方法。务必在新的虚拟环境中执行所有这些步骤。在此过程中,请确保监控计算机的内存使用情况。很多奇怪的错误可能隐藏在内存问题后面。如果你想下载量化模型,请注意你可能需要将 bitsandbytes 库降级到 0.39.1

曾经尝试在 Medium 上点击“点赞”按钮多次吗?❤️

让我们成为朋友吧!✋ 别忘了 订阅!

LinkedIn X 找到我!

如果你觉得我的故事很吸引人,并希望支持我的写作,我邀请你考虑成为 Medium 会员,你可以访问大量的生成 AI、数据工程和数据科学文章。

[## 使用我的推荐链接加入 Medium — Bildea Ana

作为 Medium 的会员,你的部分会员费将用于支持你阅读的作者,你也可以完全访问所有故事…

medium.com

查看我关于生成式人工智能、MLOps 和负责任人工智能的文章合集。

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

Ana Bildea, 博士

生成式人工智能

查看列表11 篇故事外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Ana Bildea, 博士

MLOps - 人工智能生产

查看列表4 篇故事外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Ana Bildea, 博士

负责任的人工智能

查看列表1 篇故事外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

类型提示数据框用于静态分析和运行时验证

原文:towardsdatascience.com/type-hinting-dataframes-for-static-analysis-and-runtime-validation-3dedd2df481d?source=collection_archive---------3-----------------------#2023-11-16

StaticFrame 如何实现全面的数据框类型提示

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

·

关注 发表在 Towards Data Science · 9 分钟阅读 · 2023 年 11 月 16 日

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

作者照片

自从 Python 3.5 引入类型提示以来,静态类型数据框通常只限于指定类型:

def process(f: DataFrame) -> Series: ...

这是不足的,因为它忽略了容器中包含的类型。一个 DataFrame 可能具有字符串列标签和三列整数、字符串和浮点值;这些特征定义了类型。具有此类类型提示的函数参数为开发人员、静态分析器和运行时检查器提供了理解接口期望的所有信息。 StaticFrame 2(我是该开源项目的主要开发者)现在允许这样做:

from typing import Any
from static_frame import Frame, Index, TSeriesAny

def process(f: Frame[   # type of the container
        Any,            # type of the index labels
        Index[np.str_], # type of the column labels
        np.int_,        # type of the first column
        np.str_,        # type of the second column
        np.float64,     # type of the third column
        ]) -> TSeriesAny: ...

所有核心的 StaticFrame 容器现在都支持泛型规范。虽然可以静态检查,但一个新的装饰器 [@CallGuard](http://twitter.com/CallGuard).check 允许在函数接口上对这些类型提示进行运行时验证。此外,使用 Annotated 泛型,新的 Require 类定义了一系列强大的运行时验证器,允许按列或按行数据进行检查。最后,每个容器都暴露了一个新的 via_type_clinic 接口,用于推导和验证类型提示。这些工具共同提供了一种完整的类型提示和验证 DataFrame 的方法。

泛型 DataFrame 的要求

Python 的内置泛型类型(例如 tupledict)需要指定组件类型(例如 tuple[int, str, bool]dict[str, int])。定义组件类型可以更准确地进行静态分析。尽管对于 DataFrame 也是如此,但很少有人尝试定义全面的 DataFrame 类型提示。

即使使用 pandas-stubs 包,Pandas 也不允许指定 DataFrame 组件的类型。Pandas DataFrame 允许广泛的原地变异,可能不适合进行静态类型化。幸运的是,StaticFrame 中提供了不可变的 DataFrame。

此外,直到最近,Python 用于定义泛型的工具并不适合 DataFrame。一个 DataFrame 具有可变数量的异构列类型对于泛型规范是一个挑战。使用在 Python 3.11 中引入的新的 TypeVarTuple(并在 typing_extensions 包中进行了回溯)更容易地对这种结构进行类型化。

TypeVarTuple 允许定义可以接受多个类型的泛型。 (详见 PEP 646)。借助这种新的类型变量,StaticFrame 可以定义一个通用的 Frame,它具有用于索引的 TypeVar、用于列的 TypeVar,以及用于零个或多个列类型的 TypeVarTuple

泛型 Series 定义了一个用于索引的 TypeVar 和一个用于值的 TypeVar。StaticFrame 的 IndexIndexHierarchy 也是泛型的,后者再次利用 TypeVarTuple 来定义每个深度级别的可变数量的组件 Index

StaticFrame 使用 NumPy 类型定义Frame的列类型,或者SeriesIndex的值类型。这允许严格指定大小的数值类型,如np.uint8np.complex128;或广泛指定类型的类别,如np.integernp.inexact。由于 StaticFrame 支持所有 NumPy 类型,因此对应关系是直接的。

使用泛型数据框定义接口

在上述示例的基础上,以下函数接口显示了将三列Frame转换为Series字典。通过组件类型提示提供了更多信息,函数的目的几乎显而易见。

from typing import Any
from static_frame import Frame, Series, Index, IndexYearMonth

def process(f: Frame[
        Any,
        Index[np.str_],
        np.int_,
        np.str_,
        np.float64,
        ]) -> dict[
                int,
                Series[                 # type of the container
                        IndexYearMonth, # type of the index labels
                        np.float64,     # type of the values
                        ],
                ]: ...

此函数处理来自开源资产定价(OSAP)数据集(公司级特征/个体/预测器)的信号表。每个表具有三列:安全标识符(标记为“permno”)、年和月(标记为“yyyymm”)以及信号(具有特定信号的名称)。

该函数忽略所提供Frame的索引(类型为Any),并创建由第一列“permno” np.int_值定义的组。返回以“permno”为键的字典,其中每个值是该“permno”的np.float64值的Series;索引是从np.str_“yyyymm”列创建的IndexYearMonth。(StaticFrame 使用 NumPy datetime64值来定义单位类型的索引:IndexYearMonth存储datetime64[M]标签。)

以下函数不返回dict,而是返回具有分层索引的SeriesIndexHierarchy泛型指定了每个深度级别的组件Index;在此处,外部深度是从“permno”列派生的Index[np.int_],内部深度是从“yyyymm”列派生的IndexYearMonth

from typing import Any
from static_frame import Frame, Series, Index, IndexYearMonth, IndexHierarchy

def process(f: Frame[
        Any,
        Index[np.str_],
        np.int_,
        np.str_,
        np.float64,
        ]) -> Series[                    # type of the container
                IndexHierarchy[          # type of the index labels
                        Index[np.int_],  # type of index depth 0
                        IndexYearMonth], # type of index depth 1
                np.float64,              # type of the values
                ]: ...

丰富的类型提示提供了一个自描述的接口,使功能明确。更好的是,这些类型提示可以用于与 Pyright(现在)和 Mypy(待完全支持TypeVarTuple)的静态分析。例如,使用两列np.float64Frame调用此函数将在编辑器中失败静态分析类型检查或提供警告。

运行时类型验证

静态类型检查可能不足够:运行时评估提供了更强的约束,特别是对于动态或未完全(或错误地)类型提示的值。

基于名为TypeClinic的新运行时类型检查器,StaticFrame 2 引入了[@CallGuard](http://twitter.com/CallGuard).check,一个用于类型提示接口运行时验证的装饰器。支持所有 StaticFrame 和 NumPy 泛型,并支持大多数内置 Python 类型,即使嵌套深度很深。以下函数添加了[@CallGuard](http://twitter.com/CallGuard).check装饰器。

from typing import Any
from static_frame import Frame, Series, Index, IndexYearMonth, IndexHierarchy, CallGuard

@CallGuard.check
def process(f: Frame[
        Any,
        Index[np.str_],
        np.int_,
        np.str_,
        np.float64,
        ]) -> Series[
                IndexHierarchy[Index[np.int_], IndexYearMonth],
                np.float64,
                ]: ...

现在使用 [@CallGuard](http://twitter.com/CallGuard).check 装饰,如果上述函数用于未标记的 np.float64 两列的 Frame,则会引发 ClinicError 异常,说明预期有三列,但只提供了两列,并且预期字符串列标签,但提供了整数标签。(要发出警告而不是引发异常,请使用 [@CallGuard](http://twitter.com/CallGuard).warn 装饰。)

ClinicError:
In args of (f: Frame[Any, Index[str_], int64, str_, float64]) -> Series[IndexHierarchy[Index[int64], IndexYearMonth], float64]
└── Frame[Any, Index[str_], int64, str_, float64]
    └── Expected Frame has 3 dtype, provided Frame has 2 dtype
In args of (f: Frame[Any, Index[str_], int64, str_, float64]) -> Series[IndexHierarchy[Index[int64], IndexYearMonth], float64]
└── Frame[Any, Index[str_], int64, str_, float64]
    └── Index[str_]
        └── Expected str_, provided int64 invalid

运行时数据验证

其他特性可以在运行时进行验证。例如,shapename 属性,或者索引或列上的标签顺序。StaticFrame 的 Require 类提供了一系列可配置的验证器。

  • Require.Name: 验证容器的 name 属性。

  • Require.Len: 验证容器的长度。

  • Require.Shape: 验证容器的 shape 属性。

  • Require.LabelsOrder: 验证标签的顺序。

  • Require.LabelsMatch: 验证包含标签而不考虑顺序。

  • Require.Apply: 将返回布尔值的函数应用于容器。

符合增长趋势,这些对象作为一个或多个额外参数提供给 Annotated 泛型的类型提示。 (有关详细信息,请参阅 PEP 593。)第一个 Annotated 参数引用的类型是后续参数验证器的目标。例如,如果将 Index[np.str_] 类型提示替换为 Annotated[Index[np.str_], Require.Len(20)] 类型提示,则会对与第一个参数关联的索引应用运行时长度验证。

扩展处理 OSAP 信号表的示例,我们可以验证列标签的期望。Require.LabelsOrder 验证器可以定义一系列标签,可选地使用 表示零个或多个未指定的标签。为了指定表的前两列标签为 “permno” 和 “yyyymm”,而第三个标签是可变的(取决于信号),可以在 Annotated 泛型内定义以下 Require.LabelsOrder

from typing import Any, Annotated
from static_frame import Frame, Series, Index, IndexYearMonth, IndexHierarchy, CallGuard, Require

@CallGuard.check
def process(f: Frame[
        Any,
        Annotated[
                Index[np.str_],
                Require.LabelsOrder('permno', 'yyyymm', ...),
                ],
        np.int_,
        np.str_,
        np.float64,
        ]) -> Series[
                IndexHierarchy[Index[np.int_], IndexYearMonth],
                np.float64,
                ]: ...

如果接口期望小集合的 OSAP 信号表,我们可以使用 Require.LabelsMatch 验证器验证第三列。该验证器可以指定必需的标签、标签集合(其中至少一个必须匹配)和正则表达式模式。如果只期望来自三个文件的表(即 “Mom12m.csv”、“Mom6m.csv” 和 “LRreversal.csv”),我们可以通过定义 Require.LabelsMatch 与集合来验证第三列的标签:

@CallGuard.check
def process(f: Frame[
        Any,
        Annotated[
                Index[np.str_],
                Require.LabelsOrder('permno', 'yyyymm', ...),
                Require.LabelsMatch({'Mom12m', 'Mom6m', 'LRreversal'}),
                ],
        np.int_,
        np.str_,
        np.float64,
        ]) -> Series[
                IndexHierarchy[Index[np.int_], IndexYearMonth],
                np.float64,
                ]: ...

Require.LabelsOrderRequire.LabelsMatch 都可以将函数与标签说明符关联,以验证数据值。如果验证器应用于列标签,则将一系列列值提供给函数;如果验证器应用于索引标签,则将一系列行值提供给函数。

类似于Annotated的用法,标签说明符被替换为一个列表,其中第一个项目是标签说明符,其余项目是返回布尔值的行或列处理函数。

为了扩展上述示例,我们可能需要验证所有“permno”值是否大于零,以及所有信号值(“Mom12m”、“Mom6m”、“LRreversal”)是否大于或等于-1。

from typing import Any, Annotated
from static_frame import Frame, Series, Index, IndexYearMonth, IndexHierarchy, CallGuard, Require

@CallGuard.check
def process(f: Frame[
        Any,
        Annotated[
                Index[np.str_],
                Require.LabelsOrder(
                        ['permno', lambda s: (s > 0).all()],
                        'yyyymm',
                        ...,
                        ),
                Require.LabelsMatch(
                        [{'Mom12m', 'Mom6m', 'LRreversal'}, lambda s: (s >= -1).all()],
                        ),
                ],
        np.int_,
        np.str_,
        np.float64,
        ]) -> Series[
                IndexHierarchy[Index[np.int_], IndexYearMonth],
                np.float64,
                ]: ...

如果验证失败,[@CallGuard](http://twitter.com/CallGuard).check将引发异常。例如,如果调用上述函数时遇到意外的第三列标签,将引发以下异常:

ClinicError:
In args of (f: Frame[Any, Annotated[Index[str_], LabelsOrder(['permno', <lambda>], 'yyyymm', ...), LabelsMatch([{'Mom12m', 'LRreversal', 'Mom6m'}, <lambda>])], int64, str_, float64]) -> Series[IndexHierarchy[Index[int64], IndexYearMonth], float64]
└── Frame[Any, Annotated[Index[str_], LabelsOrder(['permno', <lambda>], 'yyyymm', ...), LabelsMatch([{'Mom12m', 'LRreversal', 'Mom6m'}, <lambda>])], int64, str_, float64]
    └── Annotated[Index[str_], LabelsOrder(['permno', <lambda>], 'yyyymm', ...), LabelsMatch([{'Mom12m', 'LRreversal', 'Mom6m'}, <lambda>])]
        └── LabelsMatch([{'Mom12m', 'LRreversal', 'Mom6m'}, <lambda>])
            └── Expected label to match frozenset({'Mom12m', 'LRreversal', 'Mom6m'}), no provided match

TypeVarTuple的表达能力

如上所示,TypeVarTuple允许指定具有零个或多个异构列类型的Frame。例如,我们可以为两个浮点数或六种混合类型的Frame提供类型提示:

>>> from typing import Any
>>> from static_frame import Frame, Index

>>> f1: sf.Frame[Any, Any, np.float64, np.float64]
>>> f2: sf.Frame[Any, Any, np.bool_, np.float64, np.int8, np.int8, np.str_, np.datetime64]

虽然这适用于各种 DataFrame,但对宽型 DataFrame(例如具有数百列的 DataFrame)的类型提示可能会显得笨拙。Python 3.11 引入了一种新的语法,通过TypeVarTuple泛型提供可变范围的类型:tuple泛型别名的星号表达式。例如,要对具有日期索引、字符串列标签和任意列类型配置的Frame进行类型提示,我们可以星号解包零个或多个Alltuple

>>> from typing import Any
>>> from static_frame import Frame, Index

>>> f: sf.Frame[Index[np.datetime64], Index[np.str_], *tuple[All, ...]]

tuple星号表达式可以出现在类型列表中的任何位置,但只能有一个。例如,下面的类型提示定义了一个必须以布尔值和字符串列开始的Frame,但对后续的np.float64列数量有灵活的规定。

>>> from typing import Any
>>> from static_frame import Frame

>>> f: sf.Frame[Any, Any, np.bool_, np.str_, *tuple[np.float64, ...]]

类型提示工具

使用如此详细的类型提示可能会很具挑战性。为了帮助用户,StaticFrame 提供了便捷的运行时类型提示和检查工具。所有 StaticFrame 2 容器现在都具备via_type_clinic接口,允许访问TypeClinic功能。

首先,提供了将容器(例如完整的Frame)转换为类型提示的工具。via_type_clinic接口的字符串表示提供了容器类型提示的字符串表示;另外,to_hint()方法返回一个完整的泛型别名对象。

>>> import static_frame as sf
>>> f = sf.Frame.from_records(([3, '192004', 0.3], [3, '192005', -0.4]), columns=('permno', 'yyyymm', 'Mom3m'))

>>> f.via_type_clinic
Frame[Index[int64], Index[str_], int64, str_, float64]

>>> f.via_type_clinic.to_hint()
static_frame.core.frame.Frame[static_frame.core.index.Index[numpy.int64], static_frame.core.index.Index[numpy.str_], numpy.int64, numpy.str_, numpy.float64]

其次,提供了用于运行时类型提示测试的工具。via_type_clinic.check()函数允许根据提供的类型提示验证容器。

>>> f.via_type_clinic.check(sf.Frame[sf.Index[np.str_], sf.TIndexAny, *tuple[tp.Any, ...]])
ClinicError:
In Frame[Index[str_], Index[Any], Unpack[Tuple[Any, ...]]]
└── Index[str_]
    └── Expected str_, provided int64 invalid

为支持渐进类型,StaticFrame 定义了几个配置为每种组件类型的Any的泛型别名。例如,TFrameAny可用于任何Frame,而TSeriesAny用于任何Series。如预期的那样,TFrameAny将验证上面创建的Frame

>>> f.via_type_clinic.check(sf.TFrameAny)

结论

更好的 DataFrame 类型提示早已迫切需要。凭借现代 Python 类型工具和基于不可变数据模型构建的 DataFrame,StaticFrame 2 满足了这一需求,为优先考虑可维护性和可验证性的工程师提供了强大的资源。

Python 中的类型提示

原文:towardsdatascience.com/type-hints-in-python-1c096f44f375

你的代码将不再是一个谜

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

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

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

图片来源:Agence OllowebUnsplash

前几天我试图解读我过去编写的一个脚本如何工作。我知道它做了什么,它的解释和文档都很充分,但理解其工作原理更为麻烦。

代码繁琐且复杂,虽然有些注释,但缺乏适当的样式。这时我决定学习 PEP 8[1]并将其融入我的代码中。

如果你不知道 PEP 8 是什么,它基本上是一个提供指导方针、编码约定和最佳实践的文档,用于编写 Python 代码。

我们难以理解的代码的解决方案就在那儿。然而,我们大多数人从未花时间去阅读它并将这些指导方针融入日常实践中。

这需要时间和很多错误,但相信我,这是值得的。我学到了很多东西,代码现在开始变得更好了。

我最喜欢的发现之一是类型提示(或类型注释)——这将是今天帖子的主题。实际上,类型提示已经出现在 PEP 3107[2]中,回到 2006 年,并在 484[3]版本(2014 年)中重新审视并全面记录。从那时起,它在新的 PEP 版本中得到了多次改进,几乎成为经典。

所以,对于许多人来说,这是一个老话题但又非常新鲜。

什么是类型提示?

类型提示指示函数(同样适用于类方法)的输入和输出的数据类型。

许多 Python 用户抱怨的一个问题是我们可以自由地更改变量类型。在 C 语言和许多其他语言中,你需要声明一个变量并指定其类型:字符、整数等。

每个人都会有自己的看法——有些人可能喜欢 Python 的自由(以及对内存管理的影响),而另一些人则更喜欢传统语言的限制,因为这使他们的代码更具可读性。

无论如何。

类型提示旨在使你的 Python 代码更具可读性,我相信我们大多数人都很欣赏这种方法。然而,它们旨在澄清,它们并不强制变量的数据类型。

如果变量的类型不是我们预期的,程序不会引发错误。

为什么数据科学家应该考虑使用它们?

说实话,任何 Python 程序员都能从类型注解中受益。但这对数据科学家和其他与数据相关的专业人士来说可能更有意义。

那是因为我们处理各种数据。不仅仅是简单的字符串、列表或元组。我们使用的数据可能涉及超级复杂的结构,而类型提示有潜力节省我们很多时间,帮助我们了解函数预期的数据类型。

例如,假设我们有一个基于字典的结构。它的键是元组,值是具有字符串键和集合值的嵌套字典。

祝好运,希望几个月后你重访代码时能记住这一点!

好的一点是,类型提示非常容易理解和使用。我们没有理由不使用它们,而且不使用它们也没有任何好处。

那么,让我们继续并开始查看一些代码。

1. 首先概述

我将使用 Python 3.11,但大多数示例适用于之前的 Python 3 版本。

让我们使用一个示例和虚拟函数:

def meet_someone(name, age):
    return f"Hey {name}, I heard you are {age}. I'm 21 and from Mars!"

这很愚蠢。但它包含了我们需要的一切,我们现在将添加一些变体。

我们这里没有任何类型注解。只有一个接受两个参数并返回字符串的函数。我相信你知道 name 参数应该是字符串,而 age 参数则预期是整数(甚至是浮点数)。

但你知道,因为这是一个非常简单的函数。很少会如此简单。这就是为什么添加一些提示可能是明智的。

这是更新:

def meet_someone(name: str, age: int) -> str:
    return f"Hey {name}, I heard you are {age}. I'm 21 and from Mars!"

在这种情况下,我指定 age 应该是一个整数。让我们尝试运行这个函数:

>>> meet_someone('Marc', 22)
Hey Marc, I heard you are 22\. I'm 21 and from Mars!

为了说明我在上一节末尾说的内容:

>>> meet_someone('Marc', 22.4)
Hey Marc, I heard you are 22.4\. I'm 21 and from Mars!

即使 22.4 是浮点数(而不是预期的整数),它也工作得很好。如前所述,这些只是类型提示,仅此而已。

好的,基础知识已经涵盖。让我们开始制作一些变体。

2. 多种数据类型

假设我们希望允许整数和浮点数作为年龄参数的数据类型。我们可以使用Union,来自 typing 模块[4]:

from typing import Union
def meet_someone(name: str, age: Union[int, float]) -> str:
    return f"Hey {name}, I heard you are {age}. I'm 21 and from Mars!"

很简单:Union[int, float] 表示我们期望值是整数或浮点数。

然而,如果你使用的是 Python 3.10 或更高版本,还有另一种方法可以在不使用 Union 的情况下实现相同功能:

def meet_someone(name: str, age: int | float) -> str:
    return f"Hey {name}, I heard you are {age}. I'm 21 and from Mars!"

这只是一个简单的 OR 运算符。在我看来,更容易理解。

3. 高级数据类型

现在假设我们要处理更复杂的参数,比如字典或列表。接下来我们使用下一个函数,它使用了meet_someone函数:

def meet_them_all(people) -> str:
    msg = ""
    for person in people:
        msg += meet_someone(person, people[person])
    return msg

这仍然是一个非常简单的函数,但现在参数可能不像我们之前看到的那样清晰。如果你实际检查代码,你会看到我们期望的是一个字典。

但如果我们不需要猜测会不会更好?这就是类型提示的力量。

到此为止,如果我让你自己添加类型提示,你可能会做这样的事情:

def meet_them_all(people: dict) -> str:
    msg = ""
    for person in people:
        msg += meet_someone(person, people[person])
    return msg

这很好。但是我们还没有充分利用它的全部潜力。我们在这里指定了想要一个dict,但没有指定它的键和值的类型。这是一个改进版:

def meet_them_all(people: dict[str, int]) -> str:
    msg = ""
    for person in people:
        msg += meet_someone(person, people[person])
    return msg

在这里,我们说我们期望people是一个字典,键是字符串,值是整数。类似于{'Pol': 23, 'Marc': 21}

但记住我们想要接受年龄为整数或浮点数…

from typing import Union
def meet_them_all(people: dict[str, Union[int, float]]) -> str:
    msg = ""
    for person in people:
        msg += meet_someone(person, people[person])
    return msg

我们可以直接使用我们在第二部分学到的内容!很酷吧?

哦,它不仅适用于内置数据类型。你可以使用任何你想要的数据类型。例如,假设我们想要一个 Pandas 数据框的列表,用于一个不返回任何东西的函数:

import pandas as pd

Vector = list[pd.DataFrame]

def print_vector_length(dfs: Vector) -> None:
    print(f'We received {len(dfs)} dfs')

我在这里做的是声明数据类型,它只是一个数据框的列表,并将其用作类型提示。

此外,还有我们今天之前没有见过的,这个函数不会返回任何东西。这就是为什么输出数据类型是 None。

4. Optional 运算符

我们经常创建一些参数不是必需的——它们是可选的。

既然我们已经了解了这些,下面是如何编写一个带有可选参数的函数:

def meet_someone(name: str, 
                 age: int | float, 
                 last_name: None | str = None
                ) -> str:
    msg = f"Hey {name}{' ' + last_name if last_name else ''}, "\
          f"I heard you are {age}. I'm 21 and from Mars!"

    return msg

我已经更新了返回的消息,但重要的部分是最后一个参数last_name的类型提示。看看我这里怎么说:“last_name要么是一个字符串,要么是一个 Null 值。它是可选的,默认情况下是 None。”

这很酷,也很直观,但想象一下一个参数有几种可能的数据类型……这可能会很长。

这就是为什么 Optional 运算符在这里很有用,它基本上允许我们跳过None提示:

from typing import Optional

def meet_someone(name: str, 
                 age: int | float, 
                 last_name: Optional[str] = None
                ) -> str:
    msg = f"Hey {name}{' ' + last_name if last_name else ''}, "\
          f"I heard you are {age}. I'm 21 and from Mars!"

    return msg

结论与下一步

我希望我已经传达了类型提示在提高代码可读性和理解方面的有用性。不仅仅是为了我们的同事程序员,也是为了我们未来的自己!

我已经介绍了基础知识,但我建议你继续查看 typing 模块提供的内容。那里有几类可以让你的代码看起来更好。

**Thanks for reading the post!** 

I really hope you enjoyed it and found it insightful.

Follow me and subscribe to my mailing list for more 
content like this one, it helps a lot!

**@polmarin**

如果你想进一步支持我,请考虑通过下面的链接订阅 Medium 会员:这不会花费你额外的钱,但会帮助我完成这个过程。

[## 通过我的推荐链接加入 Medium - Pol Marin

阅读 Pol Marin 的每一篇故事(以及 Medium 上成千上万其他作家的文章)。你的会员费用直接支持 Pol…

medium.com

资源

[1] PEP 8 — Python 代码风格指南 | peps.python.org

[2] 3107 — 函数注解 | peps.python.org

[3] 484 — 类型提示 | peps.python.org

[4] typing — 对类型提示的支持

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值