TowardsDataScience 2024 中文翻译(九十)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

学习排序 — 针对用户对的情境项目推荐

原文:towardsdatascience.com/learning-to-rank-contextual-item-recommendations-for-user-pairs-dc4f56e24d94?source=collection_archive---------6-----------------------#2024-03-26

训练一个机器学习推荐引擎,学习一群人共同的偏好

https://medium.com/@franckjay?source=post_page---byline--dc4f56e24d94--------------------------------https://towardsdatascience.com/?source=post_page---byline--dc4f56e24d94-------------------------------- Jay Franck

·发表于Towards Data Science ·阅读时长 6 分钟·2024 年 3 月 26 日

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/10746b4c52d60313deafae632ae91b48.png

图片来自Lucrezia CarnelosUnsplash

本文教程适合…

  1. 任何对 DIY 推荐感兴趣的人

  2. 对基本的 PyTorch 排序模型感兴趣的工程师

  3. 咖啡迷

本文教程不适合…

  1. 想要将代码复制粘贴到生产系统中的人

  2. 想要一个 TensorFlow 模型的人

动机

想象一下,你正坐在沙发上,朋友或家人陪伴在侧。你打开了自己喜爱的游戏主机/流媒体服务/音乐应用,每一项都像是闪闪发光的可能性之宝,为你量身定制。但这些个性化的结果可能只是你独自一人的版本,并不能反映你在这群人中时的样子。

这个项目真的是从咖啡开始的。我迷恋自己烘焙来自 Sweet Maria’s(无任何关联)的绿咖啡豆,因为它有各种美味的可能性。哥伦比亚? 爪哇豆? 肯尼亚圆粒? 每一种描述都比上一种更加诱人。即使是我自己作为个人,也很难做出选择。如果你是为家人或客人购买绿咖啡豆,情况会怎样呢?

我想创建一个“学习排名”(LTR)模型,可能解决这个咖啡难题。对于这个项目,我首先构建了一个简单的 TensorFlow Ranking 项目,来预测不同咖啡的用户对偶排名。我对 TFR 有一些经验,因此它似乎是一个自然的选择。

然而,我意识到我之前从未从零开始构建过一个排名模型!于是我着手构建了一个非常简陋的 PyTorch 排名模型,看看我能否快速搭建一个并在过程中学习一些东西。显然,这并不适用于生产系统,我在过程中做了很多捷径,但这的确是一次极好的教学体验。

数据

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/c39e6fe51959571e8377a161bee7b3cb.png

照片由 Pritesh Sudra 提供,来源于 Unsplash

我们的终极目标如下:

  • 开发一个学习用户对偶偏好的排名模型

  • 将此应用于预测 k 个项目的按列表排名

用户和项目特征组合中可能包含的信号是什么,以为该用户对提供一组推荐?

为了收集这些数据,我不得不和我的妻子一起进行痛苦的咖啡品鉴研究。然后我们各自为这些咖啡打分,满分为 10 分。目标值只是我们两人分数的总和(最大为 20 分)。模型的目标是学习排名我们都喜欢的咖啡,而不仅仅是某一对中的一个成员。我们将使用的上下文数据如下:

  • 一对用户的年龄

  • 用户 ID,将转换为嵌入向量

SweetMarias.com 提供了大量的项目数据:

  • 咖啡的来源

  • 加工和培养笔记

  • 品尝描述

  • 专业评分(100 分制)

因此,对于每个训练示例,我们将把用户数据作为上下文信息,每个项目的特征集将被连接在一起。

TensorFlow Ranking 模型通常在 ELWC 格式的数据上进行训练:ExampleListWithContext。你可以把它想象成一个字典,包含两个键:CONTEXT 和 EXAMPLES(列表)。每个 EXAMPLE 内部是一个字典,包含每个你想排名的项目的特征。

例如,假设我正在寻找一款新的咖啡来尝试,系统展示了一个包含 k=10 种咖啡品种的候选池。一个 ELWC 将包括上下文/用户信息,以及一个包含 10 个项目的列表,每个项目都有自己的特征集。

由于我不再使用 TensorFlow Ranking,我自己做了一个简陋的排名/列表构建部分。我随机挑选了 k 个项目样本,并为它们评分,然后将它们添加到一个列表中。我把最初尝试的咖啡分成了训练集,之后的示例则成为了一个小的验证集,用来评估模型。

特征直觉

在这个玩具示例中,我们拥有一个相当丰富的数据集。从上下文角度来看,我们明显知道用户的年龄,并能学习到他们各自的偏好嵌入。通过 LTR 模型内部的后续层,这些上下文特征可以进行比较和对比。例如,是否一对用户中,一个喜欢浓郁的水果味,而另一个则喜欢杯中清新的柑橘和水果味呢?

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/bd27ef43ea5e42371aedad3d073eac48.png

图片由Nathan Dumlao提供,来自Unsplash

对于商品特征,我们拥有丰富的描述性文本,包括每种咖啡的品尝笔记、产地等。稍后会详细介绍,但总体来说,我们可以捕捉这些描述的含义,并将这些描述与上下文(用户对)数据进行匹配。最后,我们还有一些数值特征,比如每种商品的专家品尝评分,这些(应该)与现实有一定的相似性。

数据预处理

自从我刚开始从事机器学习行业以来,文本嵌入技术发生了惊人的变化。那些我曾用来尝试捕捉词语或短语语义的 GLOVE 和 Word2Vec 模型早已不再使用。如果你访问huggingface.co/blog/mteb,你可以轻松比较最新最强的嵌入模型,用于各种目的。

为了简化和熟悉,我们将使用huggingface.co/BAAI/bge-base-en-v1.5的嵌入模型,帮助我们将文本特征投射到 LTR 模型能够理解的格式。具体来说,我们将使用这个模型来处理 Sweet Marias 提供的产品描述和产品名称。

我们还需要将所有用户和商品的 ID 值转换为嵌入空间。PyTorch 通过Embedding层很好地处理了这一点。

最后,我们对浮动特征进行简单的RobustScaler缩放。所有这些都可以在我们的 Torch 数据集类内部完成,然后将其输入到用于训练的 DataLoader 中。关键在于分离出不同的标识符,这些标识符将在 PyTorch 的forward()调用中传递。Offir Inbar的这篇文章真的是帮我节省了不少时间!

模型构建与训练

关于 Torch 训练,唯一有趣的事情是确保两个用户嵌入(每个评分者一个)和训练列表中的k种咖啡具有正确的嵌入和维度,以便通过我们的神经网络。经过一些调整,我终于得到了一些结果:

这个前向传播将每个训练示例推入一个包含所有特征的单一连接列表中。

由于数据点如此之少(仅有 16 种咖啡被评分),训练一个强大的神经网络模型可能会很困难。我通常会并排构建一个简单的 sklearn 模型,以便比较结果。我们真的学到了什么吗?

使用相同的数据准备技术,我构建了一个 LogisticRegression 多类分类器模型,然后将 .predict_proba() 的得分输出,用作我们的排序。我们的指标能告诉我们这两个模型的表现如何吗?

结果

对于指标,我选择追踪两个:

  1. Top(k=1)准确率

  2. NDCG

目标,当然,是正确排序这些咖啡。NDCG 在这里非常适用。不过,我怀疑 LogReg 模型在排序方面可能会遇到困难,所以我考虑可能也加入一个简单的准确率指标。有时你只想要一杯真正好的咖啡,而不需要排序!

在我没有进行任何显著的参数调整的情况下,两个模型的结果非常相似。SKLearn 在(极小的)验证集上的 NDCG 稍微差一些(0.9581 对比 0.950),但准确率相似。我相信通过对 PyTorch 模型和 LogReg 模型进行一些超参数调优,结果在如此少的数据情况下会非常相似。不过至少它们的大致结论一致!

未来工作

我有一批新的 16 磅咖啡开始进行排名,并且故意将一些不太为人知的品种加入其中。我希望能稍微整理一下仓库,让它看起来不那么像一个临时的黑客作品。另外,我还需要为未见过的咖啡添加一个预测函数,以便我能搞清楚下次该买什么!

需要注意的一点是,如果你正在为生产环境构建推荐系统,通常使用一个专门为排序构建的真实库是个不错的主意。TensorFlow Ranking、XGBoost、LambdaRank 等在业界被广泛接受,并且解决了许多痛点。

请查看这里的仓库,并告诉我如果你发现任何 bug!希望你能受到启发,训练你自己的用户对排序模型。

学会遗忘:为什么数据科学家和 AI 从业者应该理解机器遗忘

原文:towardsdatascience.com/learning-to-unlearn-why-data-scientists-and-ai-practitioners-should-understand-machine-unlearning-866af9e5d712?source=collection_archive---------8-----------------------#2024-08-22

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/b325affbd7356ef4d8a853261870be17.png

图片由 Sue Winston 提供,来源于 Unsplash

探讨隐私与 AI 的交集,并通过使用 SISA 技术应用于卷积神经网络(CNNs)的 Python 示例,指导如何去除单个数据点对 AI 训练的影响。

https://medium.com/@raul.vizcarrach?source=post_page---byline--866af9e5d712--------------------------------https://towardsdatascience.com/?source=post_page---byline--866af9e5d712-------------------------------- Raul Vizcarra Chirinos

·发表于 Towards Data Science ·阅读时间:20 分钟·2024 年 8 月 22 日

截至本文撰写之时,根据世界银行数据,全球超过 32%的人口(大约 80 亿人)年龄在二十岁以下。这意味着大约 26 亿人出生在社交媒体时代,而且几乎可以确定,他们几乎所有的生活都已经在线记录,由他们的父母、亲密圈子,最终可能是他们自己*(取决于他们对社交媒体的依赖以及他们的网络)。如果再加上二十到五十岁之间的人群,我们就有另外 33 亿人,在某种程度上,他们的生活的一部分已经在线记录,涵盖不同的来源和格式(如图片、评论、视频等)。当然,我们可以根据超过五十岁的人群进行调整,或者考虑到并非每个人都有互联网接入或使用互联网根据世界银行 2021 年估算,至少有 35%的人无法接入或使用互联网)*,但我相信你明白我的意思。今天的数字世界中,确实有大量的我们生活的记录。

另一个高概率或也许是确定的*(我们可以再问一下 OpenAI 的 CTO🙄)是,这些数据中的大部分已经被用来训练今天部署的所有“最先进”模型,从大语言模型(LLM)到可以处理图像、视频或文本等信息的多模态 AI 模型。在这种背景下,当谈到数据、技术和隐私时,我们常常看到两方在寻找中间立场的过程中展开斗争。一方是每个人与技术之间的社会契约,我们愿意为获得技术带来的利益而交换部分数据权利。另一方面,是必须划定界限的问题,正如这一立场的支持者所说,“仅仅因为数据是可访问的,并不意味着它可以自由收集和使用”***。

在本文中,我们将探讨在讨论人工智能中的隐私时出现的一些挑战,包括对机器遗忘和**SISA 训练方法(分片、隔离、切片和聚合训练)**的简要概述,SISA 是一种最近开发的机器遗忘框架,旨在帮助管理或减少个体数据点在 AI 训练中的影响,并解决与“被遗忘权”相关的合规性挑战。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/87a2b54cfa5c87c3ce817b3778519984.png

照片由Tingey Injury Law Firm提供,来源于Unsplash

壁中低语的,将在屋顶上大声宣告。

历史上最早倡导隐私权的出版物之一是由两位美国律师塞缪尔·D·沃伦和路易斯·布兰代斯于 1890 年代发表的一篇文章。该文章标题为隐私权,旨在提高人们对未经授权的照片和早期报纸企业影响的认识,他们认为这些影响将八卦变成了一种商品,侵害了个人享受生活的权利,即被独立对待的权利

个人在身体和财产上的完全保护是与普通法同样古老的原则;但随着时间的推移,有时需要重新定义这种保护的确切性质和范围。….近期的发明和商业方法提醒我们,必须采取下一步措施来保护个人,并保障个体的权利,正如库利法官所称的“被独立对待的权利”(塞缪尔·D·沃伦,路易斯·布兰代斯,1890 年)。

自《隐私权》一书发布以来,时代已经发生了变化,但沃伦和路易斯·布兰代斯在一件事上并没有错;技术、政治、社会和经济的变革不断挑战现有或新兴的权利。对此,普通法应始终保持开放的态度,以应对社会的新需求,认识到社会的保护主要通过承认个人的权利来实现。

从那时起,隐私常常与传统的做法相联系,即保护我们关心和想要隐藏的东西,保持其不为公众所见,并控制其访问和使用。但同样的事实是,随着时间的推移,隐私的边界被颠覆性技术所挑战;摄影和视频设定了新的边界,最近则是数据的指数增长。然而,基于数据的技术不仅影响了数据合规的格局;它们还对我们的信仰和习惯产生了一些影响。社交媒体平台或超级应用就是一个例子,在这些平台上,我们愿意为了技术带来的好处而交换部分数据隐私。这意味着语境很重要,在某些情况下,分享我们的敏感信息更多依赖于像信任这样的价值观,而不一定是考虑隐私泄露。

“数据不仅仅是‘私密’或‘非私密’、‘敏感’或‘非敏感’的。语境很重要,社会的规范性价值观也是…”《高级 AI 助手的伦理》. Google DeepMind 2024

语境与隐私之间的关系是一个有趣的思维方向,被称为信息隐私模型。

“情境完整性” (Nissenbaum, 2004). 它指出,在发送者和接收者之间的每一次信息交换或流动中,都有社会规则来规范它。理解这些规则对确保信息交换得到适当监管至关重要。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/9aebf190562c633ed1345dca69ae20b6.png

图 01 来源:作者自创

一个清晰的例子可能是,例如,关于我孩子在学校表现的信息。如果一位老师将我孩子的成绩记录与其他家长或校外陌生人共享,我可能会认为这是隐私侵犯。然而,如果同样的老师将这些信息与其他教我孩子的老师共享,以便交流经验并改善我孩子的学校表现,我可能就不会那么担心,反而会依赖于信任、价值观和老师的良好判断。因此,在情境完整性方法下,隐私不再仅仅被看作是“独立生活的权利”这一僵化的状态。相反,重要的是信息流动应当得到适当的监管,考虑到其中的背景和治理规范,以界定其边界。隐私作为一项基本权利不应改变,但可以重新思考。

隐私的固有概念是否应该保持不变?还是我们应该首先理解支配信息流动的社会规则?

随着人工智能继续塑造未来,这一重新思考挑战着我们考虑是否需要适应现有的权利,或可能引入新的数字权利。

机器“遗忘”

无论你是将隐私视为一个僵化的概念,还是考虑情境完整性方法,我认为我们大多数人都会同意,我们所有人都应当享有公平处理个人数据的权利,且在需要时能获得我们的同意,并有权纠正或删除数据。

尽管《通用数据保护条例》(GDPR)促进了数据与隐私的共存,在监管框架中平衡隐私与人工智能仍然是一个不同的挑战。尽管我们可以从数据集删除或修改敏感数据,但在 AI 模型中这样做要复杂得多。AI 模型并非每天都重新训练,而且在大多数情况下,需要数月时间才能确保其可靠性。为了解决在 AI 模型中选择性地删除特定训练数据点(及其影响)而不显著牺牲模型性能的任务,像**机器“遗忘”**这样的技术应运而生,并正在进行研究,以寻找解决隐私问题的方案,遵守可能强制实施的法规,并保护用户删除或更正数据的法定权利。

与可以追溯到一百多年前的隐私政策研究相比,机器“遗忘”是一个相对较新的领域,最初的研究大约出现在 10 年前(Y. Cao 和 J. Yang, 2015)。

那么我们为什么要关注机器遗忘呢? 无论你是推动人工智能边界的 AI 研究员,还是在为终端用户优化 AI 解决方案的从业者,以下是将机器遗忘技术应用于机器学习流程的一些好理由:

· 被遗忘权(RTBF): 大型语言模型(LLMs)和最先进的基础模型以复杂且快速发展的方式处理数据。如同《通用数据保护条例》(GDPR)所见,用户请求删除权并将其纳入 AI 相关法规的制定,已只是时间问题。这将要求任何使用 AI 的公司调整流程以符合这些规定,并响应用户要求,从预训练模型中移除个人数据。

· 非零影响: 如今,差分隐私等框架的存在是为了通过引入噪音来确保对敏感数据集的一定隐私保护,从而隐藏单个数据点的贡献。然而,虽然差分隐私有助于减轻单个数据点的影响,但这种努力仍然是“非零的”。这意味着,目标数据点仍然有可能对模型产生某种影响。在需要完全删除数据点的情况下,可能需要采取不同的差分隐私方法。

· 性能优化: 众所周知,基础模型需要大量数据进行训练,这需要大量的时间和计算资源。从头开始重新训练完整模型以删除单个数据点可能是最有效的方式,以消除该数据点在模型中的任何影响,但这并不是最高效的做法 (模型需要频繁重新训练😨)。机器遗忘领域通过考虑时间和计算资源作为反向处理或消除特定数据点对模型参数影响的约束,来解决这个问题。

· 网络安全: 模型并不免受对手的攻击,这些攻击通过注入数据来操控模型行为,从而泄露用户的敏感信息。机器遗忘可以帮助去除有害数据点,保护用于训练模型的敏感信息。

在机器遗忘领域,我们可以找到两种思路:精确机器遗忘近似机器遗忘精确机器遗忘侧重于通过完全删除特定数据点来消除其影响 (就好像这些数据从未被引入模型一样),而近似机器遗忘旨在高效地减少训练模型中某些数据点的影响 (使模型的行为接近于如果这些数据点从未引入过模型的状态)。这两种方法都提供了多样化的技术来应对用户的删除权问题,同时考虑到模型性能下降、计算资源、时间消耗、存储资源、特定学习模型或数据结构等限制。

为了更好地理解该领域的最新研究工作,我推荐两篇有趣的阅读材料:机器去学习:解决方案与挑战(2024)学会去学习:机器去学习的见解(2023)。这两篇论文很好地回顾了近年来机器去学习领域科学家和研究人员的非凡工作。

SISA (分片、隔离、切片和聚合)

SISA 框架是“精确机器去学习”思想的一部分,旨在去除数据而无需对模型进行完全重新训练。该框架从这样一个前提出发:虽然从头开始重新训练,排除需要去学习的数据点,是与“被遗忘权”原则对齐的最直接方式(提供证明并确保不需要的数据已被移除),但它也认识到,对于使用大量数据集训练的复杂基础模型而言,这种方法可能会被视为一种天真的策略,因为这类模型训练需要高资源。因此,为了应对去学习过程的挑战,任何技术都应该满足以下要求:

  1. 易于理解(可理解性): 该技术应易于理解和实现。

  2. 准确性: 虽然某些准确性可能会丢失是合理的,但这一差距应该很小。

  3. 时间/计算效率: 它应比从头排除数据点所需的时间更少,并且所需的计算资源应类似于现有训练过程所用的资源。

  4. 易于验证(可证明的保证): 该技术应清楚地表明所请求的数据点已被去学习,而不会影响模型参数,并且证明可以轻松解释(即使是非专家也能理解)。

  5. 与模型无关: 它应适用于各种性质和复杂度的模型。

我们如何保证特定训练数据点的完全移除?我们如何验证去学习过程的成功?

SISA 框架(分片、隔离、切片和聚合)最初在 2019 年由 Bourtoule 等人提出,发表于论文 机器去学习,旨在提出一个替代解决方案,用于解决从机器学习模型中去除数据的问题,确保移除保证易于理解。该论文在引言部分易于阅读,但如果你不熟悉机器学习领域,可能会变得复杂。因此,我将尝试总结一些我认为有趣的技术特征,但如果你有时间,我强烈建议阅读这篇论文,它值得一读!(你还可以观看作者在 IEEE 安全与隐私研讨会上进行的 这篇视频演示 ,其中对论文的发现进行了有趣的展示)

SISA 训练方法包括多次复制模型,每个副本在数据集的不同子集上进行训练称为一个分片)。每个模型被称为“构成模型”。在每个分片内,数据进一步被划分为“切片”,并应用增量学习,相应地归档参数。每个构成模型在训练阶段主要与其分配的分片一起工作,同时在每个分片内使用切片来管理数据并支持增量学习。训练后,来自每个分片的子模型会被汇总,形成最终模型。在推理过程中,来自不同构成模型的预测结果会结合在一起,产生整体预测结果图 02说明了 SISA 训练方法的工作原理。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/8ff74577dc3e5573ec798beda7bfe2ba.png

图 02 来源:作者基于 Bourtoule 等人论文(2019)自行创作

当需要去学习某些数据时,只有包含需要去学习数据点的分片中的构成模型会被重新训练(数据点会从特定分片中的某个切片中去学习)。

应用 SISA:针对图像识别的 CNN 模型去学习与再训练

为了理解如何应用 SISA,我将使用 Python 进行一个案例示例。最近,我使用 PyTorch、计算机视觉技术和卷积神经网络(CNN)构建了一个基本的设置,用于追踪冰球球员和球队,并收集一些基本的表现统计数据*(你可以在这里访问完整的文章)*。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/7a960baa2383b62cff2c4cf942ad002a.png

使用计算机视觉进行球员追踪

尽管秘鲁滑旱冰曲棍球协会(APHL)已同意将 40 秒的视频用于该项目,但我们设想一个 SISA 应用案例的场景:某个球员抱怨自己的图像被使用,并行使删除权,要求从分类每个球员所属队伍的 CNN 预训练模型中移除他的图像。这将要求我们从训练数据集中移除该图像,并重新训练整个模型。然而,通过应用 SISA 技术,我们只需处理包含这些图像的分片和切片,从而避免了从头开始重新训练整个模型,节省了时间。

原始 CNN 模型结构如下:

# ************CONVOLUTIONAL NEURAL NETWORK-THREE CLASSES DETECTION**************************
# REFEREE
# WHITE TEAM (white_away)
# YELLOW TEAM (yellow_home)

import os
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torchvision.transforms as transforms
import torchvision.datasets as datasets
from torch.utils.data import DataLoader
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
import matplotlib.pyplot as plt

#******************************Data transformation********************************************
# Training and Validation Datasets
data_dir = 'D:/PYTHON/teams_sample_dataset'

transform = transforms.Compose([
    transforms.Resize((150, 150)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
])

# Load datasets
train_dataset = datasets.ImageFolder(os.path.join(data_dir, 'train'), transform=transform)
val_dataset = datasets.ImageFolder(os.path.join(data_dir, 'val'), transform=transform)

train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)

#********************************CNN Model Architecture**************************************
class CNNModel(nn.Module):
    def __init__(self):
        super(CNNModel, self).__init__()
        self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2, padding=0)
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
        self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
        self.fc1 = nn.Linear(128 * 18 * 18, 512)
        self.dropout = nn.Dropout(0.5)
        self.fc2 = nn.Linear(512, 3)  #Three Classes

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = self.pool(F.relu(self.conv3(x)))
        x = x.view(-1, 128 * 18 * 18)
        x = F.relu(self.fc1(x))
        x = self.dropout(x)
        x = self.fc2(x)  
        return x

#********************************CNN TRAINING**********************************************

# Model-loss function-optimizer
model = CNNModel()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

#*********************************Training*************************************************
num_epochs = 10
train_losses, val_losses = [], []

for epoch in range(num_epochs):
    model.train()
    running_loss = 0.0
    for inputs, labels in train_loader:
        optimizer.zero_grad()
        outputs = model(inputs)
        labels = labels.type(torch.LongTensor)  
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        running_loss += loss.item()

    train_losses.append(running_loss / len(train_loader))

    model.eval()
    val_loss = 0.0
    all_labels = []
    all_preds = []
    with torch.no_grad():
        for inputs, labels in val_loader:
            outputs = model(inputs)
            labels = labels.type(torch.LongTensor)  
            loss = criterion(outputs, labels)
            val_loss += loss.item()
            _, preds = torch.max(outputs, 1)  
            all_labels.extend(labels.tolist())
            all_preds.extend(preds.tolist())

#********************************METRICS & PERFORMANCE************************************

    val_losses.append(val_loss / len(val_loader))
    val_accuracy = accuracy_score(all_labels, all_preds)
    val_precision = precision_score(all_labels, all_preds, average='macro', zero_division=1)
    val_recall = recall_score(all_labels, all_preds, average='macro', zero_division=1)
    val_f1 = f1_score(all_labels, all_preds, average='macro', zero_division=1)

    print(f"Epoch [{epoch + 1}/{num_epochs}], "
          f"Loss: {train_losses[-1]:.4f}, "
          f"Val Loss: {val_losses[-1]:.4f}, "
          f"Val Acc: {val_accuracy:.2%}, "
          f"Val Precision: {val_precision:.4f}, "
          f"Val Recall: {val_recall:.4f}, "
          f"Val F1 Score: {val_f1:.4f}")

#*******************************SHOW METRICS & PERFORMANCE**********************************
plt.plot(train_losses, label='Train Loss')
plt.plot(val_losses, label='Validation Loss')
plt.legend()
plt.show()

# SAVE THE MODEL FOR THE GH_CV_track_teams CODE
torch.save(model.state_dict(), 'D:/PYTHON/hockey_team_classifier.pth')

如你所见,这是一个三层(conv1,conv2,conv3)的神经网络结构,使用 ReLU 作为激活函数,经过大约 90 张分类为三个类别的图像训练:裁判员、Team_Away(白色球衣的球员)和 Team_Home(黄色球衣的球员),并完成了 10 轮的完整周期。

考虑到这种初步方法,删除图像的请求将涉及从训练集和验证集中删除图像,并重新训练模型。虽然对于像我们这样的小数据集来说这可能很容易,但对于更大的数据集,比如当前大语言模型(LLM)所使用的数据集,这将是一次资源的巨大消耗。此外,反复执行此过程也可能成为一个限制。

现在,让我们假设在构建模型时,我们意识到用户有删除或更正的权利,并考虑应用 SISA 技术。这种方法将为模型做好准备,以应对未来可能需要将图像从训练数据集中永久删除的情况,以及 CNN 在学习过程中可能捕获的任何特征。第一步是将上述初始模型调整为包含 SISA 技术的四个步骤:分片(Sharding)、隔离(Isolating)、切片(Slicing)和聚合(Aggregation)。

步骤 01:分片和切片

在前面代码中指定的转换步骤之后,我们将通过将数据集划分为多个分片来开始应用 SISA。在代码中,您将看到这些分片是多样化的,并且被分割成大小相等的部分,以确保每个分片包含具有代表性的样本数量,并在我们要预测的不同类别之间保持平衡*(在我们的案例中,我们正在预测三类)*。

 #******************************Sharding the dataset**************************

def shard_dataset(dataset, num_shards):
    indices = list(range(len(dataset)))
    np.random.shuffle(indices)
    shards = []
    shard_size = len(dataset) // num_shards
    for i in range(num_shards):
        shard_indices = indices[i * shard_size : (i + 1) * shard_size]
        shards.append(Subset(dataset, shard_indices))
    return shards

#******************************Overlapping Slices***************************
def create_overlapping_slices(shard, slice_size, overlap):
    indices = list(shard.indices)
    slices = []
    step = slice_size - overlap
    for start in range(0, len(indices) - slice_size + 1, step):
        slice_indices = indices[start:start + slice_size]
        slices.append(Subset(shard.dataset, slice_indices))
    return slices

您会注意到,在切片过程中,我没有像 SISA 技术建议的那样为每个分片分配独占的切片。相反,我们使用了重叠的切片。这意味着每个切片不仅仅由来自一个分片的数据点组成;一些数据点也会出现在下一个切片中。

那么,为什么我让切片重叠呢? 正如你可能已经猜到的那样,我们的数据集很小*(大约 90 张图像)*,因此如果每个分片都使用独占的切片,将无法保证每个切片都有足够平衡的数据集来维持模型的预测能力。重叠切片 使得模型能够更好地利用可用数据并提高泛化能力。对于较大的数据集,非重叠切片可能更高效,因为它们需要的计算资源更少。最终,创建分片和切片需要考虑数据集的大小、计算资源以及维持模型预测能力的需求。

最后,在定义了函数之后,我们继续设置分片和切片过程的超参数:

 #**************************Applying Sharding and Slicing*******************

num_shards = 4  
slice_size = len(full_train_dataset) // num_shards // 2
overlap = slice_size // 2
shards = shard_dataset(full_train_dataset, num_shards)

#************************Overlapping slices for each shard*****************
all_slices = []
for shard in shards:
    slices = create_overlapping_slices(shard, slice_size, overlap)
    all_slices.extend(slices)

数据集被分为 4 个碎片,但我应该提到,最初我使用了 10 个碎片。这导致每个碎片只包含少量的样本,这没有正确代表整个数据集的类别分布,导致模型性能指标(准确率、精确度和 F1 分数)显著下降。由于我们处理的是一个小数据集,减少碎片数量到四个是一个明智的决定。最后,切片过程将每个碎片划分为两个具有 50% 重叠的切片,意味着每个切片中的一半图像与下一个切片重叠。

步骤 02:隔离特定数据点

在这一步骤中,我们继续隔离最终用户可能希望修正或从模型学习过程中删除的特定数据点。首先,我们定义一个函数,将指定的数据点从每个切片中移除。接下来,我们根据图像的文件名来确定图像的索引。然后,这些索引用来更新每个切片,移除包含这些数据点的部分。

 #**************************+*Isolate datapoints******************************
def isolate_data_for_unlearning(slice, data_points_to_remove):
    new_indices = [i for i in slice.indices if i not in data_points_to_remove]
    return Subset(slice.dataset, new_indices)

#*****Identify the indices of the images we want to rectify/erasure**********
def get_indices_to_remove(dataset, image_names_to_remove):
    indices_to_remove = [] #list is empty
    image_to_index = {img_path: idx for idx, (img_path, _) in enumerate(dataset.imgs)}
    for image_name in image_names_to_remove:
        if image_name in image_to_index:
            indices_to_remove.append(image_to_index[image_name])
    return indices_to_remove

#*************************Specify and remove images***************************
images_to_remove = []
indices_to_remove = get_indices_to_remove(full_train_dataset, images_to_remove)
updated_slices = [isolate_data_for_unlearning(slice, indices_to_remove) for slice in all_slices]

**目前,列表为空(images_to_remove = []),**因此此阶段没有删除任何图像,但当请求到达时,设置已准备好使用(稍后我们将在本文中看到一个例子)。

实施 SISA 技术的完整模型应该是这样的:

 import os
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torchvision.transforms as transforms
import torchvision.datasets as datasets
from torch.utils.data import DataLoader, Subset
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
import matplotlib.pyplot as plt

#******************************Data transformation********************************************
# Training and Validation Datasets
data_dir = 'D:/PYTHON/teams_sample_dataset'

transform = transforms.Compose([
    transforms.Resize((150, 150)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
])

# Load data
full_train_dataset = datasets.ImageFolder(os.path.join(data_dir, 'train'), transform=transform)
val_dataset = datasets.ImageFolder(os.path.join(data_dir, 'val'), transform=transform)

#******************************Sharding the dataset**************************

def shard_dataset(dataset, num_shards):
    indices = list(range(len(dataset)))
    np.random.shuffle(indices)
    shards = []
    shard_size = len(dataset) // num_shards
    for i in range(num_shards):
        shard_indices = indices[i * shard_size : (i + 1) * shard_size]
        shards.append(Subset(dataset, shard_indices))
    return shards

#******************************Overlapping Slices***************************
def create_overlapping_slices(shard, slice_size, overlap):
    indices = list(shard.indices)
    slices = []
    step = slice_size - overlap
    for start in range(0, len(indices) - slice_size + 1, step):
        slice_indices = indices[start:start + slice_size]
        slices.append(Subset(shard.dataset, slice_indices))
    return slices

#**************************Applying Sharding and Slicing*******************

num_shards = 4  
slice_size = len(full_train_dataset) // num_shards // 2
overlap = slice_size // 2
shards = shard_dataset(full_train_dataset, num_shards)

#************************Overlapping slices for each shard*****************
all_slices = []
for shard in shards:
    slices = create_overlapping_slices(shard, slice_size, overlap)
    all_slices.extend(slices)

#**************************+*Isolate datapoints******************************
def isolate_data_for_unlearning(slice, data_points_to_remove):
    new_indices = [i for i in slice.indices if i not in data_points_to_remove]
    return Subset(slice.dataset, new_indices)

#*****Identify the indices of the images we want to rectify/erasure**********
def get_indices_to_remove(dataset, image_names_to_remove):
    indices_to_remove = []
    image_to_index = {img_path: idx for idx, (img_path, _) in enumerate(dataset.imgs)}
    for image_name in image_names_to_remove:
        if image_name in image_to_index:
            indices_to_remove.append(image_to_index[image_name])
    return indices_to_remove

#*************************Specify and remove images***************************
images_to_remove = []
indices_to_remove = get_indices_to_remove(full_train_dataset, images_to_remove)
updated_slices = [isolate_data_for_unlearning(slice, indices_to_remove) for slice in all_slices]

#********************************CNN Model Architecture**************************************

class CNNModel(nn.Module):
    def __init__(self):
        super(CNNModel, self).__init__()
        self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2, padding=0)
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
        self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
        self.fc1 = nn.Linear(128 * 18 * 18, 512)
        self.dropout = nn.Dropout(0.5)
        self.fc2 = nn.Linear(512, 3)  # Output three classes

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = self.pool(F.relu(self.conv3(x)))
        x = x.view(-1, 128 * 18 * 18)
        x = F.relu(self.fc1(x))
        x = self.dropout(x)
        x = self.fc2(x)
        return x

#********************************CNN TRAINING**********************************************

# Model-loss function-optimizer
model = CNNModel()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

#*********************************Training*************************************************
num_epochs = 10
train_losses, val_losses = [], []

for epoch in range(num_epochs):
    model.train()
    running_loss = 0.0
    for slice in updated_slices:
        train_loader = DataLoader(slice, batch_size=32, shuffle=True)
        for inputs, labels in train_loader:
            optimizer.zero_grad()
            outputs = model(inputs)
            labels = labels.type(torch.LongTensor)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()
            running_loss += loss.item()

    train_losses.append(running_loss / (len(updated_slices)))

    model.eval()
    val_loss = 0.0
    all_labels = []
    all_preds = []
    with torch.no_grad():
        val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)
        for inputs, labels in val_loader:
            outputs = model(inputs)
            labels = labels.type(torch.LongTensor)
            loss = criterion(outputs, labels)
            val_loss += loss.item()
            _, preds = torch.max(outputs, 1)
            all_labels.extend(labels.tolist())
            all_preds.extend(preds.tolist())

#********************************METRICS & PERFORMANCE************************************

    val_losses.append(val_loss / len(val_loader))
    val_accuracy = accuracy_score(all_labels, all_preds)
    val_precision = precision_score(all_labels, all_preds, average='macro', zero_division=1)
    val_recall = recall_score(all_labels, all_preds, average='macro', zero_division=1)
    val_f1 = f1_score(all_labels, all_preds, average='macro', zero_division=1)

    print(f"Epoch [{epoch + 1}/{num_epochs}], "
          f"Loss: {train_losses[-1]:.4f}, "
          f"Val Loss: {val_losses[-1]:.4f}, "
          f"Val Acc: {val_accuracy:.2%}, "
          f"Val Precision: {val_precision:.4f}, "
          f"Val Recall: {val_recall:.4f}, "
          f"Val F1 Score: {val_f1:.4f}")

#*******************************SHOW METRICS & PERFORMANCE**********************************
plt.plot(train_losses, label='Train Loss')
plt.plot(val_losses, label='Validation Loss')
plt.legend()
plt.show()

# SAVE THE MODEL
torch.save(model.state_dict(), 'hockey_team_classifier_SISA.pth') 

现在,让我们进入擦除场景。假设模型已经部署了几个月,一名冰球运动员请求从 CNN 模型的训练数据中删除他们的图像。假设在这个例子中,该运动员出现在训练和验证数据集中三张图像中:Away_image03.JPG, Away_image04.JPG 和 Away_image05.JPG。为了从训练过程中删除这些图像,只需在代码的 “指定并删除图像” 部分指定这些图像(如上所示)。只有包含这些图像的切片需要重新训练。

#*************************Specify and remove images***************************
images_to_remove = ["Away_image03.JPG", "Away_image04.JPG", "Away_image05.JPG"]
indices_to_remove = get_indices_to_remove(full_train_dataset, images_to_remove)
updated_slices = [isolate_data_for_unlearning(slice, indices_to_remove) for slice in all_slices]

最后,我想分享一些将 SISA 框架应用到我的模型中的关键经验:

  • 弱学习者和性能权衡: 由于每个构成模型都在小的子集(碎片和切片)上进行训练,人们可能会认为它们的准确度会低于在整个数据集上训练的单一模型,从而降低模型的泛化能力。令人惊讶的是,在我们的案例中,模型的性能显著提升,这可能是因为在处理一个小的、重叠的数据集时,导致了某种程度的过拟合。在涉及大数据集的使用案例中,需要考虑潜在的性能权衡

  • 适当的分片: 我最初使用了大量的分片,导致每个分片只有很少的样本,这对模型性能产生了负面影响。不要低估分片和切片过程的重要性。适当的分片有助于模型避免过拟合,并在验证集上更好地泛化。

我希望你觉得这个应用 SISA 技术进行机器“忘记”的项目有趣。你可以在这个GitHub 仓库访问完整的代码。

最后的思考

我的姐姐和我有一个固定的习惯,我们会交换社交媒体平台每天提醒我们五年、十年或十五年前发布的内容的图片。我们经常会对当时分享的内容或评论大笑(显然,因为我们大多数人在社交媒体刚出现时并不完全理解它)。随着时间的推移,我学会了更明智地使用我的社交媒体存在,欣赏社交媒体生态系统之外的周围环境,以及我们生活中某些方面应当拥有的隐私。但事实是,我和我的姐姐已经不再是十或十五年前的我们,尽管过去是我们现在身份的重要部分,但它并不定义我们(在数字世界中,并非所有事情都必须“刻在石板上”)。我们每个人都有权选择是否让这些数据停留在数字世界中,并决定是否用于定义我们的选择/偏好或他人的选择。

的确,AI 在使用与将要使用它的用户相似的数据进行训练时表现更好(《先进 AI 助手的伦理问题》,谷歌 DeepMind 2024)。然而,“隐私要求透明”。因此,使用机器学习并且涉及预训练敏感数据的公司,如何以及何时实施“被遗忘权”对于朝着我们都希望拥有的可信 AI 迈进至关重要。

感谢您的阅读! 一如既往,欢迎您的建议,并保持对话的进行。

我的数据分析师第一年学习总结

原文:towardsdatascience.com/learnings-from-my-first-year-of-being-a-data-analyst-f17d4e04a9cb?source=collection_archive---------7-----------------------#2024-10-26

处理统计数据、与人互动和在职场中提高生产力的洞察

https://josephben.medium.com/?source=post_page---byline--f17d4e04a9cb--------------------------------https://towardsdatascience.com/?source=post_page---byline--f17d4e04a9cb-------------------------------- Joseph Ben

·发表于 Towards Data Science ·6 分钟阅读·2024 年 10 月 26 日

如果你不是 Medium 会员,点击👉🏽 这里 👈🏽可以免费阅读这篇文章。

去年八月,我加入了谷歌,成为一名数据分析学徒。这是我职业生涯的起点。一年过去了,我开始回顾这一年在工作和生活各个维度上学到的东西。我认为没有任何一个时期能像这一年一样让我经历如此迅速的蜕变。虽然充满挑战,但也非常有趣!

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/4283a5ac0a646599ca3cba381d3dff0c.png

图片由作者提供

我将我的学习分为三个类别:数据科学、生产力和人际关系。

数据科学

  • 在现实世界的数据科学问题中,高准确度往往是因为数据集极度不平衡,而不是因为算法表现优秀。例如,在垃圾邮件分类中,数据集的负类与正类的比例可能是 1000:1,而这种不平衡会导致我们如果将所有点都分类为负类,准确率超过 99%。因此,选择合适的评估指标至关重要,在这种情况下,召回率是最重要的评估指标。较高的召回率表示正类数据被正确识别的程度…

最小二乘回归解析:带有代码示例的可视化指南(适合初学者)

原文:towardsdatascience.com/least-squares-regression-explained-a-visual-guide-with-code-examples-for-beginners-2e5ad011eae4?source=collection_archive---------1-----------------------#2024-11-05

回归算法

滑动穿越数据点以最小化平方差

https://medium.com/@samybaladram?source=post_page---byline--2e5ad011eae4--------------------------------https://towardsdatascience.com/?source=post_page---byline--2e5ad011eae4-------------------------------- Samy Baladram

·发表于 Towards Data Science ·阅读时间:11 分钟·2024 年 11 月 5 日

当人们开始学习数据分析时,他们通常从线性回归开始。这是有充分理由的——它是理解回归如何工作的最有用且直接的方式之一。线性回归最常见的方法被称为“最小二乘法”——它通过最小化预测值与实际值之间的平方差来寻找数据中的模式。最基本的类型是普通最小二乘法(OLS),它通过数据点找到最佳的直线拟合方式。

然而,有时,OLS 并不够——尤其是当你的数据有许多相关特征,可能导致结果不稳定时。这时,岭回归就派上用场了。岭回归与 OLS 做的工作相同,但它增加了一个特殊的控制项,帮助防止模型对任何单一特征过于敏感。

在这里,我们将快速了解两种最小二乘回归方法,探索这些算法如何平滑地穿过数据点,并在理论上看到它们的差异。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/e13851516ec1dfc98b92fd91fe5661c6.png

所有视觉内容:作者使用 Canva Pro 创建,已针对移动设备优化;在桌面上可能显示过大。

定义

线性回归是一种统计方法,使用线性方程预测数值。它通过拟合一条直线(或在多维情况下为平面)来模拟因变量与一个或多个自变量之间的关系。模型计算每个特征的系数,表示它们对结果的影响。要得到结果,你需要将数据的特征值输入到线性方程中以计算预测值。

📊 使用的数据集

为了说明我们的概念,我们将使用我们的标准数据集,它用于预测某一天来访的高尔夫球手人数。该数据集包括天气预报、温度、湿度和风力等变量。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/8f70777fcdcfbe631c352c17a649d816.png

列:‘Outlook’(一热编码为晴天、多云、雨天)、‘Temperature’(以华氏度为单位)、‘Humidity’(以百分比表示)、‘Wind’(是/否)以及‘Number of Players’(数值型,目标特征)

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split

# Create dataset
dataset_dict = {
    'Outlook': ['sunny', 'sunny', 'overcast', 'rain', 'rain', 'rain', 'overcast', 'sunny', 'sunny', 'rain', 'sunny', 'overcast', 'overcast', 'rain', 'sunny', 'overcast', 'rain', 'sunny', 'sunny', 'rain', 'overcast', 'rain', 'sunny', 'overcast', 'sunny', 'overcast', 'rain', 'overcast'],
    'Temp.': [85.0, 80.0, 83.0, 70.0, 68.0, 65.0, 64.0, 72.0, 69.0, 75.0, 75.0, 72.0, 81.0, 71.0, 81.0, 74.0, 76.0, 78.0, 82.0, 67.0, 85.0, 73.0, 88.0, 77.0, 79.0, 80.0, 66.0, 84.0],
    'Humid.': [85.0, 90.0, 78.0, 96.0, 80.0, 70.0, 65.0, 95.0, 70.0, 80.0, 70.0, 90.0, 75.0, 80.0, 88.0, 92.0, 85.0, 75.0, 92.0, 90.0, 85.0, 88.0, 65.0, 70.0, 60.0, 95.0, 70.0, 78.0],
    'Wind': [False, True, False, False, False, True, True, False, False, False, True, True, False, True, True, False, False, True, False, True, True, False, True, False, False, True, False, False],
    'Num_Players': [52, 39, 43, 37, 28, 19, 43, 47, 56, 33, 49, 23, 42, 13, 33, 29, 25, 51, 41, 14, 34, 29, 49, 36, 57, 21, 23, 41]
}

df = pd.DataFrame(dataset_dict)

# One-hot encode 'Outlook' column
df = pd.get_dummies(df, columns=['Outlook'],prefix='',prefix_sep='')

# Convert 'Wind' column to binary
df['Wind'] = df['Wind'].astype(int)

# Split data into features and target, then into training and test sets
X, y = df.drop(columns='Num_Players'), df['Num_Players']
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.5, shuffle=False)

虽然不是强制性的,但为了有效使用线性回归——包括岭回归——我们可以首先对数值特征进行标准化。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/856bb5024939a0acbe57928f62346faa.png

对“温度”和“湿度”应用标准化处理,而对“天气预报”和“风力”应用一热编码处理。

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.compose import ColumnTransformer

# Create dataset
data = {
    'Outlook': ['sunny', 'sunny', 'overcast', 'rain', 'rain', 'rain', 'overcast', 'sunny', 'sunny', 
                'rain', 'sunny', 'overcast', 'overcast', 'rain', 'sunny', 'overcast', 'rain', 'sunny', 
                'sunny', 'rain', 'overcast', 'rain', 'sunny', 'overcast', 'sunny', 'overcast', 'rain', 'overcast'],
    'Temperature': [85, 80, 83, 70, 68, 65, 64, 72, 69, 75, 75, 72, 81, 71, 81, 74, 76, 78, 82, 
                   67, 85, 73, 88, 77, 79, 80, 66, 84],
    'Humidity': [85, 90, 78, 96, 80, 70, 65, 95, 70, 80, 70, 90, 75, 80, 88, 92, 85, 75, 92, 
                 90, 85, 88, 65, 70, 60, 95, 70, 78],
    'Wind': [False, True, False, False, False, True, True, False, False, False, True, True, False, 
             True, True, False, False, True, False, True, True, False, True, False, False, True, False, False],
    'Num_Players': [52, 39, 43, 37, 28, 19, 43, 47, 56, 33, 49, 23, 42, 13, 33, 29, 25, 51, 41, 
                    14, 34, 29, 49, 36, 57, 21, 23, 41]
}

# Process data
df = pd.get_dummies(pd.DataFrame(data), columns=['Outlook'])
df['Wind'] = df['Wind'].astype(int)

# Split data
X, y = df.drop(columns='Num_Players'), df['Num_Players']
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.5, shuffle=False)

# Scale numerical features
numerical_cols = ['Temperature', 'Humidity']
ct = ColumnTransformer([('scaler', StandardScaler(), numerical_cols)], remainder='passthrough')

# Transform data
X_train_scaled = pd.DataFrame(
    ct.fit_transform(X_train),
    columns=numerical_cols + [col for col in X_train.columns if col not in numerical_cols],
    index=X_train.index
)

X_test_scaled = pd.DataFrame(
    ct.transform(X_test),
    columns=X_train_scaled.columns,
    index=X_test.index
)

主要机制

线性回归通过从数据中绘制一条直线(或超平面)来预测数字:

  1. 该模型通过使真实值与线性预测值之间的误差尽可能小来找到最佳拟合线。这被称为“最小二乘法”。

  2. 每个输入都会得到一个数字(系数/权重),表示它对最终结果的影响程度。还有一个起始数字(截距/偏置),当所有输入为零时使用该数字。

  3. 要预测一个新的答案,模型会将每个输入乘以其对应的数字,然后加总这些值,再加上起始数字。这样就得到了预测结果。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/c72b0e92063ef36ac8fb47688848a377.png

普通最小二乘法(OLS)回归

让我们从普通最小二乘法(OLS)开始——线性回归的基本方法。OLS 的目标是找到一条最佳拟合线,通过数据点。我们通过衡量预测与实际值之间的“误差”来实现这一点,然后找到一条使这些误差尽可能小的线。当我们说“误差”时,我们指的是每个点与线之间的垂直距离——换句话说,就是我们的预测与实际值之间的偏差。首先让我们看看二维情况发生了什么。

二维情况

在二维情况下,我们可以这样想象线性回归算法:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/6c8ce9540dd0d3e926482943741c9ad0.png

这是上述过程的解释:

1.我们从一个训练集开始,每一行包含:

· x :我们的输入特征(数字 1,2,3,1,2)

· y :我们的目标值(0,1,1,2,3)

2. 我们可以将这些点绘制在散点图上,我们希望找到一条最佳拟合这些点的直线 y = β₀ + βx

3. 对于任何给定的直线(任何 β₀ 和 β₁),我们可以通过以下方法来衡量它的好坏:

· 计算每个点到直线的垂直距离 (d₁, d₂, d₃, d₄, d₅)

· 这些距离是每个点的 |y — (β₀ + βx)|

4. 我们的优化目标是找到 β₀ 和 β₁,使得平方距离之和最小化:d₁² + d₂² + d₃² + d₄² + d₅²。在向量表示法中,这可以写作 ||y||²,其中 X = [1 x] 包含我们的输入数据(包含用于截距的 1),β = [ββ₁]ᵀ 包含我们的系数。

5. 最优解具有封闭形式:β = (XX)⁻¹Xᵀy。通过计算,我们得到 β₀ = -0.196(截距),β₁ = 0.761(斜率)。

这个向量表示法使得公式更为简洁,且展示了我们实际上是使用矩阵和向量进行运算,而不是单独的点。我们将在下文的多维情况中看到更多关于计算的细节。

在多维情况下(📊 数据集)

同样,最小二乘法(OLS)的目标是找到系数 (β),使得我们的预测与实际值之间的平方差最小化。数学上,我们将其表示为 最小化 ||y||²,其中 X 是我们的数据矩阵,y 包含我们的目标值。

训练过程遵循以下关键步骤:

训练步骤

1. 准备我们的数据矩阵 X。这涉及到添加一列 1 来考虑偏置/截距项 (β₀)。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/94f0dc6a1292fe7a1cd4d41f648b377b.png

2. 我们可以通过使用正规方程直接计算系数,而不是通过迭代搜索最佳系数:

β = (XX)⁻¹Xy

其中:

· β 是估计系数的向量,

· X 是数据集矩阵(包括一个用于截距的列),

· y 是标签,

· Xᵀ 表示矩阵 X 的转置,

· ⁻¹ 表示矩阵的逆。

让我们分解一下:

a. 我们将 Xᵀ(X 的转置)与 X 相乘,得到一个方阵

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/41c746d7c63229c8463c4b4451d547c4.png

b. 我们计算这个矩阵的逆

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/ec4c7598c2579c5b2aed1e6f0ba14c65.png

c. 我们计算 Xy

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/56478c8b91a65d73a45096748d575778.png

d. 我们将 (XX)⁻¹ 与 Xy 相乘,以得到我们的系数

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/963c4e9a98a88c168f9c90739b1fc1a8.png

测试步骤

一旦我们得到了系数,进行预测就变得非常简单:我们只需将新的数据点与这些系数相乘,就能得到我们的预测。

在矩阵表示法中,对于一个新的数据点 x,预测 y 可以计算为

y = x × β = [1, x₁, x₂, …, xₚ] × [β₀, β₁, β₂, …, βₚ]ᵀ,

其中 β₀ 是截距,β₁ 到 βₚ 是每个特征的系数。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/fc075c4c8801b373f49a757ac742fef5.png

评估步骤

我们可以对所有数据点执行相同的过程。对于我们的数据集,以下是最终结果,并附有 RMSE 值。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/c0f9605d793360d6c403abdb3192e0d5.png

岭回归

现在,让我们考虑岭回归,它在 OLS 的基础上解决了其一些局限性。岭回归的关键思想是,有时最优的 OLS 解涉及非常大的系数,这可能导致过拟合。

岭回归在目标函数中添加了惩罚项 (λ||β||²)。该项通过将系数的平方值添加到最小化目标中,来抑制大系数的出现。目标函数变为:

min ||yXβ||² + λ||β||²

λ(lambda)参数控制我们对大系数的惩罚程度。当 λ = 0 时,我们得到 OLS;随着 λ 增加,系数会向零收缩(但永远不会完全为零)。

训练步骤

  1. 就像 OLS 一样,准备我们的数据矩阵 X。这包括添加一列全为 1 的值来考虑截距项 (β₀)。

  2. 岭回归的训练过程与 OLS 类似,但有所修改。封闭形式的解变为:

    β = (XX+ λI)⁻¹Xy

其中:

· I是单位矩阵(第一个元素对应 β₀,某些实现中有时设置为 0,以排除截距项的正则化),

· λ是正则化值。

· Y是观察到的因变量值的向量。

· 其他符号与 OLS 部分定义相同。

让我们拆解一下:

a. 我们将 λI 加到 XX 上。λ的值可以是任何正数(比如 0.1)。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/1f631555a5c3da55dcc0e5c818fdc266.png

b. 我们计算该矩阵的逆。将 λI 加到 XX 后再进行求逆的好处是:

· 使矩阵可逆,即使 XX 不是(通过 OLS 解决一个关键的数值问题)

· 按 λ 比例缩小系数

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/41521c73d61c93a7d2f3d061ed957b9a.png

c. 我们计算 (XX+ λI)⁻¹ 和 Xy 的乘积来获得系数

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/e4ed2341b5407401f67dd250d1b98f4f.png

测试步骤

预测过程与 OLS 相同——将新的数据点与系数相乘。不同之处在于系数本身,通常它们比 OLS 的系数要小且更稳定。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/918f8768c3d930ee52fc25e57f1c38bb.png

评估步骤

我们可以对所有数据点执行相同的过程。对于我们的数据集,这里是带有 RMSE 的最终结果。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/d89851ba086f08012ca77a947dd7e06f.png

最后备注:选择 OLS 与岭回归

OLS 和岭回归的选择通常取决于你的数据:

  • 当你的数据表现良好,特征之间几乎没有多重共线性并且样本数足够时(相对于特征),使用 OLS

  • 当你有以下情况时,使用岭回归:

    • 特征多(相对于样本)

    • 特征中的多重共线性

    • OLS 中的过拟合迹象

使用岭回归时,你需要选择 λ。从一系列值开始(通常以对数间隔),选择能够提供最佳验证性能的那个值。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/0d0c60a41bac36424c7f233554495189.png

显然,默认值 λ = 1 为我们的数据集提供了最佳的 RMSE。

🌟 OLS 和岭回归代码总结

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import LinearRegression
from sklearn.metrics import root_mean_squared_error
from sklearn.linear_model import Ridge

# Create dataset
data = {
    'Outlook': ['sunny', 'sunny', 'overcast', 'rain', 'rain', 'rain', 'overcast', 'sunny', 'sunny', 
                'rain', 'sunny', 'overcast', 'overcast', 'rain', 'sunny', 'overcast', 'rain', 'sunny', 
                'sunny', 'rain', 'overcast', 'rain', 'sunny', 'overcast', 'sunny', 'overcast', 'rain', 'overcast'],
    'Temperature': [85, 80, 83, 70, 68, 65, 64, 72, 69, 75, 75, 72, 81, 71, 81, 74, 76, 78, 82, 
                   67, 85, 73, 88, 77, 79, 80, 66, 84],
    'Humidity': [85, 90, 78, 96, 80, 70, 65, 95, 70, 80, 70, 90, 75, 80, 88, 92, 85, 75, 92, 
                 90, 85, 88, 65, 70, 60, 95, 70, 78],
    'Wind': [False, True, False, False, False, True, True, False, False, False, True, True, False, 
             True, True, False, False, True, False, True, True, False, True, False, False, True, False, False],
    'Num_Players': [52, 39, 43, 37, 28, 19, 43, 47, 56, 33, 49, 23, 42, 13, 33, 29, 25, 51, 41, 
                    14, 34, 29, 49, 36, 57, 21, 23, 41]
}

# Process data
df = pd.get_dummies(pd.DataFrame(data), columns=['Outlook'], prefix='', prefix_sep='', dtype=int)
df['Wind'] = df['Wind'].astype(int)
df = df[['sunny','overcast','rain','Temperature','Humidity','Wind','Num_Players']]

# Split data
X, y = df.drop(columns='Num_Players'), df['Num_Players']
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.5, shuffle=False)

# Scale numerical features
numerical_cols = ['Temperature', 'Humidity']
ct = ColumnTransformer([('scaler', StandardScaler(), numerical_cols)], remainder='passthrough')

# Transform data
X_train_scaled = pd.DataFrame(
    ct.fit_transform(X_train),
    columns=numerical_cols + [col for col in X_train.columns if col not in numerical_cols],
    index=X_train.index
)

X_test_scaled = pd.DataFrame(
    ct.transform(X_test),
    columns=X_train_scaled.columns,
    index=X_test.index
)

# Initialize and train the model
#model = LinearRegression() # Option 1: OLS Regression
model = Ridge(alpha=0.1)  # Option 2: Ridge Regression (alpha is the regularization strength, equivalent to λ)

# Fit the model
model.fit(X_train_scaled, y_train)

# Make predictions
y_pred = model.predict(X_test_scaled)

# Calculate and print RMSE
rmse = root_mean_squared_error(y_test, y_pred)
print(f"RMSE: {rmse:.4f}")

# Additional information about the model
print("\nModel Coefficients:")
print(f"Intercept    : {model.intercept_:.2f}")
for feature, coef in zip(X_train_scaled.columns, model.coef_):
    print(f"{feature:13}: {coef:.2f}")

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/ff9ff4226a31e1ad21380175a7da9bac.png

进一步阅读

对于 OLS 线性回归岭回归 的详细解释及其在 scikit-learn 中的实现,读者可以参考它们的官方文档。文档提供了关于它们的使用和参数的全面信息。

技术环境

本文使用的是 Python 3.7 和 scikit-learn 1.5。虽然讨论的概念通常适用,但在不同版本中,具体的代码实现可能会有所不同。

关于插图

除非另有说明,所有图片均由作者创建,且融入了 Canva Pro 授权设计元素。

𝙎𝙚𝙚 𝙢𝙤𝙧𝙚 𝙍𝙚𝙜𝙧𝙚𝙨𝙨𝙞𝙤𝙣 𝘼𝙡𝙜𝙤𝙧𝙞𝙩𝙝𝙢𝙨 𝙝𝙚𝙧𝙚:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/835013c69e08fec04ad9ca465c2adf6c.png

Samy Baladram

回归算法

查看列表5 篇故事https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/aa7eeaa18e4bb093f5ce4ab9b93a8a27.pnghttps://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/44e6d84e61c895757ff31e27943ee597.pnghttps://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/7f3e5f3e2aca2feec035ca92e1bc440a.png

𝙔𝙤𝙪 𝙢𝙞𝙜𝙝𝙩 𝙖𝙡𝙨𝙤 𝙡𝙞𝙠𝙚:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/835013c69e08fec04ad9ca465c2adf6c.png

Samy Baladram

分类算法

查看列表8 篇故事https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/f95c1a80b88fe6220b18cd3b2a83a30d.pnghttps://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/6ea70d9d2d9456e0c221388dbb253be8.pnghttps://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/7221f0777228e7bcf08c1adb44a8eb76.png

没有基准?没有标准?没关系!一种敏捷聊天机器人开发的实验性方法

原文:towardsdatascience.com/lessons-from-agile-experimental-chatbot-development-73ea515ba762?source=collection_archive---------3-----------------------#2024-08-26

将基于大语言模型的产品推向生产环境的经验教训

https://katherineamunro.medium.com/?source=post_page---byline--73ea515ba762--------------------------------https://towardsdatascience.com/?source=post_page---byline--73ea515ba762-------------------------------- Katherine Munro

·发表于 Towards Data Science ·阅读时间:12 分钟·2024 年 8 月 26 日

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/1d299d52ea5c591442d1c95eb740087b.png

今天的文章回顾了我最近的演讲,讲述了将基于大语言模型的产品推向生产环境的经验教训。你可以在这里查看该视频。

当你拿到一个已经在四种不同语言中为成千上万的客户提供服务的工作聊天机器人时,试图使用大语言模型来提供更好的体验会发生什么?这是个好问题。

大家都知道,评估和比较大语言模型(LLM)是非常棘手的。基准数据集很难获取,而诸如 BLEU 等度量标准也并不完美。但这些主要是学术上的问题:那么,行业中的数据团队在将大语言模型应用到生产项目时,如何解决这些问题呢?

在我作为对话式人工智能工程师的工作中,我正是在做这件事。这也让我在最近的一场数据科学会议上成为了焦点,进行了题为“No baseline? No benchmarks? No biggie!”的(乐观标题的)演讲。今天的文章是该演讲的总结,内容包括:

  • 评估一个不断发展的、基于大语言模型(LLM)驱动的概念验证(PoC)与一个正在运行的聊天机器人的挑战

  • 我们如何在从概念验证到生产的过程中,在不同阶段使用不同类型的测试

  • 不同测试类型的实际优缺点

从教授 SQL 给非技术团队的经验中得到的教训

原文:towardsdatascience.com/lessons-from-teaching-sql-to-non-technical-teams-7bd8fc9f8289?source=collection_archive---------2-----------------------#2024-03-08

从规模化方法到更量身定制的方法——以及我为什么认为远程辅导是未来趋势

https://medium.com/@jordangom?source=post_page---byline--7bd8fc9f8289--------------------------------https://towardsdatascience.com/?source=post_page---byline--7bd8fc9f8289-------------------------------- Jordan Gomes

·发表于 Towards Data Science ·11 分钟阅读·2024 年 3 月 8 日

在我的职业生涯中,我曾多次参与并举办内部 SQL 培训。虽然这些培训从未是我工作的优先事项之一,但它们却是让我最有满足感的项目之一。当你看到有人开始轻松地运行查询,能够自己找到所需的信息,构建仪表板,甚至更广泛地说,开始对这项新学会的技能感到兴奋时;我不知道——那种感觉真好。

最近,我看到我的一位前“学生”的名字出现在一个共享小组里,提了一个相当复杂的 SQL 问题——我的反应和 Alfred 在《黑暗骑士崛起》里对 Bruce Wayne 点头时的反应一样(如果你不明白这个梗——这里有)。

本文的目标是带你走一遍我举办内部 SQL 培训的历程以及我的收获,培训的对象是整个非技术(或至少不擅长 SQL)的团队,希望你也能在你的组织中分享这些知识,并获得与我相同的喜悦。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/14fc0ff911bd2bf712b16985c327267d.png

图片来源:Campaign Creators via Unsplash

我最初举办这些培训的原因

一般来说,推动我举办这些培训的情况可以分为两个大类:

  1. 技能提升需求: 有时组织中的人因为缺乏 SQL 技能而达到了瓶颈。一个典型的症状是出现复杂的流程,涉及使用多个工具和电子表格来生成一个最终报告。虽然解决方案并不总是在 SQL 方面,但根据经验,如果你有一个耗时的多阶段流程,而你内心深处觉得一定有更好的方法来做你正在做的事情——很可能确实存在。

  2. 资源匮乏: 在资源匮乏的组织中,我发现识别具有“相关技能”的个人(即习惯于使用电子表格和数据的人)并提供技能提升机会,对组织和个人都非常有益。这样一来,不仅可以拓宽个人的视野,还能为企业创造更多的价值。

虽然你可能有许多理由去举办这样的培训(上面列出的清单并不详尽;也可以提出这个目标并不一定是互斥的),但明确你想要实现的目标是很重要的。根据你的目标,你执行培训的方式可能会有很大的不同。

早期的尝试,或者说我如何发现“千人一面”式培训的局限性

在我早期的尝试中(大约是 2015 年… 哎呀,时间过得真快!)我尝试了规模化的方法。通常的格式是典型的课堂形式:一个 X 周的项目,每周有 1 小时的课程(总是在同一时间、同一天),对任何有兴趣学习 SQL 的人开放,完全专注于 SQL:

  • 每周,小组成员都会学到新的内容,从 SQL 的“Hello World”(SELECT * FROM TABLE LIMIT 1)到如何使用多个 CTE 进行窗口函数并优化查询。

  • 每堂课之间,小组成员需要做某种家庭作业(即一些练习,以测试并巩固他们在课堂上学到的知识)

尽管有些人坚持到最后,但成功率(成功定义为有人在培训后仍继续使用新学的 SQL 技能)极低。每一节课,来的人越来越少。只有少数人在课外做提议的练习。从事实来看,这并不算成功。

但我从中得到了很多宝贵的经验:

  • 我喜欢指导别人: 这让我体验到辅导和教授新技能给他人带来的乐趣,最终也为我创办这个博客和参与其他让我觉得有价值的活动铺平了道路。

  • SQL 被认为“过于技术化”:许多人没有参加那些免费的培训,或者在遇到第一个障碍时就放弃了,仅仅因为他们认为 SQL 是为技术人员准备的,而他们自己并不认为自己是技术型人才。

  • 没有“保持”机制的培训注定会失败:这让我意识到建立一种保持学员的系统有多么重要。依赖学员的自律来完成这种培训是空想——在任何一个组织中,都有太多互相竞争的优先事项和原因,导致无法完成学习。因此,你要么需要找到那些对培训有强烈内在动机的学生(例如,你有一个明确的 SQL 学习目标),要么需要提供强大的外在动机(例如,他们的经理要求他们学习 SQL,以承担一些更具技术性的项目)。

  • 教授 SQL 只是其中的一部分:最后,也是更重要的一点——这让我意识到,不能只教 SQL。没有人是在真空中使用 SQL 的。SQL 的现实情况是:

  1. 在编写 SQL 代码之前,你需要在组织中找到正确的数据集(这在成熟的组织中可能很容易,但在不太成熟的组织中可能很复杂,甚至根本不存在)。

  2. 一旦你找到了数据集,你需要找到正确的字段进行查询,并确保这些字段包含你所需要的信息(这本身就是一门艺术)

  3. 从那里,你需要请求访问数据集,一旦获得访问权限,你需要在一个特定工具中编写 SQL 代码,该工具有特定的指南和功能。

  4. 在编写查询时,你需要关注计算成本,可能还需要在运行查询之前重新组织内容。

  5. 依此类推。如果你不教授这些元素,学生将很难使用 SQL。

所有这些学习为我程序的改进版铺平了道路——一种更具个性化的方式。

最近——向更个性化的方法转变

在经过几次改进后的迭代后,我反思了自己一路上的所有收获,并尝试了一种新方法:我放弃了规模,完全转向了相反的方向。与其每周进行 1 小时的全班授课,我开始每周与几位选定的个体进行简短的 1 对 1 会谈。

尽管该项目仍然向所有人开放,但现在有了一个选择过程。想要加入的人必须展示以下内容:

  • 清晰的学习 SQL 需求:未来的学生需要填写一份表格,解释他们为何想学习 SQL,并描述一个需要 SQL 的项目(例如:“我想自动化 X 报告,我想在 Y 上构建一个仪表板”)。如果他们被选中,这个项目将是他们在整个项目期间所做的工作。

  • 预先存在的相关技能:未来的学生需要展示我所称之为“相关技能”,即与 SQL 或数据分析所需技能相似的技能。

  • 能够(并愿意)在他们的日程安排中挤出足够的时间:作为他们申请该项目的一部分——学生必须与他们的经理验证他们的“学习”项目,并愿意在接下来的 X 周内将至少 X0%的时间投入到学习中。X0%可能看起来很多——但实际上它并不完全关于 X0%,而是传达了一个信息(插入小丑表情包)。这个项目将非常耗时,潜在的学生需要确保他们能够挤出成功所需的时间。

在培训本身——重点从 SQL 转移到做项目。培训的第一节课花费在将他们的项目拆分为里程碑上。第一个里程碑对每个人来说都差不多:找到一个资源(免费的或付费的,在线的或离线的——他们喜欢哪种都可以)来学习 SQL 的基础知识——并完成它。

我想承认这可能有些令人失望——你可能会期望一篇关于“教 SQL”的文章不会在“学习 SQL”部分这么干巴巴。我的普遍看法是,你可以在很短的时间内掌握 SQL 的关键概念,但要真正精通它需要几个月甚至几年,而你越早将它应用到实际问题中,就能越快达到扎实的水平。因此,程序的大部分时间花费在将其应用到现实问题上,而不是在获取 SQL 的基础知识(这些知识你可以通过互联网这一神奇的工具轻松获得)。

一旦上述第一步完成,我们就开始朝下一个里程碑努力。例如,对于那些想要构建仪表板的人——我们会将项目拆分为:

  1. 学习 SQL 的基础知识

  2. 寻找合适的数据集和查询逻辑(学习如何获取这些信息)

  3. 如有必要,构建一个数据库(与构建数据库相关的角色和责任)

  4. 将这个数据库连接到仪表板工具

  5. 设计仪表板

  6. 构建仪表板

从那时起,每个学生每周应该达到一个不同的里程碑。如果他们卡在某个地方,他们可以随时联系我,发送电子邮件或参加每周的办公时间,但一般来说,他们必须独立完成这些里程碑。

通过这个系统,我看到了相当高的成功率(成功的定义是某人在培训后继续使用他们新获得的 SQL 技能)。回顾这一点——我认为有几个原因:

  • 选择过程增加了摩擦:增加的选择过程确保只有最有动力的、拥有实际项目的人才能参与培训。

  • 里程碑系统是一个很好的强制性因素: 拥有目标是一个很好的开始,但如果没有考虑达成目标所需的步骤,更广泛地说,也没有考虑实现目标所需的工作,那么几乎不可能实现目标。拥有一个里程碑系统,设定明确的交付物和明确的截止日期,创造了一个责任感和反馈循环,这大大帮助学生成长。

  • 从一开始就设定正确的期望使一切变得更简单: 我相信,任何事情的成功很大一部分与心态和我们对工作的看法有关。从这个项目一开始,我就尽力设定正确的期望:(1) 这将是一个时间密集的过程 (2) 这将是一个充满挑战的过程 (3) 这将是一个漫长的过程

  • (4) 但我们会花时间,我们会一个一个地克服挑战,最终我们将会取得胜利

  • 教人们如何自学 SQL 与实际教授 SQL: 最后但同样重要的是——这一关键变化对项目产生了巨大影响。它让学员们熟悉了如何在网上寻找所需的关键信息,进行实验,并在过程中学习。这使他们变得更加独立,即使在项目结束后,也能继续成长。

到目前为止,上述方法是我实施过的最成功的之一。但它非常耗时,我仍然看到很多可以改进的地方。

朝着更加混合的方式前进

现在,主要的问题是:我们如何扩大上述项目的规模?如果我回顾我在这次培训中所扮演的角色,那主要是给出方向并让人们保持诚实:

  • 一开始: 我帮助学生们构建他们的项目并将其分解成多个里程碑

  • 在每个里程碑之前: 我会给他们一些关于如何应对每个障碍的最佳建议。如果他们卡住了,我会给他们提供解决问题的方向。

  • 在整个项目过程中: 我庆祝他们的成功,挑战他们,试图在他们遇到困难时保持他们的动力,但也让他们对按设定的时间表交付预定成果保持责任感。

我认为上述内容不能自动化——或者现在也许可以借助大型语言模型(LLMs),谁知道呢——但无论如何,你绝对可以标准化并优化它,也许可以以异步方式做很多工作,而不需要每周开会。这也是我希望在下一个迭代中尝试的——减少我在每个学生身上花费的时间,以便我能培养更多的学员。

作者注:我看到越来越多的健身影响者提供“远程辅导”,你可以通过电子邮件与教练沟通,发送你的训练视频,获得个性化的计划。也许可以做类似的数据分析辅导?

关于这个项目本身,我很希望能融入一些“社区”元素。特别是,我坚信费曼技巧——它是关于(以一种非常简化的方式)教授你所学到的东西。具体来说,我希望学生们开始记录他们的学习内容,并开始将其分享给新学生(有点像电影《善意的谎言》中的情节)。我在这里看到几个好处:

  • 它可以帮助扩展这个项目(可以把它看作一种“培训培训师”的方法),并让更多的人从知识中受益。

  • 这将帮助现在是教师的“学生”内化他们学到的关键概念,并识别他们理解中的空白。

  • 它将启动一个庞大的知识库,之后可以用于为那些非常有动力、但可能无法参与项目的个人提供更多自助式的学习方式。

一如既往,想法很便宜——执行才是你了解什么有效、什么无效的地方——我将很快尝试进行实验,并可能在未来的文章中报告结果。

总结

在过去的 8 年里,我尝试了不同的项目,目的是把同事和下属培养成 SQL 专家。我并不总是成功,但几年前我从一个广泛的项目转向更加量身定制的指导方式,取得了很大的成功,并学到了很多宝贵的经验。

我现在面临的真正挑战是方法的放大效应。我们如何简化并去除所有的废话,专注于为选定的个体创造尽可能多的价值,让他们有能力将自己在组织中产生的影响力扩大十倍?也许健身影响者已经有所发现……

希望你喜欢阅读这篇文章!你有什么想分享的建议吗?在评论区让大家知道吧!

如果你想阅读更多我的文章,这里有几篇你可能喜欢的其他文章

## 如何构建成功的仪表盘

来自一个曾经构建过一些不成功仪表盘的人的清单

towardsdatascience.com ## 建设分析成熟的组织(AMO)

一些简单的框架,用来确定你所在组织的分析需求,以及如何让它变得更加成熟……

towardsdatascience.com ## 区分优秀数据分析师的因素

还在寻找新一年的决心吗?这里有 6 个技能可以帮助你和你的团队变得极其高效。

towardsdatascience.com

PS: 这篇文章也发布在 Analytics Explained,这是一个我总结在不同分析角色中学到的知识(从新加坡初创企业到旧金山的大型科技公司),并回答读者关于分析、增长和职业的问题的通讯。

🚪🚪🐐 从蒙提霍尔问题中学习决策技巧

原文:towardsdatascience.com/lessons-in-decision-making-from-the-monty-hall-problem-a6032f4b1032?source=collection_archive---------1-----------------------#2024-10-24

三种直觉的探索:常识、贝叶斯和因果

https://eyal-kazin.medium.com/?source=post_page---byline--a6032f4b1032--------------------------------https://towardsdatascience.com/?source=post_page---byline--a6032f4b1032-------------------------------- Eyal Kazin

·发布于 Towards Data Science ·25 分钟阅读·2024 年 10 月 24 日

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/a3d45650ff7d47314680564588529126.png

通过 Gemini Imagen 3 生成

蒙提霍尔问题是一个著名的脑筋急转弯,我们可以从中学习到重要的决策技巧,这些技巧对于数据科学家尤其有用。

如果你不熟悉这个问题,准备好感到困惑吧🤯。如果你已经知道这个问题,希望我能为你照亮一些你可能未曾考虑到的方面💡。

我介绍了这个问题,并通过三种类型的直觉来解决它:

  • 常识 — 本文的核心关注的是如何利用我们的常识来解决这个问题。我们将探索它为何失败 😕 ,以及我们如何直观地克服这个问题,让解决方案变得清晰明了 🤓。我们将通过使用视觉图示 🎨、定性论证和一些基本概率(保证不太深奥)来实现这一点。

  • 贝叶斯 — 我们将简要讨论信念传播的重要性。

  • 因果 — 我们将使用图模型来可视化在实际场景中使用蒙提霍尔问题所需的条件。

    🚨剧透警告 🚨 我还没有被说服认为有什么重要的结论,但这种思维过程非常有用。

我通过讨论更好的数据决策技巧总结了所学的经验。

通过实践领导:作为数据科学经理的经验教训,以及为何我决定回归个人贡献者角色

原文:towardsdatascience.com/lessons-learned-as-a-data-science-manager-and-why-im-moving-back-to-an-individual-contributor-role-65585b2e8dde?source=collection_archive---------0-----------------------#2024-07-13

我问自己三个问题,帮助我选择了职业路径

https://robodasha.medium.com/?source=post_page---byline--65585b2e8dde--------------------------------https://towardsdatascience.com/?source=post_page---byline--65585b2e8dde-------------------------------- Dasha Herrmannova 博士

·发布于Towards Data Science ·阅读时间 9 分钟·2024 年 7 月 13 日

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/11e8ff181b36cf9c7e15ecbc8c9821f1.png

图片由罗伯特·鲁杰罗提供,来自Unsplash

随着你在数据科学领域经验的积累和职业的进展,某个时刻你可能会开始听到或者问自己这样一个问题:

你想转向管理岗位吗?

两年前,我的老板问了我这个问题。当时我是一名资深数据科学家。我成功地领导了多个项目,并在其他职责之外,还辅导了初级团队成员。由于我已经做了许多管理者的工作(或者我以为是这样),我认为转到管理岗位不会是一个大的变化。此外,我认为获得一个更“高级”的职称和薪资增长(大多是做相同的工作)是一个不错的交易,所以,尽管我从未计划过尝试成为一名管理者,但最终我同意从个人贡献者(资深数据科学家)转到领导岗位(数据科学经理)。

现在,在担任管理职务近两年后,我决定重新回到个人贡献者角色。稍后我会在本文中分享促使我做出这个决定的原因,但简而言之,我发现个人贡献者的角色更符合我的兴趣……

从开发开源软件中学到的经验

原文:towardsdatascience.com/lessons-learned-from-developing-an-open-source-software-9760eb18d1b2?source=collection_archive---------1-----------------------#2024-09-13

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/922f019d5ed5ff7b52cf80ec194e6dcb.png

图片来自 Ideogram,由作者修改

以及它们给我作为数据科学家和机器学习工程师带来的意外优势

https://medium.com/@siavashyasini?source=post_page---byline--9760eb18d1b2--------------------------------https://towardsdatascience.com/?source=post_page---byline--9760eb18d1b2-------------------------------- Siavash Yasini

·发布于Towards Data Science ·17 分钟阅读·2024 年 9 月 13 日

介绍

开源在过去几十年中从根本上改变了软件开发的格局,尤其是在近年来,Python 无疑在所有编程语言中占据了主导地位(我相信某篇论文中有科学数学证明这一点)。作为一门极其易学的语言,Python 以其“开箱即用”的理念,带来了大量在数据领域中具有里程碑意义的开源软件包,涵盖了从科学计算和模拟,到数值分析和机器学习,再到如今的人工智能和聊天机器人开发等众多领域。

在我早期做博士研究生时,Python 并不像今天这样流行。我所在领域的大部分软件包和科研代码是用一种名为互动数据语言(IDL)的语言编写的。你可能会惊讶地发现,这门语言并不是免费的 —— 我们必须为此支付许可证费用。是的,你没看错,我们必须为编程付费

这段经历让我真正意识到 Python 不仅仅是一门编程语言,它还是一个平台,任何人都可以在这里贡献并创造出惊人的成果……

让光明降临!扩散模型与重光照的未来

原文:towardsdatascience.com/let-there-be-light-diffusion-models-and-the-future-of-relighting-03af12b8e86c?source=collection_archive---------3-----------------------#2024-11-04

了解最前沿的扩散模型如何在这篇关于场景编辑的深度博客中处理重光照、协调和阴影去除

https://medium.com/@darth_gera?source=post_page---byline--03af12b8e86c--------------------------------https://towardsdatascience.com/?source=post_page---byline--03af12b8e86c-------------------------------- Pulkit Gera

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

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/7a8e3f4981f22fe3485a083e12fbd430.png

Brian Aitkenhead 拍摄,图片来源于 Unsplash

重光照是指在给定输入场景的情况下,在指定的目标光照条件下渲染场景的任务。这是计算机视觉和图形学中的一项重要任务。然而,它是一个病态问题,因为场景中物体的外观是由光源、几何形状和表面材质属性等因素之间复杂的相互作用所决定的。这些相互作用会产生歧义。例如,给定一张场景的照片,物体上的暗斑是由光照投射的阴影造成的,还是材质本身就是暗色的?区分这些因素是有效重光照的关键。

在这篇博客文章中,我们讨论了不同的论文如何通过扩散模型解决重光照问题。重光照涉及多个子问题,包括简单的光照调整、图像协调、阴影去除和内在分解。这些领域对于优化场景编辑至关重要,例如平衡合成图像中的颜色和阴影,或解耦材质与光照属性。我们将首先介绍重光照问题,并简要讨论扩散模型和控制网(ControlNets)。然后,我们将讨论在不同类型的场景中解决重光照问题的不同方法,从单一物体到肖像再到大规模场景。

解决重光照问题

目标是将场景分解为其基本组成部分,如几何形状、材质和光照交互,并对它们进行参数化建模。求解完成后,我们就可以根据自己的偏好进行修改。场景中一个点的外观可以通过渲染方程来描述,如下所示:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/eebcbafb661916da1ec0d2ffb59eb303.png

渲染方程来自source

大多数方法旨在求解渲染方程的每个单独组成部分。一旦求解完成,我们就可以进行重新照明和材质编辑。由于照明项 L 出现在方程的两边,这个方程无法通过解析方法求解,只能通过蒙特卡罗方法或基于近似的方法来求解。

另一种方法是数据驱动学习,在这种方法中,模型不显式地建模场景的属性,而是直接从数据中学习。例如,网络可以直接从数据中学习表面材质属性,而不是拟合一个参数化函数。数据驱动的方法已被证明比参数化方法更强大。然而,这些方法需要大量高质量的数据,特别是在照明和材质估计任务中,收集这些数据是非常困难的。

用于照明和材质估计的数据集非常稀缺,因为这些数据集需要昂贵且复杂的设备设置,如光照舞台,用以捕捉详细的光照交互。这些设置仅对少数组织可用,从而限制了用于训练和评估的数据的获取。目前没有公开的全身光照舞台真实数据集,这进一步凸显了这一挑战。

扩散模型

随着大量图像和视频数据在线可用,计算机视觉经历了重大变革。这推动了基础模型的发展,这些模型作为强大的通用模型,可以针对广泛的具体任务进行微调。扩散模型通过从独立样本中学习建模潜在的数据分布,逐渐逆转添加噪声的过程,从而生成真实的数据。通过利用其从学习到的分布中生成高质量样本的能力,扩散模型已成为解决各种生成任务的关键工具。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/285e147fe9962066256db92eaaf48792.png

潜在扩散模型来自source

其中最突出的例子之一是Stable Diffusion(SD),它在大规模的 LAION-5B 数据集上进行了训练,该数据集包含 50 亿对图像和文本。它已经编码了大量关于视觉概念的通用知识,使其适合于针对特定任务的微调。它在训练过程中学习了诸如椅子有四条腿或识别汽车结构等基本关系和联想。这种内在的理解使 Stable Diffusion 能够生成高度连贯且逼真的图像,并可用于微调以预测其他模态。基于这一思想,问题出现了:我们能否利用预训练的 SD 来解决场景重光的问题?

那么我们如何微调 LDMs?一种简单的方法是对 LDMs 进行迁移学习。这种方法是冻结早期层(捕捉一般特征),并在特定任务上微调模型。虽然一些论文如Alchemist(用于材质迁移)已使用过这种方法,但它需要大量配对数据才能使模型很好地泛化。这个方法的另一个缺点是灾难性遗忘的风险,即模型会丧失在预训练过程中获得的知识。这将限制其在不同条件下的泛化能力。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/6faeb78a6bd8b3bd8fda521767bfddbf.png

来自来源的 ControlNet 图示

微调这些大模型的另一种方法是引入 ControlNet。在这种方法中,首先创建一个网络副本,并冻结原始网络的权重。在训练过程中,仅更新副本网络的权重,并将条件信号作为输入传递给副本网络。原始网络继续利用其预训练的知识。

尽管这增加了内存占用,但优势在于我们不会失去从大规模数据集训练中获得的泛化能力。它确保模型在学习当前任务所需的任务特定关系的同时,仍保持生成高质量输出的能力,适用于各种提示。

此外,它有助于模型学习控制输入与期望输出之间的稳健且有意义的关联。通过将控制网络与核心模型解耦,它避免了过拟合或灾难性遗忘的风险。同时,它所需的配对数据量显著减少。

虽然也有其他用于微调基础模型的技术——例如 LoRA(低秩适配)等——但我们将重点讨论两种方法:传统的迁移学习和 ControlNet。这些方法对于理解各种论文如何使用扩散模型处理基于图像的重光问题尤其重要。

DiLightNet

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/e7cc7d02681474f33ee17d588721a584.png

DiLightNet:基于扩散的图像生成的细粒度光照控制

介绍

本文提出了对输入图像重光照的细粒度控制。输入图像可以是生成的图像,也可以是作为输入给定的图像。进一步地,它还可以根据文本提示改变物体的材质。目标是对光照效果进行细粒度的控制。

方法

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/d543f869aa1291a2ee5fba0a36d7d8d3.png

方法图来自于source

给定一个输入图像,应用以下预处理步骤:

  1. 使用现成的 SOTA 模型估计背景和深度图。

  2. 通过三角剖分深度图提取网格

  3. 生成 4 种不同的辐射线索图像。辐射线索图像是通过为提取的网格分配不同的材质,并在目标光照下渲染它们来创建的。这些辐射线索图像作为编码光照效果(如高光、阴影和全局光照)的基础。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/0f10a43072a7b2914b326255e305f274.png

ControlNet 的输入来自于source

一旦这些图像生成,它们将训练一个 ControlNet 模块。输入图像和掩膜会通过一个编码解码网络,该网络输出一个 12 通道的特征图。然后将该特征图与辐射线索图像按通道进行拼接。因此,在训练过程中,带噪的目标图像会以这个自定义的 12 通道图像作为条件信号进行去噪。

此外,提供了一个外观种子,以确保在不同的光照条件下保持一致的外观。如果没有它,网络会呈现出不同的光物质相互作用的解读。此外,可以通过文本提供更多线索来改变外观,例如添加“塑料/光亮金属”来改变生成图像的材质。

实现

数据集使用来自 Objaverse 的 25K 个合成物体进行策划。每个物体从 4 个独特视角进行渲染,并在 12 种不同的光照条件下进行渲染,这些条件包括点光源、多个点光源、环境光图和区域光。为了训练,辐射线索图像是在 Blender 中渲染的。

ControlNet 模块使用稳定扩散 v2.1 作为基础预训练模型进行微调。训练大约用了 30 小时,使用了 8 块 NVIDIA V100 GPU。训练数据是在 Blender 中以 512x512 分辨率渲染的。

结果

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/b1acd57738d769c1ed1202bac18da10b.png

DiLightNet 的结果来自于source

该图展示了作为参考的临时图像和物体在重新光照下的目标光照。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/256c63b5832c8e8f8e26644606908b40.png

DiLightNet 的结果来自于source

该图展示了如何使用文本提示来改变物体的材质。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/6979caf7f09e042cc803b9e03051a7ff.png

DiLightNet 的结果来自于source

该图展示了 AI 生成的临时图像的更多结果,这些图像随后在不同的输入环境光照条件下进行了渲染。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/35a2d76fa45cbe4185b49d99c505f004.png

DiLightNet 结果来自于source

该图展示了当外观种子未固定时,网络为解决光照交互问题所提出的不同解决方案。

局限性

由于在合成物体上进行训练,该方法在处理真实图像时效果不佳,而在 AI 生成的临时图像上表现更好。此外,材料的光照交互可能无法完全按照提示的意图进行。由于它依赖于深度图生成辐射线索,因此可能无法获得令人满意的结果。最后,生成旋转光源的视频可能无法产生一致的结果。

神经光照

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/72883b2854c2037fa5466a9ccac7d95b.png

神经光照:通过扩散重新照明任何物体

介绍

本文提出了一种端到端的 2D 光照扩散模型。该模型从具有物理基础材料和 HDR 环境图的合成数据集中学习物理先验。它还可以用于重新照亮多个视角,并用于创建场景的 3D 表示。

方法

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/f7f7dd9fd071a66d99d238ea8a5efe35.png

神经光照方法图来自于source

给定一张图像和一个目标 HDR 环境图,目标是学习一个能够合成重新照明版本的图像的模型,这里是一个单一的物体。这是通过采用预训练的Zero-1-to-3模型实现的。Zero-1-to-3 是一个扩散模型,它以视角方向为条件来渲染输入图像的新视图。他们丢弃了新视图合成的部分。为了结合光照条件,他们将输入图像和环境图的编码与去噪潜在变量拼接在一起。

输入的 HDR 环境图 E 被拆分为两个组件:E_l,一个色调映射的 LDR 表示,用于捕捉低强度区域的光照细节,以及 E_h,一个对数归一化的图,用于保留跨整个光谱的信息。这两者共同为网络提供了一个平衡的能量谱表示,确保在重新照明时不会因过高的亮度而导致生成的输出看起来被冲淡。

此外,输入图像的 CLIP 嵌入也作为输入传递。因此,模型的输入包括输入图像、LDR 图像、标准化的 HDR 图像和图像的 CLIP 嵌入,这些都为去噪网络提供条件。该网络随后作为先验用于进一步的 3D 物体重新照明。

实现

该模型在一个自定义的 Relit Objaverse 数据集上进行训练,该数据集包含 90K 个物体。对于每个物体,提供了 204 张在不同光照条件和视角下渲染的图像。总的来说,数据集包含了 1840 万张分辨率为 512x512 的图像。

该模型是从 Zero-1-to-3 的检查点进行微调的,且仅微调了去噪网络。输入环境图被下采样至 256x256 分辨率。该模型在 8 个 A6000 GPU 上训练了 5 天。进一步的下游任务,如基于文本的重光照和物体插入也可以实现。

结果

他们展示了与不同背景的对比,以及与其他工作如 DilightNet 和IC-Light的对比。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/6babf1b85e8818a8e3044f9702c99353.png

Neural Gaffer 的结果来源于source

该图展示了他们的方法与另一种基于 ControlNet 的方法 IC-Light 的重光照结果对比。他们的方法能够与旋转的环境图一致地产生光照和颜色。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/64961cf772f654a103aa0aae7fc76b72.png

Neural Gaffer 的结果来源于source

该图展示了他们的方法与另一种基于 ControlNet 的方法 DiLightnet 的重光照结果对比。他们的方法能够产生镜面高光和准确的颜色。

局限性

一个主要的局限性是它只生成低分辨率的图像(256x256)。此外,它仅适用于物体,并且在人物重光照方面表现较差。

重光照协调

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/9c8aa09f65174b1a0080250bfb7ec656.png

Relightful Harmonization: Lighting-aware Portrait Background Replacement

简介

图像协调是将前景主体的颜色和光照特征与背景对齐的过程,使其成为一个合理的合成。该研究提出了一种基于扩散的方法来解决这一任务。

方法

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/41e5d56268af57453ecb1588651d890b.png

方法图来自source

给定一个输入合成图像、alpha 掩码和目标背景,目标是预测一张重光照的人物图像。通过训练一个 ControlNet 来预测协调后的图像输出,从而实现这一目标。

在第一阶段,我们训练了一个背景控制网络模型,该模型以合成图像和目标背景为输入,输出重光照的人物图像。在训练过程中,去噪网络将嘈杂的目标图像与合成图像拼接在一起,并预测噪声。背景作为条件通过控制网络提供。由于背景图像本身是 LDR,它们没有提供足够的信号用于重光照。

在第二阶段,训练了一个环境映射控制网络模型。HDR 环境映射提供了更多的信号用于重光照,这带来了更好的效果。然而在测试时,用户仅提供 LDR 背景。因此,为了弥合这一差距,两个控制网络模型彼此对齐。

最后,使用环境图控制网络模型生成更多数据,然后微调背景控制网络模型,以生成更具照片真实感的结果。

实现

用于训练的数据集包含 40 万对图像样本,这些样本是通过 100 个光阶段(lightstage)精心策划的。在第三阶段,生成了额外的 20 万个合成样本,用于光线真实感的微调。

该模型是从 InstructPix2Pix 检查点进行微调的,模型在 8 个 A100 GPU 上进行训练,分辨率为 512x512。

结果

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/006be013588b7d6c75686bc67f409787.png

Relightful Harmonization 的结果来自于 source

该图展示了该方法如何中和输入图像中显著的阴影,这些阴影通常很难去除。左侧为输入图像,右侧为重新光照后的图像。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/da10eb947f17e6da62edf5df1e3deb56.png

Relightful Harmonization 的结果来自于 source

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/a9af9f87c920b8c7b77cc5b1662515a9.png

Relightful Harmonization 的结果来自于 source

图示展示了在真实世界测试对象上的结果。与其他方法相比,他们的方法能够去除阴影,并使得合成更加可信。

局限性

尽管该方法能够可信地重光照目标,但在保持身份一致性方面表现不佳,尤其在保持衣物或头发的颜色方面有困难。此外,它可能在去除阴影方面存在问题。同时,它没有估计反射率(albedo),而反射率对于复杂的光照交互至关重要。

多光照合成

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/44a7b9666c182fb5958fd12b808554b9.png

基于扩散方法的辐射场重光照与多光照合成

介绍

本研究提出了一种 2D 重光照扩散模型,该模型进一步用于重光照场景的辐射场。首先训练一个 ControlNet 模型,预测场景在新光照方向下的表现。然后,使用该模型生成更多数据,最终用于拟合一个可重光照的辐射场。在本节中,我们讨论了 2D 重光照模型。

方法

给定一组图像 X_i 和对应的深度图 D(通过现成方法计算得到),以及光照方向 l_i,目标是预测场景在光照方向 l_j 下的表现。在训练过程中,去噪网络的输入是随机光照下的 X_i 图像,深度图 D 与含噪的目标图像 X_j 拼接在一起。光照方向通过 4 阶 SH 编码,并通过 ControlNet 模型进行条件化。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/26f19d3cf8ca01a8e59224fade3490e0.png

方法图来自于 source

尽管这产生了相当不错的结果,但也存在一些显著的问题。它无法保持颜色,导致对比度丧失。此外,它还会产生扭曲的边缘。为了解决这个问题,他们通过将预测图像与输入图像进行颜色匹配来弥补颜色差异。这是通过将图像转换为 LAB 空间并进行通道归一化来完成的。然后计算真实值和去噪输出之间的损失。为了保持边缘,解码器在图像修复任务上进行了预训练,这有助于保持边缘。然后,使用这个网络来创建在新光照方向下的相应场景,进一步用来创建可重光照的辐射场表示。

实现

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/0c11081146d4da1f30d2b2a03b504318.png

来自source的 2D 重光照模块推理图

该方法是基于多光照数据集开发的。该数据集包含了 1000 个真实的室内场景,捕捉了 25 个光照方向下的图像。图像还包括一个漫反射球和一个金属球,这对于获取世界坐标系中的光照方向非常有用。此外,还使用 Blender 渲染了更多的场景。网络训练使用了分辨率为 1536x1024 的图像,训练包括了 1015 个室内场景中的 18 个非正面朝向的光照方向。

ControlNet 模块使用 Stable Diffusion v2.1 模型作为基础进行训练。它在多个 A6000 GPU 上训练了 150K 次迭代。

结果

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/867838e946028a45ca45db6b61e66920.png

来自source的结果图

这里的漫反射球展示了测试时的光照方向。可以看到,该方法可以渲染出合理的重光照结果。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/80b2fbbb5c5b9468ed12257070d043dc.png

来自source的结果图

这张图展示了随着光照方向的变化,镜面高光和阴影的移动,正如水壶上闪亮的高光所示。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/039969c08ca765eef247b61ee7b07b17.png

来自source的结果图

这张图将结果与其他可重光照的辐射场方法进行了比较。与其他方法相比,他们的方法显著地更好地保留了颜色和对比度。

局限性

该方法没有强制物理准确性,可能会产生不正确的阴影。此外,它也难以完全去除阴影,且无法完全准确地去除阴影。此外,对于光照变化不大的分布外场景,它的表现还算合理。

LightIt

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/81d3e65b89db90f7fabcde14396167cb.png

LightIt: 照明建模与扩散模型的控制

简介

本文提出了一种单视角阴影估计方法,用于生成配对图像及其对应的直射光阴影。然后可以利用这个阴影来引导场景生成并对场景进行重新照明。他们将该问题作为内在分解问题处理,其中场景可以分解为反射率和阴影。我们将在此讨论重新照明的组件。

方法

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/36add64e8ec06b00ddd78eb38969ff54.png

方法图来自于 source

给定一张输入图像、对应的表面法线、文本条件和目标直射光阴影图像,他们生成了一张重新照明的风格化图像。这是通过训练一个 ControlNet 模块实现的。

在训练过程中,带噪声的目标图像与文本条件一起传递到去噪网络。法线图和目标直射光阴影图像被串联后传入残差控制编码器。特征图随后被用来作为网络的条件输入。此外,它还通过残差控制解码器进行重构,以规范化训练过程。

实现

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/695a3799c65c3f1855b0ba28d22aa248.png

数据集图来自于 source

数据集包含了户外 Laval 数据集,其中包含了真实世界的户外 HDR 全景图像。通过这些图像,裁剪出了 250 张 512x512 的图像,并应用了各种相机效果。数据集包含了 51250 个 LDR 图像样本和文本提示,以及估算的法线图和阴影图。法线图是通过使用现成的估算器从深度图中估算得出的。

ControlNet 模块是基于稳定扩散 v1.5 进行微调的。该网络训练了两个周期。其他训练细节未公开。

结果

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/314ec873f97a6984c1734c417181c8fd.png

结果图来自于 source

该图展示了生成的图像在自定义风格化文本提示下,光照与目标阴影保持一致。这与其他仅关注照片级真实感的论文有所不同。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/b7603273635fd4716981670c651f25b9.png

结果图来自于 source

该图展示了在不同光照条件下的身份保持效果。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/b84d91833607c18321534c4c1f3e02e0.png

结果图来自于 source

该图展示了在不同风格和场景下,随着光照条件变化的结果。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/b3364a0266714b2402a305762025eb1d.png

结果图来自于 source

该图将重新照明与另一种方法进行了比较。利用扩散先验有助于提高泛化能力并解决阴影歧义问题。

局限性

由于该方法假设定向光照,它支持沿任意方向追踪光线。它需要阴影线索来生成图像,而这些线索并不容易获取。此外,他们的方法不适用于肖像和室内场景。

主要收获

我们讨论了一个非详尽的文献列表,这些文献利用二维扩散模型进行重光照。我们探索了多种方式来条件化扩散模型进行重光照,从辐射线索、直接阴影图像、光照方向到环境图。大多数这些方法仅在合成数据集上展示结果,并且难以很好地推广到不同分布的数据集。每天都有更多的论文发表,基础模型也在不断改进。最近发布了IC-Light2,这是一个基于 Flux 模型的 ControlNet 模型。它的发展方向非常值得关注,因为维持身份一致性是一个棘手的问题。

参考文献:

  1. GitHub — lllyasviel/IC-Light: 更多重光照!

  2. IllumiNeRF — 无需反向渲染的三维重光照

  3. Neural Gaffer

  4. DiLightNet:基于扩散图像生成的精细化光照控制

  5. Relightful Harmonization

  6. 一种基于扩散方法的辐射场重光照使用多重照明合成

  7. 扩散模型是如何工作的:从零开始的数学讲解 | AI Summer

  8. 成像与视觉领域的扩散模型教程

  9. 从零开始实现扩散模型的 PyTorch 教程

  10. 扩散模型 — 现场编码教程

  11. 扩散模型 | 论文解读 | 数学原理讲解

  12. 我如何理解扩散模型 由黄家宾教授讲解

  13. 去噪扩散概率模型 | DDPM 讲解 对扩散模型的数学直觉理解

让我们通过数独了解一下计算机视觉

原文:towardsdatascience.com/lets-learn-a-little-about-computer-vision-via-sudoku-836065c0f07b?source=collection_archive---------2-----------------------#2024-12-14

解数独是编程中的一个有趣挑战,将计算机视觉技术添加到数独解题中,可以将其与流行的机器学习技术相结合

https://medium.com/@broepke?source=post_page---byline--836065c0f07b--------------------------------https://towardsdatascience.com/?source=post_page---byline--836065c0f07b-------------------------------- Brian Roepke

·发表于Towards Data Science ·阅读时间 8 分钟·2024 年 12 月 14 日

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/81e3ba184f32ab3733ba55a7a898f130.png

图片由作者使用 HubSpot AI 制作

介绍

这一切最初只是一个有趣的小实验,目的是编写另一个解谜工具,类似于我最近写的Wordle解答器。数独是一个非常适合计算机求解的问题。它是通过简单的迭代方式来寻找独特的解法。可能有成千上万的例子,因此虽然我会提到我最终如何解决这个谜题,但我更想关注我在这款游戏中采用的机器学习(ML)和人工智能(AI)方法。我想,为什么不将计算机视觉(CV)和光学字符识别(OCR)加入其中,让你能够上传谜题的图像,机器将读取并从那里开始解决剩下的部分。结果这是一次非常棒的学习体验,我很乐意和大家分享!

到现在为止,我们大概都熟悉数独是什么了,那么让我们深入了解如何从图像中提取数字吧!

让我们用 JAX 重建 NanoGPT!(第一部分)

原文:towardsdatascience.com/lets-reproduce-nanogpt-with-jax-part-1-95bec4630eb4?source=collection_archive---------2-----------------------#2024-07-21

第一部分:使用 JAX 构建 124M GPT2。

第二部分:在单 GPU 中优化训练速度。

第三部分:在 JAX 中进行多 GPU 训练。

https://lou1swang.medium.com/?source=post_page---byline--95bec4630eb4--------------------------------https://towardsdatascience.com/?source=post_page---byline--95bec4630eb4-------------------------------- Louis Wang

·发表于 Towards Data Science ·阅读时间:8 分钟·2024 年 7 月 21 日

受到 Andrej Karpathy 最近的 YouTube 视频让我们重建 GPT-2(124M)的启发,我想用 JAX 重建它,并进行大多数训练优化。JAX 专为高效计算速度而构建,非常有趣的是,可以将 Pytorch 与其最近的训练优化以及 JAX 与其相关库(如 Flax:JAX 的神经网络训练层 API 和 Optax:JAX 的梯度处理和优化库)进行对比。我们将迅速了解 JAX,并用 JAX 重建 GPT。最后,我们将比较 Pytorch 和 JAX 在多 GPU 训练中的 token/sec。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/327bdd2b1dfc0479960467df61f2d5da.png

AI 生成的 GPT

什么是 Jax?

根据其readthedoc,JAX 是一个面向加速器的数组计算和程序转换的 Python 库,旨在实现高性能的数值计算和大规模机器学习。我想用它的名字来介绍 JAX。虽然有人称它为 Just Another XLA(加速线性代数),我更愿意称其为 J(it) A(utograd) X(LA),以展示它的高效能力。

J — Just-in-time (JIT) 编译。当你运行 Python 函数时,Jax 将其转换为一组基本操作,称为 Jaxpr。然后,Jaxpr 表达式会被转换为 XLA 的输入,XLA 将其编译成底层脚本,从而为目标设备(CPU、GPU 或 TPU)生成优化后的可执行文件。

A — Autograd。计算梯度是现代机器学习方法中的一个关键部分,你只需要调用jax.grad()来获取梯度,从而优化模型。

X — XLA。这是一个开源的机器学习编译器,支持 CPU、GPU 和 ML 加速器。通常,XLA 会对StableHLO图进行几个内建的优化和分析传递,然后将 HLO 计算发送到后端进行进一步的 HLO 级别优化。后端再进行特定目标的代码生成。

这些只是 JAX 的一些关键特性,但它还有许多类似于 numpy 的用户友好 API,如jax.numpy,以及通过jax.vmap进行的自动向量化,和通过jax.pmap将代码并行化到多个设备上。我们将在以后的博客中介绍更多 Jax 的概念和应用,但现在让我们用 Jax 复现 NanoGPT!

从注意力机制到变换器(Transformer)

GPT 是一种仅解码的变换器模型,关键构建模块是注意力模块。我们可以首先定义一个模型配置数据类来保存模型的超参数,这样模型模块就能高效地使用它来初始化模型架构。类似于 124M GPT 模型,在这里我们初始化一个 12 层的变换器解码器,具有 12 个头和 50257 个词汇表大小,每个词汇表项有 768 维嵌入向量。注意力计算的块大小为 1024。

from dataclasses import dataclass

@dataclass
class ModelConfig:
  vocab_size: int = 50257
  n_head: int = 12
  n_embd: int = 768
  block_size: int = 1024
  n_layer: int = 12
  dropout_rate: float = 0.1

接下来是变换器模型的关键构建模块——注意力机制(Attention)。其思想是将输入处理成三个权重矩阵:Key、Query 和 Value。在这里,我们依赖于flax,这是一个 Jax 层和训练 API 库,用来初始化这三个权重矩阵,只需要调用[flax.linen.Dense](https://flax.readthedocs.io/en/v0.5.3/_autosummary/flax.linen.Dense.html)。如前所述,Jax 有许多类似 numpy 的 API,因此我们使用[jax.numpy.reshape](https://jax.readthedocs.io/en/latest/_autosummary/jax.numpy.reshape.html)将权重矩阵后的输出从[batch_size, sequence_length, embedding_dim]重塑为[batch_size, sequence_length, num_head, embedding_dim / num_head]。由于我们需要对 Key 和 Value 矩阵执行矩阵乘法,jax 还提供了[jax.numpy.matmul](https://jax.readthedocs.io/en/latest/_autosummary/jax.numpy.matmul.html)[jax.numpy.transpose](https://jax.readthedocs.io/en/latest/_autosummary/jax.numpy.transpose.html) API(用于转置 Key 矩阵以进行乘法运算)。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/8f2916bfb42338ef17e1526a677e4f85.png

多头注意力(Multihead Attention)

请注意,我们需要在注意力矩阵上加上一个掩码,以避免信息泄漏(防止之前的 tokens 访问到后面的 tokens),[jax.numpy.tril](https://jax.readthedocs.io/en/latest/_autosummary/jax.numpy.tril.html) 帮助构建一个下三角数组,而 [jax.numpy.where](https://jax.readthedocs.io/en/latest/_autosummary/jax.numpy.where.html) 可以为我们填充无限大的数值,以便在 softmax [jax.nn.softmax](https://jax.readthedocs.io/en/latest/_autosummary/jax.nn.softmax.html) 后得到 0。多头注意力的完整代码如下所示。

from flax import linen as nn
import jax.numpy as jnp

class CausalSelfAttention(nn.Module):

  config: ModelConfig

  @nn.compact
  def __call__(self, x, deterministic=True):

    assert len(x.shape) == 3

    b, l, d = x.shape

    q     = nn.Dense(self.config.n_embd)(x)
    k     = nn.Dense(self.config.n_embd)(x)
    v     = nn.Dense(self.config.n_embd)(x)
    # q*k / sqrt(dim) -> softmax -> @v
    q     = jnp.reshape(q, (b, l, d//self.config.n_head , self.config.n_head))
    k     = jnp.reshape(k, (b, l, d//self.config.n_head , self.config.n_head))
    v     = jnp.reshape(v, (b, l, d//self.config.n_head , self.config.n_head))
    norm  = jnp.sqrt(list(jnp.shape(k))[-1])
    attn  = jnp.matmul(q,jnp.transpose(k, (0,1,3,2))) / norm
    mask  = jnp.tril(attn)
    attn  = jnp.where(mask[:,:,:l,:l], attn, float("-inf"))
    probs = jax.nn.softmax(attn, axis=-1)
    y     = jnp.matmul(probs, v)
    y     = jnp.reshape(y, (b,l,d))
    y     = nn.Dense(self.config.n_embd)(y)
    return y

你可能会注意到,在 Pytorch 中常见的 __init__forward 方法在这里并不存在。这是 jax 的特点,在 jax 中你可以显式地通过 setup 方法定义层,或者通过在 __call__ 方法上添加 nn.compact 来隐式定义它们。[参考]

接下来让我们构建 MLP 和 Block 层,包括 Dense 层、Gelu 激活函数、LayerNorm 和 Dropout。再次,flax.linen 提供了层的 API,帮助我们构建模块。请注意,我们会传递一个 deterministic 布尔变量来控制某些层(如 Dropout)在训练或评估期间的不同行为。

class MLP(nn.Module):

  config: ModelConfig

  @nn.compact
  def __call__(self, x, deterministic=True):
    x = nn.Dense(self.config.n_embd*4)(x)
    x = nn.gelu(x, approximate=True)
    x = nn.Dropout(rate=self.config.dropout_rate)(x, deterministic=deterministic)
    x = nn.Dense(self.config.n_embd)(x)
    x = nn.Dropout(rate=self.config.dropout_rate)(x, deterministic=deterministic)
    return x

class Block(nn.Module):

  config: ModelConfig

  @nn.compact
  def __call__(self, x):
    x = nn.LayerNorm()(x)
    x = x + CausalSelfAttention(self.config)(x)
    x = nn.LayerNorm()(x)
    x = x + MLP(self.config)(x)
    return x

现在让我们使用上述模块来构建 NanoGPT:

给定一个序列的 token ids 输入,我们使用 [flax.linen.Embed](https://flax.readthedocs.io/en/v0.5.3/_autosummary/flax.linen.Embed.html) 层来获取位置嵌入和 token 嵌入。然后,我们将它们传入 Block 模块 N 次,其中 N 是模型配置中定义的层数。最后,我们将来自最后一个 Block 的输出映射到每个词汇表 token 的概率,以预测下一个 token。除了前向 __call__ 方法之外,我们还需要创建一个 init 方法来获取虚拟输入并获得模型的参数。

class GPT(nn.Module):

  config: ModelConfig

  @nn.compact
  def __call__(self, x, deterministic=False):

    B, T = x.shape
    assert T <= self.config.block_size

    pos     = jnp.arange(0, T)[None]
    pos_emb = nn.Embed(self.config.block_size, self.config.n_embd)(pos)
    wte     = nn.Embed(self.config.vocab_size, self.config.n_embd)
    tok_emb = wte(x)
    x       = tok_emb + pos_emb

    for _ in range(self.config.n_layer):
      x = Block(self.config)(x)
    x = nn.LayerNorm()(x)
    logits = nn.Dense(config.n_embd, config.vocab_size)(x)
    # logits = wte.attend(x) # parameter sharing
    return logits

  def init(self, rng):
    tokens = jnp.zeros((1, self.config.block_size), dtype=jnp.uint16)
    params = jax.jit(super().init, static_argnums=(2,))(rng, tokens, True)
    return params 

现在让我们验证一下参数的数量:我们首先初始化模型配置的数据类和随机密钥,然后创建一个虚拟输入并将其输入到 GPT 模型中。接着,我们利用 jax.util.treemap API 创建一个计数参数函数。我们得到了 124439808(124M)个参数,与 Huggingface 的 GPT2 相同,哇!

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/513bfdf90096dcb03e3cd4a76910d2d7.png

Colab 结果:参数数量

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/576fc03b5cb1912c0ba770b510de7b73.png

验证 Huggingface 的 GPT2 参数数量

数据加载器和训练循环

现在让我们在一个小数据集上进行过拟合。为了与 Andrej 的 Pytorch NanoGPT 视频中进行对比,我们使用他在视频中分享的玩具 dataset。我们使用 tiktoken 库的 GPT2 分词器对输入文件中的所有文本进行分词,并将这些 token 转换为 jax.numpy.array 以便 Jax 的模型训练。

class DataLoader:
  def __init__(self, B, T):
    self.current_position = 0
    self.B = B
    self.T = T

    with open("input.txt","r") as f:
      text = f.read()
    enc = tiktoken.get_encoding("gpt2")
    self.tokens = jnp.array(enc.encode(text))
    print(f"loaded {len(self.tokens)} tokens in the datasets" )
    print(f" 1 epoch = {len(self.tokens)//(B*T)} batches")

  def next_batch(self):
    B,T = self.B, self.T
    buf = self.tokens[self.current_position:self.current_position+B*T+1]
    x,y = jnp.reshape(buf[:-1],(B,T)), jnp.reshape(buf[1:],(B,T))
    self.current_position += B*T
    if self.current_position + B*T+1 > len(self.tokens):
      self.current_position = 0
    return x,y

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/da59795faf894d9f4bd6f4d1ed782ece.png

Colab 结果:简单的数据加载器,批量大小为 4,序列长度为 128

接下来,让我们暂时忽略分布式训练和优化,先创建一个简单的训练循环进行基本检查。初始化模型后的第一件事是创建一个TrainState,这是一个可以更新参数和梯度的模型状态。TrainState 接受三个重要输入:apply_fn(模型前向函数)、params(来自初始化方法的模型参数)和 tx(一个 Optax 梯度变换)。

然后我们使用 train_step 函数来更新模型状态(梯度和参数),以继续模型训练。Optax 提供了用于下一个令牌预测任务的 softmax 交叉熵作为损失函数,jax.value_and_grad 用于计算损失函数的梯度和损失值。最后,我们使用 apply_gradients API 更新模型的状态和新参数。[ref] 别忘了对 train_step 函数进行 JIT 编译,以减少计算开销!

def init_train_state(key, config) -> TrainState:
  model = GPT(config)
  params = model.init(key)
  optimizer = optax.adamw(3e-4, b1=0.9, b2=0.98, eps=1e-9, weight_decay=1e-1)
  train_state = TrainState.create(
        apply_fn=model.apply,
        params=params,
        tx=optimizer)
  return train_state

@jax.jit
def train_step(state: TrainState, x: jnp.ndarray, y: jnp.ndarray) -> Tuple[jnp.ndarray, TrainState]:

  def loss_fn(params: FrozenDict) -> jnp.ndarray:

      logits = state.apply_fn(params, x, False)
      loss = optax.softmax_cross_entropy_with_integer_labels(logits, y).mean()
      return loss

  loss, grads = jax.value_and_grad(loss_fn, has_aux=False)(state.params)
  new_state = state.apply_gradients(grads=grads)
  return loss, new_state

现在一切准备就绪,可以开始进行简单的训练循环了……让我们检查损失值。模型的预测应该优于随机猜测,因此损失值应该低于 -ln(1/50257)≈10.825。我们对单批次过拟合的预期是:一开始损失接近 10.825,然后下降到接近 0。让我们取一批(x,y)并运行训练循环 50 次。我还添加了类似的日志来计算训练速度。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/add7d951685406ff5cfce20cccb41414.png

如我们所见,损失值正是我们预期的,训练吞吐量大约是 400–500 k token/sec。这已经比 Andrej 视频中没有任何优化的 Pytorch 初始版本快了 40 倍。请注意,我们是在 1 个 A100 GPU 上运行 Jax 脚本,这应该消除了硬件差异对速度比较的影响。这里没有 .to(device) 的操作来将模型或数据从主机 CPU 移动到设备 GPU,这正是 Jax 的一个优势!

就这样,我们做到了。我们将在第二部分通过更多优化将训练速度提升至原来的 10 倍…

第二部分:训练优化之旅,如何在单个 GPU 上达到 1350k tokens/sec!

“除非另有说明,所有图片均为作者所提供”

让我们重新审视不同库中的 case-when,包括新玩家:Pandas

原文:towardsdatascience.com/lets-revisit-case-when-in-different-libraries-including-the-new-player-pandas-8c4febb979ba?source=collection_archive---------17-----------------------#2024-06-18

如何使用不同工具创建条件列。

https://sonery.medium.com/?source=post_page---byline--8c4febb979ba--------------------------------https://towardsdatascience.com/?source=post_page---byline--8c4febb979ba-------------------------------- Soner Yıldırım

·发布于 Towards Data Science ·6 分钟阅读·2024 年 6 月 18 日

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/bc06ef7c7230f619353bf30c88265be0.png

图片由 JESHOOTS.COM 提供,来源于 Unsplash

无论你是在做数据分析、数据清洗,还是特征工程,基于其他列的值创建新列是一个常见的操作。

我使用过的所有数据清洗和处理工具都有执行此任务的函数(例如 SQL、R 数据表、PySpark)。现在我们有了游戏中的新玩家:Pandas。

顺便提一下,虽然之前可以使用 Pandas 创建条件列,但它并没有专门的 case-when 函数。

在 Pandas 2.2.0 中,引入了 case_when 函数,用于根据一个或多个条件创建 Series 对象。

让我们重新审视如何使用常用的数据分析和处理工具完成这个非常有用的操作。

为了保持一致性并更容易看出工具之间的差异,我们将使用一个小型数据集。

SQL

以下是一个名为“mytable”的小型 SQL 表。

+-------------+----------+---------+
|           a |        b |       c |
+-------------+----------+---------+
|           0 |        5 |       1 |
|           1 |       -1 |

让我们在 Python 中编写一个可组合的、易于使用的缓存包

原文:towardsdatascience.com/lets-write-a-composable-easy-to-use-caching-package-in-python-171801935540?source=collection_archive---------11-----------------------#2024-08-29

简单、用户友好的缓存,满足您所有的需求

https://mikehuls.medium.com/?source=post_page---byline--171801935540--------------------------------https://towardsdatascience.com/?source=post_page---byline--171801935540-------------------------------- Mike Huls

·发表于Towards Data Science ·9 分钟阅读·2024 年 8 月 29 日

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/c5c0b29c65a5b610fca4844dc0182202.png

Python 选择缓存策略(图片由 ChatGPT 提供 + 作者进行的业余编辑)

在本文中,我们将逐步讲解从零开始构建 Python 缓存系统的过程。目标是……

  • 更好地理解缓存并探索各种缓存策略

  • 了解为什么我们选择了组合方式而非继承方式

  • 学习如何以及何时有效地应用缓存

我们将重点构建一个用户友好的包,使您能够轻松地将缓存添加到您的代码中。此外,我们还将提供一种简单的方式来扩展缓存,通过为其提供自定义行为。让我们开始编码吧!

在我们开始之前……

本文详细介绍了如何创建缓存,这些缓存是PyPI 上的 Cachr 包的一部分。所有代码都可以在这个Github 仓库中找到。欢迎任何贡献;随时提交错误报告、错误修复、功能请求、文档改进或增强功能!

使用 Python 线程提升你的编码技能

原文:towardsdatascience.com/level-up-your-coding-skills-with-python-threading-8f1bd06b9476?source=collection_archive---------3-----------------------#2024-11-27

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/c0583e9f19a60078de7cb7565c397b23.png

图片由Sonika Agarwal拍摄,来源于Unsplash

学习如何在机器学习项目中使用队列、守护线程和事件

https://medium.com/@marcellopoliti?source=post_page---byline--8f1bd06b9476--------------------------------https://towardsdatascience.com/?source=post_page---byline--8f1bd06b9476-------------------------------- Marcello Politi

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

引言

在大多数机器学习工作中,你并不会研究如何改进某个模型架构或设计一个新的损失函数。大多数时候,你必须利用已有的技术并将其适应你的使用场景。因此,优化你的项目在架构设计和实现方面非常重要。一切从这里开始:你需要的是最优的代码,简洁、可重用并且尽可能快地运行。线程是 Python 内置的原生库,但人们并不像应该的那样频繁使用它。

关于线程

线程是让一个程序将自己拆分为两个或更多同时(或伪同时)运行的任务的一种方式……通常,线程存在于一个进程中,同一进程中的不同线程共享相同的资源。

在这篇文章中,我们不会讨论多进程,但 Python 的多进程库与多线程库非常相似。通常:

  • 多线程非常适合 I/O 密集型任务,例如在 for 循环中调用 API

  • 多进程用于 CPU 密集型任务,例如……

通过这 10 个有用的技巧,提升你的 Git 知识,浏览 Git 历史记录

原文:towardsdatascience.com/level-up-your-git-knowledge-with-these-10-useful-tips-to-browse-git-history-5f7b9f4b0e1d?source=collection_archive---------8-----------------------#2024-03-09

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/c4d8a1144330e2ba0125bb441ad9d14b.png

图片来源:Thomas KelleyUnsplash

精通 Git

学习高级的 git log

https://zluvsand.medium.com/?source=post_page---byline--5f7b9f4b0e1d--------------------------------https://towardsdatascience.com/?source=post_page---byline--5f7b9f4b0e1d-------------------------------- Zolzaya Luvsandorj

·发表于 Towards Data Science ·阅读时长 10 分钟·2024 年 3 月 9 日

如果你已经在使用 Git,那么你可能熟悉 git log 命令。除了它的基本用法(即普通的 git log),这个命令的高级用法非常强大,可以使得浏览仓库历史记录变得更加顺畅和富有信息。在这篇文章中,我们将学习一些使用 git log 的实用方法,帮助你将 Git 知识提升到一个新水平。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/b5748d323b495713fb376aa7fcaeddcb.png

图片来源:Chris LawtonUnsplash

🔧 0. 设置

本文假设读者(即你)已经熟悉 Git 的基本用法。如果你需要复习 Git 的基础知识,可以先阅读这篇文章。为了更好地理解本文,我建议你在阅读过程中动手实践这些命令。我们在积极练习新知识时,比被动阅读更能快速学习。

我们将使用我最喜欢的 GitHub 仓库之一:ABSphreak/readme-jokes: 😄 适用于你的 GitHub README 文件的笑话 来演示命令的使用。这个精彩的轻量级仓库让我可以在我的 GitHub 个人资料中添加随机的编程笑话。让我们开始吧……

提升你的 Pandas 技能,发掘这 15 个隐藏宝藏

原文:towardsdatascience.com/level-up-your-pandas-game-with-these-15-hidden-gems-1c6aded2060f?source=collection_archive---------1-----------------------#2024-01-25

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/b5ffeecbac3e26398fd4c7cecc595942.png

图片由 Midjourney 生成

另一个探索 Pandas 更多有用功能的机会

https://eryk-lewinson.medium.com/?source=post_page---byline--1c6aded2060f--------------------------------https://towardsdatascience.com/?source=post_page---byline--1c6aded2060f-------------------------------- Eryk Lewinson

·发表于 Towards Data Science ·阅读时间 8 分钟·2024 年 1 月 25 日

我真正喜欢 pandas 的地方在于,你可以用它工作多年,可能仍然有很多你未曾了解的有用方法。这就是为什么在这一系列的第四部分中,我将向你展示一些你可能从未听说过的但绝对对数据清洗有帮助的方法。在我们开始之前,你可能想要查看一下系列的前几部分:

  • 你可能没听说过的 9 个有用的 Pandas 方法

  • 你分析中可能需要的 8 个额外的有用 Pandas 功能

  • 你可能忽略的 11 个有用的 Pandas 功能

我们将按字母顺序介绍这些方法,而不是按它们的实用性排序。让我们直接开始吧!

设置

这次我们不需要任何复杂的库,因此我们只需导入基本的库:

利用 KeyBERT、HDBSCAN 和 Zephyr-7B-Beta 构建知识图谱

原文:towardsdatascience.com/leverage-keybert-hdbscan-and-zephyr-7b-beta-to-build-a-knowledge-graph-33d7534ee01b?source=collection_archive---------0-----------------------#2024-01-07

增强型大语言模型自然语言处理与传统机器学习技术结合,用于从非结构化语料库中提取结构并构建知识图谱。

https://medium.com/@silviaonofrei?source=post_page---byline--33d7534ee01b--------------------------------https://towardsdatascience.com/?source=post_page---byline--33d7534ee01b-------------------------------- Silvia Onofrei

·发表于 Towards Data Science ·19 分钟阅读·2024 年 1 月 7 日

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/f81dc37979040a1df7f80c88c9bfba77.png

设计者:Freepik

介绍

虽然大型语言模型(LLMs)是有用且高效的工具,但完全依赖它们的输出并不总是明智的,因为它们通常需要验证和基础支持。然而,将传统的自然语言处理方法与生成型人工智能的能力相结合,通常能取得令人满意的结果。一个很好的例子是,将 KeyBERT 与 KeyLLM 相结合以进行关键词提取。

在这篇博客中,我打算探讨将传统的自然语言处理和机器学习技术与大型语言模型的多功能性相结合的有效性。这一探索包括使用 KeyBERT 进行简单的关键词提取,利用 BERT 进行句子嵌入,以及使用 UMAP 进行降维,结合 HDBSCAN 进行聚类。所有这些技术与高性能的 Zephyr-7B-Beta 一起使用,最终将结果上传到知识图谱中,以便进行更深入的分析和发现。

我的目标是开发一个针对计算机科学领域非结构化 arXiv 文章标题的结构化方法。我根据摘要的长度选择了这些文章,而并非期待内在的主题聚类。事实上,初步的社区分析揭示了几乎与文章数量相等的聚类。因此,我正在探索另一种将这些标题联系起来的方法。尽管缺乏明显的社区,这些标题通常共享相同的词汇。通过提取并聚类这些关键词,我旨在揭示标题之间的潜在联系,为数据集结构化提供一种多功能的策略。

为了简化并增强数据探索,我将结果上传至 Neo4j 知识图谱。以下是输出的快照:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/96e2d0b9ac4a38c4edcf1392d6108b9f.png

这两个紫色节点代表标题:“可数范畴结构的核心”(左)和“通过集合解释转化结构”(右)。它们通过“数学逻辑”(浅褐色节点)这一共同主题,通过关键词“结构”连接在一起。— 图像由作者提供 —

以下是项目的步骤:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/c16d1bc3d9daa4b48d465d17360fc24e.png

— 图示由作者提供 —

  • 收集并解析数据集,重点关注标题,同时保留摘要以提供上下文。

  • 使用 KeyBERT 提取候选关键词,然后基于 Zephyr-7B-Beta,通过 KeyLLM 对其进行优化,生成增强后的关键词和关键短语列表。

  • 收集所有提取的关键词和关键短语,并使用 HDBSCAN 对它们进行聚类。

  • 再次使用 Zephyr-7B-Beta,为每个聚类推导标签和描述。

  • 将这些元素结合到知识图谱中,节点代表文章、关键词和(聚类)主题。

需要注意的是,过程中的每一步都提供了实验替代方法、算法或模型的灵活性。

工作在 Google Colab Pro 上进行,配备 V100 GPU 和高内存设置,用于涉及 LLM 的步骤。笔记本被划分为自包含的部分,其中大多数部分可以独立执行,最小化对先前步骤的依赖。每个部分执行完毕后都会保存数据,以便在需要时能够在新会话中继续。此外,解析后的数据集和 Python 模块可以在这个 Github 仓库 中找到。

数据准备

我使用的是一个来自 arXiv 数据集 的子集,该数据集公开可用并主要由康奈尔大学维护。以机器可读格式提供,包含 170 万篇跨学科的学术论文,涵盖 STEM 领域,并提供文章标题、作者、类别、摘要、完整文本 PDF 等相关特征。该数据集定期更新。

数据集已清理完毕,并且格式简洁易用,因此我们可以集中精力处理任务,而无需花费过多时间进行数据预处理。为了进一步简化数据准备过程,我构建了一个 Python 模块,执行相关步骤。如果你想查看代码,可以在[utils/arxiv_parser.py](https://github.com/SolanaO/Blogs_Content/blob/master/keyllm_neo4j/utils/arxiv_parser.py)找到,或者继续使用 Google Colab:

  • 下载压缩的 arXiv 文件(1.2 GB),并选择一个目录进行存储,该目录标记为data_path

  • 下载arxiv_parser.pyutils目录,

  • 在你的 Google Colab 笔记本中导入并初始化模块,

  • 解压文件,这将提取出一个 3.7 GB 的文件:archive-metadata-oai-snapshot.json

  • 指定一个一般主题(我使用cs,即计算机科学),这样你将拥有一个更易于管理的数据集,

  • 选择要保留的特征(下载的数据集中有 14 个特征),

  • 摘要的长度可能差异较大,因此我添加了一个选项,可以选择摘要中的 token 数量在给定区间内的条目,并利用此功能来缩小数据集的规模,

  • 尽管我选择使用title特征,但也有一个选项可以采用更常见的方法,即将标题和摘要合并为一个单一特征,称为corpus

# Import the data parser module
from utils.arxiv_parser import *

# Initialize the data parser
parser = ArXivDataProcessor(data_path)

# Unzip the downloaded file to extract a json file in data_path
parser.unzip_file()

# Select a topic and extract the articles on that topic
topic='cs'
entries = parser.select_topic('cs')

# Build a pandas dataframe with specified selections
df = parser.select_articles(entries, # extracted articles
                            cols=['id', 'title', 'abstract'], # features to keep
                            min_length = 100, # min tokens an abstract should have
                            max_length = 120, # max tokens an abstract should have
                            keep_abs_length = False, # do not keep the abs_length column
                            build_corpus=False) # do not build a corpus column

# Save the selected data to a csv file 'selected_{topic}.csv', uses data_path
parser.save_selected_data(df,topic)

使用上述选项,我提取了一个包含 983 篇计算机科学文章的数据集。我们准备好进入下一步。

如果你想跳过数据处理步骤,可以使用cs数据集,该数据集可以在 Github 仓库中找到。

使用 KeyBERT 和 KeyLLM 进行关键词提取

方法

KeyBERT是一种从文本中提取关键词或关键短语的方法。它利用文档和单词的嵌入,通过余弦相似度找到与文档最相似的子短语。KeyLLM 是另一种最小化的关键词提取方法,但它基于 LLM。两种方法都由 Maarten Grootendorst 开发和维护。

这两种方法可以结合使用以获得更好的结果。通过 KeyBERT 提取的关键词可以通过 KeyLLM 进行微调。相反,通过传统 NLP 技术识别的候选关键词有助于为 LLM 提供基础,从而最小化不希望产生的输出。

有关使用 KeyLLM 的不同方式,请参见 Maarten Grootendorst, 介绍 KeyLLM — 使用 LLM 进行关键词提取。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/f59f1b8d7f5ad0fadf9cd40a15394682.png

— 作者制作的图表 —

使用 KeyBERT [source] 从每篇文档中提取关键词——这些是提供给 LLM 进行微调的候选关键词:

  • 使用 Sentence Transformers 对文档进行嵌入,以构建文档级别的表示。

  • 为 N-gram 单词/短语提取词嵌入,

  • 使用余弦相似度来查找与每个文档最相似的单词或短语。

使用 KeyLLM[source]微调通过 KeyBERT 提取的关键词,利用transformers 进行文本生成[source]:

  • Sentence Transformers 中的社区检测方法[source]将相似文档分组,因此我们只会从每个组中的一个文档中提取关键词,

  • 候选关键词由 LLM 提供,LLM 为每个聚类微调关键词。

除了 Sentence Transformers,KeyBERT 还支持其他嵌入模型,见[此处]。

Sentence Transformers 通过使用指定的阈值来促进社区检测。当文档缺乏固有的聚类时,可能不会出现明确的分组。在我的案例中,从 983 个标题中,大约识别出了 800 个不同的社区。更加自然聚类的数据往往能产生定义更明确的社区。

大型语言模型

在对各种较小的 LLM 进行实验后,我选择了Zephyr-7B-Beta用于本项目。该模型基于Mistral-7B,并且是首批使用直接偏好优化(DPO)进行微调的模型之一。它不仅在同类模型中表现优异,而且在一些基准测试中超过了 Llama2–70B。欲了解有关此 LLM 的更多见解,请查看 Benjamin Marie, Zephyr 7B Beta: 一个好老师就是你所需要的一切。虽然可以直接在 Google Colab Pro 上使用该模型,但我选择了使用由TheBloke准备的 GPTQ 量化版本。

首先按照模型卡片中的说明下载模型及其分词器

# Required installs
!pip install transformers optimum accelerate
!pip install auto-gptq --extra-index-url https://huggingface.github.io/autogptq-index/whl/cu118/

# Required imports
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline

# Load the model and the tokenizer
model_name_or_path = "TheBloke/zephyr-7B-beta-GPTQ"

llm = AutoModelForCausalLM.from_pretrained(model_name_or_path,
                                             device_map="auto",
                                             trust_remote_code=False,
                                             revision="main") # change revision for a different branch
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, 
                     use_fast=True)

此外,构建文本生成管道:

generator = pipeline(
    model=llm,
    tokenizer=tokenizer,
    task='text-generation',
    max_new_tokens=50,
    repetition_penalty=1.1,
)

关键词提取提示

这一过程中的实验至关重要。寻找最佳提示需要一些试验和错误,性能取决于所选择的模型。不要忘记,LLM 是基于概率的,因此无法保证每次都返回相同的输出。为了开发下面的提示,我依赖了实验以及以下考虑因素:

prompt = "Tell me about AI"
prompt_template=f'''<|system|>
</s>
<|user|>
{prompt}</s>
<|assistant|>
'''

这是我用来微调通过 KeyBERT 提取的关键词的提示:

prompt_keywords= """
<|system|>
I have the following document:
Semantics and Termination of Simply-Moded Logic Programs with Dynamic Scheduling
and five candidate keywords:
scheduling, logic, semantics, termination, moded

Based on the information above, extract the keywords or the keyphrases that best describe the topic of the text.
Follow the requirements below:
1\. Make sure to extract only the keywords or keyphrases that appear in the text.
2\. Provide five keywords or keyphrases! Do not number or label the keywords or the keyphrases!
3\. Do not include anything else besides the keywords or the keyphrases! I repeat do not include any comments!

semantics, termination, simply-moded, logic programs, dynamic scheduling</s>

<|user|>
I have the following document:
[DOCUMENT]
and five candidate keywords:
[CANDIDATES]

Based on the information above, extract the keywords or the keyphrases that best describe the topic of the text.
Follow the requirements below:
1\. Make sure to extract only the keywords or keyphrases that appear in the text.
2\. Provide five keywords or keyphrases! Do not number or label the keywords or the keyphrases!
3\. Do not include anything else besides the keywords or the keyphrases! I repeat do not include any comments!</s>

<|assistant|>
"""

关键词提取与解析

现在我们拥有了进行关键词提取所需的一切。让我提醒你,我处理的是标题,因此输入文档较短,完全在 BERT 嵌入的令牌限制范围内。

从创建TextGeneration 管道封装器开始,为 LLM 实例化KeyBERT。选择嵌入模型。如果未指定嵌入模型,默认模型为all-MiniLM-L6-v2。在这种情况下,我选择了句子嵌入的最高性能预训练模型,完整列表请参见这里

# Install the required packages
!pip install keybert
!pip install sentence-transformers

# The required imports
from keybert.llm import TextGeneration
from keybert import KeyLLM, KeyBERT
from sentence_transformers import SentenceTransformer

# KeyBert TextGeneration pipeline wrapper
llm_tg = TextGeneration(generator, prompt=prompt_keywords)

# Instantiate KeyBERT and specify an embedding model
kw_model= KeyBERT(llm=llm_tg, model = "all-mpnet-base-v2")

记住,数据集已经准备并保存为 pandas 数据框df。要处理标题,只需调用extract_keywords方法:

# Retain the articles titles only for analysis
titles_list = df.title.tolist()

# Process the documents and collect the results
titles_keys = kw_model.extract_keywords(titles_list, thresold=0.5)

# Add the results to df
df["titles_keys"] = titles_keys

threshold 参数决定了将文档分组到同一社区所需的最小相似度。较高的值会将几乎相同的文档分组,而较低的值则会将涵盖相似主题的文档聚类在一起。

嵌入模型的选择显著影响合适的阈值,因此建议查阅模型卡片以获取指导。感谢 Maarten Grootendorst 强调这一点,正如这里所示。

需要注意的是,我的观察仅适用于句子变换器,因为我尚未尝试其他类型的嵌入模型。

让我们看一些输出:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/c770ad00ee29463c4e22dd7621eb95fa.png

评论

  • 在这里提供的第二个示例中,我们观察到原始文本中没有出现的关键词或关键短语。如果这对你构成问题,可以考虑启用check_vocab=True,如[这里]所做。然而,需要记住的是,这些结果受 LLM 选择的影响很大,量化的影响较小,提示的构造也有一定影响。

  • 在处理较长的输入文档时,我注意到输出与预期结果的偏差增多。

  • 一个一致的观察是,提取的关键词数量通常偏离五个。尤其在输入较简短时,常会遇到提取的关键词较少的标题。相反,一些标题会提取出多达 10 个关键词。让我们来看一下此次运行的关键词数量分布:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/77b2a23d02c46e836763d01129a71041.png

这些变化使得后续的解析步骤变得复杂。对此,有几种解决方案:我们可以详细调查这些情况,要求模型修正并修剪或重新整理关键词,或者简单地忽略这些情况,只专注于包含正好五个关键词的标题,正如我为这个项目决定的那样。

使用 HDBSCAN 进行关键词聚类

接下来的步骤是对关键词和关键短语进行聚类,以揭示文章中的共同话题。为此,我使用了两种算法:UMAP 用于降维,HDBSCAN 用于聚类。

算法:HDBSCAN 和 UMAP

基于密度的层次空间聚类与噪声HDBSCAN,是一种高效的无监督算法,旨在发现数据中的模式。它根据聚类的密度和接近度找到最优的聚类。这在聚类的数量和形状可能未知或难以确定的情况下尤其有用。

如果使用相同的超参数多次运行 HDBSCAN 聚类算法,其结果可能会有所不同。这是因为 HDBSCAN 是一个随机算法,这意味着聚类过程涉及一定程度的随机性。具体来说,HDBSCAN 使用随机初始化聚类层次结构,这可能导致每次运行算法时产生不同的聚类分配。

然而,算法在不同运行之间的变化程度可能取决于多个因素,例如数据集、超参数以及用于随机数生成器的种子值。在某些情况下,变化可能很小,而在其他情况下则可能较大。

HDBSCAN 提供了两种聚类选项。

  • 主要的聚类算法,标记为 hard_clustering,将每个数据点分配到一个聚类或标记为噪声。这是硬性分配;没有混合的隶属关系。这种方法可能导致一个大的聚类被归类为噪声(聚类标签为 -1),而其他则为许多较小的聚类。微调超参数至关重要 [参见这里],因为它选择了一个专门为该领域量身定制的嵌入模型。请查看相关的 Google Colab,查看该项目数据集上硬聚类的结果。

  • 软聚类是 HDBSCAN 库的一个新特性。在这种方法中,点不会被分配聚类标签,而是被分配一个概率向量。该向量的长度等于找到的聚类数。向量中每个位置的概率值表示该点是该聚类成员的概率。这使得点可能是多个聚类的混合。如果你想更好地理解软聚类是如何工作的,请参阅HDBSCAN 的软聚类工作原理。这种方法更适合当前项目,因为它生成了一个较大的、相对相似大小的聚类集。

虽然 HDBSCAN 在低到中维度数据上表现良好,但随着维度的增加,性能通常会显著下降。一般而言,HDBSCAN 在最多大约 50 维的数据上表现最佳,[请参阅这里]。

聚类用的文档通常使用 BERT 家族中的高效变换器进行嵌入,得到的是一个几百维的数据集。

为了减少嵌入向量的维度,我们使用UMAP统一流形近似与投影),这是一种非线性降维算法,并且是同类中表现最好的算法。它旨在学习数据的流形结构,并找到一个低维嵌入,从而保留该流形的基本拓扑结构。

研究表明,UMAP 在将高维数据的整体结构保留到低维时非常有效,同时在性能上优于其他流行算法,如 t-SNE 和 PCA。

关键词聚类

  • 安装并导入所需的包和库。
# Required installs
!pip install umap-learn
!pip install hdbscan
!pip install -U sentence-transformers

# General imports
import pandas as pd
import numpy as np
import re
import pickle

# Imports needed to generate the BERT embeddings
from sentence_transformers import SentenceTransformer

# Libraries for dimensionality reduction
import umap.umap_ as umap

# Import the clustering algorithm
import hdbscan
  • 准备数据集,将每个标题的单独五元组中的所有关键词和关键短语聚合成一个独特关键词的单一列表,并将其保存为一个 pandas 数据框。
# Load the data if needed - titles with 5 extracted keywords
df5 = pd.read_csv(data_path+parsed_keys_file) 

# Create a list of all sublists of keywords and keyphrases
df5_keys = df5.titles_keys.tolist()

# Flatten the list of sublists
flat_keys = [item for sublist in df5_keys for item in sublist]

# Create a list of unique keywords
flat_keys = list(set(flat_keys))

# Create a dataframe with the distinct keywords
keys_df = pd.DataFrame(flat_keys, columns = ['key'])

我从 884 个处理过的标题中获得了将近 3000 个独特的关键词和关键短语。以下是一个示例:n-可染图、实验、约束、树结构、复杂性等。

  • 使用 Sentence Transformers 生成 768 维的嵌入。
# Instantiate the embedding model
model = SentenceTransformer('all-mpnet-base-v2')

# Embed the keywords and keyphrases into 768-dim real vector space
keys_df['key_bert'] = keys_df['key'].apply(lambda x: model.encode(x))
  • 使用 UMAP 进行降维。
# Reduce to 10-dimensional vectors and keep the local neighborhood at 15
embeddings = umap.UMAP(n_neighbors=15, # Balances local vs. global structure.
                       n_components=10, # Dimension of reduced vectors
                       metric='cosine').fit_transform(list(keys_df.key_bert))

# Add the reduced embedding vectors to the dataframe
keys_df['key_umap'] = embeddings.tolist()
  • 使用 HDBSCAN 对 10 维向量进行聚类。为了保持这篇博客简洁,我将省略与硬聚类相关的参数描述。有关每个参数的详细信息,请参阅[HDBSCAN 参数选择]。
# Initialize the clustering model
clusterer = hdbscan.HDBSCAN(algorithm='best',
                            prediction_data=True,
                            approx_min_span_tree=True,
                            gen_min_span_tree=True,
                            min_cluster_size=20,
                            cluster_selection_epsilon = .1,
                            min_samples=1,
                            p=None,
                            metric='euclidean',
                            cluster_selection_method='leaf')

# Fit the data
clusterer.fit(embeddings)

# Create soft clusters
soft_clusters = hdbscan.all_points_membership_vectors(clusterer)

# Add the soft cluster information to the data
closest_clusters = [np.argmax(x) for x in soft_clusters]
keys_df['cluster'] = closest_clusters

以下是关键词在各个聚类中的分布情况。通过检查关键词和关键短语在软聚类中的分布,发现总共有 60 个聚类,每个聚类中的元素分布较为均匀,数量从大约 20 个到近 100 个不等。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/07b5e9852a75b59e177d37efc1149ad4.png

提取聚类描述和标签

在对关键词进行聚类后,我们现在准备再次使用 GenAI 来增强和优化我们的发现。在这一步,我们将使用大型语言模型(LLM)分析每个聚类,总结关键词和关键短语,并为每个聚类分配一个简短的标签。

尽管这不是必要的,我选择继续使用相同的 LLM——Zephyr-7B-Beta。如果您需要下载该模型,请参考相关章节。值得注意的是,我会调整提示,以适应此任务的不同特点。

以下函数旨在为每个聚类提取标签和描述,解析输出并将其集成到 pandas 数据框中。

def extract_description(df: pd.DataFrame,
                        n: int     
                        )-> pd.DataFrame:
    """
    Use a custom prompt to send to a LLM
    to extract labels and descriptions for a list of keywords.
    """

    one_cluster = df[df['cluster']==n]
    one_cluster_copy = one_cluster.copy()
    sample = one_cluster_copy.key.tolist()

    prompt_clusters= f"""
    <|system|>
    I have the following list of keywords and keyphrases:
    ['encryption','attribute','firewall','security properties',
    'network security','reliability','surveillance','distributed risk factors',
    'still vulnerable','cryptographic','protocol','signaling','safe',
    'adversary','message passing','input-determined guards','secure communication',
    'vulnerabilities','value-at-risk','anti-spam','intellectual property rights',
    'countermeasures','security implications','privacy','protection',
    'mitigation strategies','vulnerability','secure networks','guards']

    Based on the information above, first name the domain these keywords or keyphrases 
  belong to, secondly give a brief description of the domain.
    Do not use more than 30 words for the description!
    Do not provide details!
    Do not give examples of the contexts, do not say 'such as' and do not list the keywords 
  or the keyphrases!
    Do not start with a statement of the form 'These keywords belong to the domain of' or 
  with 'The domain'.

    Cybersecurity: Cybersecurity, emphasizing methods and strategies for safeguarding digital information
    and networks against unauthorized access and threats.
    </s>

    <|user|>
    I have the following list of keywords and keyphrases:
    {sample}
    Based on the information above, first name the domain these keywords or keyphrases belong to, secondly
    give a brief description of the domain.
    Do not use more than 30 words for the description!
    Do not provide details!
    Do not give examples of the contexts, do not say 'such as' and do not list the keywords or the keyphrases!
    Do not start with a statement of the form 'These keywords belong to the domain of' or with 'The domain'.
    <|assistant|>
    """

    # Generate the outputs
    outputs = generator(prompt_clusters,
                    max_new_tokens=120,
                    do_sample=True,
                    temperature=0.1,
                    top_k=10,
                    top_p=0.95)

    text = outputs[0]["generated_text"]

    # Example string
    pattern = "<|assistant|>\n"

    # Extract the output
    response = text.split(pattern, 1)[1].strip(" ")
    # Check if the output has the desired format
    if len(response.split(":", 1)) == 2:
        label  = response.split(":", 1)[0].strip(" ")
        description = response.split(":", 1)[1].strip(" ")
    else:
        label = description = response

    # Add the description and the labels to the dataframe
    one_cluster_copy.loc[:, 'description'] = description
    one_cluster_copy.loc[:, 'label'] = label

    return one_cluster_copy

现在我们可以将上述函数应用于每个聚类并收集结果:

import re
import pandas as pd

# Initialize an empty list to store the cluster dataframes
dataframes = []
clusters = len(set(keys_df.cluster))

# Iterate over the range of n values
for n in range(clusters-1):
    df_result = extract_description(keys_df,n)
    dataframes.append(df_result)

# Concatenate the individual dataframes
final_df = pd.concat(dataframes, ignore_index=True)

让我们来看一个输出示例。完整的输出列表请参见Google Colab

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/bd5b0a43c213efe4df24d1d6b122cb5f.png

我们必须记住,LLM 由于其固有的概率性,可能会表现出不可预测的行为。虽然它们通常遵循指令,但它们的遵从性并非绝对。即使是对提示或输入文本的微小修改,也可能导致输出结果的重大差异。在extract_description()函数中,我添加了一个功能,在标签描述列中记录响应,以应对那些没有遵循Label: Description格式的情况,如上面第 7 个聚类的异常输出所示。所有 60 个聚类的输出可以在附带的Google Colab笔记本中查看。

第二个观察是,每个聚类都由 LLM 独立解析,并且可能会得到重复的标签。此外,可能会出现从输入列表中提取的重复关键词。

该过程的有效性在很大程度上依赖于 LLM 的选择,使用高性能的 LLM 时,问题较少。输出结果还依赖于关键词聚类的质量以及聚类中是否存在固有的主题。

缓解这些挑战的策略取决于聚类的数量、数据集的特征以及项目所需的准确性。以下是两种选择:

  • 手动修正每个问题,正如我在这个项目中所做的那样。只有 60 个聚类和三个错误的输出,手动调整以修正错误的输出,并确保每个聚类都有唯一的标签。

  • 使用 LLM 进行修正,尽管这种方法无法保证完全无误。

构建知识图谱

上传到图谱的数据

有两个 csv 文件(或如果在单个会话中工作,则是 pandas 数据框)可以从中提取数据。

  • articles - 它包含每篇文章的唯一idtitleabstracttitles_keys,后者是提取的五个关键词或关键短语的列表;

  • keywords - 包含 keyclusterdescriptionlabel 等列,其中 key 包含一个完整的唯一关键词或关键短语列表,其他特征则描述关键词所属的集群。

Neo4j 连接

要构建知识图谱,我们首先需要设置一个 Neo4j 实例,可以选择如 Sandbox、AuraDB 或 Neo4j Desktop 等选项。对于这个项目,我使用的是 AuraDB 的免费版本。启动一个空白实例并下载其凭证非常简单。

接下来,建立与 Neo4j 的连接。为了方便,我使用了一个自定义的 Python 模块,您可以在 utils/neo4j_conn.py 中找到。这个模块包含了连接和与图数据库交互的方法。

# Install neo4j
!pip install neo4j

# Import the connector
from utils.neo4j_conn import *

# Graph DB instance credentials
URI = 'neo4j+ssc://xxxxxx.databases.neo4j.io'
USER = 'neo4j'
PWD = 'your_password_here'

# Establish the connection to the Neo4j instance
graph = Neo4jGraph(url=URI, username=USER, password=PWD)

我们即将构建的图有一个简单的架构,由三个节点和两个关系组成:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/71d425143be8fb32291f099340ec6c1e.png

— 作者图片 —

现在构建图表非常简单,只需两个 Cypher 查询:

# Load Keyword and Topic nodes, and the relationships HAS_TOPIC
query_keywords_topics = """
    UNWIND $rows AS row
    MERGE (k:Keyword {name: row.key})
    MERGE (t:Topic {cluster: row.cluster, description: row.description, label: row.label})
    MERGE (k)-[:HAS_TOPIC]->(t)
    """
graph.load_data(query_keywords_topics, keywords)

# Load Article nodes and the relationships HAS_KEY
query_articles = """
    UNWIND $rows as row
    MERGE (a:Article {id: row.id, title: row.title, abstract: row.abstract})
    WITH a, row
    UNWIND row.titles_keys as key
    MATCH (k:Keyword {name: key})
    MERGE (a)-[:HAS_KEY]->(k)
    """
graph.load_data(query_articles, articles)

查询图谱

让我们查看节点和关系按类型的分布:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/01877831cc27975b9ded0373be022c78.png

我们可以通过计算与它们连接的关键词关联的文章数量,找出我们文章集合中最受欢迎的主题(或集群):

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/cb582327dfffc75475762e64f9ecad96.png

这是与集群 58 对应的 Semantics 节点的快照及其相关的关键词:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/42523efd9df95b35eb5d9ef5c4743273.png

— 作者图片 —

我们还可以通过以下查询来识别标题中常见的词汇:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/0fe0b27744cbcc18c6df860cf2221184.png

结论

我们已经看到,如何通过结构化和丰富一组看似无关的短文本条目。使用传统的 NLP 和机器学习,我们首先提取关键词,然后进行聚类。这些结果为 Zephyr-7B-Beta 进行的精炼过程提供了指导和基础。尽管仍然需要对 LLM 进行一些监督,但初步输出已经得到了显著的增强。知识图谱用于揭示语料库中新发现的连接。

我们的关键收获是,没有任何单一方法是完美的。然而,通过战略性地结合不同的技巧,认识到它们的优缺点,我们可以取得更优的结果。

参考文献

Google Colab 笔记本和代码

数据

技术文档

博客与文章

  • Maarten Grootendorst,介绍 KeyLLM——使用 LLM 进行关键词提取,数据科学前沿,2023 年 10 月 5 日。

  • Benjamin Marie,Zephyr 7B Beta:一个好老师就是你所需要的一切,数据科学前沿,2023 年 11 月 10 日。

  • H4 团队,Zephyr:LM 对齐的直接蒸馏,技术报告,arXiv: 2310.16944,2023 年 10 月 25 日。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值