置信区间与预测区间
你应该了解的一个小而重要的区别
https://medium.com/@jodancker?source=post_page---byline--a6b0c4816a92--------------------------------https://towardsdatascience.com/?source=post_page---byline--a6b0c4816a92-------------------------------- Jonte Dancker
·发表于Towards Data Science ·阅读时间 8 分钟·2024 年 11 月 24 日
–
(图片来自作者)
在许多与数据科学相关的任务中,我们希望知道我们对结果有多确定。知道我们能多大程度上信任结果有助于我们做出更好的决策。
一旦我们量化了结果的不确定性程度,我们可以将其用于:
-
情景规划,用于评估最佳情况和最差情况的情景
-
风险评估,评估对决策的影响
-
模型评估,比较不同模型及其性能
-
与决策者沟通,告知他们应该多大程度上相信结果
如何通过三行代码提升你的机器学习模型
towardsdatascience.com
不确定性来自哪里?
让我们来看一个简单的例子。我们想要估算德国一套 300 平方米房子的平均价格。收集所有 300 平方米房子的价格数据是不现实的。相反,我们将根据一个具有代表性的子集来计算平均价格。
配置 Pytest 以运行 Doctest
PYTHON 编程
学习如何在 pyproject.toml 中配置 pytest 以运行 doctests
https://medium.com/@nyggus?source=post_page---byline--a4e6b2821043--------------------------------https://towardsdatascience.com/?source=post_page---byline--a4e6b2821043-------------------------------- Marcin Kozak
·发表于 Towards Data Science ·10 分钟阅读·2024 年 4 月 2 日
–
由 Paul Hanaoka 提供的照片,来源于 Unsplash
现代 Python 项目通过 pyproject.toml
文件进行管理。你可以用它来管理常规项目和 Python 包,这使得该文件成为设置各种类型 Python 项目的通用工具。
pyproject.toml
文件可以包含你项目所需的一切内容,例如:
-
元数据,如项目名称、版本、作者、许可证、分类器、网址和描述(包括简短描述和长描述)。
-
开发环境和生产环境的配置,即依赖项和可选依赖项。
-
开发工具的配置,如
[black](https://github.com/psf/black)
、[ruff](https://docs.astral.sh/ruff/)
、[pylint](https://github.com/pylint-dev/pylint)
、[pytest](https://docs.pytest.org/)
和许多其他工具。
当项目变大时,你可以将大部分信息移到其他配置文件中,如 pytest.ini
、requirements.txt
、requirements_dev.txt
等等。但当 pyproject.toml
文件不会变得过长时,我会将所有信息保存在里面——这样可以保持项目根目录的简洁。
这只是关于 pyproject.toml
文件的基本信息。如果你想了解更多关于如何使用它的内容,可以从这里开始:
相互冲突的提示与构建企业级提示库的艺术
基于强化学习的提示库自动化策划
https://debmalyabiswas.medium.com/?source=post_page---byline--fb2a2b38849f--------------------------------https://towardsdatascience.com/?source=post_page---byline--fb2a2b38849f-------------------------------- Debmalya Biswas
·发表于Towards Data Science ·10 分钟阅读·2024 年 8 月 20 日
–
介绍
目前,提示是与大型语言模型(LLM)进行互动的主要方式。提示需要根据用户需求进行调整,提供正确的上下文和指导 —— 以最大化获得“正确”响应的机会。
这导致了提示工程学的兴起[1],作为一门专业学科,提示工程师系统地进行试验,记录他们的发现,以得出能够引发“最佳”响应的“正确”提示。成功的提示列表随后会组织成一个库,便于高效重用 —— 这被称为提示库。
不幸的是,策划和维护一个高质量的提示库仍然是具有挑战性的。提示库的总体目标是能够为给定的任务检索到最佳提示,而无需重复整个实验过程。然而,这一检索过程并不容易,主要是由于提示的重叠特性。
问题陈述
让我们尝试借助一些内容创作领域的提示(这是当前生成型人工智能应用最广泛的领域之一)来理解重叠提示的问题:
意识:概念、理论与神经网络
我们如何判断 AI 是否会变得有意识?
https://jshen9889.medium.com/?source=post_page---byline--661ce72a4979--------------------------------https://towardsdatascience.com/?source=post_page---byline--661ce72a4979-------------------------------- Stephanie Shen
·发布于Towards Data Science ·阅读时间 18 分钟·2024 年 3 月 13 日
–
图片由Alina Grubnyak提供,来源于Unsplash
意识是一个艰深的课题,困扰了哲学家们超过两千年。由于其主观性和难以捉摸性,直到最近几十年,它才成为科学研究的主题。随着人工智能(AI)的前所未有的进展,特别是大型语言模型(LLM)表现出的卓越性能,理解意识变得切实可行且迫在眉睫,不仅超越了哲学和学术辩论——我们如何判断 ChatGPT 是否具有意识?如果“人工”意识在可预见的未来出现,人类应如何为此做好准备?
在过去的三十年里,神经科学家们在理论化人类大脑中意识的内在运作方面取得了初步进展,这些进展主要基于大量的实验数据,且由两个主要因素推动。
首先,科学方法的进步使得科学家们能够研究清醒状态下的灵长类动物和人类大脑神经细胞群的活动。这些技术包括大脑成像技术、对数百个神经元同时进行神经生理学记录,以及由 AI 推动的神经网络建模。
其次,一群杰出的科学家做出了最初的大胆尝试,开始研究意识……
在人工智能训练中的同意问题
你是否应该控制有关你个人信息是否用于训练生成性人工智能的使用?
https://medium.com/@s.kirmer?source=post_page---byline--75a377f32f65--------------------------------https://towardsdatascience.com/?source=post_page---byline--75a377f32f65-------------------------------- Stephanie Kirmer
·发布于 Towards Data Science ·10 分钟阅读·2024 年 10 月 2 日
–
图片来源:Caroline Hall 在 Unsplash
我相信很多正在阅读这篇文章的你们都听说过最近的争议,LinkedIn 似乎开始在没有通知用户或更新隐私政策以允许此事的情况下,悄悄地使用用户个人数据来训练大型语言模型(LLM)。正如我当时在那边提到的,这让我感到相当震惊,因为考虑到我们对人工智能的监管态度和公众的普遍担忧,似乎这是一项相当惊人的举措。在最近的新闻中,在线培训平台 Udemy 做了一些类似的事情,他们悄悄地给讲师提供了一个小窗口,让他们可以选择是否让自己的个人数据和课程材料用于训练人工智能,并且现在已经关闭了这个窗口,不再允许选择退出。在这两种情况下,企业都选择使用了被动选择同意框架,这种框架有其优缺点。
为了说明这些情况下发生的事情,让我们从一些基本概念开始。像 Udemy 和 LinkedIn 这样的社交平台与用户相关的内容大致分为两种类型。一种是个人数据,指的是你提供的信息(或他们通过合理推测得出的信息),这些信息可以单独使用或组合使用来识别你在现实生活中的身份。另一种是你创建或发布的其他内容,包括你对他人帖子发表评论或点赞、你为课程创建的幻灯片等。有些内容可能不被视为个人数据,因为它无法单独识别你个人的身份。然而,这并不意味着这些内容对你不重要,但数据隐私通常不涵盖这些内容。各种司法管辖区的法律保护(如果存在)通常会涵盖个人数据,因此这就是我将在这里关注的重点。
LinkedIn 故事
LinkedIn 有一项关于一般内容(非个人数据)权利的通用且非常标准的政策,在该政策中,他们获得了非独占权利,允许他们使这些内容对用户可见,通常这也使他们的平台得以运作。
然而,有一项单独的政策管理数据隐私,该政策涉及到你的个人数据,而非你发布的帖子,这也是在 AI 训练情况下引发争议的部分。今天(2024 年 9 月 30 日),它写道:
我们如何使用你的个人数据将取决于你使用哪些服务、你如何使用这些服务以及你在 设置 中所做的选择。我们可能会使用你的个人数据来改进、开发和提供产品与服务,开发和训练人工智能(AI)模型,开发、提供和个性化我们的服务,并通过 AI、自动化系统和推理获得洞察,以便我们的服务能对你和其他人更有相关性和实用性。你可以在此查看 LinkedIn 的负责任 AI 原则 这里 并进一步了解我们对生成性 AI 的处理方式 这里。 了解更多 关于我们可能做出的推理,包括有关 你的年龄和性别 以及我们如何使用它们。
当然,在他们开始使用你的个人数据进行 AI 模型训练时,并没有这样表述。2024 年 9 月中旬的早期版本(感谢 Wayback Machine)是:
我们如何使用您的个人数据将取决于您使用的服务、您如何使用这些服务以及您在 设置 中所做的选择。我们利用我们所掌握的关于您的数据来提供和个性化我们的服务,包括通过自动化系统和我们做出的推断,从而使我们的服务(包括广告)能够对您和其他人更相关、更有用。
理论上,“通过自动化系统和我们做出的推断”可以在某些方面被扩展为包括 AI,但大多数用户可能很难接受这一点。然而,在 9 月 18 日之前,这段文字尚未更改时,人们已经注意到 LinkedIn 网站上添加了一个非常隐蔽的选择退出切换按钮,形状如下:
作者从 linkedin.com 截图
(我的切换按钮是关闭的,因为我更改了它,但默认设置是“开启”的。)
这强烈暗示了,在更新服务条款之前,LinkedIn 就已经在使用用户的个人数据和内容进行生成性 AI 开发。我们当然不能完全确定,但很多用户都有疑问。
Udemy 的故事
对于 Udemy 的情况,事实稍有不同(而且新的事实正在不断揭露),但根本问题类似。Udemy 的教师和学生将大量个人数据以及他们编写和创作的材料提供给 Udemy 平台,而 Udemy 提供基础设施和协调,使课程得以进行。
Udemy 在八月发布了讲师生成 AI 政策,其中包含了相当多的关于他们希望拥有的数据权利的细节,但对于他们的 AI 程序到底是什么,文中并未详细说明。通过阅读这份文档,我对他们计划训练的模型或已经在训练的模型,以及他们期望达成的结果感到非常不清楚。它没有区分个人数据(如讲师的肖像或个人详细信息)和其他内容(如讲座记录或评论)。这份政策显然涵盖了个人数据,而且他们对此非常开放,在他们的隐私政策中也有明确说明。在“我们如何使用您的数据”一节下,我们找到了:
改进我们的服务并开发新产品、服务和功能(所有数据类别),包括通过使用与 讲师生成 AI 政策 (讲师共享内容)一致的 AI;
它们所说的“所有数据类别”包括(但不限于):
-
账户数据:用户名、密码,但对于讲师来说,如果您提供,还包括“政府身份证信息、验证照片、出生日期、种族/民族和电话号码”
-
个人资料数据:“照片、头衔、简介、语言、网站链接、社交媒体资料、国家或其他数据。”
-
系统数据:“你的 IP 地址、设备类型、操作系统类型和版本、唯一设备标识符、浏览器、浏览器语言、域名及其他系统数据,以及平台类型。”
-
大致的地理数据:“国家、城市和地理坐标,基于你的 IP 地址计算。”
但是所有这些类别都可能包含个人数据,有时甚至是受保护的个人身份信息(PII),这些数据在全球多个司法辖区内都受到全面的数据隐私法规的保护。
生成式 AI 的推进似乎从今年夏天悄然开始,像 LinkedIn 一样,它采用了一个选择退出机制,所以不想参与的用户必须主动采取措施。至少从我们目前能看到的情况来看,他们似乎在更改隐私政策之前并没有开始这些操作,但 Udemy 采取了一个不同寻常的做法,即将选择退出设定为时间限制的事项,他们的讲师必须等到每年指定的时间段才能做出改变。这已经开始让用户感到措手不及,尤其是因为这个时间窗口的通知显然没有广泛共享。从美国的数据隐私角度来看,Udemy 在实施这个奇怪的时间限制之前,并没有做出任何新的或出乎意料的举措,只要他们更新了隐私政策,并且至少在开始使用个人数据进行训练之前尝试通知用户。
(还有一个问题是平台上教师对自己创作内容的知识产权问题,但这是我文章之外的问题,因为知识产权法与隐私法大相径庭。)
伦理
鉴于这些事实,并推测 LinkedIn 确实在通知用户之前就开始使用人们的数据来训练生成式 AI 模型,那么我们该如何看待这一切呢?如果你是这些平台的用户,这是否重要?你应该在乎这些吗?
我将建议,有几个重要的原因让我们关心这些数据使用模式的发展,不论你个人是否介意将自己的数据包含在训练集里。
你的个人数据构成了风险。
你的个人数据对这些公司来说很有价值,但它也构成了风险。当你的数据被转移并用于多种用途时,包括训练 AI,随着数据副本的增多,泄露或数据丢失的风险也随之增加。在生成式 AI 中,还有一个风险是训练不充分的大型语言模型(LLM)可能会在其输出中意外地泄露个人信息。每一个使用你的数据进行训练的新模型,都是数据以这种方式无意曝光的机会,尤其是因为许多从事机器学习的人对保护数据的最佳实践知之甚少。
知情同意的原则应该被认真对待。
知情同意是生物医学研究和医疗保健领域的一个著名基石原则,但在其他行业中并没有得到足够的关注。这个理念是,每个人都有不应被侵犯的权利,除非该个人同意,并且在完全掌握相关事实的基础上,才能做出慎重的决定。如果我们认为个人数据的保护是这一系列权利的一部分,那么这些情况应该要求知情同意。如果我们对公司忽视这些权利视而不见,就相当于在设定一个先例,表示这些违规行为并不重要,更多公司会继续以同样的方式行事。
黑暗模式可以构成强迫行为。
在社会科学中,关于选择加入(opt-in)和选择退出(opt-out)作为框架的研究相当多。通常,将这样一个敏感问题设置为选择退出,是为了让人们难以行使他们真正的选择,可能是因为它难以操作,或者因为他们甚至没有意识到自己有选择的余地。实体通过设计人们表达选择的界面,能够鼓励甚至强迫行为朝着有利于商业的方向发展。这种具有强制性倾向的设计属于我们所说的“黑暗模式”用户体验设计。在 Udemy 将选择退出的时限限制为一个时间窗口时,这个问题变得更加严重。
这不仅仅涉及图像和多媒体,还包括文本。
这可能不会立刻引起每个人的注意,但我只想强调,当你将个人照片或任何形式的个人照片上传到这些平台时,那就成为了他们收集的关于你的数据的一部分。即便你可能并不在意你的 LinkedIn 帖子上的评论被用于模型训练过程,你可能会更在意你的面部图像被用于训练生成深度伪造视频(deepfake)之类的生成性 AI 模型。也许你不在乎!但当你考虑你的数据被用于生成性 AI 时,请记住这一点。
该怎么办?
目前,不幸的是,受影响的用户在面对这些不道德商业行为时,几乎没有什么反应的选择。
如果你意识到你的数据正在被用来训练生成式 AI,并且你希望避免这种情况,可以选择退出,如果企业允许的话。然而,如果(如同 Udemy 的情况)它们限制了这一选项,或者根本不提供这个选项,你就需要关注监管领域。许多美国人可能没有太多的追索权,但像 CCPA 这样的全面数据隐私法往往会稍微涉及此类问题。(查看 IAPP 追踪器,了解你所在州的状态)。CCPA 通常允许选择退出框架,其中用户没有采取行动将被视为同意。然而,CCPA 确实要求选择退出不应过于困难。例如,不能要求通过邮寄纸质信件来提交选择退出请求,而你本可以通过电子邮件确认同意。公司还必须在 15 天内对选择退出请求作出回应。Udemy 将选择退出限制为每年特定时间范围,这符合要求吗?
但让我们退一步思考。如果你没有意识到你的数据正在被用来训练 AI,而是在事后才发现,那你该怎么办?好吧,CCPA 允许同意是被动的,但它确实要求你被告知个人数据的使用情况。隐私政策中的披露通常已经足够,因此,鉴于 LinkedIn 一开始没有这样做,这可能成为一些法律挑战的原因。
值得注意的是,欧盟居民可能不需要担心这些问题,因为保护他们的法律更加明确和一致。我之前写过关于欧盟 AI 法案的文章,该法案对 AI 应用有相当多的限制,但它并未真正涉及同意问题或数据如何用于训练。相反,GDPR 更可能保护人们免受类似这里发生的事情。在这项法律下,欧盟居民必须被告知并要求明确同意,而不仅仅是提供选择退出的机会。他们还必须有能力撤回对个人数据使用的同意,而我们不知道这种行动的时限是否符合要求,因为GDPR 的规定是,要求停止处理某人个人数据的请求必须在一个月内处理。
经验教训
我们目前并不清楚 Udemy 和 LinkedIn 实际上在如何使用这些个人数据,除了它们正在训练生成式 AI 模型的这一大致想法之外,但我认为我们可以从这两个新闻故事中学到的一点是,保护个人数据权利不能仅仅依赖于公司利益,必须有政府的介入。对于那些注重通知客户并确保容易退出的道德企业来说,将会有很多其他公司规避规则,做最少的事情或更少,除非人们的权利得到了执行的保护。
阅读更多我的作品,访问www.stephaniekirmer.com.
深入阅读
www.datagrail.io/blog/data-privacy/opt-out-and-opt-in-consent-explained
[## LinkedIn 正在使用用户数据训练 AI,在更新其服务条款之前
多位 LinkedIn 用户在周三注意到一个设置,显示 LinkedIn 正在使用用户数据来改进其…
www.404media.co](https://www.404media.co/linkedin-is-training-ai-on-user-data-before-updating-its-terms-of-service/?source=post_page-----75a377f32f65--------------------------------) [## 美国各州隐私立法追踪器
这个工具追踪全国范围内的美国各州隐私法案,帮助我们的成员跟进…
iapp.org](https://iapp.org/resources/article/us-state-privacy-legislation-tracker/?source=post_page-----75a377f32f65--------------------------------)
隐私政策
web.archive.org/web/20240917144440/https://www.linkedin.com/legal/privacy-policy#use
www.linkedin.com/blog/member/trust-and-safety/updates-to-our-terms-of-service-2024
www.linkedin.com/legal/privacy-policy#use
www.udemy.com/terms/privacy/#section1
GDPR 与 CCPA
[## GDPR 合规性检查清单 - GDPR.eu
使用此 GDPR 合规性检查清单来规划贵组织的数据隐私和安全措施。记录您的步骤…
gdpr.eu](https://gdpr.eu/checklist/?source=post_page-----75a377f32f65--------------------------------) [## 加利福尼亚消费者隐私法案(CCPA)
更新于 2024 年 3 月 13 日,《2018 年加利福尼亚消费者隐私法案》(CCPA)赋予消费者更多对其个人信息的控制权…
oag.ca.gov](https://oag.ca.gov/privacy/ccpa?source=post_page-----75a377f32f65--------------------------------)
受限句子生成使用吉布斯采样和 BERT
一种快速有效的方法,通过使用公共预训练模型,从给定的关键词生成流畅的句子
https://medium.com/@sergeyvilov?source=post_page---byline--8d326b9027b1--------------------------------https://towardsdatascience.com/?source=post_page---byline--8d326b9027b1-------------------------------- Sergey Vilov
·发布于 Towards Data Science ·阅读时间 10 分钟·2024 年 7 月 19 日
–
图片来自 Brett Jordan 在 Unsplash
大型语言模型,如 GPT,在自由形式文本生成方面取得了前所未有的成果。它们广泛用于撰写电子邮件、广告文案或讲故事。然而,它们在受限文本生成方面的成功仍然有限[1]。
受限文本生成是指生成具有特定属性的句子,例如情感、时态、模板或风格。我们将考虑一种特定的受限文本生成方式,即基于关键词的生成。在这个任务中,要求模型生成包含给定关键词的句子。根据应用的不同,这些句子应该(a)包含所有关键词(即保证高覆盖率)(b)语法正确(c)符合常识(d)展示词汇和语法的多样性。
对于自回归的前向生成模型,如 GPT,受限生成尤其具有挑战性。这些模型一次一个地从左到右顺序生成标记。由于设计原因,它们缺乏对生成序列的精确控制,并且难以支持在输出中的任意位置上的约束或涉及多个关键词的约束。因此,这些模型通常表现出较差的覆盖度(a)和多样性(d),但能够生成流畅的句子(b,c)。尽管一些采样策略,如动态束分配[2],专门设计来改善前向模型的受限文本生成,但在独立测试中,它们的结果表现较差[3]。
另一种方法[4],被称为 CGMH,包含通过对现有序列执行基本操作(如单词删除、插入或替换)来迭代地构建句子。初始序列通常是给定关键词的有序序列。由于搜索空间极其庞大,这类方法往往难以在合理的时间内生成有意义的句子。因此,尽管这些模型可能确保良好的覆盖度(a)和多样性(d),它们可能未能满足流畅性要求(b,c)。为了解决这些问题,曾建议通过引入可微分的损失函数[5]或预训练神经网络[6]来引导采样器,从而限制搜索空间。然而,与 CGMH 相比,这些调整并未带来任何在实践中显著的改进。
在以下内容中,我们将提出一种基于给定关键词生成句子的全新方法。该方法的核心思想是通过从一个正确的句子出发,限制搜索空间并减少可能的操作集合。事实证明,当仅考虑替换操作时,BERT 模型通过吉布斯采样提供了一种便捷的方式来生成期望的句子。
从 BERT 进行吉布斯采样
通过从BERT进行吉布斯采样来采样句子,最早是在[7]中提出的。在这里,我们将这一思路应用于受限句子的生成。
为了简化理论介绍,我们将从解释 CGMH 方法的基础开始[4],该方法使用Metropolis-Hastings 算法从满足给定约束条件的句子分布中进行采样。
采样器从给定的关键词序列开始。在每一步中,选择当前句子中的一个随机位置,并执行三种可能操作之一(按概率p=1/3 选择):插入、删除或替换单词。之后,从相应的提议分布中采样候选句子。特别地,替换的提议分布具有以下形式:
(图片来源:作者)
其中x是当前句子,*x’*是候选句子,w_1…w_n是句子中的单词,w^c是提议的单词,V是词典大小,π是采样分布。然后,可以使用接受率来决定是否接受或拒绝候选句子:
(图像来自作者)
为了获得句子的概率,作者建议使用一个简单的基于 seq2seq LSTM 的网络:
(图像来自作者)
其中*p_LM(x)*是语言模型给出的句子概率,*χ(x)*是一个指示函数,当所有关键字都包含在句子中时值为 1,否则为 0。
当施加关键字约束时,生成从给定的关键词序列开始。这些词然后被排除在删除和替换操作之外。在一段时间后(预热期),生成过程会收敛到一个平稳分布。
如上所述,这类方法的一个弱点是庞大的搜索空间,这使得它们无法在合理的时间内生成有意义的句子。我们现在将通过完全消除插入和删除操作来缩小搜索空间。
好吧,这和吉布斯采样以及 BERT 有什么关系呢?
引用 Wikipedia,吉布斯采样在联合分布未知或难以直接从中采样时使用,但每个变量的条件分布是已知的,并且易于(或者至少,更容易)从中采样。
BERT 是一个基于变换器的模型,旨在通过同时考虑左右上下文来进行深度双向表示的预训练,使其能够根据周围的上下文理解一个单词的意义。对我们来说,特别重要的是 BERT 是以掩码语言模型的方式进行训练的,即给定句子中的所有其他单词(标记),它预测掩码单词(标记)。如果仅掩盖一个单词,那么模型直接提供条件概率p(w_c|w_1,…,w_{m-1},w_{m+1},…,w_n)。请注意,这只有在 BERT 的双向特性下才能实现,因为它能够访问掩码单词左右两侧的标记。另一方面,联合概率p(w_1,…w_n)并不直接从 BERT 输出中获得。看起来像是一个吉布斯采样的应用案例,对吧?重新编写g(x’|x),可以得到:
(图像来自作者)
请注意,只要考虑替换操作,接受率总是 1:
(图像来自作者)
所以,替换实际上是一个吉布斯采样步骤,其中提议分布由 BERT 模型直接提供!
实验
为了说明这个方法,我们将使用来自Hugging Face的一个预训练 BERT 模型。为了独立评估句子的流畅度,我们还将使用GPT2模型计算句子的困惑度。
让我们从加载所有必要的模块和模型到内存开始:
from transformers import BertForMaskedLM, AutoModelForCausalLM, AutoTokenizer
import torch
import torch.nn.functional as F
import numpy as np
import pandas as pd
device = torch.device('cpu') #works just fine
#Load BERT
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
model = BertForMaskedLM.from_pretrained("bert-base-uncased")
model.to(device)
#Load GPT2
gpt2_model = AutoModelForCausalLM.from_pretrained("gpt2") #dbmdz/german-gpt2
gpt2_tokenizer = AutoTokenizer.from_pretrained("gpt2")
gpt2_tokenizer.padding_side = "left"
gpt2_tokenizer.pad_token = gpt2_tokenizer.eos_token
然后我们需要定义一些重要的常量:
N_GIBBS_RUNS = 4 #number of runs
N_ITR_PER_RUN = 500 #number of iterations per each run
N_MASKED_WORDS_PER_ITR = 1 #number of masked tokens per iteration
MIN_TOKENS_PROB = 1e-3 #don't use tokens with lower probability for replacement
由于我们只会使用替换操作,我们需要选择一个包含所需关键词的初始句子。假设是:
我常常梦想着有一座位于海边的宽敞别墅。
每个人在某个时候肯定都曾梦想过这个……我们将随意选择梦想和海作为关键词。
initial_sentence = 'I often dream about a spacious villa by the sea .'
words = initial_sentence.split(' ')
keyword_idx = [2,9]
keyword_idx.append(len(words)-1) # always keep the punctuation mark at the end of the sentence
现在我们准备好进行采样:
def get_bert_tokens(words, indices):
sentence = " ".join(words)
masked_sentence = [word if not word_idx in indices else "[MASK]" for word_idx,word in enumerate(words) ]
masked_sentence = ' '.join(masked_sentence)
bert_sentence = f'[CLS] {masked_sentence} [SEP] '
bert_tokens = tokenizer.tokenize(bert_sentence)
return bert_tokens
n_words = len(words)
n_fixed = len(keyword_idx)
generated_sent = []
for j in range(N_GIBBS_RUNS):
words = initial_sentence.split(' ')
for i in range(N_ITR_PER_RUN):
if i%10==0:
print(i)
#choose N_MASKED_WORDS_PER_ITR random words to mask (excluding keywords)
masked_words_idx = np.random.choice([x for x in range(n_words) if not x in keyword_idx], replace=False, size=N_MASKED_WORDS_PER_ITR).tolist()
masked_words_idx.sort()
while len(masked_words_idx)>0:
#reconstruct successively each of the masked word
bert_tokens = get_bert_tokens(words, masked_words_idx) #get tokens from tokenizer
masked_index = [i for i, x in enumerate(bert_tokens) if x == '[MASK]']
indexed_tokens = tokenizer.convert_tokens_to_ids(bert_tokens)
segments_ids = [0] * len(bert_tokens)
tokens_tensor = torch.tensor([indexed_tokens]).to(device)
segments_tensors = torch.tensor([segments_ids]).to(device)
with torch.no_grad():
outputs = model(tokens_tensor, token_type_ids=segments_tensors)
predictions = outputs[0][0]
reconstruct_pos = 0 #reconstruct leftmost masked token
probs = F.softmax(predictions[masked_index[reconstruct_pos]],dim=0).cpu().numpy()
probs[probs<MIN_TOKENS_PROB] = 0 #ignore low probabily tokens
if len(probs)>0:
#sample a token using the conditional probability from BERT
token = np.random.choice(range(len(probs)), size=1, p=probs/probs.sum(), replace=False)
predicted_token = tokenizer.convert_ids_to_tokens(token)[0]
words[masked_words_idx[reconstruct_pos]] = predicted_token #replace the word in the sequence with the chosen token
del masked_words_idx[reconstruct_pos]
sentence = ' '.join(words)
with torch.no_grad():
inputs = gpt2_tokenizer(sentence, return_tensors = "pt")
loss = gpt2_model(input_ids = inputs["input_ids"], labels = inputs["input_ids"]).loss
gpt2_perplexity = torch.exp(loss).item()
#sentence = sentence.capitalize().replace(' .','.')
gpt2_perplexity = int(gpt2_perplexity)
generated_sent.append((sentence,gpt2_perplexity))
df = pd.DataFrame(generated_sent, columns=['sentence','perplexity'])
现在让我们看看困惑度图:
对于采样句子的 GPT2 困惑度(图片由作者提供)。
这里有两点需要注意。首先,困惑度从一个相对较小的值开始(困惑度=147)。这是因为我们用一个对 GPT2 来说不显得尴尬的有效句子初始化了采样器。基本上,困惑度不超过起始值(虚线红线)的句子可以视为通过了外部检查。其次,后续的样本是相关的。这是Gibbs 采样的一个已知特性,也是为什么通常建议每取第k个样本的原因。
实际上,在 2000 个生成的句子中,我们得到了 822 个独特句子。它们的困惑度从 60 到 1261 不等,341 个样本的困惑度低于初始句子的困惑度:
GPT2 困惑度在独特句子中的分布(图片由作者提供)。
这些句子看起来怎么样?让我们随机挑选一部分:
生成的困惑度低于起始值的句子随机子集(图片由作者提供)。
这些句子看起来确实相当流畅。请注意,所选的关键词(梦想和海)在每个句子中都出现了。
如果我们不设置任何关键词,看看会发生什么也是很有趣的。让我们随机选取一些使用空关键词集生成的句子:
使用未固定关键词生成的句子的随机子集(图片由作者提供)。
所以,这些句子也看起来相当流畅和多样化!事实上,使用一个空的关键词集实际上会将 BERT 变成一个随机句子生成器。不过需要注意的是,所有这些句子都有 10 个单词,就像初始句子一样。原因是 BERT 模型不能随意改变句子的长度。
为什么我们需要运行采样器 N_GIBBS_RUNS=4 次,单次运行不足以吗?实际上,运行多次是必要的,因为 Gibbs 采样器可能会陷入局部最小值[7]。为了说明这一情况,我们计算了在单次运行采样器 2000 次迭代时和在每 500 次迭代时重新初始化采样器的情况下,累积词汇量(生成的句子中迄今使用的不同单词数量):
在单次运行中运行 Gibbs 采样 2000 次迭代和在 4 次运行中每次 500 次迭代时的累积词汇量(作者提供的图片)
可以清楚地看到,单次运行在约 1500 次迭代时陷入困境,采样器无法在此点之后生成具有新单词的句子。相反,每 500 次迭代重新初始化采样器有助于摆脱这一局部最小值,并提高生成句子的词汇多样性。
结论
总的来说,所提出的方法从包含给定关键词的句子开始生成逼真的句子。生成的句子确保 100% 覆盖(a),语法正确(b),符合常识(c),并提供词汇多样性(d)。此外,该方法非常简单,可以与公开可用的预训练模型一起使用。该方法的主要弱点当然是其依赖于满足给定约束的起始句子。首先,起始句子应该由专家或其他外部来源提供。其次,虽然确保生成语法正确的句子,但也限制了输出的语法多样性。一个可能的解决方案是通过挖掘可靠的句子数据库提供几个输入句子。
参考文献
[1] Garbacea, Cristina, and Qiaozhu Mei. “为什么受限神经语言生成特别具有挑战性?.” arXiv 预印本 arXiv:2206.05395 (2022).
[2] Post, Matt, and David Vilar. “使用动态束分配进行快速词汇受限解码的神经机器翻译.” arXiv 预印本 arXiv:1804.06609 (2018).
[3] Lin, Bill Yuchen, 等. “CommonGen: 用于生成常识推理的受限文本生成挑战.” arXiv 预印本 arXiv:1911.03705 (2019).
[4] Miao, Ning, 等. “Cgmh: 通过 Metropolis-Hastings 采样进行受限句子生成.” 人工智能 AAAI 会议论文集. Vol. 33. №01. 2019.
[5] Sha, Lei. “梯度引导的无监督词汇受限文本生成.” 2020 年自然语言处理实证方法会议论文集(EMNLP). 2020.
[6] He, Xingwei, and Victor OK Li. “告诉我如何修改:通过 xlnet 改进词汇受限句子生成.” 人工智能 AAAI 会议论文集. Vol. 35. №14. 2021.
[7] 王艾利克斯,赵庆贤. “BERT 有嘴巴,它必须说话:BERT 作为马尔可夫随机场语言模型.” arXiv 预印本 arXiv:1902.04094 (2019).
离散优化中的构造性启发式
使用 Python 示例为组合优化问题获得初始解
https://medium.com/@bruscalia12?source=post_page---byline--f6a41bf26d01--------------------------------https://towardsdatascience.com/?source=post_page---byline--f6a41bf26d01-------------------------------- Bruno Scalia C. F. Leite
·发布于Towards Data Science ·阅读时间 11 分钟·2024 年 5 月 30 日
–
表示构造性启发式从基集选择元素的过程。(图像来源:作者)
离散优化或组合优化是运筹学(OR)和计算机科学中的一个重要研究领域,致力于从有限的可能解集中识别最佳(或合适的)解。其应用包括车辆路径规划、操作调度和网络设计等。通常,这类问题无法通过精确方法在可接受的运行时间内解决。因此,启发式方法提供了一个有趣的替代方案,能够在合理的计算时间内提供快速且质量较好的解决方案,指导操作。
构造性启发式不仅可以作为独立的技术使用,还可以与其他算法结合,以提高它们的运行时间、成本函数或其他性能方面。例如,为混合整数规划(MIP)求解器提供初始解,可以建立一个双重界限,帮助剪枝搜索空间。此外,这个初始解还可以使求解器更有效地结合局部搜索启发式方法,从而可能加速收敛并提升整体解的质量。
在这篇文章中,你将找到离散优化的基本定义,并介绍了构造性启发式方法。文章将通过 Python 示例来阐释相关主题,并应用于背包问题和最大独立集问题。我们将分析随机选择和贪婪选择元素的过程,并通过这些方法来创建解决方案。
除了几个其他优化示例外,针对这些问题的完整代码可以在我的GitHub 仓库中找到。
离散优化
广义上,数值优化问题旨在找到目标函数f的最佳值,目标函数f是决策变量x的函数,且可能受到某些等式和不等式约束的限制,这些约束也是x的函数。目标可以定义为最小化或最大化。
离散优化是指一类优化问题,其中决策变量只能取离散值。因此,面临的是一个有限的(尽管可能很大)可行解集合S,必须从中选择一个可行解,使目标函数达到最佳。
解构建
许多组合优化问题的算法通过从头构建解决方案,逐步增加基础集合中的元素,在每一步中将一个基础集合元素添加到正在构建的部分解中。每一步添加的基础集合元素不能与之前添加的一个或多个元素结合,导致不可行性*(Resende & Ribeiro, 2016)*。
假设我们有一个元素的基础集合E,这些元素可能用于构建一个解* S*。假设F是E的一个子集,将F中的元素加入到部分解* S*中不会导致不可行,并且会改善整体结果。一个构造性启发式算法的伪代码可以描述如下。
function constructive(E){
S = {}
F = {i for i in E if S union {i} is feasible}
while F is not empty{
i = choose(F)
S = S union {i}
F = {i for i in E if S union {i} is feasible}
}
return S
}
下一步选择添加到解决方案中的元素可能会根据问题和采用的策略有所不同。在某些情况下,选择一个能够在部分解中带来最佳即时效果的元素可能是一个有趣的替代方案,而在其他情况下,随机效果可能是可取的。我们将在本文的剩余部分中对两种方法在两个不同问题中的表现进行比较。
在一些问题中,即使采用贪心的增量方法,也有精确的构造算法可以在多项式时间内完成,正如本文将展示的那样。一个有趣的例子是最小生成树(MST)问题。然而,对于本文将介绍的问题,情况并非如此。
背包问题
在背包问题中,给定一组具有重量和价值属性的物品,必须选择最有价值的物品放入一个预定义容量的背包中,且所选物品的总重量不能超过背包容量。在这个问题中,我们可以将可用物品视为我们的基础集合。
那么,让我们创建一个 Python 类来表示我们每个可用物品。
class Item:
index: int
weight: float
value: float
density: float
selected: bool
def __init__(self, index, weight, value) -> None:
self.index = index
self.weight = weight
self.value = value
self.density = value / weight
self.selected = False
@classmethod
def from_dict(cls, x: dict):
index = x["index"]
weight = x["weight"]
value = x["value"]
return cls(index, weight, value)
我们还创建了属性density
,表示给定物品的“每单位重量的价值”比率,index
表示它的标识符,selected
表示该物品是否为我们最终解的一部分。classmethod from_dict
用于从一个包含index、weight和value键的 Python dict
初始化一个新物品。
现在,让我们考虑背包问题的一种构造性启发式抽象。它的初始化参数是背包容量和一个物品列表(以字典形式提供)。这两个参数应作为我们类的属性,以便在解决过程中使用。
from typing import Dict, List, Union
class BaseConstructive:
items: List[Item]
capacity: float
solution: List[Item]
def __init__(self, capacity: float, items: List[Dict[str, Union[int, float]]]) -> None:
self.items = []
self.capacity = capacity
for new_element in items:
item = Item.from_dict(new_element)
self.items.append(item)
self.solution = []
@property
def cost(self):
return sum(i.value for i in self.solution)
一个简单的解决过程可以遍历我们的物品集合,并在下一个物品的重量小于或等于剩余容量时将其包括在解中。
class BaseConstructive:
# Check previous definition
def solve(self):
remaining = self.capacity
for item in self.items:
if remaining >= item.weight:
item.selected = True
self.solution.append(item)
remaining = remaining - item.weight
然而,这种方法可能会导致较差的解。假设在列表的开头有一个重量较大的物品,但其价值较小。它将被纳入解中,占据了本可以用于更有价值物品的可用空间。
更好的选择可能是先按密度对物品进行排序,然后执行之前的步骤,依次从输入中选择下一个物品,前提是它能够适应剩余空间。这将引导我们进入贪心选择方法。
贪心选择
贪心近似算法是一种迭代算法,通过逐步构建部分解来产生结果。每次迭代都会对当前部分解进行局部最优或次优的增量,从而在算法结束时得到一个全局次优解*(Wan, 2013)*。
在背包问题的背景下,我们可以像之前建议的那样,基于密度优先选择下一个元素。在这种情况下,贪心方法并不能保证解的最优性,但它可以成为快速且高质量结果的有趣替代方案。在我们的 Python 代码中,我们可以通过在应用解法之前对物品进行排序来实现这一点。
class GreedyConstructive(BaseConstructive):
def solve(self):
self.items.sort(key=lambda x: x.density, reverse=True)
super().solve()
在我的GitHub 仓库中,你可能会找到一个包含 10 个物品的实例,我对这些物品应用了两种方法。基于原始输入序列的选择产生的解总价值为 68,而基于密度的选择则得到了总价值 91。我会选择贪心方法,因为它能够提供高质量且快速的解。
最大独立集问题
下一个例子是一个经典的子集划分问题,其中我们的目标是从无向图G(V, E)中找到一个元素子集,使得子集中的元素数最多且没有任何一对元素之间存在边连接。
让我们从创建类来处理此问题的图元素开始。Node
类将用于表示我们无向图中的一个顶点(或节点)。它将具有以下属性:
-
neighbors
:一个邻接顶点的列表 -
index
:它的标识符 -
selected
:一个布尔值,用于表示何时将其包含在解集中。
每当从我们的可行元素子集删除一个Node
实例时,我们必须将其从邻居的邻居列表中移除,因此我们创建一个delete
方法以简化这一过程。
属性degree
计算给定节点的邻居数量,并将作为我们选择下一个元素的贪心方法的标准。
import copy
from typing import Dict, List, Optional, Tuple
class Node:
neighbors: List['Node']
index: int
selected: bool
def __init__(self, index):
self.index = index
self.neighbors = []
self.selected = False
def __repr__(self) -> str:
return f"N{self.index}"
def add_neighbor(self, node: 'Node'):
if node not in self.neighbors:
self.neighbors.append(node)
def delete(self):
for n in self.neighbors:
n.neighbors.remove(self)
@property
def degree(self):
return len(self.neighbors)
现在,让我们创建我们的Graph
类。它应该通过一个边的列表和一个可选的节点列表来实例化。它应该有一个属性N
,该属性是一个包含现有节点(或顶点)的字典。
属性queue
应返回一个节点列表,这些节点尚未被选中,以便我们在每一步的构造性启发式方法中考虑将其纳入解集中。
每当选择一个新的Node
实例时,应该调用select
方法,它会更改其selected
属性并调用其delete
方法。
class Graph:
N: Dict[int, Node]
def __init__(
self,
edges: List[Tuple[int, int]],
nodes: Optional[List[int]] = None
):
# Start the set
if nodes is None:
self.N = {}
else:
self.N = {i: Node(i) for i in nodes}
# Include all neighbors
for i, j in edges:
self._new_edge(i, j)
@property
def active_nodes(self):
return [node for node in self.N.values() if node.selected]
@property
def inactive_nodes(self):
return [node for node in self.N.values() if not node.selected]
@property
def nodelist(self):
return list(self.N.values())
@property
def queue(self):
return [n for n in self.nodelist if not n.selected]
def _new_node(self, i: int):
if i not in self.N:
self.N[i] = Node(i)
def _new_edge(self, i: int, j: int):
self._new_node(i)
self._new_node(j)
self.N[i].add_neighbor(self.N[j])
self.N[j].add_neighbor(self.N[i])
def select(self, node: Node):
node.selected = True
selected_neighbors = node.neighbors.copy()
for n in selected_neighbors:
other = self.N.pop(n.index)
other.delete()
def deactivate(self):
for n in self.N.values():
n.selected = False
def copy(self):
return copy.deepcopy(self)
现在,让我们为我们的构造性启发式方法创建一个抽象。它应该像对应的Graph
一样,通过一个边的列表和一个可选的节点列表来实例化。在实例化时,它的属性graph
将从问题实例的原始图定义。
from abc import ABC, abstractmethod
from mis.graph import Graph, Node
from typing import List, Optional, Tuple
class BaseConstructive(ABC):
graph: Graph
def __init__(
self,
edges: List[Tuple[int, int]],
nodes: Optional[List[int]] = None,
):
self.graph = Graph(edges, nodes)
solve
方法将是我们求解过程的核心。它应该返回一个候选解的子图G(V, E)。当使用解程序的实例作为可调用对象时,它应该根据solve
方法返回的结果覆盖其节点的selected
属性。
请注意,choice
方法在这里是一个抽象,尚未被子类覆盖。
class BaseConstructive(ABC):
# Check previous definitions
def __call__(self, *args, **kwargs):
S = self.solve(*args, **kwargs)
for i, n in S.N.items():
self.graph.N[i].selected = n.selected
@property
def cost(self):
return len(self.graph.active_nodes)
def solve(self, *args, **kwargs) -> Graph:
self.graph.deactivate()
G = self.graph.copy()
for i in range(len(G.N)):
n = self.choice(G)
G.select(n)
if len(G.queue) == 0:
assert len(G.N) == i + 1, "Unexpected behavior in remaining nodes and iterations"
break
return G
@abstractmethod
def choice(self, graph: Graph) -> Node:
pass
让我们首先创建一个随机选择下一个要加入解集的节点的算法。
import random
class RandomChoice(BaseConstructive):
rng: random.Random
def __init__(
self,
edges: List[Tuple[int, int]],
nodes: Optional[List[int]] = None,
seed=None
):
super().__init__(edges, nodes)
self.rng = random.Random(seed)
def choice(self, graph: Graph) -> Node:
return self.rng.choice(graph.queue)
它已经可以在我们的求解过程中使用,并且生成最大独立集的可行解(不是最大的)。然而,它的性能会根据随机序列的不同而变化,我们可能会受到较差结果的影响。
自适应贪心
或者,在每一步中,我们可以选择对“可行元素池”影响最小的下一个节点。这意味着选择子图中邻居数量最少的下一个元素。换句话说,选择具有最小degree
属性的下一个元素。这是 Feo 等人(1994)采用的相同方法。
请注意,我们节点的degree
可能会随着部分解的变化和元素从子图中移除而变化。因此,它可以定义为一个自适应贪心过程。
还有一些情况,元素贡献的成本会受到算法先前选择的元素的影响。我们将这些称为自适应贪心算法*(Resende & Ribeiro, 2016)*。
然后让我们实现一个算法,选择子图中degree
最小的元素作为下一个元素。
class GreedyChoice(BaseConstructive):
def choice(self, graph: Graph) -> Node:
return min([n for n in graph.queue], key=lambda x: x.degree)
尽管它并未提供最优性的证明,但自适应贪婪方法仍然是为此问题提供快速且高质量解的一个有趣策略。试着多次运行随机方法…在某些情况下,它可能会优于贪婪策略(至少在一次或几次运行中)。那为什么不实现一个多启动框架呢?
多启动
在这种方法中,执行多个独立的运行,并记录最佳解。最终返回最好的解。
class MultiRandom(RandomChoice):
def solve(self, n_iter: int = 10) -> Graph:
best_sol = None
best_cost = 0
for _ in range(n_iter):
G = super().solve()
if len(G.N) > best_cost:
best_cost = len(G.N)
best_sol = G
return best_sol
在我的GitHub 仓库中,您将找到一个 32 节点图的例子,其中自适应贪婪方法找到了一个 5 个顶点的子集,但使用多启动的随机框架找到了一个包含 6 个顶点的解。解决过程如下所示。
解决构造性启发式应用于最大独立集问题的过程。(由作者制作的动画)。
进一步阅读
在本文开头,我提到过构造性启发式可以与局部搜索技术结合。一种探索这一方法的极具潜力的元启发式方法叫做贪婪随机自适应搜索过程(GRASP)。
GRASP 的思路是使用一个多启动框架,其中随机元素将引导构造阶段生成不同的初始解,接着对其应用局部搜索。这样,解决过程能够避免局部最优解。对于那些有兴趣更详细探讨启发式和元启发式方法的人,值得访问毛里西奥·雷森德教授的官网,他是最初提出 GRASP 的作者之一。在该网站上,他列出了自己的一些工作和对运筹学学术界的贡献。
对于有兴趣了解 GRASP 代码示例的人,也可以查看我的GitHub 仓库,其中包含用于作业车间调度问题的应用。
对于那些有兴趣深入探讨更多优化问题和解决技术的人,我在 Medium 上有一些其他故事,可以在一个综合列表中找到。
优化时代的故事
查看列表15 个故事https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/848ca03a7d7366b8a040f720f5d51f5c.pnghttps://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/b79fd62ce301f6295199d983f7633588.pnghttps://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/a6e8cbe0e088f4e7b1edcf27c524b072.png
结论
在本文中,介绍并应用了离散优化背景下的构造性启发式方法,具体应用于背包问题和最大独立集问题。阐述了如何选择基本元素以构建解决方案的直觉,举例说明了贪心选择、随机因素和多重起始的使用。完整的代码可以在我的GitHub 仓库中找到。
参考文献
Feo, T. A., Resende, M. G., & Smith, S. H., 1994. 一种用于最大独立集的贪心随机自适应搜索过程。运筹学,42(5),860–878。
Resende, M. G., & Ribeiro, C. C., 2016. 通过 GRASP 进行优化。Springer Science+ Business Media 纽约。
Wan, PJ., 2013. 贪心近似算法。载:Pardalos, P., Du, DZ., Graham, R.(编辑)《组合优化手册》。Springer, 纽约, NY。
上下文辅助预测:利用文本数据提升预测准确性
一种有前景的替代方法,用于改善预测
https://medium.com/@nikoskafritsas?source=post_page---byline--c9af2ab057c1--------------------------------https://towardsdatascience.com/?source=post_page---byline--c9af2ab057c1-------------------------------- Nikos Kafritsas
·发表于 Towards Data Science ·阅读时长:8 分钟·2024 年 12 月 2 日
–
图片来源 [1]
使用文本数据来提升预测表现并非新鲜事。
在金融市场中,文本数据和经济新闻通常在产生准确预测中扮演着至关重要的角色——有时甚至比数值历史数据更为重要。
最近,许多大型语言模型(LLMs)已经在Fedspeak和新闻情感分析上进行了微调。这些模型完全依赖文本数据来估计市场情绪。
一篇引人注目的新论文,“上下文至关重要”[1],探讨了一种不同的方法:通过结合数值和外部文本数据,预测准确性能提高多少?
论文提出了几项关键贡献:
-
上下文至关重要(CiK)数据集:一个将数值数据与相应文本信息配对的预测任务数据集。
-
感兴趣区域 CRPS (RCRPS):一种为评估概率预测而设计的修改版 CRPS 指标,专注于上下文敏感窗口。
-
上下文至关重要基准:一种新的评估框架,展示了外部文本信息如何提升流行的时间序列模型。
自注意力机制与代码解析
大型语言模型如何创建丰富的上下文嵌入
https://medium.com/@bradneysmith?source=post_page---byline--d7a9f0f4d94e--------------------------------https://towardsdatascience.com/?source=post_page---byline--d7a9f0f4d94e-------------------------------- Bradney Smith
·发表于Towards Data Science ·32 分钟阅读·2024 年 2 月 9 日
–
“从零开始的 LLMs”系列的第三部分 —— 完整指南,帮助你理解和构建大型语言模型。如果你有兴趣了解这些模型的工作原理,鼓励你阅读:
-
第三部分:自注意力机制与代码解析
介绍
论文《Attention is All You Need》可能标志着过去 10 年自然语言处理(NLP)领域最大的进展:Transformer 架构[1]。这一架构大幅简化了当时语言模型的复杂设计,同时取得了无与伦比的成果。最先进(SOTA)的模型,如 GPT、Claude 和 Llama 系列,都将其成功归功于这一设计,而其核心便是自注意力机制。在本深度解析中,我们将探讨这一机制的工作原理,以及它如何被 transformer 用于创建具有丰富上下文信息的嵌入,从而使这些模型能够表现得如此出色。
目录
1 — Transformer 嵌入过程概述
2 — 位置编码
3 — 自注意力机制
4 — Python 中的 Transformer 嵌入
5 — 结论
6 — 进一步阅读
1 — Transformer 嵌入过程概述
1.1 — Transformer 回顾
在本系列的前导文章中,我们简要探讨了 Transformer 的历史及其对自然语言处理(NLP)的影响。回顾一下:Transformer 是一种深度神经网络架构,是今天几乎所有大型语言模型(LLM)的基础。衍生模型通常被称为基于 Transformer 的模型,简称Transformer,因此这些术语将在本文中交替使用。像所有机器学习模型一样,Transformer 处理的是数字和线性代数,而不是直接处理人类语言。由于这一点,它们必须通过几个步骤将用户的文本输入转换为数值表示。也许这些步骤中最重要的一步是应用自注意力机制,这是本文的重点。将文本表示为向量的过程称为嵌入(或编码),因此输入文本的数值表示被称为Transformer 嵌入。
1.2 — 静态嵌入的问题
在本系列的第二部分中,我们以 word2vec 为例,探讨了语言模型的静态嵌入。这种嵌入方法早于 Transformer,并且存在一个主要的缺点:缺乏上下文信息。具有多重含义的词(称为多义词)会被编码为具有某种程度模糊性的表示,因为它们缺乏精确意义所需的上下文信息。一个典型的多义词例子是bank
。使用静态嵌入模型时,bank
这个词在向量空间中的表示会与money
和deposit
等词有一定的相似度,同时与river
和nature
等词也会有一定的相似度。这是因为该词会出现在训练数据的多种不同上下文中。这就是静态嵌入的核心问题:它们不会根据上下文而变化——因此称为“静态”。
1.3 — 修正静态嵌入
Transformer 通过生成自我上下文感知的 Transformer 嵌入来克服静态嵌入的局限性。在这种方法中,固定的词嵌入通过位置信息(词语在输入文本中的位置)和上下文信息(词语的使用方式)进行增强。这两个步骤分别发生在 Transformer 的不同组件中,即位置编码器和自注意力模块。我们将在接下来的章节中详细探讨这些内容。通过结合这些额外的信息,Transformer 可以基于输入序列中的使用情况,生成更强大的词向量表示。扩展向量表示,超越静态嵌入,正是让基于 Transformer 的模型能够处理多义词,并相比之前的模型更深入地理解语言。
1.4 — 引入学习嵌入
类似于四年前发布的 word2vec 方法,Transformer 通过一个线性层(一个小型神经网络)的权重存储每个标记的初始向量表示。在 word2vec 模型中,这些表示形成了静态嵌入,但在 Transformer 上下文中,这些被称为学习到的嵌入。在实践中,它们非常相似,但使用不同的名称强调这些表示仅仅是 Transformer 嵌入的起点,而不是最终的形式。
线性层位于 Transformer 架构的起始位置,只有权重,没有偏置项(每个神经元的偏置为 0)。该层的权重可以表示为一个V × d_model大小的矩阵,其中V是词汇表的大小(训练数据中唯一词汇的数量),d_model是嵌入维度的数量。在上一篇文章中,我们将d_model表示为N,以符合 word2vec 的记法,但在这里我们将使用d_model,这是 Transformer 中更常见的术语。原始的 Transformer 提出时,d_model的大小为 512 维,但在实际应用中,可以使用任何合理的值。
一张图展示了线性层在 Transformer 架构中的位置,该层存储了学习到的嵌入。图像由作者提供,改编自《Attention is All You Need》论文中的 Transformer 架构图[1]。
1.5 — 创建学习到的嵌入
静态嵌入和学习到的嵌入之间的一个关键区别是它们的训练方式。静态嵌入在一个单独的神经网络中进行训练(使用 Skip-Gram 或 Continuous Bag of Words 架构),通过在给定的窗口大小内进行词预测任务进行训练。一旦训练完成,这些嵌入就会被提取,并与不同的语言模型一起使用。然而,学习到的嵌入是你正在使用的 Transformer 模型的一部分,并作为权重存储在模型的第一个线性层中。这些权重,进而词汇表中每个标记的学习到的嵌入,会在与其他模型参数相同的反向传播步骤中进行训练。下面是学习到的嵌入的训练过程总结。
步骤 1:初始化
在模型的开始阶段,随机初始化线性层中每个神经元的权重,并将偏置项设置为 0。这个层也被称为嵌入层,因为它是存储学习到的嵌入的线性层。权重可以表示为一个V × d_model大小的矩阵,其中词汇表中每个词的词嵌入沿着矩阵的行存储。例如,词汇表中第一个词的嵌入存储在第一行,第二个词的嵌入存储在第二行,以此类推。
步骤 2:训练
在每个训练步骤中,Transformer 接收一个输入单词,目标是预测序列中的下一个单词——这一任务称为下一个标记预测(NTP)。最初,这些预测会非常差,因此网络中的每个权重和偏置项都会被更新,以改善相对于损失函数的性能,包括嵌入。在多次训练迭代后,学习到的嵌入应为词汇表中的每个单词提供强大的向量表示。
第 3 步:提取学习到的嵌入
当新的输入序列提供给模型时,单词会被转换为带有关联的标记 ID 的标记,这些标记 ID 对应于标记在分词器词汇表中的位置。例如,单词cat
可能位于分词器词汇表中的位置349
,因此其 ID 为349
。标记 ID 用于创建独热编码向量,从权重矩阵中提取正确的学习嵌入(即,V维向量,其中每个元素都是 0,除了标记 ID 位置的元素为 1)。
注意: PyTorch 是一个非常流行的 Python 深度学习库,驱动了一些最著名的机器学习包,如 HuggingFace
*Transformers*
库[2]。如果你熟悉 PyTorch,可能已经遇到过*nn.Embedding*
类,它通常用于构建 Transformer 网络的第一层(*nn*
表示该类属于神经网络包)。该类返回一个常规的线性层,该层以恒等函数作为激活函数进行初始化,并且没有偏置项。由于权重是模型在训练过程中学习的参数,它们会随机初始化。这实际上在一行简单的代码中完成了上述步骤。记住,*nn.Embedding*
层不会直接提供预训练的词嵌入,而是初始化一个空白的嵌入画布,供训练期间学习。这是为了让 Transformer 在训练阶段学习自己的嵌入。
1.6 — Transformer 嵌入过程
一旦学习到的嵌入经过训练,嵌入层中的权重将不再变化。也就是说,每个单词(或更具体地说,标记)的学习嵌入总是提供单词向量表示的相同起点。从这里开始,位置和上下文信息将被添加进去,生成一个独特的单词表示,这个表示能够反映其在输入序列中的使用。
Transformer 嵌入是通过四个步骤创建的,下面使用示例提示词Write a poem about a man fishing on a river bank.
来演示。请注意,前两步与我们之前看到的 word2vec 方法相同。第 3 步和第 4 步是进一步的处理,它们为嵌入添加上下文信息。
第 1 步)分词:
分词是将较长的输入序列拆分为单独的单词(以及部分单词),这些单独的单词称为“tokens”。在这种情况下,句子将被拆分为:
write
, a
, poem
, about
, a
, man
, fishing
, on
, a
, river
, bank
接下来,将每个 token 与其 token ID 关联,token ID 是一个整数值,表示 token 在分词器词汇表中的位置(有关分词过程的详细介绍,请参见本系列第一部分)。
第 2 步)将 tokens 映射到学习到的嵌入:
一旦输入序列被转换为一组 token ID,tokens 就会被映射到它们的学习到的嵌入向量表示,这些向量是在变换器训练过程中获得的。这些学习到的嵌入具有“查找表”行为,正如我们在本系列第二部分中的 word2vec 示例中所看到的那样。映射过程通过将由 token ID 创建的独热编码向量与权重矩阵相乘来进行,就像 word2vec 方法一样。学习到的嵌入在下图中表示为V。
第 3 步)通过位置编码添加位置信息:
位置编码用于将位置信息添加到单词嵌入中。与递归神经网络(RNNs)按顺序处理文本(一次处理一个单词)不同,变换器是并行处理所有单词的。这消除了每个单词在句子中位置的任何隐含信息。例如,句子the cat ate the mouse
和the mouse ate the cat
使用相同的单词,但具有完全不同的意义。为了保留单词顺序,会为每个单词生成位置编码向量,并将其添加到学习到的嵌入中。在下图中,位置编码向量表示为P,学习到的嵌入和位置编码的和表示为X。
第 4 步)使用自注意力修改嵌入:
最后一步是使用自注意力机制添加上下文信息。这决定了哪些单词为输入序列中的其他单词提供上下文。在下图中,变换器嵌入表示为y。
从输入文本到变换器嵌入的变换器嵌入过程概述。图片来自作者。
2 — 位置编码
2.1 — 位置编码的需求
在应用自注意力机制之前,位置编码用于将 tokens 的顺序信息添加到学习到的嵌入中。这弥补了前面描述的变换器并行处理导致的位置信息丢失。对于注入这些信息,有多种可行的方法,但所有方法必须遵守一组约束。用于生成位置信息的函数必须产生以下值:
-
有界性 — 值不应在正负方向上爆炸,而应受到限制(例如在 0 和 1、-1 和 1 之间等)
-
周期性 — 该函数应生成一个重复的模式,模型可以学习识别并从中推断位置
-
可预测性 — 位置信息应以一种方式生成,使得模型能够理解在其未训练过的序列长度中单词的位置。例如,即使模型没有见过长度恰好为 412 的序列,Transformer 也应该能够理解序列中每个嵌入的位置信息。
这些约束确保了位置编码器生成的位置信息使得单词能够关注(从中获得上下文)序列中任何其他重要单词,无论它们在序列中的相对位置如何。理论上,在足够强大的计算机上,单词应该能够从每个相关单词中获得上下文,甚至在一个无限长的输入序列中也能做到。在像 ChatGPT 这样的聊天机器人中,上下文包括当前的提示和所有之前的提示与响应(在上下文长度限制内)。这个限制通常在几千个词汇之间,GPT-3 支持最多 4096 个词汇,而 GPT-4 企业版的上限约为 128,000 个词汇 [3]。
2.2 — 论文《Attention is All You Need》中的位置编码
原始的 Transformer 模型提出了以下位置编码函数:
这张图展示了论文《Attention is All You Need》中提出的位置信息编码的方程式。[1] 图片由作者提供。
其中:
-
pos 是单词在输入中的位置,pos = 0 对应序列中的第一个单词
-
i 是每个嵌入维度的索引,范围从 i=0(第一个嵌入维度)到 d_model
-
d_model 是每个学习到的嵌入向量的维度数(因此也是每个位置编码向量的维度数)。在关于 word2vec 的文章中,这个值以前被表示为 N。
两个提出的函数分别接受 2i 和 2i+1 作为参数,实际上意味着正弦函数为每个单词向量的偶数维度生成位置信息(i 为偶数),而余弦函数为奇数维度生成位置信息(i 为奇数)。根据 Transformer 的作者:
“位置编码对应一个正弦波。波长从 2π 到 10000·2π 形成几何级数。我们选择这个函数,因为我们假设它可以使模型轻松学习通过相对位置进行关注,因为对于任何固定偏移量 k*,* PE_pos+k 可以表示为 PE_pos* 的线性函数。”*
分母中的常数值10_000
在一些实验后发现是合适的,但这是作者的一个相对随意的选择。
2.3 — 其他位置编码方法
上面显示的位置编码被认为是固定的,因为它们是由已知的函数生成,具有确定性(可预测)的输出。这代表了最简单形式的位置编码。也可以通过随机初始化一些位置编码并使用反向传播训练它们,来使用学习的位置编码。BERT 架构的衍生模型就是采用这种学习编码方法的例子。最近,旋转位置编码(RoPE)方法变得越来越流行,并在 Llama 2 和 PaLM 等模型中得到了应用,此外还有其他位置编码方法。
2.4 — 在 Python 中实现位置编码器
在 Python 中创建一个位置编码器类相当简单。我们可以从定义一个函数开始,该函数接受嵌入维度的数量(d_model
)、输入序列的最大长度(max_length
),以及对向量中每个值四舍五入的小数位数(rounding
)。需要注意的是,transformer 定义了一个最大输入序列长度,任何少于此限制的序列都会通过填充标记(padding tokens)直到达到该限制。为了考虑这种行为,我们在位置编码器中接受一个max_length
参数。实际上,这个限制通常是数千个字符长。
我们还可以利用一个数学技巧来节省计算量。我们不需要为每个PE_{pos, 2i}和PE_{pos, 2i+1}计算分母,而是可以注意到,分母在连续的i对之间是相同的。例如,i=0和i=1的分母是相同的,i=2和i=3的分母也是相同的。因此,我们可以先为偶数值的i计算分母,并将其重复用于奇数值的i。
import numpy as np
class PositionalEncoder():
""" An implementation of positional encoding.
Attributes:
d_model (int): The number of embedding dimensions in the learned
embeddings. This is used to determine the length of the positional
encoding vectors, which make up the rows of the positional encoding
matrix.
max_length (int): The maximum sequence length in the transformer. This
is used to determine the size of the positional encoding matrix.
rounding (int): The number of decimal places to round each of the
values to in the output positional encoding matrix.
"""
def __init__(self, d_model, max_length, rounding):
self.d_model = d_model
self.max_length = max_length
self.rounding = rounding
def generate_positional_encoding(self):
""" Generate positional information to add to inputs for encoding.
The positional information is generated using the number of embedding
dimensions (d_model), the maximum length of the sequence (max_length),
and the number of decimal places to round to (rounding). The output
matrix generated is of size (max_length X embedding_dim), where each
row is the positional information to be added to the learned
embeddings, and each column is an embedding dimension.
"""
position = np.arange(0, self.max_length).reshape(self.max_length, 1)
even_i = np.arange(0, self.d_model, 2)
denominator = 10_000**(even_i / self.d_model)
even_encoded = np.round(np.sin(position / denominator), self.rounding)
odd_encoded = np.round(np.cos(position / denominator), self.rounding)
# Interleave the even and odd encodings
positional_encoding = np.stack((even_encoded, odd_encoded),2)\
.reshape(even_encoded.shape[0],-1)
# If self.d_model is odd remove the extra column generated
if self.d_model % 2 == 1:
positional_encoding = np.delete(positional_encoding, -1, axis=1)
return positional_encoding
def encode(self, input):
""" Encode the input by adding positional information.
Args:
input (np.array): A two-dimensional array of embeddings. The array
should be of size (self.max_length x self.d_model).
Returns:
output (np.array): A two-dimensional array of embeddings plus the
positional information. The array has size (self.max_length x
self.d_model).
"""
positional_encoding = self.generate_positional_encoding()
output = input + positional_encoding
return output
MAX_LENGTH = 5
EMBEDDING_DIM = 3
ROUNDING = 2
# Instantiate the encoder
PE = PositionalEncoder(d_model=EMBEDDING_DIM,
max_length=MAX_LENGTH,
rounding=ROUNDING)
# Create an input matrix of word embeddings without positional encoding
input = np.round(np.random.rand(MAX_LENGTH, EMBEDDING_DIM), ROUNDING)
# Create an output matrix of word embeddings by adding positional encoding
output = PE.encode(input)
# Print the results
print(f'Embeddings without positional encoding:\n\n{input}\n')
print(f'Positional encoding:\n\n{output-input}\n')
print(f'Embeddings with positional encoding:\n\n{output}')
Embeddings without positional encoding:
[[0.12 0.94 0.9 ]
[0.14 0.65 0.22]
[0.29 0.58 0.31]
[0.69 0.37 0.62]
[0.25 0.61 0.65]]
Positional encoding:
[[ 0\. 1\. 0\. ]
[ 0.84 0.54 0\. ]
[ 0.91 -0.42 0\. ]
[ 0.14 -0.99 0.01]
[-0.76 -0.65 0.01]]
Embeddings with positional encoding:
[[ 0.12 1.94 0.9 ]
[ 0.98 1.19 0.22]
[ 1.2 0.16 0.31]
[ 0.83 -0.62 0.63]
[-0.51 -0.04 0.66]]
2.5 — 可视化位置编码矩阵
记住,生成的位置编码必须是有界的、周期性的和可预测的。之前提到的正弦函数的输出可以收集到一个矩阵中,然后可以通过逐元素加法轻松地与学习到的嵌入结合。绘制这个矩阵可以清楚地展示所需的属性。在下面的图中,负值的弯曲带(蓝色)从矩阵的左边缘发散出来。这些带状图案形成了一个模式,transformer 可以很容易地学会预测这个模式。
import matplotlib.pyplot as plt
# Instantiate a PositionalEncoder class
d_model = 400
max_length = 100
rounding = 4
PE = PositionalEncoder(d_model=d_model,
max_length=max_length,
rounding=rounding)
# Generate positional encodings
input = np.round(np.random.rand(max_length, d_model), 4)
positional_encoding = PE.generate_positional_encoding()
# Plot positional encodings
cax = plt.matshow(positional_encoding, cmap='coolwarm')
plt.title(f'Positional Encoding Matrix ({d_model=}, {max_length=})')
plt.ylabel('Position of the Embedding\nin the Sequence, pos')
plt.xlabel('Embedding Dimension, i')
plt.gcf().colorbar(cax)
plt.gca().xaxis.set_ticks_position('bottom')
这是一个位置编码矩阵的可视化,模型的嵌入维度为 400(d_model = 400),最大序列长度为 100(max_length = 100)。图片来自作者。
3 — 自注意力机制
3.1 — 注意力机制概述
现在我们已经介绍了变压器嵌入和位置编码步骤的概述,我们可以将注意力集中在自注意力机制本身。简而言之,自注意力修改词汇的向量表示,以捕捉它们在输入序列中的使用上下文。自注意力中的“自”指的是该机制使用单一序列中周围的词来提供上下文。因此,自注意力要求所有词汇并行处理。这实际上是变压器的主要优势之一(尤其是与 RNNs 相比),因为这些模型能够利用并行处理来显著提高性能。近年来,关于这种方法的思考有所重新调整,未来我们可能会看到这一核心机制被替代[4]。
变压器中使用的另一种注意力形式是交叉注意力。与仅在单一序列内操作的自注意力不同,交叉注意力将输出序列中的每个词与输入序列中的每个词进行比较,穿越两个嵌入矩阵。请注意与自注意力的区别,后者完全专注于单一序列内。
3.2 — 可视化自注意力如何上下文化嵌入
下图展示了一组简化的二维嵌入向量。与自然和河流相关的词汇集中在图表的右上象限,而与金钱相关的词汇则集中在左下象限。由于bank
一词具有多义性,它的向量被定位在两个聚类之间。自注意力的目标是将学习到的嵌入向量移动到向量空间中更准确地捕捉它们在输入序列中的含义的区域。在示例输入Write a poem about a man fishing on a river bank.
中,目标是调整bank
一词的向量,使其更多地反映自然和河流的含义,而减少与金钱和存款相关的含义。
注意: 更准确地说,这里自注意力的目标是更新输入中每个词的向量,使得所有嵌入更好地表示它们被使用的上下文。这里并没有特别之处,词
*bank*
并不是变压器具备某些特殊知识的词——自注意力会应用于所有词。我们稍后会进一步探讨这一点,但现在,只考虑*bank*
是如何受到自注意力影响的,就能很好地理解注意力块中发生了什么。在这个可视化过程中,位置编码信息没有被明确显示。它的影响是微乎其微的,但请注意,自注意力机制实际上会作用于学习的嵌入向量与位置编码的总和,而不仅仅是学习的嵌入向量。
import matplotlib.pyplot as plt
# Create word embeddings
xs = [0.5, 1.5, 2.5, 6.0, 7.5, 8.0]
ys = [3.0, 1.2, 0.5, 8.0, 7.5, 5.5]
words = ['money', 'deposit', 'withdraw', 'nature', 'river', 'water']
bank = [[4.5, 4.5], [6.7, 6.5]]
# Create figure
fig, ax = plt.subplots(ncols=2, figsize=(8,4))
# Add titles
ax[0].set_title('Learned Embedding for "bank"\nwithout context')
ax[1].set_title('Contextual Embedding for\n"bank" after self-attention')
# Add trace on plot 2 to show the movement of "bank"
ax[1].scatter(bank[0][0], bank[0][1], c='blue', s=50, alpha=0.3)
ax[1].plot([bank[0][0]+0.1, bank[1][0]],
[bank[0][1]+0.1, bank[1][1]],
linestyle='dashed',
zorder=-1)
for i in range(2):
ax[i].set_xlim(0,10)
ax[i].set_ylim(0,10)
# Plot word embeddings
for (x, y, word) in list(zip(xs, ys, words)):
ax[i].scatter(x, y, c='red', s=50)
ax[i].text(x+0.5, y, word)
# Plot "bank" vector
x = bank[i][0]
y = bank[i][1]
color = 'blue' if i == 0 else 'purple'
ax[i].text(x+0.5, y, 'bank')
ax[i].scatter(x, y, c=color, s=50)
词语“bank”在嵌入空间中随着上下文信息的添加而移动的向量表示的可视化。图片由作者提供。
3.3 — 自注意力算法
在上面的部分中,我们提到自注意力的目标是将每个标记的嵌入移动到一个更好地代表其在输入序列中使用上下文的向量空间区域。我们没有讨论的是如何实现这一目标。在这里,我们将通过逐步示例展示自注意力机制是如何通过添加来自周围标记的上下文来修改bank
的嵌入的。
自注意力块的简化概述(省略了键、查询和值矩阵)。图片由作者提供。
步骤 1)使用点积计算词语之间的相似度:
一个标记的上下文由句子中周围的标记提供。因此,我们可以使用输入序列中所有标记的嵌入来更新任何词的嵌入,例如bank
。理想情况下,提供重要上下文的词(例如river
)将对嵌入产生重大影响,而提供较少上下文的词(例如a
)则影响较小。
一个词对另一个词贡献的上下文程度通过相似度得分来衡量。具有相似学习嵌入的标记比那些嵌入不相似的标记更可能提供更多上下文。相似度得分是通过将当前标记的嵌入(其学习嵌入加上位置信息)与序列中每个其他标记的当前嵌入进行点积来计算的。为清楚起见,本文中将当前的嵌入称为自注意力输入,并用x表示。
有几种方法可以衡量两个向量之间的相似度,通常可以分为:基于距离的度量和基于角度的度量。基于距离的度量使用它们之间的直线距离来表征向量的相似度。这个计算相对简单,可以理解为在d_model维空间中应用勾股定理。尽管直观,但这种方法在计算上非常昂贵。
对于基于角度的相似度度量,两个主要的候选方法是:余弦相似度和点积相似度。这两者都通过计算两个向量之间的夹角余弦值来表征相似度,θ。对于正交向量(彼此垂直的向量),cos(θ) = 0,表示没有相似度。对于平行向量,cos(θ) = 1,表示这些向量是相同的。单纯使用向量之间的角度(如余弦相似度所做的那样)并不理想,原因有二。其一是没有考虑向量的大小,因此偶然对齐的远距离向量会产生膨胀的相似度分数。其二是,余弦相似度需要首先计算点积,然后再除以向量大小的乘积——这使得余弦相似度成为一个计算开销较大的度量。因此,点积被用来确定相似度。下面给出了两个向量x_1和x_2的点积公式。
两个向量 x_1 和 x_2 的点积公式。图片来自作者。
以下图所示是bank
的自注意力输入向量x_bank与输入序列中每个标记的向量表示矩阵X^T的点积。我们还可以将x_bank写作x_11,以反映其在输入序列中的位置。矩阵X存储了输入序列中每个标记的自注意力输入,每一行表示一个标记的输入。该矩阵的列数由L_max给出,它是模型的最大序列长度。在这个示例中,我们假设最大序列长度等于输入提示中的单词数量,因此不需要任何填充标记(更多关于填充的内容请参见本系列的第四部分)。为了直接计算点积,我们可以对X进行转置,并使用公式S_bank = x_bank ⋅ X^T计算相似度分数向量S_bank。S_bank中的每个元素代表bank
与输入序列中每个标记之间的相似度分数。
对 X_11 与每个自注意力输入(输入序列中每个标记的学习到的嵌入和位置信息之和)进行相似度分数计算的示例。图片来自作者。
步骤 2) 缩放相似度分数:
点积方法缺乏任何形式的归一化(与余弦相似度不同),这可能导致相似度分数变得非常大。这会带来计算上的挑战,因此必须进行某种形式的归一化。最常见的方法是将每个分数除以√d_model,从而得到缩放点积注意力。缩放点积注意力不仅限于自注意力,它还用于变换器中的交叉注意力。
步骤 3) 使用 Softmax 函数计算注意力权重:
上一步的输出是向量 S_bank,它包含了bank
与输入序列中每个标记之间的相似度分数。这些相似度分数被用作加权系数,从而通过对每个周围标记的嵌入进行加权求和,构造出bank
的 transformer 嵌入。这些权重,称为注意力权重,是通过将 S_bank 输入 softmax 函数计算得出的。输出结果存储在一个名为 W_bank 的向量中。有关 softmax 函数的更多信息,请参阅上一篇关于 word2vec 的文章。
基于与每个自注意力输入的相似度计算“bank”的注意力权重的示例。图片来自作者。
步骤 4)计算 Transformer 嵌入
最后,bank
的 transformer 嵌入是通过对write
、a
、prompt
、…、bank
的嵌入加权求和得到的。当然,bank
与自身的相似度最高(因此具有最大的注意力权重),所以这一过程后的输出嵌入将与之前相似。这个行为是理想的,因为初始嵌入已经占据了一个编码bank
意义的向量空间区域。目标是将嵌入稍微调整,朝向那些提供更多上下文的单词。提供较少上下文的单词,如a
和man
,其权重非常小。因此,它们对输出嵌入的影响极小。提供显著上下文的单词,如river
和fishing
,将具有更高的权重,因此会将输出嵌入拉近它们所在的向量空间区域。最终结果是一个新的嵌入,y_bank,它反映了整个输入序列的上下文。
通过对序列中每个标记的其他嵌入进行加权求和,给出“bank”的新嵌入示例计算。图片来自作者。
3.4 — 使用矩阵扩展自注意力机制
上面,我们回顾了计算单一单词bank
的 transformer 嵌入的步骤。输入由bank
的学习嵌入向量及其位置信息组成,我们将其表示为 x_11 或 x_bank。这里的关键点是,我们仅考虑了一个向量作为输入。如果我们将矩阵 X(维度为 L_max × d_model)传递给自注意力模块,我们就可以同时计算输入提示中每个标记的 transformer 嵌入。输出矩阵 Y 包含了矩阵行中每个标记的 transformer 嵌入。这种方法使得 transformer 能够快速处理文本。
自注意力模块的黑箱示意图。输入序列的词向量矩阵用 X 表示,输出序列的矩阵用 Y 表示。图片来自作者。
3.5 — 查询、键和值矩阵
上述描述概述了自注意力模块的核心功能,但仍然有一个关键部分没有提到。上面的简单加权和没有包含任何可训练的参数,但我们可以将一些可训练的参数引入到过程中。没有可训练的参数,模型的表现可能仍然很好,但通过允许模型从训练数据中学习更复杂的模式和隐藏特征,我们可以观察到模型表现明显更强。
自注意力的输入被使用三次来计算新的嵌入,这些输入包括 x_bank 向量、点积步骤中的 X^T 矩阵和加权和步骤中的 X^T 矩阵。这三个位置是引入一些权重的理想候选位置,这些权重以矩阵的形式添加(如图中红色所示)。当这些矩阵与各自的输入(如图中蓝色所示)相乘时,它们形成了 键、查询和值矩阵,分别为 K、Q 和 V(如图中紫色所示)。这些权重矩阵的列数是用户的架构选择。选择一个小于 d_model 的 d_q、d_k 和 d_v 值将导致维度降维,这可以提高模型的速度。最终,这些值是超参数,可以根据模型的具体实现和使用场景进行调整,如果不确定,通常会将它们都设置为 d_model [5]。
一个完整的自注意力模块图,包括键、查询和值矩阵。图像来源:作者。
3.6 — 数据库类比
这些矩阵的名称来源于与数据库的类比,下面简要解释了这一点。
查询:
- 在数据库中,查询是你在执行搜索时要寻找的内容。例如,“展示数据库中所有销量超过 1,000,000 张唱片的专辑”。在自注意力模块中,我们基本上是在问同样的问题,只不过表达为“展示这个向量(例如 x_bank)的 Transformer 嵌入”。为了简单起见,我们只考虑了单个向量 x_bank,但请记住,我们可以通过将多个向量收集成矩阵来对任意多个向量执行自注意力步骤。因此,我们可以很容易地将矩阵 X 作为查询传入,这样问题就变成了“展示输入序列中每个向量的 Transformer 嵌入”。这就是基于 Transformer 的模型中实际发生的事情。
键:
- 数据库中的键是正在被搜索的属性或列。在前面给出的例子中,你可以将其视为“专辑销量”列,它存储了我们感兴趣的信息。在自注意力中,我们对输入提示中每个单词的嵌入感兴趣,因此我们可以计算一组注意力权重。因此,键矩阵是所有输入嵌入的集合。
值:
- 值对应于数据库中的实际数据,即我们示例中的实际销售数据(例如,2,300,000 本)。对于自注意力机制,这与键矩阵的输入完全相同(以及我们刚刚讨论的查询矩阵):一个包含所有输入嵌入的集合。因此,查询、键和值矩阵都以矩阵X作为输入。
3.7 — 关于多头注意力的说明
将计算分布到多个头部:
《Attention is All You Need》论文通过将注意力机制分为多个头部,将标准自注意力扩展为多头注意力(MHA)。在标准自注意力中,模型学习一组权重矩阵(W_Q、W_K 和 W_V),这些矩阵将词嵌入矩阵X转换为查询、键和值矩阵(Q、K 和 V)。这些矩阵然后用于计算注意力得分,并像我们上面看到的那样更新X,以获取上下文信息。
相比之下,MHA 将注意力机制拆分成H个独立的头部,每个头部学习自己的一组较小的权重矩阵。这些权重用于计算一组较小的、头部特定的查询、键和值矩阵(分别表示为Qh*、*Kh 和 V^h)。每个头部独立处理输入序列,生成不同的注意力输出。然后将这些输出连接在一起(堆叠在一起),并通过最终的线性层,生成更新后的X矩阵,如下图中的Y所示,带有丰富的上下文信息。
通过引入多个头部,MHA 增加了注意力过程中的可学习参数数量,使模型能够捕捉数据中的更复杂关系。每个头部学习自己的一组权重矩阵,从而使它们能够专注于输入的不同方面,如长程依赖(远距离单词之间的关系)、短程依赖(相邻单词之间的关系)、语法结构等。总体效果是产生一个对输入序列有更细致理解的模型。
多头注意力机制的概述。如需深入了解术语和每个过程步骤,请参阅本系列第五部分第 2.8 节。图片由作者提供。
以下段落重点介绍了如何建立这种工作原理的广泛直觉,以及为什么这个步骤是有用的。然而,如果你有兴趣深入了解 MHA 的实现细节,请参阅第五部分第 2.8 节——《Mistral 7B 完整指南及代码》[链接即将发布!]。
使用多个头部的好处:
核心思想是允许每个头部学习输入序列中单词之间的不同类型关系,并将它们结合起来创建深度文本表示。例如,一些头部可能学习捕捉长期依赖(文本中远距离单词之间的关系),而其他头部则可能专注于短期依赖(文本中相邻单词之间的关系)。
如果模型给定句子A man withdrew money from the bank then sat on the river bank
,使用多头注意力机制可以让模型捕捉到money
与第一个bank
之间的短期依赖关系,以及river
与第二个bank
之间的独立依赖关系。因此,bank
这个词的两个用法将通过不同的上下文信息得到正确更新,以对应它们各自的含义。
构建多头注意力的直觉:
为了加深对多头注意力机制有用性的直觉,考虑一个需要大量上下文的句子中的单词。例如,在句子I ate some of Bob’s chocolate cake
中,单词ate
应该关注I
、Bob’s
和cake
来获得完整的上下文。这是一个相对简单的例子,但如果你将这个概念扩展到包含成千上万个单词的复杂序列,那么分配计算负载到独立的注意力机制上似乎是合理的。
一个示例,展示了注意力头如何捕捉输入序列中不同的单词依赖关系。图像由作者提供。
4 — Python 中的变换器嵌入
4.1 — 从变换器模型中提取学习到的嵌入和变换器嵌入
Python 有许多用于处理变换器模型的选项,但可能没有比 Hugging Face 更为知名的了。Hugging Face 为 NLP 研究人员和开发者提供了一个集中的资源中心,包括如下工具:
-
transformers
: Hugging Face 的核心库,提供了使用、训练和微调预训练变换器模型的接口。 -
tokenizers
: 一个用于处理多种变换器的分词器库,可以使用预先构建的分词器模型,也可以从头开始构建全新的分词器。 -
datasets
: 一个用于训练各种任务模型的数据集集合,不仅仅限于 NLP。 -
模型中心:一个大型的前沿模型库,包含了已发布论文中的模型、社区开发的模型以及介于两者之间的所有内容。这些模型是免费提供的,并可以通过
transformers
API 轻松导入到 Python 中。
以下代码单元展示了如何使用transformers
库将基于变换器的模型加载到 Python 中,并展示了如何提取单词的学习嵌入(无上下文)和变换器嵌入(有上下文)。本文的其余部分将分解此单元中展示的步骤,并描述在处理嵌入时可用的其他功能。
import torch
from transformers import AutoModel, AutoTokenizer
def extract_le(sequence, tokenizer, model):
""" Extract the learned embedding for each token in an input sequence.
Tokenize an input sequence (string) to produce a tensor of token IDs.
Return a tensor containing the learned embedding for each token in the
input sequence.
Args:
sequence (str): The input sentence(s) to tokenize and extract
embeddings from.
tokenizer: The tokenizer used to produce tokens.
model: The model to extract learned embeddings from.
Returns:
learned_embeddings (torch.tensor): A tensor containing tensors of
learned embeddings for each token in the input sequence.
"""
token_dict = tokenizer(sequence, return_tensors='pt')
token_ids = token_dict['input_ids']
learned_embeddings = model.embeddings.word_embeddings(token_ids)[0]
# Additional processing for display purposes
learned_embeddings = learned_embeddings.tolist()
learned_embeddings = [[round(i,2) for i in le] \
for le in learned_embeddings]
return learned_embeddings
def extract_te(sequence, tokenizer, model):
""" Extract the tranformer embedding for each token in an input sequence.
Tokenize an input sequence (string) to produce a tensor of token IDs.
Return a tensor containing the transformer embedding for each token in the
input sequence.
Args:
sequence (str): The input sentence(s) to tokenize and extract
embeddings from.
tokenizer: The tokenizer used to produce tokens.
model: The model to extract learned embeddings from.
Returns:
transformer_embeddings (torch.tensor): A tensor containing tensors of
transformer embeddings for each token in the input sequence.
"""
token_dict = tokenizer(sequence, return_tensors='pt')
with torch.no_grad():
base_model_output = model(**token_dict)
transformer_embeddings = base_model_output.last_hidden_state[0]
# Additional processing for display purposes
transformer_embeddings = transformer_embeddings.tolist()
transformer_embeddings = [[round(i,2) for i in te] \
for te in transformer_embeddings]
return transformer_embeddings
# Instantiate DistilBERT tokenizer and model
tokenizer = AutoTokenizer.from_pretrained('distilbert-base-uncased')
model = AutoModel.from_pretrained('distilbert-base-uncased')
# Extract the learned embedding for bank from DistilBERT
le_bank = extract_le('bank', tokenizer, model)[1]
# Write sentences containing "bank" in two different contexts
s1 = 'Write a poem about a man fishing on a river bank.'
s2 = 'Write a poem about a man withdrawing money from a bank.'
# Extract the transformer embedding for bank from DistilBERT in each sentence
s1_te_bank = extract_te(s1, tokenizer, model)[11]
s2_te_bank = extract_te(s2, tokenizer, model)[11]
# Print the results
print('------------------- Embedding vectors for "bank" -------------------\n')
print(f'Learned embedding: {le_bank[:5]}')
print(f'Transformer embedding (sentence 1): {s1_te_bank[:5]}')
print(f'Transformer embedding (sentence 2): {s2_te_bank[:5]}')
------------------- Embedding vectors for "bank" -------------------
Learned embedding: [-0.03, -0.06, -0.09, -0.07, -0.03]
Transformer embedding (sentence 1): [0.15, -0.16, -0.17, -0.08, 0.44]
Transformer embedding (sentence 2): [0.27, -0.23, -0.23, -0.21, 0.79]
4.2 — 导入Transformers
库
生成 Transformer 嵌入的第一步是从 Hugging Face transformers
库中选择一个模型。在本文中,我们将不使用模型进行推理,而仅仅用来检查它生成的嵌入。这不是一个标准的用例,因此我们需要进行一些额外的探索,以便访问嵌入。由于 transformers
库是用 PyTorch 编写的(在代码中称为 torch
),我们可以导入 torch
来提取模型内部的数据。
4.3 — 选择一个模型
在这个例子中,我们将使用 DistilBERT,这是 Google 的 BERT 模型的一个较小版本,Hugging Face 于 2019 年 10 月发布 [6]。根据 Hugging Face 的文档 [7]:
DistilBERT 是一个小巧、快速、便宜且轻量的 Transformer 模型,通过蒸馏 BERT base 训练得到。与
*bert-base-uncased*
相比,它的参数少了 40%,运行速度快 60%,同时在 GLUE 语言理解基准测试中保留了超过 95% 的 BERT 性能。
我们可以直接从 transformers
库中或使用 AutoModel
和 AutoTokenizer
类将 DistilBERT 及其相应的标记化器导入 Python。两者之间几乎没有区别,尽管通常更倾向于使用 AutoModel
和 AutoTokenizer
,因为模型名称可以作为参数存储在字符串中,这使得更换所用模型变得更加简单。
import torch
from transformers import DistilBertTokenizerFast, DistilBertModel
# Instantiate DistilBERT tokenizer and model
tokenizer = DistilBertTokenizerFast.from_pretrained('distilbert-base-uncased')
model = DistilBertModel.from_pretrained('distilbert-base-uncased')
import torch
from transformers import AutoModel, AutoTokenizer
# Instantiate DistilBERT tokenizer and model
tokenizer = AutoTokenizer.from_pretrained('distilbert-base-uncased')
model = AutoModel.from_pretrained('distilbert-base-uncased')
导入 DistilBERT 及其相应的标记化器后,我们可以调用 from_pretrained
方法,为每个组件加载我们想要使用的 DistilBERT 模型和标记化器的特定版本。在这个例子中,我们选择了 distilbert-base-uncased
,其中 base
指的是模型的大小,uncased
表示该模型是在没有大小写区分的文本上训练的(所有文本都转换为小写)。
4.4 — 创建一些示例句子
接下来,我们可以创建一些句子,为模型提供一些需要嵌入的单词。这两个句子 s1
和 s2
都包含单词 bank
,但在不同的语境中。这里的目标是展示单词 bank
在两个句子中的学习嵌入是相同的,然后通过 DistilBERT 使用自注意力机制加以修改,从而为每个输入序列生成独特的、具语境的嵌入。
# Create example sentences to produce embeddings for
s1 = 'Write a poem about a man fishing on a river bank.'
s2 = 'Write a poem about a man withdrawing money from a bank.'
4.5 — 对输入序列进行标记化
标记化器类可以用来标记化输入序列(如下所示),并将字符串转换为标记 ID 的列表。可选地,我们还可以传递一个 return_tensors
参数,将标记 ID 格式化为 PyTorch 张量(return_tensors=pt
)或 TensorFlow 常量(return_tensors=tf
)。如果不传递此参数,将返回一个 Python 列表形式的标记 ID。返回值是一个字典,包含 input_ids
:包含标记 ID 的类似列表的对象,以及 attention_mask
,我们暂时将忽略该部分。
注意: 基于 BERT 的模型在每个序列的开头包括一个
[CLS]
token,并在输入中使用[SEP]
token 来区分两个文本块。这些 token 存在是因为 BERT 最初的训练任务所需,可以在这里忽略不计。有关 BERT 特殊 token、模型大小、cased
与uncased
以及注意力掩码的讨论,请参见 本系列的第四部分。
token_dict = tokenizer(s1, return_tensors='pt')
token_ids = token_dict['input_ids'][0]
4.6 — 从模型中提取学习到的嵌入
每个 transformer 模型都可以通过 embeddings.word_embeddings
方法访问其学习到的嵌入。此方法接受一个 token ID 或一组 token ID,并返回作为 PyTorch 张量的学习嵌入。
learned_embeddings = model.embeddings.word_embeddings(token_ids)
learned_embeddings
tensor([[ 0.0390, -0.0123, -0.0208, ..., 0.0607, 0.0230, 0.0238],
[-0.0300, -0.0070, -0.0247, ..., 0.0203, -0.0566, -0.0264],
[ 0.0062, 0.0100, 0.0071, ..., -0.0043, -0.0132, 0.0166],
...,
[-0.0261, -0.0571, -0.0934, ..., -0.0351, -0.0396, -0.0389],
[-0.0244, -0.0138, -0.0078, ..., 0.0069, 0.0057, -0.0016],
[-0.0199, -0.0095, -0.0099, ..., -0.0235, 0.0071, -0.0071]],
grad_fn=<EmbeddingBackward0>)
4.7 — 从模型中提取 Transformer 嵌入
将缺乏上下文的学习嵌入转换为具有上下文感知的 transformer 嵌入需要进行一次模型的前向传播。由于我们在这里并不更新模型的权重(即不进行模型训练),因此可以使用 torch.no_grad()
上下文管理器来节省内存。这样我们就可以将 tokens 直接传递给模型,并计算 transformer 嵌入,而无需进行任何不必要的计算。一旦 tokens 被传入模型,模型会返回一个 BaseModelOutput
,其中包含关于前向传播的各种信息。这里唯一关心的数据是最后一个隐藏状态中的激活值,它们构成了 transformer 嵌入。可以使用 last_hidden_state
属性访问这些值,如下所示,这也结束了本节代码单元的解释。
with torch.no_grad():
base_model_output = model(**token_dict)
transformer_embeddings = base_model_output.last_hidden_state
transformer_embeddings
tensor([[[-0.0957, -0.2030, -0.5024, ..., 0.0490, 0.3114, 0.1348],
[ 0.4535, 0.5324, -0.2670, ..., 0.0583, 0.2880, -0.4577],
[-0.1893, 0.1717, -0.4159, ..., -0.2230, -0.2225, 0.0207],
...,
[ 0.1536, -0.1616, -0.1735, ..., -0.3608, -0.3879, -0.1812],
[-0.0182, -0.4264, -0.6702, ..., 0.3213, 0.5881, -0.5163],
[ 0.7911, 0.2633, -0.4892, ..., -0.2303, -0.6364, -0.3311]]])
4.8 — 将 Token ID 转换为 Token
可以将 token ID 转换回文本 token,这样可以准确显示分词器是如何划分输入序列的。当使用子词分词器(如 WordPiece,BERT 基于模型使用)或字节对编码(Byte-Pair Encoding,例如 GPT 系列模型使用)时,这尤其有用,因为较长或较罕见的单词可能会被拆分成多个子词。
tokens = tokenizer.convert_ids_to_tokens(token_ids)
tokens
['[CLS]', 'write', 'a', 'poem', 'about', 'a', 'man', 'fishing', 'on', 'a',
'river', 'bank', '.', '[SEP]']
5 — 结论
自注意力机制通过同时处理输入序列中的每个 token,生成丰富的、上下文感知的 transformer 嵌入。这些嵌入建立在静态词嵌入(如 word2vec)的基础上,并使得更强大的语言模型(如 BERT 和 GPT)成为可能。在这一领域的进一步研究将继续提升大型语言模型(LLM)和自然语言处理(NLP)的整体能力。
6 — 进一步阅读
[1] A. Vaswani, N. Shazeer, N. Parmar, J. Uszkoreit, L. Jones, A. N. Gomez, Ł. Kaiser, 和 I. Polosukhin,Attention is All You Need(2017),神经信息处理系统进展 30(NIPS 2017)
[2] Hugging Face,Transformers(2024),HuggingFace.co
[3] OpenAI,ChatGPT 定价(2024),OpenAI.com
[4] A. Gu 和 T. Dao, Mamba: 线性时间序列建模与选择性状态空间(2023),ArXiv abs/2312.00752
[5] J. Alammar, 插图版 Transformer (2018). GitHub
[6] V. Sanh, L. Debut, J. Chaumond, 和 T. Wolf, DistilBERT:BERT 的蒸馏版——更小、更快、更便宜、更轻 (2019),第五届节能机器学习与认知计算研讨会——NeurIPS 2019
[7] Hugging Face, DistilBERT 文档 (2024) HuggingFace.co
[8] Hugging Face, BERT 文档 (2024) HuggingFace.co
持续学习——深入探讨弹性权重整合损失
带有 PyTorch 实现
https://medium.com/@alexml0123?source=post_page---byline--7cda4a2d058c--------------------------------https://towardsdatascience.com/?source=post_page---byline--7cda4a2d058c-------------------------------- Alexey Kravets
·发表于Towards Data Science ·阅读时间 9 分钟·2024 年 7 月 2 日
–
介绍
训练人工神经网络时最重要的挑战之一是灾难性遗忘。当神经网络在某一任务(任务 A)上进行训练后,接着学习一个新任务(任务 B),在这个过程中,它会忘记如何执行原始任务。这篇文章将探讨一种解决该问题的方法——弹性权重整合(EWC)。EWC 提供了一种有前景的方法,能够缓解灾难性遗忘,使神经网络在获得新技能的同时,保留之前学过的任务的知识。
除非另有说明,本文中的所有图像均为作者提供
直觉
图 1:EWC 的直觉,图来自论文
已经证明,对于一个任务,存在许多配置的最优参数,它们在该任务上有较低的误差——如上图所示,任务 A 和任务 B 分别对应灰色和黄色区域。假设我们已经为任务 A 找到了一个这样的配置θꭺ,当从这个配置继续训练模型以解决新任务 B 时,我们有三种不同的情况:
- 如果只是继续在任务 B 上训练而不加以惩罚,我们最终会进入任务 B 的低水平区域,但在任务 A 上的表现将低于期望的准确度……
持续学习:入门
附加的论文推荐
https://pascaljanetzky.medium.com/?source=post_page---byline--e328ed1d072f--------------------------------https://towardsdatascience.com/?source=post_page---byline--e328ed1d072f-------------------------------- Pascal Janetzky
·发布于 Towards Data Science ·阅读时长 7 分钟·2024 年 10 月 15 日
–
目前,训练大型语言模型的成本大约在 430 万美元(GPT3)到 1.91 亿美元(Gemini)之间[1]。一旦有新的文本数据可用,例如通过许可协议,使用这些数据进行再训练可以提高模型的性能。然而,以这样的成本(并且不仅仅是在这些水平;哪家公司能拿出 100 万美元仅用于最终训练,更不用说前期实验了?)来看,频繁地从头开始再训练的费用是难以承受的。
照片由 Dan Schiumarini 提供,来源于 Unsplash
这就是持续学习(CL)发挥作用的地方。在持续学习中,数据随着时间的推移逐步到达,且无法(完全)存储。机器学习模型仅基于新数据进行训练;这里的挑战是灾难性遗忘:模型在旧数据上的表现下降。表现下降的原因在于,模型仅将权重调整为当前数据的需求,因为没有动机去保留从以前数据中获得的信息。
为了应对遗忘并保持旧知识,提出了许多方法。这些方法可以分为三大类*:基于复习、基于正则化、和基于架构。在接下来的部分中,我将详细介绍每一类,并介绍一些选定的论文供进一步探索。虽然我将重点放在分类问题上,但所有涉及的思想大多也适用于其他领域,例如……
持续学习:三种常见场景
另外,推荐论文
https://pascaljanetzky.medium.com/?source=post_page---byline--e6c3260fe0cb--------------------------------https://towardsdatascience.com/?source=post_page---byline--e6c3260fe0cb-------------------------------- Pascal Janetzky
·发布于Towards Data Science ·阅读时间:6 分钟·2024 年 10 月 29 日
–
随着机器学习模型的训练成本上升[1],持续学习(CL)作为一种有效的应对措施应运而生。在 CL 中,机器学习模型(例如,像 GPT 这样的 LLM)在不断到来的数据流(例如,文本数据)上进行训练。关键在于,在 CL 中数据不能被存储,因此只有最新的数据可用于训练。主要挑战是如何在当前数据(通常称为任务)上进行训练,同时又不忘记从旧任务中学到的知识。避免遗忘旧知识至关重要,因为在测试时,模型会在所有已见任务的测试数据上进行评估。这个挑战通常在文献中被称为灾难性遗忘,且它是稳定性-可塑性权衡的一部分。
一方面,稳定性-可塑性权衡指的是保持网络参数(例如,层权重)稳定,以避免遗忘(稳定性)。另一方面,它意味着允许参数发生变化,以便从新任务中学习(可塑性)。CL 方法从多个角度处理这一权衡,我在上一篇文章中曾提到过。
图片来源:lionel mermoz 于Unsplash
今天文章的重点是 CL 研究中反复出现的基本场景:类别增量学习、领域增量学习和任务增量学习。
使用 sktime 便捷的时间序列预测
如何让预测像散步一样简单
https://dr-robert-kuebler.medium.com/?source=post_page---byline--bb82375e846c--------------------------------https://towardsdatascience.com/?source=post_page---byline--bb82375e846c-------------------------------- Dr. Robert Kübler
·发布于Towards Data Science ·阅读时间 8 分钟·2024 年 9 月 25 日
–
图片由Christel F.提供,来源于Unsplash
啊,时间序列预测。这是许多数据科学家的经典任务,几乎在各行各业都有应用。这个领域之所以如此宝贵,是因为如果你拥有一颗水晶球,能提前看到一些关键数字,你可以利用这些信息抢占先机,为即将到来的挑战做好准备。
作者提供的图片。
以呼叫中心为例:预测呼叫量可以优化人员配置,确保高效处理客户咨询。在零售业,预测商品何时会缺货可以及时重新订货,避免销售损失并最大化收入。当然,股市预测的“圣杯”也在这里:如果你能做到这一点,你就会变得富有。
在本文中,我想向你展示如何使用令人惊叹的库sktime轻松完成这项工作,sktime 是时间序列预测领域中的 scikit-learn。
为什么不直接使用 scikit-learn 呢?
很好的问题!这就像在问:“我有刀和砧板,为什么还要使用复杂的食物处理器?”当然,你可以切菜……
会话分析是企业数据科学的未来
LLMs 不会取代数据科学家,但它们将改变我们与决策者的合作方式
https://medium.com/@jmpatel?source=post_page---byline--c0f0c2f7ba7b--------------------------------https://towardsdatascience.com/?source=post_page---byline--c0f0c2f7ba7b-------------------------------- Jignesh Patel
·发表于Towards Data Science ·阅读时长 8 分钟·2024 年 7 月 9 日
–
大型语言模型(LLMs)本应让数据科学变得更加简单。它们可以为每一个想得到的功能生成 Python 和 SQL 代码,将重复性任务的完成时间从几分钟缩短到几秒钟。然而,随着 LLMs 的出现,组装、维护和验证数据工作流变得更加困难,而不是更简单。
LLM 代码生成器为私营部门的数据科学家带来了两个相关的问题。首先,LLMs 设定了数据科学家应当更快工作的预期,但隐私考虑可能要求他们不向 LLM 发送机密数据。在这种情况下,数据科学家必须以零散的方式使用 LLMs 来生成代码,确保 LLM 无法了解整个数据集。
这就导致了第二个问题:在解释结果时缺乏透明性和可重复性。当数据科学家以“传统”方式进行分析时,他们会创建确定性代码,例如使用 Python 编写的 Jupyter 笔记本,并生成最终的分析结果。而 LLM 是非确定性的。你多次问它相同的问题,可能会得到不同的答案。所以,虽然工作流可能会产生洞察,但数据科学家可能无法重现得出这一结果的过程。
因此,LLMs 可以加速单个步骤代码的生成,但它们也有可能削弱数据团队与决策者之间的信任。我认为解决方案是采用一种更加会话化的分析方法,在这种方法中,数据专家与决策者共同创建和讨论洞察。
生成式 AI 的双重祝福
高管们为数据科学分配预算,希望它能推动决策,从而增加利润和股东价值——但他们不一定知道或关心分析是如何工作的。他们希望更快地获得更多信息,如果 LLM 能加速数据科学代码的生成,那么数据团队最好能与之配合生成代码。如果代码相对简单,这一切都很顺利,如果 数据科学家能够构建并检查每个组件,然后再进行下一步。但随着复杂性的增加,这个过程变得复杂,导致分析更容易出错,文档编写和审查变得更加困难,也更难向业务用户解释。
为什么?首先,数据科学家越来越多地在多种编程语言中工作,并且使用特定工具(如 Snowflake 或 Databricks)的方言。LLM 可能生成 SQL 和 Python 代码,但它们并不免除数据科学家理解代码并进行测试的责任。在多种编程语言中充当防止“幻觉”发生的前线防线是一个重要负担。
第二,LLM 不一致,这可能导致新生成的代码整合起来时变得杂乱。如果我运行一个请求 Python 中表连接函数的提示,LLM 可能会每次给出不同的输出。如果我想稍微修改一个工作流,LLM 可能会生成与之前给出的所有代码不兼容的代码。在这种情况下,我是应该调整现有代码,还是采纳新代码?如果旧代码已经在生产环境中部署了呢?这就有点乱了。
第三,LLM 代码生成有可能快速放大一个错误并掩盖根本原因。例如,一旦代码深度嵌套,从头开始可能比排查问题更容易。
如果一个分析结果表现出色,并且决策者从中受益,那么没有人会要求了解工作流的细节。但如果决策者发现他们基于误导性的分析做出了决策——并因此牺牲了自己的优先事项——他们会开始不信任数据,并要求数据科学家解释他们的工作。当分析结果存在于笔记本中并呈现为嵌套的代码,每个组件都来自 LLM 时,说服业务用户信任该分析是非常困难的。
我们不是以代码思考的
如果我向其他数据科学家展示一个 Python 笔记本,他们会理解我想做什么——但他们会很难识别代码中任何问题的根本原因。问题在于,我们试图在代码中进行推理和思考。编程语言就像摩尔斯电码一样,在没有提供语境和意义的通用语言的情况下,它们本身没有任何意义。一个潜在的解决方案是,花更少的时间在代码的世界里,多花些时间在通俗的英语中。
如果我们用英语进行分析、记录和讨论,我们更有可能理解我们开发的工作流,以及它们为何合理或不合理。此外,我们也更容易将这些工作流传达给那些应该根据这些分析采取行动但可能不完全信任它们的业务用户。
自 2016 年以来,我一直在研究如何将代码抽象为英语,以及如何将自然语言抽象为 SQL 和 Python。这项工作最终促使我的同事 Rogers Jeffrey Leo John 和我基于用普通英语命令和问题创建分析的想法,推出了一家公司——DataChat。在我在卡内基梅隆大学的工作中,我经常使用这一工具进行初步的数据清理和准备、探索和分析。
如果,企业数据团队不仅仅是用英语记录工作,而是与决策者合作,在实际环境中共同创建他们的初步分析呢?数据科学家不再是孤立地花费数小时进行无法复现、可能无法回答高管最大问题的分析,而是像创意人员引导头脑风暴会议一样,推动分析会议的进行。这是一种能够建立信任和共识的方法。
为了说明为什么这是企业数据科学的一个富有成效的方向,我将通过一个示例来演示这可能是什么样子。我将使用 DataChat,但我想强调的是,还有其他方法可以用通俗语言呈现代码,并使用 LLM 记录数据工作流。
与决策者同在的会议室中
总结一下,我们使用的是 LLM 现在流利的编程语言——但它们可以针对相同的提示提出多个解决方案,这可能会影响我们保持代码质量和复现分析的能力。这种现状带来了分析结果可能误导决策者并导致代价高昂的行动,从而破坏分析创建者和用户之间的信任。
然而,现在我们正处在一个董事会会议室内,那里坐着一家专注于电子产品的电子商务公司 C 级高管。本示例中的数据集是为了看起来真实而生成的,但并不来自任何实际公司。
在 Python 中分析电子商务数据集的典型逐步指南可能会像这样开始:
import pandas as pd
# Path to your dataset
file_path = 'path/to/your/dataset.csv'
# Load the dataset
df = pd.read_csv(file_path)
# Display the first few rows of the dataframe
print(df.head())
这对于数据科学家来说是有指导意义的——我们知道编码者已经加载了数据集。这正是我们要避免的。业务用户并不关心。用英语抽象出来,以下是我们数据集的等效步骤:
C 级团队现在明白我们在分析中包含了哪些数据集,他们希望将这些数据集作为一个整体来探索。因此,我们需要将这些数据集合并。我将使用普通英语命令,就像我在与 LLM 对话一样(间接地,我确实是在与 LLM 对话):
现在我有了一个合并的数据集和一个 AI 生成的关于它们如何合并的描述。注意到我之前的步骤,即加载数据集,也可以看到。如果我的听众想了解更多关于导致此结果的实际步骤,我可以调出工作流程。这是以引导英语语言(GEL)编写的高层次代码描述,最初我们在一篇学术论文中开发了它:
以 DataChat GEL 表达的工作流程
现在,我可以回答来自 C 级团队的问题,这些人是我们业务领域的专家。我一边进行分析,一边培训团队如何使用这个工具(因为最终,我希望他们能自己解答基本问题,将我分配给能充分利用我技能的工作)。
首席财务官注意到每个订单项的价格有给出,但没有给出每个订单的总额。他们希望看到每个订单的价值,因此我们问:
首席营销官问关于特定商品销售及其在不同时间点的波动情况。然后,首席执行官提出了一个更具战略性的问题。我们有一个类似于亚马逊 Prime 的会员计划,旨在提高客户生命周期价值。会员对销售有何影响?团队假设会员花费更多,但我们问:
图表显示会员几乎没有增加销售额。执行团队感到惊讶,但他们已经和我一起走过了分析过程。他们知道我使用的是一个强大的数据集。他们要求看看这个趋势是否在几年内依然成立:
从一年到另一年,会员似乎对购买几乎没有影响。当前在提高会员数方面的投资可以说是浪费的。测试旨在增加购买的会员福利或等级可能更有意义。这对我们的数据团队来说可能是一个有趣的项目。如果我们反而向高管发送了一份报告,声称会员对销售没有影响,那就会遇到更多的抵触。
如果某个与当前会员策略相关的人不满这个结论——并且希望亲自了解我们是如何得出这个结论的——我们可以仅共享该图表的工作流程:
我们的分析环节即将结束。工作流程已经记录下来,这意味着任何人都可以审查并重现它(GEL 代表精确的代码)。几个月后,在测试和实施新的会员功能之后,我们可以在更新的数据集上重新运行这些步骤,看看会员和销售之间的关系是否随着时间的推移发生了变化。
对话式数据科学
通常,数据科学是按需定制的。决策者会要求对某个问题进行分析;数据团队提供分析结果;至于决策者是否使用这些信息,以及如何使用,分析师和数据科学家未必知道。也许决策者会基于初步分析提出新问题,但时间已不多——他们必须立刻采取行动。没有时间再请求更多的见解。
利用 LLM,我们可以使数据科学变得更加对话式和协作性,同时揭开分析背后的神秘面纱,弄清楚这些分析从何而来,以及它们是否值得信任。数据科学家可以像我刚才展示的那样,使用广泛可用的工具进行简洁明了的英语会话。
会话分析并不会让笔记本环境变得不重要——它通过提高数据科学家与业务用户之间的沟通质量来补充这一环境。希望这种分析方法能培养出更多的知情决策者,他们学会提出更有趣、更大胆的数据问题。也许这些对话将促使他们更加关注分析质量,而不再过于关注我们通过代码生成 LLM(大语言模型)能多快创造分析结果。
除非另有说明,所有图片均由作者提供。
卷积层—CNN 的构建模块
卷积层是什么,以及它们如何促进计算机视觉领域的深度学习
https://medium.com/@egorhowell?source=post_page---byline--501b5b643e7b--------------------------------https://towardsdatascience.com/?source=post_page---byline--501b5b643e7b-------------------------------- Egor Howell
·发表于Towards Data Science ·阅读时间 9 分钟·2024 年 1 月 11 日
–
”www.flaticon.com/free-icons/neural-network
" title=”neural network icons”>神经网络图标由 juicy_fish 创建 — Flaticon.
计算机如何看待图像
和我你不同,计算机只使用二进制数字工作。所以,它们不能像人类一样看和理解图像。然而,我们可以用像素来表示图像。对于灰度图像,像素越小,图像越暗。像素的取值范围是 0(黑色)到 255(白色),中间的数字则代表不同灰度值。这个数值范围等同于字节在二进制中的表示,即²⁸,这是大多数计算机的最小工作单位。
以下是我用 Python 创建的示例图像及其对应的像素值:
一张花朵图像的示例,已分解成其像素。图表由作者在 LaTeX 中生成。
使用这个概念,我们可以开发出能够在这些像素中识别模式的算法,从而对图像进行分类。这正是卷积神经网络(CNN)所做的***。
大多数图像不是灰度图像,而是带有颜色的。它们通常通过 RGB 表示,其中有三个通道:红色、绿色和蓝色。每种颜色都是一个二维的像素网格,然后将这些网格叠加在一起…
任何 Rummy 变种的核心 AI
Rummy AI 的逐步指南
https://medium.com/@iheb.rachdi?source=post_page---byline--4ff414da1703--------------------------------https://towardsdatascience.com/?source=post_page---byline--4ff414da1703-------------------------------- Iheb Rachdi
·发表于 Towards Data Science ·阅读时间 10 分钟·2024 年 11 月 9 日
–
作者使用 DALL-E 生成的图像
动机
在我为 Rummy 游戏开发强化学习(RL)模型的过程中,我到了需要一个 AI 对手来执行环境设置并为模型训练提供支持的阶段。然而,在网上搜索后,我发现用于创建 Rummy 游戏 AI 的资源非常有限,且现有的少数解决方案对我来说太慢了。由于 AI 将用于训练(而且没有它训练时间已经很长),因此,AI 需要在处理速度和内存使用上都能快速高效地运行。不用说,暴力破解法根本无法满足需求,所以我必须尝试各种算法和优化技术,以达到适合训练的复杂性和速度。
为什么要阅读这篇文章?
我们在这里构建的是一个通用、可适应的系统,适用于几乎任何你可能开发的 Rummy 游戏类型。你只需要在其基础上添加自己的策略层,然后让 AI 根据该系统的输出做出决策。此外,你还可以将其直接集成到你的 Rummy 游戏中,作为帮助玩家整理卡牌的工具,自动将卡牌分成可能的组合。此外,我们在这里实现的技术还可以应用到其他领域,所以无论如何,我保证它会在某种程度上对你有所帮助。
从整体来看,
本文不会涵盖完整的 AI,而是介绍 AI 的基本构建块和核心组件,我们称之为“手牌评估器”系统。该手牌评估器分析给定的 Rummy 手牌,并提取所有可能形成的“组合”。它作为初步步骤,为 AI 决策过程打下基础,后续将在另一篇 Medium 文章中进一步探讨。
项目范围与预期
在开始之前,定义我们目标系统的范围至关重要。简而言之,我们将构建一个可以处理一组n张 Rummy 牌(在我们的例子中为 15 张)的系统,并输出从这些牌中能提取出的有效组合或“组合(combo)”。为了使该系统能广泛适应不同的 Rummy 变种,我们将排除两个特定选项:首先是小丑牌的使用,其次是将 A 牌放在 K 牌后面组成顺子。通过设定这些规则,系统变得更加易于理解。然而,这些设计选择并不限制系统的适应性,因为如果需要,它可以很容易地扩展以包括这些规则。
由于这个手牌评估器将在整个游戏过程中反复调用,因此必须保持优化并高效使用内存。
此外,鉴于 Rummy 游戏的性质,为了让 AI 处理所有潜在的行动,它需要通过添加或移除牌来评估不同的场景。为了解决这个问题,手牌评估器系统必须支持动态修改手牌。理想情况下,我们希望避免从头开始重新处理手牌;而是希望利用之前系统运行中已经处理过的手牌,以最小化每次手牌修改后重新提取组合的工作量。
关键术语和设置
牌组: 该牌组包含 104 张卡牌,其中有 52 张独特的卡牌,每张卡牌都重复一次,总共有 13 * 4 * 2 = 104 张卡牌。
牌面值(Card Ranks): 从 1 到 13,其中 11、12 和 13 分别代表 J(杰克)、Q(皇后)和 K(国王)。
花色(Card Suits): 四种花色为红心、黑桃、梅花和方块,分别可以用H、S、C、D或相应的图标表示。
顺子(Run): 三张或更多连续点数的同花色牌。
示例:3H | 4H | 5H
刻子(Set): 三张或四张同点数但花色不同的牌。
示例:6H | 6S | 6D
丢牌(Dump): 无法用来组成有效组合的牌。
组合(Combo): 一种可能的手牌划分方式,包括顺子、刻子和丢牌。
示例:
手牌:
3H | 4H | 5H | 6H | 7H | 7C | 7S | 6S | 10S | JD | 6D | KH | 2C | 3D | 4S
一个可能的组合:
· 顺子: 3H | 4H | 5H | 6H
· 刻子(Set): 7H | 7C | 7S
· 丢牌区: 6S | 10S | JD | 6D | KH | 2C | 3D | 4S
系统分析
识别与收集关键数据
我探索了几种算法,以优化和减少所有可能组合的搜索空间。然而,每张牌可以出现两次的事实增加了潜在组合的数量,使得跟踪和验证每个组合变得具有挑战性。在参与 Codeforces 比赛时,我遇到一个问题,提醒我想到了‘岛屿问题’,它让我对手牌评估系统的处理方式有了新的洞察。
我们可以将手牌表示为一个 4x13 的二维网格,其中每一列代表从 1 到 13 的点数,每一行对应四种花色。网格中的每个单元格包含手牌中对应牌的数量,在我们的情况下可能是 1、2 或 0。这允许我们将手牌划分为“岛屿”,岛屿定义为根据以下连接规则,计数为 1 或 2 的相邻格子的组:
1. 如果两个单元格在网格中有一边相接(左、右、上或下),则它们被认为是连接的。
2. 同一列中的所有单元格也被认为是连接的,如果它们都包含至少一个 1,即使它们不相邻(上下)。
‘手牌 A’的 EXP:11C 3H 4H 11D 3D 5H 9D 2H 6H 3C 4H 3D 4D 5H 12D 3C
‘手牌 A’的表格表示
我们的第一个任务是识别并标记所有不同的岛屿。由于每个岛屿彼此独立,我们可以通过将每个岛屿映射到一个类类型来简化工作,称其为 cardGraph。该类将负责该岛屿的提取、修改或删除操作。
为了更清楚地说明,接下来我们将孤立一个岛屿并在后续章节中进行处理,这样你可以更容易地跟随。如果有帮助,你可以将每个岛屿视为一个连接图,如下图所示:
左侧:表格中表示的岛屿;右侧:相同岛屿在连接图中的视角
现在,如果你考虑多个岛屿示例并尝试提取可能的组合,你会注意到有些牌在分支潜在组合时起着独特的作用。我们将这种牌称为控制点或简写为Cpts,因为它们通过显著减少搜索空间,在后续步骤中起到了至关重要的作用。
Cpts:为了将一张牌视为 Cpts,它必须处于一个需要我们做出选择的位子,即决定将它添加到哪个组合(顺子或三张相同)。如果一张牌可以自然地适应多个组合而不需要做出强制选择(例如,一张重复的牌可以有两个顺子选项,每张牌都会添加到一个顺子),那么它不会被视为 Cpts。
在我们的岛屿示例中,红心 3 被标识为一个 Cpts。下面列出了红心 3 可以附加的所有组合,每次一个。
我们的下一步是标记每张符合条件的卡片,称之为 Cpts。为此,我们将创建一个 4x13(字节类型)的表格,称为 _flagMap。为了提高内存效率,可以将此表格设置为共享表格,每个由手牌创建的 _cardGraph 实例都可以引用并使用它。在此表格中,每张岛屿上的卡片将被分配到 _flagMap 中相应索引的位置,这个字节将表示其在不同顺子或套牌中的潜在位置。如果一张卡片符合 Cpts 条件,它将被存储在一个栈中(我们稍后需要使用),我们称之为 _cptsStack。以下是字节结构的细节:第一位表示卡片是否属于顺子,第二位表示卡片是否属于另一个顺子,第三位表示卡片是否属于套牌,第四位表示卡片是否属于多个套牌。
这是一个比特流的示例:00000111 在这里我们有:
• 第一位(1)表示卡片可以属于一个顺子。
• 第二个位(1)表示该卡片可以属于第二个顺子。
• 第三位(1)表示卡片属于一个套牌。
• 第四位(0)表示卡片不属于第二个套牌。
我们可能会遇到一种情况,其中某张卡片的配置为 00000101(没有副本),这意味着卡片属于一个顺子或一个套牌。或者,另一种配置可能是 00000011,这意味着卡片属于两个不同的顺子。
要识别一个 Cpts,只需计算其位表示中的‘1’的数量。如果这个计数超过了该卡片在手牌中的总数量,则认为它是一个 Cpts。例如,如果一张卡片出现了两次(即有两个副本),并且其位表示为 00000101,则它不是 Cpts。然而,如果位表示为 00000111,如示例所示,则它符合 Cpts 的条件。
在我们的岛屿示例中,_flagMap 表格将如下所示:
_‘手牌 A’ 示例的 FlagMap 表示 _
一旦我们填充了 _flagMap 并识别出 Cpts,接下来的任务是将岛屿分解为水平线和垂直线。但为什么? 将卡片图分解为这些线条可以简化顺子和套牌的识别过程,因为它让我们能够专注于可以更高效处理的连续卡片序列。正如你可能猜到的,垂直线将代表套牌,而水平线将代表顺子。
岛屿分解为水平线和垂直线
我们将把每条水平线存储在一个元组类型的列表中,其中第一个元素表示线的起始索引,最后一个元素表示结束索引(包括)。对于垂直线,简单地将列索引存储在一个列表中就足够了。
提示: 我们可以通过在一个循环中完成此任务,同时进行位表示步骤,从而实现 O(n)的复杂度。
生成组合
现在,让我们休息一下并回顾一下:我们已经识别了控制点 (CPTs),并将它们存储在 _cptsStack
中。我们还将岛屿分解为垂直和水平线,并用卡片的位表示填充了 _flagMap
。
在我们准备好数据后,剩下的就是利用它生成岛屿的所有有效组合。但我们该怎么做呢?这里有一个简化的方法:
1. 为控制点 (Cpts) 分配有效放置位置:
我们从 _flagMap
获取一个 cpts 的位表示,表示该 cpts 所有可能的放置位置。然后,我们查看该 cpts 在 _cardGraph
中的副本数量,并将其位表示调整为当前有效的配置。例如,如果该 cpts 的位表示为 00001111,并且有 2 个副本,我们可以生成该 cpts 的所有有效放置位置,计算公式为 C(4,2)=6。可能的组合包括 0011、0101、1100、1010、1001 和 0110。
2. 使用 DFS 配置每个 Cpts 的所有可能组合:
我们将使用深度优先搜索 (DFS) 来遍历每个 cpts 的有效放置位置,如步骤 1 所示。DFS 树中的每个节点代表给定 cpts 的一个可能放置位置,因此每条唯一的 DFS 路径代表一个有效的组合配置。对于每个“叶子”节点(DFS 路径的结束),我们进入下一步骤。
3. 生成组合:
在这一步中,我们遍历岛屿中的水平和垂直线,以识别连续段、集合和丢弃列表。对于每条线,分两次遍历完成,如下所示:
-
第 1 轮: 例如,对于一条水平线,我们不断地将卡片从 [线开始到线结束] 添加到一个列表中以形成连续段。如果 ( card_bit_representation | 00000001 == 0 ),则停止添加。如果连续段的长度大于或等于 3,我们将其添加到连续段组合中;否则,每张卡片将进入丢弃列表,并继续尝试形成另一个连续段,直到达到线的末端。
-
第 2 轮: 重复该过程,这次寻找与不同位模式匹配的卡片(或操作 00000010)。这使我们能够识别可能的第二个连续段。
相同的方法也适用于提取集合,但我们使用位操作 00000100 和 00001000。
4. 注册有效组合并移动到下一个 DFS 配置:
完成当前组合的所有连续段、集合和丢弃操作后,我们保存该组合,然后继续下一个 DFS 配置,重复该过程。通过这种方式,我们系统地探索所有潜在的有效组合配置。
演示输出
如果你正确编写了代码,并且将岛屿示例 “2H3H4H5H4H5H6H3C3C3D3D4D” 作为输入,它应该被分解为如下所示。注意,我已为每个生成的组合添加了一些计算,以便我们可以了解 AI 会如何反应。
控制台输出显示生成的岛屿组合示例
下一步是什么?
在下一篇文章中,我将深入探讨系统的其余部分,重点讨论手牌的动态修改和 AI 策略。如果你已经跟随到这里,应该不难看出我们如何优化添加和移除卡牌,并将我们一开始放置一旁的两个规则纳入其中。敬请期待,下次见!“希望如此😉”。
除非另有说明,所有图片均由作者使用 Lucidchart、Gimp 和 Python 创建。
会话式人工智能驱动的数据分析是否终于能够解决数据民主化的难题?
艺术作品由 Galen Okazaki 使用 Midjourney 创作
数据领导者的现实评估
https://medium.com/@kingkoa96746?source=post_page---byline--989ceec7f1e1--------------------------------https://towardsdatascience.com/?source=post_page---byline--989ceec7f1e1-------------------------------- Galen Okazaki
·发表于 Towards Data Science ·阅读时长:7 分钟·2024 年 10 月 1 日
–
开场场景:未来感办公室中的高管
一间宽敞现代的办公室。落地窗提供了下方繁忙城市的全景。空间既简洁又优雅,配有光滑的极简主义家具和几件精心挑选的艺术作品挂在墙上。
一位穿着得体的 40 多岁女高管专注于她的电脑屏幕,并问道:“请显示上个月的收入。” 她瞥了一眼屏幕,面无表情,接着问道:“按销售区域划分收入。” 此时,她皱着眉头,带着疑惑的神情问道:“按区域显示净利润率。” 在长时间凝视之后,她慢慢地叹了口气,拿起了手机……
数据民主化 - 使组织中的每个人都能访问数据的过程,无论其技术能力如何。
数据民主化是一个难题,像我这样的传统拉尔夫·金博尔信徒已经尝试解决了几十年。从用户友好的数据模型(数据仓库)开始,再到如今众多高度进化、用户友好的商业智能工具,我们已经走了很长一段路。
然而,大多数时候,从数据中获取新洞察的能力仍然是数据分析师、数据科学家和业务分析师的领域。对于大多数商业组织中的其他人来说,围绕数据的技术障碍(无论是真实的还是想象的)依然存在。
一线希望?
2022 年 11 月下旬,OpenAI 发布的 ChatGPT 使得普通大众(即:非技术人员)只需输入自然语言的请求***(prompt)***,就能与大型语言模型(LLM)进行互动。通过这个对话式用户界面,用户可以向 LLM 提问,LLM 会根据其“训练”过的数据回答问题。以 ChatGPT 为例,它的训练数据来源,嗯……互联网。
ChatGPT 将强大的数据处理能力带到了任何有权限访问它的人手中。当我们意识到这一机制的潜力时,许多数据分析领域的从业者开始思考它对我们领域可能带来的影响。
我们并没有思考太久……
在 ChatGPT 首次面向公众发布仅四个月后,OpenAI 发布了一个名为Code Interpreter的 ChatGPT 插件的 alpha 版本。通过这个插件,任何人都可以将数据集加载到 ChatGPT 中,输入几个提示语,并调用 Python 执行回归分析、描述性分析,甚至创建可视化图表。所有这些都不需要写任何代码!
Code Interpreter 的发布让我们初步看到了由对话式 AI 驱动的数据分析可能是如何工作的。这简直令人震惊!
不久之后,鉴于 ChatGPT 已经具备了编写代码(例如 SQL、R、Python 等)的能力,再加上 Code Interpreter 的初步功能,许多人开始预测数据分析师角色最终将被取代。(当时,我不同意这个观点,甚至还写了一篇文章反驳它)。***
艺术作品由 Galen Okazaki 使用 Midjourney 创作
**生成型 AI 会取代数据分析师的需求吗?**Galen Okazaki 为《Towards Data Science》撰写
当然,当你考虑到即便是你公司里技术能力最弱的人也能够仅凭输入文字或甚至口头提问就从数据中提取见解时,这种预测看起来并不难以接受。
那么,对话式 AI 驱动的数据分析,是否真的能成为弥合数据与其民主化之间技术鸿沟的关键?
让我们仔细看看。
对话式 AI 驱动的数据分析的现状
所以……从 Code Interpreter 的 alpha 版本发布到现在已经快一年半了,我们在对话式 AI 驱动的数据分析方面取得了多少进展?可能没有你预期的那么多。
例如:在 2023 年 7 月,ChatGPT 的 Code Interpreter 被重新命名并作为高级数据分析重新发布。Code Interpreter 不仅名称被更改了,而且……嗯……呃……好吧,至少新名称更准确地描述了它的实际功能。🤷♂️
公平地说,代码解释器/高级数据分析是一个很好的工具,但它从未旨在成为企业范围的分析解决方案。它仍然只能处理您上传到其中的静态文件,因为您无法将其连接到数据库。
为了更好地了解,让我们看一些目前已经整合了对话式人工智能界面的可用分析工具。
Power BI Q&A
第一次尝试实施对话式数据分析早于 ChatGPT 发布。在 2019 年,微软广泛使用的Power BI发布了一个名为“Q&A”的功能。它允许用户用他们的自然语言输入关于他们数据的问题,只要是英语(目前是唯一支持的语言)。
这是通过嵌入在现有仪表板或报告中的文本框界面完成的。通过这个界面,用户用自然语言提出关于特定仪表板或报告背后数据集的问题。Power BI 利用***自然语言查询(NLQ)***将文本问题转换为查询。响应以可视化方式呈现。
尽管这个功能有其用途,但它有一个重要的限制。Power BI Q&A 仅限于查询查看的报告或仪表板背后的数据集,如果您的最终目标是公司范围内数据的民主化,这一范围就太窄了。
Snowflake Cortex Analyst
一个更合适的对话式人工智能驱动数据分析的例子,可能支持数据民主化的是Snowflake 的 Cortex Analyst。
简而言之,Snowflake 本身是一个不断增长的 SaaS、基于云的数据仓库和分析平台,为客户提供了根据需要扩展其存储和/或计算的选项。其架构还支持高速数据处理和查询。
Cortex Analyst 是 Snowflake 的对话式人工智能驱动数据分析版本。一开始,它在一个方面远远优于 Power BI 的 Q&A,即不仅允许用户对现有报告或仪表板背后的数据集进行查询,而且 Cortex Analyst 将允许用户对整个基础数据库进行查询。它通过依赖语义层和模型来解释用户请求来实现这一点。
这带我们来到一个关键点。
拥有完全经过审查的语义层是数据民主化的绝对先决条件。在赋予公司内每个人使用数据的能力之前,必须就正在使用的数据和指标达成普遍一致的定义才是合理的。稍后会详细介绍。
尽管我在这里只讨论了两个关于对话式人工智能驱动的数据分析的例子,但它们应该足以帮助您设想它们在数据民主化中的潜在作用。
数据民主化面临的挑战
虽然以自然语言提问数据并获得答案的能力具有巨大潜力,我认为数据民主化面临的最大挑战并非技术性问题。
让我们从成功的数据民主化前提条件开始。这些条件包括强大的数据基础设施,充分解决前面提到的语义层和模型、数据素养、数据质量和数据治理。这些本身就是重大项目,现实情况是,对于许多公司来说,这些仍然是正在进行中的工作。
这对于数据素养尤为重要。
也就是说,尽管 92%的企业决策者认为数据素养很重要,但目前只有 34%的公司提供数据素养培训(来源:数据素养指数,沃顿商学院)。
另一个挑战是我在整个数据分析职业生涯中一直看到的。在我的经验中,总是有一部分用户(其中一些是 C 级高管),由于各种原因,拒绝使用我们为他们创建的商业智能界面。虽然他们通常是少数人,但这提醒我们,尽管花哨的功能很吸引人,但许多人仍然固执地只愿意使用他们最熟悉的工具。
总结
成功的数据民主化努力不能仅仅依赖于某一项特定技术,无论它多么具有前景。 它需要一种具有远见的、多方位的方法,除了适当的技术外,还需要强大的数据基础设施和以数据为核心的组织思维模式。
虽然由对话式 AI 驱动的数据分析本身无法解决数据民主化的难题,但它无疑可以在整体努力中发挥重要作用。
顺便提一下:
作为一个相信赋能业务部门与数据合作的人,我认为对话式 AI 驱动的数据分析具有巨大的价值。
在我看来,至少目前,这一工具的最高效和最佳使用方式是由业务分析师掌握。鉴于他们对业务运作的深刻理解(领域知识)以及已经具备的数据素养,他们最有能力利用对话式分析工具来获得答案,而无需被复杂的代码所束缚。
AI 在数据分析中的角色令我着迷。如果你对这篇文章或 AI 在分析中的作用有任何想法或评论,我非常希望听到你的声音!欢迎直接联系我:kingkoa96746@gmail.com
给我一个关注…
如果你有兴趣看到更多关于影响数据分析领域的文章,请@关注 Galen。感谢!
语言 AI 中的反事实
使用开源语言模型和 LLM
https://joshuabanksmailman.medium.com/?source=post_page---byline--956673049b64--------------------------------https://towardsdatascience.com/?source=post_page---byline--956673049b64-------------------------------- Joshua Banks Mailman, Ph.D.
·发表于Towards Data Science ·27 分钟阅读·2024 年 7 月 23 日
–
由作者使用 DALL-E 3 生成
去年 ChatGPT 的“轰动”使人们意识到——对许多人来说是一个惊讶——假定的人工智能有时可能会毫不犹豫地给出非常错误的答案。这个现象通常被称为“幻觉”,而过去 18 个月中发展出的主要应对方法是通过引入事实,通常通过检索增强生成(RAG),有时也称为相关答案生成,它基本上是将 GPT(生成预训练变换器语言模型)重新定向,去从已知包含相关事实的上下文中提取信息。
然而,幻觉并不是 GPT 可能犯错的唯一方式。从某些方面来看,其他类型的错误更深刻,也更值得我们考虑——特别是当人工通用智能(AGI)的前景经常被讨论时。我具体指的是被称为反事实(反事实推理)的现象,以及反事实性在决策中的关键作用,尤其是在因果推理方面。因此,事实性并不是有效 LLM 操作的唯一标准。
在这篇文章中,我将反思反事实如何帮助我们以不同的方式思考生成型 AI 的陷阱与潜力。我将通过一些具体示例,使用开源语言模型(特别是微软的 Phi)来展示。我将展示如何在本地设置 Ollama(也可以在 Databricks 中进行),无需太多复杂操作(包括有无 Docker 容器的设置),让你可以亲自尝试。我还将比较 OpenAI 的 LLM 对同样提示的反应。
我建议,如果我们想要开始思考“智能”是否存在于人工技术中,或由其散发出来,我们可能需要超越既定的机器学习范式,这种范式假设某种先前存在的事实正确性作为衡量标准。智能行为可能本质上是推测性的,尚缺乏足够的过去证据来明显证明其价值。艾萨克·牛顿、查尔斯·达尔文,或你的宠物猫,都能够就它们所处的物理世界进行实际推理,而这是大型语言模型(LLMs)——因为它们是非具身的——无法做到的。在一个机器能够流利写作的世界里,谈论比推测性的实际推理更为廉价。
由Florian Klauer拍摄,来源于Unsplash
反事实
什么是反事实,为什么我们要关心它?当然有一些无聊的猜测,有时还带有修辞性的转折:在一次年度会议上,一位股东问到,
“…自 1888 年以来的回报本来会没有董事们的‘傲慢和贪婪’以及他们的奖金。” ¹
…对此,退休银行家查尔斯·芒恩²回答道:
“那是我不会回答的问题。我是个历史学家,我不处理反事实问题。” ¹
这是一种回避问题的方法。毫无疑问,政治家们也曾使用过。尽管在法律事务中强调先例,反事实性仍然可以是一个合法的考虑因素。正如罗伯特·N·斯特拉斯费尔德所说:
“就像生活中的其他事情一样,我们沉迷于,实际上,要求进行许多关于‘可能本该是怎样’的猜测。虽然这种反事实思维往往保持隐蔽或隐含,但每当我们确定一个原因时,我们就会遇到它,实际上我们常常在尝试制定补救措施时也会遇到它……然而,……在法律争议中,麻烦的‘可能曾经是’无处不在。我们在各种情况下都会被它绊倒,并以不一致的方式回应它,从肆无忌惮的自信到面对任务时的瘫痪。然而,当我们意识到这一行为本质时,我们的自信往往会消退,我们变得不安、困惑,并对整个过程产生怀疑。”³
他进一步提出:
“…法律决策者无法避免反事实问题。因为这些问题是必要的,我们应该认真思考何时以及如何提出这些问题,以及如何区分好的回答与差的回答。”
反事实并不是一种“随便什么都行”的事务——远非如此。
这个问题贯穿于负责任的人工智能和可解释人工智能的讨论中(这两者往往会交织在一起)。请考虑欧盟《通用数据保护条例》(“GDPR”)中的“解释权”。⁴ 部分得益于Julia Stoyanovich的努力,纽约市在 2021 年通过了一项法律,要求被人工智能辅助招聘流程拒绝的求职者有权了解被拒绝的具体原因。⁵ ⁶ ⁷
如果你是数据科学家,提到“解释”(就模型而言)可能会让你想到 SHAP(或 LIME)。基本上,SHapley 加性解释(SHAP)是通过逐个处理每个预测特征(每一列数据),并打乱该特征(列)的观察值(行),来评估打乱哪个特征(列)会对预测结果产生最大影响。例如,对于被拒绝的求职者,它可能会说:算法拒绝你的主要原因是“工作经验”,因为当我们随机替换(排列)其他候选人的“工作经验”时,它对算法对你的评分的影响大于当我们用你其他特征(如性别、教育等)做这种替换(排列)时的影响。它是在进行定量比较,类似于“如果…会怎样”。那么,SHAPley 除了反事实之外还有什么呢?反事实性是解释的核心,因为它揭示了因果关系;而解释正是使人工智能变得负责任的关键。
暂时搁置责任和伦理问题,因果解释在商业中仍然有其他用途。至少在某些公司中,数据科学和人工智能被期望指导决策,这在因果推断的术语中意味着进行干预:调整价格或选择这个客户群体而不是那个,等等。干预是对现状的改变。因果推断的根本问题在于,我们无法观察到从未发生过的事情。因此,我们无法在进行干预之前观察到其结果。涉及风险时,我们不希望在没有充分预测结果的情况下进行干预。因此,我们希望提前推断,我们期望的结果可以通过我们的干预来实现。这意味着要对尚未成为事实的事件的因果效应进行推断。相反,这些事件是反事实的,也就是说,与事实相反。这就是为什么反事实被 Judea Pearl 等人描述为
因果推断的根本问题 ⁸
因此,“思想实验”这一概念,特别是在哲学中尤为重要——自路德维希·维特根斯坦普及以来,这一概念越来越被重视——作为一种探讨我们如何使用语言构建对世界理解的方式,并不仅仅是一个感伤的星愿。相反,反事实思维才是理性决策的核心。
在这方面,Eric Siegel 在他近期的《AI Playbook》中提出的建议可以作为推论:Siegel 建议将变革管理从事后思考调整为任何机器学习项目的前提条件。¹⁰ 如果从一开始就没有在机器学习项目中融入商业变革的构思,那么它的部署很可能永远只是幻想(永远的反事实)。解药是提前想象干预措施,并系统地推算其因果效应,这样你几乎可以品味到它们。如果所有有可能受益的各方都能预见——甚至持续地想象——其潜在收益,那么机器学习项目更有可能从幻想转变为现实。
正如 Aleksander Molak 在他近期的《Python 中的因果推断与发现》(2023)一书中解释的那样
“反事实可以被视为假设的或模拟的干预,假定一个特定的世界状态。” ¹¹
理性想象力的能力在许多哲学对理性行为体的定义中都起着重要作用。¹² ¹³
“[P]sychological research shows that rational human agents do learn from the past and plan for the future engaging in counterfactual thinking. Many researchers in artificial intelligence have voiced similar ideas (Ginsberg 1985; Pearl 1995; Costello & McCarthy 1999)” ¹³ ¹⁴ ¹⁵ ¹⁶
正如 Molak 所展示的,“当我们满足某些假设时,我们可以计算反事实”(33)¹¹。 这意味着在某些情况下,我们可以判断反事实推理是正确还是错误,正确还是不正确。在这方面,即使是虚构的东西(与事实相对)也可以在某种意义上是真实的。
图片来源:Hans Eiskonen 在 Unsplash
超越“随机鹦鹉”的人工智能
语言流畅性似乎成了新的亮眼之物。但这真的是思维吗?如果 IBM 的深蓝和 DeepMind 的 AlphaGo 的卓越表现仅仅归结于冷冰冰的计算,那么自 2022 年底以来,ChatGPT 语言流畅性所散发的人类气息则极大提升——或者至少重新框定——了那个老问题:人工智能真的能够“思考”吗?还是说,ChatGPT 背后的大型语言模型仅仅是一个“随机鹦鹉”,将极有可能的词语串联在一起,形成无数种新组合?不过,有时似乎某些被认为是人类思维的人——其中一些人甚至竞选国家最高领导职务——所做的也不过如此。那么,真正的智能能否站出来?
在她关于 2024 年达沃斯的简短文章《2024 年培养 AI 婴儿》中,Fiona McEvoy 报道了 Yann LeCun 强调人工智能不仅要从文本中学习,还要从视频资料中学习的观点。¹⁷ 然而,这仍然是被动的;它仍然是试图从已经存在的视频“事实”(已有的视频资料)中学习;McEvoy 报道道
“[达芙妮]·科勒认为,要超越单纯的关联,达到类似人类因果推理的水平,系统需要以具身的方式与真实世界互动——例如,从‘野外’技术中获取输入,如增强现实和自动驾驶车辆。她补充说,这些系统还需要有空间进行实验,与世界互动,以学习、成长并超越人类所能教给它们的。” ¹⁷
另一种表达方式是:人工智能至少需要以某种方式通过具身的方式与世界互动,才能磨练它们进行反事实推理的能力。我们都看过这些视频——或者近距离观察过——一只猫把物体从台面上推下来,似乎没有任何目的,除了让我们烦恼。人类的婴儿和幼儿也会这样做。然而,尽管看起来像是在表演,这其实并非单纯的行为表现。相反,在某种程度上,这些行为是假设检验。这些行为源于一种好奇心:如果我推倒这个花瓶,会发生什么?
由 DALL-E 3 生成并由作者编辑
请观看这个由北多伦多猫咪救援展示的3 秒动画 gif。¹⁸ 在这个简短的猫咪视频中,有一个额外的细节提供了更多的信息:猫咪即将跳跃;但在跳跃之前,它意识到有一个可以立即使用的物体,可以用来在跳跃之前测试距离或表面。她的跳跃是反事实的(她还没有跳跃)。她几乎已经准备好跳跃,说明她已经假设跳跃是可行的;猫咪迅速在脑海中模拟了跳跃;突然意识到台面上的瓶子提供了一个机会,可以进行干预,测试她的假设;这个行为是习惯性的。
我毫不怀疑她评估这种跳跃可行性的能力,源于她之前曾多次实际演练过类似的情境。一个没有实际“身体参与”的人工智能,会做出相同的判断吗?显然,人类在这方面的表现远远超过猫咪。这正是科学发现和技术发明发生的方式;但在更平凡的层面,这种智慧是所有生物常规运作的一部分,无论是猫咪跳到地板上,还是人类做出商业决策。
推理
通过进行干预来测试反事实似乎能提升我们进行查尔斯·桑德斯·皮尔士所称的溯因推理的能力。¹⁹ ²⁰ 与归纳推理(从重复的案例中推断出模式)和演绎推理(推导出逻辑含义)不同,溯因推理是提出一个假设。尽管数据科学家常常通过特征工程和超参数调优来探索假设场景,但溯因推理并不是机器学习应用的直接组成部分,因为机器学习通常是在基于事实的预设可能性空间上进行优化,而溯因推理则是在扩展可能性空间,超越已有的事实。因此,也许人工通用智能(AGI)还有很多需要追赶的地方。
这是一个假设:
- 缺乏进行干预的能力(或机会)的实体(无论是生物体还是人工体)不会培养出多少反事实推理能力。
反事实推理或溯因推理的价值主要在于,通过干预可以尝试验证这些假设。这就是它与动物(或人类)相关的原因。如果没有最终干预的机会,因果推理(溯因推理,假设)就是徒劳的,因此也不值得培养。
如果溯因推理(反事实推理)不能提供某种优势,那么它就不会在人类(或猫)身上进化出来。这样的优势只能与进行干预相关,因为溯因推理(反事实推理)按定义并不陈述当前世界状态的事实。这些观察促使了上述关于生物和人工实体的假设。
图片由Alice Alinari拍摄,来源于Unsplash
大型语言模型
如我上面提到的,RAG(检索增强生成,也称为相关答案生成)已成为指导基于 LLM 的生成型人工智能系统(如聊天机器人)朝向适当甚至最优响应的事实标准。RAG 的前提是,如果将相关的真实文本片段与您的问题一起直接提供给生成型 LLM,那么它不太可能产生幻觉,从而提供更好的响应。“幻觉”是人工智能行业的术语,指的是编造错误的回应。
正如大家所知,幻觉的产生是因为 LLM 尽管在大量人类编写的互联网文本上经过了充分的训练,但仍然不是无所不知的,反而倾向于以一种相对一致的自信语气发出回应。令人惊讶吗?实际上,这不应该令人惊讶。它是有道理的,正如著名的批评所说:LLM 本质上是在复述它们所训练过的文本。因为 LLM 的训练并不是基于人们有时不确定或不断变化的内心想法,而是基于那些已经达到了足够确定性阈值的想法,这些想法已经足够清晰,能够让一个人将其发布到互联网上最大的公共论坛,供所有人阅读。所以,也许可以理解,LLM 倾向于表现出过度自信——它们就是它们“吃”到的东西。
事实上,我认为可以公平地说,与许多诚实的人不同,LLM 根本不会口头表达它们的确定性水平;它们不会调节语气来反映它们的确定性。因此,避免或减少幻觉的策略是通过将 LLM 所需的事实直接呈现在它面前,确保它无法忽视这些事实,从而为 LLM 的成功设置条件。这对于聊天机器人通常部署的场景是可行的,因为这些场景通常具有有限的范围。通常与该范围相关的文档会预先收集(在向量存储/数据库中),以便在需要时可以搜索到特别相关的文本片段,并与正在提出的问题一起提供给 LLM,从而促使 LLM 在生成回应时能够利用这些片段。
从 RAG 到更丰富的
仍然存在许多可能导致问题的方式。为了解决这些问题,已经出现了一个完整的可配置工具包生态系统。NVIDIA 的开源 NeMo-guardrails 可以过滤掉不安全和不恰当的回应,并帮助检查事实性。John Snow Labs 的 LangTest 宣称拥有“60+种测试类型,用于比较 LLM 和 NLP 模型的准确性、偏见、公平性、稳健性等。”两个最专注于回应真实性的工具包是 Ragas 和 TruEra 的 TrueLens。
TrueLens(以及类似的 Ragas)的核心理念是:每次调用 RAG 管道时,涉及三种相互关联的文本单元:查询、检索的上下文 和 回应;而管道的失败程度取决于这些单元之间是否存在语义差距。TruEra 将其称为“RAG 三元组”。换句话说,为了使 RAG 管道正常工作,必须成功地完成三件事:(1)检索的上下文必须足够相关;(2)生成的回应必须充分基于检索到的上下文;(3)生成的回应还必须与原始查询充分相关。该循环中的任何弱点都会导致 RAG 管道调用的弱点。例如:
问题: “哪个国家首先登月?”
检索到的上下文:“尼尔·阿姆斯特朗于 1969 年 7 月登上月球。巴兹·奥尔德林是飞行员。”
生成的回应:“尼尔·阿姆斯特朗和巴兹·奥尔德林在 1969 年登月。”
生成的回应与原始查询不够相关——第三个链接已损坏。
评估 RAG 还有许多其他细微差别,其中一些已经在Adam Kamor 的 RAG 评估系列文章中讨论过。²¹
就真实性而言,RAG 策略是尽量通过从相关、可信的人工编写的现有文本中推导回应来避免幻觉。
RAG 的不足之处
那么,RAG 策略如何与大众对语言 AI 的批评相协调呢?即它仅仅是在重复训练时使用的文本?它是否超越了简单的重复,能够处理反事实情况?RAG 策略基本上试图通过补充由人工精心策划的额外文本来避免幻觉,这些人工环节能够关注聊天机器人的特定应用场景。因此,人工环节通过提供相关事实文本的语料库来补充生成型大型语言模型(LLM)的训练。
小说类作品通常不包括在填充 RAG 向量存储的语料库中。即便是现有的虚构散文也无法涵盖理论上无限多的反事实命题,这些命题可能在某种意义上被认为是真实的或正确的。
但智力包括评估此类反事实命题的能力:
“如果我踩进那个大水坑,我的脚踝以下会被打湿。”
在这种情况下,一个能够综合人类之前发出的言辞的 GenAI 系统——无论是来自 LLM 的训练集还是从下游检索并提供的上下文——并不令人印象深刻。与其说是原创推理,不如说它只是重复了别人已经说过的话。而当考虑到反事实情况时,重复已说过的话并不能达到目的。
这里有一对命题:
-
在 GenAI 仅仅是在重复的情况下,它只是呈现或综合它所训练的言辞。
-
在 GenAI 能够克服单纯重复并进行准确推理的情况下,它能够成功处理反事实情况。
Molak 解释的关于 反事实 的关键点是,它们“可以被认为是假设的或模拟的干预,假定了一个特定的世界状态”,或者正如 Pearl、Gilmour 和 Jewell 所描述的,反事实是对系统的最小修改(Molak, 28)。¹¹ ²² 重点是,正确回答反事实问题——甚至是合理回答——需要对世界有超越轶事的了解。对于 LLM,它们基于语料库的预训练,以及通过检索的事实文档填充的提示,使得它们的成功依赖于轶事的力量。而人类智能往往不需要,也无法依赖于轶事来进行反事实推理,这是为什么反事实推理在某些方面比忠实于事实更能衡量 LLM 的能力。
图片由 sutirta budiman 提供,来自 Unsplash
运行开源 LLM:Ollama
为了更具体地探讨与大型语言模型相关的反事实问题,我们通过运行一个生成模型来进行深入分析。为了减少障碍,我将通过下载一个模型并在本地计算机上运行来演示——因此你不需要 API 密钥。我们将使用 Ollama 来完成这一过程。(如果你不想自己尝试,可以跳过本节的其余部分。)
Ollama 是一款免费工具,方便在本地计算机上运行开源 LLM。也可以在 DataBricks 以及可能的其他云平台上运行 Ollama。为了简便起见,我们将在本地运行。(对于这种本地设置,我要感谢 Iago Modesto Brandão 的实用教程 Building Open Source LLM based Chatbots using Llama Index²³,以下内容即源自该教程。)
最简单的方法是:下载并安装 Docker(Docker 应用),然后在终端中运行几个命令来拉取并运行 Ollama 作为服务器,之后可以在 jupyter notebook 中访问(安装两个软件包后)。
下面是步骤:
1. 下载并安装 Docker www.docker.com/products/docker-desktop/
2. 启动终端并依次运行以下命令:
docker pull ollama/ollama
docker run -d -v ollama:/root/.ollama -p 11434:11434 - name ollama ollama/ollama
pip install install llama-index==0.8.59
pip install openai==0.28.1
3. 启动 jupyter:
jupyter notebook
4. 在 jupyter notebook 中导入 Ollama 并创建 LLM 对象。为了加快速度,我们将使用一个相对较小的模型:微软的 phi。
现在我们已经准备好通过 Ollama 使用 Phi 生成文本来响应我们的提示。为此,我们使用 llm 对象的 complete() 方法。它会生成一个响应(可能需要一分钟左右),然后我们将其打印出来。
让我们尝试一个反事实:“水泥气球会浮起来吗?”
图片由 Haneen Krimly 提供,来自 Unsplash
接下来让我们看看 Phi 输出中的“推理”过程。
解读 LLM(生成式人工智能)对反事实问题的回答
暂且不论微软的 Phi 不是一个非常强大的 GPT(生成预训练变换器),我们来看看它的回答:
I don’t have personal beliefs or opinions, but based on my knowledge of
physics, i can tell you that a cement balloon would not be able to float
in air due to its low density. air is lighter than most materials used for
balloons, and the inflated rubber material of a typical balloon provides
enough buoyancy to lift it into the sky. however, if you were to fill a
cement balloon with helium gas or another lighter-than-air substance, it
would likely float as long as the weight of the balloon itself is less than
the upward force from the lifting gas.
这看起来有道理。如果一个普通气球(我直接用嘴吹起来的那种)不能在空气中浮起,那么一个外壳是水泥的气球也不会浮起来。但是,如果气球内充入的气体比空气轻,比如氦气,那么一个普通气球就会飘升到天空中。
图片由 First Last @ rtisanyb 提供,来源于Unsplash
原始问题询问的是浮起的问题,但并没有明确指定介质;显然 Phi 假设它是空气。
那么现在让我们在问题中明确这一点,但选择水作为介质,毕竟这其实是我最初的意图。
No, a cement balloon would not float in water. Cement is a very dense material
and does not have much air trapped inside it, so it cannot create buoyancy
like a regular balloon filled with helium or air. Additionally, the weight of
the cement balloon combined with its density would make it sink to the bottom
of the water rather than floating on the surface.
很有趣。不仅得出它会下沉的结论,现在还假设介质是水而不是空气。它推理水泥的密度与空气相比,但没有考虑气球内部的空气。或者它可能在推理一个固体水泥球;不过,嗯,那可不是气球。除非,它所说的“水泥气球”是指类似于水气球:一个充满水的气球。
让我们再次修改问题,这次明确介质是水。
No, a cement balloon filled with helium would not float on water. This is
because helium is lighter than air and therefore lifts the balloon upwards
when it is released into the air. However, cement is much denser than water
and thus will sink to the bottom of the water. When you combine these two
facts, the result is that the concrete balloon filled with helium would not
float on water but instead sink to the bottom due to its weight.
In summary, while the helium in the balloon provides buoyancy to keep it
afloat in air, the density of the cement itself makes it too heavy for the
helium to lift and keep it afloat in water. Thus, a cement balloon filled
with helium would not float on water but instead sink to the bottom due to
its weight.
嗯,这有点奇怪。根据这个最新的回答,同样一个会在空气中浮起的物体,在水中却会下沉,尽管水的密度比空气大。确实,这是一种反事实的情境——谁见过水泥气球呢?不过让我们尝试想象一下:你站在一个水池旁,或者你可能在一艘船上。你手里拿着这个球形物体,假设在腰部位置。你放开它,它开始向空中浮起,但你在它还没有超出你的接触范围之前抓住了它。现在你把这个同样的球形物体放到水面上。当它接触到水面时,你慢慢地轻轻放开它。你觉得会发生什么呢?
这个充满氦气的水泥气球,刚刚还开始飘升到天空,现在是否会突然被水吞噬,沉入水底呢?这个感觉靠谱吗?
请记住我们自身的体现性知识,这在这里可能是相关的:我们自己的肉体——它们甚至没有轻到可以自发浮起——当放在水面上时,实际上不会下沉,而是会浮起来。这种体现性知识表明,一个足够轻以至于能够浮起的物体,也就比我们轻,因此它也足够轻,能够在任何地方浮动,如果我们(比那个物体重)能够在水面上漂浮的话。(请参阅 Lakoff & Johnson 了解体现认知的概述²⁴)
当然,GPT 语言模型像 Phi 这样的模型,显然是没有实体的;它缺乏体现的知识。在这种情况下,也许正是 Phi 缺乏的这种体现的知识,影响了它的反事实推理能力。
好的,但也许这里的问题是上下文和连续性。我上面展示的并不是一个真实的对话,就像与聊天机器人互动那样;我没有把之前的上下文传递给 Phi,而是问了孤立的问题。所以,或许 Phi 在回答不同问题时,想象了不同的水泥与氦气的比例。毕竟,如果水泥足够厚,或者它内部的氦气腔室足够小,那么氦气相对于空气(或水)的轻盈就不足以抵消水泥的重量。所以让我们重新措辞问题,澄清这一点。
It is unlikely that a cement balloon would be able to float in water since
cement is denser than water. The reason why the cement balloon might float
in air is due to the helium gas inside, which has a lower density than both
air and cement. In water, the buoyant force exerted by the water on the cement
balloon will be much less than the weight of the balloon itself. Hence, the
cement balloon would sink in water rather than float as it does in the air.
好的,但似乎还有问题。在同一个回答中,它断言水泥气球足够轻,可以飘浮到空中,但又太重,无法在水中浮起。这不是意味着水比空气轻吗?嗯,
那件事有点不对劲——但那不是水的问题。
我不是第一个提出 GPT 语言模型(像 Phi 这样的模型,通过在大量文本语料库上的训练获得能力)有时缺乏基本推理能力的人。在这种情况下,我提出的是一些推理能力如何源于隐性体现的知识——我们通过身体在物质世界中的存在获得的知识(我们的身体不会漂浮到空中,但确实会漂浮在水上)。我还在建议,反事实问题如何有助于揭示这一点。
GPT 模型,比如 Phi,主要是通过联想来运作的。GPT 和大型语言模型(LLMs)通常掌握得非常好的,正是语言使用中的联想或内涵方面。在内涵上,基于最常见的用法,‘水泥’一词与沉重相关。它和重金属铅有相同的联想——“Led Zeppelin”最初就是一种讽刺,暗示新“飞鸟”乐队将无法飞翔。虽然如果把足够的空气注入其中,水泥可能变得轻,但这不是一个容易想到的联想。相反,这是一个反事实,一个思想实验。即使我之前的措辞提到“非常薄的一层水泥……充满了氦气”,但“水泥”这个材料本身,太强烈地与沉重相关,使 Phi 无法想象它在水中漂浮——毕竟从来没有人造过水泥船!
图片由Tim Huyghe提供,来自Unsplash
那我们就换个东西,把水泥换成一个没有那么沉重联想的物质。比如玻璃怎么样?
As an AI language model, I do not have personal opinions or beliefs, but based
on scientific principles, a glass balloon filled with helium would indeed
float in water. This is because the density of helium gas is much lower than
that of water, causing it to rise and displace its weight in water.
Additionally, glass is less dense than water, which means that it would also
be buoyant enough to keep the balloon afloat on top of the water’s surface.
However, if the balloon were to be filled with a denser gas or a heavier
object, such as sand, then it may sink or become too heavy to float.
啊哈!在联想(内涵)障碍被排除之后,Phi 能够得出与人类推理相当的推理,充满了隐性具体现实的知识,即使在这种情况下,后者并非来源。在这个例子中,Phi 揭示了关键点:“氦气的密度远低于水的密度”,尽管它在“附加”点中搞混了,声称玻璃的密度低于水的密度,这其实是幻觉,根本不是真的。
OpenAI 的 ChatGPT 怎么样?
然而,显而易见的一个问题是,微软的 Phi 距离成为最先进的 LLM 还很远。它足够小,可以在笔记本电脑上本地运行。那么,OpenAI 的 GPT 模型怎么样呢?
要运行下面的代码,你需要在openai.com
上免费注册,获取一个 OpenAI API 访问令牌。
OpenAI 的 GPT 对于氦气填充的玻璃气球在水中的表现有什么看法?
No, a glass balloon filled with helium cannot float in water, regardless of
its size. The buoyancy of an object in a fluid is determined by the density
of the object compared to the density of the fluid. Density is defined as mass
divided by volume. In simple terms, if the object is less dense than the fluid,
it will float; if it is more dense, it will sink. In this case, the glass
balloon is filled with helium, which is a gas that is less dense than water.
Helium-filled balloons float in the air because the density of helium is lower
than that of the surrounding air. However, when we place the helium-filled
glass balloon in water, the density of the water is significantly higher than
that of helium. Even though the glass balloon is very thin and lightweight,
the overall density of the balloon, including the glass and helium, will still
be higher than that of water. Therefore, the balloon will not displace enough
water to generate enough buoyant force to counteract the weight of the balloon
itself. As a result, the balloon will sink in water rather than float.
哦不!就连 OpenAI 的 GPT 3.5 turbo 也陷入了水泥的内涵中。它知道“氦气充满的气球会浮在空气中”。它知道氦气比水的密度小。但随后,它错误地认为“气球的整体密度,包括玻璃和氦气,仍然会高于水的密度。”就像 Phi 上面做的那样,OpenAI 的 GPT 3.5 turbo 暗示气球比水重,但比空气轻,这就意味着水比空气轻。
我们知道它是错误的;但它并不是因为缺乏事实或直接与事实相矛盾而错误:整个水泥气球的情景远非事实;它是反事实的。
事后我们可以运用归谬法推断出,Phi 和 OpenAI 的 GPT 3.5 turbo 的负面结论确实与另一个事实相矛盾,即水比空气重。但在这一点上,反事实推理实际上是一种推理,而不仅仅是做白日梦。也就是说,反事实推理可以被证明是确定的真或假。尽管它偏离了事实,但它实际上和基于事实的推理一样,也是推理的一种形式。
事实、虚构与幻觉:反事实给我们展示了什么
自从 ChatGPT 在 2022 年底席卷公众意识以来,随之而来并持续引发的主要担忧就是幻觉。哦,天哪,一个 AI 系统居然会声称一些没有事实依据的事情!但是,与其将事实作为 AI 系统的主要标准——就像许多商业用例中那样——现在看来,事实与虚构并不是衡量 AI 系统是否成功的唯一维度。即使一个 AI 系统的回应是基于事实,它仍然可能是无关的,或者是个不合逻辑的推论,这就是为什么像 Ragas 和 TruVera 这样的评估方法特别关注回应的相关性。
当在相关性标准上失败时,甚至不涉及事实与虚构的轴线。一个无关的回答可以与相关的回答一样是事实,并且根据定义,反事实推理,无论其是否正确,都不是字面意义上的事实,当然也不是由 RAG 系统所构成的那种事实。也就是说,反事实推理并不是通过检索与提出的问题主题相关的文档来实现的。反事实推理的强大之处在于它如何运用类比来引入那些看似完全与问题无关的事实体系。它可能像这样被图示化:
图片由作者提供
也可以以这种方式表示其中的一些方面:
图片由作者提供
线性估计器与此有何关联? 由于反事实推理并非基于看似相关的事实,而是基于系统化的事实,有时这些事实来自其他领域,或是与主题相远的内容,因此它并不是直接从文档存储检索系统中明显获益的东西。这里可以类比线性估计器的类型:梯度提升树线性估计器本质上无法在特征大大超出训练数据的数值范围时做出准确预测;这是因为决策切分点只能基于训练时提供的数据来做出。相比之下,回归模型(其可以有封闭解形式)能够在特征超出训练数据的数值范围时做出准确预测。
从实践角度来看,这就是为什么线性模型在商业应用中可能会有帮助。只要你的线性模型准确,它可以帮助你预测提高或降低产品售价后,超出你曾经提供过的任何价格时的结果:一个反事实价格。而一个在验证中表现同样良好的梯度提升树模型并不能帮助你推理这些反事实,这有些讽刺,因为开发该模型的初衷可能正是为了应对这种情况。从这个意义上说,线性模型的可解释性与 SHAP 值提供的完全不同,因为后者对于模型训练数据分布之外的数据会显得毫无解释力。
LLM 的强大表现无疑证明了从人群外包写作的文本中巧妙合成的“智能”比预期的要强大得多。显然,这超越了过去将“智能”寄托于概念理解的趋势,特别体现在超越事实进行准确推理的能力上。因此,我发现挑战 LLM 达到这一标准是非常有趣的,这挑战了它们的基本特性。
Bonnie Moreland 来自 Freerange Stock
反思
反事实不仅仅是轻浮的娱乐,它们在科学进步中发挥了作用,体现了查尔斯·桑德斯·皮尔士所说的归纳推理,这与归纳(归纳推理)和演绎(演绎推理)不同。归纳基本上是指假设的提出。我们可能会问:我们应该期望 LLM 具备这种能力吗?有什么优势?我没有一个确定的答案,更像是一个猜测:在生成式 AI(GenAI)社区中,广为人知的是,当给 LLM 提供提示,要求其“逐步推理”时,通常会得到更令人满意的回答,即使推理步骤本身并不是预期的回答。换句话说,出于某种尚未完全理解的原因,让 LLM 在某种程度上模拟人类推理中最可靠的过程(逐步思考)会导致更好的最终结果。或许,虽然 LLM 并没有像人类那样进行训练推理,但人类推理的传承普遍有助于 AI 的最终结果。在这种情况下,考虑到归纳在科学发展中的重要作用,AI 的最终结果可能会随着 LLM 能够进行反事实推理而得到改进。
在LinkedIn上访问我
[1] 底线。 (2013) 《赫拉尔德报》。 5 月 8 日
[2] C. Munn, 《代际投资:联盟信托的历史》(2012)
[3] R. Strassfeld. 如果……:法律中的反事实。 (1992) 凯斯西储大学法学院学术共享平台
[4] S. Wachter, B. Mittelstadt, & C. Russell, 在不揭开黑箱的情况下进行反事实解释:自动化决策与 GDPR。2018 年。 《哈佛法律与技术期刊》,第 31 卷,第 2 期
[5] J. Stoyanovich, Julia Stoyanovich 在纽约市消费者和工人保护部关于 2021 年地方法第 144 条与自动化就业决策工具(AEDTs)相关事宜的证词。 (2022) 负责任 AI 中心,纽约大学坦顿工程学院
[6] S. Lorr, 一项招聘法为人工智能监管开辟了道路 (2023) 《纽约时报》,5 月 25 日
[7] J. Stoyanovich, “只有在你尝试监管时,你才会学会如何做”:Julia Stoyanovich 讨论《纽约时报》中的负责任 AI (2023),Medium。 6 月 21 日
[8] J. Pearl, 因果推断基础(2010)。《社会学方法学》。第 40 卷,第 1 期。
[9] L. Wittgenstein. 《哲学调查》(1953)由 G.E.M. Anscombe 和 R. Rhees 编,G.E.M. Anscombe 翻译,牛津:Blackwell 出版。
[10] E. Siegel. AI 行动指南:掌握机器学习部署的稀有艺术(2024)MIT 出版社。
[11] A. Molak. Python 中的因果推断与发现(2023)Packt 出版。
[12] R. Byrne. 《理性想象:人们如何创造现实的替代品》(2005)MIT 出版社。
[13] W. Starr. 反事实(2019)斯坦福哲学百科全书。
[14] M. Ginsburg. 反事实(1985)第九届国际人工智能联合会议论文集。
[15] J. Pearl, Judea. 《因果关系、行动与反事实》(1995)收录于《计算学习与概率推理》。约翰·威利与儿子出版社。
[16] T. Costello 和 J. McCarthy. 《有用的反事实》(1999)Linköping 电子计算机与信息科学文献,第 4 卷,第 12 期。
[17] F. McEvoy. 2024 年抚养“宝宝 AI”(2024)Medium。
[18] N. Berger. 为什么小猫喜欢把东西弄倒?(2020)北多伦多猫救援。
[19] C. S. Peirce. 《查尔斯·桑德斯·皮尔士文集》(1931–1958),由 C. Hartshorne、P. Weiss 和 A. Burks 编辑。哈佛大学出版社。
[20] I. Douven. 皮尔士的溯因(2021)斯坦福哲学百科全书。
[21] A. Kamor. RAG 评估系列:验证 OpenAI 的 RAG 助手与谷歌的 Vertex 搜索与对话性能(2024)Tonic.ai。
[22] Pearl, J., Glymour, M., & Jewell, N. 《统计学中的因果推断:入门》(2016)。纽约:Wiley 出版社。
[23] I. M. Brandão. 使用 Llama Index 构建基于开源 LLM 的聊天机器人(2023)Medium。
[24] G. Lakoff 和 M. Johnson. 《肉体中的哲学:具身心灵与它对西方思想的挑战》(1999)Basic Books 出版社。