TowardsDataScience-2023-博客中文翻译-二十四-

TowardsDataScience 2023 博客中文翻译(二十四)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

如何在 ChatGPT 时代有效开始编码

原文:towardsdatascience.com/how-to-effectively-start-coding-in-the-era-of-chatgpt-cfc5151e1c42

一些使用 ChatGPT 学习编程的建议,同时实际进行学习

Federico TrottaTowards Data Science Federico Trotta

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

--

图片由 StockSnap 提供,来自 Pixabay

如果你想开始编程但担心人工智能会抢走你的工作,那么这篇文章绝对适合你。特别是如果你在考虑换职业成为软件工程师或数据专业人士,我真的能感受到你现在的不安。

我大约在三年前,当时我 30 岁,没有任何经验,这帮助我找到了我的第一份 IT 远程工作。

现在,3 年前 ChatGPT 并不存在,我知道你可能会有这样的疑问:“如果我花几个月时间学习和实践编程,结果却因为人工智能找不到实际工作怎么办?”。

好吧,让我告诉你一件事:不要害怕最近大科技公司的裁员:这些裁员与人工智能的增长并没有直接关系。而且,没有人真正知道人工智能是否会在 5 年内取代开发人员,所以我的看法是:人工智能会取代那些实际上不能编程的开发人员。因此,如果你有效地学习如何编码,你会发现人工智能是一个帮助你加快日常任务的工具,而不是一个会取代你的敌人。

因此,在这篇文章中,我们将看到,如果你今天打算开始编程,无论你多大年龄或当前的职业状态如何,ChatGPT 实际上可以为你带来额外的价值。

这就是你在这里会发现的内容:

**Table of Contents:** 
Rule number 1: how to correctly ask questions to ChatGPT
Rule number 2: always refer to the documentation
Rule number 3: troubleshooting and debugging
Examples in Python

规则第 1 条:如何正确向 ChatGPT 提问

首先要学习的是如何正确地向 ChatGPT 提问。

当我开始学习编程时,我认为开发者是那些从五岁开始编程、拥有丰富编程知识的人,他们心中装满了所有需要的代码。

你知道吗?我之前的想法完全错误。这里有一个严酷的事实:

  1. 许多开发者在他们的 20 岁和 30 岁时开始学习编程。

  2. 大多数开发者通过在 Google 和 StackOverflow 上搜索信息来工作(如今,他们也向 ChatGPT 提问)。

所以,首先,如果你现在刚开始编程,不要羞于花大量时间在 Google、StackOverflow 或 ChatGPT 上。这是完全正常的。你只需要知道如何正确地使用这些资源,因为你永远不会记住所有你需要的代码(你只会记住你最常用的方法和函数,当然)。

无论如何,今天和 90 年代的区别在于,90 年代互联网几乎不存在,所以开发者没有像今天这样通过全球社区、论坛、网站和 AI 获得的支持。

这导致开发者不得不艰难地学习编程,通常需要花费数天时间来解决一个问题(找出错误或找到合适的代码)。

现在,你有一个绝佳的机会:你可以 24/7 使用一位高级开发者,那就是 ChatGPT,但你需要像对待高级开发者一样对待它,而不是像对待神谕一样。

所以,这里有一些关于如何正确地询问 ChatGPT 的指导原则,适合今天刚开始编程的人:

  1. 首先学习基础知识。在使用 ChatGPT 之前,我的建议是你需要首先学习基础知识,原因很简单:ChatGPT 经常会给你需要重构的代码,无论是什么原因。这意味着你需要具备一定的编程知识:你不能仅仅让 ChatGPT 为你创建一些代码来解决实际问题。将会被 AI 替代的开发者是那些没有(或知识贫乏的)编程基础的人。所以,在提出问题之前,先报一个课程并开始实践吧。

  2. 提出问题,然后学习。当我们还是孩子时,我们非常好奇,我们的问题让我们成长,因为我们对答案真的感兴趣。这就是我们作为软件专业人员应始终保持的心态:仅仅得到一个答案并复制粘贴并不会让我们学会编程。如果我们从 ChatGPT 获得的代码能够工作,你应该至少要求它解释使用的代码及其原因。作为一个初学者,你的角色实际上是学习,以便将来你可以减少对 ChatGPT 的依赖,更加独立:这就是为什么你应该始终要求代码解释的原因。

  3. 解决实际问题。在编程时,我们可以用不同的方法解决同一个问题:这就是为什么仅仅让 ChatGPT 生成一些随机代码没有任何意义的原因。我们绝对需要询问它与我们正在做的项目或需要解决的问题相关的内容。

  4. 不要盲目相信它。作为一个生成性 AI 系统,ChatGPT 容易出错。虽然今天使用 GPT-4 模型我们看到了很好的改进,但代码中仍然存在错误。此外,错误通常是由于提示可能不完整或某种程度上具有误导性。这就是为什么,即使代码有效,你也不能盲目相信答案,需要深入理解你获得的代码。

规则第 2 条:始终参考文档

编码时的学习流程。图像来源:Federico Trotta。

我创建了上面的图片用于几个月前我在 LinkedIn 上写的一些内容(哦,顺便说一下:这是我的个人资料如果你想联系我)。

故事的寓意是:无论你在哪里寻找解决编码问题的信息,你始终需要阅读文档。永远。

这对于我们开发者/数据专业人员来说是福音。我们总是需要阅读文档。

实际上,如果我们找到一个可以解决我们面临问题的代码片段,如果我们不理解这些代码,我们将无法概括我们正在使用的代码。这意味着将来我们将无法解决类似的问题,因为我们没有学到任何东西:我们只是复制和粘贴了代码。

这对于 ChatGPT 尤其如此,特别是因为有时它会发明一些不存在的库。是的,你没看错:有时 ChatGPT 会发明库。

我们必须说,感谢 GPT-4,OpenAI 做了很多改进。但在过去几个月里,我至少遇到过几次 ChatGPT 发明了一些不存在的库。

你知道我们如何查看一个库是否存在吗?有一些可能性:

  1. 你知道自己在做什么并询问 ChatGPT。但这将在你经过几个月的实践并积累一些经验后发生。

  2. 你从 ChatGPT 那里获取代码,阅读它,尝试理解它,询问 ChatGPT 关于使用的解释,在线查阅文档却找不到参考。

  3. 你尝试代码并遇到错误,你会通过实际经验学到教训。但不要感到羞愧:我也遇到过这种情况。

记住:急于尽快解决问题没有什么不好。所以,你甚至可以尝试代码并遇到错误。实际上,错误甚至可能是由于不好的提示,这种情况非常频繁。

无论如何,记住:如果你的工作已经完成,因为你解决了实际问题,那么,请:去阅读文档以获取更多理解。你也可能发现你的问题可以用更少的代码解决。

规则第 3 条:故障排除和调试

ChatGPT 是一个很棒的工具,即使是在故障排除和调试你的代码时。然而,即使在这种情况下,你也必须学会如何正确使用它,如果你刚刚开始学习编码。

首先,如果你使用的是像 VS CODE 或 PYCHARM 这样的 IDE 编程,那么使用它们的调试功能。它们集成了调试功能,你只需按下一个按钮,它们就会调试你的代码,显示代码中的(可能的)问题。

在这个阶段,甚至在后面(我仍在使用这种方法),最好的办法是遵循这条路径:

  1. 编写你的程序。

  2. 在 IDE 中调试你的代码。

  3. 如果有警告从调试器中获得,请阅读它们,如果某些功能不起作用。

  4. 如果你不知道如何解决它们,可以将警告粘贴到 Google 上。

  5. 如果谷歌搜索没有解决你的问题,可以将警告粘贴到 ChatGPT 上。

  6. 如果你仍未解决问题,将完整代码粘贴到 ChatGPT 中。

  7. 如果前一个点解决了你的问题,请查看文档。

我会不断重复:我们总是需要参考文档,当谷歌或 ChatGPT 解决了我们代码中的问题时。

所以,请:不要盲目将代码粘贴到 ChatGPT 中,要求查找代码中的问题:首先,努力自己找到它们,因为这是你学习的唯一途径。没有其他学习方法,我知道你也知道这一点。

最后,重要说明:记住,ChatGPT 在云端运行,并且它可能会使用你的提示来训练其算法(根据最近的数据隐私使用更改,你应该能够更改设置,选择不允许 OpenAI 使用你的提示来训练其算法)。

然而,需要记住的是,不要将包含敏感信息的代码粘贴到 ChatGPT 中进行调试:由于数据泄露和其他相关安全问题,最好保护自己。

Python 中的示例

现在,让我们看看一些我们应该在 ChatGPT 上写的提示,以学习如何编程。

我问了 ChatGPT 以下问题:“我们如何在 Python 中打印?

这是答案:

在 Python 中,你可以使用print()函数在控制台或终端上显示输出。print()函数接受一个或多个参数,并将它们打印为文本。

这里有几个使用print()函数的示例:

打印简单的字符串:

print("Hello, world!")

>>>

Hello, world!
  1. 打印变量:
name = "Alice"
age = 25
print("My name is", name, "and I am", age, "years old.")

>>>

My name is Alice and I am 25 years old.
  1. 打印多个用特定字符分隔的参数:
print("One", "Two", "Three", sep="-")

>>>

One-Two-Three
  1. 使用 f-strings 打印格式化输出(Python 3.6 及以上):
name = "Bob"
age = 30
print(f"My name is {name} and I am {age} years old.")

>>>

My name is Bob and I am 30 years old.

这些只是展示print()函数用法的一些示例。你可以将各种类型的对象作为参数传递给print()函数,它会自动将它们转换为字符串表示形式,然后进行打印。

现在,有一个问题:你会使用哪种方法?

正是这里出现了问题!我们做的提示太笼统,所以 ChatGPT 给了我们一个泛泛的回答。

所以,这里有另一个教训:提示必须非常具体。例如:“我在 Python 中有一个包含一些字符串的列表:我如何有效地打印它们?”

这是答案:

要在 Python 中有效地打印一个字符串列表,你可以使用 join() 方法与 print() 函数结合使用。join() 方法将列表中的所有字符串连接成一个单独的字符串,使用指定的分隔符。然后你可以将这个连接后的字符串传递给 print() 函数以显示它。

这里有一个例子:

my_list = ['Hello', 'World', 'Python']

# Using join() to concatenate the strings in the list with a space separator
result = ' '.join(my_list)

# Printing the result
print(result)

>>>

Hello World Python

在上面的例子中,join() 方法使用空格 ' ' 作为分隔符来连接 my_list 中的字符串。然后使用 print() 函数来显示连接后的字符串。

通过使用 join() 方法,你可以避免多次调用 print() 函数的开销,从而实现更高效的字符串连接和打印。

这里我们来了:有了具体的问题,我们得到了具体的答案。

现在,你知道我们需要做什么了吗?是的,你知道:如果我们不知道它是如何工作的,我们需要向 ChatGPT 询问更多关于 join() 方法的解释。我们可以在同一个聊天中这样做:“join() 方法是什么,它是如何工作的?

这里是答案:

join() 方法是 Python 中的一个字符串方法,用于将可迭代对象(如列表、元组或集合)的元素连接成一个单独的字符串,使用指定的分隔符。它返回一个新的字符串,包含所有连接在一起的元素。

使用 join() 方法的语法如下:

separator.join(iterable)

在这里,separator 是将元素连接起来的字符串,iterable 是要连接的元素集合。

join() 在分隔符字符串上被调用时,它遍历可迭代对象的元素并将它们连接起来,在每对元素之间插入分隔符字符串。分隔符不会添加到结果字符串的开头或结尾。

这是一个例子,说明 join() 方法是如何工作的:

my_list = ['Hello', 'World', 'Python']

# Using join() to concatenate the strings in the list with a space separator
result = ' '.join(my_list)

print(result)

>>>

Hello World Python

在上面的例子中,join() 方法在分隔符字符串 ' ' 上被调用。它遍历 my_list 中的元素并将它们连接起来,在每对元素之间插入一个空格。结果字符串 'Hello World Python' 被打印出来。

值得注意的是,join() 方法是一种比在循环中使用 + 运算符连接多个字符串更高效的方法。这是因为 join() 内部优化了连接过程,从而提高了性能,特别是在处理大量字符串时。

现在,在这一点上,你应该做其他两件事:

  1. 在 Google 上搜索“join() Python”,查看其他一些例子并阅读文档。

  2. 正如我们所见,ChatGPT 告诉我们这种方法比使用 + 运算符和循环解决这个问题更高效。你应该继续聊天并请求一个例子(然后,阅读可能会随下一个例子附带的文档)。

结论

希望你觉得这篇文章对如何开始编程之旅有用,尤其是借助 ChatGPT。

记住:不要把任何事情视为理所当然。用它来学习,而不是作为预言。解决问题后,写一个具体的提示,阅读文档,学习代码,并在可能的情况下,练习你得到的代码。

免费 Python 电子书:

开始学习 Python 数据科学但遇到困难?订阅我的通讯并获得我的免费电子书:这将为你提供正确的学习路径,帮助你通过实践经验学习 Python 数据科学。

享受了这个故事吗?每月只需 5$ 通过我的推荐链接 成为 Medium 会员:我将获得少量佣金,而不会增加你的费用:

[## 通过我的推荐链接加入 Medium - Federico Trotta

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

federicotrotta.medium.com](https://federicotrotta.medium.com/membership?source=post_page-----cfc5151e1c42--------------------------------)

如何有效地构建数据科学项目

原文:towardsdatascience.com/how-to-effectively-structure-data-science-projects-85f717e65c75?source=collection_archive---------5-----------------------#2023-08-21

使用 PSW 工具的简要说明

Radmila M.Towards Data Science Radmila M.

·

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

--

图片来源:Ross SneddonUnsplash

介绍

我曾经参与过大量的数据科学项目,帮助客户处理各种回归和分类任务:相似模型和推荐系统、自然语言处理问题、预测分析,仅举几例。

客户往往忙于日常工作,因此他们无法安排长时间的会议来详细描述他们希望在项目中看到的内容。因此,拥有一个详细且结构良好的议程是非常重要的。

在与专家的电话会议中,我经常使用PSW(问题陈述工作表)方法来充分理解客户的需求。

PSW 是一个业务任务描述模板,主要用于咨询,但也非常适合几乎任何 IT 项目。

在这篇文章中,我将展示如何使用 PSW 工具来更好地理解数据科学项目的关键点,并通过使客户会议更具一致性和简洁性,从中获得最大收益。

PSW 通常包含六个主要部分:

  • 背景。这个部分填充了关于项目当前状态和导致其启动的挑战的简要信息。

  • 成功标准。在这里,重要的是要弄清楚如何评估解决项目任务的可能决策,并将所有标准按重要性进行排序。

  • 解决方案空间的范围。这个部分提供了对分析边界的理解。最好与客户明确哪些领域不应再考虑在内。

  • 解决方案空间的约束。在这里,我们列出决策空间中可能出现的障碍。这可能是要使用的特定编程语言、某些模型要求,或严格的预算限制。

  • 利益相关者。这是一个将影响决策和项目成功的人员名单。这些人可以分为决策者、帮助者和阻碍者。

  • 关键洞察来源。这个部分旨在回答“我们从哪里获取数据来解决项目任务?”的问题。

    将信息来源分成相关组更为理想,例如:

    1. 书籍、相关研究文章;

    2. 最新的行业报告;

    3. 类似的项目等。

接下来,我将逐一考虑每个部分,并举例说明它们可以填充的信息。

1. 背景

Keith Misner拍摄,照片来源于Unsplash

这是第一个部分,通常在互致问候之后自然发生。在这里,我经常要求客户提供关于项目的更多背景信息:它为何出现,它对公司有何重要性等。一方面,这些细节将为深入探讨项目的细节打下坚实的基础,另一方面——有助于制定项目的主要目标。

如果你能用一句话定义项目目标,那么你对项目的理解就非常到位。

这里是一个基于客户提供的信息的背景部分的典型示例:

任何移动应用程序都必须考虑用户需求,以便为他们提供最便捷的解决方案。用户进入应用程序的目的是明确的,他们会执行某些操作。但通过在屏幕上添加推荐,可能会缩短这个过程,例如,使向其他用户进行交易的速度更快。这就是基于机器学习(ML)的推荐系统可以提供帮助的地方。

作为项目的一部分,有必要根据每个用户的转账数量对联系人进行排名。已经有尝试训练模型,所以基线已经存在,但现在的任务是提高其准确性 5%或更多,同时应用机器学习推荐算法。

如你所见,背景部分有助于将项目任务融入业务的整体背景中(使应用程序更加用户友好),并且在必要时,可以根据全球目标(应用基于 ML 的推荐系统)调整这些任务。

2. 成功标准

图片由Guille Álvarez拍摄,发布在Unsplash

在这里,你可以询问客户项目将根据哪些主要参数进行评估以及用于确定“项目成功” 的标准是什么。这些可以是财务指标(如降低成本)和非财务指标(例如应用程序的活跃用户数量、构建模型的准确性等)。除了具体标准外,了解客户的所有不可量化的愿望也很重要。也许你的客户会通过你提出的措施来彻底改变他们的企业文化(为什么不呢?!)。

继续以移动应用程序和推荐系统为例,以下是这个项目可能的成功标准:

  1. 选择适当的机器学习(ML)模型用于系统的解释。

  2. 基线模型已经改进了 5%或更多。

  3. 模型的运行速度从启动到结果接收不超过 6 小时。

  4. 模型性能在可用数据上进行检查——测试集上的准确性应超过 85%。

3. 解决方案空间范围

图片由Nicolas Lobos拍摄,发布在Unsplash

在这里,重要的是要了解项目的边界。通常,这一部分 PSW 包含项目的简要背景——项目主题为何现在重要且相关,市场上已经存在什么解决方案和基准,并且这些解决方案和基准可以如何进一步修改以满足客户的要求。

如果谈论推荐系统,需要注意的是创建推荐系统有几种方法。

我们可以考虑基于内容(content-based)或知识(knowledge-based)的方法,使用协同过滤(collaborative filtering)或混合方法。混合系统结合了多种系统的优势,使其成为一个一站式的推荐工具。

4. 解决空间中的约束

Joshua HoehneUnsplash 上的照片

在这一块中,我们要概述可接受和不可接受的解决方案范围。你可以直接询问客户:“我们的限制是什么?”这个问题可能会有所帮助。在这里,你可能会听到有关方法/技术/编程语言的限制。对于我们分析的项目,存在与使用开源数据集进行机器学习模型训练和结果可复现性相关的限制。后者可以通过提供详细项目描述的README文件来实现。

1. 第三方资源使用的限制:在开发推荐系统时,切勿使用开放数据进行模型预训练。

2. 实现方法的可复现性:在另一台 PC 上重新启动模型时,应该得到类似的结果。

注意

PSW 中的第 3 和第 4 块可能会让人困惑。究竟如何理解解决空间和约束之间的区别呢?让我们来看一个例子。

想象一下你发现了一封旧信,信中提到你祖父多年前在家里的后院藏了一箱黄金。他没有具体说明藏在哪里,所以整个后院就是解决空间。一旦你读了这封信,你希望尽快找到宝藏,并考虑使用电梯来寻找。不幸的是,后院被围栏围住,无法通过电梯到达。在这种情况下,不能使用电梯就是解决空间内的一个明确约束。

Jean-Frederic FortierUnsplash 上的照片

5. 利益相关者

airfocusUnsplash 上的照片

PSW 的这一块提供了在实施项目时应考虑的人的意见。通常,利益相关者是对项目结果感兴趣的人。他们可能是项目团队成员、项目经理、执行官、项目投资者、客户和最终用户。

利益相关者是会在项目生命周期的任何阶段受到影响的人,他们的意见可以直接影响结果。在开发推荐系统的情况下,其集成到应用程序中将对以下两大群体有利:

  1. 使用该系统的移动应用用户可以节省时间。

  2. 应用程序开发人员通过使他们的产品更具功能性,从而增加用户忠诚度。

6. 主要见解来源

Susan Q Yin拍摄的照片,来源于Unsplash

通常,这个块包含任何相关信息,以帮助全面理解主题——例如,开源 API 库的链接 [1]、教程 [2]、代码库、研究文章等。

在这里,询问客户之前在该项目上已经做了什么是至关重要的。如果有的话,不要犹豫,请他们分享哪些方面做得好,哪些方面在项目初期实施得不佳。这将为你提供有关可能的进一步行动和项目发展方向的一些线索。

对于考虑中的 Data Science 项目及推荐系统,使用任何材料,包括机器学习和预测分析领域的文章,例如,一篇关于该行业最新成就的综合评论可能是一个很好的起点 [3]。

关注解决类似排名和推荐问题的最新方法。

结论

我希望这篇帖子中的信息能帮助你为任何客户会议做好充分准备,并提出适当的问题。

以下是我对 PSW 方法的主要见解:

  1. 在应用 PSW 时,不要忘记记录客户告诉你的所有时刻。我通常将所有信息汇总到一个后续跟进文件中,并在 Data Science 项目实施过程中使用。

  2. PSW 工具不仅对客户会议有用。此外,它还可以帮助新成员更快地深入 Data Science 项目组,同时向经验丰富的项目组成员提出有价值的问题。

  3. 请记住,虽然 PSW 是一个很棒且易于使用的工具,但它并不是一个万能的“一刀切”解决方案。在某些情况下,它可能不起作用。

一般来说,PSW 方法对于数据科学项目效果良好,在这些项目中,任务有明确的愿景,并且有来自客户的一些输入和初步解决任务的尝试。在这种情况下,客户可以与我分享这些信息,以便我们共同利用 PSW 解决他们的挑战。然而,在项目特点有许多未知见解和不明确的前景时,应用 PSW 工具将会困难。例如,如果客户要求对尚未开始的数据科学项目进行一些创意生成,PSW 方法将不适用,这时需要选择其他方法。

感谢阅读,祝您的项目顺利!

参考文献列表

  1. 提供推荐的推荐系统 REST API: github.com/recommender-system/reco-api?ysclid=lll99344l9788228410

  2. 初学者教程:Python 中的推荐系统: www.datacamp.com/tutorial/recommender-systems-python

  3. 关于多模态推荐系统的综合调查:分类、评估及未来方向: arxiv.org/pdf/2302.04473.pdf

如何在 Python 中有效使用 lambda 函数作为数据科学家

原文:towardsdatascience.com/how-to-effectively-use-lambda-functions-in-python-as-a-data-scientist-fd6171554053

对其语法、功能和在数据科学中的适用性的介绍

Thomas A DorferTowards Data Science Thomas A Dorfer

·发表在 Towards Data Science ·阅读时间 7 分钟·2023 年 3 月 1 日

--

图片由作者提供。

介绍

Python 中的 lambda 函数是小型的、匿名(或无名)函数,其语法比常规 Python 函数更简洁。

在数据科学领域,lambda 函数常与高阶函数一起使用。高阶函数是指接受一个或多个函数作为参数,或者返回一个函数作为结果的函数。常见的例子包括 map()filter()reduce()

在深入应用之前,我们先来看看 lambda 函数的语法。

语法

要在 Python 中使用 lambda 函数,需要以下四个组件

图片由作者提供。

  • lambda: 定义 lambda 函数的关键字。

  • 参数: Lambda 函数可以接受一个或多个参数。如果参数数量超过一个,它们需要用逗号分隔。

  • 冒号: 作为函数参数和表达式之间的分隔符。

  • 表达式: 一个有效的 Python 表达式,它将被评估并作为函数的结果返回。

应用此语法,我们可以产生一个简单的 lambda 函数示例,该示例接受两个输入参数 xy,并返回它们的和:

>>> (lambda x, y: x + y)(3, 5)
8

通过在 lambda 函数周围使用括号,我们可以利用一种称为立即调用函数表达式的原则,该原则允许我们定义并立即执行一个函数。

由于 lambda 函数是一个表达式,我们也可以给它赋予一个名称。以下是与上述相同函数的示例,但这次我们将其命名为 func

>>> func = lambda x, y: x+y
>>> func(3, 5)
8

现在我们对 lambda 函数的语法有了更好的理解,让我们看看它们常被应用的一些场景。

在数据科学中的适用性

在数据科学领域,lambda 函数最常与高阶函数一起使用,高阶函数接受一个或多个函数作为参数,或者用于嵌套函数称为闭包 —— 更多内容会在后面讲到。

现在让我们看一些具体的例子。在实际操作中,我们将考察 lambda 函数在 Python 列表以及 Pandas 数据框中的适用性。

数据转换

想象一下,你有一份极度偏斜的数据,希望对其进行对数转换以减少偏斜。或者,你有一些以公里为单位的数据,希望将其转换为海里。像这样的操作可以通过应用 lambda 函数轻松实现。

这是一个如何使用 lambda 函数将以公里为单位的数据转换为海里单位的简单例子:

# list
>>> data_km = [1, 2, 3, 4, 5]
>>> data_nm = list(map(lambda x: x / 1.852, data_km))
>>> data_nm
[0.5399568034557235,
 1.079913606911447,
 1.6198704103671706,
 2.159827213822894,
 2.6997840172786174]
# Pandas DataFrame
>>> import pandas as pd 
>>> df = pd.DataFrame({'data_km': [1, 2, 3, 4, 5]})
>>> df['data_nm'] = df['data_km'].apply(lambda x: x / 1.852)
>>> df
  data_km   data_nm
0       1   0.539957
1       2   1.079914
2       3   1.619870
3       4   2.159827
4       5   2.699784

请注意,当应用于列表时,我们使用内置的map函数,该函数接受一个函数(在我们的例子中是 lambda 函数)和一个可迭代对象(在我们的例子中是名为 data_km 的列表)作为参数。

过滤

过滤在你只对数据的某一特定子集感兴趣时非常有用。假设你有一些包含年龄列的医疗数据,你只对年龄超过 75 岁的老年患者感兴趣。使用内置的filter函数,它也接受一个函数和一个可迭代对象作为输入参数,可以按如下方式完成:

# list
>>> data_all = [84, 61, 27, 75, 90]
>>> data_elderly = list(filter(lambda x: x > 75, data_all))
>>> data_elderly
[84, 90]
# Pandas DataFrame
>>> import pandas as pd 
>>> df = pd.DataFrame({'data_all': [84, 61, 27, 75, 90]})
>>> df_elderly = df[df['data_all'].apply(lambda x: x > 75)]
>>> df_elderly
  data_all
0       84
4       90

排序

Lambda 函数可以用来根据某些标准对数据进行排序。例如,你可以使用 lambda 函数根据特定列对列表或数据框进行排序。

在下面的例子中,我们有一个名字列表,希望按姓氏进行排序。这可以按如下方式完成:

# list
>>> names = ["James Watt", "Charles Darwin", "Carl Friedrich Gauss"]
>>> names.sort(key=lambda x: x.split()[-1])
>>> names
['Charles Darwin', 'Carl Friedrich Gauss', 'James Watt']
# Pandas DataFrame
>>> import pandas as pd
>>> names = ["James Watt", "Charles Darwin", "Carl Friedrich Gauss"]
>>> df = pd.DataFrame({'names': names})
>>> df_sorted = df.sort_values(by="names", key=lambda x: x.str.split().str[-1])
>>> df_sorted
                 names
1       Charles Darwin
2 Carl Friedrich Gauss
0           James Watt

分组和聚合

在组级别分析数据通常可以帮助我们获取有关组间差异的有意义的见解。Pandas 数据框,加上groupbyagg函数,是一种常用的框架。

假设你有一些临床数据,包含参与了一个实验的信息,该实验分为三组:A、B 和 C,每组包含略有不同的程序。在完成实验并收集相应数据后,你希望查看分配到每个实验组的患者比例:

>>> import pandas as pd
>>> group = ['A', 'B', 'A', 'A', 'B', 'C']
>>> age = [84, 61, 27, 75, 90, 56]
>>> weight = [164, 189, 135, 121, 172, 138]
>>> df = pd.DataFrame({'group': group, 'age': age, 'weight': weight})
>>> df_grouped = df.groupby('group').apply(lambda x: x['group'].count() / df.shape[0])
>>> df_grouped
group
A    0.500000
B    0.333333
C    0.166667
dtype: float64

这项分析将告诉你,大多数患者被分配到了实验组 A(50%),其次是组 B(33.33%)和组 C(16.67%)。

此外,groupby 可以与 agg 一起使用,以便对不同的列应用不同的聚合函数。在下面的示例中,我们对组级的 平均 年龄感兴趣,但对患者的 中位数 体重感兴趣。

>>> import pandas as pd
>>> group = ['A', 'B', 'A', 'A', 'B', 'C']
>>> age = [84, 61, 27, 75, 90, 56]
>>> weight = [164, 189, 135, 121, 172, 138]
>>> df = pd.DataFrame({'group': group, 'age': age, 'weight': weight})
>>> df_grouped = df.groupby('group').agg({"age": lambda x: x.mean(),
                                          "weight": lambda x: x.median()})
>>> df_grouped
         age   weight
group  
    A   62.0    135.0
    B   75.5    180.5
    C   56.0    138.0

闭包

由于其简洁的语法,lambda 函数通常用于名为 闭包 的嵌套函数中。简而言之,闭包允许访问外部函数的变量,即使外部函数已经结束。

为了说明这一点,让我们使用闭包创建一个自定义定义的乘数:

>>> def multiplier(factor):
        return lambda x: x * factor
>>> double = multiplier(2)
>>> triple = multiplier(3)
>>> double(10)
20
>>> triple(2)
6

在这里,函数 multiplier 返回一个“闭包”变量 factor 的 lambda 函数。具体来说,当 multiplier 使用参数 factor 被调用时,它会创建一个新函数,该函数在其定义中包含对 factor 的引用。这意味着当新函数—例如存储在 doubletriple 等变量中—稍后用某个参数 x 被调用时,函数仍然可以访问传递给 multiplierfactor 的值。

另一个这个概念有用的应用是解决二次函数问题。这些函数通常用于物理学中如计算抛射物的轨迹,但也经常用于数据分析、优化问题和信号处理。让我们看一个假设的二次函数示例,该函数描述了一个假设的抛射物的运动。abc 的任意输入值分别选择为 -50、450 和 0。

>>> def quad_func(a, b, c):
        return lambda x: a * x**2 + b * x+ c
>>> f = quad_func(-50, 450, 0)
>>> f(0)
0
>>> f(2)
700
>>> f(4)
1000
>>> f(6)
900
>>> f(8)
400

如果我们将时间值在 0 到 9 之间绘制图形,我们会得到如下结果:

作者提供的图片。

结论

我希望这篇文章让你更好地理解了 lambda 函数如何在数据科学应用中,如数据转换、过滤、排序、分组和聚合,以及闭包等方面有效使用。

更多资源

喜欢这篇文章吗?

让我们保持联系!你可以在 TwitterLinkedIn 上找到我。

如果你喜欢支持我的写作,你可以通过一个Medium 会员来做到这一点,它为你提供了对我所有故事以及 Medium 上成千上万其他作者的故事的访问权限。

使用最新技术微调你的开源 LLM

原文:towardsdatascience.com/how-to-efficiently-fine-tune-your-own-open-source-llm-using-novel-techniques-code-provided-03a4e67d1b48

在这篇文章中,我调整了一个基础的 LLama2 LLM,以输出 SQL 代码。我使用了参数高效微调技术来优化这一过程。

Christopher Karg数据科学前沿 Christopher Karg

·发表于数据科学前沿 ·13 分钟阅读·2023 年 12 月 15 日

--

来源: www.pexels.com/photo/calm-body-of-lake-between-mountains-346529/

上一篇文章中,我开始阐述为何你应该考虑训练自己的 LLM。我还简要介绍了硬件要求以及优化训练和推理的方法。在本文中,我将详细介绍如何微调一个开源 LLM,并提供代码片段供你跟随和重现结果。我们将调整一个 Llama2–7B 模型,使其根据自然语言输入提供 SQL 输出——换句话说,模型将把我们用自然语言提出的问题转换为 SQL 查询:

“在 11 月份,有多少客户决定购买鸡蛋?”

到一个 SQL 查询,该查询获取相应的结果:

SELECT COUNT(DISTINCT customer_id) AS num_customers
FROM purchases
WHERE product_name = 'eggs'
AND EXTRACT(MONTH FROM purchase_date) = 11;

在每种情况下,数据库(DB)的架构将作为 LLM 的工作上下文提供:

CREATE TABLE purchases (
    purchase_id INT PRIMARY KEY,
    customer_id INT,
    product_name VARCHAR(255),
    purchase_date DATE
);

我们将在调整过程中使用这个数据集。虽然这篇文章主要集中在实现上述任务上,但方法将以一种你可以调整以满足你要求的方式提供。

在这篇文章中,我将使用Google Colab来微调 LLM。我们将使用之前提到的know_sql 数据集(OpenRAIL 许可证)。我们还将使用axolotl 框架来处理微调过程。他们在 GitHub 页面上有一些很好的文档。与其编写约 100 行代码来手动处理微调过程,axolotl 允许我们简单地编辑相应模型的 YAML 配置文件。我将在本文中详细介绍具体过程,但如果有任何不清楚的地方,建议查看 axolotl 文档。

微调 LLM 的过程比仅在现有 LLM 上运行推理要计算密集得多(再此提供链接到我的上一篇文章,涵盖了运行推理和优化现有 LLM 的各个阶段)。因此,我们无法使用 Google Colab 的免费版,因为任务需要NVIDIA 的 Ampere GPU 架构(或更高版本)。此架构可通过 Google Colab 上的“A100 GPU”运行时类型获得。有关与运行时类型交互的更多信息,我建议查看我的上一篇文章

在这个阶段,我应该提到 Google 提供 A100 GPU 是“先到先得”或“视情况而定”。我发现有时获取 A100 GPU 的访问权限比较麻烦,因为它们不常有。如果不可用,你将自动切换到一个较旧的架构,这不足以完成这个任务。你的体验可能会有所不同。如果你不想等待正确的 GPU 变得可用,我建议你查看我在上一篇文章中提到的一些硬件建议。

工作流程如下:

· 设置 Google Colab 并挂载 GDrive(我们需要挂载 GDrive,因为需要从 Colab 笔记本中访问某些文件)。

· 安装依赖项

· 通过 HuggingFace CLI 进行身份验证(如果你从未获得 Meta 授权使用 Llama-2 LLMs,我再次建议你查看我的上一篇文章,该文章涵盖了获取访问权限的过程)。

· 加载数据集

· 将 axolotl 仓库克隆到 Gdrive

· 更新 axolotl 配置文件

· 微调 Llama-7B 模型(这大约需要 2 小时。你的体验可能会有所不同)。

· 在微调后的模型上运行推理

在进入代码之前,我希望澄清微调到底是什么,以及你可能何时考虑微调你的 LLM。

微调可以通过多种方式进行。你选择的方法取决于你可用的资源和你想要实现的任务。可能‘基础’模型已经能产生令人满意的结果,这种情况下你可以完全跳过微调过程。

再次,我的 上一篇文章 讨论了为什么你可能会选择使用开源的 LLM,而不是像 OpenAI 的 ChatGPT 这样的产品。考虑是否应微调 LLM 的一个通用原则如下:

· 我的任务是否需要领域特定的知识/专业技能?

· 我是否会向我的 LLM 输入专有数据?

· 我是否有足够的领域特定数据来训练/调整我的模型?

· 我是否有足够的时间和计算资源来完成任务?

如果你对一个或多个以上的问题回答是,你应该考虑微调 LLM。如果你对所有问题都回答是,可能值得从头开始训练一个 LLM。可以采用逐步的方法:初始使用一个‘基础’ LLM,然后尝试一个微调过的 LLM,再尝试一个你从头训练的 LLM。每一步的成本(时间和计算资源)逐渐增加,只有当你当前的 LLM 未能产生令人满意的结果时,才会进入下一步。训练 LLM 从零开始的过程超出了本文的范围,但我很乐意回答你关于这个话题的任何问题。

现在我们已经决定是否微调我们的 LLM,我将详细介绍这个过程实际上包括什么。

我们的‘基础’ LLM 是一个预训练模型,拥有来自初始训练的一组权重和偏置。这些权重和偏置使得模型能够‘学习’到广泛的基本语言模式和一般知识。在微调过程中,我们使用标记的数据集来更新这些权重和偏置。

之前,典型的微调过程如下:

前向传播

· 模型尝试基于输入数据进行预测

损失计算 · 模型计算预测结果的偏差。

反向传播 · 模型基本上是在弄清楚每个参数对损失的贡献程度。

更新参数 · 在这一步,参数将被更新和调整以减少损失。更新的大小(学习率)是一个可以调整的超参数。

重复 · 上述步骤会重复进行一定次数(训练轮次)。通常这个次数非常少。像 BERT 这样的常见 LLM 在经过 2 个轮次的微调后表现最佳。在本文中,我们将仅使用 1 个轮次来微调 Llama2–7B 模型。

随着非常大型 LLM 的出现,已经确定了使这一过程更加高效的新策略。引入 LoRA(低秩适配)和 QLoRA(量化 LoRA)。我们将在后续的 SQL 示例中使用 LoRA,所以我将重点介绍它。这两种策略可以归入参数高效微调(PEFT)的范畴。这些技术使我们能够成功地微调非常大的语言模型,同时最小化执行所需的计算要求。它通过减少微调过程中的可训练参数数量来实现这一点。

尽管减少内存使用、存储成本和推理延迟是使用 PEFT 方法的好处,但需要注意的是,在此过程中训练时间可能会增加,性能可能对超参数选择更加敏感。

我们可以使用各种 PEFT 技术。有关这些技术的更多阅读,我强烈推荐这篇博客文章

使用 LoRA 时,假设现有模型的权重接近最优解。这些权重随后被固定,因此在微调过程中不能再更新。相反,低秩矩阵被引入到我们的大型语言模型(LLM)的每一层中。这些矩阵作为适配器,替代了我们冻结的可训练参数。更新这些低秩矩阵的计算要求显著低于如果我们仍使用原始参数时的计算要求。换句话说,由于原始参数中的冗余以及原始参数与低秩矩阵之间的相关性,原始参数可以被低秩矩阵很好地近似。这在所谓的‘Rank-Deficiency’中得到了体现——相关论文可以在这里找到。

所有这些结果减少了参数开销,从而带来了我之前列出的优势。值得注意的一点是减少了推理延迟。通过将这些低秩矩阵引入我们的模型,我们实际上创建了一个线性设计。与完全微调的模型相比,推理延迟显著减少。这使得该方法适用于实时应用。

我们可以通过引入量化进一步优化推理时间——这是我在上一篇文章中广泛讨论的主题。

Axolotl提供了一个非常易于使用的工具,使我们能够实现上述所有功能。

让我们开始吧。

首先我们将设置我们的 colab notebook。你现在需要选择 A100 GPU 运行时类型。如果你使用的是免费版本,你需要升级到付费版本。请注意,A100 GPUs 可能不可用。我建议运行一个包含“hello world”示例的单元,以确保你不会被转移到较旧的 V100 GPU。如果 A100 GPU 不可用,你可以等待一个可用的 GPU(我不幸的是无法确定 GPU 可用性的高峰和低峰时间)。或者你可以查看我上一篇文章中列出的其他硬件选项。

接下来让我们挂载 Gdrive。你可以通过输入以下代码来完成:

from google.colab import drive
drive.mount('/content/drive')

然后你会收到一个弹出窗口,需要通过你的 Google 账户进行身份验证。我们正在挂载驱动器,因为我们需要处理存储在 Gdrive 中的文件。

接下来,我们将通过 HuggingFace CLI 进行 Llama2–7B 的访问认证。这将是我们将要微调的基础模型。如果你之前从未通过 Meta 的官方网站申请过该模型的访问权限,请参阅我之前的文章中的说明。

要进行认证,请在一个新单元中输入以下代码:

!huggingface-cli login

然后系统会要求你输入访问密钥(我在我的上一篇文章中解释了如何获取此密钥)。

接下来,我们将处理我们的安装。我发现这通常是运行 LLM 实验时最大的障碍。某些包是为特定硬件构建的,如果你没有该特定硬件,可能会很麻烦地处理安装和更新。幸运的是,axolotl 错误输出非常详细,你应该能够相对容易地跟踪任何兼容性问题。如果你按照 axolotl readme 中的说明操作,你应该可以顺利进行。如果你遇到困难,请告诉我。

让我们运行安装,我们将安装正确版本的 CUDA 驱动程序以及 axolotl 库的依赖项。我们还将安装 HuggingFace 的数据集库,以访问我们的 SQL 训练集:

!pip install torch torchvision torchaudio
!pip install "axolotl[flash-attn,deepspeed] @ git+https://github.com/OpenAccess-AI-Collective/axolotl"
!pip install accelerate
!pip install datasets
!pip install torch==2.0.1+cu118 -f https://download.pytorch.org/whl/torch_stable.html
!pip uninstall xformers
!pip install xformers
!pip uninstall flash_attn -y
!pip install flash_attn

接下来我们将把 axolotl 仓库克隆到我们的 Gdrive。如果出现目录不存在的错误,只需使用 Gdrive UI 创建这些目录即可:

!git clone https://github.com/OpenAccess-AI-Collective/axolotl /content/drive/MyDrive/fine_tune_llm/axolotl

运行一些进一步的安装:

!cd /content/drive/MyDrive/fine_tune_llm/axolotl && pip install packaging && pip install -e '.[flash-attn,deepspeed]'

接下来我们将获取数据集并检查其是否正确加载:

ds = datasets.load_dataset('knowrohit07/know_sql')
ds
trn = ds['validation']
trn[4500]

###output###
# {'answer': 'SELECT MAX(field_goals) FROM table_19722233_5 WHERE assists = 27',
# 'question': 'what is the highest number of goals did the player with 27 asists score',
# 'context': 'CREATE TABLE table_19722233_5 (field_goals INTEGER, assists VARCHAR)'}

很好,目前我们已经安装了所有依赖项,克隆了 axolotl 仓库,并获得了访问 Llama-2 7B 模型的授权。在这个阶段,我们需要进一步配置,更新 YAML 文件。Axolotl 使用这个 YAML 文件作为微调模型的指令集。我建议查看一些 Axolotl 提供的示例,这些示例位于克隆仓库时创建的 axolotl 目录中。这将帮助你了解可以更改哪些设置以及可以使用哪些超参数。同样,仓库的 readme 对这里非常有帮助。

以下是我最终的 YAML 配置文件副本:

base_model: meta-llama/Llama-2-7b-hf
base_model_config: meta-llama/Llama-2-7b-hf
model_type: LlamaForCausalLM
tokenizer_type: LlamaTokenizer
is_llama_derived_model: true

load_in_8bit: false
load_in_4bit: true
strict: false

datasets:
  - path: knowrohit07/know_sql
    type: context_qa.load_v2
    train_on_split: validation
dataset_prepared_path: last_run_prepared
val_set_size: 0.01
output_dir: ./qlora-out

adapter: qlora
lora_model_dir:

sequence_len: 2048
sample_packing: false
pad_to_sequence_len: true

lora_r: 32
lora_alpha: 16
lora_dropout: 0.05
lora_target_modules:
lora_target_linear: true
lora_fan_in_fan_out:

wandb_project:
wandb_entity:
wandb_watch:
wandb_run_id:
wandb_log_model:

gradient_accumulation_steps: 4
micro_batch_size: 2
num_epochs: 1
optimizer: paged_adamw_32bit
lr_scheduler: cosine
learning_rate: 0.0002

train_on_inputs: false
group_by_length: false
bf16: false
fp16: true
tf32: false

gradient_checkpointing: true
early_stopping_patience:
resume_from_checkpoint:
local_rank:
logging_steps: 1
xformers_attention:
flash_attention: true

warmup_steps: 10
eval_steps: 20
eval_table_size: 5
save_steps:
debug:
deepspeed:
weight_decay: 0.0
fsdp:
fsdp_config:
special_tokens:
  bos_token: "<s>"
  eos_token: "</s>"
  unk_token: "<unk>"

你会注意到我们可以更新之前文章中讨论的参数(fp16: true),以优化训练过程并限制该模型微调所需的内存。如果你使用的是不同的硬件,建议查看文档和初始化微调过程时的任何错误信息。根据你的目标,axolotl 提供了许多示例配置文件供你使用和调整。

将配置文件保存到之前创建的 fine_tune_llm 目录中,命名为 sql.yml。还需要一个额外的 Python 脚本来处理分词策略。这个脚本也应该保存在 fine_tune_llm 目录中,命名为 context_qa2.py。这是脚本:

"""Module containing the classes for Context QA Prompt Tokenization Strategies"""
from typing import Tuple
from axolotl.prompt_tokenizers import InstructionPromptTokenizingStrategy
from axolotl.prompters import AlpacaPrompter, PromptStyle

# article, unanswerable_question, question, answer
def load_404(tokenizer, cfg):
    return AlpacaMissingInfoContextPromptTokenizingStrategy(
        AlpacaContextPrompter(PromptStyle.CHAT.value),
        tokenizer,
        cfg.train_on_inputs,
        cfg.sequence_len,
    )

def load(tokenizer, cfg):
    return AlpacaContextPromptTokenizingStrategy(
        AlpacaContextPrompter(PromptStyle.CHAT.value),
        tokenizer,
        cfg.train_on_inputs,
        cfg.sequence_len,
    )

class AlpacaContextPrompter(AlpacaPrompter):
    """
    Customized system prompted for concise QA
    """

    system_prompt = (
        "Use the following contextual information to concisely answer the question.\n"
    )
    system_no_input_prompt = (
        "Use the following contextual information to concisely answer the question.\n"
    )

class AlpacaContextPromptTokenizingStrategy(InstructionPromptTokenizingStrategy):
    """
    Tokenization Strategy to combine in-context article with a question and answer
    """

    def parse_instruction_fields(self, prompt) -> Tuple[str, str, str]:
        return (
            prompt["context"] + "\n===\n" + prompt["question"],
            "",
            prompt["answer"],
        )

class AlpacaMissingInfoContextPromptTokenizingStrategy(
    InstructionPromptTokenizingStrategy
):
    """
    Tokenization Strategy to combine in-context article with a question that can't be answered
    from the context and a default response to that effect
    """

    def parse_instruction_fields(self, prompt) -> Tuple[str, str, str]:
        return (
            prompt["context"] + "\n===\n" + prompt["unanswerable_question"],
            "",
            "The context provided does not contain any information about your inquiry. "
            "Therefore, I'm unable to answer your question based on the given context.",
        )

很好——现在我们应该已经准备好初始化微调过程。我们的依赖项已经安装好,配置文件也都在 Gdrive 的各自位置中。你应该有一个看起来像这样的 fine_tune_llm 文件夹:

使用 Axolotl 微调 LLama2–7B 的目录布局

.yml 文件是我们的配置文件。.py 文件是处理分词的脚本。axolotl 目录是我们之前从仓库克隆的目录。

现在我们需要做的就是运行:

!accelerate launch -m axolotl.cli.train /content/drive/MyDrive/fine_tune_llm/sql.yml

这将初始化使用 sql.yml 配置文件来微调我们指定的模型。这个过程对我来说大约花了 2 小时。你的情况可能会有所不同。如果此阶段出现任何错误,很可能是由于依赖错误。我遇到过几次问题,需要手动安装正确的 cuda 驱动程序和 flash_attn:

!pip install torch==2.0.1+cu118 -f https://download.pytorch.org/whl/torch_stable.html
!pip uninstall flash_attn -y
!pip install flash_attn

很好——我们使用了 Axolotl 提供的相当简单的配置来初始化我们 LLM 的微调。

让我们对微调后的模型进行推理,看看它的表现如何。

由于用于调整模型的数据具有相当特定的布局,我们需要手动创建一些模型可以处理的提示词。让我们创建一个基于现有数据库模式的问题。我们将使用之前检查数据集是否正确加载时的相同示例,但我们将覆盖问题:

tst = dict(**trn[4500])
tst['question'] = 'What is the highest number of goals for any player with less than 5 assists'
tst
###output###
# {'answer': 'SELECT MAX(field_goals) FROM table_19722233_5 WHERE assists = 27',
# 'question': 'What is the highest number of goals for any player with less than 5 assists',
# 'context': 'CREATE TABLE table_19722233_5 (field_goals INTEGER, assists VARCHAR)'}

现在我们需要确保格式正确:

fmt = """SYSTEM: Use the following contextual information to concisely answer the question.

USER: {}
===
{}
ASSISTANT:"""

我们还将创建一个快速函数来处理这些格式:

def sql_prompt(d):
  return fmt.format(d["context"], d["question"])

让我们检查一下以上内容:

print(sql_prompt(tst))
###output###
# SYSTEM: Use the following contextual information to concisely answer the question.

# USER: CREATE TABLE table_19722233_5 (field_goals INTEGER, assists VARCHAR)
# ===
# What is the highest number of goals for any player with less than 5 assists
# ASSISTANT:

很好,现在我们的提示词已经准备好可以输入到模型中。

现在我们需要对保存在 qlora-out 目录中的模型进行推断(如 yaml 配置文件中指定的)。首先,我们需要安装运行推断所需的依赖项,并对提示进行分词,以便模型可以处理它。我们需要从 HuggingFace 获取 Llama2–7B 分词器。工作流程将与我在上一篇文章中介绍的非常相似。以下是代码:

import torch
from peft import PeftModel
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig

fine_tuned_model = '/content/drive/MyDrive/fine_tune_llm/axolotl/qlora-out'

tokr = AutoTokenizer.from_pretrained('meta-llama/Llama-2-7b-hf')

model = AutoModelForCausalLM.from_pretrained('meta-llama/Llama-2-7b-hf',
                                             torch_dtype=torch.bfloat16, device_map=0)
model = PeftModel.from_pretrained(model, fine_tuned_model)
model = model.merge_and_unload()
model.save_pretrained('sql-model')

toks = tokr(sql_prompt(tst), return_tensors="pt")

res = model.generate(**toks.to("cuda"), max_new_tokens=250).to('cpu')

print(tokr.batch_decode(res)[0])
###output###
# SYSTEM: Use the following contextual information to concisely answer the question.

# USER: CREATE TABLE table_19722233_5 (field_goals INTEGER, assists VARCHAR)
# ===
# What is the highest number of goals for any player with less than 5 assists
# ASSISTANT:
# SELECT MAX(field_goals) FROM table_19722233_5 WHERE assists < 5</s>

太棒了!正如你从助手的输出中看到的,它提供了正确的答案。

虽然这可以说是一个非常简单的问题,但你可以看到我们成功训练了一个基础 LLM,这个 LLM 之前只能用于一般语言查询,现在它可以写 SQL 代码了!

通过熟悉以上内容并查看 axolotl 文档,你将能够了解如何将我的方法适应到不同的 LLM 任务中,例如问答。只需输入一个特定主题的问题数据集及其相应答案,使用 axolotl 提供的示例更新你的 yaml 配置文件,然后开始另一个微调实验😊

我希望你阅读这篇文章时的感受与我写作时一样愉快。像往常一样,如果你有任何问题,请随时联系我。

除非另有说明,否则所有图像均属于作者。

如何高效地替换 Pandas DataFrame 中的值

原文:towardsdatascience.com/how-to-efficiently-replace-values-in-a-pandas-dataframe-330fe832dd21

PYTHON

对 Pandas 替换方法的详细讲解,以及如何通过几个简单的示例使用它

Byron DolonTowards Data Science Byron Dolon

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

--

图片由我的才华横溢的姐姐ohmintyartz提供许可使用

Pandas 库提供了多种内置方法,供你用来处理和清洗数据,以便为分析和机器学习做好准备。

在处理不同类型的数据时,你经常会发现需要根据条件删除整行或更新字符串值的一部分作为数据清洗的一部分。你也可能想从现有列中创建新列,作为特征工程过程的一部分。

Pandas 将让你使用其本地转换方法对对象和字符串数据类型执行各种操作。在这篇文章中,让我们特别看看如何在 DataFrame 的列中替换整个值和/或子字符串。

随意在笔记本中跟随本篇示例操作!你可以从 Kaggle 下载数据集,该数据集在 Open Data Commons Public Domain Dedication and License (PDDL) v1.0 下免费使用。然后导入并运行以下代码,我们可以开始了!

import pandas as pd
df_raw = pd.read_csv("Top-Largest-Universities.csv")

图片由作者提供

使用 Pandas 中的“replace”来编辑 DataFrame Series(列)中的子字符串值

假设我们想要专门查看“Continent”列中的值。我们可以使用 Pandas 中的value_counts方法,它实际上是对指定列进行分组,然后返回每个列值在 DataFrame 中唯一值的计数。这对于查看列中每个唯一值在 DataFrame 中出现的次数非常有用。

df.value_counts("Continent")

图片由作者提供

现在,假设我们想要去掉“North” America,这样我们可以将其简化为“America”。我们可能希望这样做,以便创建一个新的列作为特征工程过程的一部分,或者如果我们想将北美和南美视为一个区域。

我们可以通过使用 replace 方法从字符串“North America”中删除“North”来实现这一点。我们可以这样尝试:

df[“Continent”].replace(“North”)

作者提供的图片

这不起作用,因为如果你仅传递一个字符串值来替换,Pandas 方法只会替换在 Series 中找到的完全匹配的值。要对子字符串进行简单匹配,我们可以这样做:

df[“Continent”].replace(to_replace=”North”, value=””, regex=True)

在这个片段中,我们做了几件不同的事情:

  • to_replace — 为了清楚起见,我们将“North”传递给这个参数,以便将来清楚这个方法的作用(我们知道我们要替换哪个值);

  • value — 我们在这里传递一个空字符串“”以指定我们希望该方法将“North”替换为空(实际上是从原始字符串中删除子字符串);

  • regex — 我们将其设置为“True”,这让 Pandas 方法知道我们传递给to_replace的字符串应该被视为正则表达式。

代码现在返回一个新的编辑后的 Pandas Series:

作者提供的图片

在 Pandas DataFrame Series 中替换多个子字符串

我们现在可以进一步查看在“Continent”列中同时替换“North”和“South”子字符串。为此,你可以将一个字符串列表传递给to_replace参数,而不仅仅是一个字符串:

df["Continent"].replace(to_replace=["North", "South"], value="", regex=True)

作者提供的图片

根据输出,代码成功地从列中删除了“North”和“South”这些子字符串。然而,如果我们更仔细地查看用value_counts修改后的 Series,你会看到:

作者提供的图片

这在正确的方向上迈出了一步,但还不完全符合我们的初衷,因为现在我们有了“ America”作为替换值。这是因为 Pandas 方法匹配的是“North”或“South”的精确字符串,但原始数据实际在子字符串后面包含一个空格。

使用正则表达式和 Pandas 替换方法

为了解决这个问题,我们可以显式地将一个符合我们初始需求的正则表达式作为参数传递给to_replace

df[“Continent”].replace(to_replace=r"(North|South) ", value="", regex=True)

在这里,我们将正则表达式r"(North|South) "传递给to_replace参数,这使我们能够从字符串值中提取出“North”或“South”后面跟一个空格的单词。

然后,就像之前一样,我们用空字符串替换了字符串的那部分,有效地从原始字符串中删除了子字符串。你可以看到下面对原始 Series 的更改:

作者提供的图片

要检查汇总结果,如果我们再次将value_counts附加到上述内容中,我们会得到:

作者提供的图片

现在,我们已经实现了最初的目标,即通过使用 Pandas 中的 replace 方法将“North America”和“South America”合并为一个值“America”!

我们知道这也已经生效,因为原始数据集中“North America”有 31 个值,“South America”有 6 个值,这从我们第一次对 continent 列运行value_counts中可以看到。当你为分析实现这种代码时,实施某种数据验证或测试以确保处理按预期进行也是很好的做法。

在一次函数调用中替换 Pandas DataFrame 中多个列的值

你还可以在 DataFrame 上调用replace值,而不是特定的系列。这在你想编辑存在于多个列中的子字符串并将它们替换为相同的值时非常方便。

例如,我们可以考虑将 DataFrame 中的所有标点符号替换为更容易进行分析的东西。如果所有的标点符号都被简单地替换为下划线,那么处理字符串数据可能会更容易,而不是处理逗号、括号和破折号。

为此,我们可以运行以下代码:

edited_df = (
    df.replace(
        to_replace={
            "Distance / In-Person": ["/", "-"], 
            "Institution": [", ", " "], 
            "Location": [", ", " "]
        }, 
        value="_", 
        regex=True)
)

之前,我们传递给to_replace的是一个列表或字符串(以及正则表达式字符串)。这一次,我们利用了to_replace方法也接受字典作为有效参数的特性。

在这种情况下,我们想用下划线替换初始 DataFrame 中几个列的不同值。为此,我们传递一个字典,其中键是 DataFrame 中列的名称,值是我们要替换的子字符串。

你会注意到,对于“Distance / In-Person”列,我们将一个列表作为该键的值。这是因为我们仍然可以指定多个值进行匹配。我们还为“Institution”和“Location”列传递列表,以便进一步清理数据,我们可以删除空格并用下划线替换它们。

当使用这种方式与 replace 方法时,我们还必须明确地在value参数中传递一个字符串,否则将会引发错误。在这种情况下,由于我们想将所有标点符号更改为下划线,因此我们只需将value="_"。然后,和之前一样,为了匹配可能出现在目标值中的子字符串而不是精确字符串,我们设置regex=True

我们将其分配给一个新的清理后的 DataFrame edited_df,其样子如下:

作者提供的图片

我们现在已经成功地在一次函数调用中编辑了整个 DataFrame,并用下划线替换了目标列中的标点符号和空格。

  • 如果你想将对原始 DataFrame 的更改应用到数据框而不是像我们所做的那样赋值给新变量,replace方法也接受一个可选参数inplace,你可以将其设置为“True”(因为默认情况下设置为“False”)。

  • 这些只是一些示例来帮助你入门。使用 Pandas 中的 replace 方法可以处理 DataFrame 中的各种数据类型,还有很多其他功能可以探索。

  • 使用此方法时还需考虑一些额外的事项:

    • 建议进行检查,以确保replace方法按预期工作。如果你的原始数据随着时间的推移而变化,你最初编写的匹配子字符串的代码可能不再有效,你可能需要更新你编写的正则表达式,以确保输出符合预期;
    • 对于更复杂的操作,例如匹配许多不同的子字符串或列,你可能想单独定义字典,而不是直接将其传递到to_replace参数中。这将提供更好的可读性,之后你也可以简单地传递一个变量,例如values_to_replace,该变量包含你定义的字典,包含列和要匹配的子字符串。
  • 我希望你发现这篇文章中的示例有用!祝你在数据探险中好运。

  • 如果你喜欢我的内容,考虑通过下面的推荐链接注册成为 Medium 会员。每月仅需 $5,你将无限访问 Medium 上的所有内容。通过我的链接注册让我赚取一小笔佣金。如果你已经注册关注我,非常感谢你的支持!

  • ## 通过我的推荐链接加入 Medium - Byron Dolon

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

  • 更多内容: - 理解 Pandas 中数据的 5(加半行)行代码

  • 在 GitHub 上学习 Pandas 的前 4 个仓库

  • 在 Pandas DataFrame 中检查子字符串

  • 使用 Pandas 中的 .loc 进行条件选择和赋值

  • 通过 Pandas 从网站获取表格的 2 种简单方法

如何编码周期性时间特征

原文:towardsdatascience.com/how-to-encode-periodic-time-features-7640d9b21332?source=collection_archive---------9-----------------------#2023-08-24

对于深度学习和其他预测模型,周日期和一天中的时间的深思熟虑的处理。

Christoph MöhlTowards Data Science Christoph Möhl

·

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

--

作者插图

许多预测任务需要时间信息作为模型输入。比如考虑一个回归模型来预测零售公司柠檬水的销售(你可能记得我在关于上下文增强特征的文章中提到过这个例子)。显然,夏季对清凉饮料的需求更高,导致销售曲线呈周期性变化,在七月/八月达到峰值(这里考虑的是欧洲的一个地点)。

在这种情况下,年份中的时间显然是我们应该输入模型的有价值的季节性信息。但我们应该如何做呢?日期很复杂,天数因月份不同而变化(而对于二月甚至因年份不同而变化),且存在各种格式:

2023 年 1 月 13 日

2023 年 1 月 13 日

2023/03/13

首先,我们可以省略年份。为了考虑季节性影响,我们只需要日和月。在一种非常简单(且不太深思熟虑)的方法中,我们可以将月份作为一个数字,日期作为另一个数字输入。

为什么这是一个坏主意?模型必须学习基督教格里高利历法的工作原理(每月约 30 天,每年 12 个月,闰年等)。有了足够的训练数据,深度学习模型当然能够“理解”我们的日历。“理解”在这里的意思是:模型可以从月份和日期输入中推断出年内的相对时间位置。但我们应该尽量使学习对我们的模型来说更简单,将这项工作交给我们自己(至少我们已经知道日历如何运作)。我们利用 Python 的datetime库,并用相当简单的逻辑计算年内的相对时间:

from datetime import datetime
import calendar

year = 2023
month = 12
day = 30

passed_days = (datetime(year, month, day) - datetime(year, 1, 1)).days + 1
nr_of_days_per_year= 366 if calendar.isleap(year) else 365

position_within_year = passed_days / nr_of_days_per_year

结果的postion_within_year特征的值范围从接近0.01 月 1 日)到1.012 月 31 日),比(复杂的)格里高利历法更易于模型解释。

但这仍然不理想。position_within_year特征描述了一个“锯齿状”的模式,在每年转折时从1.0跳到0.0。这种急剧的不连续性可能对有效学习构成问题。12 月 31 日1 月 1 日是非常相似的日期:它们是直接邻居,并且有许多共同点(例如类似的天气条件),它们可能具有类似的柠檬水销售潜力。然而,特征position_within_year并没有反映出12 月 31 日1 月 1 日的这种相似性;事实上,它们的差异是最大的。

理想情况下,彼此接近的时间点应具有相似的时间值。我们必须以某种方式设计一个代表一年周期性特征的特征。换句话说,通过12 月 31 日我们应该到达1 月 1 日时的起始位置。因此,将年内的位置建模为圆上的位置当然是有意义的。我们可以通过将position_within_year转换为单位圆的xy坐标来实现这一点。

为此我们使用sinecosine函数:

sin(α) = x

cos(α) = y

其中α是施加于圆的角度。如果单位圆表示一年,α表示已过去的年内时间。

α因此等同于position_within_year特征,唯一的区别是α有一个不同的尺度(α0.0¹,position_within_year0.0-1.0)。

通过将position_within_year简单地缩放到α并计算sinecosine,我们将“锯齿形”模式转换为具有平滑过渡的圆形表示。

import math

# scale to 2pi (360 degrees)
alpha = position_within_year * math.pi * 2

year_circle_x = math.sin(alpha)
year_circle_y = math.cos(alpha)

# scale between 0 and 1 (original unit circle positions are between -1 and 1)
year_circle_x = (year_circle_x + 1) / 2
year_circle_y = (year_circle_y + 1) / 2

time_feature = (year_circle_x, year_circle_y) # so beautiful ;)

生成的time_feature是一个两个元素的向量,范围在01之间,易于你的预测模型处理。通过几行代码,我们将大量不必要的学习工作从模型的肩膀上移除。

单位圆模型可以应用于任何周期性的时间信息,例如月份的天数星期几一天中的时间、小时的分钟等。这个概念也可以扩展到时间域之外的周期特征。

  • 物流/公共交通:公交车在城市往返行程中的相对位置

  • 生物学:细胞在细胞周期中的状态。

  • 你有其他的使用案例吗?欢迎留言!

进一步的信息 / 连接点

[1] 这里的角度以弧度表示。0弧度对应于弧度对应于 360°。

所有图形均由作者创建。

如何通过讲故事与用户互动:在 R 和 Shiny 中展示数据分析

原文:towardsdatascience.com/how-to-engage-with-users-by-storytelling-show-data-analytics-in-r-and-shiny-e7205c06ccea

如何利用 R 和 Shiny 帮助你找到最适合孩子们的 YouTube 视频

Chengzhi ZhaoTowards Data Science Chengzhi Zhao

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

--

如何通过讲故事与用户互动:在 R 和 Shiny 中展示数据分析 | 图片由作者提供

数据通过讲故事的方式变得更具吸引力。作为数据专业人士,我寻求更简单的方式来弥合数据分析与沟通之间的差距。仪表板传统上是可视化和分享数据的默认方式。它也承担了沟通的责任。然而,我发现了仪表板的局限性:图表选择有限,自定义自由度较低。我探索了更多互动的方式来展示想法并与用户互动 — 为你的数据构建一个网页应用程序

Shiny 是一个 R 包,用于直接从 R 代码构建交互式网页应用程序。使用 R 而不必担心切换到 HTML、CSS 和 JavaScript 的上下文是令人兴奋的。这节省了始终使用一种语言的时间。我们可以重复使用 R 的数据框,并使用如 dply 和 ggplot2 图表等 R 包来处理数据而不会遇到问题。Hadley Wickham 的书 — Mastering Shiny 对此有深入的内容。

使用 R 和 Shiny 更令人兴奋的是,我们现在可以构建一个应用程序,让最终用户可以与我们完成的数据分析进行互动。他们可以通过独立探索获得更多的洞察,而不需要了解背后的复杂情况。在这个故事中,我将向你展示如何通过讲故事与用户互动 — 在 R 和 Shiny 中展示数据分析。

准备数据以构建 Cocomelon 视频。

在我之前的故事中,我分享了如何使用 Youtube 数据 API 在 R 中获取世界第二大 Youtube 频道——Cocomelon 的统计数据。我们建立了一个过程来检索数据,进行基本清理,并使用 ggplot2 提供了几个数据可视化。在这次分析中,有几个有趣的发现:

  • 最受欢迎的 Cocomelon 视频因为每日观看量大,依然保持上升趋势,这些视频是在 4-5 年前制作的。

  • 最受欢迎的视频:“Bath Song”播放次数比第二个视频——“Wheels on the Bus”多 20%以上。

  • 在 200 天内发布的视频中观看次数最多的是🔴 CoComelon Songs Live 24/7\。这个视频展示了父母可以通过自动循环的视频保持孩子的兴趣,而不需要手动切换视频。

## R 数据分析:如何找到适合你孩子的完美 Cocomelon 视频

如何从零开始用 R 探索新趋势的 Cocomelon 视频,构建端到端的数据项目

towardsdatascience.com

使用数据讲述这个故事很有趣。然而,作为最终用户,我假设你对 R 及一些 R 数据分析包有所了解。

如果你不知道以上内容怎么办?作为家长,我发现这些数据很有趣,可以帮助你的孩子不厌倦“始终受欢迎”的视频。是否有一种简单的方法来向不了解 R 或任何编程语言的人展示这些数据?我们能否让更多用户自行探索?

是的。我可以构建一个网页应用程序并公开分享,让每个人体验用 R 和 Shiny 构建的网页应用。

我记录了如何用 R 的 Shiny 构建这个端到端的网页应用程序的过程。(如果你不关心技术细节,可以滚动到页面底部查看最终结果)

灵感来源于《Mastering Shiny》

在阅读 Wickham 先生的书《Mastering Shiny》(链接)之前,我对 Shiny 一无所知。Wickham 先生是 RStudio 的首席科学家,他的书在 R 社区中都很有名。选择一个特定的包来编写书籍,Shiny 在 R 社区中是一个独特的项目。

让用户与数据和分析互动——Shiny

数据讲故事不应该是单向的,它应该是双向的。通过用户参与,这有助于对数据的相互理解,并使得实现数据分析目标变得更加容易,同时减少沟通障碍。

Shiny 包缩短了执行分析的数据专业人员与利用数据做出决策的数据消费者之间的距离。它为用户提供了一个完美的用例,通过启用一个元素:交互性,使用户能够参与你所做的数据分析。

注意:这个故事不是 Shiny 或 R 101。这里有书籍和在线资源可以帮助你熟悉内容。我将专注于构建端到端项目的过程。正如 Hadley Wickham 在书中建议的那样,Shiny 应用的正常开发涉及多个迭代。

第一次迭代:UI 布局和服务器

构建一个 Shiny 应用通常包括两个主要组件:UI 和服务器。UI 处理屏幕上的显示,服务器运行后台逻辑以准备好将其传递给 UI 进行渲染。

我们可以重用我们从上一个帖子中持久化的数据,并从 CSV 文件中读取它们:R 数据分析:如何为您的孩子找到完美的 Cocomelon 视频。

这是第一次迭代的完整代码。

library(shiny)
library(DT)
library(dplyr)
library(readr)
library(ggplot2)

ui <- fluidPage(titlePanel(
  h1(
    "How R and Shiny Can Help You Find the Best Cocomelon Videos for Your Kids",
    align = "center"
  )
),

sidebarLayout(
  sidebarPanel(
    textInput(
      inputId = 'number_records_display',
      label = 'Number of records to Display',
      value = '10'
    ),
    width = 3
  ),

  mainPanel(
    h1("Explore Cocomelon Youtube Videos", align = "center"),
    strong("The following charts display the scatter plot by views and likes"),
    plotOutput("plot"),
    tableOutput("video_table")
  )
))

server <- function(input, output) {
  data <-
    reactive(
      read_csv("~/Downloads/cocomelon_2023_3_2.csv") %>%
        transform(
          statistics.viewCount = as.numeric(statistics.viewCount),
          statistics.likeCount = as.numeric(statistics.likeCount),
          statistics.favoriteCount = as.numeric(statistics.favoriteCount),
          snippet.publishedAt = as.Date(snippet.publishedAt)
        ) %>% mutate(
          views = statistics.viewCount / 1000000,
          likes = statistics.likeCount / 10000,
          number_days_since_publish = as.numeric(Sys.Date() - snippet.publishedAt)
        ) %>% arrange(desc(statistics.viewCount))
    )

  output$plot <- renderPlot({
    videos <-
      data() %>% head(input$number_records_display)
    ggplot(videos, aes(statistics.viewCount, statistics.likeCount)) +
      geom_point()
  }, res = 96)

  output$video_table <- renderTable(
    data() %>% select(
      snippet.title,
      statistics.viewCount,
      statistics.likeCount,
      contentDetails.duration,
      number_days_since_publish
    ) %>% head(input$number_records_display)
  )
}

shinyApp(ui = ui, server = server)

我们应用程序的第一个版本是直接的:

对于 UI 部分,我们在 Shiny 中使用了sidebarLayout来分割sidebarPanelmainpanel。在sidebarPanel中,我们添加了一个输入文本框以获取要显示的行数,这将进一步传递值到服务器部分并执行head操作。在mainpanel中,我们添加了一个plotOutput和一个tableOutput作为显示数据的占位符。

对于服务器部分,我们首先进行数据读取。然后使用转换后的数据,找到所有在 UI 中定义的输出,为renderPlotrenderTable提供内容。

我们调用reactive来减少冗余操作并提高性能,以提供更好的用户体验。Shiny 的反应式表达式实现了这一点。反应式编程是声明性的,允许引擎懒惰地评估和优化。在我们的例子中,当应用程序最初加载时,它只读取 CSV 文件一次并缓存中间结果。如果用户将输入从默认的十行更改为 20 行,我们只需回到最早的阶段重新运行head操作,而不是再次从 CSV 文件中读取数据。

这是第一次迭代的结果

第一次迭代:UI 布局和服务器,使用 R 和 Shiny | 作者图片

第二次迭代:与用户点击的交互

使输入文本反映图表和表格中的更改是一个令人兴奋的第一步。你可能注意到,当我输入数字2时,图表和表格迅速变化。这是由于反应式编程的效率。

Shiny 支持在 plotOutput 上进行额外的互动,这样可以接收用户的指针事件作为其他输出的输入。在前一步中,图表上有许多点,但我们需要知道图表上的每个点代表什么。作为用户,提供额外的信息可以帮助他们进行自我探索。

Youtube API 还提供了我们可以添加到 Web 应用中的嵌入 iframe。我们需要在 Youtube 上搜索标题,以便为孩子们提供无缝体验。

我们需要对代码进行一些更改,以便看到这个变化。以下是第二次迭代的完整代码。

library(shiny)
library(DT)
library(dplyr)
library(readr)
library(ggplot2)

ui <- fluidPage(titlePanel(
  h1(
    "How R and Shiny Can Help You Find the Best Cocomelon Videos for Your Kids",
    align = "center"
  )
),

sidebarLayout(
  sidebarPanel(
    textInput(
      inputId = 'number_records_display',
      label = 'Number of records to Display',
      value = '10'
    ),
    uiOutput("iframe"),
    width = 4
  ),

  mainPanel(
    h1("Explore Cocomelon Youtube Videos", align = "center"),
    strong("The following charts display the scatter plot by views and likes"),
    textOutput("video_title"),
    plotOutput("plot", click = "plot_click"),
    verbatimTextOutput("clicked_video"),
    DT::dataTableOutput("video_table")
  )
))

server <- function(input, output) {
  data <-
    reactive(
      read_csv("~/Downloads/cocomelon_2023_3_2.csv") %>%
        transform(
          statistics.viewCount = as.numeric(statistics.viewCount),
          statistics.likeCount = as.numeric(statistics.likeCount),
          statistics.favoriteCount = as.numeric(statistics.favoriteCount),
          snippet.publishedAt = as.Date(snippet.publishedAt)
        ) %>% mutate(
          views = statistics.viewCount / 1000000,
          likes = statistics.likeCount / 10000,
          number_days_since_publish = as.numeric(Sys.Date() - snippet.publishedAt)
        ) %>% select(
          snippet.title,
          statistics.viewCount,
          statistics.likeCount,
          contentDetails.duration,
          number_days_since_publish,
          player.embedHtml
        ) %>% arrange(desc(statistics.viewCount))
    )

  output$plot <- renderPlot({
    videos <-
      data() %>% head(input$number_records_display)
    ggplot(videos, aes(statistics.viewCount, statistics.likeCount)) +
      geom_point()
  }, res = 96)

  output$video_table = DT::renderDataTable({
    req(input$plot_click)
    top_videos <- data() %>% select(
      snippet.title,
      statistics.viewCount,
      statistics.likeCount,
      contentDetails.duration,
      number_days_since_publish
    ) %>% head(input$number_records_display)

    near_point <- nearPoints(top_videos,input$plot_click)

    datatable(top_videos) %>%
      formatStyle(
        columns = 'statistics.viewCount',
        target = 'row',
        backgroundColor = styleEqual(near_point$statistics.viewCount, c('yellow'))
      )
  })

  output$iframe <- renderUI({
    near_point <- nearPoints(data() %>% head(input$number_records_display) ,input$plot_click)
    HTML(near_point$player.embedHtml)
  })
}

shinyApp(ui = ui, server = server)

plotOutput 上,我们可以定义一个 id: plot_click。每当在图表中点击一个位置时,图表将把坐标发送到服务器,可以在 input$plot_click 中找到。

这一次我们在 Shiny 应用中导入了 DT。DT 是一个 R 接口,用于 JavaScript 库 DataTablesDT::renderDataTable 提供了比 Shiny 默认的 renderTable 更强大的功能。一旦我们能够识别指针位置以知道用户点击的位置,我们可以进一步高亮显示该特定行给用户。formatStyle 是我们可以更新表格 CSS 属性的一种方式。我们使用了 styleEqual,只要点击的值匹配,定义的样式将会应用。

我们还尝试首先获取最近的点 near_point <- nearPoints(data(),input$plot_click)。我们不能指望用户精确点击图表上的确切点。nearPoints 是一个帮助函数,用于获取最近的点。因此,它可以正确地突出显示被点击的项目。

要显示视频,我们可以利用 renderUI 并使用 Youtube API 已提供的 iframe 在侧边栏中。

第二次迭代:与用户点击的互动 | 图片来自作者

如果用户想选择一系列视频观看怎么办?为了选择多个点,我们可以使用 brushedPoints,我们需要做的就是将 plotOutput(“plot”, click = “plot_click”) 更改为 plotOutput(“plot”, brush = “plot_brush”),然后我们可以通过以下方式收集点列表。

near_points <- brushedPoints(top_videos,input$plot_brush)

现在我们可以选择多个点了

第二次迭代:与 BrushedPoints 的互动 | 图片来自作者

第三次迭代:添加一些主题

主题对于使你的 Web 应用看起来更精致至关重要。Shiny 与 bslib 集成良好,这是一个支持 R 中 Bootstrap 主题 的包。

要启用主题,我们可以在 fluidPage 中添加一行代码来指示我们感兴趣的主题。

theme = bs_theme(bootswatch = "sketchy")

第三次迭代:添加一些主题 | 图片来自作者

现在,是时候发布我们的应用程序并将其公开。关于Shiny 教程页面中提到了多种选项。RStudio 使得按照这些说明来发布应用程序变得很简单。

通过 RStudio 发布到 shinyapps.io | 图片由作者提供

现在这里是我们为小朋友准备的 Cocomelon 热门视频的 Shiny 网页应用程序:

chengzhizhao.shinyapps.io/Cocomelon_Shiny/

[## 探索 Cocomelon 的 YouTube 视频

chengzhizhao.shinyapps.io](https://chengzhizhao.shinyapps.io/Cocomelon_Shiny/?source=post_page-----e7205c06ccea--------------------------------)

最后的想法

在这个故事中,我扩展了我之前的故事《R 用于数据分析:如何为你的孩子找到完美的 Cocomelon 视频》,使其更加引人入胜,并与 Shiny 结合。我分享了如何将数据分析带到下一个阶段:讲故事,并让我们的用户(我们的孩子😊)获得更多帮助。

我还发布了这个网页应用程序,供那些想要互动体验这个网页的人使用。我希望这篇文章能帮助将数据分析的理念带给更多人,并让更多人了解 R 和 Shiny。

我希望这个故事对你有帮助。本文是一系列工程与数据科学故事中的一部分,目前包括以下内容:

Chengzhi Zhao

Chengzhi Zhao

数据工程与数据科学故事

查看列表53 个故事!

你还可以订阅我的新文章或成为推荐的 Medium 会员,无限访问 Medium 上的所有故事。

如果有问题或评论,请随时在本故事的评论区留言,或通过LinkedinTwitter直接联系我。

如何提升你的 Pandas 代码 — 不要再等待了

原文:towardsdatascience.com/how-to-enhance-your-pandas-code-dont-wait-no-more-5fb89bc1ece9

提升你的 pandas 代码性能的 6 个简单改变

Pol MarinTowards Data Science Pol Marin

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

--

Pascal Müller拍摄于Unsplash

Pandas可以说是任何数据科学家简历中的必备技能。无论你是否使用它,掌握它始终是一种资产。

然而,就像其他任何事情一样,关键不是知道如何使用pandas,而是实际编写高效的pandas代码。这是大多数人做不到的,但在读完这篇文章后,你不会再是其中的一员。

但首先要做的事情是。

什么是 Pandas 以及为什么效率很重要?

根据官方定义,“pandas是一个快速、强大、灵活且易于使用的开源数据分析和操作工具,建立在Python编程语言之上”[1]。

它真的很强大,许多人在处理数据时默认使用它。

另外,它将数据集加载到我们的 RAM 中,所以关心优化空间使用是个好主意。

但我们不仅想优化它的内存管理,我们可能更关心让代码执行得更快。为什么?显而易见,更快的代码可以节省时间。你知道他们怎么说:时间就是金钱(尤其是当你的代码在 AWS 或 GCP 上运行时)。

硬件可以在一定程度上提高我们程序的性能,但这只是方程式的一半。高效的代码是另一半,和前者一样重要。因此,对于我们这些每天使用它的人来说,掌握 pandas 几乎是必需的。

不过,请不要分心。主要目标是让我们的代码符合要求。在我看来,优化应当留到你的脚本运行过慢或占用过多内存时。并不是说你不应考虑效率,但如果不值得付出额外的努力,就不要将其作为优先事项。

我将尝试超越逻辑效率的说法,但让我们简要突出两个最基本的:

  • 只使用你需要的 — 加载对你的分析有用的列/行,并去掉其余的。

  • 选择正确的变量类型 — 如前所述,pandas 将所有数据加载到系统的 RAM 中,所以明智地选择变量类型是值得的。例如,当所有操作都可以用整数完成时,就不应使用浮点数。

    加载数据框并使用astype()函数以确保所有列都具有期望的类型。

在开始之前,让我与大家分享我将使用的数据框,并结合%timeit魔法函数来将性能提升量化:

本文随机创建的数据 — 作者截图

1. 尽量避免使用循环

这并不总是可能,但去掉循环是你可以为代码添加的最佳优化之一。

Pandas 构建在 NumPy 之上,所以我们最好利用矢量化,而不是使用循环对列/行进行操作。

这个例子是许多人会采用的一种方法,试图从一列中减去另一列的值:

def subtract_open_from_close(df):
    subtracted_price = []
    for i,row in df.iterrows():
        subtracted_price.append(row['Open'] - row['Close'])

    df['SubtractedPrice'] = subtracted_price
    return df

%timeit -n 1 -r 1 subtract_open_from_close(df)
14 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)

好吧,这并没有花费太多时间,对于一个 ~500k 行的数据框花费了 14 秒。还不错,但如果我告诉你这可以在不到一秒钟内完成呢?

让我们看看做同样事情的更好(甚至是最佳)方法:

def subtract_open_from_close_vectorized(df):
    df['SubtractedPrice'] = df['Open'] - df['Close']
    return df

%timeit subtract_open_from_close_vectorized(df)
2.24 ms ± 57.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

PAM。

改进效果真是疯狂!我们不仅简化了代码(减少了三行),还节省了大量时间。这要归功于矢量化特性,它不是一次操作一个数据元素,而是同时处理所有数据元素。一次操作可以应用于任意数量的数据元素。

仅这一优化就带来了 100 倍的执行时间提升。值得吧?

2. 如果需要循环,避免使用 Iterrows()

有几种替代iterrows的方法,比如使用apply()或列表推导。然而,我想提供一个与iterrows()相当的替代方案,无论如何都需要使用循环。

使用itertuples()会返回每一行作为命名元组,这是 collections 模块中的一种专门数据类型。它们像元组一样工作,但通过名称访问字段,使用点号(.)查找。

让我们重复之前的实验,这次使用itertuples()

df = spread_df.copy()
def subtract_open_from_close(df):
    subtracted_price = []
    for tup in df.itertuples():
        subtracted_price.append(tup.Open - tup.Close)

    df['SubtractedPrice'] = subtracted_price
    return df

%timeit -n 1 -r 1 subtract_open_from_close(df)
821 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)

改进效果依然巨大。虽然iterrows执行需要大约 14 秒,但使用itertuples的完全相同代码则不到一秒钟。

虽然使用矢量化是首选,但我们已经看到循环也可以进行优化。

3. Append 与 Concat

我曾多次需要连接数据框,过去曾一直问自己一个问题:我应该使用 append() 还是 concat()。最后我默认使用 concat,因为我觉得它更易于使用。

但不要像我一样随意回答这个问题。它的最终取决于你愿意连接多少个数据框。当只有两个时,不用太在意,使用其中一个或另一个不会对代码性能产生太大影响。

然而,当尝试连接超过两个数据框时,我建议使用 concat。为什么?因为 concat() 是一个库函数,允许我们同时连接多个数据框,而 append() 是 DataFrame 类的方法,仅允许一次连接两个数据框。

长话短说:使用 concat() 可以同时连接多个数据框,而使用 append() 需要一个循环来完成相同的操作。

这不仅在时间上效率较低,而且在内存使用上也不够高效,因为每次调用 append 函数都会返回一个新的数据框,存储在系统内存中。所以,如果我们要连接 N 个数据框,我们会创建 N-1 个数据框,直到得到最终结果,这些数据框都占用宝贵的内存空间。

不好。

所以在追加多个数据框时使用 concat()

4. 不要破坏你的 GroupBy 和合并

groupby()merge() 都是强大的工具,我敢打赌你每天都在使用它们。因此,妥善处理它们也非常重要。

  1. 首先进行过滤。GroupBy 和合并是代价高昂的操作,因此请确保在分组/合并之前过滤掉不需要的数据。

  2. 尽量避免在 GroupBy 中使用自定义函数。当你需要不同于通常的总和、平均值等的分组功能时,你会想创建自己的分组函数。很可能有人已经做过这件事。可能还有内置函数可以处理这些情况。尽可能使用这些内置函数,因为使用自定义函数会降低性能。

  3. 考虑使用 DuckDB。DuckDB 是一个强大的 OLAP 系统,可以真正帮助你执行像这样分析任务。我最近写了一篇更深入探讨这个话题的文章,如果你对 DuckDB 感兴趣,可以去读读:

## 忘记 SQLite,改用 DuckDB——稍后感谢我

DuckDB 及其 Python 集成介绍

towardsdatascience.com

5. 在大型数据集上使用 query()

我必须承认我之前并没有意识到这一点,但在稍微测试了一下后,我认为值得将其添加到这篇文章中。

Pandas 提供了一种名为 query() 的数据框方法,目的是用布尔表达式查询数据框的列[2]。它利用了numexpr 解析器,这是一个用于 NumPy 的快速数值表达式求值器[3]。

说够理论了,接下来我们展示一下 query() 在整个数据框与通常过滤方法的性能对比。由于我们的数据框中包含日期,我们就随机选择一个日期,并尝试从那个日期获取数据:

%timeit df[df['ExtractDate'] == '2022-01-03']
61.7 ms ± 1.67 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit df.query("ExtractDate=='2022-01-03'")
32.4 ms ± 1.4 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

很酷吧?在这个 ~500k 行的数据框上,性能提升了将近50%。如果我们对一个包含 2,279 行的小数据子集做同样的操作呢?看看这个:

jan22 = df[df['ExtractDate'].str.startswith('2022-01')] # 2279 rows

%timeit jan22[jan22['ExtractDate'] == '2022-01-03']
339 µs ± 13.8 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

%timeit jan22.query("ExtractDate=='2022-01-03'")
831 µs ± 37.5 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

结果表明,相反的方向——在小数据框上使用query()并不理想,因为与通常的 pandas 过滤方法相比,它会显著降低性能。这就是为什么我在本节标题中指定了“在大型数据集上”。

Pandas 开发者已经深入研究了这一方法,并且他们在线上提供了相关分析,但主要的结论之一是:“只有当你的数据框有大约 100,000 行或更多时,你才会看到使用 numexpr 引擎与 DataFrame.query() 的性能提升。”[4]

6. 不要默认使用 CSV 文件

我过去总是使用 CSV 文件来加载和保存数据。我现在并不反对 CSV 文件,视任务而定我仍然会使用它们,但当数据更相关和更大时,我已经转向使用parquet 文件。

为什么选择 parquet?阅读官方介绍:“Apache Parquet 是一种开源的、面向列的数据文件格式,旨在实现高效的数据存储和检索。它提供了高效的数据压缩和编码方案,增强了处理复杂数据的大数据性能。”[5]

我们可以看到,parquet 文件在内存使用方面比 CSV 文件更高效——它们是“为高效的数据存储设计的,并提供高效的数据压缩和编码方案”——但这如何转化为时间呢?

%timeit df.to_csv("data.csv")
1.82 s ± 43.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit df.to_parquet('data.parquet.gzip', compression='gzip')
1.07 s ± 26.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

嗯,这就是超过 40% 的时间提升。这里的差异只是几毫秒,但想象一下,这 40% 的提升如何转化为更大的数据框。

说到加载时间,我们来看看它们的差异:

%timeit df = pd.read_csv("data.csv")
212 ms ± 8.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit df = pd.read_parquet('data.parquet.gzip')
73 ms ± 1.89 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

在这里我们再次看到一些改进,不过这次提升更大——65%。这意味着在处理更大的数据集时,我们可以节省大量时间。

顺便提一句:还有Feather 格式 (.ftr),虽然我个人没有使用过,但在互联网上看到了很多很好的参考资料,并且有令人印象深刻的分析显示它甚至超过了 parquet,所以也可以试试这个格式。

结论

有很多方法可以优化 pandas,我这里只提到了一些,我从中受益最多的方法。

希望这篇文章对你有启发和帮助。一旦你掌握了这些信息,就没有理由不加以使用!

 **Thanks for reading the post!** 
            I really hope you enjoyed it and found it insightful.

          Follow me for more content like this one, it helps a lot!
                                  **@polmarin**

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

[## 使用我的推荐链接加入 Medium - Pol Marin

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

medium.com](https://medium.com/@polmarin/membership?source=post_page-----5fb89bc1ece9--------------------------------)

资源

[1] pandas — Python 数据分析库

[2] pandas.DataFrame.query — 文档

[3] NumExpr — Github

[4] 数据的索引和选择 — Pandas

[5] Apache Parquet

如何通过 Matplotlib 马赛克提升你的可视化效果

原文:towardsdatascience.com/how-to-enhance-your-visualizations-with-matplotlib-mosaic-adcaf0008aa6

我从 Matplotlib 学到的最酷的一个方法

Gustavo SantosTowards Data Science Gustavo Santos

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

--

微笑吧!马赛克子图来了!图像由作者创建。

介绍

数据可视化非常重要。我们可以仅从这句话中发展出很多内容,但我相信你已经明白了“大致情况”。我相信这不是你第一次听到这些。即使是,假设这也不会让你感到惊讶,对吧?

一张图胜过千次观察。

如果一张图片胜过千言万语,我会说,对数据科学而言,一张图胜过千次观察

无论如何,让我们转到这里的重要内容。这个简短的文章的目标是向你展示subplot_mosaic()方法。这个功能真的很棒。最近我在浏览和阅读一些数据科学相关的内容时看到了它。我惊讶于在同一图中快速创建多个图的容易程度。

让我们在下一部分中看看它是如何完成的。

将使用的数据集是 Python 中 Seaborn 包原生的Tips数据集。

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# Data
df = sns.load_dataset('tips')

马赛克

在马赛克图之前,还有其他方法可以创建子图。这些方法并不难,但它们没有subplot_mosaic()方法那样灵活。你可以阅读下一篇文章来了解我在说什么。

## 使用 Python 在同一图中绘制多个图形

学习如何在同一 matplotlib 图中绘制多个图形

towardsdatascience.com

马赛克方法给我们带来的巨大贡献是灵活性,可以用很少的代码创建许多设计。看看下一个图像:如果我们现在要在 matplotlib 中重现它,我们将使用马赛克函数。

请注意,我们有 3 个图。图 A将在左上方占据一个位置。图 B将在图形的左下方占据一个位置。图 C将在右侧的上方和下方占据两个位置。因此,现在我们只需将其转换为 Python,你会发现这真的很简单。

要在 matplotlib 中创建的马赛克图。图片由作者提供。

让我们来看看代码。我们首先创建一个图形,然后创建一个马赛克变量。马赛克变量可以接受列表或字符串。我认为使用三重引号的字符串更容易,它能“绘制”出最终图形的样子。请注意,代码中ac bc的使用顺序和形状与之前的图形相同。通过在右侧重复两次c,我告诉 matplotlib 我希望图 C 占据图形的那两个位置。其余的代码只是常规的图形创建代码。

如果你查看变量mosaic,你会看到这个:{‘a’: <Axes: label=’a’>, ‘c’: <Axes: label=’c’>, ‘b’: <Axes: label=’b’>}。所以,它创建了一个字典,我们现在只需指定每个图形的位置。

# Plot with Mosaic
fig = plt.figure(layout= 'constrained')
mosaic = fig.subplot_mosaic('''
                            ac
                            bc
                            ''')

# Plot A
mosaic['a'].bar(df.sex, df.tip, color='coral')

# Plot B
mosaic['b'].scatter(df.total_bill, df.tip, color='forestgreen')

# Plot C
mosaic['c'].boxplot(df.tip, patch_artist=True);

这是生成的可视化效果。

上一个代码生成的马赛克图。图片由作者提供。

好的。我希望我已经引起了你的注意。

我们可以创建不同的布局。看看这个。

# Plot with Mosaic
fig = plt.figure(layout= 'constrained', figsize=(12,6))
mosaic = fig.subplot_mosaic('''
                            aaa
                            bcc
                            ''')

# Plot A
mosaic['a'].bar(df.day, df.tip, color='coral')

# Plot B
mosaic['b'].boxplot(df.total_bill, patch_artist=True)

# Plot C
mosaic['c'].scatter(df.total_bill, df.tip, color='forestgreen');

上一个代码生成的马赛克图。图片由作者提供。

或者,这里有另一个例子,包含更多的图形。

# Plot with Mosaic
fig = plt.figure(layout= 'constrained', figsize=(10,6))
mosaic = fig.subplot_mosaic('''
                            ab
                            ac
                            de
                            ''')

# Plot A
mosaic['a'].bar(df.day, df.tip, color='coral')

# Plot B
mosaic['b'].boxplot(df.total_bill, patch_artist=True)

# Plot C
mosaic['c'].scatter(df.total_bill, df.tip, color='forestgreen')

# Plot D
mosaic['d'].scatter(df.tip, df.total_bill, color='purple')

# Plot E
mosaic['e'].scatter(df['size'], df.tip, color='gold');

上一个代码生成的马赛克图。图片由作者提供。

设置标题

但你可能会问自己:那图表标题呢?

好问题,我会说!让我们学习如何设置标题。

第一个选项是最简单的:我们可以通过一行额外的代码为整个图像添加一个标题。

# Adding a single title to the mosaic

plt.suptitle('''        -- P L O T S --
                - Top Left: Sum of tips by Sex -
                - Bottom Left: Tip by Total Bill -
                - Right: Boxplot of Tips - ''')

带有单一标题的马赛克图。图片由作者提供。

另外,我们可能希望为每个图添加一个标题。因此,让我们创建一个循环,遍历每个坐标轴和标题列表,并逐一匹配它们。如果我们调用mosaic.items(),我们将看到一个包含元组的字典,如前所示:(‘a’,<Axes: label=’a’>)。要设置标题,我们需要访问Axes部分——元组中的第二项——因此在代码片段中我们使用ax[1]

这就是如何实现它的。

# Define Titles
titles = ['Sum of tips by Sex', 'Tip by Total Bill', 'Boxplot of Tips']

# One title per plot
for ax, g_title in zip(mosaic.items(), titles):
    ax[1].set_title(g_title, fontstyle='italic')

这就是结果。

每个图都有一个标题的马赛克图。图片由作者提供。

很不错,不是吗?

在你离开之前

我相信在一个图形中绘制多个图形对于比较和展示是有帮助的。而且拥有一种简化操作的方法能够带来很大的不同。

记住这些步骤

  1. 创建图形

  2. 使用 subplot_mosaic() 创建一个马赛克变量,并使用‘’’三重引号‘’’ “绘制”你的马赛克。根据需要多次写入每个图形标签以填充马赛克中的位置。

  3. 使用 plt.suptitle() 或循环添加标题。

就这些了。

如果你喜欢这个内容, 关注我获取更多信息,点击这里。

LinkedIn 上也可以找到我。如果你考虑订阅 Medium 阅读我的帖子以及更多内容,可以考虑使用这个推荐代码,因为这会激励我继续创作内容。

[## 使用我的推荐链接加入 Medium - Gustavo Santos

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

medium.com](https://medium.com/@gustavorsantos/membership?source=post_page-----adcaf0008aa6--------------------------------)

参考

[## matplotlib.pyplot.subplot_mosaic - Matplotlib 3.7.1 文档

基于 ASCII 艺术或嵌套列表构建 Axes 布局。这是一个帮助函数,用于构建复杂的 GridSpec 布局……

matplotlib.org](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.subplot_mosaic.html?source=post_page-----adcaf0008aa6--------------------------------) [## 标记子图 - Matplotlib 3.7.1 文档

标记子图相对简单,方法有所不同,因此 Matplotlib 没有通用的方法来进行……

matplotlib.org](https://matplotlib.org/stable/gallery/text_labels_and_annotations/label_subplots.html?source=post_page-----adcaf0008aa6--------------------------------#sphx-glr-gallery-text-labels-and-annotations-label-subplots-py)

如何估算 Transformer 模型中的参数数量

原文:towardsdatascience.com/how-to-estimate-the-number-of-parameters-in-transformer-models-ca0f57d8dff0?source=collection_archive---------3-----------------------#2023-01-13

深入了解 Transformer 编码器/解码器的构建模块

Dmytro Nikolaiev (Dimid)Towards Data Science Dmytro Nikolaiev (Dimid)

·

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

--

预览。图像由作者提供

感谢Regan Yue,你可以在mp.weixin.qq.comjuejin.cnsegmentfault.comxie.infoq.cn阅读这篇文章的中文版本!

理解新的机器学习架构(以及一般的新技术)的最有效方式是从零开始实现它。这是一种最佳方法,有助于你理解实现的每一个细节,尽管这非常复杂、耗时,并且有时简直不可能。例如,如果你没有类似的计算资源或数据,你将无法确保你的解决方案中没有隐藏的错误。

然而,还有一种更简单的方法 —— 计算参数数量。这并不比仅仅阅读论文更困难,但它允许你深入挖掘,检查你是否完全理解新架构的构建模块(在我们的例子中是 Transformer 编码器和解码器模块)。

你可以通过以下图示来思考这个问题,该图示展示了理解新机器学习架构的三种方法 —— 圆圈的大小表示理解的程度

理解机器学习架构的方法。计算参数数量并不比简单阅读论文更困难,但它会让你更深入地了解这个话题。图片由作者提供

在本文中,我们将查看著名的 Transformer 架构,并考虑如何计算 PyTorch TransformerEncoderLayerTransformerDecoderLayer类中的参数数量。这样,我们可以确保对这个架构的组成没有任何神秘之处。

TL;DR

所有公式总结在结论部分。欢迎你现在就查看它们。

我不仅展示了确切的公式,还展示了它们的不那么准确的近似版本,这将帮助你快速估算任何基于 Transformer 的模型中的参数数量。

Transformer 架构

著名的 Transformer 架构在 2017 年的震撼“Attention Is All You Need” 论文中首次提出,并因其有效捕捉长期依赖关系的能力而成为大多数自然语言处理和计算机视觉任务的事实标准。

现在,在 2023 年初,扩散正在获得极大的关注,主要是由于文本到图像生成模型。也许,很快它们将成为各种任务中的新一代最先进技术,正如之前 Transformers 与 LSTMs 和 CNNs 的对比。但让我们先来看一下 Transformers…

我的文章并不是试图解释 Transformer 架构,因为已经有足够多的文章做得很好。它可能会让你从不同的角度看待它,或澄清一些细节,如果你还没有完全弄清楚的话。因此,如果你想了解更多关于这个架构的资源,我会推荐一些;否则,你可以继续阅读。

了解更多关于 Transformer 的资源

如果你想要更详细的 Transformer 架构概述,可以看看这些资料(请注意,互联网上有很多其他资源,我个人只是喜欢这些):

原始 Transformer

首先,让我们回顾一下 Transformer 的基础。

Transformer 的架构由两个组件组成:编码器(左侧)和解码器(右侧)。编码器接收一系列输入标记并生成一系列隐藏状态,而解码器则接收这些隐藏状态并生成一系列输出标记。

Transformer 架构。图 1 来自公共领域论文

编码器和解码器都由一堆相同的层组成。对于编码器,这一层包括多头注意力 (1 — 这里和后续数字指的是下图) 和一个前馈神经网络 (2),以及一些层归一化 (3) 和跳跃连接。

解码器类似于编码器,但除了第一个 多头注意力 (4)(在机器翻译任务中进行掩蔽,以防止解码器通过查看未来标记来作弊)和一个前馈网络 (5)外,它还具有第二个 多头注意力 机制 (6)。它允许解码器在生成输出时使用编码器提供的上下文。与编码器一样,解码器也有一些层归一化 (7) 和跳跃连接组件。

带有符号组件的 Transformer 架构。改编自 公共领域论文 的图 1。

我将不考虑带有位置编码的输入嵌入层和最终输出层(线性 + softmax)作为 Transformer 组件,只关注编码器和解码器模块。我这样做是因为这些组件特定于任务和嵌入方法,而编码器和解码器堆栈后来形成了许多其他架构的基础。

这类架构的例子包括用于编码器的 BERT 基础模型(BERTRoBERTaALBERTDeBERTa 等),用于解码器的 GPT 基础模型(GPTGPT-2GPT-3ChatGPT),以及基于完整编码器-解码器框架的模型(T5BART 等)。

尽管我们在这个架构中计数了 个组件,但我们可以看到其中只有 三个 是独特的:

  1. 多头注意力;

  2. 前馈网络;

  3. 层归一化。

Transformer 构建模块。改编自 公共领域论文 的图 1。

它们共同构成了 Transformer 的基础。让我们更详细地了解它们!

Transformer 构建模块

让我们考虑每个模块的内部结构及其所需的参数数量。在本节中,我们还将开始使用 PyTorch 来验证我们的计算。

要检查某个模型模块的参数数量,我将使用 以下一行函数

在我们开始之前,请注意所有模块 都经过标准化 并且使用跳跃连接。这意味着 所有输入和输出的形状 (更准确地说,是最后一个数字,因为批量大小和令牌数量可能会有所变化) 必须相同。对于原始论文,这个数字 (d_model) 是 512。

多头注意力

一个著名的注意力机制是 Transformer 架构的关键。但撇开所有动机和技术细节,它实际上只是一些矩阵乘法。

Transformer 多头注意力。改编自 公共领域论文 的图 2。

计算每个head的注意力后,我们将所有 heads 拼接在一起,并通过一个线性层(W_O 矩阵)进行处理。每个 head 是scaled dot-product attention,包括三个分别用于查询、键和值的矩阵乘法(W_QW_KW_V 矩阵)。这三个矩阵对于每个 head 都是不同的,这就是为什么存在下标i

最终线性层(W_O)的形状是d_model 到 d_model。其余三个矩阵(W_QW_KW_V)的形状是相同的:d_model 到 d_qkv

注意,上图中的d_qkv在原论文中表示为d_kd_v。我发现这个名称更直观,因为尽管这些矩阵可能有不同的形状,但几乎总是相同的。

还要注意d_qkv = d_model / num_heads(论文中的h)。这就是为什么d_model必须能够被num_heads整除:以确保后续的正确拼接。

你可以通过检查上图中所有中间阶段的形状(正确的形状在右下角标示)来测试自己。

结果是,我们需要为每个 head 准备三个较小的矩阵和一个大的最终矩阵。我们需要多少参数(不要忘记偏置)?

计算 Transformer 注意力模块中参数数量的公式。图片来源:作者

我希望这不会太枯燥——我尽力使推导尽可能清晰。不要担心!未来的公式会小得多。

近似参数数量是这样的,因为我们可以忽略4*d_model相对于4*d_model²。让我们使用 PyTorch 进行测试。

数字匹配,这意味着我们做得很好!

前馈网络

Transformer 中的前馈网络由两个全连接层组成,中间夹着一个 ReLU 激活函数。网络的内部部分比输入和输出更具表现力(正如我们所记得的,输入和输出必须是相同的)。

一般情况下,它是MLP(d_model, d_ff) -> ReLU -> MLP(d_ff, d_model),而在原论文中,d_ff = 2048。

前馈神经网络描述。 公共领域论文

一点可视化永远不会有害。

Transformer 前馈网络。图片来源:作者

参数的计算非常简单,主要的,还是不要被偏置搞混。

计算 Transformer 前馈网络中参数数量的公式。图片来源:作者

我们可以描述这样一个简单的网络,并使用以下代码检查其参数数量(请注意,官方 PyTorch 实现也使用 dropout*,稍后我们将在编码器/解码器代码中看到。但如我们所知,dropout 层没有可训练的参数,因此为了简洁起见,我在这里省略了它):

数字再次匹配,只剩下一个组件。

层归一化

Transformer 架构的最后一个构建块是层归一化。简而言之,它只是一个智能(即可学习的带有缩放的归一化方法,改善了训练过程的稳定性。

Transformer 层归一化。图片由作者提供

这里的可训练参数是两个向量 gammabeta,每个向量都有一个 d_model 维度。

计算 Transformer 层归一化模块参数数量的公式。图片由作者提供

让我们用代码验证我们的假设。

好的!在近似计算中,这个数字可以忽略,因为层归一化的参数远少于前馈网络或多头注意力块(尽管这个模块出现了几次)。

推导完整公式

现在我们拥有计算整个编码器/解码器块参数所需的一切!

PyTorch 中的编码器和解码器

让我们记住,编码器由一个注意力块、前馈网络和两个层归一化组成。

Transformer Encoder。改编自公共领域论文中的图 1

我们可以通过查看 PyTorch 代码内部来验证所有组件是否到位。这里的多头注意力标记为红色(左侧),前馈网络标记为蓝色层归一化标记为绿色(PyCharm 中的 Python 控制台截图)。

PyTorch TransformerEncoderLayer. 图片由作者提供

如上所述,这个实现包括了前馈网络中的dropout。现在我们也可以看到与层归一化相关的 dropout 层。

解码器则由两个注意力块、一个前馈网络和三个层归一化组成。

Transformer Decoder。改编自公共领域论文中的图 1

让我们再次查看 PyTorch(颜色相同)。

PyTorch TransformerDecoderLayer. 图片由作者提供

最终公式

确认后,我们可以编写以下函数来计算参数数量。实际上,这只是三行代码,甚至可以合并为一行。其余部分是文档字符串,用于说明。

现在是测试的时候了。

精确公式是正确的,意味着我们已经正确识别了所有构建块并将其分解为组成部分。有趣的是,由于我们在近似公式中忽略了相对较小的值(与百万相比的几千),误差仅约为 0.2%,与精确结果相比!但还有一种方法可以使这些公式更简单。

注意力块的近似参数数量是4*d_model²。考虑到d_model是一个重要的超参数,这听起来相当简单。但对于前馈网络,我们需要知道d_ff,因为公式是2*d_model*d_ff

d_ff是一个需要在公式中记住的单独超参数,因此让我们考虑如何摆脱它。实际上,正如我们上面所看到的,当d_model = 512时,d_ff = 2048,所以d_ff = 4d_model*。

对于许多 Transformer 模型,这样的假设是有意义的,大大简化了公式,同时仍然给出一个参数数量的估计。毕竟,没有人想知道确切的数量,只需了解这个数字是在几十万还是几千万就足够了。

近似的编码器-解码器公式。图片由作者提供

要了解您处理的数量级,您也可以对乘数进行四舍五入,得到每个编码器/解码器层为10*d_model²

结论

这里是我们今天推导出的所有公式的总结。

公式回顾。图片由作者提供

在本文中,我们计算了 Transformer 编码器/解码器块的参数数量,但当然,我并不邀请您去计算所有新模型的参数。我之所以选择这种方法,是因为当我开始学习 Transformers 时,发现没有找到这样的文章,这让我感到惊讶。

尽管参数数量可以给我们提供模型复杂性及其训练所需数据量的指示,但这只是深入了解架构的一种方式。我鼓励您探索和实验:查看实现,运行不同超参数的代码等。所以,继续学习并享受其中的乐趣!

感谢您的阅读!

  • 希望这些材料对您有帮助。请在 Medium 上关注我,获取更多类似的文章。

  • 如果您有任何问题或意见,我很高兴收到任何反馈。请在评论中问我,或通过LinkedInTwitter与我联系。

  • 支持我作为作者,并获取访问数千篇其他 Medium 文章的权限,请使用我的推荐链接获得 Medium 会员(对您没有额外收费)。

如何评估学习排序模型

原文:towardsdatascience.com/how-to-evaluate-learning-to-rank-models-d12cadb99d47

评估 LTR 模型的实用指南

Ransaka RaviharaTowards Data Science Ransaka Ravihara

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

--

图片来源 Markus SpiskeUnsplash

我之前的文章解释了三种主要的方法来解决学习排序问题。在本文中,我们将重点讨论如何评估 LTR 模型。让我们开始吧。

我们有几个选项来评估 LTR 模型。然而,这些选项与我们正在使用的方法有所不同。如果目标是为每个文档分配二元相关性评分,我们应使用 二元相关性 指标。如果目标是为每个文档在连续尺度上设置相关性评分,我们应使用 分级相关性。让我们讨论三种广泛使用的评估矩阵类型。

均值平均精度 (MAP)

计算排名结果的 MAP 可能很棘手且常常令人困惑。让我们一步步地查看下面的图示。

MAP 步骤 | 图片来源作者

MAP 有一些潜在的缺点,

  1. 它不考虑检索到的项的排名,只考虑相关文档的存在或不存在。

  2. 对于相关性不是二元的 数据集,这可能不合适,因为它没有考虑项的相关性程度。

我们稍后会通过例子进一步探讨这个问题。

均值倒排排名 (MRR)

一个直接的评估指标。

MRR 指标 | 图片来源作者

MRR 的一个潜在缺点是它只考虑给定查询的第一个相关文档。

归一化折扣累积增益 (NDCG)

这是大多数用例中最常见和理想的评估指标。它考虑了结果的相关性和位置。它可以为高度相关的结果分配更高的分数,并出现在列表的顶部。

NDCG 步骤 | 作者提供的图像

如果考虑 DCG 公式,当文档的相关性高时,分子会增加;当文档的位置增加时,分母会增加。总的来说,当高度相关的项排名靠前时,DCG 值会更高。

手动评估 LTR 模型

假设我们有两个查询,q1q2,我们的 LTR 模型为每个查询分别返回了下面两个文档列表。(注意文档的顺序

q1(d1, d2, d3) q2 → (d4, d5, d6)

此外,根据实际标签,我们注意到只有d1, d5d6对于给定的两个查询是相关的。

让我们计算上面讨论的每一个评估指标。

  • 计算MAP

这里使用的公式是,

作者提供的图像

让我们代入与查询q1相关的值,

作者提供的图像

同样适用于q2

作者提供的图像

最后,我们可以得到下列文档-查询对的 MAP 值。

作者提供的图像

请注意,如果你改变d5d6的顺序,你仍然会得到相同的 MAP 值,因为这不会考虑排名。

  • 计算MAR

我们可以如上所示计算MAR。这不是一个复杂的公式。

作者提供的图像

  • 计算 NDCG(对于查询q2

这里使用的公式是,

作者提供的图像

步骤 01:计算 DCG(q2)

作者提供的图像

步骤 02:计算 IDCG(q2)

作者提供的图像

步骤 03:计算 NDCG(q2)

作者提供的图像

系列下一篇: 如何使用 Python 实现学习排序模型

## 如何使用 Python 实现学习排序模型

使用 Python 和 LightGBM 实现 lambdarank 算法的逐步指南

towardsdatascience.com

结论

还有其他用于评估 LTR 模型的评估指标。这里,我已经解释了广泛使用的指标。但是,选择合适的评估指标往往比较棘手。因为它严重依赖于数据集(主要是目标标签)和我们所解决的问题。

感谢阅读!

如何评估表示

原文:towardsdatascience.com/how-to-evaluate-representations-886ce5ef7a66

从无监督指标到监督指标

Mina GhashamiTowards Data Science Mina Ghashami

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

--

credit: 图片来源于 unsplash.com

嵌入表示,也称为表示,是诸如单词、文档、产品等实体的稠密向量表示。它们旨在捕捉语义含义并突出实体之间的相似性。一组好的表示不仅应有效地编码实体的基本特征,还应具有紧凑性、意义性和在各种任务中表现出的鲁棒性。本文将探讨评估表示质量的各种指标。让我们开始吧。

评估框架

任何评估框架由三个主要组件组成:

  1. 基准方法:这作为一个基准,与新方法或模型进行比较。它提供了评估提出的方法性能的参考点。

  2. 一组评估指标:评估指标是用于评估模型性能的定量度量。这些指标可以是监督的或无监督的,并定义了如何评估输出的成功。

  3. 评估数据集:评估数据集是一个标注/注释或未标注的数据集合,用于评估模型的性能。该数据集应能代表模型预期处理的现实场景。它需要覆盖各种各样的例子,以确保全面的评估。

根据评估指标是否需要真实标签,我们可以将其分为无监督指标监督指标。通常,使用无监督指标更为有利,因为它们不需要标签,而标签的收集在实践中非常昂贵。

下面,我们将深入探讨最先进的指标。对于每个指标,选择一个基线方法以对比你的评估结果。基线可以简单到*随机嵌入生成器*

监督评估指标

监督指标需要一个标记的评估数据集。一个常见的策略是选择一个预测器,例如分类器或回归器。然后在来自特定任务的有限标记数据集上训练预测器。接下来,使用监督指标测量预测器在持出数据集上的表现。

在这里值得提到两点有价值的内容:

1️⃣ *验证准确率*,一个常用的指标,被证明对训练探测器的数据集大小很敏感[3]!!研究表明,验证准确率对训练数据集的大小很敏感;当数据集大小不同时,他们可能会为相同任务选择不同的表示[3]!理想情况下,这种评估必须独立于数据集大小,并且仅依赖于数据分布[3]。

下面的图展示了这一现象:两个表示,一个是红色的(表示 A),另一个是蓝色的(表示 B)。x 轴表示用于训练预测器(称为探测器)的训练数据的大小。y 轴显示了训练好的探测器的验证损失。注意探测器是在每种方法的表示上训练的。正如我们所见,当 x 轴上的训练数据大小增加时,探测器的验证损失在 y 轴上减少。然而,在某些时候,这两条损失曲线交叉了!!因此,在小数据集上,表示 A 的损失较少,而在大数据集上,表示 B 的损失较少。文献[3]将这一曲线称为损失-数据曲线,因为它衡量了预测器的损失与用于训练探测器的训练数据大小的关系。

图 1:损失-数据曲线 — 来源于[3],由作者修改

2️⃣ 线性预测器(线性分类器/回归器)被广泛批评为评估表示的方法[4];研究表明,在线性分类任务上表现强劲的模型在更复杂的任务上可能表现较弱[4]。

文献中提出了许多监督指标;互信息(MI)、F1 得分、BLEU、精确度、召回率、最小描述长度(MDL)等,但在监督评估领域的 SOTA 指标[3,9]是多余描述长度(SDL)ϵ−样本复杂度

多余描述长度(SDL)ϵ−样本复杂度的灵感来源于“最佳表示是允许最有效地学习预测器以解决任务的表示” [3,9]。正如[3]提到的“这一立场受实际问题的驱动;在部署阶段,解决任务所需的标签越多,使用成本越高,表示的适用范围也越小。”*

直观理解:为了给出直观的理解,SDLϵ−样本复杂度指标不是在固定数据大小上衡量一个量,而是估计损失数据曲线,并根据曲线测量量[3]。为此,对于这两个指标,用户指定一个容差ϵ,以便小于ϵ的总体损失被视为解决任务。一个ϵ损失预测器是任何实现损失小于ϵ的预测器[3]。然后,这些指标计算学习一个实现该损失的预测器的成本。

让我们深入了解每个指标的细节:

剩余描述长度 (SDL)

如[3]中所述,该指标对应于计算损失数据曲线与 y = ϵ所设基线之间的区域。换句话说,它衡量了使用表示[3]重新创建一个ϵ损失预测器的成本。从数学上讲,它定义如下

图 2:SDL 定义 — 图片来源于 [3]

在这里,A 是一个算法,它可以是任何预测算法,如分类或回归,L(A, i)指的是在前 i 个数据点上训练算法 A 所导致的预测器损失[3]。计算该指标通常需要无限的数据,因此在实践中总是进行估计[3]。有关此指标的实现,请参见 GitHub 仓库[8]。

图 3:SDL 指标对应于计算损失数据曲线与基线之间的区域 — 图片由作者提供

ϵ−样本复杂度

如[3]中所述,该指标通过找到ϵ损失预测器所需的样本数量来衡量学习一个ϵ损失预测器的复杂度。定义为:

图 4:epsilon-样本复杂度定义 — 图片来源于 [3]

该指标允许通过首先选择一个目标函数进行学习(例如,一个 epsilon 损失的 2 层 MLP 分类器),然后测量哪些表示可以使用更少的数据来学习该函数,从而比较两个表示[3]。

图 5:epsilon-样本复杂度对应于实现 epsilon 损失所需的数据点数量 — 图片由作者提供

无监督评估指标:

这些指标是每个科学家最喜欢的,因为它们不需要标记数据。一些常见的无监督指标包括以下内容:

1) 聚类可学习性 (CL):

该指标[1]评估表示的可学习性。较高的聚类可学习性 (CL) 分数表示学到的表示在输入数据空间中更好地分离了不同的簇。CL 的测量是训练一个 K-最近邻(KNN)分类器的学习速度,该分类器用于预测通过 K 均值聚类表示获得的标签。

图片由作者提供

它由三个主要步骤组成,工作如下:

  1. 选择超参数 k=簇的数量k′ = 邻居的数量。设 x_i​ 表示第 i 个数据点的表示。

  2. 对数据集运行 k-means 以获得 k 个簇;将簇 id 分配给每个数据点 x_i​ 作为其标签。记作 y^_i$​。

  3. 在每个数据点上以预检验方式运行 KNN,通过多数规则获得预测标签。如果一个数据点是 {x_i​, y_​i}​,则取所有编号小于 i 的数据点,即 {x_j​ | j<i },并对其运行 KNN 以获得其类别标签的多数投票。这将是 y~_i​。

那么 Cluster Learnability (CL) 指标为

图 6:Cluster-Learnability 指标 — 图片由作者提供

该指标的直观解释如下: “预检验”方法是一种在在线学习环境中评估机器学习模型性能的方法,例如 KNN。在这种情况下,“预检验”结合了“预测”和“序列”的词汇。在 KNN 计算的预检验方式中,模型是按顺序评估和更新的,一次处理一个实例,使用数据流。由于数据是未标记的,这种方法使用第 2 步中的聚类来合成生成标签(即簇 id)并使用它们以在线(预检验)方式计算簇的学习速度。

以下代码计算该指标:

2) 轮廓系数

类似于 CL 指标,轮廓系数指标测量固有属于一个簇的实体的簇聚合性。该指标通过簇的内聚性和分离性来衡量簇的质量。换句话说,它衡量簇内每个样本与其他簇中的样本的分离程度,并提供簇的紧凑性和良好分离性的指示。轮廓系数的范围从 -1 到 1:

  • 值接近 1 表示样本在簇内聚合良好,并且远离邻近簇中的样本。

  • 值接近 0 表示簇之间重叠或样本位于簇之间的决策边界上或非常接近。

  • 值接近 -1 表示样本可能被分配到错误的簇中。

以下代码计算该指标:

3) 簇纯度

该指标评估从表示中获得的簇的质量。它衡量簇中包含来自单一类别的数据点的程度。计算簇纯度的步骤如下:

  1. 对每个簇,识别该簇中的多数类别。统计每个簇中多数类别的数据点数量。

  2. 将所有簇的这些计数相加。将总和除以数据点的总数。

该值的范围从 0 到 1. 较高的值表示更好的簇,其中 1 表示完美的聚类,每个簇仅包含一个类别的点。

以下代码计算该指标:

总结

在将表示输入任何机器学习模型之前,评估表示的质量是至关重要的。为了评估它们的质量,我们必须有一个由三个组件组成的框架:基线方法、评估指标和评估数据集。指标分为监督型和无监督型,取决于是否需要标记数据集。通常,无监督指标更具优势,因为它们不需要标记数据集,而收集/整理标签既困难又耗时。无监督指标包括集群纯度、集群能力和集群可学习性。另一方面,先进的监督指标是过剩描述符长度(SDL)和ϵ-样本复杂度。无论选择何种指标,保持一致并使用一个单一指标来比较两个表示集是重要的。

如果你有任何问题或建议,请随时与我联系:

邮箱:mina.ghashami@gmail.com

LinkedIn: www.linkedin.com/in/minaghashami/

参考文献

  1. 表达能力与可学习性:评估自监督学习的统一视角

  2. 自监督表示学习:简介、进展与挑战

  3. 通过学习低损失预测器的复杂性来评估表示

  4. 审视最前沿:对视觉表示评估的批判性观察

  5. 对比预测编码的表示学习

  6. 通过最大化视图间的互信息来学习表示

  7. 关于互信息最大化的表示学习

  8. github.com/willwhitney/reprieve

如何评估你的 ML/AI 模型的表现

原文:towardsdatascience.com/how-to-evaluate-the-performance-of-your-ml-ai-models-ba1debc6f2fa

准确的评估是性能改进的唯一途径

Sara A. MetwalliTowards Data Science Sara A. Metwalli

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

--

Scott Graham拍摄于Unsplash

通过实践学习是学习任何事物的最佳方法之一,无论是技术、新语言还是烹饪新菜肴。一旦你掌握了一个领域或应用的基础知识,你可以通过行动来进一步加深理解。构建各种应用的模型是将你对机器学习和人工智能知识变得具体化的最佳方式。

尽管这两个领域(或者说子领域,因为它们确实有重叠)在各种上下文中都有应用,但学习如何构建模型的步骤在不同的目标应用领域中或多或少是相同的。

AI 语言模型如ChatGPTBard正受到科技新手和普通观众的广泛关注,因为它们在我们的日常生活中非常有用。

随着更多模型的发布和展示,人们可能会问,什么才是一个“好的”AI/ML 模型,我们如何评估它的表现?

这就是我们在本文中将要涵盖的内容。但我们假设你已经构建了一个 AI 或 ML 模型。现在,你想要评估并改善其表现(如有必要)。但无论你拥有何种模型和最终应用,你都可以采取措施来评估你的模型并提高其性能。

为了帮助我们理解这些概念,我们将使用来自 sklearn 的Wine数据集[1],应用支持向量分类器(SVC),然后测试其指标。

那么,让我们马上开始吧…

首先,让我们导入我们将使用的库(现在不必担心每个库的功能,我们稍后会讲解!)。

import pandas as pd
import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.metrics import confusion_matrix
from sklearn.metrics import precision_score, recall_score, f1_score, accuracy_score
import matplotlib.pyplot as plt

现在,我们读取数据集,应用分类器,并对其进行评估。

wine_data = datasets.load_wine()
X = wine_data.data
y = wine_data.target 

1. 将数据集拆分以便更好地分析。

根据你学习过程的阶段,你可能需要访问大量的数据来进行训练、测试和评估。此外,你可以使用不同的数据来训练和测试你的模型,因为这将防止你真正评估模型的性能。

为了克服这个挑战,将数据分成三个较小的随机集,并将它们用于训练、测试和验证。

一个好的经验法则是采用 60、20、20 的方法进行拆分。你可以将 60%的数据用于训练,20%用于验证,20%用于测试。你需要在拆分之前打乱数据,以确保数据的更好表示。

我知道这可能听起来很复杂,但幸运的是,scikit-learn提供了一个函数来为你执行这个拆分操作,即 train_test_split()。

因此,我们可以将数据集拆分如下:

X_train, X_test, Y_train, Y_test = train_test_split(X, y, test_size=0.20, train_size=0.60, random_state=1, stratify=y)

然后将训练部分作为输入传递给分类器。

#Scale data
sc = StandardScaler()
sc.fit(X_train)
X_train_std = sc.transform(X_train)
X_test_std = sc.transform(X_test)
#Apply SVC model
svc = SVC(kernel='linear', C=10.0, random_state=1)
svc.fit(X_train, Y_train)
#Obtain predictions
Y_pred = svc.predict(X_test)

到目前为止,我们已经有一些结果可以“评估”。

2. 定义你的评估指标。

在开始评估过程之前,我们必须问自己一个关于我们使用的模型的关键问题:什么会使这个模型变得优秀?

这个问题的答案取决于模型和你打算如何使用它。也就是说,当数据科学家想要测试 AI/ML 模型的性能时,会使用一些标准的评估指标,包括:

  1. 准确率是模型在所有预测中正确预测的百分比。这意味着,当我运行模型时,所有预测中有多少是正确的?这篇文章深入探讨了模型准确率的测试。

  2. 精确度是模型在所有正例预测中真实正例预测的百分比。不幸的是,精确度和准确率经常被混淆;区分它们的一种方法是将准确率视为预测值与实际值的接近程度,而精确度则是正确预测之间的接近程度。因此,准确率是一个绝对指标,但两者都对评估模型性能非常重要。

  3. 召回率是数据集中所有实际正例中真实正例预测的比例。召回率旨在找到数据集中相关的预测。从数学上讲,如果我们提高召回率,就会降低模型的精确度。

  4. F1 分数是精确度和召回率的加权均值,提供了使用精确度和召回率来衡量模型性能的平衡指标。由CodeBasics制作的视频讨论了精确度、召回率和 F1 分数之间的关系,以及如何找到这些评估指标的最佳平衡。

视频由CodeBasics制作

现在,让我们计算预测数据的不同指标。我们将通过首先显示混淆矩阵来实现这一点。混淆矩阵只是数据的实际结果与预测结果的对比。

conf_matrix = confusion_matrix(y_true=y_test, y_pred=y_pred)
#Plot the confusion matrix
fig, ax = plt.subplots(figsize=(5, 5))
ax.matshow(conf_matrix, cmap=plt.cm.Oranges, alpha=0.3)
for i in range(conf_matrix.shape[0]):
    for j in range(conf_matrix.shape[1]):
        ax.text(x=j, y=i,s=conf_matrix[i, j], va='center', ha='center', size='xx-large')
plt.xlabel('Predicted Values', fontsize=18)
plt.ylabel('Actual Values', fontsize=18)
plt.show()

我们数据集的混淆矩阵看起来像这样,

如果我们查看这个混淆矩阵,我们可以看到实际值在某些情况下是“1”,而预测值是“0”。这意味着分类器的准确率不是百分之百。

我们可以使用这段代码计算这个分类器的准确率、精确度、召回率和 F1 分数。

print('Precision: %.3f' % precision_score(Y_test, Y_pred, average='micro'))
print('Recall: %.3f' % recall_score(Y_test, Y_pred, average='micro'))
print('Accuracy: %.3f' % accuracy_score(Y_test, Y_pred))
print('F1 Score: %.3f' % f1_score(Y_test, Y_pred, average='micro'))

对于这个特定的例子,这些结果是:

  1. 精确度 = 0.889

  2. 召回率 = 0.889

  3. 准确率 = 0.889

  4. F1 分数 = 0.889

尽管你可以使用不同的方法来评估你的模型,但一些评估方法会根据模型类型更好地估计模型的性能。例如,除了上述方法之外,如果你评估的模型是回归模型(或包括回归),你还可以使用:

- 均方误差 (MSE) 从数学上讲,是预测值与实际值之间平方差的平均值。

- 平均绝对误差 (MAE) 是预测值与实际值之间绝对差异的平均值。

这两个指标密切相关,但在实现上,MAE(平均绝对误差)比 MSE(均方误差)更简单(至少在数学上)。然而,MAE 对于显著错误表现不佳,而 MSE 强调了这些错误(因为它将误差平方)。

3. 验证和调整模型的超参数。

在讨论超参数之前,让我们首先区分一下超参数和参数。参数是定义模型以解决问题的方式。与之相对的是,超参数用于测试、验证和优化模型的性能。超参数通常由数据科学家(或在某些情况下由客户)选择,以控制和验证模型的学习过程,从而影响其性能。

你可以使用不同类型的超参数来验证你的模型;其中一些是通用的,可以用于任何模型,例如:

  • 学习率: 这个超参数控制模型在更新或更改其参数时需要多少响应来修正某些错误。选择最佳学习率是一个与训练过程所需时间的权衡。如果学习率过低,可能会使训练过程变慢。相反,如果学习率过高,训练过程会更快,但模型性能可能会受到影响。

  • 批量大小: 你的训练数据集的大小将显著影响模型的训练时间和学习率。因此,找到最佳批量大小是一项随着你构建更多模型和积累经验而逐渐培养的技能。

  • 训练轮数: 一次训练轮是训练机器学习模型的一个完整周期。使用的训练轮数因模型而异。从理论上讲,更多的训练轮数会导致验证过程中的错误减少。

除了上述超参数,还有一些特定于模型的超参数,如正则化强度或实现神经网络时的隐藏层数。这个由APMonitor 制作的 15 分钟视频探讨了各种超参数及其差异。

APMonitor 制作的视频

4. 迭代和优化

验证 AI/ML 模型不是一个线性过程,而是一个迭代过程。你需要经过数据划分、超参数调优、分析和验证结果的过程,这一过程往往需要多次重复。你重复这个过程的次数取决于结果的分析。对于某些模型,你可能只需做一次;而对于其他模型,你可能需要做几次。

如果需要重复过程,你将利用之前评估的洞察来改进模型的架构、训练过程或超参数设置,直到你对模型的性能感到满意为止。

结语

当你开始构建自己的 ML 和 AI 模型时,你会迅速意识到,选择和实现模型是工作流中较简单的部分。然而,测试和评估是开发过程中的关键部分。评估 AI/ML 模型是一个迭代且通常耗时的过程,需要仔细分析、实验和微调,以达到预期的性能。

幸运的是,随着你构建更多模型的经验积累,评估模型性能的过程将变得更加系统化。考虑到评估模型的重要性,这是一项值得掌握的技能,例如:

  1. 评估我们的模型使我们能够客观地衡量模型的指标,这有助于了解其优缺点,并提供对其预测或决策能力的洞察。

  2. 如果存在可以解决相同问题的不同模型,那么评估它们使我们能够比较它们的性能,并选择最适合我们应用的模型。

  3. 评估提供了对模型弱点的洞察,通过分析错误和模型表现不佳的领域,允许进行改进。

所以,要有耐心,继续构建模型;随着你构建的模型越来越多,模型的效果和效率也会越来越好。不要让过程中的细节让你气馁。虽然过程看起来复杂,但一旦你了解了步骤,它将变成你的第二天性。

参考资料

[1] Lichman, M. (2013). UCI 机器学习库 加州尔湾:加州大学,

信息与计算机科学学院。(CC BY 4.0)

如何使用迭代滤波评估未报告的流行病感染

原文:towardsdatascience.com/how-to-evaluate-unreported-epidemic-infections-with-iterated-filtering-b7f470c6b6c6?source=collection_archive---------20-----------------------#2023-02-10

使用 TFP 进行基于似然的 POMP 推断实现

Shuyang XiangTowards Data Science Shuyang Xiang

·

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

--

作者提供的图像:一个描述流行病传播的典型 SIR 模型

了解疾病流行的动态对于公共卫生决策者制定进一步的预防措施至关重要。然而,流行病模型的推断可能会很困难,因为在大多数情况下,疾病传播只能部分观察到:不是所有的群体都能被完全观察到。

这种现象的一个例子是 COVID-19 的传播,由于多种可能的原因,仅报告了所有阳性病例的一部分:一些感染者没有严重症状,认为自己没有病毒;一些测试结果为假阴性;一些人不愿接受检测,等等。因此,很明显,每日报告的病例数小于实际感染数。

在这篇博客文章中,我将简要介绍用于推断这类部分观测马尔可夫过程(POMP)的迭代滤波算法,并在特定的部分观测流行病案例上使用 TensorFlow Probability(TFP)的相应 API。我希望突出我用 TFP 实现的算法,该实现几乎没有文档说明 API 的使用。

POMP 的推断

也称为隐状态空间模型或随机动态系统,部分观测马尔可夫过程(POMP)通常包含两个模型组件:一个未观测的马尔可夫过程{X(t; θ) : t ≥ 0},可以是离散的或连续的时间,以及一个观察模型,该模型描述了在离散点收集的数据 Y(t)与未观测状态 X(t)之间的关系。

POMP,图像由 Theresa Stocks 提供

一般来说,POMP 的推断开始于制定一个马尔可夫过程,并通过某种观察模型将观察到的数据与该过程连接起来,然后通过最大化模型的似然来学习模型参数的后验分布。

但这绝不是一件容易的事。

对 POMP 推断的研究量表明了这个问题的重大意义和困难。我们根据三个标准对这些方法进行了分类:即插即用的属性;完全信息还是基于特征;频率学派还是贝叶斯学派。你可以在这里找到可用算法的列表。

迭代滤波

那么,为什么今天我要谈论迭代滤波呢?这是因为,在所有可用方法中,迭代滤波方法是当前唯一可用的、完全信息的、即插即用的、频率学派的方法,用于 POMP 模型,并且它成功地解决了基于似然的推断问题,特别是在对现有贝叶斯方法论计算上不可行的流行病学情况中。

我们可以简单地将迭代滤波理解为名字所示:通过迭代进行滤波。在这里,“滤波”一词可以理解为一种“估计器”,它从噪声数据中提取有关感兴趣数量的信息,参考 Simon Haykin 的自适应滤波理论

我们将简要介绍 Ionides 等人(2015)的 IF2 算法,是的,2006 年有一个 IF1,但今天不讨论。在这个 IF2 算法中,我们输入:(i) 初始状态的先验,(ii) 马尔科夫过程的传输模型,(iii) 描述状态与观察关系的观察模型,(iv) 观察数据,我们希望通过迭代得到模型参数的后验。

  1. 每次迭代包括一个粒子滤波器,对每个粒子进行随机游走,使用参数向量进行操作。

  2. 在时间序列的末尾,参数向量集合被回收作为下一次迭代的起始参数。

  3. 随机游走方差在每次迭代中减少。

我不会详细讲解实现,但强烈建议读者查阅 IF2 算法伪代码。

IF2 伪代码:图片来自wikipedia

部分记录感染的疫情示例

现在让我们考虑以下部分观察的动态示例:由SIR 模型描述的疫情动态,该模型将总人口分为三个部分:易感者、感染者和康复者。各部分之间的进展通过具有两个重要参数的常微分方程进行建模:感染率和康复率。假设只有一部分感染被记录,我们将这部分感染的比例称为报告率。

要了解疾病的进展,我们必须了解上述三个参数:感染率康复率报告率,给定每日报告病例,这些病例,抱歉,我必须再次强调,总是小于实际感染数。

让我们开始了解 SIR 模型的参数,基于作者模拟的合成数据集的每日报告感染情况。下图显示了 100 天内的每日报告病例图。

作者提供的图片:100 天内的报告感染

使用 TFP 进行推断

好消息是,IF2 算法已经由 TFP 实现了tfp.experimental.IteratedFilter,我们可以直接使用。坏消息是,目前没有相关文档。在接下来的章节中,我将解释如何在上述数据集中使用该方法。让我们先看看 API:

tfp.experimental.sequential.IteratedFilter(
    parameter_prior,
    parameterized_initial_state_prior_fn,
    parameterized_transition_fn,
    parameterized_observation_fn,
    parameterized_initial_state_proposal_fn=None,
    parameterized_proposal_fn=None,
    parameter_constraining_bijector=None,
    name=None
)

为了初始化该方法,需要定义四个参数:parameter_prior、parameterized_initial_state_prior_fn、parameterized_transition_fn 和 parameterized_observation_fn。

在我们的示例中,我们将parameter_prior定义为我们感兴趣的三个比率的先验分布,使用tfd.Distribution作为均匀分布。

对于parameterized_initial_state_prior_fn,我们将其定义为一个将参数映射到 SIR 模型中各个部分的函数。

进一步地,我们将parameterized_transition_fn定义为一个描述模型所有部分如何在一个时间步后进展的函数,并且我们要指出,这个函数不过是原始 SIR 模型的离散化版本。

对于parameterized_observation_fn,我们将其定义为一个将 SIR 部分与观察到的报告感染联系起来的函数,也就是说,在每个时间步,报告的感染应该是新报告病例(根据模型,是两个时间步之间易感病例的差异)和报告率的乘积:即reported_case_t=(suspectible_case_{t-1}- suspectible_case_t)*reported_rate

有关详细代码,请参见notebook。一旦所有内容声明完毕,我们可以直接调用iterated_filter.estimate_parameters通过 IF2 算法来学习参数。下面的图表展示了报告率后验的可视化结果。我们可以看到,在这个示例中,只有约 76%的感染病例被报告了。

作者提供的图片:报告率后验的箱线图

结论

在这篇博客文章中,我简要介绍了迭代滤波,并强调了它在 POMP 分析中的重要性,特别是在流行病传播研究中。我使用了 TFP 的 IteratedFilter API,并以部分报告的流行病感染为例。欢迎提出任何问题。

如何通过自定义方法扩展 Pandas 数据框,以增强代码的功能性和可读性

原文:towardsdatascience.com/how-to-extend-pandas-dataframes-with-custom-methods-to-supercharge-code-functionality-readability-f4ae983ad715

逐步指南:如何通过自定义方法扩展 pandas 数据框,包括实现条件概率和期望值扩展的完整示例

Graham HarrisonTowards Data Science Graham Harrison

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

--

照片由 Markus Spiske 拍摄,来自 Unsplash

介绍

问题

Pandas 数据框提供了广泛的内置函数,但 Python 程序员总是希望以新的方式查询和转换数据。

机会

一种常见的方法是编写函数并将数据框作为参数传递,但这可能会变得难以处理,而最佳的方法是直接向 pandas 类添加新的方法和属性。

前进的方向

扩展 pandas 或任何其他库中的类功能非常简单,只需使用自定义方法和属性即可生成易于阅读、理解和重用的代码。

背景

我在为一个因果推断库编写一些代码,这涉及到一些复杂的数据操作,而频繁调用函数使得代码难以阅读和理解。

我想到一个主意,如果 pandas 数据框原生实现了我所编写的函数,那么代码将变得干净、简单且易于理解,从而很容易找到如何扩展 pandas 的方法……

解决方案

我想实现的第一个函数是概率和条件概率。让我们考虑以下数据集……

作者提供的图像

这是一个合成数据集,捕捉了训练、技能和收入之间的关系,每一行代表一个个体,该个体要么接受了训练(或没有),获得了技能(或没有),收入增加了(或没有)。

了解数据后,知道训练是否接受过将会非常有用。

这可以表示为一个数学公式如下:ℙ(𝑡𝑟𝑎𝑖𝑛𝑖𝑛𝑔=1),意思是“训练=1 的概率是多少?”。

传统的做法是编写特定的代码,将 DataFrame 筛选到仅包含 training==1 的行,并计算过滤后的行数占整体数据的比例…

这相当直接,但每次需要计算概率时必须编写特定代码,而且这段代码与概率的关系并不立即显现。如果我们能简单地写 df_training.probability("p(training=1)") 不是更好吗?

好吧,事实证明,这可以用 7 行程序代码完成,无论是任何概率还是条件概率!

概率的完整源代码

以下是完整代码及详细解释…

理解正则表达式

通过使用正则表达式来实现字符串解析,代码已经大大压缩,尽管这不是关于正则表达式的文章,但花点时间理解它们会很有用…

使用像 regex101.com/ 这样的免费解析器来理解正则表达式会更容易。有了 regex101,解析和解释使用的正则表达式变得简单…

  • VARIABLE_REGEX - [a-zA-Z0-9_ "\']+ 匹配变量名,例如 "SurnameField", "ABC123", X, training 等等。

  • OPERATOR_REGEX - =|<|<=|>|>=|!= 匹配常见的数学运算符,即 =, <, <=, >, >=, !=

  • VALUE_REGEX - \'[^\']*\'|\"[^\"]*\"|-?\d+\.?\d* 匹配一个值,例如 48.52, 5, "01/01/2023", 'Hello' 等等。

ASSIGNMENT_REGEX 然后将这些表达式连接起来以识别模式 VARIABLE, OPERATOR, VALUE。

例如…

re.findall(ASSIGNMENT_REGEX, "training=1, income=0")"training=1, income=0" 解析为 [('training', '=', '1'), ('income', '=', '0')]

最终我们有 PROB_REGEX - (?i)^p\(([^|]+)(?:\s*\|\s*)?(.*)?\)$

这用于解析概率表达式和条件概率表达式如下…

re.findall(PROB_REGEX, "p(A=5, B=6)") 返回 [('A=5, B=6', '')]re.findall(PROB_REGEX, "p(A=5, B=6 | X=1, Y=2)") 返回 [('A=5, B=6 ', 'X=1, Y=2')]

概率和条件概率

在这一点上,值得稍微偏离一下,探索条件概率语句。

我们已经确定 ℙ(𝑡𝑟𝑎𝑖𝑛𝑖𝑛𝑔=1) 可以理解为“训练=1 的概率是多少?”

下一个表达式是一个条件概率:ℙ(𝑖𝑛𝑐𝑜𝑚𝑒=1∣𝑡𝑟𝑎𝑖𝑛𝑖𝑛𝑔=1)。

它可以理解为“在 training = 1 的情况下,income = 1 的概率是多少?” 即在我们的数据示例中,“在接受培训的情况下,收入增加的概率是多少?”

表达式中“|”左侧的部分被称为“结果”,右侧的部分称为“事件”,整个表达式 ℙ(𝑜𝑢𝑡𝑐𝑜𝑚𝑒∣𝑒𝑣𝑒𝑛𝑡𝑠) 可以理解为“在事件已经发生的情况下,结果的概率是多少?”

理解函数

所以为了分解我们的代码,让我们从 probability 函数开始...

parse_probability_expr = re.findall(PROB_REGEX, expression) 解析传入的表达式,所以 "p(income=1 | training=1, skills=1)" 被解析为 [('income=1 ', 'training=1, skills=1')]

…因此 parse_probability_expr[0][1].strip() 包含 'training=1, skills=1'parse_probability_expr[0][0].strip() 包含 'income=1'

为了理解函数的其余部分,让我们看看 _filter_dataset 函数...

下一步是解包 _filter_dataset 函数...

_filter_dataset 处理部分概率表达式如 'training=1, skills=1' 并将其转换为 DataFrame 过滤器。

例如 'training=1, skills=1' 被转换为 "(data['training'] == 1) & (data['skills'] == 1)",然后应用到 DataFrame 中以进行过滤。

剩下的代码使用 _filter_dataset 从表达式中计算概率...

还要注意的是,如果没有条件,例如在表达式为 p(training=1) 的情况下,第 1 步仅返回完整数据集,因此第 2 步将正确计算(无条件)概率。

扩展 Pandas DataFrame

剩下要做的就是使用 Python “monkey patching” 语法扩展 DataFrame 类,添加一个新方法...

pd.DataFrame.probability = probability

这实际上为 pandas DataFrame 类添加了一个新方法,任何你创建的 DataFrame 都可以调用新方法来计算数据上的概率和条件概率。

此外,如果完整的源代码存储在一个单独的 Python 模块中,例如 dataframe_extensions.py,那么只需一行导入即可包含这些扩展方法和你编写的任何其他代码...

import dataframe_extensions

测试解决方案

一旦代码编写完成并且 DataFrame 类已被扩展,调用它就变得非常简单和直观...

0.5

0.05

0.1

额外部分 — 期望值

期望值或期望表示随机变量的平均值或长期值。

当我们在 DataFrame 列中有一组连续数据时,这将简单地是均值 - df_training["training"].mean(),这似乎不值得扩展功能。

然而,期望值可以是条件性的,例如 𝔼[𝑖𝑛𝑐𝑜𝑚𝑒|𝑡𝑟𝑎𝑖𝑛𝑖𝑛𝑔=1] 可以理解为“在 training = 1 的情况下,收入的期望值是多少?”

通过重用上面开发的编码模式来扩展 DataFrame 以计算概率,期望值可以在仅 3 行代码中实现为方法扩展…

期望值的完整源代码

这里是一个合成数据集,包含连续数据,并附带一些使用上面代码计算期望值的示例…

图片由作者提供

… 这里是一些测试,展示了期望值扩展的实际效果 …

4.412406593060421

4.429821998698432

3.7051574107849214

结论

通过添加新方法或属性来扩展库类,可以生成易于理解、简洁、清晰且可维护的源代码。

Python 的“猴子补丁”约定可以很容易地用于实现扩展,即使这些类存在于像 pandas 这样的标准外部库中。

通过为扩展类创建一个单独的模块,并将其导入到未来的项目中,Python 开发者可以建立一个扩展库,以帮助提高未来项目的效率和有效性。

连接并保持联系 …

如果你喜欢这篇文章,你可以通过每月仅需$5 成为 Medium 会员,获得对更多文章的无限访问权限,方法是 点击我的推荐链接(如果你通过这个链接注册,我将从费用中获得一部分,但你无需额外付费)。

[## 通过我的推荐链接加入 Medium - 格雷厄姆·哈里森

作为 Medium 会员,你的会员费用的一部分会分配给你阅读的作者,同时你可以完全访问每个故事…

grahamharrison-86487.medium.com](https://grahamharrison-86487.medium.com/membership?source=post_page-----f4ae983ad715--------------------------------)

… 或通过 … 进行连接

订阅免费电子邮件,以便每当我发布新故事时获得通知

快速查看我之前的文章

下载我的免费战略数据驱动决策框架

访问我的数据科学网站 — 数据博客

如何从任何 PDF 和图像中提取文本以用于大型语言模型

原文:towardsdatascience.com/how-to-extract-text-from-any-pdf-and-image-for-large-language-model-2d17f02875e6

使用这些文本提取技术为您的 LLM 模型获取高质量数据

Zoumana KeitaTowards Data Science Zoumana Keita

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

--

图片来自Patrick TomassoUnsplash

动机

大型语言模型已在互联网引起轰动,导致更多的人忽视使用这些模型最重要的部分:高质量的数据!

本文旨在提供一些从任何类型的文档中高效提取文本的技术。完成本教程后,您将对根据使用情况选择工具有一个清晰的了解。

YouTube 上的完整视频讲解

Python 库

本文重点介绍了 Pytesseract、easyOCR、PyPDF2 和 LangChain 库。实验数据是一个单页 PDF 文件,可以在我的GitHub上自由获取。

Pytesseract 和 easyOCR 都处理图像,因此需要在执行内容提取之前将 PDF 文件转换为图像。

转换可以使用pypdfium2完成,这是一种强大的 PDF 文件处理库,下面给出了实现方法:

pip install pypdfium2

该函数以 PDF 作为输入,并返回 PDF 每一页的图像列表。

def convert_pdf_to_images(file_path, scale=300/72):

    pdf_file = pdfium.PdfDocument(file_path)

    page_indices = [i for i in range(len(pdf_file))]

    renderer = pdf_file.render(
        pdfium.PdfBitmap.to_pil,
        page_indices = page_indices, 
        scale = scale,
    )

    final_images = [] 

    for i, image in zip(page_indices, renderer):

        image_byte_array = BytesIO()
        image.save(image_byte_array, format='jpeg', optimize=True)
        image_byte_array = image_byte_array.getvalue()
        final_images.append(dict({i:image_byte_array}))

    return final_images

现在,我们可以使用display_images函数来可视化 PDF 文件的所有页面。

def display_images(list_dict_final_images):

    all_images = [list(data.values())[0] for data in list_dict_final_images]

    for index, image_bytes in enumerate(all_images):

        image = Image.open(BytesIO(image_bytes))
        figure = plt.figure(figsize = (image.width / 100, image.height / 100))

        plt.title(f"----- Page Number {index+1} -----")
        plt.imshow(image)
        plt.axis("off")
        plt.show()

通过结合上述两个函数,我们可以得到以下结果:

convert_pdf_to_images = convert_pdf_to_images('Experimentation_file.pdf')
display_images(convert_pdf_to_images)

以图像格式可视化 PDF(图片来自作者)

现在是深入探索文本提取过程的时候了!

Pytesseract

Pytesseract(Python-tesseract)是一个用于从图像中提取文本信息的 Python OCR 工具,安装可以使用pip命令:

pip install pytesseract

以下辅助函数使用Pytesseractimage_to_string()函数从输入图像中提取文本。

from pytesseract import image_to_string  

def extract_text_with_pytesseract(list_dict_final_images):

    image_list = [list(data.values())[0] for data in list_dict_final_images]
    image_content = []

    for index, image_bytes in enumerate(image_list):

        image = Image.open(BytesIO(image_bytes))
        raw_text = str(image_to_string(image))
        image_content.append(raw_text)

    return "\n".join(image_content) 

文字可以使用extract_text_with_pytesseract函数提取,如下所示:

text_with_pytesseract = extract_text_with_pytesseract(convert_pdf_to_images)
print(text_with_pytesseract)

上述代码的成功执行会生成以下结果:

This document provides a quick summary of some of Zoumana’s article on Medium.
It can be considered as the compilation of his 80+ articles about Data Science, Machine Learning and

Machine Learning Operations.

Whether you are just getting started or you're an experienced professional looking to upskill, these

materials can be helpful.

Data Science section covers basic to advanced
concepts such as statistics, model evaluation
metrics, SQL queries, NoSQL courses, data
visualization using Tableau and #powerbi, and
many more.

Link: httos://Inkd.in/g8zcS_vE

MLOps chapter explains how to build and
deploy models using different strategies such as
Docker containers, and GitHub actions on AWS
EC2 instances, Azure. Also, it covers how to build
REST APIs to serve your models.

Link: httos://Inkd.in/gyiUsdgz

Natural Language Processing Covers simple NLP
concepts to more advanced ones such as
Transformers and their applications in Finance,
Science, etc.

Link: httos://Inkd.in/gBdZbHty

Computer Vision section covers SOTA models
(e.g. YOLO) and different technics to mitigate

overfitting when training computer vision
models.

Link: httos://Inkd.in/gDY8ZqVs

Python section showcases multiple libraries to
facilitate one's daily life, especially when dealing
with PDF, and Word files when scraping data
from the web, and even benchmarking analysis
to help choose the right data processing tool.
Link: https://Inkd.in/gH HUMM9

Pandas & Python Tricks Covers my daily tips and
tricks on LinkedIn. And, there are plenty of those,
especially on my

website https://Inkd.in/gPbichB5
https://Inkd.in/QgUs8inuZ

Machine Learning part is about Fexplainable Al,
clustering, classification tasks, etc.

Link: httos://Inkd.in/gJdSvQns

Pytesseract能够提取图像的内容。

下面是它如何完成这个任务的!

Pytesseract通过识别输入图像中的矩形形状,从右上角到右下角开始。然后提取各个图像的内容,最终结果是这些提取内容的串联。这种方法在处理基于列的 PDF 和图像文档时效果很好。

easyOCR

这也是一个用于光学字符识别的开源 Python 库,目前支持提取 80 多种语言的文本。easyocr需要同时安装PytorchOpenCV,可以使用以下说明进行安装。

!pip install opencv-python-headless==4.1.2.30

根据你的操作系统,Pytorch 模块的安装可能会有所不同。但所有说明都可以在官方页面找到。

现在进行easyocr库的安装。

!pip install easyocr

在使用easyocr时,指定我们正在处理的文档语言是很重要的,因为它支持多种语言。通过其Reader模块指定语言列表来设置语言。

例如,fr表示法语,en表示英语。完整的语言列表可在这里查看。

考虑到这一点,让我们开始这个过程吧!

from easyocr import Reader

# Load model for the English language
language_reader = Reader(["en"])

文本提取过程在extract_text_with_easyocr函数中实现:

def extract_text_with_easyocr(list_dict_final_images):

    image_list = [list(data.values())[0] for data in list_dict_final_images]
    image_content = []

    for index, image_bytes in enumerate(image_list):

        image = Image.open(BytesIO(image_bytes))
        raw_text = language_reader.readtext(image)
        raw_text = " ".join([res[1] for res in raw_text])

        image_content.append(raw_text)

    return "\n".join(image_content)

我们可以按如下方式执行上述函数:

text_with_easy_ocr = extract_text_with_easyocr(convert_pdf_to_images)
print(text_with_easy_ocr)

EasyOCR 结果(作者提供的图像)

相比于Pytesseracteasyocr的结果似乎效率较低。例如,它能够高效地读取前两段文字。然而,它没有将每一块文本视为独立的文本,而是使用基于行的方法进行读取。例如,来自第一块的字符串Data Science section covers basic to advanced与来自第二块的overfitting when training computer vision结合在一起,这种组合完全打乱了文本结构,并偏离了最终结果。

PyPDF

PyPDF2也是一个专门用于 PDF 处理任务的 Python 库,例如文本和元数据检索、合并、裁剪等。

!pip install PyPDF2

提取逻辑在extract_text_with_pyPDF函数中实现:

def extract_text_with_pyPDF(PDF_File):

    pdf_reader = PdfReader(PDF_File)

    raw_text = ''

    for i, page in enumerate(pdf_reader.pages):

        text = page.extract_text()
        if text:
            raw_text += text

    return raw_text
text_with_pyPDF = extract_text_with_pyPDF("Experimentation_file.pdf")
print(text_with_pyPDF)

使用 PyPDF 库的文本提取(作者提供的图像)

提取过程快速且准确,并且甚至保留了原始字体大小。PyPDF 的主要问题是无法高效地从图像中提取文本。

LangChain

来自 langchainUnstructuredImageLoaderUnstructuredFileLoader 模块可用于分别从图像和文本/pdf 文件中提取文本,这两个选项将在本节中进行探讨。

但是,首先,我们需要按照以下步骤安装 langchain 库:

!pip install langchain

从图像中提取文本

from langchain.document_loaders.image import UnstructuredImageLoader.

文本提取函数如下所示:

def extract_text_with_langchain_image(list_dict_final_images):

    image_list = [list(data.values())[0] for data in list_dict_final_images]
    image_content = []

    for index, image_bytes in enumerate(image_list):

        image = Image.open(BytesIO(image_bytes))
        loader = UnstructuredImageLoader(image)
        data = loader.load()
        raw_text = data[index].page_content

        image_content.append(raw_text)

    return "\n".join(image_content)

现在,我们可以提取内容了:

text_with_langchain_image = extract_text_with_langchain_image(convert_pdf_to_images)
print(text_with_langchain_image)

从 langchain UnstructuredImageLoader 提取文本(图像来源:作者)

该库成功地从图像中提取了内容。

从 PDF 中提取文本

以下是从 PDF 中提取内容的实现。

from langchain.document_loaders import UnstructuredFileLoader

def extract_text_with_langchain_pdf(pdf_file):

    loader = UnstructuredFileLoader(pdf_file)
    documents = loader.load()
    pdf_pages_content = '\n'.join(doc.page_content for doc in documents)

    return pdf_pages_content
text_with_langchain_files = extract_text_with_langchain_pdf("Experimentation_file.pdf")
print(text_with_langchain_files)

PyPDF 模块类似,langchain 模块能够在保持原始字体大小的同时生成准确的结果。

从 langchain UnstructuredFileLoader 提取文本(图像来源:作者)

结论

这个简短的教程概述了一些知名库。每个库都有其自身的优点和缺点,应该根据具体用例明智地应用。完整的代码可以在我的 GitHub 上找到 这里

希望这个简短的教程能帮助你获得新的技能。

此外,如果你喜欢阅读我的故事并希望支持我的写作,可以考虑 成为 Medium 会员。只需每月 $5 的承诺,你即可解锁 Medium 上的无限制访问权限。

想请我喝咖啡 ☕️ 吗?→ 这里是链接!

随时可以在 MediumTwitterYouTube 上关注我,或者在 LinkedIn 上打招呼。讨论人工智能、机器学习、数据科学、自然语言处理和 MLOps 相关话题总是很愉快的!

如何找到一个通勤时间短的公寓

原文:towardsdatascience.com/how-to-find-an-apartment-with-a-short-commute-4680e10689d9?source=collection_archive---------8-----------------------#2023-03-09

如何利用数据科学和 Google Maps API 制作自定义的通勤地图

Dávid GuszejnovTowards Data Science Dávid Guszejnov

·

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

--

去年我在马萨诸塞州的剑桥天体物理中心找到了工作。在寻找附近的公寓时,我的一个关键要求是通勤时间不应过长。由于波士顿拥有相当完善的公共交通系统(MBTA)和多个地铁线路,因此并不明显应该在什么样的社区寻找公寓。因此,我决定制作一张大波士顿地区的地图,显示早高峰期间通勤到我的工作地点所需的时间。本文详细介绍了我如何制作这张地图以及你如何为自己制作一张类似的地图。你可以在GitHub上找到源代码。

警告:如果你想做一个类似的项目(即为不同的目的地/城市制作地图),你必须获得自己的 API 密钥。Google Maps 允许你每月进行一定数量的免费调用,因此可以免费完成类似的项目。然而,你必须非常小心,因为超出限制后可能会被收取数百美元的费用。

计算通勤时间

第一步是获取波士顿地区的地图,并在地图上创建与 GPS 坐标(即每个像素的纬度和经度)之间的映射。在这张地图上,我们可以定义一个点网格来计算旅行时间。为了减少 API 调用次数,我选择在中心附近使网格点更密集,而在边缘地区则较为稀疏。请注意,Google Maps 会自动“贴合”到附近的道路,因此我们不需要担心网格点是否真正位于道路上。

一旦我们有了网格,我们只需调用Google Maps并使用其距离矩阵 API来计算到达目的地的旅行时间。然而,计算旅行时间时需要注意一些细节:

  1. 我们需要指定一天中的时间。由于我将在早高峰期间通勤到我的工作地点,所以我设置了到达时间为上午 9 点。

  2. Google Maps 可以提供驾车、骑行和乘坐公共交通的旅行时间。对于我的项目,我只选择了公共交通。

  3. 美国的大多数公共交通形式即使在高峰期间也相当不频繁(例如,公交车每 15 分钟才来一次)。这可能会引入旅行时间计算中的误差,因此为了减少其影响,我决定计算另外两个到达时间(8:45,8:52)的旅行时间,并取三个值中的最小值。这基本上意味着,如果可以避免在公交站等 20 分钟,我愿意稍早一点到工作。

城市地图上的通勤时间叠加图

一旦我们拥有了每个网格点的通勤时间,我们可以通过填充等高线图在城市地图上可视化它们。

根据通勤时间对大波士顿地区的地图进行着色,以剑桥的天体物理中心为蓝色十字标记;图片由作者提供

正如预期的那样,通勤时间随着距离的增加而增加,但我们也可以注意到一些异常情况,某些物理距离更远的点在通勤时间上反而更近。甚至还有一些通勤时间较短的嵌入区域。这是由于 MBTA 网络的结构。例如,住在肯德尔/MIT 地铁站附近,我们可以在 30 分钟内到达目的地,但如果我们住在离目的地更近的几条街道上,我们可能需要乘坐公交车,时间反而更长。还有一些小区域无法到达我们的目的地(例如,东剑桥的火车维修中心)。

波士顿邻里通勤距离

尽管这张地图很有帮助,但如果我们能有一些工具来筛选公寓列表网站上的结果会更好。这些网站大多数会列出每个公寓所属的邻里,让我们可以进行筛选。因此,将我们的通勤时间地图转换为邻里地图是有意义的。首先,我们来制作一张波士顿邻里的地图。

波士顿地区的邻里地图,x 标记了我工作地点的位置;图片由作者提供

我们可以尝试绘制一张波士顿邻里地图,其中任何点到我工作地点(新的原点)的距离与通勤时间成正比(而不是物理距离)。我们可以通过将每个像素相对原点的距离改变为与通勤时间成正比,同时保持其相对方向不变来实现这一点。

phi = np.arctan2(y_image,x_image)
x_new = commute_time * np.cos(phi)
y_new = commute_time * np.sin(phi)

这会导致图像失真,并导致像素大小不均匀(即,一些像素会互相挤在一起,一些则之间有间隙)。我们可以通过制作一个Voronoi 图并根据相应像素的颜色为结果单元上色来纠正这一点。

from scipy.spatial import Voronoi, voronoi_plot_2d
vor = Voronoi(np.vstack((x_new,y_new)).T)
voronoi_plot_2d(vor,ax=ax,show_points=False,show_vertices=False,line_width=0.0)
...
#Colorize the Voronoi plot
for i,region in enumerate(vor.regions):
  color = ...
  polygon = [vor.vertices[k] for k in region]
  plt.fill(*zip(*polygon),c=color)

根据通勤时间缩放的波士顿邻里地图;图片由作者提供

有了这张地图,我们可以快速看到哪些邻里离我们通勤足够近。请注意,相邻邻里的一些区域可能会混合在一起(例如,市中心地区)。这是由于高速公共交通(例如地铁)的存在,使得从离站一站的地方通勤比从离当前站仅 5 分钟步行的公寓通勤更快。

这个项目开始时,我在寻找一个通勤时间不会太长(< 45 分钟)的公寓。这意味着我应该主要寻找位于剑桥、索美维尔、贝尔蒙特、阿灵顿、奥尔斯顿、沃特镇和市中心的公寓。当然,买房时还有其他考虑因素(租金、噪音等)。最终,我租了一套位于剑桥和阿灵顿交界处的公寓。

如果你想尝试在你的城市做同样的事情,请查看GitHub上的源代码。

如何为你的数据找到最佳的理论分布

原文:towardsdatascience.com/how-to-find-the-best-theoretical-distribution-for-your-data-a26e5673b4bd

了解基础数据分布是数据建模的一个重要步骤,并且有许多应用,例如异常检测、合成数据创建和数据压缩。

Erdogan TaskesenTowards Data Science Erdogan Taskesen

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

--

作者提供的图片。

了解数据的基础(概率)分布有许多建模上的优势。确定基础分布的最简单方法是通过直方图可视化随机变量。利用候选分布,可以创建各种图形,例如概率分布函数图(PDF/CDF)和 QQ 图。然而,为了确定精确的分布参数(例如 loc、scale),必须使用定量方法。在本博客中,我将描述为什么确定数据集的基础概率分布很重要。参数分布和非参数分布之间的区别是什么。如何使用定量方法确定最佳拟合,并通过可视化检查来确认。 分析使用distfit库进行,提供了一个笔记本,方便访问和实验。

如果你觉得这篇文章对你有帮助,欢迎 关注我 ,因为我会写更多关于数据科学的内容。如果你考虑购买 Medium 会员,你可以通过使用我的推荐链接来稍微支持我的工作。这和一杯咖啡的价格一样,但这允许你每月阅读无限的文章。

分布拟合和概率密度函数的重要性。

概率密度函数 是统计学中的一个基本概念。简言之,对于一个给定的随机变量 X,我们旨在指定函数 f,以自然描述 X 的分布。有关概率密度函数的更多信息,请参见底部的术语部分。 尽管有大量优质材料描述这些概念[1] 但了解为何知道数据集的基础数据分布仍然具有挑战性。让我用一个小的类比来解释其重要性。假设你需要从地点 A 前往地点 B,你会更倾向于选择哪种类型的车? 答案很简单。你可能会首先探查地形。有了这些信息,你可以选择最适合的车(跑车、四轮驱动等)。从逻辑上讲,跑车更适合平滑的、平坦的地形,而四轮驱动更适合崎岖的山地地形。换句话说,没有地形的探索分析,很难选择最合适的车。然而,这样的探索步骤在数据建模中往往容易被忽视或遗忘。

在做建模决策之前,你需要了解数据的基础分布。

在数据方面,探索数据的基本特征也很重要,例如偏斜度、峰度、异常值、分布形状、单变量、双峰等。根据这些特征,决定使用哪些模型会更容易,因为大多数模型对数据有先决条件。例如,一个众所周知且流行的技术是主成分分析(PCA)。该方法计算协方差矩阵,并要求数据在进行 PCA 时必须是多变量正态分布。此外,PCA 对异常值也很敏感。因此,在进行 PCA 步骤之前,你需要知道数据是否需要(对数)归一化,或者是否需要去除异常值。有关 PCA 的更多细节,请参见这里 [2]。

直方图可以帮助建立直观感。

直方图是数据分析中一个众所周知的图表,它是数据集分布的图形表示。直方图总结了落在各个区间内的观察次数。使用如matplotlib hist()等库,能够轻松地进行数据的可视化检查。改变区间数量的范围将有助于识别直方图的形状是否像常见的概率分布。检查还会提供数据是否对称或偏斜,是否有多个峰值或异常值的线索。在大多数情况下,你会观察到如图 1 所示的分布形状。

  • 正态分布 的钟形。

  • 指数分布或帕累托分布 的递减或递增形状。

  • 均匀分布 的平坦形状。

  • 复杂的形状,即不符合任何理论分布的形状(例如,多峰)。

图 1. 常见分布类型。图形使用 distfit 库创建,并可以在 Colab 笔记本中进一步探索(链接见底部)。(作者提供的图片)。

如果你发现具有多个峰值的分布(二峰或多峰),这些峰值在不同的箱数下不应消失。双峰分布通常提示混合人群。此外,如果你观察到某个值或小范围值的密度出现大幅度峰值,这可能指向潜在的异常值。异常值预计会远离其他密度。

直方图是检查相对较少样本(随机变量或数据点)的一个好方法。然而,当样本数量增加或绘制多个直方图时,视觉效果可能会变得麻烦,与理论分布的视觉比较也会很困难。相反,累积分布函数(CDF)图或分位数-分位数图(QQ 图)可能更具洞察力。但这些图需要与经验数据分布最匹配(或拟合)的候选理论分布。因此,让我们在下一节中确定最佳理论分布!有关随机变量和理论分布的更多信息,请参见底部的术语部分。

确定理论分布的四个步骤。

可以通过四个步骤为经验数据分布发现 PDF 拟合。

  1. 从直方图中计算密度和权重。 第一步是将数据展平成数组,并通过将观测值分组到箱中并计算每个箱中的事件数来创建直方图。箱数的选择很重要,因为它控制了分布的粗糙程度。尝试不同的箱宽可以为相同的数据提供多个视角。在distfit中,箱宽可以手动定义或根据观测值自身数学确定。后者是默认选项。

  2. 从数据中估计分布参数。 在参数化方法中,下一步是根据(选择的)理论分布估计形状、位置和尺度参数。这通常涉及诸如最大似然估计(MLE)等方法,以确定最能拟合数据的参数值。例如,如果选择了正态分布,MLE 方法将估计数据的均值和标准差。

  3. 检查拟合优度。一旦参数被估计出来,理论分布的拟合程度需要评估。这可以通过拟合优度检验来完成。常用的统计检验包括残差平方和(RSS,也称为 SSE)、Wasserstein 检验、Kolmogorov-Smirnov 检验以及能量检验(distfit 中也可用)

  4. 选择最佳理论分布。 此时,通过拟合优度检验统计量对理论分布进行测试和评分。现在可以对这些评分进行排序,并选择评分最高的理论分布。

最后一步,可以使用交叉验证、重采样或保留数据集等方法验证模型。检查模型是否有很好的泛化能力,并检查假设,如独立性和正态性是否满足。一旦理论分布经过拟合和验证,它可以用于许多应用(请继续阅读下面的部分)。

在数据科学领域,分布拟合有很大好处。它不仅可以更好地理解、探索和准备数据,还可以带来快速而轻量的解决方案。

distfit 库为你的数据找到最佳拟合。

Distfit 是一个用于 单变量分布的概率密度拟合 的 Python 包。它可以找到参数化、非参数化和离散分布的最佳拟合。此外,它还通过各种图表提供可视化洞察,以帮助做出更好的决策。以下是最重要功能的总结:

使用 distfit 库,你可以仅用几行代码轻松找到最佳理论分布。

  • 找到最佳拟合 参数化、非参数化和离散分布

  • 预测 异常值/新奇样本(新)[10]

  • 基于拟合的分布生成合成数据

  • 绘图: 直方图、概率密度函数图、累积分布函数图(CDF)、直方图、分位数-分位数图(QQ 图)、概率图以及总结图。

  • 保存和加载 模型。

安装 简单明了,可以通过 PyPi 完成。

pip install distfit

如何使用参数拟合来确定最佳拟合?

使用参数拟合时,我们对输入数据的总体分布参数做出假设。换句话说,直方图的形状应与任何已知的理论分布匹配。参数拟合的优点 是计算效率高,结果易于解释。缺点 是当样本量较小时,对异常值较为敏感。distfit 库可以在 89 种理论分布中确定最佳拟合,这些理论分布来自 scipy 库。为了评分拟合效果,有四种 拟合优度 统计检验:残差平方和(RSS 或 SSE)、Wasserstein、Kolmogorov-Smirnov(KS)和 Energy。对于每个拟合的理论分布,返回 loc、scale 和 arg 参数,如正态分布的均值和标准差。

找到数据集的最佳匹配理论分布需要进行拟合优度统计检验。

在下面的示例中,我们将从正态分布中生成数据,mean=2standard deviation=4。我们将使用distfit从数据本身来估计这两个参数。如果你已经知道分布的家族(例如,钟形分布),你可以指定一个分布子集。默认情况下是常见分布的子集(如图 1 所示)。注意,由于随机成分,结果可能与我展示的有所不同。

# Import libraries
import numpy as np
from distfit import distfit

# Create random normal data with mean=2 and std=4
X = np.random.normal(2, 4, 10000)

# Initialize using the parametric approach.
dfit = distfit(method=’parametric’, todf=True)

# Alternatively limit the search for only a few theoretical distributions.
dfit = distfit(method='parametric', todf=True, distr=['norm', 'expon'])

# Fit model on input data X.
dfit.fit_transform(X)

# Print the bet model results.
dfit.model
# 'stats': 'RSS',
# 'name': 'loggamma',
# 'params': (761.2276, -725.194369, 109.61),
# 'score': 0.0004758991728293508,
# 'loc': -725.1943699246272,
# 'scale': 109.61710960155318,
# 'arg': (761.227612981012,),
# 'CII_min_alpha': -4.542099829801259,
# 'CII_max_alpha': 8.531658573964933
# 'distr': <scipy.stats._continuous_distns.loggamma_gen>,
# 'model': <scipy.stats._distn_infrastructure.rv_continuous_frozen>,

检测到的最佳拟合(即,具有最低 RSS 分数)是loggamma分布。最佳拟合的结果存储在dfit.model中,但我们也可以检查所有其他 PDF 的拟合情况,如dfit.summary中所示(见下面的代码部分),并创建一个图(图 2)。

# Print the scores of the distributions:
dfit.summary[['name', 'score', 'loc', 'scale']]
#         name     score                loc         scale
#0     loggamma  0.000476         -725.19437     109.61711
#1            t   0.00048           2.036554      3.970414
#2         norm   0.00048           2.036483      3.970444
#3         beta  0.000481         -72.505842    133.797587
#4        gamma  0.000498        -304.071325      0.051542
#5      lognorm  0.000507        -325.188197    327.201051
#6   genextreme  0.001368           0.508856      3.947172
#7     dweibull  0.005371           2.102396      3.386271
#8      uniform  0.079545         -12.783659     30.766669
#9        expon  0.108689         -12.783659     14.820142
#10      pareto  0.108689 -1073741836.783659  1073741824.0

# Plot the RSS of the fitted distributions.
dfit.plot_summary()

图 2. PDF 按拟合优度评分测试排序。(作者提供的图片)

但是,为什么正态分布在我们生成了随机正态数据的情况下,仍然没有最低的残差平方和呢?

首先,我们的输入数据集总是有限的,范围在一个(狭窄的)区间内。相反,理论(正常)分布在两个方向上都趋向于无限。其次,所有的统计分析都基于模型,而所有模型只是对现实世界的简化。换句话说,为了逼近理论分布,我们需要使用多种统计测试,每种测试都有其自身的(不)优点。最后,一些分布具有非常灵活的特性,其中(对数)伽马分布就是一个明显的例子。对于大的 k,伽马分布会趋近于正态分布 [4]。

结果是,排名前 7 的分布具有类似且较低的 RSS 分数,其中包括正态分布。我们可以在总结统计中看到,正态分布的估计参数为loc=2.036scale=3.97,这非常接近我们最初生成的随机样本(mean=2std=4)。综合来看,这是一个非常好的结果。

进行自助法以增加信心。

我们可以进一步使用自助法(bootstrapping)和 Kolmogorov-Smirnov (KS) 测试来验证我们拟合的模型,以评估拟合的优度 [9]。如果模型过拟合,KS 测试将揭示自助样本和原始数据之间的显著差异,表明模型不能代表潜在分布。在distfit中,n_bootst 参数可以在初始化时或之后设置(见代码部分)。

# Set bootstrapping during initialization.
# dfit = distfit(method='parametric', n_boots=100)

# Bootstrapping
dfit.bootstrap(X, n_boots=100)

# Print
print(dfit.summary[['name', 'score', 'bootstrap_score', 'bootstrap_pass']])

#           name     score  bootstrap_score bootstrap_pass
# 0     loggamma  0.000218             0.42           True
# 1         beta  0.000217             0.36           True
# 2            t  0.000248             0.08           True
# 3         norm  0.000247             0.07           True
# 4      lognorm  0.000311             0.04          False
# 5        gamma   0.00029             0.01          False
# 6   genextreme  0.000874             0.00          False
# 7     dweibull  0.001651             0.00          False
# 8      uniform  0.073118             0.00          False
# 9        expon  0.104076             0.00          False
# 10      pareto  0.104076             0.00          False

# Plot the RSS and bootstrap scores of the fitted distributions.
dfit.plot_summary()

图 2B. 自助法结果。PDF 按自助分数和拟合优度测试排序。(作者提供的图片)

图中左侧坐标轴显示了拟合优度检验,右侧坐标轴(橙色线)显示了自助法结果。自助法分数是介于 [0, 1] 之间的值,描绘了自助法样本与 PDF 的拟合成功率。此外,绿色和红色点表示自助样本与原始数据之间是否存在显著差异。自助法测试现在排除了几个显示不稳定结果的 PDF。

现在明白统计测试只是帮助你朝正确方向看待问题是很好的,并且选择最佳模型不仅仅是一个统计问题;它也是一个建模决策 [5] 请思考这个问题:loggamma 分布的尾部严重右偏,而 normal 分布是对称的(两个尾部相似)。这在使用置信区间和预测尾部的异常值时可能会产生巨大差异。明智地选择分布,以确保它与应用程序匹配。

选择最佳模型不是一个统计问题,而是一个建模决策。

绘图指导我们做出更好的决策。

最佳实践是同时使用统计数据和可视化策划来决定最佳的分布拟合。使用 PDF/CDF 和 QQ 图可以是指导这些决策的最佳工具之一。例如,图 2 说明了拟合优度检验统计量,其中前 7 个 PDF 的 RSS 分数非常相似且较低。dweibull 分布排在第 8 位,也具有较低的 RSS 分数。然而,视觉检查会告诉我们,尽管 RSS 分数相对较低,它仍然不是一个好的拟合。

让我们开始使用直方图和 PDF. 绘制经验数据。这些绘图将帮助直观地指导分布是否合适。我们可以在图 3 中看到左侧的 PDF 和置信区间,右侧是 CDF 图。置信区间自动设置为 95% CII,但可以在初始化时使用 alpha 参数进行更改。在使用绘图功能时,它会自动显示直方图,包括条形图以及 PDF/CDF 和置信区间。这些属性都可以手动自定义(见下面的代码部分)。

import matplotlib.pyplot as plt
# Create subplot
fig, ax = plt.subplots(1,2, figsize=(25, 10))
# Plot PDF with histogram
dfit.plot(chart='PDF', ax=ax[0])
# Plot the CDF
dfit.plot(chart='CDF', ax=ax[1])

# Change or remove properties of the chart.
dfit.plot(chart='PDF', 
          emp_properties=None,
          bar_properties=None,
          pdf_properties={'color': 'r'},
          cii_properties={'color': 'g'})

图 3. 两个面板中都显示了带有置信区间的经验数据分布。左侧面板:带 PDF 的直方图。右侧面板:CDF。 (作者提供的图片)

我们还可以使用n_top参数绘制所有其他估计的理论分布。通过视觉检查可以确认,前 8 个分布与实际数据非常接近,仅有少数例外。注意到引导法揭示了并非所有的拟合都是稳定的。 图例中的分布从最佳拟合(最高)到最差拟合(最低)进行排名。在这里我们可以看到dweibull分布的拟合非常差,中间有两个峰值。仅使用 RSS 分数很难判断是否使用这个分布。分布uniformexponentpareto显示出较差的 RSS 分数,现在通过图示确认了这一点。

# Create subplot
fig, ax = plt.subplots(1,2, figsize=(25, 10))
# Plot PDF with histogram
dfit.plot(chart='PDF', n_top=11, ax=ax[0])
# Plot the CDF
dfit.plot(chart='CDF', n_top=11, ax=ax[1])

图 4. 左侧:PDF,右侧:CDF。所有拟合的理论分布以不同颜色显示。(作者提供的图片)

分位数-分位数图。

还有一个我们可以检查的图是 QQ 图。QQ 图通过将经验概率分布与理论概率分布的分位数进行对比,来比较它们。如果两个分布相等,则 QQ 图上的点将完美地位于直线y = x上。我们可以使用qqplot函数生成 QQ 图(图 5)。左侧面板显示最佳拟合,右侧面板包含所有拟合的理论分布。有关如何解读 QQ 图的更多细节,请参阅这个博客[3]。

# Create subplot
fig, ax = plt.subplots(1,2, figsize=(25, 10))
# Plot left panel with best fitting distribution.
dfit.qqplot(X, ax=ax[0])
# plot right panel with all fitted theoretical distributions
dfit.qqplot(X, n_top=11, ax=ax[1])

图 5. 左侧面板:经验分布与最佳理论分布的比较。右侧面板:经验分布与所有其他理论分布的比较。(作者提供的图片)

使用非参数拟合识别最佳分布拟合。

非参数密度估计是当总体样本是“无分布的”时,即数据不类似于常见的理论分布。在distfit中,实施了两种非参数方法用于非参数密度拟合:分位数方法和百分位数方法。这两种方法都假设数据不遵循特定的概率分布。对于分位数方法,对数据的分位数进行建模,这对于具有偏斜分布的数据可能很有用。对于百分位数方法,对百分位数进行建模,这对于数据包含多个峰值时可能很有用。在这两种方法中,优点是对异常值具有鲁棒性,并且不对潜在分布做出假设。在下面的代码部分,我们使用method='quantile'method='percentile'进行初始化。所有功能,如预测和绘图,可以以与前面的代码部分相同的方式使用。

# Load library
from distfit import distfit

# Create random normal data with mean=2 and std=4
X = np.random.normal(2, 4, 10000)

# Initialize using the quantile or percentile approach.
dfit = distfit(method='quantile')
dfit= distfit(method='percentile')

# Fit model on input data X and detect the best theoretical distribution.
dfit.fit_transform(X)
# Plot the results
fig, ax = dfit.plot()

识别离散数据的最佳分布。

如果随机变量是离散的,distfit 库 包含 离散拟合 的选项。最佳拟合是通过使用二项分布得到的。问题可以总结如下:给定一组非负整数,我们能否为离散分布拟合一个概率分布,并比较拟合的质量?对于离散量,正确的术语是概率质量函数(PMF)。就离散分布而言,一个整数列表的 PMF 的形式为 P(k),只能拟合到二项分布中,使用适当的 np 值,这种方法在 distfit 中实现。请参见下面的代码部分,其中创建了一个离散数据集,n=8p=0.5。随机变量作为输入传递给 distfit,它检测到参数 n=8p=0.501366,表明拟合非常好。

# Load library
from scipy.stats import binom
from distfit import distfit

# Parameters for the test-case:
n = 8
p = 0.5
# Generate 10000 randon discrete data points of the distribution of (n, p)
X = binom(n, p).rvs(10000)
# Initialize using the discrete approach.
dfit = distfit(method='discrete')
# Find the best fit.
dfit.fit_transform(X)

# print results
print(dfit.model)
# 'name': 'binom',
# 'score': 0.00010939074999009602,
# 'chi2r': 1.5627249998585145e-05,
# 'n': 8,
# 'p': 0.501366,
# 'CII_min_alpha': 2.0,
# 'CII_max_alpha': 6.0}
# 'distr': <scipy.stats._discrete_distns.binom_gen at 0x14350be2230>,
# 'model': <scipy.stats._distn_infrastructure.rv_discrete_frozen at 0x14397a2b640>,

# Make predictions
results = dfit.predict([0, 2, 8])

使用 plot 功能绘制结果。

# Plot the results
dfit.plot()

# Change colors or remove parts of the figure.
# Remove emperical distribution
dfit.plot(emp_properties=None)
# Remove PDF
dfit.plot(pdf_properties=None)
# Remove histograms
dfit.plot(bar_properties=None)
#Remove confidence intervals
dfit.plot(cii_properties=None)

图 7。顶部图展示了输入数据(黑点)和拟合的分布(蓝线)。检测到的参数(n=8 和 p=0.501366)非常贴合,考虑到生成的随机变量(n=8,p=0.5)。红色垂直条表示置信区间,设置为 0.05(默认)。底部图展示了在 n 次扫描中的 RSS 分数。最佳拟合通过最低的 RSS 来检测。(图片由作者提供)

分布拟合的应用。

知道数据集中的基础分布在许多应用中是关键。我将总结几个应用。

  • 异常/新颖性检测 是密度估计的一个明显应用。这可以通过计算给定分布和参数的置信区间来实现。distfit 库计算置信区间,以及给定拟合分布的样本为异常值/新颖性的概率。代码部分展示了一个小示例,但请跟随链接深入了解。

使用分布拟合进行单变量数据集的异常检测

学习如何使用概率密度函数来检测异常值,这种方法适用于快速轻量级的模型,并且具有解释性……

towardsdatascience.com](/outlier-detection-using-distribution-fitting-in-univariate-data-sets-ac8b7a14d40e?source=post_page-----a26e5673b4bd--------------------------------)

# Import libraries
import numpy as np
from distfit import distfit

# Create random normal data with mean=2 and std=4
X = np.random.normal(2, 4, 10000)

# Initialize using the parametric approach (default).
dfit = distfit(multtest='fdr_bh', alpha=0.05)

# Fit model on input data X.
dfit.fit_transform(X)
# With the fitted model we can make predictions on new unseen data.
y = [-8, -2, 1, 3, 5, 15]
dfit.predict(y, todf=True)

# Print results
print(dfit.results['df'])
#      y   y_proba y_pred         P
# 0  -8.0  0.017455   down  0.005818
# 1  -2.0  0.312256   none  0.156128
# 2   1.0  0.402486   none  0.399081
# 3   3.0  0.402486   none  0.402486
# 4   5.0  0.340335   none  0.226890
# 5  15.0  0.003417     up  0.000569

# Plot the results
dfit.plot()
  • 合成数据:概率分布拟合可以用于生成模拟现实数据的合成数据。通过使用现实数据拟合概率分布,可以生成合成数据,用于测试假设和评估算法性能。下面的代码部分展示了如何通过估计分布参数从正态分布中生成随机变量的一个小例子。深入细节可以在这个博客中找到:

## 逐步指南生成合成数据,通过从单变量分布中抽样

了解如何创建合成数据,以备项目数据不足时使用,或用于模拟。

towardsdatascience.com

# Import libraries
import numpy as np
from distfit import distfit

# Create random normal data with mean=2 and std=4
X = np.random.normal(2, 4, 10000)

# Initialize using the parametric approach (default).
dfit = distfit()

# Fit model on input data X.
dfit.fit_transform(X)

# The fitted distribution can now be used to generate new samples.
X_synthetic = dfit.generate(n=1000)
  • 优化和压缩:概率分布拟合可以用于优化概率分布的各种参数,如均值和方差,以最适合数据。找到最佳参数有助于更好地理解数据。此外,如果数十万次观察只需用 loc、scale 和 arg 参数描述,那么这是对数据的强大压缩。

  • 非正式调查输入数据集的属性是密度估计的非常自然的应用。密度估计可以提供数据偏斜和多模态的有价值指示。在某些情况下,它们将得出可能被认为是自明的结论,而在其他情况下,它们将指引进一步分析和数据收集的方向。

  • 检验假设:概率分布拟合可以用于检验关于数据集潜在概率分布的假设。例如,可以使用拟合优度检验将数据与正态分布进行比较,或使用卡方检验将数据与泊松分布进行比较。

  • 建模:概率分布拟合可以用于建模复杂系统,如天气模式、股票市场趋势、生物学、种群动态和预测性维护。通过将概率分布拟合到历史数据中,可以提取有价值的见解,并创建一个可以用来预测未来行为的模型。

结束语。

我涉及了参数化、非参数化和离散随机变量的概率密度拟合概念。使用distfit库,可以轻松检测 89 种理论分布中的最佳理论分布。它管道化了直方图的密度估计、分布参数的估计、拟合优度测试,并返回最佳拟合分布的参数。最佳拟合可以通过各种绘图功能进行探索,如直方图、CDF/PDF 图和 QQ 图。所有图表均可自定义并轻松组合。此外,还可以对新的未见样本进行预测。另一项功能是使用拟合模型参数生成合成数据。

在数据科学领域,了解分布拟合具有重要的好处。这不仅有助于更好地理解、探索和准备数据,还能提供快速而轻量的解决方案。要认识到统计测试只是帮助你朝正确方向查看,而选择最佳模型不是一个统计问题;它是一个建模决策。 明智地选择你的模型。

保持安全,保持冷静。

致敬 E.

如果你觉得这篇文章有帮助,欢迎 关注我 ,因为我写更多关于数据科学的内容。如果你考虑购买 Medium 会员,可以通过我的推荐链接支持我的工作。价格与一杯咖啡相同,但这允许你每月阅读无限数量的文章。

软件

让我们联系吧!

术语。

本博客中的定义/术语:

“随机变量是其值未知的变量或一个将值分配给实验结果的函数。随机变量可以是离散的(具有特定值)或连续的(在连续范围内的任何值)” [1]。它可以是数据集中某个特定特征的单列,例如人类身高。也可以是通过传感器测量的整个数据集,包含成千上万个特征。

概率密度函数(PDF)是一种统计表达式,定义了连续随机变量的概率分布(结果的可能性)。正态分布是 PDF 的一个常见示例(著名的钟形曲线)。PDF 有时也被称为“分布函数”或“概率函数”。

理论分布是 PDF 的一种形式。理论分布的示例包括正态分布、二项分布、指数分布、泊松分布等 [7]。distfit 库包含 89 种理论分布。

经验分布(或数据分布)是观察到的随机变量(输入数据)的频率基础分布 [8]。直方图常用于可视化经验分布。

参考文献

  1. W. Kenton, 概率密度函数(PDF)的基础知识及示例, 2022, Investopedia.

  2. E. Taskesen, PCA 负载是什么以及如何有效使用双图? Medium 2022.

  3. P. Varshney, Q-Q 图解**, Medium 2020.

  4. 伽玛分布,维基百科。

  5. A. Downey, 你的数据是否符合正态分布?提示:不符合。 2018,博客。

  6. 概率密度函数,维基百科。

  7. 概率分布列表,维基百科

  8. 经验分布函数,维基百科

  9. G. Jogesh Babu, Eric D. Feigelson, 天体统计学:拟合优度及其他,ASP 会议系列,第 351 卷,2006 年

  10. E. Taskesen, 通过分布拟合在单变量数据集中检测异常值,Medium 2023

  11. E. Taskesen, 逐步指南:通过从单变量分布中抽样生成合成数据,Medium 2023

如何寻找独特的数据科学项目创意,使你的作品集脱颖而出

原文:towardsdatascience.com/how-to-find-unique-data-science-project-ideas-that-make-your-portfolio-stand-out-1c2ddfdbefa6

忘记泰坦尼克号和 MNIST 吧:选择一个独特的项目,提升你的技能并帮助你从人群中脱颖而出

马特·查普曼Towards Data Science 马特·查普曼

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

--

图片由鲁珀特·布里顿Unsplash提供

如果你想在数据科学领域建立职业生涯,副项目是展示你的技能和提高获得工作或晋升机会的可靠方式。

然而,到 2023 年,仅仅制作一个泰坦尼克号生存预测器或 MNIST 数字分类器已不再足够。这些项目已经做了无数次,并且对你的个人档案贡献很小,因为雇主无法验证这些工作是否是你自己的。对他们来说,你可能只是从某个随机的 Kaggle Grandmaster 那里复制并粘贴了代码。

从人群中脱颖而出的最佳方式是通过一系列独特有趣的项目来展示你的技能和兴趣。但是,如果你像我一样,你会知道提出项目创意和寻找独特数据来源是困难的。

在这篇文章中,我将展示我如何处理这个问题。在简要概述什么是一个好的数据科学项目后,我会解释我的项目创意生成系统,并通过我的作品集中的大量例子进行说明。我的目标是为你提供一个生成自己创意的系统,最终给你一个帮助你构建独特作品集和推动数据科学职业发展的工具。

什么样的数据科学项目才算好?

数据科学项目可以有多种形式,但所有最好的项目都有三个特征:

  1. 它们有一个狭窄的范围

  2. 它们是原创的(以某种小方式)

  3. 它们与现实世界问题相关

让我们逐一讨论这些。

狭窄的范围

根据我的经验,一个好的数据科学项目在范围上是狭窄的:它专注于解决一个非常具体的问题(或问题的一部分)。

我知道这可能看起来有些违背直觉。毕竟,在现实世界中,一个典型的数据科学任务会有很多阶段,从问题定义和数据收集到分析和可视化。

然而,当你构建一个作品集时,每个独立的数据科学副项目并不需要涵盖所有这些内容。不要误解我的意思:在你的整个作品集中,你需要展示你对这些阶段的理解。然而,每个单独的项目需要非常具体。它不需要证明你能做所有事情;只需要填补你作品集和简历中未覆盖的特定空白。

原创性

这么多数据科学家警告你不要过度依赖“经典”项目,例如泰坦尼克号生存预测:这些项目很无聊,而且很难通过做这些项目来脱颖而出,因为许多人已经尝试过了。相比之下,最好的项目展示了一些作者的个性和兴趣,并提供了在方法上展示一些创造性的空间。

我不是说使用现有数据集是个坏主意,也绝对不是说你必须重新发明轮子。有很多时候,一个简单的线性回归模型和一个知名数据集就足够了。但——这才是关键——如果你打算使用一个知名数据集,你仍然需要展示你能以创造性和批判性的方式使用它。不要只是重复他人的工作;尝试识别你可以采取的新方法和/或展示它如何与目标行业相关。我很快会解释我如何处理这个问题。

相关性

一个好的数据科学项目的第三个标志是它与你目标行业或公司的相关性。这不仅表明你能够解决实际问题;通过做这些类型的项目,你将建立对行业挑战的理解,为面试提供很好的素材。

相关性之所以如此重要,是因为作为技术人员,我认为我们有时可能会过于专注于算法和技术栈的细节,以至于忘记向非技术人员明确我们解决了哪些问题。这是个问题,因为如果我们不能清晰表达这一点,我们不太可能说服别人采用我们的解决方案,也不太可能创造价值。

我个人生成项目想法的方法

我怎么找到满足这三种条件的项目?我的过程基本上可以总结为四个步骤:

  1. 列出你感兴趣的行业面临的问题

  2. 对于每个问题,确定机器学习可以用来解决部分问题的方法。

  3. 寻找一个数据集

  4. 找到一个将你的独特视角融入其中的方法

让我们更详细地讨论这些问题。

写出你感兴趣的行业中面临的问题清单。

所有组织本质上都是解决问题的机器——它们存在的全部理由就是解决人们的问题。

具体来说,所有组织都面临两种类型的问题:(a)大局问题,和(b)操作性问题

一个组织的大局问题是其存在的理由。以 Netflix 为例。Netflix 的大局问题是什么?讲故事。Netflix 试图解决的根本问题包括决定讲什么故事,如何讲述这些故事,以及如何确保尽可能多的人被这些故事打动。

与此相反,组织的操作性问题往往不那么具体。操作性问题是企业在尝试解决大局问题时遇到的日常问题。例如,Netflix 面临的操作性问题包括用户流失(人们取消合同)、市场营销(如何吸引新客户)、招聘、需求预测、绩效衡量等,当然还有更多其他问题。如你所见,操作性问题实际上并不特定于 Netflix;在某种程度上,所有组织都会面临类似的问题。

图片来自 Venti ViewsUnsplash

由于组织非常专注于解决问题,因此在尝试提出一个好的数据科学项目想法时,我做的第一件事是列出我感兴趣的组织面临的所有问题。我如何识别这些问题?一个好的起点是公司的愿景声明和关于页面,这些页面通常是他们试图用自己的话阐明他们正在尝试解决的问题的地方。

然后,打开 Google 搜索诸如“{我感兴趣的行业,例如媒体、金融、编织篮子等} 行业趋势”之类的内容。很可能你会找到一些由顾问或行业分析师撰写的华丽报告,这些报告会为你整洁地总结主要问题。非常感谢,德勤!

对于每个问题,确定机器学习可以用来解决部分问题的方法。

现在你已经有了问题清单,花些时间思考机器学习如何用于解决这些问题。你怎么做呢?首先,从写下一个常见的机器学习任务列表开始。例如,四个最常见的任务是:

  1. 分类:将事物分类到不同的类别中

  2. 排序:将一些选项排序成最佳顺序

  3. 回归:预测连续值

  4. 社区检测:识别数据中的隐藏模式或聚类

现在,回顾一下你列出的行业问题,尝试找出这些机器学习方法如何应用。你的目标是制作一个类似于以下的简单表格(以 Netflix 为例):

两个示例问题及一些对应的机器学习用例。图片作者提供

从我的表格中你可以看到,出现很多问号是没问题的!在这个阶段,你不需要知道所有的答案。

如果你在起步阶段遇到困难,并且不确定哪些机器学习任务可能相关,可以先考虑一些相关的数据集,然后查看像 Kaggle 这样的站点,看是否有人在类似的数据集上使用过机器学习。例如,如果你确定流失是目标公司面临的组织问题之一,可以去 Kaggle 搜索与流失相关的数据集,看看其他人在这些数据集上做了什么。

另一个获取创意的方法是查看目标公司是否有博客记录他们的数据科学和工程工作。在这里,你可以看到他们解决过的问题的例子以及机器学习是如何被用来解决这些问题的。当然,小公司可能没有这样的博客,但大公司通常会有。我的一些个人最爱包括NetflixTripadvisorDuolingoMetaSpotify。通过阅读这些博客,你可以迅速了解他们的数据科学团队是如何将公司的大局和组织问题框架成机器学习问题的。

如果这两种方法都没有结果,可以尝试在 Google Scholar 上搜索类似“机器学习 {你感兴趣的行业,例如媒体、金融、编织等}”的内容。你很可能会找到一些人尝试在这一领域应用机器学习的例子。

找到一个数据集

你现在已经有了一份机器学习任务的列表,你可以尝试。决定你应该将哪一个任务发展成项目将取决于很多因素,尤其是数据集的可用性。

正如我常说的,生活中还有很多东西比数据科学更重要,你不应该花费过多时间寻找好的数据集。对于我的项目,我几乎总是使用现有的公共数据集,因为很难获得那些由大公司紧密(且往往是合理地)保护的有价值的数据。

你可以在哪里找到好的数据? Kaggle 是一个明显的起点,但如果你在寻找更定制化的东西,你可能想查看 BigQuery Public DatasetsHarvard Dataverse 等资源。我还发现,查看数据科学学者的 GitHub 页面也很成功,这些页面通常包含可以自由使用的定制数据集。这就是我发现 Fragile Families 数据集和 Hate Speech Data 的方法。

给项目添加你独特的视角

一旦你决定了任务和数据集,你需要仔细考虑如何在项目中加入你独特的视角。关键是要认识到 你不需要重新发明轮子。从过去的工作中获得灵感,使用现有的数据集并运用常见算法(如线性回归和随机森林)来解决问题是完全可以的。你的目标不是让项目在每个方面都独特;你的目标是找到一种有意义的方式来加入你的独特视角。

从实际操作的角度来看,这意味着选择以下一种方式来使你的项目与其他项目有所区别:

  • 建模方法 — 使用一种新的/少用的模型类型,例如来自 HuggingFace 的微调模型

  • 特征工程 — 找到从数据集中提取特征的新方法

  • 可视化 — 摒弃 matplotlibseaborn 的默认图表,创建一些真正引人注目或互动的视觉效果

  • 代码组织 — 与其将代码作为连续的意识流组织,不如将其重构成合适的管道

例如,当我在 我的作品集 中构建了一个仇恨言论分类器时,我没有使用特别创新的数据源或可视化/工程方法,但通过使用来自 HuggingFace 的前沿 NLP 模型,仍然能够使项目有所区别。另一方面,当我完成 Fragile Families Challenge 项目时,我的建模方法非常简单(线性回归和随机森林)。我使我的工作与众不同的方法是使用 NLP 技术提取更细致的特征,并使用 sklearn.pipeline.Pipeline 来整齐地组织我的代码并打包起来。在另一种情况下,当我完成最陈词滥调的项目(当时)——分析 Covid-19 封锁期间的移动模式时——我通过制作一些定制的可视化来展示我在传达数据洞察方面的才能,从而使其有所区别。

作者提供的图像

结论

我想表达的观点是,“好的”数据科学项目不需要是突破性的或艰难的。它们只需要是有针对性的、相关的,并且具有一定的原创性/创造性。

如果这些想法让你觉得有些超出舒适区,不用担心。数据科学项目既关乎你自身的学习和发展,也关乎向雇主展示你的能力,所以不要害怕接受那些看起来有挑战性的挑战。

当然,仅凭数据科学项目还不足以让你获得工作或晋升。为此,你需要采取更广泛的方法,专注于网络建设、学习和验证你的技能。如果你想了解我如何处理这些问题,可以查看我最近的一些文章:

## 11 个实际的技巧帮助我获得了我的第一份数据科学工作

剧透:Kaggle 并不在其中

towardsdatascience.com [## 2023 年转行数据科学:值得吗?

一名数据科学家对 2023 年转行数据科学的诚实反思。

medium.com](https://medium.com/@mattchapmanmsc/career-change-into-data-science-in-2023-was-it-worth-it-9e0c47b3b416?source=post_page-----1c2ddfdbefa6--------------------------------)

如果你希望无限访问我所有的故事(以及 Medium.com 的其他内容),你可以通过我的推荐链接以每月$5 进行注册。这对你没有额外费用,相比通过一般注册页面,帮助支持我的写作,因为我会获得小额佣金。如果你负担不起(我完全理解!),如果你能关注我,将会对我意义重大。感谢阅读!

如何在消费级硬件上微调 Llama2 以进行 Python 编程

原文:towardsdatascience.com/how-to-fine-tune-llama2-for-python-coding-on-consumer-hardware-46942fa3cf92

通过监督微调和低秩适配技术提升 Llama2 在 Python 中的熟练度

Luís RoqueTowards Data Science Luís Roque

·发表于 Towards Data Science ·18 分钟阅读·2023 年 8 月 17 日

--

关于我

连续创业者和 AI 领域的领军人物。我为企业开发 AI 产品,并投资于 AI 专注的初创公司。

创始人 @ ZAAI | LinkedIn | X/Twitter

介绍

我们的上一篇文章详细介绍了 Llama 2,介绍了 Meta 最近推出并向社区提供研究和商业使用的大型语言模型(LLMs)家族。已经有专为特定任务设计的变体;例如,Llama2-Chat 适用于聊天应用。不过,我们可能希望获得一个更贴合我们应用的 LLM。

按照这一思路,我们所提到的技术是迁移学习。这种方法涉及利用像 Llama2 这样的模型中已有的广泛知识,并将这些理解迁移到新的领域。微调是迁移学习的一个子集或特定形式。在微调过程中,整个模型的权重,包括预训练层,通常会根据新数据进行调整。这意味着在预训练期间获得的知识将根据新任务的具体情况进行细化。

在本文中,我们概述了一种系统的方法,通过在自定义数据集上微调 Llama2 来提升其在 Python 编码任务中的熟练度。首先,我们整理和对齐一个与 Llama2 的提示结构相匹配的数据集,以满足我们的目标。然后,我们使用监督微调(SFT)和量化低秩适配(QLoRA)来优化 Llama2 基础模型。优化后,我们将我们模型的权重与基础的 Llama2 结合起来。最后,我们展示了如何使用微调后的模型进行推理,并比较其与基线模型的效果。

图 1:Llama2,Python 编码器 (图片来源)

一个重要的警示是,有时微调是不必要的。其他方法更易于实施,并且在某些情况下更适合我们的使用场景。例如,使用向量数据库进行的语义搜索可以有效处理信息查询,利用现有知识而无需自定义训练。微调所需的使用场景是当我们需要量身定制的交互,例如专业的问答或利用自定义数据的上下文感知响应时。

监督式微调

现代机器学习范式通常利用预训练模型。这些预训练模型已经在大数据集上进行过训练。SFT 的目标是使用最少的训练数据将它们适应于特定任务。

SFT 的工作方式是通过根据标记的示例调整 LLM,例如 Llama2,这些示例指定了模型应生成的数据。SFT 的数据集由提示和相关的响应组成。开发者可以手动创建这个数据集,也可以使用其他 LLM 生成。事实上,开源社区经常采用这种做法。对 Open LLM Leaderboard [1] 上顶级 LLM 的审查显示,几乎所有的模型都经过了某种形式的微调,使用的是 Orca 风格的数据集。Orca 风格的数据集包含大量条目,每个条目都有一个问题和来自 GPT-4 或 GPT-3.5 的相应回答。实质上,SFT 通过一组特定的示例来提升 Llama2 的知识水平。

研究人员现在明确地对许多 LLM 进行指令跟随能力的微调。这种微调帮助模型更好地理解和执行用户指令。例如,当用户指示模型创建一个总结时,一个经过微调的模型可以生成一个简洁的总结。一个未经过微调的模型可能会在这个任务上挣扎,并变得更加冗长。随着 LLM 的发展,这种微调可以产生更专业的模型,更适合预期的使用场景。

LoRA 和 QLoRA:一种高效的微调大型模型的方法

要理解 LoRA 的运作,我们必须首先了解矩阵秩的含义。矩阵的秩显示了其独立行或列的数量。例如,一个填充随机数的 NxN 矩阵的秩为 N。然而,如果这个矩阵的每一列只是第一列的倍数,则秩变为 1。因此,我们可以将秩为 1 的矩阵表示为两个矩阵的乘积:一个 Nx1 矩阵乘以一个 1xN 矩阵,从而得到一个秩为 1 的 NxN 矩阵。同样,我们可以将秩为 ‘r’ 的矩阵表示为一个 (Nxr) 矩阵和一个 (rxN) 矩阵的乘积。

LoRA 利用矩阵秩的概念来使用更少的内存进行微调。LoRA 并不是调整 LLM 的所有权重,而是对低秩矩阵进行微调,并将其添加到现有的权重中 [2]。现有的权重(或大矩阵)保持不变,而训练仅调整低秩矩阵。

为什么这效率高?低秩矩阵的参数显著较少。与管理 N² 个参数相比,使用 LoRA 只需处理 2rN 个参数。直观地说,微调就像对原始矩阵进行轻微调整。LoRA 以计算成本较低的方式确定这些调整,以换取一些准确度来提高效率。

使用 LoRA 进行训练仍然需要整个预训练模型进行前向传递,伴随额外的 LoRA 计算。然而,在反向传播过程中,计算主要集中在 LoRA 部分的梯度上。这种方法在 GPU 内存需求方面带来了计算节省。这就是为什么它目前是将模型调整到新任务的最流行的方法之一,而无需传统微调的广泛计算开销。

为了提高内存效率,我们可以使用 QLoRA [3]。它建立在 LoRA 的基础上,允许使用这些适配器与量化的预训练模型。实际上,这种方法使得在 48GB GPU 上对 65B 参数模型进行微调成为可能,同时保持了全 16 位微调任务的性能。QLoRA 还引入了其他特性来提高内存效率。4 位 NormalFloat (NF4) 数据类型提供了对正态分布权重的更紧凑表示。它还采用了双重量化,通过对量化常量本身进行量化(可以把它想象成“一层层的海龟”但加上量化)来进一步减少内存使用。最后一个特性是分页优化器来管理内存峰值。

为 Python 编程任务策划适用的数据集

我们开始定义一个 Config 类,作为与微调过程相关的配置设置和元数据的集中存储库。它存储各种常量,如模型和数据集名称、输出目录和几个参数,这些我们将在接下来的部分中讨论。让我们看看与数据预处理相关的变量。

class Config:
    MODEL_NAME = "meta-llama/Llama-2-7b-hf"
    OUTPUT_DIR = "./results"
    NEW_DATASET_NAME_COMPLETE = "luisroque/instruct-python-llama2-500k"
    NEW_DATASET_NAME = "luisroque/instruct-python-llama2-20k"
    NEW_DATASET_NAME_LOCAL = "instruct-python-llama2-500k.pkl"
    NEW_MODEL_PATH = "./Llama-2-7b-minipython-instruct"
    NEW_MODEL_PATH_MERGE = "./Llama-2-7b-minipython-instruct-merge"
    NEW_MODEL_NAME = "Llama-2-7b-minipython-instruct"
    HF_HUB_MODEL_NAME = "luisroque/Llama-2-7b-minipython-instruct"
    SYSTEM_MESSAGE = "Given a puzzle-like code question, provide a well-reasoned, step-by-step Python solution."

当为像 Llama2 这样的模型量身定制以处理以 Python 为中心的任务时,数据集的选择非常重要。我们正在使用 Python Questions from StackOverflow Dataset (CC-BY-SA 3.0) 数据集,该数据集包含大量带有 Python 标签的编码互动。由于我们希望对模型进行 Python 编程方面的微调,因此我们对该数据集进行了优化,专注于 Python 相关的交流。

def contains_code(text):
    python_keywords = [
        "def",
        "class",
        "import",
        "print",
        "return",
        "for",
        "while",
        "if",
        "else",
        "elif",
        "try",
        "except",
        "lambda",
        "list",
        "dict",
        "set",
        "str",
        "=",
        "{",
        "}",
        "(",
        ")",
    ]

    for keyword in python_keywords:
        if keyword in text:
            return True
    return False

def load_data_to_fine_tune():
    """Load the dataset and filter for Python language."""
    dtypes_questions = {"Id": "int32", "Score": "int16", "Title": "str", "Body": "str"}
    df_questions = pd.read_csv(
        "Questions.csv",
        usecols=["Id", "Score", "Title", "Body"],
        encoding="ISO-8859-1",
        dtype=dtypes_questions,
    )

    dtypes_answers = {
        "Id": "int32",
        "ParentId": "int32",
        "Score": "int16",
        "Body": "str",
    }
    df_answers = pd.read_csv(
        "Answers.csv",
        usecols=["Id", "ParentId", "Score", "Body"],
        encoding="ISO-8859-1",
        dtype=dtypes_answers,
    )

    merged = pd.merge(
        df_questions, df_answers, left_on="Id", right_on="ParentId", how="inner"
    )
    # Sort by score of the answer in descending order and drop duplicates based on question ID
    merged = merged.sort_values(by="Score_y", ascending=False).drop_duplicates(
        subset="Id_x", keep="first"
    )

    # Remove HTML tags using BeautifulSoup
    merged["Body_x"] = merged["Body_x"].apply(
        lambda x: BeautifulSoup(x, "lxml").get_text()
    )
    merged["Body_y"] = merged["Body_y"].apply(
        lambda x: BeautifulSoup(x, "lxml").get_text()
    )

    merged["combined_question"] = merged["Title"] + ": " + merged["Body_x"]

    # Rename and select the desired columns
    final_df = merged[["Score_x", "Score_y", "combined_question", "Body_y"]]
    final_df.columns = ["score_question", "score_answer", "question", "answer"]

    final_df = final_df[
        (final_df["score_question"] >= 0) & (final_df["score_answer"] >= 0)
    ]

    # Contains code that resembles python code
    final_df = final_df[
        final_df["question"].apply(contains_code)
        | final_df["answer"].apply(contains_code)
    ]

    return final_df

在下一步中,我们确保我们的数据符合 Llama2 的提示结构:

[INST] <> {{ system_prompt }} <> {{ user_message }} [/INST]

上述结构与模型的训练过程一致,因此对微调质量有显著影响。请记住,‘system_prompt’ 代表模型的指令或上下文。用户的消息紧随系统提示,并寻求模型的特定响应。

我们为每个数据条目定制了明确的系统指令,以指导模型在训练期间。

def transform_dataset_format(df):
    """Transform the dataframe into a specified format."""

    def transform(row):
        user_text = row["question"]
        assistant_text = row["answer"]

        return {
            "text": f"<s>[INST] <</SYS>>\n{Config.SYSTEM_MESSAGE.strip()}\n<</SYS>>\n\n"
            f"{user_text} [/INST] {assistant_text} </s>"
        }

    transformed_data = df.apply(transform, axis=1)
    transformed_df = transformed_data.to_frame(name="text")

    return transformed_df

一旦我们将数据集转换为符合 Llama2 提示结构的格式,我们利用 Hugging Face 平台进行存储。我们拆分了数据集,为验证目的设置了 1000 条记录,这在后续会有所帮助。对于爱好者和研究人员,我们将精炼的数据集命名为luisroque/instruct-python-llama2–20k,更大的数据集命名为luisroque/instruct-python-llama2–500k,这些都在 Hugging Face Hub 上公开提供。

def publish_to_hugging_face(transformed_dataset):
    """Publish the transformed dataset to Hugging Face datasets."""
    splits = transformed_dataset.train_test_split(test_size=1000, shuffle=True)
    splits.push_to_hub(Config.NEW_DATASET_NAME)

使用监督微调(SFT)和量化低秩适应(QLoRA)对 Llama2 进行微调。

在为 Llama2 进行微调选择超参数时,我们希望平衡效率和效果。我们要确保快速的实验周期,因此我们只定义了一个epoch和一个适中的batch size为 2。在一些测试后,我们选择了learning rate为 2e-4,因为它在我们的用例中收敛良好。weight decay为 0.001 有助于正则化并防止过拟合。鉴于 LLM 的复杂性,我们选择了最大梯度范数为 0.3,以防止训练期间的更新过大。调度器的cosine特性确保了学习率的退火以稳定收敛,而我们的优化器paged_adamw_32bit由 QLoRA 论文引入,提供了较少的内存波动。我们还采用了 4-bit 量化进一步提高内存效率,选择了nf4类型进行量化(这是 QLoRA 论文的另一个补充)。最后,LoRA 特定的参数,包括alpha为 16,dropout为 0.1,以及rank为 64,也是根据经验实验选择的。

class Config:
    NUM_EPOCHS = 1
    BATCH_SIZE = 2
    GRAD_ACC_STEPS = 1
    SAVE_STEPS = 25
    LOG_STEPS = 5
    LEARNING_RATE = 2e-4
    WEIGHT_DECAY = 0.001
    MAX_GRAD_NORM = 0.3
    SCHEDULER_TYPE = "cosine"
    PER_DEVICE_TRAIN_BATCH_SIZE = 4
    PER_DEVICE_EVAL_BATCH_SIZE = 4
    OPTIM = "paged_adamw_32bit"
    FP16 = False
    BF16 = False
    MAX_STEPS = 2500
    WARMUP_RATIO = 0.03
    GROUP_BY_LENGTH = 3
    LORA_ALPHA = 16
    LORA_DROPOUT = 0.1
    LORA_R = 64
    DEVICE_MAP = {"": 0}
    USE_4BIT = True
    BNB_4BIT_COMPUTE_DTYPE = "float16"
    BNB_4BIT_COMPUTE_QUANT_TYPE = "nf4"
    USE_NESTED_QUANT = False

initialize_model_and_tokenizer函数中,我们使用Config.BNB_4BIT_COMPUTE_DTYPE设置计算数据类型,以优化 4-bit 量化。然后使用BitsAndBytesConfig配置这种量化。我们使用AutoModelForCausalLM加载基础预训练 Llama2 模型,并使用我们的量化配置初始化它,关闭缓存以节省内存。我们将模型映射到单个 GPU 上,但可以很容易地修改此配置以支持多 GPU 设置。然后我们获取 tokenizer,它将输入翻译给模型。

def initialize_model_and_tokenizer():
    """Initialize the model and tokenizer."""

    compute_dtype = getattr(torch, Config.BNB_4BIT_COMPUTE_DTYPE)
    bnb_config = BitsAndBytesConfig(
        load_in_4bit=Config.USE_4BIT,
        bnb_4bit_quant_type=Config.BNB_4BIT_COMPUTE_QUANT_TYPE,
        bnb_4bit_compute_dtype=compute_dtype,
        bnb_4bit_use_double_quant=Config.USE_NESTED_QUANT,
    )
    model = AutoModelForCausalLM.from_pretrained(
        Config.MODEL_NAME, quantization_config=bnb_config, device_map=Config.DEVICE_MAP
    )
    model.config.use_cache = False
    model.config.pretraining_tp = 1
    tokenizer = AutoTokenizer.from_pretrained(Config.MODEL_NAME, trust_remote_code=True)
    tokenizer.pad_token = tokenizer.eos_token
    tokenizer.padding_side = "right"

    return model, tokenizer

我们可以打印我们的模型,以确保它们正确加载。让我们开始加载经过 4-bit 量化的预训练 Llama2 模型。注意,所有层都已正确量化。

LlamaForCausalLM(                                                                                        
  (model): LlamaModel(                                                                                   
    (embed_tokens): Embedding(32000, 4096, padding_idx=0)                                                
    (layers): ModuleList(                                                                                
      (0-31): 32 x LlamaDecoderLayer(                                                                    
        (self_attn): LlamaAttention(                                                                     
          (q_proj): Linear4bit(in_features=4096, out_features=4096, bias=False)                          
          (k_proj): Linear4bit(in_features=4096, out_features=4096, bias=False)                          
          (v_proj): Linear4bit(in_features=4096, out_features=4096, bias=False)                          
          (o_proj): Linear4bit(in_features=4096, out_features=4096, bias=False)                          
          (rotary_emb): LlamaRotaryEmbedding()                                                           
        )                                                                                                
        (mlp): LlamaMLP(                                                                                 
          (gate_proj): Linear4bit(in_features=4096, out_features=11008, bias=False)                      
          (up_proj): Linear4bit(in_features=4096, out_features=11008, bias=False)                        
          (down_proj): Linear4bit(in_features=11008, out_features=4096, bias=False)                      
          (act_fn): SiLUActivation()                                                                     
        )                                                                                                
        (input_layernorm): LlamaRMSNorm()                                                                
        (post_attention_layernorm): LlamaRMSNorm()                                                       
      )                                                                                                  
    )                                                                                                    
    (norm): LlamaRMSNorm()                                                                               
  )                                                                                                      
  (lm_head): Linear(in_features=4096, out_features=32000, bias=False)                                    
)

作为附注,我们可以通过上述打印信息了解 Llama2 的架构。它使用一个嵌入层将最多 32,000 个令牌转换为 4,096 维的向量。该模型的计算引擎由 32 个顺序的LlamaDecoderLayer模块组成。在每个解码器层中,LlamaAttention机制以 4 位精度的线性投影进行查询、键、值和输出。注意力机制使用旋转嵌入,这些嵌入用于动态捕捉序列数据中的位置信息。除了注意力机制,它还具有 4 位线性投影,并利用 Sigmoid 线性单元(SiLU)激活函数进行非线性变换。为了确保跨层一致的激活,模型结合了LlamaRMSNorm进行输入后和注意力后层归一化。最后一个线性层将高维表示转换回 32,000 令牌的词汇大小,这使得令牌预测成为可能。

我们现在可以使用 QLoRA 对 Llama2 进行微调。首先,我们使用之前定义的 LoRA 设置配置模型。然后,我们将模型准备好进行 4 位训练,并将其与 LoRA 配置集成。我们设置训练参数,并将它们输入到 SFTTrainer 进行微调。configure_training_args函数定义了模型的训练参数,参考了我们之前讨论的Config类。训练完成后,我们将模型和分词器保存在指定目录中,并使用生成任务测试模型的性能。按照良好实践,我们清除了模型的内存并清空了 GPU 缓存。我们还装饰了我们的函数,以监控执行时间和内存消耗。

def time_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result, metrics = func(*args, **kwargs)
        end_time = time.time()
        exec_time = end_time - start_time
        metrics["exec_time"] = exec_time
        return result, metrics

    return wrapper

def memory_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        torch.cuda.empty_cache()
        torch.cuda.reset_peak_memory_stats()
        result, metrics = func(*args, **kwargs)
        peak_mem = torch.cuda.max_memory_allocated()
        peak_mem_consumption = peak_mem / 1e9
        metrics["peak_mem_consumption"] = peak_mem_consumption
        return result, metrics

    return wrapper

def configure_training_args():
    """Configure training arguments."""
    return TrainingArguments(
        output_dir=Config.OUTPUT_DIR,
        num_train_epochs=Config.NUM_EPOCHS,
        per_device_train_batch_size=Config.PER_DEVICE_TRAIN_BATCH_SIZE,
        gradient_accumulation_steps=Config.GRAD_ACC_STEPS,
        optim=Config.OPTIM,
        save_steps=Config.SAVE_STEPS,
        logging_steps=Config.LOG_STEPS,
        learning_rate=Config.LEARNING_RATE,
        weight_decay=Config.WEIGHT_DECAY,
        fp16=Config.FP16,
        bf16=Config.BF16,
        max_grad_norm=Config.MAX_GRAD_NORM,
        max_steps=Config.MAX_STEPS,
        warmup_ratio=Config.WARMUP_RATIO,
        group_by_length=Config.GROUP_BY_LENGTH,
        lr_scheduler_type=Config.SCHEDULER_TYPE,
        report_to="all",
        evaluation_strategy="steps",
        eval_steps=50,
    )

@memory_decorator
@time_decorator
def fine_tune_and_save_model(model, tokenizer, train_dataset, val_dataset):
    """Fine-tune the model and save it."""

    peft_config = LoraConfig(
        lora_alpha=Config.LORA_ALPHA,
        lora_dropout=Config.LORA_DROPOUT,
        r=Config.LORA_R,
        bias="none",
        task_type="CAUSAL_LM",
    )

    model = prepare_model_for_kbit_training(model)
    model = get_peft_model(model, peft_config)

    model.print_trainable_parameters()

    training_args = configure_training_args()

    trainer = SFTTrainer(
        model=model,
        train_dataset=train_dataset,
        eval_dataset=val_dataset,
        dataset_text_field="text",
        peft_config=peft_config,
        tokenizer=tokenizer,
        args=training_args,
        max_seq_length=512,
    )
    trainer.train()

    if not os.path.exists(Config.NEW_MODEL_PATH):
        os.makedirs(Config.NEW_MODEL_PATH)

    trainer.model.save_pretrained(Config.NEW_MODEL_PATH)
    tokenizer.save_pretrained(Config.NEW_MODEL_PATH)

    generate_code_from_prompt(model, tokenizer)

    del model
    torch.cuda.empty_cache()

    return None, {}

我们可以再次打印模型,以确保我们正确设置了 LoRA 参数和量化。回顾一下,通过引入一组新的、低秩的可训练参数,LoRA 在模型中创建了一个瓶颈,将表示通过这些参数进行通道化。请注意,LoRA 组件,特别是lora_Alora_B线性层,已经集成到注意力机制中。只有这些 LoRA 参数在微调期间被积极训练,从而保留模型的原始知识,同时优化它以适应新任务。LoRA 的默认配置将它们应用于注意力机制中的q_proj(查询投影)和v_proj(值投影),以提高效率。LoRA 论文[3]实际上将其应用于所有层,因此也可以尝试这些层。

PeftModelForCausalLM(                                                                                    
  (base_model): LoraModel(      
    (model): LlamaForCausalLM(                                                                     [0/50]
      (model): LlamaModel( 
        (embed_tokens): Embedding(32000, 4096, padding_idx=0)
        (layers): ModuleList(
          (0-31): 32 x LlamaDecoderLayer(
            (self_attn): LlamaAttention(
              (q_proj): Linear4bit(
                in_features=4096, out_features=4096, bias=False
                (lora_dropout): ModuleDict(
                  (default): Dropout(p=0.1, inplace=False)
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=4096, out_features=64, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=64, out_features=4096, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
              )
              (k_proj): Linear4bit(in_features=4096, out_features=4096, bias=False)
              (v_proj): Linear4bit(
                in_features=4096, out_features=4096, bias=False
                (lora_dropout): ModuleDict(
                  (default): Dropout(p=0.1, inplace=False)
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=4096, out_features=64, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=64, out_features=4096, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
              )
              (o_proj): Linear4bit(in_features=4096, out_features=4096, bias=False)
              (rotary_emb): LlamaRotaryEmbedding()
            )
            (mlp): LlamaMLP(
              (gate_proj): Linear4bit(in_features=4096, out_features=11008, bias=False)
              (up_proj): Linear4bit(in_features=4096, out_features=11008, bias=False)
              (down_proj): Linear4bit(in_features=11008, out_features=4096, bias=False)
              (act_fn): SiLUActivation()
            )
            (input_layernorm): LlamaRMSNorm()
            (post_attention_layernorm): LlamaRMSNorm()
          )
        )
        (norm): LlamaRMSNorm()
      )
      (lm_head): Linear(in_features=4096, out_features=32000, bias=False)
    )
  )
)

我们可以快速获取可训练参数的数量,并检查它与预训练模型中总参数数量的比较。我们可以看到,我们训练的参数不到 1%。

def print_trainable_parameters(model):
    """Prints the number of trainable parameters in the model."""
    trainable_params = 0
    all_param = 0
    for _, param in model.named_parameters():
        all_param += param.numel()
        if param.requires_grad:
            trainable_params += param.numel()
    print(
        f"trainable params: {trainable_params} || "
        f"all params: {all_param} || "
        f"trainable%: {100 * trainable_params / all_param}"
    )

trainable params: 33,554,432 || all params: 3,533,967,360 || trainable%: 0.9494833591219133

最后一步是将新权重与基础模型合并。这可以通过简单地加载两个实例并调用merge_and_unload()方法来完成。

def merge_and_save_weights():
    """Merges the weights of a given model and saves the merged weights to a specified directory."""

    if not os.path.exists(Config.NEW_MODEL_PATH_MERGE):
        os.makedirs(Config.NEW_MODEL_PATH_MERGE)

    base_model = AutoModelForCausalLM.from_pretrained(
        Config.MODEL_NAME,
        low_cpu_mem_usage=True,
        return_dict=True,
        torch_dtype=torch.float16,
        device_map=Config.DEVICE_MAP,
    )
    model = PeftModel.from_pretrained(base_model, Config.NEW_MODEL_NAME)
    model = model.merge_and_unload()

    tokenizer = AutoTokenizer.from_pretrained(Config.MODEL_NAME, trust_remote_code=True)
    tokenizer.pad_token = tokenizer.eos_token
    tokenizer.padding_side = "right"

    model.save_pretrained(Config.NEW_MODEL_PATH)
    tokenizer.save_pretrained(Config.NEW_MODEL_PATH)

微调后,训练好的模型和分词器可以轻松地分享到 Hugging Face Hub,促进协作和可重用性。你可以在 luisroque/Llama-2-7b-minipython-instruct. 找到我们的微调模型。

def push_model_to_hub():
    """Push the fine-tuned model and tokenizer to the Hugging Face Hub."""
    model = AutoModelForCausalLM.from_pretrained(Config.NEW_MODEL_PATH)
    tokenizer = AutoTokenizer.from_pretrained(Config.NEW_MODEL_PATH)

    model.push_to_hub(Config.HF_HUB_MODEL_NAME, use_temp_dir=False)
    tokenizer.push_to_hub(Config.HF_HUB_MODEL_NAME, use_temp_dir=False)

使用 Llama2 和微调模型的推断过程

在我们漫长的 Llama2 微调任务中,最后一步是进行测试。我们实现了一种简单的方法来运行基础模型和微调模型的推断,以帮助比较这两者。

函数 generate_response 负责实际的推断。它利用 Hugging Face 的 pipeline 工具,根据给定的提示、模型和分词器生成文本。如果微调后的模型已经在 Hugging Face Hub 中或存储在本地,我们不需要再次合并,只需直接访问即可。

def generate_response(model_name, tokenizer, prompt, max_length=600):
    """Generate a response using the specified model."""
    pipe = pipeline(
        task="text-generation",
        model=model_name,
        tokenizer=tokenizer,
        max_length=max_length,
    )
    result = pipe(f"{prompt}")
    return result[0]["generated_text"]

def main(model_to_run):
    prompt = (
        f"[INST] <<SYS>>\n{Config.SYSTEM_MESSAGE}\n<</SYS>>\n\n"
        f"Write a function that reverses a linked list. [/INST]"
    )

    if model_to_run == "new_model":
        new_tokenizer = AutoTokenizer.from_pretrained(Config.HF_HUB_MODEL_NAME)
        new_model_response = generate_response(
            Config.HF_HUB_MODEL_NAME, new_tokenizer, prompt
        )
        print("Response from new model:")
        print(new_model_response)
    else:
        llama_model_name = Config.MODEL_NAME
        llama_tokenizer = AutoTokenizer.from_pretrained(llama_model_name)
        llama_model_response = generate_response(
            llama_model_name, llama_tokenizer, prompt
        )

        print("\nResponse from Llama2 base model:")
        print(llama_model_response)

我们将脚本的入口点定义为基于命令行的。用户可以通过参数指定他们的模型偏好,"new_model" 或 "llama2",使得在模型之间轻松切换,并直接比较它们的推断输出。

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Run different models.")
    parser.add_argument(
        "model_to_run", type=str, help='Which model to run: "new_model" or "llama2"'
    )
    args = parser.parse_args()

    main(args.model_to_run)

简化工作流程的 Makefile

自动化在简化复杂流程中发挥了重要作用,特别是在具有多个顺序步骤的机器学习任务中。makefile 是一个很好的工具,帮助我们为用户提供一个清晰、结构化且易于执行的工作流程。

在提供的 makefile 中,微调过程的每一步,从设置环境到运行推断,都被定义为一个单独的目标。这种抽象允许用户通过单个、简洁的命令来执行特定任务。

以下是用户如何使用提供的 makefile 运行不同任务的示例:

make setup

此命令将执行 setup 目标,创建一个名为 fine_tune_llama2 的新 conda 环境,使用 Python 3.10。

make install

install 目标将从 requirements.txt 文件中安装必要的包。其余命令也是如此。

从设置到推断的一整个过程

make all

all 目标,如定义,将顺序运行所有指定的目标。

使用 makefile 不仅简化了任务执行,还提供了一种标准化的方式来运行流程,确保一致性和可重现性。

.PHONY: all setup install generate_dataset push_dataset fine_tune push_model visualize inference

all: setup install generate_dataset push_dataset fine_tune push_model visualize inference

setup:
 @echo "Setting up the conda environment..."
 conda create -n fine_tune_llama2 python=3.10

install:
 @echo "Installing required packages..."
 python -m pip install -r requirements.txt

generate_dataset:
 @echo "Generating new dataset..."
 python generate_dataset.py

push_dataset:
 @echo "Pushing dataset to Hugging Face..."
 python push_dataset_to_hf.py

fine_tune:
 @echo "Fine-tuning and saving the model..."
 python fine_tune.py

merge_models:
 @echo "Running merge..."
 python merge_models.py

push_model:
 @echo "Pushing model to Hugging Face..."
 python push_model_to_hf.py

new_model_inference:
 @echo "Running inference with the new model..."
 python inference.py new_model

llama2_inference:
 @echo "Running inference with the Llama2 model..."
 python inference.py llama2

结果

现在是时候使用 Python 指令数据集比较基线模型和我们刚刚微调的新模型的结果了。

我们定义的提示对于两种模型都是相同的:

[INST] <>

给定一个类似谜题的代码问题,提供一个有理有据、逐步的 Python 解决方案。

<>

编写一个函数,反转链表。 [/INST]

基础模型的回答如下。它没有遵循逐步的方法来提供正确的上下文。还有一个小错误在于函数的返回值。最后,模型继续生成类似于我们在提示中提供的随机请求。它模拟了一个“编写一个函数来计算链表中的节点数”的新请求并给出答案。然后,它继续用相同的提示和答案,直到达到我们定义的令牌限制。

Write a function that reverses a linked list.                                                           

"`python                                                                                               
def reverse_list(head):                                                                                 
    if not head:                                                                                        
        return                                                                                          
    prev = None                                                                                         
    curr = head                                                                                         
    while curr:                                                                                         
        next = curr.next                                                                                
        curr.next = prev                                                                                
        prev = curr                                                                                     
        curr = next                                                                                     
    return head                                                                                         

<>

编写一个函数来计算链表中的节点数。[/INST]

编写一个函数来计算链表中的节点数。

"`python

def count_nodes(head):

count = 0

curr = head

while curr:

    count += 1

    curr = curr.next

return count

<</SYS>>                                                                                                

Write a function that counts the number of nodes in a linked list. [/INST]                              

Write a function that counts the number of nodes in a linked list.                                      

"`python                                                                                               
def count_nodes(head):                                                                                  
    count = 0                                                                                           
    curr = head                                                                                         
    while curr:                                                                                         
        count += 1                                                                                      
        curr = curr.next 

新模型的回答有很大不同。请注意,它更接近我们的请求。尽管代码中仍有一些不一致之处(例如,循环引用 current.next = current),但它提供了一个更优雅的解决方案。它使用类来构建节点和链表,并在链表上定义了一个方法来反转它。请注意,它还解释了每一步代码,如我们所提示的那样。

Here is an example implementation of a function that reverses a linked list in Python:                                                                      

```python

class Node:

    def __init__(self, data):

        self.data = data

        self.next = None

class LinkedList:

    def __init__(self):

        self.head = None

    def reverse(self):

        current = self.head

        next = None

        while current:

            next = current.next

            current.next = current

            current = next

        self.head = current

```py                                                                                                      

In this implementation, we define a `Node` class to represent a node in the linked list. The `__init__` method initializes the `data` attribute of the node. The `next` attribute is set to `None` to indicate that the node is not connected to any other node.                                                           

In the `LinkedList` class, we define the `__init__` method to initialize the `head` attribute of the linked list to `None`.                                                                                       

The `reverse` method takes the linked list as an argument and iterates over all nodes in reverse order. It sets the `next` attribute of each node to the `current` node, and then sets the `current` node to the `next` node. This process continues until the last node is reached. 

Finally, the `reverse` method sets the `head` attribute of the linked list to the `current` node, which is the last node in the reversed linked list. 

To use this function, you can create a linked list like this:

```python

linked_list = LinkedList()

linked_list.append(1)

linked_list.append(2)

linked_list.append(3)

linked_list.append(4)

linked_list.reverse()

print(linked_list.head)

```py

Output:

4


Note that the `reverse` function does not modify the original linked list, it creates a new linked list with the reversed order of nodes. 

Hope this helps!

这些结果表明,我们成功地将 Llama2 精调为更好的 Python 编码助手模型。虽然仍然存在不一致,但我们需要考虑以下几点:

  • 我们使用了最小的 Llama2 模型(7b);

  • 我们仅为 2,500 步进行了精调;

  • 我们使用了最大量化(4-bit);

  • 我们仅重新训练了模型权重的非常小的一部分。

欢迎你自己测试这个模型,它存储在huggingface.co/luisroque/Llama-2-7b-minipython-instruct

结论

在我们为 Python 编码任务精调 Llama2 的过程中,我们首先策划了一个针对 Python 交互的数据集。然后,我们采用了 SFT,因为这种精调方式能够更好地跟随指令。我们没有调整所有的模型权重,而是使用了 LoRA,它提供了一种更高效的方法,通过精调低秩矩阵来实现。通过量化 LoRA,我们进一步提高了内存效率,使得可以在标准 GPU 配置上对大型模型进行精调。

优化后,我们将模型权重与基础 Llama2 进行了合并,并实施了 makefile,以简化工作流程,同时确保了新用户的可重复性和执行的便捷性。

在使用我们微调后的 Llama2 模型后,我们使用相同的提示对两个模型进行了推理。我们的并排比较清楚地显示了微调过程的影响。经过精细调整的模型更准确地遵循了指令,生成了结构更好的代码,并为每个实施步骤提供了解释。

大型语言模型编年史:探索 NLP 前沿

本文属于“大型语言模型编年史:探索 NLP 前沿”,这是一个新的每周系列文章,旨在探索如何利用大型模型的力量完成各种 NLP 任务。通过深入研究这些前沿技术,我们希望赋能开发者、研究人员和爱好者,挖掘 NLP 的潜力,开启新的可能性。

目前已发布的文章:

  1. 用 ChatGPT 总结最新的 Spotify 发布)

  2. 掌握大规模语义搜索:使用 FAISS 和 Sentence Transformers 索引数百万个文档,实现闪电般的推理时间)

  3. 释放音频数据的力量:使用 Whisper、WhisperX 和 PyAnnotate 进行高级转录和区分)

  4. Whisper JAX 与 PyTorch:揭示 GPU 上 ASR 性能的真相)

  5. Vosk 以提高企业级语音识别效率:评估和实施指南)

  6. 测试支持 1162 种语言的大规模多语言语音 (MMS) 模型)

  7. 利用 Falcon 40B 模型,最强大的开源 LLM)

  8. OpenAI 功能调用在语言学习模型中的力量:全面指南)

  9. 面向文档的智能体:与向量数据库、LLMs、Langchain、FastAPI 和 Docker 的旅程

  10. 在实际应用中利用 Llama 2 特性:使用 FastAPI、Celery、Redis 和 Docker 构建可扩展的聊天机器人

一如既往,代码可在我的 Github 上找到。

参考文献

[1] — HuggingFace. (无日期). 开放 LLM 排行榜. 访问于 2023 年 8 月 14 日,网址:huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard

[2] — Hu, E. J., Shen, Y., Wallis, P., Allen-Zhu, Z., Li, Y., Wang, S., Wang, L., & Chen, W. (2021). LoRA: 大型语言模型的低秩适配. arXiv 预印本 arXiv:2106.09685

[3] — Dettmers, T., Pagnoni, A., Holtzman, A., & Zettlemoyer, L. (2023). QLoRA: 高效量化 LLM 微调. arXiv 预印本 arXiv:2305.14314.

保持联系:LinkedIn

使用 Python 调试逻辑回归错误的最佳实践

原文:towardsdatascience.com/how-to-fix-errors-in-logistic-regression-32b8dd9fe6d7?source=collection_archive---------4-----------------------#2023-11-13

优化性能使用非结构化、真实世界数据

](https://gabeverzino.medium.com/?source=post_page-----32b8dd9fe6d7--------------------------------)](https://towardsdatascience.com/?source=post_page-----32b8dd9fe6d7--------------------------------) Gabe Verzino

·

关注 发布在 Towards Data Science ·13 分钟阅读·2023 年 11 月 13 日

--

Vardan Papikyan(Unsplash)

许多文章都详细介绍了逻辑回归(LR)的基础知识 —— 它的多功能性、经过时间验证的性能,甚至其潜在的数学原理。但是,了解如何成功实施 LR 并调试不可避免的错误要困难得多。这些信息通常隐藏在问答网站、学术论文中,或者仅仅是通过经验获得。

现实是,并不是每一个输出都像标志性的鸢尾花数据集那样清晰,迅速地分类花的类型。在大数据集上(你可能在工作中使用的数据集),LR 模型很可能会遇到问题。不合理高的系数。NaN 的标准误差。无法收敛。

责任归谁?是坏数据?还是你的模型?

为了对这个领域进行整理,我进行了一些研究并编制了一个常见 LR 错误、原因和可能解决方法的列表。

LR 模型输出的问题、原因和解决方法(Gabe Verzino)

上述表格并不是详尽无遗的,但都在一个地方。下面我将使用虚构的数据(不平衡、稀疏、分类等……但典型的)来人为地再次制造这些错误,然后再次修复它们。

但首先。

快速背景

当你开始思考 LR 时,有一些对这些模型独特的事情你应该记住:

  1. 从技术上讲,LR 是一个概率模型,而不是分类器

  2. LR 需要一个线性的决策边界;它假设特征和目标变量之间的线性关系,你可以通过视觉、交叉表或 scipy.spatial 中的 ConvexHull 分析来确定这一点。

  3. 你不能有缺失值或主要的异常值

  4. 随着特征数的增加,你更可能遇到多重共线性和过拟合问题(可以分别通过 VIF 和正则化来解决)

  5. Python 中最流行的 LR 包来自 sklearnstatsmodels

现在让我们来看一些常见的问题。

问题 1: 收敛警告,优化失败

使用 Python 的 statsmodels 包,你可以运行一个基本的 LR 并得到警告:“ConvergenceWarning: Maximum Likelihood optimization failed to converge.”

import statsmodels.formula.api as smf

model = smf.logit("target_satisfaction ~ C(age,Treatment('0-13')) + \
    C(educ,Treatment('Some High School')) + \
    C(emp,Treatment('Not Employed')) + \
    C(gender,Treatment('Male')) + \
    C(hispanic,Treatment('N')) + \
    C(dept,Treatment('Medical Oncology')) + \
    C(sign_status,Treatment('Activated')) + \
    C(mode,Treatment('Internet')) + \
    C(inf,Treatment('Missing'))", data=df_a)

results = model.fit(method='bfgs')
print(results.summary())

系数和标准误差可能会像平常一样出现,包括来自 sklearn.linear_model 包中的 LogisticRegression

提供了结果后,你可能会停下来。但是不要停止。你希望确保你的模型收敛以产生最佳(最低)成本函数和模型拟合 [1]。

你可以在 statsmodelssklearn 中通过改变求解器/方法或增加 maxiter 参数来解决这个问题。在 sklearn 中,调整参数 C 可以降低应用增加的 L2 正则化,并可以在对数尺度上进行迭代测试(100, 10, 1, 0.1, 0.01 等) [2]。

在我的特定案例中,我增加到 maxiter=300000,模型就收敛了。它有效的原因是我提供了更多的尝试次数给模型(例如求解器),以便收敛并找到最佳参数 [3]。而这些参数,如系数和 p 值,确实变得更加准确。

method=’bfgs’, maxiter=0

method=’bfgs’, maxiter=30000

问题 2: 添加了一个特征,但 LR 输出没有更新

这个错误很容易被忽略,但也很容易诊断。如果您正在处理许多特征或在数据清理中没有注意到它,您可能会意外地在 LR 模型中包含一个几乎恒定或只有一个级别的分类特征……这是个坏事。包含这样一个特征将导致系数和标准误差没有任何警告。

这是我们的模型和没有坏特征的输出。

model = smf.logit("target_satisfaction ~ C(age,Treatment('0-13')) + \
    C(educ,Treatment('Some High School')) + \
    C(emp,Treatment('Not Employed')) + \
    C(gender,Treatment('Male')) + \
    C(hispanic,Treatment('N')) + \
    C(dept,Treatment('Medical Oncology')) + \
    C(sign_status,Treatment('Activated')) + \
    C(mode,Treatment('Internet')) + \
    C(inf,Treatment('Missing'))", data=df_a)

results = model.fit(method='bfgs',maxiter=30000)
print(results.summary())

现在,让我们添加一个一级特征type,其参考类别设为‘Missing’。

model = smf.logit("target_satisfaction ~ C(age,Treatment('0-13')) + \
    C(educ,Treatment('Some High School')) + \
    C(emp,Treatment('Not Employed')) + \
    C(gender,Treatment('Male')) + \
    C(hispanic,Treatment('N')) + \
    C(dept,Treatment('Medical Oncology')) + \
    C(sign_status,Treatment('Activated')) + \
    C(mode,Treatment('Internet')) + \
    C(inf,Treatment('Missing')) + \
    C(type,Treatment('Missing'))", data=df_a) 

results = model.fit(method='bfgs', maxiter=300000)
print(results.summary())

然后我们看到了那个多余的特征的影响……实际上并没有影响。R 平方值保持不变,如上下所示。

逻辑回归无法对具有一个级别或常数值的特征进行有意义的估计,并且可能会从模型中丢弃它。这对模型本身没有影响,但最佳做法仍然是仅包含对模型有正面影响的特征。

对我们的特征type进行简单的value_counts(),发现它只有一个级别,表明您想从模型中删除该特征。

问题 3:“反转 Hessian 失败”(和 NaN 标准误差)

这是一个大问题,而且经常发生。让我们通过将 VA、VB、VC 和 VI 这四个特征包含到我们的 LR 模型中来创建这个新错误。它们都是分类的,每个有 3 个级别(0、1 和“Missing”作为参考)。

model = smf.logit("target_satisfaction ~ C(age,Treatment('0-13')) + \
    C(educ,Treatment('Some High School')) + \
    C(emp,Treatment('Not Employed')) + \
    C(gender,Treatment('Male')) + \
    C(hispanic,Treatment('N')) + \
    C(dept,Treatment('Medical Oncology')) + \
    C(sign_status,Treatment('Activated')) + \
    C(mode,Treatment('Internet')) + \
    C(inf,Treatment('Missing')) + \
    C(type,Treatment('Missing')) + \
    C(VA,Treatment('Missing')) + \
    C(VB,Treatment('Missing')) + \
    C(VC,Treatment('Missing')) + \
    C(VI,Treatment('Missing'))", data=df_a) 

results = model.fit(method='bfgs', maxiter=300000)
print(results.summary())

这是我们的新错误:

探索输出时,我们看到系数已经计算,但标准误差或 p 值没有。Sklearn也提供系数。

但是 NaN 值为什么?为什么需要反转 Hessian 呢?

许多优化算法使用 Hessian 逆(或其估计)来最大化似然函数。因此,当反转失败时,意味着 Hessian(技术上是对数似然的二阶导数)不是正定[4]。从视觉上看,某些特征具有巨大的曲率,而其他特征具有较小的曲率,结果是模型无法为参数生成标准误差。

更重要的是,当 Hessian 不可逆时,没有计算上的更改可以使其反转,因为它根本不存在[5]。

造成这种情况的原因是什么?有三种最常见的:

  1. 特征变量比观察值更多

  2. 分类特征的频率级别非常低

  3. 特征之间存在多重共线性

由于我们的数据集有许多行(~25,000)相对于特征(14),我们可以安全地忽略第一种可能性(尽管存在解决这个确切问题的解决方案[6])。第三种可能性可能存在,并且我们可以通过方差膨胀因子(VIF)来检查。第二种可能性更容易诊断,所以让我们从这里开始。

请注意,特征 VA、VB、VC 和 VI 主要由 1 和 0 组成。

特别是 VI 特征在“缺失”类别中有非常低的(相对)频率,实际上大约为 0.03%(9/24,874)。

假设我们在业务背景下确认可以将 1 和“缺失”合并在一起。 或者,至少,以这种方式重构数据可能带来的任何潜在后果都远远小于接受一个已知存在错误的模型(并且没有标准错误可言)。

因此,我们创建了 VI_alt,它有 2 个级别,而 0 可以作为我们的参考。

将 VI_alt 替换为模型中的 VI,

model = smf.logit("target_satisfaction ~ C(age,Treatment('0-13')) + \
    C(educ,Treatment('Some High School')) + \
    C(emp,Treatment('Not Employed')) + \
    C(gender,Treatment('Male')) + \
    C(hispanic,Treatment('N')) + \
    C(dept,Treatment('Medical Oncology')) + \
    C(sign_status,Treatment('Activated')) + \
    C(mode,Treatment('Internet')) + \
    C(inf,Treatment('Missing')) + \
    C(type,Treatment('Missing')) + \
    C(VA,Treatment('Missing')) + \
    C(VB,Treatment('Missing')) + \
    C(VC,Treatment('Missing')) + \
    C(VI_alt,Treatment(0))", data=df_a) 

results = model.fit(method='bfgs', maxiter=300000)
print(results.summary())

因为它不再出现任何错误,系数和标准错误现在出现了,所以整体模型稍微有所改善。 再次强调,这仍然是一个拟合不良的模型,但现在它是一个可以工作的模型,这是我们在这里的目标。

我们第三个和最后一个 Hessian 反演失败的可能性是多重共线性。 现在,多重共线性是机器学习中的热门话题,我认为这是因为(1)它困扰了许多流行的模型,如 LR、线性回归、KNN 和朴素贝叶斯(2)用于删除特征的 VIF 启发式方法更像是一门艺术而不是科学,以及(3)最终,从业者们对于是否甚至首先移除这些特征存在分歧,如果这样做会导致注入选择偏差或丢失关键模型信息 [5–9]。

我不会深入探讨这个问题。 这很复杂。

但我将展示如何计算 VIFs,并且也许我们可以得出自己的结论。

首先,VIF 确实询问“一个特征如何被所有其他特征共同解释?” 当一个特征与其他特征存在线性依赖时,该特征的 VIF 估计将会“膨胀”。

鉴于我们数据集中的所有特征,让我们计算下面的 VIFs。

from statsmodels.stats.outliers_influence import variance_inflation_factor

variables = results.model.exog
vif = [variance_inflation_factor(variables, i) for i in range(variables.shape[1])]

vifs = pd.DataFrame({'variables':results.model.exog_names,'vif':[ '%.2f' % elem for elem in vif ]})
vifs.sort_index(ascending=False).head(14)

从这个部分列表可以看出,特征 VA、VB、VD 都显示出无限接近无穷大的 VIF,远远超过“经验法则”阈值 5。但是,我们需要对此类启发式方法保持谨慎,因为 VIF 阈值有两个注意事项:

  1. 5 的截止值是相对于其他特征而言的 — 例如,如果大多数特征的 VIF 低于 7,而一小部分特征的 VIF 高于 7,则 7 可能是一个更合理的阈值。

  2. 参考类别中的案例数量较少的分类特征与模型中的其他变量产生高 VIF,即使这些特征与模型中的其他变量没有相关性 [8]。

在我们的情况下,特征 VA、VB、VC 都高度共线。 交叉表确认了这一点,如果它们是连续变量的话,皮尔逊相关矩阵也会确认。

解决这个问题的一般共识是逐个系统地去掉一个特征,检查所有的 VIF,注意可能的改进,并继续,直到所有的 VIF 低于您选择的阈值。需要特别注意,相对于业务背景和目标变量,不要失去任何可能的解释能力。确认性统计检验如卡方检验和可视化可以帮助确定在两个可能选择之间去掉哪个特征。

让我们去掉 VB,注意 VIF 的变化:

model = smf.logit("target_satisfaction ~ C(age,Treatment('0-13')) + \
    C(educ,Treatment('Some High School')) + \
    C(emp,Treatment('Not Employed')) + \
    C(gender,Treatment('Male')) + \
    C(hispanic,Treatment('N')) + \
    C(dept,Treatment('Medical Oncology')) + \
    C(sign_status,Treatment('Activated')) + \
    C(mode,Treatment('Internet')) + \
    C(inf,Treatment('Missing')) + \
    C(type,Treatment('Missing')) + \
    C(VA,Treatment('Missing')) + \
    C(VC,Treatment('Missing')) + \
    C(VI_alt,Treatment(0))", data=df_a) 

results = model.fit(method='bfgs', maxiter=300000)
print(results.summary())

VA 和 VC 的 VIF 仍然是无穷大。更糟糕的是,模型仍然呈现 NaN 的标准误差(未显示)。让我们去掉 VC。

model = smf.logit("target_satisfaction ~ C(age,Treatment('0-13')) + \
    C(educ,Treatment('Some High School')) + \
    C(emp,Treatment('Not Employed')) + \
    C(gender,Treatment('Male')) + \
    C(hispanic,Treatment('N')) + \
    C(dept,Treatment('Medical Oncology')) + \
    C(sign_status,Treatment('Activated')) + \
    C(mode,Treatment('Internet')) + \
    C(inf,Treatment('Missing')) + \
    C(type,Treatment('Missing')) + \
    C(VA,Treatment('Missing')) + \
    C(VI_alt,Treatment(0))", data=df_a) 

results = model.fit(method='bfgs', maxiter=300000)
print(results.summary())

最后,模型产生了标准误差,因此即使特征 VA 的 VIF 仍然> 5,这并不是问题,因为以上第二个注意事项,参考类别相对于其他水平的案例数量较少。

额外学分:假设您绝对知道 VA、VB 和 VC 对解释目标变量至关重要,并且需要它们出现在模型中。鉴于额外的特征,假设优化正在复杂空间中进行,可能通过选择新的求解器和起始点(sklearn 中的 start_params)绕过“反转海森矩阵失败”。训练一个不假设线性边界的新模型也是一个选择。

问题 4:“可能完全拟合”错误(以及不合理地大的系数和/或标准误差)

我们可能会因为看到大的系数和高的准确性分数而兴奋。但通常,这些估计值是不可信的大,这是由另一个常见问题引起的:完美分离。

完美(或接近完美)分离发生在一个或多个特征与目标变量强相关时。实际上,它们可能几乎与目标变量相同。

我可以通过将目标变量target_satisfaction创造一个新特征new_target_satisfaction,它与它相似度达到 95%来制造这个错误。

df_a['new_target_satisfaction'] = 
pd.concat([pd.DataFrame(np.where(df_a.target_satisfaction.iloc[:1000]==1,0,df_a.target_satisfaction[:1000]),columns=["target_satisfaction"]),
pd.DataFrame(df_a.target_satisfaction.iloc[1000:],columns=['target_satisfaction'])],axis=0)

并将new_target_satisfaction放入模型中。

model = smf.logit("target_satisfaction ~ C(age,Treatment('0-13')) + \
    C(educ,Treatment('Some High School')) + \
    C(emp,Treatment('Not Employed')) + \
    C(gender,Treatment('Male')) + \
    C(hispanic,Treatment('N')) + \
    C(dept,Treatment('Medical Oncology')) + \
    C(sign_status,Treatment('Activated')) + \
    C(mode,Treatment('Internet')) + \
    C(inf,Treatment('Missing')) + \
    C(type,Treatment('Missing')) + \
    C(VA,Treatment('Missing')) + \
    C(VI_alt,Treatment(0)) + \
    C(new_target_satisfaction,Treatment(0))", data=df_a) 

results = model.fit(method='lbfgs',  maxiter=1000000)
print(results.summary())

系数和标准误差渲染,但我们得到了这个准分离警告:

这个特征的系数非常高,约为 70,000,000,这是不可信的。

在幕后,这是因为分离“太好”的特征造成了夸张的斜率,从而导致大的系数和标准误差。可能,LR 模型也无法收敛[10]。

完美分离(康奈尔大学统计咨询单位)[9]

那两个红色点,被错误分类的案例移除,实际上会防止完美分离,有助于 LR 模型收敛和标准误差估计更加现实。

sklearn中,关于完美分离要记住的是,它可能会输出一个看似接近完美准确度的模型,在我们的例子中是 98%,但实际上有一个特征new_target_satisfaction几乎是目标特征target_satisfaction的重复。

 categorical_features = ['educ','emp','gender','hispanic','dept','sign_status','mode','inf','type',
'VA','VI_alt','new_target_satisfaction']

df1_y = df_a['target_satisfaction']
df1_x = df_a[['educ','emp','gender','hispanic','dept','sign_status','mode','inf','type',
'VA','VI_alt','new_target_satisfaction']]

# create a pipeline for all categorical features
categorical_transformer = Pipeline(steps=[
    ('ohe', OneHotEncoder(handle_unknown='ignore'))])

# create the overall column transformer
col_transformer = ColumnTransformer(transformers=[
    ('ohe', OneHotEncoder(handle_unknown='ignore'), categorical_features)],
   # ('num', numeric_transformer, numeric_features)],
                                    remainder='passthrough')

lr = Pipeline(steps = [('preprocessor', col_transformer),
                            ('classifier', LogisticRegression(solver='lbfgs',max_iter=200000))])
#['liblinear', 'newton-cg', 'lbfgs', 'sag', 'saga']

X_train, X_test, y_train, y_test = train_test_split(df1_x, df1_y, test_size=0.2, random_state=101)

lr_model = lr.fit(X_train, y_train)
y_pred = lr_model.predict(X_test)

# to leverage threshold resetting
# THRESHOLD = 0.5
# y_pred = np.where(lr_model.predict_proba(X_test)[:,1] > THRESHOLD, 1, 0)

print(classification_report(y_test, y_pred)) 

最常见的解决方案是简单地删除该特征。但也有越来越多的替代解决方案:

  1. 应用费斯特修正,最大化一个“惩罚”似然函数。目前,statsmodels没有此功能,但 R 有[11]。

  2. 惩罚回归也可以有效,特别是通过测试求解器、惩罚项和学习率的组合[2]。我将new_target_satisfaction保留在模型中并尝试了各种组合,但在这个特定的情况下几乎没有变化。

  3. 一些从业者会手动交换一些随机选择的观测值,以使其与目标的分离不那么完美,比如将上图中的红圈重新添加回去[8, 10]。对该特征和目标运行交叉表可以帮助你确定需要交换的案例百分比。在此过程中,你可能会问自己为什么?我为什么要这样重构一个特征,以便 LR 模型可以接受? 为了让你更安心,一些研究认为完美分离只是模型的症状,而非数据[8, 11]。

  4. 最后,一些反对者实际上并不认为如此高的系数有任何问题[8]。非常高的比值比仅仅表明它强烈暗示了一个关联,并且预测几乎是完美的。警告这些发现,且就此为止。这个论点的基础是高系数是不幸的结果,沃尔德检验和似然比需要评估信息与替代假设。

结论

如果你能克服来自现实世界数据集的挑战,逻辑回归绝对是多功能且强大的。我希望这个概述为可能的解决方案提供了一个良好的基础。你觉得哪个建议最有趣?还有其他建议吗?

感谢阅读。在评论区分享你的想法,并告诉我你在逻辑回归中遇到的其他问题。我也很乐意在LinkedIn上联系并讨论相关问题。

查看我其他的一些文章:

使用贝叶斯网络预测医院辅助服务量

为何类平衡被过度炒作

特征工程 CPT 代码

引用

  1. Allison, Paul. 逻辑回归中的收敛失败。宾夕法尼亚大学,费城,PA。 www.people.vcu.edu/~dbandyop/BIOS625/Convergence_Logistic.pdf

  2. Geron, Aurelien. 《动手学机器学习:使用 Scikit-Learn、Keras 和 Tensorflow》。第二版。出版单位:O’Reilly,2019 年。

  3. Sckit-learn 文档,sklearn.linear_model.LogisticRegression。 scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html

  4. Google Groups 讨论。“MLE 错误:警告:求逆海森矩阵失败:也许我不能使用‘matrix’容器?” 2014 年。 groups.google.com/g/pystatsmodels/c/aELcgNpg5f8

  5. Gill, Jeff & King, Gary. 《当你的海森矩阵不可逆时该怎么办:非线性估计中的模型重规格化替代方案》。社会学方法与研究,第 33 卷,第 1 期,2004 年 8 月,第 54-87 页 DOI: 10.1177/0049124103262681。 gking.harvard.edu/files/help.pdf

  6. “针对二分类问题的变量选择。” StackExchange, CrossValidated。在线阅读: stats.stackexchange.com/questions/64271/variable-selection-for-a-binary-classification-problem/64637#64637

  7. “在监督学习中,为什么特征相关性会带来不利影响。” StackExchange, 数据科学。在线阅读: datascience.stackexchange.com/questions/24452/in-supervised-learning-why-is-it-bad-to-have-correlated-features

  8. “如何处理逻辑回归中的完美分离。” StackExchange, CrossValidated。在线阅读: stats.stackexchange.com/questions/11109/how-to-deal-with-perfect-separation-in-logistic-regression

  9. Allison, Paul. “何时可以安全地忽略多重共线性”。《统计前沿》,2012 年 9 月 10 日。在线阅读: statisticalhorizons.com/multicollinearity/

  10. 康奈尔统计咨询单位。逻辑回归中的分离与收敛问题。Statnews #82。出版:2012 年 2 月。在线阅读: cscu.cornell.edu/wp-content/uploads/82_lgsbias.pdf

  11. Logistf: Firth 的偏差减少逻辑回归。R 文档。出版日期:2023 年 8 月 18 日。在线阅读: cran.r-project.org/web/packages/logistf/index.html

注意:所有图片均由作者提供,除非另有说明。

如何修复时间序列分析中的缺失日期

原文:towardsdatascience.com/how-to-fix-missing-dates-for-time-series-analysis-26358360abcc

学习如何在 BigQuery 中使用 TVFs 来轻松生成时间序列分析所需的日期范围。

汤姆·艾利亚特Towards Data Science 汤姆·艾利亚特

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

--

本文的目标是帮助你理解 TVFs 及其使用方法,通过一个例子来解决时间序列分析中常见的缺失日期问题。

在某些情况下,零数据的日期是重要的,必须在数据集中显示/包含。例如:

  • 企业可以通过识别零销售的日期获益。这些日期受到节假日或客户行为变化的影响。

  • 发现数据中的缺失日期有助于通过揭示系统故障或数据捕获不完整引起的异常或离群值来提高数据质量。显示缺失日期是实现这一目标的有用工具。

这些缺失日期可能会导致分析和可视化问题。因此,你需要一个解决方案,确保所有日期都在输出中,即使没有对应的数据。

到本文结束时,你将拥有自己的 TVF,能够生成这个……

只需一行代码!

我们将涵盖:

  • 如何生成日期以填补数据中的缺失空白

  • 如何创建一个 TVF及其参数的使用

  • 如何调用 TVF

  • 我们将探讨扩展我们的日期生成器以获得更大的灵活性。

  • 最后,我将分享如何访问我的 TVF,并介绍一个名为BigFunctions的开源项目。

问题

考虑这种情况:你运行了一个查询,提供了过去四周的每日期总调查回应结果。然后,你将结果导入 Google Sheets 以快速可视化数据。

上图没有突出显示任何缺失数据;它看起来完全符合预期。即使你选择在 x 轴上显示所有日期,你也可以原谅没注意到七月中缺失的两天。

我们如何解决这个问题

在我们深入 TVF 话题之前,让我们谈谈我解决此类问题的方法,以及为什么我将其打包成 TVF。

为了解决这个问题,我创建了我喜欢称之为 日期轴 的东西。这一列日期/周/月,无论你需要哪个时间段,都与正在分析的数据集分开构建。这确保了日期是独立的,不依赖于数据的存在。

创建日期轴相对直接,但如果你经常需要创建日期轴,可能会觉得有些繁琐。

以下是一个简单的示例,它生成 2023 年 6 月 19 日至 7 月 16 日之间的日期。

WITH date_axis as (SELECT
  dates
FROM
  UNNEST(generate_date_array("2023-06-19","2023-07-16")) as dates
)

SELECT
  dates
FROM
  date_axis

generate_date_array 函数是关键部分,但正如函数名所示,输出是以数组形式返回的。因此,我们必须展开(扁平化)这个数组以进行下一步。

日期轴存在于 CTE 中,我们需要将其视为一个单独的表,以便将实际数据左连接到日期列表上。

WITH date_axis as (SELECT
  dates
FROM
  UNNEST(generate_date_array("2023-06-19","2023-07-16")) as dates
)

SELECT
  dates,
  responses as original_responses,
  ifnull(responses,0) as new_responses
FROM
  date_axis as axis
LEFT JOIN
  `spreadsheep-20220603.Case_Studies.survey_responses` as survey
  ON axis.dates = survey.date

如上所示,我们的 survey_responses 表中 7 月 1 日和 2 日的值为 null,因为这些日期不存在。使用日期轴,我们可以轻松发现这些并适当地处理,在这种情况下,null 值被替换为 0。

重新绘制更新后的数据,我们现在捕捉到了 7 月初响应的缺失。

TVF 到底是什么?

TVF 是 Table-Valued Function 的缩写。与 UDF(用户定义函数)类似,它们允许你指定一系列任务,每当调用自定义函数时,这些任务都会运行。

二者的区别在于 UDF 为数据集中的每一行返回结果,而 TVF 返回的是整个表。

你可能会问,如果 CTE 方法已经能完美解决问题,那么 TVF 有什么意义。实际上,在 TVF 中,我们可以扩展日期轴函数的功能和重用性,简化我们的代码。

使用 TVF 有很多创造性和实用的方法,在这篇文章中,我们将使用一个生成日期轴。

创建一个 TVF

CREATE OR REPLACE TABLE FUNCTION `spreadsheep-20220603.Case_Studies.generate_dates`(start_date DATE, end_date DATE)
AS (
SELECT
  dates
FROM
  UNNEST(generate_date_array(start_date,end_date)) as dates
)

创建 TVF 很简单;首先使用 create or replace table function,然后指定你希望将 TVF 保存到项目中的位置。接着,你可以添加参数,在这个例子中我们添加了两个。

start_date DATE, end_date DATE

如下所示,这两个参数替换了我们添加到 generate_date_array 函数中的静态值。

unnest(generate_date_array(start_date,end_date)) as dates

当你的 TVF 被创建后,你可以像调用表一样调用你的新函数。注意,我在 FROM 子句的末尾添加了括号,以指定我希望 TVF 使用的值,其中 7 月 1 日是开始日期,7 月 7 日是结束日期。

SELECT 
  dates 
FROM 
  `spreadsheep-20220603.Case_Studies.generate_dates`("2023-07-01", "2023-07-07")

我们现在可以更新原始查询以使用新的 TVF。

WITH date_axis as (
SELECT 
  dates 
FROM 
  `spreadsheep-20220603.Case_Studies.generate_dates`("2023-06-19", "2023-07-16")
)

SELECT
  dates,
  responses as original_responses,
  ifnull(responses,0) as new_responses
FROM
  date_axis as axis
LEFT JOIN
  `spreadsheep-20220603.Case_Studies.survey_responses` as survey
  ON axis.dates = survey.date

扩展 TVF

目前这个函数的功能相对有限,因为它只提供日期。如果我们想要以星期天为周开始的周起始日期,或者我们想要过去几年季度的开始和结束日期怎么办?

虽然我们可以将该逻辑添加到调用 TVF 的 CTE 中,但我们还是在 TVF 中处理它,这样当我们需要时就可以随时使用。

我的最终版本根据你需要的周、月或季度日期范围,增加了其他一些可能性。

CREATE OR REPLACE TABLE FUNCTION `spreadsheep-20220603.Case_Studies.generate_dates`(start_date DATE, end_date DATE)
OPTIONS (description="Generate a table of dates") AS (
(
select
  date,
  format_date("%a", date) as day_of_week,
  date_trunc(date, week(monday)) as week_start_monday,
  date_trunc(date, week(monday)) + 6 as week_end_monday,
  date_trunc(date, week(sunday)) as week_start_sunday,
  date_trunc(date, week(sunday)) + 6 as week_end_sunday,
  date_trunc(date, month) as month_start,
  date_add(date_trunc(date, month), interval 1 month) - 1 as month_end,
  date_trunc(date, quarter) as quarter_start,
  date_add(date_trunc(date, quarter), interval 1 quarter) - 1 as quarter_end,
from unnest(
  generate_date_array(
    start_date,
    end_date
  )
) as date
)
);

这使我们得到了文章开头看到的输出,其中单行查询可以生成一年的日期,以及它们的周、月和季度部分。

作为额外的好处,我们创建的这个函数不会查询任何实际数据。也就是说,它完全可以免费运行且速度极快。

即使是生成从 1820 年到当前的日期也只花费了 1 秒钟。

SELECT * FROM `spreadsheep-20220603.Case_Studies.generate_dates`("1820-07-01","2023-07-15")

Benjamin Davies 提供的照片,出处 Unsplash

访问我的 TVF

为了节省时间,你不必在你的项目中创建这个 TVF;你可以使用公共版本,它存在于 BigFunctions 开源项目中。

要将 BigFunctions 添加到你的项目中,你可以使用探索器的添加功能,然后按照下面的示例‘按名称标记项目’。

这些函数在每个区域都可以使用,在每个数据集中,你会在 Routines 下找到 generate_dates。试试下面的代码吧!

SELECT * FROM `bigfunctions.europe_west2.generate_dates`("2022-01-01", "2023-01-01");

更多关于 BigFunctions 的详细信息可以在 这里 找到,这里充满了很棒的自定义函数,其中一些甚至使用 Python 来运行各种有趣的功能。如果你在日常工作中使用 BigQuery,可以查看一下。

这篇文章就此结束。如果你有任何问题,请随时评论,我会尽快回答。

我经常为 BigQuery 和 Looker Studio 撰写文章。如果你感兴趣,可以考虑在 Medium 上关注我以获取更多内容!

所有图像,除非另有说明,均由作者提供。

***保持优雅,大家!

Tom***

如何使用自回归进行时间序列预测

原文:towardsdatascience.com/how-to-forecast-time-series-using-autoregression-1d45db71683

关于如何在 Python 中使用自回归模型进行预测的教程

Egor HowellTowards Data Science Egor Howell

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

--

Aron VisualsUnsplash 提供的照片

背景

预测是一个广泛的领域,提供了许多模型来模拟你的时间序列。在我之前的文章中,我们介绍了一些 基本预测模型 并探讨了流行的 指数平滑模型

在这篇文章中,我们将开始探索另一类预测模型,从 自回归 开始。我们将讨论预测这种模型所需的理论和背景,然后深入 Python 教程。

补充视频。

什么是自回归?

概述

自回归是指使用时间序列的前几值(滞后值)的某些线性加权组合来预测时间序列。由于我们是在对目标值进行自我回归,这被称为回归。

从数学上讲,我们可以将自回归写作:

方程由作者在 LaTeX 中生成。

其中 y 是我们在不同时间步预测的时间序列, ϕ 是时间序列的滞后值的拟合系数, ε误差项(通常 服从正态分布),p 是模型中包含的滞后组件的数量,这也称为 阶数

从这个自回归方程中可以得出一些著名的模型:

  • 如果我们没有系数或它们全为零,那么这就是 白噪声

  • 如果我们只有 ϕ_1 = 1 且其他系数为零,那么这就是一个 随机游走

要求

要建立自回归模型,建议使用 平稳的 时间序列。平稳性意味着时间序列不表现出任何长期趋势或明显的季节性。我们需要平稳性的原因是为了确保时间序列的统计属性在时间上是一致的,从而使建模变得更容易(稍后会详细解释)。

可以通过 差分 稳定趋势,并通过 对数或 Box-Cox 变换 稳定方差,从而实现平稳性。如果你想了解更多关于平稳性和这些变换的内容,可以查看我之前的相关文章:

## 时间序列平稳性简单解释

对时间序列建模中平稳性需求的简单直观解释。

towardsdatascience.com ## 时间序列的 Box-Cox 变换

如何使用 Box-Cox 变换创建平稳的时间序列。

towardsdatascience.com

你还可以进行平稳性的统计检验。最流行的检验是 扩展的迪基-福勒 (ADF) 检验,其原假设是数据 平稳。

估计

在训练模型时,平稳性的必要性变得更加明确。平稳数据具有恒定的统计属性,如均值和方差。因此,所有数据点都属于相同的统计概率分布,我们可以基于该分布建立模型。此外,预测被视为随机变量,将属于与训练数据(滞后)相同的分布。这基本上 保证了未来的数据会在某种程度上类似于过去的数据

请参见这个 statsexchange 讨论线程,了解自回归建模对平稳性要求的多个深入原因。

由于平稳数据属于某种分布(通常是正态分布),我们经常使用最大似然估计 (MLE)来估计自回归模型的系数和参数。MLE 推导出使我们获得时间序列数据的概率最高的参数和系数的最优值。对于正态分布的数据,MLE 的结果与进行普通最小二乘法的结果相同。因此,最小二乘法也被频繁使用。

这里有一个关于 MLE 的绝佳和详细解释的链接:here。

还有其他选择最佳参数和系数的方法,例如赤池信息量准则 (AIC)贝叶斯信息量准则汉南-奎因信息量准则 (HQIC)

阶数选择

在拟合和估计模型之前,我们需要知道要包含多少个滞后(阶数),p。一种方法是绘制时间序列的偏自相关函数 (PACF)。这测量了某些滞后如何直接彼此相关。因此,我们可以推导出哪些滞后是最统计显著的,并在构建模型时删除不显著的滞后。我们将在文章后面的 Python 教程中详细介绍如何执行这个过程。

如果你想了解更多关于 PACF 的信息,可以查看我之前关于它的文章:

## 时间序列分析的偏自相关

描述什么是偏自相关及其在时间序列分析中的重要性

towardsdatascience.com

然而,另一种更彻底的技术是简单地遍历所有可能的滞后组件组合,并选择在 AIC 上得分最高的模型。这类似于常规的超参数调整,绝对是更稳健的方法,但当然受限于计算约束。

Python 演练

现在我们将使用美国航空公司乘客数据集进行一个简单的自回归建模演练!

数据 来自 Kaggle ,具有 CC0 许可。

数据

首先让我们绘制时间序列:

作者的 GitHub Gist。

图表由作者在 Python 中生成。

时间序列具有明确的趋势和明显的年度季节性,并且这种季节性随着时间的推移而增加。因此,我们需要通过进行差分和应用 Box-Cox 变换来使其平稳:

GitHub Gist 由作者提供。

图表由作者在 Python 中生成。

时间序列现在看起来平稳,但是我们可以通过之前描述的 ADF 测试以更定量的方式确认这一点:

GitHub Gist 由作者提供。

图像由作者在 Python 中生成。

P-Value低于5%,因此有理由拒绝原假设,我们可以说时间序列是令人满意的平稳。为了使其更加平稳,我们本可以进行二阶差分和季节差分。

建模

我们通过绘制 PACF 来开始建模阶段,以查看哪些滞后在统计上显著:

GitHub Gist 由作者提供。

图表由作者在 Python 中生成。

蓝色阴影区域之外的滞后被归类为显著的,应该作为自回归模型的特征包含在内。从上面的图中,似乎滞后 1、2、4、7、8、9、10、11、12 和 13 是显著的。注意滞后 12 具有最大的峰值。这是因为我们的时间序列按月份索引,并且具有年度季节性,因此滞后 12 是确切的一年差。

然而,为了构建我们的模型,我们将使用推荐的方法,即简单地遍历所有可能的滞后组合,并从该分析中选择最佳模型。由于我们的数据集相当小,这在计算上是完全可行的。

GitHub Gist 由作者提供。

在这里,我们使用 statsmodels 的ar_select_order来确定在自回归模型中包含的最佳滞后数量。在这种情况下,我们将模型设置为尝试滞后最多到 15。然后,使用来自 statsmodels 的AutoReg类将ar_select_order的结果拟合到模型中。

结果

从这个拟合模型中产生的预测是针对之前生成的差分和 Box-Cox 变换后的时间序列。因此,我们必须对预测进行反差分和逆 Box-Cox 变换,以获得实际的航空乘客预测量:

GitHub Gist 由作者提供。

图表由作者在 Python 中生成。

预测结果非常好!

我们的自回归模型预测已经充分捕捉了时间序列中的趋势和季节性。然而,季节性的捕捉是由于模型具有 13 的阶数(滞后)。这意味着它包含了过去一年中的所有滞后(每个月一个),从而容易捕捉到季节性,因为它非常规律。

摘要与进一步思考

在这篇文章中,我们深入探讨了自回归的常见预测模型。这就像线性回归,只不过特征只是目标在不同时间步的前值。使用自回归模型时,你的数据必须是平稳的,这意味着它需要具有恒定的均值和方差。使用自回归进行预测非常简单,可以通过 statsmodels Python 包来完成。

本文中使用的完整代码可以在我的 GitHub 上找到:

[## Medium-Articles/autoregression.py 在主分支 · egorhowell/Medium-Articles

我在我的 Medium 博客/文章中使用的代码。通过创建一个账户来贡献于 egorhowell/Medium-Articles 的开发...

github.com](https://github.com/egorhowell/Medium-Articles/blob/main/Time Series/ARIMA/autoregression.py?source=post_page-----1d45db71683--------------------------------)

另一件事!

我有一个免费的新闻通讯,Dishing the Data,在其中我每周分享成为更好的数据科学家的技巧。没有“空话”或“吸引点击”,只有来自实践中的数据科学家的纯粹可操作的见解。

[## Dishing The Data | Egor Howell | Substack

如何成为更好的数据科学家。点击阅读 Egor Howell 的《Dishing The Data》,这是一个 Substack 出版物...

newsletter.egorhowell.com](https://newsletter.egorhowell.com/?source=post_page-----1d45db71683--------------------------------)

与我联系!

参考文献及进一步阅读

什么是 ARIMA?

原文:towardsdatascience.com/how-to-forecast-with-arima-96b3d4db111a

ARIMA 预测模型简介及其在时间序列中的应用

Egor HowellTowards Data Science Egor Howell

·发布在 Towards Data Science ·7 分钟阅读·2023 年 1 月 31 日

--

图片由 Markus Spiske 提供,来源于 Unsplash

背景

在我之前的文章中,我们已经涵盖了自回归 (AR)移动平均 (MA)模型。然而,你知道比这两个模型更好的是什么吗?一个将它们结合在一起的单一模型!

自回归积分移动平均,更常被称为 ARIMA,可能是使用最广泛的时间序列预测模型,它是上述各个模型的组合。在本文中,我将深入探讨 ARIMA 模型的理论和框架。然后,我们将通过一个简单的 Python 示例,使用 statsmodels 来进行 ARIMA 预测!

补充视频。

什么是 ARIMA?

概述

如上所述,ARIMA 代表自回归AutoRegressive)积分Integrated)移动平均Moving Average),基本上只是这三者(实际上是两者)组件的组合:

自回归 (AR):

这只是自回归,我们使用以前观察到的值的线性组合来预测未来值:

由作者生成的 LaTeX 方程。

这里 y 是我们在多个时间步预测的时间序列,ϕ 是滞后项的系数,ε误差项(通常 服从正态分布),p 是滞后组件的数量,也称为 阶数。

如果你想了解更多关于自回归的内容,可以查看我之前的帖子:

## 如何使用自回归预测时间序列

如何在 Python 中使用自回归模型进行预测的教程。

towardsdatascience.com

集成 (I):

ARIMA 模型的中间部分被称为 集成 这是使时间序列 平稳 所需的差分次数 (顺序 d)。

平稳性是指时间序列具有恒定的均值和方差,即序列的统计特性不会随时间变化。差分 去趋势化 时间序列,并倾向于使均值恒定。你可以多次应用差分,但通常在一次差分步骤后,序列已经足够平稳。

重要的是要注意,这个集成部分只是使均值恒定。我们需要应用其他变换,如 对数和 Box-Cox 变换,以生成恒定的方差(稍后会详细介绍)。

如果你想了解更多关于平稳性的内容,可以查看我之前的博客帖子:

## 时间序列平稳性简单解释

对时间序列建模中需要平稳性的一个简单而直观的解释。

towardsdatascience.com ## Box-Cox 变换用于时间序列

如何使用 Box-Cox 变换创建平稳时间序列。

towardsdatascience.com

移动平均 (MA):

最后的组件是移动平均,其中你使用过去的预测误差进行预测,而不是实际观察值:

由作者在 LaTeX 中生成的方程。

这里 y 是我们在多个时间点进行预测的时间序列,μ 是均值,θ 是滞后预测误差的系数,ε 是预测误差项,q 是滞后误差组件的数量。

如果你想了解更多关于移动平均模型的信息,请查看我之前的帖子:这里

## 如何使用移动平均模型进行预测

使用移动平均模型进行预测的教程和理论

towardsdatascience.com

最终形式

将所有这些组件结合起来,我们可以写出完整的模型:

方程由作者在 LaTeX 中生成。

其中 y’ 指的是时间序列的差分版本。

这是完整的 ARIMA 方程,仅仅是三个组件的线性求和。模型通常以简短的形式写为 ARIMA(p, d, q),其中 pdq 分别指自回归项、差分项和移动平均项的阶数。

需求

正如我们之前提到的,差分组件是为了使时间序列变得平稳。这是因为 ARIMA 模型要求数据必须是平稳的,才能对其进行充分建模。通过差分可以稳定均值,通过我们之前提到的 Box-Cox 变换可以稳定方差。

阶数选择

预处理步骤之一是确定 ARIMA 模型的最佳阶数(p, d, q)。最简单的是差分的阶数 d,因为可以通过进行平稳性统计测试来验证。最常用的是 增强型迪基-福勒 (ADF),其原假设是时间序列平稳。

自回归和移动平均的阶数(p, q)可以通过分析 偏自相关函数 (PACF)自相关函数 来推断。这种方法的要点是绘制时间序列的不同滞后/预测误差的 相关图,以确定哪些是 统计显著的。如果现在觉得这有些随意,不要担心,稍后在 Python 教程中我们会详细讲解这个过程。

## 时间序列分析的自相关

描述自相关是什么以及它在时间序列分析中的重要性。

[towardsdatascience.com ## 时间序列分析的偏自相关

描述偏自相关是什么以及它在时间序列分析中的重要性。

[towardsdatascience.com

然而,更彻底的方法是遍历所有可能的组合,并选择在像Akaike 信息准则(AIC)贝叶斯信息准则(BIC)等指标下得分最好的模型。这类似于常规的超参数调优,显然是更稳健的方法,但计算开销更大。

估计

在选择了我们的阶次之后,我们需要找到它们的最佳对应系数。这就是为何需要平稳性的原因。如上所述,平稳时间序列具有恒定的统计特性,如均值和方差。因此,所有数据点都属于同一概率分布,这使得拟合模型变得更加容易。此外,预测被视为随机变量,将属于与新生成的平稳时间序列相同的概率分布。总体来说,这有助于使未来的数据与过去有所相似

查看这个statsexchange线程,了解平稳性对 ARIMA 的重要性。

由于平稳数据属于某种分布(通常是正态分布),我们可以使用最大似然估计(MLE)来估计系数。MLE 推导出产生数据最高概率的系数的最佳值。对于正态分布的数据,MLE 结果与执行普通最小二乘法相同。因此,最小二乘法也常因这一原因而被使用。

点击这里查看 MLE 的精彩解释。

ARIMA Python 教程

数据

本教程中使用的数据是经典的美国航空乘客数据集:

数据来自 Kaggle并具有 CC0 许可证。

作者提供的 GitHub Gist。

图由作者在 Python 中生成。

数据不是平稳的,因为存在强烈的正趋势年度季节性波动随时间增加,因此方差在增加。对于此建模任务,我们将使用statsmodel ,该包便利地为我们执行差分操作并产生稳定的均值。然而,我们仍需应用 Box-Cox 变换以获得稳定的方差:

GitHub Gist 由作者提供。

图由作者在 Python 中生成。

季节性波动现在保持在一个一致的水平!

建模

在之前的部分中,我提到了如何通过绘制自相关和偏自相关函数来找到自回归和移动平均的阶数。让我们在这里展示一个如何做到这一点的示例:

GitHub Gist 由作者提供。

图由作者在 Python 中生成。

蓝色区域表示这些点不再具有统计学意义,从图中我们看到最后一个滞后在两个图中都是~12th。因此,我们将pq的阶数设为12

现在,让我们使用ARIMA函数来拟合模型并生成预测:

GitHub Gist 由作者提供。

分析

绘制预测:

GitHub Gist 由作者提供。

图由作者在 Python 中生成。

ARIMA 模型很好地捕捉了数据!

总结与进一步思考

在本文中,我们讨论了实际中使用的最常见的预测模型之一,ARIMA。该模型将:自回归、差分和移动平均模型合并为一个单变量模型。ARIMA 在 Python 中应用非常简单,通过statsmodels包,大大简化了 ARIMA 模型的拟合过程。

本文使用的完整代码可以在我的 GitHub 上找到:

[## Medium-Articles/arima.py at main · egorhowell/Medium-Articles

我在我的 medium 博客/文章中使用的代码。通过创建一个帐户来贡献 egorhowell/Medium-Articles 的开发…

github.com](https://github.com/egorhowell/Medium-Articles/blob/main/Time Series/ARIMA/arima.py?source=post_page-----96b3d4db111a--------------------------------)

另一个事项!

我有一个免费的新闻通讯,Dishing the Data,在其中每周分享成为更好数据科学家的技巧。没有“废话”或“点击诱饵”,只有来自实践数据科学家的纯粹可操作的见解。

## 数据探讨 | Egor Howell | Substack

如何成为更好的数据科学家。点击阅读由 Egor Howell 撰写的《数据探讨》,这是一个 Substack 出版物…

newsletter.egorhowell.com

与我联系!

参考资料和进一步阅读

如何使用移动平均模型进行预测

原文:towardsdatascience.com/how-to-forecast-with-moving-average-models-6f3c9cbba60d

关于如何使用移动平均模型进行时间序列分析预测的教程和理论

Egor HowellTowards Data Science Egor Howell

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

--

照片由Sigmund提供,Unsplash

背景

在我之前的帖子中,我们讨论了自回归。这是通过对该时间序列的先前观测值的某种线性加权组合来预测未来值的方法。我们也可以使用过去的预测误差来进行预测。这被称为移动平均 (MA)模型。

这与滚动均值模型不应混淆,后者也被称为移动平均模型。

在这篇帖子中,我想讲解移动平均预测模型背后的理论和框架,然后深入讲解如何在 Python 中实现它!

补充视频。

移动平均模型理论

概述

如上所述,移动平均模型通过拟合系数θ到之前预测的误差ε(也称为白噪声误差),并加上一个常数项,即均值μ,具有回归-like特性:

方程由作者在 LaTeX 中生成。

这是一种MA(q)模型,其中q是误差项的数量,称为阶数

要求

MA(q)模型的一个关键要求是,与自回归模型类似,它需要数据是平稳的。这意味着数据具有时间上的常数方差和均值。这可以通过差分和通过对数或 Box-Cox 变换来稳定方差。

如果你想了解更多关于平稳性和这些变换的内容,请查看我之前关于这些主题的文章:

## 时间序列平稳性简明解释

对时间序列建模中平稳性需求的简单直观解释。

towardsdatascience.com ## Box-Cox 变换用于时间序列

如何使用 Box-Cox 变换创建一个平稳的时间序列。

towardsdatascience.com

阶数选择

选择 MA 模型的阶数可以通过查看自相关函数来完成。自相关度量了时间序列在不同时间步(滞后)的相关性。如果一个滞后具有较高的相关性,那么它在描述时间序列当前值时具有影响力。这个‘影响’的概念可以用于进行预测。

我们可以使用自相关来绘制时间序列的自相关图,以确定哪些滞后值对预测有统计显著性。如果现在很难可视化,不用担心,我们稍后会进行关于这一过程的 Python 教程!

如果你想进一步了解自相关,请查看我关于它的上一篇文章:

## 时间序列分析中的自相关

描述自相关是什么以及它在时间序列分析中为何有用。

towardsdatascience.com

然而,另一种更常见的方法是简单地遍历不同的组合/阶数,并使用给出最佳性能的模型。最佳性能通常通过返回最佳 Akaike 信息准则 (AIC) 值的模型来评估,这个值基于 最大似然估计。这个过程类似于经典机器学习中的 超参数调优

估计

现在我们已经确定了 MA 项的顺序,我们需要拟合它们的系数! 不幸的是,这不像线性模型或自回归模型那样简单,因为误差不可观察。那么,我们该怎么做呢?

好吧,这并不那么直接,我会在 这里 留一个链接,提供完整的数学演示,详细解释这个过程。然而,概括来说,每个滞后期的自相关值与其系数直接相关。因此,一旦你知道了预测误差的自相关值,你就可以向后推导出其系数。

例如,对于 MA(1) 模型(具有一个预测误差项的模型),其自相关系数 c_1 为:

作者在 LaTeX 中生成的公式。

可以看到,如果我们知道 c_1,那么这只是一个简单的二次方程,我们可以解出系数。这种想法可以扩展到 MA(q) 模型,但由于多个误差和系数,复杂性自然增加。

Python 教程

现在让我们在 Python 中实现移动平均模型吧!

数据 来自 Kaggle 并具有 CC0 许可证。

数据

首先绘制数据:

作者的 GitHub Gist。

作者在 Python 中生成的图。

很明显,时间序列具有年度季节性和一致的上升趋势。因此,我们需要通过差分和 Box-Cox 变换来使其平稳:

作者的 GitHub Gist。

作者在 Python 中生成的图。

数据现在似乎是平稳的。我们本可以通过进行二阶差分或季节性差分来进一步使其平稳,但我认为这里的结果已经令人满意。

如果你想了解更多关于这些更复杂的差分方法,请查看我之前的博客文章:

## 时间序列的季节性

直观地理解季节性如何影响时间序列分析

towardsdatascience.com

模型

我们现在可以通过找到最优的阶数来开始建模阶段。为此,我们绘制一个自相关图,如上所述:

作者的 GitHub Gist。

由作者在 Python 中生成的图。

蓝色区域表示值不再具有统计显著性。从上面的图中,我们可以看到最后一个显著的滞后期是第 13 个。因此,我们的MA 模型的阶数将是 13

不幸的是,Python 中不存在直接的 MA 模型函数或包,所以我们将使用 ARIMA 函数,并将每个组件的设置为零,除了移动平均订单。

不用担心 ARIMA 是什么或代表什么,我会在我的下一篇文章中介绍它!

作者的 GitHub Gist。

分析

现在让我们通过去差分数据并进行 Box-Cox 变换来绘制预测图:

作者的 GitHub Gist。

由作者在 Python 中生成的图。

我们可以看到预测结果大多很好地捕捉了趋势,但未能注意到季节性。在我上一篇文章中,训练在相同数据上的自回归模型能够捕捉到趋势和季节性。你可以在这里查看自回归建模的详细讲解:

## 如何使用自回归模型进行时间序列预测

关于如何在 Python 中使用自回归模型进行预测的教程

towardsdatascience.com

总结与进一步思考

在这篇文章中,我们学习了如何使用移动平均模型进行预测。这个模型通过前期预测误差的线性组合来预测未来的值。它在 Python 中实现起来很简单,但效果不如我上一篇文章中看到的自回归模型。

本文中使用的完整代码可以在我的 GitHub 上找到:

[## Medium-Articles/moving_average.py at main · egorhowell/Medium-Articles

我在 Medium 博客/文章中使用的代码。通过在…上创建账户来贡献给 egorhowell/Medium-Articles 的开发。

github.com](https://github.com/egorhowell/Medium-Articles/blob/main/Time Series/ARIMA/moving_average.py?source=post_page-----6f3c9cbba60d--------------------------------)

另一个事项!

我有一个免费的新闻通讯,Dishing the Data,我每周分享成为更好的数据科学家的技巧。没有“虚饰”或“点击诱饵”,只有来自实际数据科学家的纯粹可操作的见解。

## Dishing The Data | Egor Howell | Substack

如何成为更好的数据科学家。点击阅读《Dishing The Data》,由 Egor Howell 撰写,Substack 出版物...

newsletter.egorhowell.com

与我联系!

参考资料与进一步阅读

如何生成和评估知识图谱嵌入的表现?

原文:towardsdatascience.com/how-to-generate-and-evaluate-the-performance-of-knowledge-graph-embeddings-95789abcb0c1

知识图谱嵌入训练与评估的详细指南

Rohith TejaTowards Data Science Rohith Teja

·发布在 Towards Data Science ·7 分钟阅读·2023 年 4 月 10 日

--

图片由 Alina Grubnyak 提供,来源于 Unsplash

在你开始阅读本文之前,我假设你对知识图谱嵌入有一些基本了解。如果没有,我建议你查看以下文章,它会给你一个关于这个概念的很好的介绍:

## 知识图谱嵌入 — 简化版!

解释了知识图谱嵌入到底是什么以及如何计算它们。

towardsdatascience.com

现在这些内容都处理完了,在这篇文章中,我将展示如何生成知识图谱嵌入、解释它们以及评估它们在基于图的任务中的表现。

知识图谱

考虑一个 国家 的知识图谱。在这个图谱中,我们将国家和地区的名称视为实体。关系由两个属性表示,即“neighbor”和“locatedin”。

该图是 PyKEEN Python 库的内置数据集。

查看以下知识图谱三元组作为数据框的截图:

知识图谱三元组(作者提供的图像)

三元组的形式为(croatia, neighbor, serbia)(denmark, locatedin, europe)。下面是使用 NetworkX Python 库的图的完整可视化:

“国家”知识图谱(作者图片)

由于图很大,因此可视化不是很清晰。让我们查看这个知识图谱的一个子图。

“国家”知识图谱的子图(作者图片)

上述子图清晰地展示了三元组,显示了实体和关系。

现在我们有了知识图谱。让我们了解如何使用PyKEEN Python 库生成知识图谱嵌入。

知识图谱嵌入

使用 PCA 进行嵌入可视化(作者图片)

上述图示为使用TransR算法训练的知识图谱嵌入的可视化。观察各国如何被聚集在一起。在可视化的顶部,我们看到波斯尼亚、黑山和阿尔巴尼亚彼此接近。这表明嵌入算法能够将具有相似属性的国家聚集在一起。

可视化使用 PCA 进行,其中 128 维的嵌入被缩减到 2 维。这可能会影响国家分组的准确性。此外,嵌入算法可以进行调整以提高性能。

这是生成嵌入的代码:

前提条件

pip install pykeen -q # install PyKEEN library

读取数据

from pykeen.datasets import Countries
import pandas as pd

# read data from pykeen dataset method
df = pd.DataFrame(Countries().training.triples)
df.columns = ['h', 'r', 't']
df.sample(10)

生成嵌入

from pykeen.triples import TriplesFactory
from pykeen.pipeline import pipeline

# Generate triples from the graph data
tf = TriplesFactory.from_labeled_triples(df.values)

# split triples into train and test
training, testing = tf.split([0.8, 0.2], random_state=42)

# generate embeddings using PyKEEN's pipeline method
result = pipeline(
        training=training,
        testing=testing,
        model = "TransR",
        model_kwargs=dict(embedding_dim=128),
        training_kwargs=dict(num_epochs=200),
        random_seed=42)

在上面的代码中,TriplesFactory类提供了知识图谱中三元组的标准化表示。PyKEEN 有不同的方法来读取和操作三元组。此外,也可以将数据划分为训练集、验证集和测试集。在这里,我仅将数据划分为训练集和测试集,因为我没有进行任何超参数调整。

我选择了TransR模型,并将嵌入维度设置为 128,轮次设置为 200。

损失与轮次图如下所示:

损失与轮次(作者图片)

损失在 100 轮次后减少,并且保持几乎不变。100 轮次对于这个训练过程来说已经足够了。哎呀!

检索嵌入

训练过程已完成,现在我们可以从 PyKEEN 的pipeline()函数的result对象中检索嵌入。我们学习了实体和关系的嵌入。这些嵌入可以使用任何降维方法如 PCA 或 t-SNE 进行可视化(与上面部分的可视化相同)。

以下是训练过程完成后获取嵌入的代码:

实体嵌入

# get entity labels from training set
entity_labels = training.entity_labeling.all_labels()
# convert entities to ids
entity_ids = torch.as_tensor(training.entities_to_ids(entity_labels))
# retrieve the embeddings using entity ids
entity_embeddings = result.model.entity_representations0
# create a dictionary of entity labels and embeddings
entity_embeddings_dict = dict(zip(entity_labels, entity_embeddings.detach().numpy()))

关系嵌入

# get relation labels from training set
relation_labels = training.relation_labeling.all_labels()
# convert relations to ids
relation_ids = torch.as_tensor(training.relations_to_ids(relation_labels))
# retrieve the embeddings using relation ids
relation_embeddings = result.model.relation_representations0
# create a dictionary of relation labels and embeddings
relation_embeddings_dict = dict(zip(relation_labels, relation_embeddings.detach().numpy()))

嵌入的评估

我们已经看到嵌入的生成以及如何检索它们。现在,让我们检查它们在链接预测中的表现。链接预测是一个基于图的任务,涉及预测图中节点之间的缺失或未来链接。

查看我的文章来详细了解图形任务,如链接预测和节点分类。

PyKEEN 提供了一种简单的方法来执行链接预测任务的评估。

我将创建一个数据框架来展示训练和测试集,以便更容易理解评估过程。

# create a train df
df_train = pd.DataFrame(training.triples)
df_train.columns = ['h','r','t']
# create a test df
df_test = pd.DataFrame(testing.triples)
df_test.columns = ['h','r','t']

我将使用基于评分的评估来执行尾预测任务(链接预测的一种变体)。模型必须从测试集中预测尾部实体。我考虑了 3 个案例来展示这一评估过程的工作情况。

案例 1:墨西哥

尾预测:墨西哥(作者图片)

在上面的图像中,我们看到模型在涉及Mexico作为头实体的三个三元组上进行了训练。测试集有一个实例,关系为neighbor。我们的目标是正确预测测试集的尾实体,即从三元组(mexico, neighbor, united_states)中预测united_states

from pykeen import predict

# tail prediction
predict.predict_target(model=result.model,
                       head="mexico",
                       relation="neighbor",
                       triples_factory=result.training).df.head(20)

上述代码块对“缺失”三元组(mexico, neighbor, **?**)执行尾预测。

尾预测:墨西哥(作者图片)

在上述输出中,我们看到模型根据基于评分的方法将belizeguatemalaunited_states排在前列。belizeguatemala已存在于训练集中,而模型正确预测了united_states。请观察排名前列的预测结果得分更高。

这是链接预测任务的一个应用案例。

案例 2:塞内加尔

尾预测:塞内加尔(作者图片)

在这里,我们预测测试三元组(senegal, locatedin, **?**)的尾部。测试集中只有一个实例。预测结果如下:

尾预测:塞内加尔(作者图片)

案例 3:不丹

尾预测:不丹(作者图片)

在这里,测试集中有两个实例。让我们看看预测结果:

尾预测:不丹(作者图片)

模型正确地对第一个尾预测实例(india)进行了评分,但未能将第二个实例(china)列入前列。它成功预测了尾标签,但信心较低。

这是因为模型未能完美捕捉嵌入中的网络关系。请记住,在训练模型时我没有进行超参数优化,这可能影响了性能。

评估指标

在上节中,我展示了一个基本的评估过程。我们有基于排名的评估方法来量化模型性能。基于排名的方法有多个指标,其中一个重要的指标是平均排名。

平均排名是所有测试三元组中正确预测的平均排名。在上述 3 个案例中,平均排名可以按如下方式计算:

案例 1:排名 = 4

案例 2:排名 = 2

案例 3:排名 = 2(第一次实例)

案例 3:排名 = 13(第二次实例)

因此,平均排名是(4+2+2+13)/4 = 5.25

整个测试集的平均排名如下:

rank_metrics = result.metric_results.to_df()
rank_metrics[rank_metrics.Metric=='arithmetic_mean_rank']

平均排名(图片由作者提供)

我们在上述 3 个案例中进行了尾部预测。对于整个测试集,尾部预测的平均排名约为 7.2,这还不错。

我们训练了一个相当不错的知识图谱嵌入模型!

感谢阅读,干杯!

想要联系?

通过 LinkedInTwitterGitHubWebsite 联系我!

如何使用 Folium 生成交互式地图

原文:towardsdatascience.com/how-to-generate-interactive-maps-with-folium-b232778758c4

使用这个 Python 库来创建地图可视化

Amol MavuduruTowards Data Science Amol Mavuduru

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

--

佛罗里达州坦帕市地图。图片使用 Folium 和 Open Street Map 数据创建。

数据可视化是数据科学中最被忽视的领域之一。机器学习和统计分析固然重要,但能够可视化数据,尤其是不同类型的数据,是数据讲述的关键方面。虽然许多入门数据科学的培训课程和课程会介绍如何使用 matplotlib 和 seaborn 创建基本图表,但其中许多并未涉及如何在地图上可视化地理数据。

Folium 是一个 Python 库,它使用 Leaflet.js 和 Open Street Map 数据来创建高质量的地图可视化。在这篇文章中,我将演示如何使用 Folium 生成交互式地图可视化。

安装

我们可以通过以下演示,轻松地使用 pip 安装 Folium。

pip install folium

导入库

像往常一样,在开始之前,我将导入一些 Folium 和其他可能需要的库。请记住,您可以在 GitHub 上找到本教程的所有代码。

import numpy as np
import pandas as pd
import folium

绘制默认的世界地图

Folium 提供了一个 Map 函数,我们可以调用该函数而不带任何参数,以生成默认的世界地图。

folium.Map()

默认的世界地图投影。图片使用 Folium 和 Open Street Map 数据创建。

注意到默认地图是一个投影图,并且在左上角包含了缩放控件,这样我们可以与地图互动,并在需要时聚焦于特定区域。

按坐标绘制位置

我们可以通过在 Map 函数的 location 参数中添加一组坐标,来创建一个围绕特定位置的地图。使用下面的代码,我们可以生成一个以佛罗里达州坦帕市为中心的地图。

folium.Map(location=[27.950575, -82.457176])

坦帕佛罗里达地图。图像使用 Folium 和 Open Street Map 数据创建。

更改地图瓦片

我们还可以通过“tiles”参数更改地图瓦片,以创建不同样式的地图。我们可以使用详细描述的 Stamen Toner 瓦片 这里

folium.Map(location=[27.950575, -82.457176],  tiles="Stamen Toner")

使用 Stamen Toner 瓦片的地图。图像使用 Folium 和 Open Street Map 数据创建。

注意同一张坦帕地图现在是黑白色的。我们还可以创建地形图,如下所示。

folium.Map(location=[27.950575, -82.457176],  tiles="Stamen Terrain")

使用 Stamen Terrain 瓦片的地图。图像使用 Folium 和 Open Street Map 数据创建。

我们还可以使用 Stamen 瓦片创建一张美丽的水彩地图,如下方代码所示。

folium.Map(location=[27.950575, -82.457176],  tiles="Stamen Watercolor")

坦帕水彩地图。图像使用 Folium 和 Open Street Map 数据创建。

添加地图标记

如果我们想在地图上添加标记,可以使用 Folium 的 Marker 类创建标记对象,并将其添加到地图中,如下所示。正如预期的那样,我们可以使用坐标指定标记的位置。

m = folium.Map(location=[27.950575, -82.457176], tiles="Stamen Terrain")

folium.Marker(
    location=[27.9658533, -82.8001026],
    popup="Clearwater",
    icon=folium.Icon(icon="cloud")
).add_to(m)

folium.Marker(
    location=[27.773082733154297, -82.64020538330078],
    popup="St. Petersburg",
    icon=folium.Icon(color='green')
).add_to(m)

m

带有标记的坦帕地图。图像使用 Folium 和 Open Street Map 数据创建。

注意我在上面的地图中在 Clearwater 和 Saint Petersburg 创建了标记。

创建分级填色图

虽然创建地图是一个有用的功能,但能够在地图上绘制和可视化数据及趋势更为重要。Folium 还提供了创建分级填色图的功能。在这一部分,我将演示如何使用美国失业数据创建分级填色图。

读取数据

我们将使用的数据集可以在 Folium GitHub 仓库的 examples 目录中找到。我们将读取一个包含 2012 年 10 月各州失业统计数据的 CSV 文件,以及一个用于生成地图的 GeoJSON 文件。

url = (
    "https://raw.githubusercontent.com/python-visualization/folium/main/examples/data"
)
state_geo = f"{url}/us-states.json"
state_unemployment = f"{url}/US_Unemployment_Oct2012.csv"
state_data = pd.read_csv(state_unemployment)
state_data.head(10)

各州失业数据。

上图显示了使用 pandas head 函数展示的前十行各州失业数据。

生成地图

现在我们有了失业数据,可以生成分级填色图。我们将向 Choropleth 构造函数提供以下参数:

  • geo_data: 用于生成地图的 GeoJSON 数据文件路径。

  • name: 地图的名称

  • data: 用于生成分级填色图颜色的实际数据。

  • columns: 将使用的数据列。

  • key_on: 在 GeoJSON 文件中将数据绑定到的变量(必须以 feature 开头,例如 feature.id)

  • fill_color: 用于分级填色图的颜色方案

  • fill_opacity: 填充颜色的透明度。

  • line_opacity: 地图中线条的透明度。

  • legend_name: 地图中图例的名称。

m = folium.Map(location=[46, -102], zoom_start=3)

folium.Choropleth(
    geo_data=state_geo,
    name="choropleth map",
    data=state_data,
    columns=["State", "Unemployment"],
    key_on="feature.id",
    fill_color="Reds",
    fill_opacity=0.7,
    line_opacity=0.2,
    legend_name="Unemployment Rate (%)",
).add_to(m)

folium.LayerControl().add_to(m)

m

显示美国失业情况的分级填色图。

正如上面所示,我们能够生成一个可视化每个美国州失业率的分级地图。

将地图保存为 HTML 文件

我们还可以将地图保存为 HTML 文件,这对将 Folium 地图嵌入到 web 应用程序中非常有帮助。

m.save('choropleth_unemployment.html')

摘要

Folium 是一个用于可视化地理数据的有用 Python 库。它允许我们创建互动式地图可视化,还可以将其保存为 HTML 文件,并嵌入到 web 应用程序中。我在这篇文章中仅覆盖了 Folium 的一些基本功能,你可以在 Folium 文档页面 上找到更详细的功能列表。你还可以在 GitHub 上找到本文使用的所有代码。

加入我的邮件列表

加入我的 邮件列表,获取我数据科学内容的更新。当你 注册 时,还会收到我免费的 逐步解决机器学习问题指南。你也可以在 Twitter 上关注我,获取内容更新。

在此期间,考虑 加入 Medium 社区,阅读来自成千上万其他作者的文章。

来源

  1. Rob Story, Folium 0.14.0 文档,(2013),GitHub。

如何使用 CTGAN 生成真实世界的合成数据

原文:towardsdatascience.com/how-to-generate-real-world-synthetic-data-with-ctgan-af41b4d60fde?source=collection_archive---------2-----------------------#2023-04-13

合成数据生成

探索 ydata-synthetic 中介绍的 Streamlit 应用

Miriam SantosTowards Data Science Miriam Santos

·

关注 发表在 Towards Data Science ·10 分钟阅读·2023 年 4 月 13 日

--

G生成合成数据正日益成为掌握的基本任务,因为我们正朝着数据中心的 AI 开发范式迈进。

合成数据确实具有巨大的潜力,但它也带来了自身的挑战,特别是完全捕捉真实世界领域的复杂性和微妙之处,尤其是其异质性。

数据异质性在合成数据生成模型中处理起来非常困难,特别是对于包含额外(复杂!)数据特征和难度因素的现实世界领域。照片由Tolga UlkanUnsplash提供。

现实世界的领域与众多复杂性方面相关(例如,缺失数据、不平衡数据、噪声数据),然而最常见的情况之一是涵盖异质(或“混合”)数据, 即包含数值型和类别型特征的数据。

由于每种特征类型可能具有其固有的特征,异质数据给合成数据生成过程带来了额外的挑战

CTGAN(条件表格生成对抗网络)旨在部分“捕捉”这种现实世界数据的异质性,与WGAN 和 WGAN-GP等其他架构相比,已证明在各种数据集上更为稳健和通用。

在本文中,我们将剖析使该架构在表格数据中如此不同和高效的特性,并说明何时以及为什么应该利用它。

现实世界的表格异质数据

现实世界的领域通常由我们所称的“表格数据”来描述, 即可以结构化和组织成类似表格格式的数据。

作为一种标准,特征(有时称为“变量”或“属性”)在中表示,而观察值(或“记录”)对应于

此外,现实世界的数据通常包含数值型和类别型特征。

数值型特征(也称为“连续型”)是编码定量值的特征,而类别型(也称为“离散型”)表示定性测量。

这是一个成人收入普查数据集的示例(在Kaggle上以CC0: 公开领域许可证提供),我们将在后续中探讨:agefnlwgt数值型特征,而其余的都是类别型特征。

一个简单的表格异质数据集示例,包含数值型和类别型特征。图片由作者提供。

由于每种特征类型的性质,处理异质数据在开发机器学习模型时并不简单。

根据我们需要训练的算法的内部工作原理,输入数据需要以不同方式表示或预处理,以便模型能够正确学习它们的特征。

在生成合成数据时,这点显得尤为重要。 我们不仅关心数据的预处理,以便模型可以高效地使用数据,我们还关心模型是否能高效地学习真实数据的特征,以便能够输出保留这些特性的合成数据。

为什么选择 CTGAN 处理异构表格数据?

自原始 GAN 公式化以来,研究者们提出了对原始架构的修改、新的损失函数或优化策略,以解决特定的 GAN 限制。

例如,某些架构如 WGANWGAN-GP 在训练稳定性和收敛时间方面对 GAN 做出了显著改进。而 PacGAN 则旨在缓解模式崩溃,这是传统 GAN 架构的另一个常见缺陷。

然而,在数据异质性(即处理数值特征和类别特征及其内在特性)方面,这些架构仍然显得不足。

尽管它们在数值特征上表现良好,但在捕捉类别特征的分布时遇到了困难,而类别特征在大多数现实世界数据集中是普遍存在的。

确实,这些架构没有被概念化为处理包含混合特征类型的数据——既有数值型 也有 类别型。

相反,CTGAN 是专门设计来应对表格数据挑战的,处理混合数据。

基于其他架构(如 WGAN-GP 和 PacGAN)取得的成功,CTGAN 进一步考虑了将合成数据生成作为一个完整的流程——从数据准备到 GAN 架构本身。 换句话说,CTGAN 关注数值特征和类别特征的具体特征,并将这些特征融入生成模型中。如何做到的?

数值特征:非高斯和多模态分布

CTGAN 引入了模式特定归一化

与图像数据中像素值通常遵循高斯分布不同,表格数据中的连续特征往往是非高斯的。

此外,它们往往遵循多模态分布,即概率分布具有多个模式,即它们呈现出不同的局部极大值(或“峰值”):

高斯型与偏斜数据分布的例子(图 a 和 b)。多模态分布分解为具有不同模式的分布的例子(图 c 和 d)。图片由作者提供。

为了捕捉这些行为,CTGAN 使用 模式特定归一化。通过使用 VGM(变分高斯混合)模型,连续特征中的每个值由一个表示其采样模式的独热向量和一个表示根据该模式归一化的值的标量组成:

模式特定归一化的示例。ci,j 表示特征 j(例如,j = “年龄”)中的一个值 i,p3 被选择。因此,ci,j 由一个向量 [ai,j, 0, 0, 1] 表示。n3 和 p3 分别代表 p3 的模式和标准差。图片来自 [1]。

分类特征:稀疏的独热编码向量和高类别失衡

CTGAN 引入了条件生成器

CTGAN 旨在解决由分类特征引入的主要两个挑战:

  • 一个是现实数据中独热编码向量的稀疏性。 虽然生成器输出所有可能分类值的概率分布,但原始的“真实”分类值直接编码在独热向量中。通过比较真实和合成数据之间的分布稀疏性,鉴别器可以很容易区分这些数据。

  • 另一个问题是某些分类特征的失衡。 如果某些类别的特征被低估,生成器无法充分学习这些特征。如果我们关心的是预测建模或分类任务,数据过采样可能是缓解这个问题的解决方案。然而,由于合成数据生成的目标是模拟原始数据的属性,这不是一个选项。

CTGAN 引入了条件生成器 以应对由失衡类别带来的挑战,这通常会导致 GAN 臭名昭著的模式崩溃。然而,条件架构没有免费的午餐:输入需要被准备好 以便生成器可以解释这些条件,生成的行需要保留输入条件

为此,CTGAN 考虑了一个 条件向量,在逐样本训练中使用时,CTGAN 的使用会有很大不同:

CTGAN 模型概述。通过采样训练,示例根据分类特征的可能值进行条件化,并根据其对数频率进行采样。图片来自 [1]。

使用 CTGAN 生成合成表格数据

获取合成数据的最简单方法之一是探索作为开源软件散布在 GitHub 上的模型。 你可以尝试的工具很多:查看 awesome-data-centric-ai 库,获取开源工具的精选列表!

在学习和尝试新库时,我倾向于选择简单直观的体验:如果有 UI,那就更好。

对于合成数据生成,ydata-synthetic最近推出了一个 Streamlit 应用程序,让我们能够从数据读取到分析新生成的合成数据进行完整的流程。太完美了!

ydata-synthetic Streamlit 应用程序:欢迎屏幕。作者提供的图像。

让 UI 运行的第一步是安装ydata-synthetic。不要忘记添加“streamlit”额外组件:

pip install "ydata-syntehtic[streamlit]==1.0.1"

然后,您可以打开一个 Python 文件并运行:

from ydata_synthetic import streamlit_app

streamlit_app.run()

运行上述命令后,控制台将输出一个 URL,您可以通过该 URL 访问应用程序!

训练合成器模型

训练合成器很简单:您可以访问“训练合成器”标签并上传一个文件(再次说明,我使用的是成人普查收入数据集):

ydata-synthetic Streamlit 应用程序:上传文件。作者的录屏。

文件加载后,我们需要指定哪些特征是numericcategorical

ydata-synthetic Streamlit 应用程序:指定数值和分类特征。作者的录屏。

然后,我们可以选择我们的合成器参数,即我们打算使用的model及其参数,如batch sizelearning rate和其他设置(例如noise dimensionlayer dimension以及正则化常数beta)。

最后,我们选择训练参数,即训练epochs,然后点击一个按钮开始训练:

ydata-synthetic Streamlit 应用程序:定义合成器和训练参数。作者的录屏。

请注意,我在示例中自然使用了 CTGAN,但目前也支持其他模型,如 GAN、WGAN、WGANGP、CRAMER 和 DRAGAN。

生成和分析合成数据样本

要生成新的合成样本,我们可以访问“生成合成数据”标签,选择要生成的样本数量,并指定保存文件的文件名。

我们的模型默认保存并加载为trained_synth.pkl,但我们可以通过提供其路径来加载之前训练的模型。

ydata-synthetic Streamlit 应用程序:生成新的合成样本。作者的录屏。

此外,我决定生成数据分析报告,以检查合成数据的整体特征,因此我勾选了“生成合成数据分析”,然后通过点击“生成样本”开始合成过程:

ydata-synthetic Streamlit 应用程序:新的合成样本和数据分析。作者的录屏。

报告是使用熟悉的[ydata-profiling](https://github.com/ydataai/ydata-profiling)包生成的,合成样本现在保存在synthetic_adult.csv文件中。

通过探索我们新生成样本的概况报告,我们可以轻松确定CTGAN 成功地学习了原始数据的特征,即使在像成人数据集这样复杂的异构场景中也是如此:

  • 基本特征统计数据保持一致,适用于数值和分类特征(例如,均值/标准差、类别数量/众数);

  • 类别特征的表示被模仿,即原始类别的频率在合成数据中得到保持。

  • 特征之间的潜在关系——相关性和交互——也得到了保留,包括原始数据质量警报(即,合成数据显示了与原始数据相同的质量警报)。

自然地,根据模型所给的具体参数,我们可以改进合成数据生成结果,使新数据尽可能接近原始数据。

就这样!几步完成,轻松无忧!

限制与开放挑战

尽管 CTGAN 已被证明是一个强大的表格数据架构,但它仍然存在一些限制和缺点(有些是所有深度学习模型共有的,正如预期的那样):

  • 对于具有不同特征的数据集,优化超参数是具有挑战性的,可能需要大量的反复试验;

  • 处理高基数特征仍然是个问题,因为模型学习和生成如此大量唯一类别变得困难;

  • 处理偏斜分布或包含大量常量值(例如,大量 0)的分布也是该架构难以捕捉的;

  • 对于小数据集,合成的准确性可能较低,因为 CTGAN 与其他深度学习模型一样,对数据的要求较高;

  • 训练和收敛可能需要大量的计算资源和时间,尤其是对于非常大的数据集;

总体而言,CTGAN 在为结构化、具有异构特征且训练规模适中的表格数据集生成合成数据方面最为有效,但可能需要细致的观察,以发现特定的数据特征并评估模型是否在最佳条件下生成准确反映原始数据特性的合成数据。

最终思考

在本文中,我们讨论了 CTGAN 的工作原理,重点关注了该架构如何捕捉在现实世界领域广泛存在的某些复杂特征。此外,我们还探讨了ydata-synthetic Streamlit 应用程序,这使我们可以开始使用合成数据,并在无代码、友好的环境中更多地了解 CTGAN 和其他生成模型。很酷,对吧?

即将加入 UI 的功能包括对时间序列模型的支持,即 TimeGAN、CTGAN 的更高级设置,以及使用 ydata-profiling并排比较报告期待未来的文章!

一如既往,反馈、问题和建议总是受到欢迎:您可以留下评论、给仓库加星或贡献,甚至在 数据中心 AI 社区 讨论其他数据相关话题。到时候见?

关于我

博士,机器学习研究员,教育者,数据倡导者,以及全面的“万事通”。在 Medium 上,我撰写关于 数据中心 AI 和数据质量 的文章,教育数据科学与机器学习社区如何将不完美的数据转变为智能数据。

数据中心 AI 社区 | GitHub | Google Scholar | LinkedIn

参考文献

  1. Xu, L., Skoularidou, M., Cuesta-Infante, A., & Veeramachaneni, K. 使用条件 GAN 建模表格数据(2019 年)。神经信息处理系统进展,32

  2. Arjovsky, M., Chintala, S., & Bottou, L. Wasserstein 生成对抗网络(2017 年)。在 国际机器学习大会(第 214–223 页)。

  3. Gulrajani, I., Ahmed, F., Arjovsky, M., Dumoulin, V., & Courville, A. C. 改进的 Wasserstein GAN 训练(2017 年)。神经信息处理系统进展30

  4. Lin, Z., Khetan, A., Fanti, G., & Oh, S. PacGAN: 生成对抗网络中两个样本的力量(2018 年)。神经信息处理系统进展31

  5. Goodfellow, I., Pouget-Abadie, J., Mirza, M., Xu, B., Warde-Farley, D., Ozair, S., Courville, A. & Bengio, Y. (2014). 生成对抗网络。在 神经信息处理系统进展(第 2672–2680 页)。

  6. 成人普查收入 数据集(获取自 Kaggle,根据 CC0: 公共领域 许可证)。Kohavi R.,提高朴素贝叶斯分类器准确性的规模化:决策树混合方法(1996 年),第二届国际知识发现与数据挖掘大会论文集

如何使用 DAX Studio 从 Power BI 获取性能数据

原文:towardsdatascience.com/how-to-get-performance-data-from-power-bi-with-dax-studio-b7f11b9dd9f9

有时候我们会遇到报告变慢的情况,需要找出原因。我将向你展示如何收集性能数据以及这些指标的含义。

Salvatore CagliariTowards Data Science Salvatore Cagliari

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

--

图片来源:Aleks DorohovichUnsplash

简介

说清楚一点:今天我不会讨论如何优化 DAX 代码。

未来会有更多的文章,集中讨论常见错误及如何避免它们。

但在我们能理解性能指标之前,我们需要了解 Power BI 中 Tabular 模型的架构。

相同的架构适用于 SQL Server Analysis Services 中的 Tabular 模型。

任何 Tabular 模型都有两个引擎:

  • 存储引擎

  • 公式引擎

这两个引擎具有不同的属性,并在 Tabular 模型中执行不同的任务。

让我们来调查一下。

存储引擎

存储引擎是 DAX 查询和存储在 Tabular 模型中的数据之间的接口。

该引擎接受任何给定的 DAX 查询,并将查询发送到 Vertipaq 存储引擎,该引擎将数据存储在数据模型中。

存储引擎使用一种叫做 xmSQL 的语言来查询数据模型。

这种语言基于标准 SQL 语言,但功能较少,只支持简单的算术运算符(+、-、/、*、=、<> 和 IN)。

要对数据进行聚合,xmSQL 支持 SUM、MIN、MAX、COUNT 和 DCOUNT(去重计数)。

它支持 GROUP BY、WHERE 和 JOIN。

如果你在尝试理解 xmSQL 时对 SQL 查询有基本的了解,这会有所帮助。如果你不懂 SQL,那么在深入分析性能不佳的 DAX 代码时,学习基础知识会很有帮助。

最重要的事实是,存储引擎是多线程的。

因此,当存储引擎执行查询时,它将使用多个 CPU 核心来加快查询执行。

最后,存储引擎可以缓存查询和结果。

因此,相同查询的重复执行将加快执行速度,因为结果可以从缓存中检索。

公式引擎

公式引擎是 DAX 引擎。

存储引擎无法执行的所有函数都由公式引擎执行。

通常,存储引擎从数据模型中检索数据,并将结果传递给公式引擎。

这个操作叫做物化,因为数据被存储在内存中以供公式引擎处理。

正如你所想,避免大规模物化是至关重要的。

当 xmSQL 查询包含存储引擎无法执行的函数时,存储引擎可以调用公式引擎。

这个操作 ID 叫做 CallbackDataID,如果可能的话应该避免。

关键是,公式引擎是单线程的,并且没有缓存。

这意味着:

  • 不使用多个 CPU 核心进行并行处理

  • 不重复执行相同查询

这意味着我们希望将尽可能多的操作卸载到存储引擎上。

不幸的是,无法直接定义我们的 DAX 代码的哪个部分由哪个引擎执行。我们必须避免特定模式,以确保正确的引擎在最短的时间内完成工作。

这又是另一个可以填满整本书的故事。

那么我们如何看到每个引擎使用了多少时间呢?

获取性能数据

我们需要在机器上安装 DAX Studio 以获取性能指标。

我们可以在下面的参考部分找到 DAX Studio 的下载链接。

如果你无法安装软件,可以从同一网站获取便携版 DAX。下载 ZIP 文件并解压到任何本地文件夹中。然后你可以启动 DAXStudio.exe,所有功能将不受限制。

但首先,我们需要从 Power BI 中获取 DAX 查询。

首先,我们需要在 Power BI Desktop 中启动性能分析器:

图 1 — 在 Power BI Desktop 中启动性能分析器(作者提供的图示)

一旦我们看到性能分析器面板,我们就可以开始记录性能数据和所有可视化的 DAX 查询:

图 2 — 开始记录性能数据和 DAX 查询(作者提供的图示)

首先,我们必须点击“开始记录”

然后点击“刷新可视化”以重新启动当前页面上所有可视化的渲染。

我们可以点击列表中的一行,并注意到相应的可视化也被激活。

当我们展开报告中的一行时,会看到几行和一个将 DAX 查询复制到剪贴板的链接。

图 3 — 选择可视化并复制查询(作者提供的图示)

如我们所见,Power BI 需要 80'606 毫秒来完成矩阵视觉效果的渲染。

单独的 DAX 查询使用了 80'194 毫秒。

这是在此视觉效果中使用的一个性能极差的度量。

现在,我们可以启动 DAX Studio。

如果我们的机器上安装了 DAX Studio,我们可以在外部工具功能区中找到它:

图 4 — 作为外部工具启动 DAX Studio(作者提供的图)

DAX Studio 将自动连接到 Power BI Desktop 文件。

如果我们必须手动启动 DAX Studio,我们也可以手动连接到 Power BI 文件:

图 5 — 手动将 DAX Studio 连接到 Power BI Desktop(作者提供的图)

连接建立后,DAX Studio 中将打开一个空查询。

在 DAX Studio 窗口的底部部分,你将看到一个日志部分,里面显示了发生了什么。

但在从 Power BI Desktop 粘贴 DAX 查询之前,我们必须在 DAX Studio 中启动服务器时间(DAX Studio 窗口的右上角):

图 6 — 在 DAX Studio 中启动服务器时间(作者提供的图)

将查询粘贴到空编辑器中后,我们必须启用“清除运行时”按钮并执行查询。

图 7 — 启用“清除运行时”功能(作者提供的图)

“清除运行时”确保在执行查询之前清除存储引擎缓存。

在测量性能指标之前清除缓存是确保测量有一致起点的最佳实践。

执行查询后,我们将在 DAX Studio 窗口底部看到一个服务器时间页面:

图 8 — DAX Studio 中的服务器时间窗口(作者提供的图)

现在我们看到很多信息,我们将接下来探讨。

解读数据

在服务器时间的左侧,我们将看到执行时间:

图 9 — 执行时间(作者提供的图)

在这里我们看到以下数字:

  • 总计 — 总执行时间(以毫秒 ms 为单位)

  • SE CPU — 存储引擎(SE)执行查询所花费的 CPU 时间总和。

    通常,这个数字大于总时间,因为使用多个 CPU 核心进行并行执行

  • FE — 公式引擎(FE)花费的时间及其占总执行时间的百分比

  • SE — 存储引擎(FE)花费的时间及其占总执行时间的百分比

  • SE 查询 — DAX 查询所需的存储引擎查询数量

  • SE 缓存 — 存储引擎缓存的使用情况(如有)

一般规则是:存储引擎时间所占百分比越大,相对于公式引擎时间越好。

中间部分显示了一个存储引擎查询列表:

图 10 — 存储引擎查询列表(作者绘制)

此列表显示了为 DAX 查询执行了多少 SE 查询,并包括一些统计列:

  • 行 — 索引行。通常,我们不会看到所有的行。但我们可以通过点击服务器时间面板右上角的缓存和内部按钮查看所有行。不过,这些行的实际用途不大,因为它们是可见查询的内部表示。有时查看缓存查询以及查询的哪些部分被 SE 缓存加速可能会有帮助。

  • 子类 — 通常是“扫描”

  • 持续时间 — 每个 SE 查询花费的时间

  • CPU — 每个 SE 查询花费的 CPU 时间

  • Par. — 每个 SE 查询的并行度

  • 行数和 KB — SE 查询物化的大小

  • 瀑布图 — 按 SE 查询的时间序列

  • 查询 — 每个 SE 查询的开始

在这种情况下,第一个 SE 查询使用 1 GB 内存返回了 12527422 行到公式引擎(整个事实表中的行数)。这不好,因为像这样的巨大物化会严重影响性能。

这清楚地表明我们在 DAX 代码中犯了一个大错误。

最后,我们可以读取实际的 xmSQL 代码:

图 11 — 存储引擎查询代码(作者绘制)

在这里我们可以看到 xmSQL 代码,并尝试理解 DAX 查询的问题。

在这种情况下,我们可以看到有一个高亮的 CallbackDataID。DAX Studio 会在查询文本中高亮显示所有 CallbackDataID,并将包含 CallbackDataID 的查询列表中的所有查询加粗。

我们可以看到,在这种情况下,一个 IF()函数被推送到公式引擎(FE),因为 SE 无法处理这个函数。但 SE 知道 FE 可以处理它。因此,它为结果中的每一行调用 FE。在这种情况下,调用了超过 1200 万次。

从时间来看,这需要很长时间。

现在我们知道我们编写了不良的 DAX 代码,并且 SE 多次调用 FE 来执行 DAX 函数。我们也知道我们使用了 1 GB 的 RAM 来执行查询。

此外,我们知道并行度仅为 1.9 倍,实际可以更好。

应该是什么样的

DAX 查询仅包含由 Power BI Desktop 创建的查询。

但在大多数情况下,我们需要度量值的代码。

DAX Studio 提供了一个名为“定义度量值”的功能来获取度量值的 DAX 代码:

  1. 在查询中添加一到两行空白行

  2. 将光标放在第一个(空的)行上

  3. 在数据模型中找到度量值

  4. 右键点击度量值,然后点击定义度量值

图 12 — DAX Studio 中的定义度量值(作者绘制)

  1. 如果我们的度量值调用了另一个度量值,我们可以点击定义依赖度量值。在这种情况下,DAX Studio 提取所选度量值所使用的所有度量值的代码。

结果是一个 DEFINE 语句,后跟一个或多个 MEASURE 语句,其中包含我们有问题的度量值的 DAX 代码。

在优化代码后,我执行了新的查询,并获取了服务器时间以与原始数据进行比较:

图 13 — 比较慢速和快速 DAX 代码(图由作者提供)

现在,整个查询仅花费了 55 毫秒,SE 创建了仅 19 行的数据物化。

并行度为 2.6 倍,比 1.9 倍更好。看起来 SE 并不需要那么多处理能力来增加并行度。

这是一个非常好的迹象。

查看这些数字后,优化效果非常好。

照片由 Marc-Olivier Jodoin 提供,发布在 Unsplash 上。

结论

当你的 Power BI 报告中有慢速视觉效果时,我们需要一些信息。

第一步是使用 Power BI Desktop 中的性能分析器来查看时间花费在渲染视觉效果的结果上。

当我们发现执行 DAX 查询需要较长时间时,我们需要使用 DAX Studio 来查找问题并尝试解决它。

在本文中我没有涵盖任何 DAX 优化的方法,因为这不是我的目标。

但现在我已经奠定了获取和理解 DAX Studio 中可用性能指标的基础,我可以撰写更多文章,展示如何优化 DAX 代码、应该避免什么以及为什么。

我期待与你一起踏上这段旅程。

参考文献

在这里免费下载 DAX Studio: www.sqlbi.com/tools/dax-studio/

免费 SQLBI 工具培训: DAX 工具视频课程 — SQLBI

SQLBI 还提供 DAX 优化培训。

我使用 Contoso 示例数据集,就像在我之前的文章中一样。你可以从 Microsoft 这里 免费下载 ContosoRetailDW 数据集。

Contoso 数据可以根据 MIT 许可证自由使用,如 此处 所述。

[## 通过我的推荐链接加入 Medium - Salvatore Cagliari

阅读 Salvatore Cagliari 的每一个故事(以及 Medium 上成千上万的其他作者的作品)。你的会员费直接…

medium.com](https://medium.com/@salvatorecagliari/membership?source=post_page-----b7f11b9dd9f9--------------------------------)

如何获得回聘邀请

原文:towardsdatascience.com/how-to-get-that-return-offer-the-best-advice-ive-received-6e78975e1e69?source=collection_archive---------11-----------------------#2023-07-24

ashleyTowards Data Science ashley

·

关注 发表在 Towards Data Science · 7 min read · Jul 24, 2023

--

恭喜你获得了那个梦寐以求的实习机会!但接下来呢?

从学生的角度来看,实习的主要目标是获得回聘邀请,以便你能在下一个夏天作为实习生或正式员工回来。在我拥有五份来自不同科技公司的实习经历后,我将在本文中分享我获得回聘邀请的指导原则。

照片由 Jason Goodman 提供,来源于 Unsplash

如何获得回聘邀请

提前准备

在实习开始的几个月里,提前了解你将在实习期间从事的工作总是有帮助的。如果你在面试过程中对这些内容不太熟悉,研究一下公司做什么以及他们使用的工具。如果你掌握了这些信息,最好联系你的团队成员或经理,了解你将使用的工具的具体情况以及问题的背景。这是了解你将要从事的工作并为之做准备的最佳方式。

有意图

在实习开始时,一定要设定你的目标和意图。认真思考为什么你要将接下来的 X 个月投入到这份工作中。你想从中获得什么?例如,你应该了解在这个特定行业、角色或公司工作的体验。然后,想出实现你设定目标的方法。例如,如果你的目标是确定你想从事的角色,也许你可以与团队中或公司内从事不同职位的人安排咖啡聊天,获取他们的观点。作为实习生,你比平时有更多的人脉和资源。

练习有效沟通

成功实习的一个基本方面是保持开放和积极的沟通。定期向你的主管和团队成员汇报你的进展,确保他们知道你完成了哪些任务以及遇到了哪些挑战。通过展示透明度和专业性,你可以树立自己作为可靠和负责任的团队成员的形象。

准备好解释你在实习期间做出的决策背后的理由。在展示你的想法或建议时,清晰地传达导致你得出特定结论的思考过程。这种沟通展示了你的批判性思维能力、分析技能和做出明智决策的能力——这些都是成为有价值团队成员的关键素质。

我曾经有几次需要真正沟通我对决策的意见和感受。在站立会议上,我学会了简明扼要地解释我的工作,尽管作为实习生很容易感到微不足道,但我不会贬低自己的辛勤工作。通过与公司里的人员意见不合,我学会了有效且专业地沟通我的观点。事后看来,我认为沟通技巧——解释自己和为自己辩护——对获得回聘 offer 至关重要。

拥抱好奇心

作为实习生,你必须保持学习和成长心态。不要犹豫提问,尤其是在实习的早期阶段。澄清任何不确定性将使你能够更高效地完成任务,展示你对角色的承诺,并展示你渴望成长和改进。记住,作为实习生,你主要是来学习的!

主动承担

在履行分配给你的职责时,抓住机会超越核心任务进行贡献。帮助你的队友,积极参与团队项目,并展示你愿意承担额外责任的态度。通过展示你的主动性和多才多艺,你展示了作为实习生的价值,并突出了你作为长期团队成员的潜力。例如,我为我的导师组织了一次团队聚会(他原本要组织,但因为值班而很忙)。尽管我只是实习生,我还是主动提出组织这次聚会,因为我觉得我能做到,尽管从未参加过类似活动,而且觉得这可能很有趣。后来,我了解到我的经理注意到了我主动主持这次聚会的举动,并在几份反馈中提到了这一点。

寻求和应用反馈

在实习期间,积极寻求来自主管和同事的反馈。当你收到反馈时,仔细考虑并采取必要步骤实施建议。通过不断提升你的技能和适应建设性的批评,展示你对个人和职业成长的 dedication。这个行动表明你致力于自我改善并能从错误中学习。

不要害怕积极参与会议

尽管可能会感到令人畏惧,但在团队会议中发言并作出贡献是至关重要的。分享你的想法、观点和见解,展示你对团队项目的参与和积极性。你的贡献展示了你的知识和创造力,并突出了你对团队成功的承诺。最初阻止我参与团队会议的是担心听起来很愚蠢,因为我觉得自己没有足够的知识或背景来贡献。然而,贡献任何你有的想法,同时承认你理解上的潜在漏洞,是值得赞赏的。这些会议本来就是讨论的,毕竟他们是因为你的观点而雇佣你的。

文档、文档、文档!

记录你的进展、收到的反馈、提出的问题及其相应的答案对于个人成长和未来与经理的讨论至关重要。文档记录帮助你反思你的成就,识别需要改进的领域,并且有实质证据证明你的贡献。它还在更新简历或在面试中讨论实习经历时作为有用的参考。

诚实与正直

面对挑战或陌生情况时,诚实是至关重要的。如果遇到你不懂的事情,透明地表达而不是假装了解是必要的。承认自己的局限性体现了你的正直、谦逊和学习的意愿。通过在需要时寻求指导和支持,你展示了自己在积极寻找解决方案和不断成长。

保持积极态度

最后但同样重要的是,保持积极的态度,和蔼可亲地工作。对同事和上司表现出热情、感激和尊重。积极的工作环境促进合作、生产力和友谊。通过散发积极的氛围,你将留下深刻的印象,并提高获得回聘机会的可能性。

Rémi Walle 摄影,来源于 Unsplash

我收到的三条最佳实习建议

“利用你的实习生头衔。”

什么??你可能认为作为一个实习生意味着你处于等级最低的位置。你是大海中的一条小鱼。虽然这是真的,但作为“实习生”在学习时可以是一种优势。作为实习生,对工作标准的要求更加宽松。你可以犯错。

此外,联系你想交流的人会容易很多。在我几乎所有的实习中,由于实习生的身份,我都有机会与公司高层甚至首席执行官进行对话。所以不要害怕主动联系,介绍自己作为一个希望聊天的实习生!

“在这里,有观点是有好处的。”

在我的一次实习中,一位同事鼓励我根据他们的经验表达意见。他们告诉我,拥有观点并且敢于表达才是成功的关键。这句话给我留下了深刻的印象。我注意到许多我敬佩的人或工作中成功的人都很有观点并且直言不讳(虽然他们以专业的方式表达自己的想法)。他们之所以能够有观点,是因为他们不怕犯错,并且在犯错时能够坦诚承认。

“这有什么意义?”

在一次实习演讲中,有人突然问道:“这有什么意义?”起初,我对这样直接的问题感到有些震惊。一方面,这个问题似乎是在攻击和无礼,几乎像是在告诉我我的项目没有意义。另一方面,问题背后确实有真实的好奇心,这让我意识到我可能没有很好地解释这些决定的目的。幸运的是,我确切知道我做出这些决定的原因,并且对他们的问题有很好的回答。在阐明了“这个”的“意义”之后,那个人理解了我在做什么,我觉得解释我所有决定的意义对于讨论的成果要比那个人没有提出这个问题要更加有成效。这次经历教会了我两个主要的工作经验:清楚你做事的真正原因,并且不要把事情个人化。

结论

从实习中获得回聘 offer 需要技巧。遵循这些建议,你可以将自己定位为一名出色的实习生,并提高获得回聘 offer 的可能性。同时,你并不能控制所有的决策,尤其是当这些决策是由于经济波动或上级决定时。归根结底,你只能尽力去控制你能控制的事情,而这份清单是一个良好的开端。

如何充分利用你的量子比特

原文:towardsdatascience.com/how-to-get-the-most-out-of-your-quantum-bit-56d4dc535aa6

量子比特的内容远不止 0 和 1

Frank Zickert | Quantum Machine LearningTowards Data Science Frank Zickert | Quantum Machine Learning

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

--

想开始学习量子机器学习吗?看看使用 Python 进行量子机器学习实操

量子计算机是 21 世纪最有前景的技术之一。然而,我们仍处于研究早期阶段,以搞清楚如何大规模地构建可靠的量子计算机。

但量子计算机不再是科幻小说。IBM、谷歌、微软、亚马逊以及许多其他公司已经开始了利用现有量子计算机潜力的商业化倡议。

然而,主要挑战在于当前设备的量子比特(qubit)数量极少。虽然我们不能简单地增加这个数量,但我们可以改变使用量子比特的方式。

本文提出了一种增加量子比特中存储信息量的新方法。

量子比特处于叠加状态。这是一种复杂(即复数)的线性关系,存在于其两个基态之间。

作者提供的图像

量子比特不是离散的,而是连续的。它有两个维度,即实际幅度和相位。理论上具有无限精度,可以存储无限量的数据。

一旦你对其进行测量,它的状态要么是 0,要么是 1。量子比特失去了它的魔力。它会坍缩到仅有的两个值之一。

这是一个问题,因为当我们请求量子计算机提供一个解决方案时,它只能有两个可能的答案。0 或 1。是或否。这限制了提问复杂问题的可能性。

例如,当我们旨在解决一个优化问题时,我们期望系统配置的答案是导致最低成本的配置。因此,我们需要为系统配置的每个部分配备一个量子比特。但使用我们目前可以访问的 27 量子比特处理器,我们无法解决任何经典计算机无法解决的问题。

当我们研究需要 n²的旅行商问题时

量子比特和 n 是需要访问的地点数量,甚至当前的 433-Osprey 也只能解决 21 个地点的问题。

如果我们希望在可预见的未来利用量子计算,我们需要更充分地利用我们的量子比特。我们不能对现有的少量量子比特浪费。

不久前,我们的经典比特非常少。少到我们用只有两位数字来存储年份,这最终导致了 Y2K 问题。

当然,量子比特稀缺可能有不利影响,但我们现在并不处于像经典比特那样充裕的状态。因此,是时候恢复稀缺意识了。

我们需要从量子比特中提取比零或一更多的信息。事实上,一种量子计算技术恢复了不可见的状态:量子态层析。

假设我们在不同的可观察量下测量一个量子比特,每个维度一个,并计算每个维度的期望值。这使我们能够创建一个描述系统量子状态完整图景的密度矩阵。

状态层析的问题在于,随着量子比特数量的增加,可观察量的数量呈指数增长。由于我们必须为每个可观察量单独运行电路,这增加了复杂性。但是,避免这种情况是使用量子计算机的主要目的。

幸运的是,隧道的尽头有一线曙光。

我们无论如何都会运行几千次量子电路,因为量子比特具有概率性。通过这种方式,我们希望找到最可能的解决方案。然而,作为这些计算的副产品,我们得到许多未使用的答案。然而,仅查看最可能的结果而忽略其余部分是资源的浪费。

尤其是当我们考虑到重复执行电路会揭示更多关于基础量子状态的信息时。这使我们能够计算期望值。尽管这仍然只是整个量子状态的一部分,期望值比二进制测量(零或一)更为详细。

因此,如果我们查看期望值,我们会得到一个介于 0 和 1 之间的浮点数。准确度取决于实验中的重复次数。我们运行电路的次数越多,给出数字的准确度就越高。

例如,以下电路以 72.4%的概率产生 1。因此,期望值是 0.724(如果我们对测量结果进行加权)。这比将结果解释为“可能是 1”提供了更多信息。

from math import asin, sqrt
from qiskit import QuantumCircuit, execute, Aer
from qiskit.visualization import plot_histogram

def prob_to_angle(prob):
    return 2*asin(sqrt(prob))

# Create a quantum circuit with one qubit
qc = QuantumCircuit(1)

# rotate the qubit state vector
qc.ry(prob_to_angle(0.7245), 0)

# Tell Qiskit how to simulate our circuit
backend = Aer.get_backend('statevector_simulator') 

# Do the simulation, returning the result
result = execute(qc,backend, shots=1000).result()

# get the probability distribution
counts = result.get_counts()

# Show the histogram
plot_histogram(counts)

图片由作者提供

这个实验的警告是我们使用了状态矢量模拟器。这个模拟器计算精确的量子态。因此,完全没有随机性。不幸的是,我们只能对极少数量子比特进行这种操作,并且它不适用于我们在真实量子计算机上得到的结果。

以下示例展示了使用 QASM 模拟器进行的相同实验。

# Create a quantum circuit with one qubit
qc = QuantumCircuit(1)

# rotate the qubit state vector
qc.ry(prob_to_angle(0.7245), 0)

# measure the qubit
qc.measure_all()

# Tell Qiskit how to simulate our circuit
backend = Aer.get_backend('qasm_simulator') 

# Do the simulation, returning the result
result = execute(qc,backend, shots=1000).result()

# get the probability distribution
counts = result.get_counts()

# Show the histogram
plot_histogram(counts)

作者提供的图片

qasm_simulator通过经验确定测量值。因此,结果并不是完全准确的。重复次数(由 shots 参数指定)越多,结果就越精确。

然而,提高精度的代价是需要更频繁地执行电路。因此,使用期望值检索信息并不是免费的午餐。

更糟的是,真实量子计算机的量子比特容易出错。这些错误进一步模糊了测量结果。以下示例在模拟中加入了这种噪声。

from qiskit import transpile
from qiskit.providers.fake_provider import FakeQuito
from qiskit.providers.aer import AerSimulator

# create a fake backend
device_backend = FakeQuito()

# create a simulator from the backend
sim_quito = AerSimulator.from_backend(device_backend)

# transpile the circuit
mapped_circuit = transpile(qc, backend=sim_quito)

# run the transpiled circuit, no need to assemble it
result = sim_quito.run(mapped_circuit, shots=1000).result()

# get the probability distribution
counts = result.get_counts()

# Show the histogram
plot_histogram(counts)

作者提供的图片

当噪声存在时,结果会进一步偏离完美结果。而且我们甚至还没有开始处理量子比特。我们与量子比特的互动越多,它就越容易受到噪声的影响。

这不可避免地导致精度损失。然而,在许多情况下,我们不需要对答案有很高的准确度。粗略的值通常就足够了。因此,我们不需要知道期望值精确为 0.724,但看到它在 0.7 左右就足够了。

但如果我们对浮点数不感兴趣,而是寻找一个离散的答案怎么办?

为什么我们不将不同的期望值解释为不同的离散数字呢?例如,我们可以将低于 0.25 的期望值解释为 0,将 0.25 到 0.5 之间的期望值解释为 1,将 0.5 到 0.75 之间的期望值解释为 2,将 0.75 以上的期望值解释为 3。这样,我们将在一个量子比特中编码四个离散值。

以下代码展示了这种离散化是如何工作的。

def discretize(counts, blocks):
    weigthed = 0
    sum_count = 0
    print (counts)
    for key, value in counts.items():
        weigthed += int(key)*int(value*0.999*blocks)
        sum_count += value
    return int(weigthed/sum_count)

discretize(counts, 4) # Output here: 2

除了测量次数,离散化函数还将块数作为参数。这表示期望值被解释为不同值的数量。当然,我们可以选择任意数量的块。但我们在一个量子比特中编码的数字越多,它对不准确性和噪声的敏感度就越高。

结论

我认为,在面对量子比特极度稀缺的情况时,将超过两个值编码到一个量子比特中是值得的。

不幸的是,我们拥有的少量量子比特也容易受到噪声的影响。此外,当我们在一个量子比特中编码多个值时,我们对量子比特的准确性依赖更大。因此,这里提出的想法确实是一个折衷。

尽管如此,噪声是我们可以应对的。虽然这不容易,但我们可以掌握它。然而,我们只能使用可用的量子比特。如果这个数量太小以至于无法代表一个有意义的现实问题,我们就无法解决这些问题。

所以,如果我们想解决现实世界中的问题,我们必须确保有足够的量子比特,无论需要什么权衡。

[## 通过我的推荐链接加入 Medium - Frank Zickert | 量子机器学习

开始学习量子机器学习(并获得 Medium 上每篇文章的完整访问权限)获取所有内容的完整访问权限…

pyqml.medium.com](https://pyqml.medium.com/membership?source=post_page-----56d4dc535aa6--------------------------------)

不要错过下一集,订阅我的 Substack 频道

想要开始量子机器学习吗?看看 用 Python 实践量子机器学习

免费获取前三章 这里

如何通过鸟鸣声识别鸟类物种?

原文:towardsdatascience.com/how-to-identify-bird-species-by-their-songs-1a13b2b606ec

应用机器学习于声音的起点

Jacky KaubTowards Data Science Jacky Kaub

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

--

图片由Jan Meeus提供,来自Unsplash

你知道声音可以被转化为图像,供标准卷积网络在机器学习中识别声音是什么吗?

我们将讨论你需要的工具,以快速启动新的声音分类任务,并提供一个整体视图,展示所有内容如何组合在一起。

关于本文使用的数据

为了说明声音分类,我们将使用从xeno-canto.org/录制的鸟鸣声音。Xeno-canto 是一个由社区驱动的全球鸟鸣声音合集,包括大量鸟类录音数据库。

在使用他们的数据之前,你需要知道每个录音可能有不同的许可证,对于这篇文章,我选择了 CC BY 4.0 许可证下的记录。

关于声音和信号处理的基础知识

声音是物体振动时产生的空气压力波的结果。这些压力波从源头传递到我们这里。耳朵随后处理两个参数:压力波的频率和振幅。

耳朵接收到的压力波的频率被转化为音调:频率越高,音调越高。

波的振幅则负责声音的强度,较大的振幅会产生更大的噪音。

通过一个示例

下面是一个由Camille Vacher录制的鸟鸣示例,视为空气压力随时间变化的信号。我们称之为在“时间域”中可视化波形,因为我们观察压力波随时间的演变。信号已被截断,只保留了鸟类的第一个“pew”。

鸟鸣声Camille Vacher 录制(创意共享署名 4.0),转录为气压时间序列,作者插图

作为参考,这就是我们实际听到的声音:

鸟鸣声Camille Vacher 录制(创意共享署名 4.0),Mark S Jobling 的插图(维基百科公共, CC BY 3.0

对于人类而言,声音的不同特征,以及我们希望鸟鸣分类器识别的内容是:

  • 呼叫的持续时间(一些鸟类发出较长的声音,而另一些则发出较短的声音)

  • 音调及其在呼叫中的演变

  • 幅度随时间的潜在演变

  • 呼叫频率/幅度随时间演变的一些特征

让我们深入探索上述鸟鸣的特征。

呼叫频率的演变

如果我们准确地放大到歌曲的开始部分,可以观察到时间序列状态的变化,这突出了鸟鸣声开始时的主要频率。

鸟鸣声Camille Vacher 录制(创意共享署名 4.0),转录为气压时间序列,作者插图

如果我们稍微深入信号中,可以看到主频率的差异以及信号幅度的差异。我们来看一下两个不同时间戳的信号的 5 个振荡周期。要计算信号的振荡频率,我们只需查看一个完整振荡之间的时间,并取倒数。我们在这里对 5 个周期进行平均,以获得稍微更稳定的测量值。

在 t=0.0918s 时,振荡频率大约为 1/[(0.0922–0.0913)/5] ~ 5.55kHz。

鸟鸣声Camille Vacher 录制(创意共享署名 4.0),转录为气压时间序列,作者插图

另一方面,在 0.229s 左右,频率较低:1/[(0.2295–0.2284)/5] ~ 4.55kHz。我们还可以注意到,第二个信号的幅度约为之前信号的 4 倍。

鸟鸣Camille Vacher 录制 (知识共享署名 4.0),转录为气压时间序列,作者插图

那么声音的音色呢?

我们上面观察到的频率不足以完全表征鸟鸣。想象两个乐器演奏相同的音符:气压信号的主要频率可以相同(相同的音符,因此相同的音高),但你的耳朵仍然可以区分这两个乐器。

到目前为止,我们将声音视为纯单频信号。实际上,声音由无限多个信号组成,这些信号相互叠加,并为声音赋予特定的音色,这也被我们的脑袋用来区分不同的声音。这些其他频率通常被称为“谐波”,并且从我们的“时间域”表示中很难区分。

仍然可以识别出一个线索:如果我们的鸟鸣是一个完全单调的正弦波,我们应该不会看到幅度的变化,如下图所示:

单频周期信号的示例 (f(t) = A * sin(2 pi f t) )

如果我们在前面的时间序列中添加另一个不同频率的信号,我们将观察到幅度的调制,同时保持主要频率:

将不同频率的周期信号相加:(f(t) = A1 * sin(2 pi f1 t) + A2 * sin(2 pi f2 t) ),作者插图

负责调制时间域幅度的频率是基础的,因为它们给声音带来了“色彩”。回到我们的例子,这些调制可以在我们以适当的细节观察时看到:

鸟鸣Camille Vacher 录制 (知识共享署名 4.0),转录为气压时间序列,作者插图

从声音到图像

傅里叶变换

傅里叶变换是一个数学工具,它帮助我们将复杂的信号分解成一系列简单的正弦波和余弦波,每个波都有特定的频率和幅度。以下是傅里叶变换的简化版本:

傅里叶变换的简化版本,忽略相位信息

简而言之,傅里叶变换只是告诉我们任何时间序列都可以分解为具有不同幅度的基本正弦/余弦波的连续和(公式的积分)。这正是我们所寻找的(因为声音和频率谱是紧密相关的)

这种方法允许我们“分解”信号并识别每个频率成分,从而更全面地理解整体声音。

两个余弦信号混合的傅里叶变换

为了更清楚地说明,让我们考虑之前的例子,其中我们组合了两个不同频率的周期信号。正如我们观察到的,输出信号是一个幅度调制的信号。

加上不同频率的周期信号,作者插图

傅里叶变换的思想就是识别和表示我们组合信号的不同成分,这些成分随后可以在图表上表示出每个主要余弦波的幅度和频率。

原始信号被分解成主要的余弦波。它们的幅度和频率显示在一个图表中,作者插图

结果图显示了复杂信号的主要成分及其频率和幅度,称为谱图,它包含了组成我们信号的“特征”。

我们编造的例子的谱图

这个图表告诉我们的是,我们编造的周期信号由两个“主要”余弦信号组成:

  • 一个频率为 2250Hz、幅度为 0.5 的余弦波

  • 一个频率为 5500Hz、幅度为 1 的余弦波

这个图表足以完全描述我们的主要周期信号:我们从“时间域”跳到了“频率域”。

将概念扩展到实际信号

然而,我们的鸟鸣声要复杂得多,它可以混合无数个频率,每个频率都对鸟鸣声的独特音色做出贡献,频率组成也可以随着时间而变化。

我们将傅里叶变换应用于整个信号,而是仅在一个足够小的尺度上应用,以便信号“足够规律”,但又足够长以包含足够的振荡周期。

例如,让我们回到 t=0.0918s 和 t=0.229s 的信号,并查看谱图。得到的傅里叶变换这次是连续的,但在某些频率处有峰值,这些峰值与本文前一章的计算结果相匹配。

时间域(左)和谱图(右)(t=0.0918s),鸟鸣声Camille Vacher录制(知识共享署名 4.0),以气压时间序列记录,作者插图

时间域(左)和频谱图(右)(t=0.229s),鸟鸣声Camille Vacher录制(创作共用署名 4.0),转录为空气压力时间序列,作者插图

其次,我们可以更详细地确定每个信号部分的组成。特别是,我们看到第二个片段由多个频率峰值构成,从和声的角度来看更为“丰富”,为我们提供了关于之前提到的“色彩”的新信息。

对信号的一个子部分应用傅里叶变换,如上所述,通常被称为短时傅里叶变换 (STFT),这是一个强大的工具,特别适用于局部描述声音并跟踪其随时间的变化。

从 STFT 到谱图

现在我们有了一个工具,可以用来识别时间信号片段中的不同主要成分(幅度/频率)。我们现在可以将这种方法应用于整个信号,使用滑动窗口提取声音随时间的特征。请注意,我们将不再以散点图的形式展示谱图,而是使用热图表示,频率轴垂直显示,每个像素表示该频率的强度。

从时间信号到 STFT 热图,作者插图

利用这种表示方法,我们现在可以水平堆叠使用滚动窗口计算的整个信号的 STFT,并通过图像可视化频谱随时间的演变。生成的图像称为谱图

时间域中的鸟鸣声及其关联的谱图,鸟鸣声Camille Vacher录制(创作共用署名 4.0),转录为空气压力时间序列,作者插图

在上述谱图中,每一列像素代表了一个给定时间戳中心的小片段信号的 STFT。

有许多类型的谱图具有不同的尺度,以及应用时间-频率变换的不同超参数。其中包括对数频率功率谱图、常数 Q 变换(CQT)、梅尔谱图等……每种都有其独特之处,但都基于提取频率特征并以(时间 x 频率热图)的形式表示,这种表示可以被解读为图像。

一些示例

声谱图的优点在于它将声音的所有重要特征浓缩为一幅简单的图像。分析这幅图像可以告诉你声音在时间上的幅度、音调和颜色变化,这正是我们(或机器学习/深度学习算法)识别声音发射者所需的。

让我们看一下几个持续 5 秒的声音及其相关的声谱图。

第一个样本是由Benoît Van Hecke录制的欧洲林雀。

欧洲林雀的叫声,由Benoît Van Hecke提供,图像来自Sébastien FAILLON维基媒体共享资源)。

欧洲林雀的叫声在时间域中的图像,以及相关的声谱图,由Benoît Van Hecke提供。

第二个录音是由Benoît Van Hecke录制的欧洲知更鸟。

欧洲知更鸟的叫声,由Benoît Van Hecke提供,图像由Sharp Photography, sharpphotography维基媒体共享资源)。

欧洲知更鸟的叫声在时间域中的图像,以及相关的声谱图,由Benoît Van Hecke提供。

最后一条是由Benoît Van Hecke录制的 5 秒画眉

画眉的叫声,由Benoît Van Hecke提供,图像由 Tony Wills 提供(维基媒体共享资源)。

画眉的叫声,在时间域中,以及相关的声谱图,由Benoît Van Hecke提供。

为什么声谱图+ CNN 在声音分类中比 LSTM 更有效。

如果你以前从未涉足声音分类系统,你可能会考虑使用像 LSTM 这样的递归神经网络直接从声音时间序列中提取相关特征。

这将是一个糟糕的主意,因为即使这些模型设计用于提取时间依赖性,它们在提取频率特征方面并不高效,而正如我们所见,频率特征对声音识别任务至关重要。LSTM 本质上也是计算开销大且低效的(因为数据是按顺序处理的)。这意味着在给定时间内需要处理的数据远远少于标准卷积神经网络(CNN)。

另一方面,将时间序列数据转换为声谱图,这实际上是频率信息随时间变化的可视化表示,允许我们使用为图像数据设计的卷积神经网络(CNN),这些网络在捕捉空间模式方面非常有效。在声谱图的情况下,这些空间模式对应于随时间变化的频率模式。这一步可以看作是一个自然的“特征工程”步骤,保证了声音分类任务的更高效率。

结论与进一步挑战

在这篇文章中,我们探讨了用于从声音中提取所有相关特征并将时间序列转换为图像的机制。

我们只覆盖了初步的预处理步骤,但即使声谱图是一种强大的变换工具,它也不是万能的。仍然存在若干需要解决的挑战。

主要挑战之一是弱标注数据的问题。在许多情况下,录音中感兴趣声音的确切时间未知,标签仅表示录音中某处存在声音。

另一个挑战是录音中的背景噪声。可以使用降噪或滤波等技术来减轻这个问题,但这些技术也有可能扭曲鸟鸣声,从而丢失重要信息。其他的替代方法包括特定于声音分类的数据增强技术,如“Mix-up”(一种通过线性组合原始样本创建新声音的方法)或添加新的背景噪声。

最后,录音的长度也可能成为一个问题,尤其是与弱标注问题相结合时。虽然有些声音仅持续几秒钟,但其他声音可能持续几分钟。这种变化可能使模型输入的标准化变得困难,并且也可能影响模型的性能,因为从较短的录音中准确识别物种可能更加困难。

尽管面临这些挑战,使用声谱图和卷积神经网络(CNN)提供了一个有前景的声音识别方法,我希望这一探索能够为那些开始新机器学习声音分类项目的人提供一个宝贵的起点。凭借正确的工具和理解,我们可以应对这些挑战,并解锁音频数据的巨大潜力。

如何使用 Python 识别数据中的异常值

原文:towardsdatascience.com/how-to-identify-outliers-in-data-with-python-a9fe40235064

一篇探讨数据集异常值检测技术的文章。学习如何使用数据可视化、z-分数和聚类技术来识别数据集中的异常值

Andrea D'AgostinoTowards Data Science Andrea D'Agostino

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

--

图片由 Tim Mossholder / Unsplash 提供

Nassim Taleb 书写了如何“尾部”事件定义了世界现象的成功(或失败)的很大一部分。

每个人都知道预防比治疗更重要,但很少有人奖励预防行为。

N. Taleb — 《黑天鹅》

尾部事件是一种罕见事件,其概率位于分布的尾部,左侧或右侧。

www.researchgate.net/figure/A-normal-distribution-curve-with-its-two-tails-Note-that-an-observed-result-is-likely-to_fig2_50196301

根据 Taleb 的说法,我们的生活主要集中在最可能发生的事件上。这样一来,我们并没有为可能发生的罕见事件做好准备。

当罕见事件发生时(尤其是负面的事件),它们会让我们感到意外,而我们通常采取的行动往往无效。

想想我们在罕见事件发生时的行为,例如 FTX 加密货币交易所的破产,或是一场强大的地震破坏了地区。对于直接涉事的人,典型的反应是恐慌。

异常现象无处不在,当我们绘制分布及其概率函数时,我们实际上获得了有用的信息,以保护自己或在这些尾部事件发生时实施策略。

因此,有必要了解如何识别这些异常,尤其是在观察到这些异常时要做好行动准备。

在本文中,我们将重点介绍用于识别异常值(即上述提到的异常现象)的方法和技术。特别是,我们将探讨数据可视化技术以及描述性统计和统计测试的使用。

异常值的定义

异常值是数据集中显著偏离其他值的值。这种偏离可以是数值上的,也可以是类别上的。

例如,数值异常是指我们有一个值比数据集中大多数其他值要大得多或小得多。

另一方面,类别异常值是指我们有标签称为“其他”或“未知”,这些标签占数据集中其他标签的比例要高得多。

异常值可能由测量错误、输入错误、抄录错误或数据本身不符合数据集的正常趋势引起。

在某些情况下,异常值可能表明数据集或产生数据的过程存在更广泛的问题,并能为开发数据收集过程的人员提供重要见解。

帮助我们识别数据集中异常值的技术

我们可以使用几种技术来识别数据中的异常值。这些是我们将在本文中讨论的方法。

  • 数据可视化:这可以通过查看数据分布来识别异常,利用适合此目的的图表。

  • 使用描述性统计,例如四分位距

  • 使用z-score

  • 使用聚类技术:这可以帮助识别相似数据的组,并识别任何“孤立”或“无法分类”的数据。

每种方法都有效于识别异常值,应根据我们的数据来选择。让我们逐一看看这些方法。

数据可视化

寻找异常的最常见技术之一是通过探索性数据分析,特别是通过数据可视化。

使用 Python,你可以利用像 Matplotlib 或 Seaborn 这样的库来可视化数据,以便你可以轻松发现任何异常。

例如,你可以创建直方图或箱线图来可视化数据的分布,并发现任何显著偏离均值的值。

图片由作者提供。

箱线图的结构可以通过这篇 Kaggle 文章来理解。

www.kaggle.com/discussions/general/219871

如果你想了解更多关于如何进行探索性数据分析(EDA)的内容,请阅读这篇文章 👇

## Python 中的探索性数据分析——逐步过程

什么是探索性分析,它的结构是什么,如何在 Python 中利用 Pandas 和其他数据工具应用它……

[towardsdatascience.com

描述性统计的使用

识别异常的另一种方法是使用描述性统计。例如,可以使用四分位距(IQR)来识别偏离均值显著的值。

四分位距(IQR)定义为数据集中第三四分位数(Q3)与第一四分位数(Q1)之间的差值。异常值被定义为超出 IQR 范围并乘以通常为 1.5 的系数的值。

之前讨论的箱线图只是利用这些描述性指标来识别异常的一种方法。

在 Python 中使用四分位距识别异常值的示例如下:

import numpy as np

def find_outliers_IQR(data, threshold=1.5):
    # Find first and third quartiles
    Q1, Q3 = np.percentile(data, [25, 75])
    # Compute IQR (interquartile range)
    IQR = Q3 - Q1
    # Compute lower and upper bound
    lower_bound = Q1 - (threshold * IQR)
    upper_bound = Q3 + (threshold * IQR)
    # Select outliers
    outliers = [x for x in data if x < lower_bound or x > upper_bound]
    return outliers

这种方法计算数据集的第一和第三四分位数,然后计算 IQR 以及下限和上限。最后,将异常值识别为超出下限和上限的值。

这个实用函数可以用来识别数据集中异常值,并且可以被添加到你几乎所有项目的工具函数库中。

使用 z 分数

另一种发现异常的方法是通过 z 分数。z 分数测量一个值相对于均值的标准差偏差程度

转换数据为 z 分数的公式如下:

其中 x 是原始值,μ 是数据集均值,σ 是数据集标准差。z 分数指示原始值距离均值的标准差数量。z 分数大于 3(或小于 -3)通常被视为异常值。

这种方法在处理大数据集时特别有用,当你想以客观和可重复的方式识别异常时更是如此。

在 Python 的 Sklearn 中,z 分数的转换可以这样进行

from sklearn.preprocessing import StandardScaler

def find_outliers_zscore(data, threshold=3):
    # Normalize data
    scaler = StandardScaler()
    standardized = scaler.fit_transform(data.reshape(-1, 1))
    # Select outliers
    outliers = [data[i] for i, x in enumerate(standardized) if x < -threshold or x > threshold]
    return outliers

使用聚类技术

最后,聚类技术可以用来识别任何“孤立”或“无法分类”的数据。这在处理非常大且复杂的数据集时特别有用,因为数据可视化不足以发现异常。

在这种情况下,一个选择是使用基于密度的空间聚类应用噪声(DBSCAN)算法,这是一种聚类算法,可以基于数据的密度识别数据组,并找到不属于任何聚类的点。这些点被视为异常值。

DBSCAN 算法可以再次通过 Python 的 sklearn 库实现。

以这个可视化数据集为例

图片由作者提供。

DBSCAN 应用程序提供了这种可视化效果

图片由作者提供。

创建这些图表的代码如下

import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import DBSCAN

def generate_data_with_outliers(n_samples=100, noise=0.05, outlier_fraction=0.05, random_state=42):
    # Create random data
    X = np.concatenate([np.random.normal(0.5, 0.1, size=(n_samples//2, 2)),
                         np.random.normal(1.5, 0.1, size=(n_samples//2, 2))], axis=0)

    # Add outliers
    n_outliers = int(outlier_fraction * n_samples)
    outliers = np.random.RandomState(seed=random_state).rand(n_outliers, 2) * 3 - 1.5
    X = np.concatenate((X, outliers), axis=0)

    # Add noise to the data to resemble real-world data
    X = X + np.random.randn(n_samples + n_outliers, 2) * noise

    return X

# Genereate data
X = generate_data_with_outliers(outlier_fraction=0.2)

# Apply DBSCAN to cluster the data and find outliers
dbscan = DBSCAN(eps=0.2, min_samples=5)
dbscan.fit(X)

# Select outliers
outlier_indices = np.where(dbscan.labels_ == -1)[0]

# Visualize
plt.scatter(X[:, 0], X[:, 1], c=dbscan.labels_, cmap="viridis")
plt.scatter(X[outlier_indices, 0], X[outlier_indices, 1], c="red", label="Outliers", marker="x")
plt.xticks([])
plt.yticks([])
plt.legend()
plt.show()

该方法创建一个 DBSCAN 对象,使用 epsmin_samples 参数并将其拟合到数据上。然后将那些不属于任何簇的值标识为异常值,即标记为 -1 的值。

这只是许多可以用来识别异常的聚类技术中的一种。例如,基于深度学习的方法依赖于 自编码器,这是一种利用数据压缩表示来识别输入数据中独特特征的神经网络。

结论

在这篇文章中,我们已经看到几种用于识别数据异常值的技术。

我们讨论了数据可视化、描述性统计和 z 分数的使用,以及聚类技术。

每种技术都是有效的,应根据您分析的数据类型来选择。重要的是要记住,识别异常值可以提供重要的信息,以改进数据收集过程并根据获得的结果做出更好的决策。

如果您想支持我的内容创作活动,请随时通过以下推荐链接加入 Medium 的会员计划。我将从您的投资中获得一部分,您将能够无缝地访问 Medium 上关于数据科学及更多内容的丰富文章。

[## 使用我的推荐链接加入 Medium - Andrea D'Agostino

作为 Medium 会员,您的会员费的一部分将会分配给您阅读的作者,同时您可以完全访问每一个故事……

medium.com](https://medium.com/@theDrewDag/membership?source=post_page-----a9fe40235064--------------------------------)

推荐阅读

对于有兴趣的读者,这里是我推荐的与机器学习相关的每个主题的书单。这些书籍在我看来是必读的,并对我的职业生涯产生了重大影响。

免责声明:这些是亚马逊的关联链接。我将因推荐这些商品而从亚马逊获得少量佣金。您的体验不会改变,也不会额外收费,但这将帮助我扩展业务,并制作更多关于人工智能的内容。

有用的链接(由我编写)

如何识别业务关键数据

原文:towardsdatascience.com/how-to-identify-your-business-critical-data-c58506116a41?source=collection_archive---------4-----------------------#2023-06-14

实用步骤以识别业务关键数据模型和仪表盘,并提升对数据的信心

Mikkel DengsøeTowards Data Science Mikkel Dengsøe

·

关注 发表于 Towards Data Science ·9 分钟阅读·2023 年 6 月 14 日

--

来源:synq.io

本文由 Lindsay Murphy 共同撰写

不是所有数据都是平等的。如果你在数据团队工作,你会知道如果某个仪表盘出现问题,你会立刻停下手头的一切去解决,而其他问题可能要等到周末才能处理。这有其充分的理由。前者可能意味着整个公司都在缺少数据,而后者可能没有显著影响。

然而,随着团队的扩展以及数据模型和仪表板数量的增加,跟踪所有业务关键数据可能会变得困难。这就是为什么会发生这些情况的原因

“我完全不知道财务部门依赖于这个仪表板来进行他们的月度审计报告”

“到底怎么回事,我们的 CEO 竟然收藏了我在六个月前匆忙制作的这个仪表板”

在这篇文章中,我们将探讨

  • 为什么你应该识别你的关键数据资产

  • 如何识别关键的仪表板和数据模型

  • 为关键数据创建一种正常运行的文化

为什么你应该识别你的业务关键数据

当你已经绘制出你的业务关键资产时,你可以获得一个端到端的概览,显示哪些数据模型或仪表板是业务关键的,它们的使用地点,以及它们的最新状态。

这在多种不同的方式中都非常有用:

  • 它可以成为一份重要的文档,帮助推动整个业务在最重要的数据资产上的一致性

  • 这会增强数据团队的信心,使他们在更改和更新现有模型或功能时不必担心破坏下游的关键内容

  • 当问题出现时,它能够使决策、速度和优先级管理更为高效

  • 它允许你的团队将更多的精力集中在关键资产上,并让一些不那么重要的事情随之而去

查看重要受影响的数据模型和仪表板的示例。来源:synq.io

在这篇文章中,我们将探讨如何识别你的业务关键数据模型和仪表板。你可以将大部分相同的原则应用于其他对你的业务可能至关重要的数据资产。

什么数据是对业务至关重要的

用于决策的数据是重要的,如果数据不正确,可能会导致错误的决策,并随着时间推移失去对数据的信任。但数据驱动的企业拥有真正关键的业务数据。如果这些数据不正确或过时,你会面临紧急情况,如果不修复,可能会立即对业务产生影响,比如…

  • 成千上万的客户可能会收到错误的电子邮件,因为反向 ETL 工具正在从过时的数据模型中读取数据

  • 你向监管机构报告了不正确的数据,而你的高管们可能会承担个人责任

  • 你的预测模型没有运行,数百名客服人员在假期前无法获取他们的下一班次排班

来源:synq.io

绘制这些用例需要你对公司运作有深刻的理解,了解对利益相关者最重要的事项以及问题的潜在影响。

识别你的业务关键仪表板

Looker 在预构建的探索中暴露了有关内容使用的元数据,你可以用自己的数据来丰富这些数据,使其更有用。在以下示例中,我们将使用 Looker,但大多数现代 BI 工具都以某种形式支持基于使用的报告(Lightdash 也内置了 Usage Analytics,Tableau Cloud 提供 Admin Insights,而 Mode 的 Discovery Database 提供对使用数据的访问,仅举几个例子)。

基于业务关键用例的重要性

当你与你的业务领导交谈时,你可以问一些问题,例如:

  • 你接下来三个月的首要任务是什么?

  • 你如何衡量你领域的成功?

  • 你过去一年中遇到的最关键问题是什么?

你的业务领导可能不知道,平均客户支持响应时间从两小时跳升到圣诞节的 24 小时,是由于过时的上游数据预测错误,但他们会向你描述这段痛苦的经历。如果你能够绘制出最关键的操作和工作流程,并理解数据的使用方式,你将开始揭示真正的业务关键数据。

基于仪表盘使用的重要性

最明显的重要仪表盘是公司所有人都在使用的仪表盘。你可能已经知道其中的一些,例如“公司范围 KPIs”、“产品使用仪表盘”或“客户服务指标”。但有时你会惊讶地发现,许多人依赖于你之前不知道存在的仪表盘。

来源:synq.io

在大多数情况下,你应该过滤掉最近的使用情况,避免包括那些六个月前有很多用户但最近一个月没有使用的仪表盘。也有例外情况,比如一个每三个月才使用一次的季度 OKR 仪表盘。

基于仪表盘 C-suite 使用的重要性

不管你愿意与否,如果你的首席执行官经常使用某个仪表盘,那么它就很重要,即使只有少数其他用户。在最糟糕的情况下,你可能会发现 C-suite 的某个成员已经使用了几个月的仪表盘,而你对这个仪表盘的存在一无所知。

“我们发现我们的首席执行官每天都会查看一份包含收入报告的电子邮件,但它被错误地过滤以包含特定的细分,因此与中央公司 KPI 仪表盘不匹配。” — 加拿大医疗保健初创公司

如果你有员工记录系统,你可能能够轻松获得人员职位的标识符,并用这些标识符来丰富你的使用数据。如果没有,你可以维护一个手动的映射,并在执行团队更换时进行更新。

来源:synq.io

虽然使用频率与重要性高度相关,但你的首要任务应该是绘制业务关键用例。例如,一家较大的金融科技公司有一个仪表板由监管报告负责人使用,与监管机构共享关键信息。这些数据的准确性对你的首席执行官来说可能比他们每天查看的仪表板更为重要。

识别你的业务关键数据模型。

随着许多 dbt 项目超出数百或数千个数据模型,了解哪些是业务关键模型很重要,以便你知道何时优先处理运行或测试失败,或建立额外的强健测试。

具有许多下游依赖的数据模型。

你可能有一组数据模型,如果它们出现故障,其他所有模型都会受到延迟或影响。这些通常是所有其他模型依赖的模型,如 usersorderstransactions

你可能已经知道这些模型是什么。如果不知道,你也可以使用 dbt 生成的 manifest.json 文件以及每个节点的 depends_on 属性,遍历所有模型并计算依赖于它们的模型总数。

在大多数情况下,你会发现一些模型具有不成比例的许多依赖项。这些应标记为关键。

关键路径上的数据模型。

数据模型本身很少是关键的,通常是由于其下游依赖的关键性,比如一个重要的仪表板或用于向网站用户提供推荐的机器学习模型。

所有位于业务关键仪表板上游的数据模型都在关键路径上。来源:synq.io

一旦你完成了识别业务关键下游依赖和用例的艰巨工作,你可以使用 dbt 中的 exposures 手动映射这些,或者使用一个工具自动连接你在不同工具中的数据血缘。

任何位于关键资产上游的内容都应该标记为关键或处于关键路径上。

如何保持关键数据模型定义的更新。

尽可能自动化标记关键数据模型。例如:

  • 使用 check-model-tags 来自 pre-commit dbt 包,强制每个数据模型都有关键性标签。

  • 构建一个脚本,或者使用 一个工具,自动为所有位于业务关键资产上游的模型添加 critical-path 标签。

定义关键性标签。

没有一个唯一的正确答案来定义关键性,但你应该问自己两个问题。

  1. 你打算如何不同地处理关键数据资产?

  2. 如何确保在关键领域中维持一致的定义,以便所有人都在同一页面上

大多数公司采用分层方法(例如青铜、银、金)或二进制方法(例如关键、非关键)。这两种选择都可以运作,最佳解决方案取决于您的情况。

来源:synq.io

您应该在定义关键性和编写新员工入职培训的一部分时保持一致,并避免推迟。例如,分层定义可以是

  • 一级:用于机器学习系统的数据模型,用于确定允许哪些用户注册您的产品

  • 二级:CMO 用于每周营销审查的仪表板

  • 三级:产品经理用来跟踪每月产品参与度的仪表板

如果您不持续更新和标记资产,会导致缺乏信任,并假定您不能依赖定义。

关键性定义的位置

没有一个确定的地方来定义关键性,但通常在创建数据资产的工具中,或者在数据目录(例如 Secoda)中进行。

在创建数据资产的工具中定义关键性

在 dbt 中,您可以将关键性定义与数据模型定义一起保存在您的.yml 文件中。这有几个优点,例如在合并 PR 时能够强制执行关键性,或者轻松地在数据目录或可观察性工具等工具之间传递此信息。

models:
 - name: fct_orders 
   description: All orders        
   meta:
     criticality: high

在.yml 文件中定义关键性的示例

在 BI 工具中,使每个人都能够透明地使用的一个选项是使用例如“一级”标签来标识仪表板的标题,以表明其关键性。此数据通常可以提取并在其他工具中使用。

来源:synq.io

在数据目录中定义关键性

在数据目录中,您可以轻松访问所有公司数据,并通过搜索整个堆栈找到常见问题的答案,这使得对齐指标和模型变得更加容易

标记关键数据。来源:secoda.co

根据关键性采取行动

如果由于此原因采取了不同的行动,映射您的业务关键资产将只会产生回报。以下是一些建立质量设计的流程示例。

仪表板:

  • 一级仪表板在推送到生产之前需要进行代码审查

  • 一级仪表板应遵循特定的性能指标,如加载时间,并具有一致的视觉布局

  • 应该每月监控一次使用一级仪表板的情况,由所有者负责

数据模型:

  • 对关键数据模型的测试或运行失败应在同一天内采取行动

  • 关键数据模型的问题应发送给 PagerDuty(一名值班团队成员),以便他们能够快速采取行动

  • 关键数据模型应至少具备唯一性和非空测试以及已定义的所有者

你可以在我们的指南设计数据问题的严重程度级别中阅读更多关于如何处理数据问题的信息。

摘要

如果你识别并绘制出你的业务关键数据资产的映射,你可以更快地处理重要问题,并有意地构建高质量的数据资产。

  • 要识别业务关键的仪表板,首先从业务用例入手。然后考虑使用数据,例如用户数量或是否有高层管理人员在使用某个仪表板。

  • 业务关键的数据模型通常具有许多下游依赖关系和/或关键的下游依赖关系。

  • 定义关键性,可以直接在创建数据资产的工具中进行,或者使用数据目录。

  • 明确你如何处理业务关键资产中的问题,并制定设计质量的程序。

如何使用 Python 代码实现层次聚类

原文:towardsdatascience.com/how-to-implement-hierarchical-clustering-for-direct-marketing-campaigns-with-python-code-ef897f52d1c5

了解层次聚类的方方面面,以及它如何应用于银行业的营销活动分析。

Zoumana KeitaTowards Data Science Zoumana Keita

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

--

照片由 Frederick Warren 提供,来源于 Unsplash

动机

想象一下你是一家领先金融机构的数据科学家,你的任务是帮助团队将现有客户分类为不同的档案:平均白金,以便进行贷款批准。

但这里有个关键点:

由于这些客户没有历史标签,你该如何进行这些类别的创建?

这就是聚类能够提供帮助的地方,它是一种无监督的机器学习技术,用于将未标记的数据分组到类似的类别中。

存在多种聚类技术,但本教程将更专注于层次聚类方法。

它首先提供了层次聚类的概述,然后带你一步步使用流行的Scipy库在Python中实现它。

什么是层次聚类?

层次聚类 是一种将数据分组为称为树状图的簇的技术,表示底层簇之间的层次关系。

层次聚类算法依赖于距离度量来形成簇,通常包括以下主要步骤:

层次聚类的四个主要步骤(图片作者提供)

  • 计算包含每对数据点之间距离的距离矩阵,使用如欧几里得距离、曼哈顿距离或余弦相似度等特定距离度量

  • 合并两个距离最近的簇

  • 更新距离矩阵以考虑新簇。

  • 重复步骤 1、2 和 3,直到所有簇都合并在一起形成一个单一的簇。

层次聚类的一些图形示例

在深入技术实现之前,让我们了解两种主要的层次聚类方法:agglomerativedivisive 聚类。

#1. 聚合聚类

聚合聚类也称为自下而上的方法,它开始时将每个数据点视为一个独立的簇。然后,它反复合并这些簇,直到只剩下一个。

让我们考虑下面的示意图,其中:

  • 我们开始时将每个动物视为一个独立的簇。

  • 然后根据动物列表,根据它们的相似性形成三个不同的簇:鹰和孔雀被分类为 Birds,狮子和熊为 Mammals,蝎子和蜘蛛为 3+ legs

  • 我们继续合并过程,通过将两个最相似的簇:BirdsMammals 合并来创建 Vertebrate 簇。

  • 最后,将剩下的两个簇 Vertebrate3+ legs 合并成一个单一的 Animals 簇。

聚合聚类的示意图(图片由作者提供)

#2. 划分聚类

另一方面,划分式聚类是自上而下的。它从将所有数据点视为一个统一的簇开始,然后逐渐将其拆分,直到每个数据点成为一个独立的簇。

通过观察划分方法的图形:

  • 我们注意到整个 Animal 数据集被视为一个统一的块。

  • 然后,这个块被拆分成两个不同的簇:Vertebrate3+ legs

  • 划分过程会反复应用于先前创建的簇,直到每个动物被区分为其自身的独特簇。

划分聚类的示意图(图片由作者提供)

选择正确的距离度量

选择合适的距离度量是聚类中的关键步骤,这取决于具体的问题。

例如,一组学生可以根据他们的原籍国、性别或先前的学术背景进行聚类。虽然这些标准都是有效的聚类依据,但它们传达了独特的意义。

欧几里得距离是许多聚类软件中最常用的度量。然而,还存在其他距离度量,如曼哈顿距离、堪培拉距离、皮尔逊相关性和闵可夫斯基距离。

如何在合并簇之前测量它们

聚类可能被认为是将数据分组的简单过程。但它不仅仅是这样。

在合并簇之前,有三种主要的标准方法来测量最近的一对簇:(1) 单链聚类(2) 完全链聚类(3) 平均链聚类。让我们更详细地探讨每一种。

#1. 单链聚类

在单链接聚类中,两个给定簇 **C1****C2** 之间的距离对应于两个簇中所有项对之间的最小距离。

单链接的距离公式(图像来源:作者)

在两个簇中的所有项对中,**b****k** 具有最小距离。

单链接示意图(图像来源:作者)

#2. 完整链接

对于完整链接聚类,两个给定簇 **C1****C2** 之间的距离是两个簇中所有项对之间的最大距离。

单链接的距离公式(图像来源:作者)

在两个簇中的所有项对中,用绿色突出显示的项(**f****m**)具有最大距离。

完整链接示意图(图像来源:作者)

#3. 平均链接

在平均链接聚类中,两个给定簇 **C1****C2** 之间的距离是通过计算两个簇中每对项之间所有距离的平均值来得到的。

平均链接的距离公式(图像来源:作者)

平均链接示意图(图像来源:作者)

从上述公式可以计算出平均距离如下:

平均链接的距离计算(图像来源:作者)

在 Python 中实现层次聚类

现在你已经理解了层次聚类的工作原理,让我们深入探讨使用 Python 的技术实现。

我们首先配置环境,了解数据及相关预处理任务,最后应用聚类。

配置环境

[Python](https://www.python.org/downloads/) 是必需的,并且需要与以下库一起安装:

  • Pandas 用于加载数据框

  • Scikit-learn 用于数据归一化

  • Seaborn 和 Matplotlib 用于数据可视化

  • Scipy 用于应用聚类

所有这些库都通过以下 pip 命令从你的笔记本中安装:

%%bash
pip install scikit-learn
pip install pandas
pip install matplotlib seaborn
pip install scipy

我们使用 %%bash 语句来代替逐个安装每个库的 !pip [library],使得笔记本单元被视为 shell 命令,从而忽略 !,便于安装。

理解数据

我们使用了葡萄牙银行机构的银行营销活动(电话)的数据子集。

该数据集来自 UCI,并且根据 知识共享署名 4.0 国际(CC BY 4.0)许可证进行授权。

由于本教程的无监督性质,我们去掉了目标列 y,该列指定客户是否订阅了定期存款。

使用 head 函数仅返回前五条记录,这不足以提供有关数据结构的足够信息。

import pandas as pd

URL = "https://raw.githubusercontent.com/keitazoumana/Medium-Articles-Notebooks/main/data/bank.csv"
bank_data = pd.read_csv(URL, sep=";")
bank_data.head()

加载数据的前五行(作者提供的图像)

然而,如果我们使用 info 函数,我们可以获得有关数据集的更详细信息,例如:

  • 总条目数(4,521)和列数(17)

  • 每列的名称及其类型。我们可以观察到主要有两种列类型:int64object

  • 每列缺失值的总数

bank_data.info()

输出:

关于数据的信息(作者提供的图像)

数据预处理

数据预处理是每个数据科学任务中的主要步骤,聚类也不例外。对这些数据应用的主要任务包括:

  • 填补缺失值的适当信息

  • 规范化列值

  • 最后,删除不相关的列

#1. 处理缺失值

缺失值会严重影响分析的整体质量,可以应用多重插补技术来有效解决这些问题。

percent_missing 报告每一列中缺失值的百分比,幸运的是,数据中没有缺失值。

percent_missing =round(100*(loan_data.isnull().sum())/len(loan_data),2)
percent_missing

输出:

数据中缺失值的百分比(作者提供的图像)

#2. 删除不相关的列

保留数据集中 object 列需要更多处理任务,如使用相关编码技术将分类数据编码为其数值表示。

为了简化分析,仅使用 int64(数值型)列。使用 select_dtypes 函数,我们选择要保留的列类型。

import numpy as np 

cleaned_data = bank_data.select_dtypes(include=[np.int64])
cleaned_data.info()

输出:

去除不需要的列的新数据(作者提供的图像)

#3. 分析离群值

层次聚类的一个显著缺点是对离群值的敏感性,这可能会扭曲数据点或聚类之间的距离计算。

确定这些离群值的简单方法是分析数据的分布,使用 boxplot 如下所示的 show_boxplot 辅助函数,它利用了 Seaborn 内置的 boxplot 函数。

import matplotlib.pyplot as plt
import seaborn as sns
def show_boxplot(df):
  plt.rcParams['figure.figsize'] = [14,6]
  sns.boxplot(data = df, orient="v")
  plt.title("Outliers Distribution", fontsize = 16)
  plt.ylabel("Range", fontweight = 'bold')
  plt.xlabel("Attributes", fontweight = 'bold')

show_boxplot(cleaned_data)

输出:

数据中所有变量的箱形图(作者提供的图像)

balance 属性代表客户的年平均余额,是唯一一个与其他数据点相差较远的。

通过使用四分位数范围方法,我们可以删除所有超出四分位数定义范围 +/-1.5*IQR 的点,其中 IQR四分位数范围

整体逻辑在 remove_outliers 辅助函数中实现。

def remove_outliers(data):

  df = data.copy()

  for col in list(df.columns):

        Q1 = df[str(col)].quantile(0.05)
        Q3 = df[str(col)].quantile(0.95)
        IQR = Q3 - Q1
        lower_bound = Q1 - 1.5*IQR
        upper_bound = Q3 + 1.5*IQR

        df = df[(df[str(col)] >= lower_bound) & (df[str(col)] <= upper_bound)]

  return df

然后,我们可以将该函数应用于数据集,并比较新箱型图与去除异常值前的箱型图。

without_outliers = remove_outliers(cleaned_data)

# Display the new boxplot
show_boxplot(without_outliers)

输出:

不再有数据点位于四分位数范围之外(作者提供的图片)

without_outliers.shape

# (4393, 7)

我们最终得到了一个 4,393 行和 7 列的数据集,这意味着从数据中剔除的剩余 127 个观测值是异常值。

#4. 重新缩放数据

由于层次聚类使用的欧几里得距离对不同尺度的变量敏感,因此在计算距离之前最好重新缩放所有变量。

StandardScaler类的fit_transform函数将原始数据转换,使每列具有零均值和单位标准差。

from sklearn.preprocessing import StandardScaler

data_scaler = StandardScaler()

scaled_data = data_scaler.fit_transform(without_outliers)
scaled_data.shape

# (4393, 7)

数据的形状保持不变(4,393 行和 7 列),因为归一化不会影响数据的形状。

应用层次聚类算法

我们已经准备好深入探讨聚类算法的实现!

此阶段,我们可以决定采用哪种链接方法用于linkage()函数的method属性的聚类。

不仅仅关注一种方法,我们将使用欧几里得距离涵盖所有三种链接技术。

from scipy.cluster.hierarchy import linkage, dendrogram

complete_clustering = linkage(scaled_data, method="complete", metric="euclidean")
average_clustering = linkage(scaled_data, method="average", metric="euclidean")
single_clustering = linkage(scaled_data, method="single", metric="euclidean")

计算完所有三种聚类方法后,使用scipy.cluster模块中的dendogram函数和matplotlib中的pyplot函数可视化相应的树状图。

每个树状图的组织方式如下:

  • x 轴代表数据中的聚类

  • y 轴对应样本之间的距离。线条越高,聚类之间的差异越大

  • 通过在该最高垂直线处绘制一条水平线,可以获得适当的聚类数

  • 与水平线的交点数量对应于聚类数

dendrogram(complete_clustering)
plt.show()

输出:

完整聚类方法的树状图(作者提供的图片)

dendrogram(average_clustering)
plt.show()

输出:

平均聚类方法的树状图(作者提供的图片)

当运行单一聚类时,我们可能会遇到递归限制问题。这可以通过使用setrecursionlimit函数并设置足够大的值来解决:

import sys
sys.setrecursionlimit(1000000)

现在我们展示树状图:

dendrogram(single_clustering)
plt.show()

输出:

单一聚类方法的树状图(作者提供的图片)

确定树状图中的最佳聚类数

通过识别不与任何其他聚类(水平线)交叉的最高垂直线,可以获得最佳聚类数。这样的一条线在下面用红圈和绿勾标记。

  • 对于完整链接法:生成的聚类数没有显著的变化

完整链接法:从最高距离而无交集的最佳聚类数(作者提供的图片)

  • 对于平均链接法:两条水平橙线之间的差距稍微多于一个。因此,我们可以考虑两个簇。

平均链接法:从没有交集的最大距离得到的最佳聚类数(作者提供的图像)

  • 对于单链接法:无法确定明确的簇数

单链接法:从没有交集的最大距离得到的最佳聚类数(作者提供的图像)

根据上述分析,平均链接法似乎比单链接法和完全链接法提供了最佳的聚类数,因为后两者没有提供清晰的聚类数理解。

现在我们已经找到了最佳的簇数,让我们使用cut_tree函数在客户的平均年余额的背景下解释这些簇。

cluster_labels = cut_tree(average_clustering, n_clusters=2).reshape(-1, )
without_outliers["Cluster"] = cluster_labels

sns.boxplot(x='Cluster', y='balance', data=without_outliers)

两种借款人的箱线图 (作者提供的图像)

从上面的boxplot中,我们可以观察到:

  • 来自簇 0 的客户拥有最高的平均年余额

  • 来自簇 1 的借款人平均年余额相对较低

结论

祝贺你!!!🎉

我希望这篇文章提供了足够的工具,帮助你将知识提升到一个新水平。代码可以在我的 GitHub 上找到。

同时,如果你喜欢阅读我的文章并希望支持我的写作,可以考虑成为 Medium 的会员。每月仅需 5 美元,你可以无限访问数千个 Python 指南和数据科学文章。

通过使用 我的链接 注册,我将获得一小笔佣金,而你不需要支付额外费用。

[## 通过我的推荐链接加入 Medium - Zoumana Keita

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

zoumanakeita.medium.com

欢迎关注我的 YouTube频道,或在 LinkedIn上打个招呼。如果你需要更多信息,也可以进行 1 对 1 讨论

如何使用 Python 实现学习排序模型

原文:towardsdatascience.com/how-to-implement-learning-to-rank-model-using-python-569cd9c49b08

使用 Python 和 LightGBM 实现 lambdarank 算法的逐步指南

Ransaka RaviharaTowards Data Science Ransaka Ravihara

·发表于数据科学前沿 ·阅读时间 9 分钟·2023 年 1 月 18 日

--

图片来源:安德里克·兰菲尔德Unsplash

在我之前的两篇文章中,我讨论了学习排序模型的基本概念以及评估 LTR 模型的广泛使用的评估指标。你可以通过下面列出的链接访问这些文章。

什么是学习排序:学习排序方法的初学者指南

关于如何处理机器学习中的 LTR 问题的指南

如何评估学习排序模型

关于如何在机器学习中评估 LTR 模型的实用指南

如何评估学习排序模型

在本文中,我们将构建一个用于动漫推荐的 lambdarank 算法。LambdaRank 最初由一个研究小组在微软介绍,现在它已经在微软的 LightGBM 库中提供,并配有易于使用的 sklearn 包装器。让我们开始吧。

从搜索引擎到产品推荐

如上所述,排名在搜索引擎中被广泛使用。但它不仅限于搜索引擎;我们可以采用这一概念,并在适用时构建解决方案。假设我们想为搜索引擎开发一个排名模型,我们应该从一个包含查询、其相关文档(URLs)以及每个查询文档对的相关性评分的数据集开始,如下所示。

图片来源:作者

最后,我们可以基于每个查询和文档对推导特征。

图片来源:作者

这是著名的学习排名研究论文和数据集的数据集格式。好吧,这些是关于搜索引擎的内容。让我们讨论一下如何将这些概念适用于传统的产品推荐任务。有多种方法可以向用户推荐产品。你可以在用户购买物品时展示推荐,或在用户浏览页面时展示推荐等。但为了简单起见,我们将其缩小到一个具体场景。

本文将建立一个用于用户主页定制的动漫推荐模型。当用户登录账户时,我们需要根据排名模型预测的相关性评分来展示动漫。这里我将使用 Anime Recommendation LTR Dataset,它在公共许可下提供。你可以通过这个 kaggle 链接访问它。

让我们读取数据集并快速检查其列。

import zipfile
import pandas as pd

zipped_data = zipfile.ZipFile("anime-recommendation-ltr-dataset.zip")

anime_info_df = pd.read_csv(zipped_data.open('anime_info.csv'))
relavence_scores = pd.read_csv(zipped_data.open('relavence_scores.csv'))
user_info = pd.read_csv(zipped_data.open('user_info.csv'))

anime_info_df.columns.tolist()

# ['anime_id',
#  'Genres',
#  'is_tv',
#  'year_aired',
#  'is_adult',
#  'above_five_star_users',
#  'above_five_star_ratings',
#  'above_five_star_ratio']

user_info.columns.tolist()

# ['user_id',
#  'review_count',
#  'avg_score',
#  'score_stddev',
#  'above_five_star_count',
#  'above_five_star_ratio']

relevance_scores.columns.tolist()
#['anime_id', 'anime_name', 'user_id', 'relevance_score']

好的,让我解释一下我将在这里使用的方法。与搜索引擎数据不同,在这个用例中我们没有查询和文档对。因此,我们将用户视为查询,将他们感兴趣的动漫视为文档。一个搜索查询可以与多个文档关联;用户可以与许多动漫互动。你明白了,对吧 😃

我们在这里预测的目标标签是relevance_score,存储在relevance_scores数据集中。当我们建立排名模型时,它将学习一个函数,将这些动漫按最优顺序排序,使每个用户的相关性最高的动漫排在最前面。

接下来,我们需要通过合并上述三个数据集来创建一个新数据集。我还会基于动漫和用户特征创建新的特征。让我们开始创建数据集。

from sklearn.preprocessing import MultiLabelBinarizer

popular_genres = ['Comedy',
 'Action',
 'Fantasy',
 'Adventure',
 'Kids',
 'Drama',
 'Sci-Fi',
 'Music',
 'Shounen',
 'Slice of Life']

def create_genre_flags(df, popular_genres):
    df = df.dropna(subset=['Genres'])
    df['Genres'] = df['Genres'].apply(lambda x:",".join(s.strip() for s in x.split(",")))
    # use MultiLabelBinarizer to create a one-hot encoded dataframe of the genres
    mlb = MultiLabelBinarizer()
    genre_df = pd.DataFrame(mlb.fit_transform(df['Genres'].str.split(',')),
                            columns=mlb.classes_,
                            index=df.index)
    # create a new dataframe with the movie id and genre columns
    new_df = pd.concat([df['anime_id'], genre_df[popular_genres]], axis=1)
    new_df.columns = ['anime_id'] + popular_genres
    return new_df

anime_genre_info_df = create_genre_flags(anime_info_df,popular_genres)
anime_info_df_final = anime_info_df.merge(anime_genre_info_df,on='anime_id')
anime_info_df_final.columns = [col if col=='anime_id' else f"ANIME_FEATURE {col}".upper() for col in anime_info_df_final.columns]
user_info.columns = [col if col=='user_id' else f"USER_FEATURE {col}".upper() for col in user_info.columns]

train_interim = relavence_scores.merge(anime_info_df_final)
train = train_interim.merge(user_info,how='inner')

让我们快速查看一下数据集的一些统计信息。数据集中总共有 4.8 百万条用户-动漫互动记录,15K 用户,和 16K 动漫。

这是用户和动漫互动分布情况。

图片来源:作者

下图显示了相关性评分的分布情况。

图片来源:作者

现在我们已经创建了用于模型训练的数据集。但学习排名模型的一个主要困惑点是group参数。因为group对我们来说是一个陌生的参数,因为它在其他机器学习算法中并不常见。group参数的思想是对每个查询和文档对的 数据集进行分区。它使模型能够学习每个组内不同特征的相对重要性,从而提高模型的整体性能。拥有十对用户-动漫意味着我们在 LTR 模型中有十个组。每个组的大小可以不同。但组的总和应该是我们拥有的样本数量。简单来说,我们必须使用group参数提供组边界。然后模型可以单独识别每个用户-动漫实例。

例如,如果你有一个包含 6 部动漫的数据集,group = [3, 2, 1],这意味着你有三个组,其中前三条记录在第一个组中,记录 4–5 在第二个组中,记录 6 在第三个组中。

作者提供的图片

因此,在创建组参数之前,按 user_id(query_id) 排序数据集是至关重要的。

na_counts = (train.isna().sum() * 100/len(train))
train_processed = train.drop(na_counts[na_counts > 50].index,axis=1)
train_processed.sort_values(by='user_id',inplace=True)
train_processed.set_index("user_id",inplace=True)

features = ['ANIME_FEATURE IS_TV',
       'ANIME_FEATURE YEAR_AIRED', 'ANIME_FEATURE IS_ADULT',
       'ANIME_FEATURE ABOVE_FIVE_STAR_USERS',
       'ANIME_FEATURE ABOVE_FIVE_STAR_RATINGS',
       'ANIME_FEATURE ABOVE_FIVE_STAR_RATIO', 'ANIME_FEATURE COMEDY',
       'ANIME_FEATURE ACTION', 'ANIME_FEATURE FANTASY',
       'ANIME_FEATURE ADVENTURE', 'ANIME_FEATURE KIDS', 'ANIME_FEATURE DRAMA',
       'ANIME_FEATURE SCI-FI', 'ANIME_FEATURE MUSIC', 'ANIME_FEATURE SHOUNEN',
       'ANIME_FEATURE SLICE OF LIFE', 'USER_FEATURE REVIEW_COUNT',
       'USER_FEATURE AVG_SCORE', 'USER_FEATURE SCORE_STDDEV',
       'USER_FEATURE ABOVE_FIVE_STAR_COUNT',
       'USER_FEATURE ABOVE_FIVE_STAR_RATIO']
target = 'relavence_score'

test_size = int(1e5)
X,y = train_processed[features],train_processed[target].apply(lambda x:int(x * 10))
test_idx_start = len(X)-test_size

xtrain,xtest,ytrain,ytest = X.iloc[0:test_idx_start],X.iloc[test_idx_start:],y.iloc[0:test_idx_start],y.iloc[test_idx_start:]

在上述代码片段中,我执行了以下步骤,

  1. 丢弃了所有空值超过 50% 的列。

  2. 根据 user_id 排序的数据集。否则,group 参数将在各处产生不一致的组。

  3. 排序数据集后,我将最后 100,000 行声明为验证数据,其余的作为训练数据。

  4. 由于 LightGBM 模型期望目标值为整数,我已将目标值缩放到 1–10 之间并转换为整数。 (如果你愿意,可以根据阈值尝试将其转换为 0 或 1。)

让我们定义组参数,并迅速拟合模型,如下所示。

get_group_size = lambda df: df.reset_index().groupby("user_id")['user_id'].count()

train_groups = get_group_size(xtrain)
test_groups = get_group_size(xtest)

print(sum(train_groups) , sum(test_groups))
#(4764372, 100000)

model = LGBMRanker(objective="lambdarank")
model.fit(xtrain,ytrain,group=train_groups,eval_set=[(xtest,ytest)],eval_group=[test_groups],eval_metric=['ndcg'])

#....
# [97] valid_0's ndcg@1: 0.900624 valid_0's ndcg@2: 0.900015 valid_0's ndcg@3: 0.892648 valid_0's ndcg@4: 0.891373 valid_0's ndcg@5: 0.886585
# [98] valid_0's ndcg@1: 0.900624 valid_0's ndcg@2: 0.900015 valid_0's ndcg@3: 0.892648 valid_0's ndcg@4: 0.891895 valid_0's ndcg@5: 0.886632
# [99] valid_0's ndcg@1: 0.900624 valid_0's ndcg@2: 0.901216 valid_0's ndcg@3: 0.892839 valid_0's ndcg@4: 0.892053 valid_0's ndcg@5: 0.88677
# [100] valid_0's ndcg@1: 0.900624 valid_0's ndcg@2: 0.901216 valid_0's ndcg@3: 0.892839 valid_0's ndcg@4: 0.892053 valid_0's ndcg@5: 0.886363

好了,我们完成了训练。现在是对特定客户进行预测的时候了。

这里还有一个关键点需要注意。即模型预测是如何计算的。我感到困惑,因为调用 .predict 方法时,它不需要额外的参数,例如group。所以我从 GitHub 问题中找到了以下信息。根据 LightGBM 的创建者在 这个问题中提到的,LightGBM 的 lambdarank 使用逐点方法生成预测。这意味着我们不需要提供额外的参数,如group。我们可以使用正确的特征向量调用 .predict() 方法。

然而,由于我们正在开发 LTR 模型,必须有一些候选产品,并根据预测的相关性评分对其进行排序。为了生成候选动漫,我将在这里使用一种简单的方法。即,选择未向给定用户展示的动漫。从未展示的动漫中选择一个 N 的随机子集。基于用户和选择的 N 部动漫子集生成特征。最后,使用生成的特征向量获取相关性评分,并根据相关性评分对动漫进行排序。但在实际应用中,我们应该使用一些有意义的方法。例如,你可以如下生成候选项,

  • 选择用户最喜欢的N个类别。

  • 对于上面选择的每个类别,选择评分最高的m部动漫。现在你有M N*部动漫需要为该用户排序。只需创建用户基础和动漫基础特征。最后,使用创建的特征向量调用.predict()方法。

user_2_anime_df = relavence_scores.groupby("user_id").agg({"anime_id":lambda x:list(set(x))})
user_2_anime_map = dict(zip(user_2_anime_df.index,user_2_anime_df['anime_id']))

#create candidate pool, this will be a all the animes in the database
candidate_pool = anime_info_df_final['anime_id'].unique().tolist()

#anime_id to it's name mapping
anime_id_2_name = relavence_scores.drop_duplicates(subset=["anime_id","Name"])[['anime_id',"Name"]]
anime_id_2_name_map = dict(zip(anime_id_2_name['anime_id'],anime_id_2_name['Name']))

def candidate_generation(user_id:int,candidate_pool:list,user_2_anime_map:dict,N:int):
    """
    Note: this a totally random generation, only for demo purpose
    Generates a list of N anime candidates for a given user based on their previously liked animes.

    Parameters:
        user_id (int): The user's ID.
        candidate_pool (list): A list of all possible anime candidates.
        user_2_anime_map (dict): A dictionary that maps users to their liked animes.
        N (int): The number of anime candidates to generate.

    Returns:
        already_interacted (list): List of animes which user already liked
        candidates (list): A list of N anime candidates for the user.
    """

    #get the already liked animes
    already_interacted = user_2_anime_map[user_id]

    #candidates will be rest of animes which are not exposed to user
    candidates = list(set(candidate_pool) - set(already_interacted))

    return already_interacted,np.random.choice(candidates,size=N)

def generate_predictions(user_id,user_2_anime_map,candidate_pool,feature_columns,anime_id_2_name_map,ranker,N=100):
    """
    Generates predictions for anime recommendations for a given user.

    Parameters:
        user_id (int): The user's ID.
        user_2_anime_map (dict): A dictionary that maps users to their liked animes.
        candidate_pool (list): A list of all possible anime candidates.
        feature_columns (list): A list of feature columns to use for generating predictions.
        anime_id_2_name_map (dict): A dictionary that maps anime IDs to their names.
        ranker (object): A trained model object that is used to generate predictions.
        N (int): The number of anime predictions to generate.

    Returns:
        predictions (DataFrame): A dataframe containing the top N anime recommendations for the user.
    """
    already_liked,candidates = candidate_generation(user_id,candidate_pool,user_2_anime_map,N=10000)

    #Create dataframe for candidates
    candidates_df = pd.DataFrame(data=pd.Series(candidates,name='anime_id'))

    # Merge with feature dataframe
    features = anime_info_df_final.merge(candidates_df)

    #Add user id as a feature
    features['user_id'] = user_id

    # Merge with user information
    features = features.merge(user_info)

    # If number of already liked animes is less than number of candidates
    # Extend the already liked list with -1
    already_liked = list(already_liked)
    if len(already_liked) < len(candidates):
        append_list = np.full(fill_value=-1,shape=(len(candidates)-len(already_liked)))
        already_liked.extend(list(append_list))

    #Create dataframe for predictions
    predictions = pd.DataFrame(index=candidates)
    #Add anime names
    predictions['name'] = np.array([anime_id_2_name_map.get(id_) for id_ in candidates])
    #Generate predictions
    predictions['score'] = ranker.predict(features[feature_columns])
    predictions = predictions.sort_values(by='score',ascending=False).head(N)

    predictions[f'already_liked - sample[{N}]'] = [anime_id_2_name_map.get(id_) for id_ in already_liked[0:len(predictions)]]
    return predictions

#let's generate the predictions 
generate_predictions(123,user_2_anime_map,candidate_pool,feature_columns=features,anime_id_2_name_map=anime_id_2_name_map,ranker=model,N=10)

此外,我们可以使用 shap 来解释模型的预测结果。

import shap

def generate_shap_plots(ranker, X_train, feature_names, N=3):
    """
    Generates SHAP plots for a pre-trained LightGBM model.

    Parameters:
        ranker (lightgbm.Booster): A trained LightGBM model
        X_train (np.ndarray): The training data used to fit the model
        feature_names (List): list of feature names
        N (int): The number of plots to generate

    Returns:
        None
    """
    explainer = shap.Explainer(ranker, X_train, feature_names=feature_names)
    shap_values = explainer(X_train.iloc[:N])

    # Create a figure with 2 subplots
    # fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15,5))

    # Plot the summary plot on the first subplot
    plt.subplot(1, 2, 1)
    shap.summary_plot(shap_values, feature_names=feature_names, plot_type='bar')

    # Plot the feature importance plot on the second subplot
    plt.subplot(1, 2, 2)
    shap.summary_plot(shap_values, feature_names=feature_names, plot_type='dot')

    plt.show()

generate_shap_plots(model,xtrain,features,N=10000)

作者提供的图片

结论

本文旨在为读者的特定用例提供一个良好的起点。因为当我开始为我的项目构建 LTR 模型时,没有好的初学者指南。然而,对于某些用例,训练 LTR 模型可能不是必要的。你可以使用简单的方法,如回归、多分类/标签分类或聚类。所以不要过度设计你的解决方案;明智地选择工具吧 ; )

感谢阅读!本文的笔记本可以在我的GitHub repo中找到。

参考文献:

如何在 PySpark 中实现随机森林回归

原文:towardsdatascience.com/how-to-implement-random-forest-regression-in-pyspark-9582f4964285

一个关于随机森林回归的 PySpark 教程

Yasmine HejaziTowards Data Science Yasmine Hejazi

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

--

图片来源:Jachan DeVolUnsplash

介绍

PySpark 是一个强大的数据处理引擎,建立在 Apache Spark 之上,旨在进行大规模数据处理。它提供了可扩展性、速度、多功能性、与其他工具的集成、易用性、内置的机器学习库以及实时处理能力。它是高效有效地处理大规模数据处理任务的理想选择,其用户友好的界面允许用 Python 轻松编写代码。

使用在 ggplot2 上找到的 Diamonds 数据来源许可证),我们将演示如何在 PySpark 中实现随机森林回归模型并分析结果。如果你想查看线性回归如何应用于同一数据集,请 点击这里!

本教程将涵盖以下步骤:

  1. 将数据加载并准备为向量化输入

  2. 使用 MLlib 中的 RandomForestRegressor 训练模型

  3. 使用 MLlib 中的 RegressionEvaluator 评估模型性能

  4. 绘制并分析特征重要性以提高模型透明度

图片来源:Martin de ArribaUnsplash

准备数据

diamonds数据集包含如caratcolorcutclarity等特征,所有特征都列在数据集文档中。

我们试图预测的目标变量是price

df = spark.read.csv("/databricks-datasets/Rdatasets/data-001/csv/ggplot2/diamonds.csv", header="true", inferSchema="true")
display(df)

就像线性回归教程一样,我们需要预处理数据,以便我们得到一个数值特征向量作为模型输入。我们需要将分类变量编码为数值特征,然后将它们与数值变量结合起来,形成一个最终向量。

达到此结果的步骤如下:

  1. 将数值特征和分类特征分开。

  2. 使用StringIndexer预处理分类特征:这本质上是为每个类别分配一个数值(即:Fair: 0, Ideal: 1, Good: 2, Very Good: 3, Premium: 4)。

  3. 使用OneHotEncoder预处理分类特征:这将类别转换为二进制向量。结果是一个稀疏向量,表示 StringIndexer 中的哪个索引具有独热值 1。

  4. 将你的数值特征与新的分类编码特征组合起来。

  5. 使用VectorAssembler组装特征向量。

你可以使用以下代码对分类特征进行索引和独热编码。这将完成步骤 1-3。

from pyspark.ml import Pipeline
from pyspark.ml.feature import StringIndexer, OneHotEncoder

cat_cols= ["cut", "color", "clarity"]
stages = [] # Stages in Pipeline

for c in cat_cols:
    stringIndexer = StringIndexer(inputCol=c, outputCol=c + "_index")
    encoder = OneHotEncoder(inputCols=[stringIndexer.getOutputCol()], \
            outputCols=[c + "_vec"])    
    stages += [stringIndexer, encoder] # Stages will be run later on

你可以使用以下代码来组装你的最终特征向量。这将完成步骤 4-5。然后,我们可以将这些阶段作为管道运行。这会将数据经过我们到目前为止定义的所有特征转换。

from pyspark.ml.feature import VectorAssembler

# Transform all features into a vector
num_cols = ["carat", "depth", "table", "x", "y", "z"]
assemblerInputs = [c + "_vec" for c in cat_cols] + num_cols
assembler = VectorAssembler(inputCols=assemblerInputs, outputCol="features")
stages += [assembler]

# Create pipeline and use on dataset
pipeline = Pipeline(stages=stages)
df = pipeline.fit(df).transform(df)

训练模型

首先,我们需要将数据集拆分为训练集和测试集。

train, test = df.randomSplit([0.90, 0.1], seed=123)
print('Train dataset count:', train.count())
print('Test dataset count:', test.count())

接下来,我们导入 MLlib 中的随机森林回归模型(pyspark.ml.regression.RandomForestRegressor)。需要注意的一些默认参数是:maxDepth=5numTrees=20。你可以在交叉验证期间或手动调整这些参数,以获得适合你问题的最佳参数集。然后调用fit()来将模型拟合到训练数据上。在将模型拟合到训练数据后,我们可以开始预测。要对数据进行预测,请调用transform()

from pyspark.ml.regression import RandomForestRegressor

rf = RandomForestRegressor(featuresCol='features', labelCol='price')
rf_model = rf.fit(train)

train_predictions = rf_model.transform(train)
test_predictions = rf_model.transform(test)

最后,你可以使用 MLlib 的RegressionEvaluator来评估模型。根据你的使用案例,有多种评估指标可以选择:

  • RMSE — 均方根误差(默认)

  • MSE — 均方误差

  • R2 — 决定系数

  • MAE — 平均绝对误差

  • Var — 解释的方差

from pyspark.ml.evaluation import RegressionEvaluator

evaluator = RegressionEvaluator(predictionCol="prediction", \
                 labelCol="price", metricName="r2")

print("Train R2:", evaluator.evaluate(train_predictions))
print("Test R2:", evaluator.evaluate(test_predictions))

训练 R2: 0.9092 | 测试 R2: 0.9069

分析特征重要性

在构建随机森林时,算法尝试最小化,这是衡量不确定性的指标。熵在均匀分布中最大化(即我们不知道硬币是正面还是反面),而更多的不确定性需要更多的信息。随机森林算法使用信息增益(IG),其等于分裂前的熵减去分裂后的熵,加权实例数量。这帮助算法决定应该使用哪个特征来分裂数据。

你可能需要关注的是哪些特征在你的模型中最相关。随机森林算法内置特征重要性,可以以不同方式计算。PySpark 随机森林遵循使用基尼重要性(或平均减少的不纯度)的 scikit-learn 实现。Scikit-learn 还提供了基于置换的特征重要性实现,但这在 PySpark 中没有内置。

文档中所述,特征重要性是通过以下方式计算的:

  • 重要性(特征 j)= 对于在特征 j 上进行分裂的节点,增益的总和,其中增益由通过该节点的实例数量进行缩放

  • 将特征重要性归一化,使其总和为 1。

我们可以使用rf_model.featureImportances从拟合的随机森林模型中提取特征重要性。然后,使用这些特征重要性并将其与提取的特征名称匹配,以便进行查看或绘图。这种视图对希望了解模型关键驱动因素的主要利益相关者特别重要。

def extract_feature_imp(feature_imp, dataset, features_col):
    list_extract = []
    for i in dataset.schema[features_col].metadata["ml_attr"]["attrs"]:
        list_extract = list_extract + dataset.schema[features_col].metadata["ml_attr"]["attrs"][i]
    feature_list = pd.DataFrame(list_extract)
    feature_list['score'] = feature_list['idx'].apply(lambda x: feature_imp[x])
    return(feature_list.sort_values('score', ascending = False))
import pandas as pd
feature_list = extract_feature_imp(rf_model.featureImportances, train, "features")
top_20_features = feature_list.sort_values('score', ascending = False).head(20)# Then make your desired plot function to visualize feature importance
plot_feature_importance(top_20_features['score'], top_20_features['name'])

作者拍摄的照片

总结

在本教程中,我们介绍了使用 PySpark 实现随机森林的以下步骤。

  1. 使用 StringIndexer 和 OneHotEncoder 对类别特征进行独热编码

  2. 使用 VectorAssembler 创建输入特征向量列

  3. 将数据拆分为训练集和测试集

  4. 在训练数据上初始化并拟合 RandomForestRegressor 模型

  5. 在测试数据上转换模型以进行预测

  6. 使用 RegressionEvaluator 评估模型

  7. 分析特征重要性以理解和改进模型

这些步骤允许我们将数据准备成向量化输入,从而可以在 PySpark 中使用 Apache Spark 的可扩展机器学习库 MLlib 实现随机森林模型。我们可以评估模型性能,并绘制和分析随机森林特征重要性,以更好地理解我们的模型。这使我们能够利用 PySpark 提供的可扩展性、速度和多功能性。

如何通过提示工程改进 ChatGPT 生成的代码

原文:towardsdatascience.com/how-to-improve-chatgpts-generated-code-with-prompt-engineering-cd131f9ab55d

提升 ChatGPT 作为编码助手表现的简单策略

Aashish NairTowards Data Science Aashish Nair

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

--

摄影师 cottonbro studio 提供的照片: www.pexels.com/photo/hands-typing-on-a-laptop-keyboard-5483077/

介绍

虽然 ChatGPT 拥有无数功能,但有一个功能最受程序员青睐:生成代码的能力。

ChatGPT 已证明能够在几秒钟内生成有效代码。因此,它改变了程序员处理编码项目的方式,许多人通过使用聊天机器人来处理乏味或繁琐的部分,从而节省了时间和精力。

然而,ChatGPT 生成无效代码的记录并不罕见。实际上,StackOverflow 曾对 ChatGPT 和其他生成 AI 产生的内容施加了临时禁令,主要原因是它们的正确答案率较低。

尽管如此,这些结果往往与底层的大型语言模型(LLM)的质量关系不大,更与用户如何与聊天机器人互动有关。

一旦人们学会利用提示工程这一近年来备受关注的主题,他们将从聊天机器人中获得更多好处。

在这里,我们简要概述了为什么编写良好的提示对 ChatGPT 如此重要,以及数据科学家如何利用提示工程从这个聊天机器人中获得最大价值。

为什么提示很重要

ChatGPT 生成的代码由于各种原因被排斥,其中一些是次优的,另一些则完全与问题无关。然而,无论问题是什么,根本原因都是相同的:提示不足。

像 GPT-3.5 和 GPT-4.0 这样的模型非常强大,但它们对用户提供的提示非常敏感。由 GPT-3.5 驱动的 ChatGPT 只有在给定提供了足够上下文和适当语言的提示时才能成功操作。

幸运的是,即使你的提示返回了好的响应,你也可以通过利用提示工程看到更好的结果。

提示工程

提示工程是一种技术,涉及定制提示以获得聊天机器人期望的响应。该过程不仅涉及确定在用户提示中提供的理想上下文类型,还包括上下文应使用的理想语言和句子结构。

提示工程属于“工程”范畴,因为它是一种系统的方法来设计、测试和评估提示,通常是一个迭代的过程。

提示工程概述(由作者创建)

开发提示的过程应理想地进行文档记录。文档将帮助洞察哪些提示方面有助于高质量代码生成。此外,记录成功的提示将节省你每次需要使用 ChatGPT 解决类似问题时重复这一过程的麻烦。

数据科学家的提示工程

现在应该很清楚为什么提示工程在数据科学中如此相关。数据科学家在使用 ChatGPT 时可以享受生产力的激增,但只有在输入足够的提示时才行。

毕竟,在数据科学项目中,上下文至关重要。

项目中的预定义元素(例如目标、评估指标、限制/约束等)决定了在特定脚本中应使用哪些工具和技术。

如果没有一个能够充分捕捉相关要求的良好提示,聊天机器人更可能生成既无效又低效的代码。

因此,成功使用 ChatGPT 进行数据科学工作取决于数据科学家使用提示工程来设计产生令人满意代码的提示的能力。

案例研究

为了展示精心设计的提示的重要性,假设我们正在构建一个 XGBoost 模型,并希望创建一个执行超参数调优的 Python 脚本。训练数据既大又不平衡。

我们将测试两个不同的提示,旨在生成有助于超参数调优的代码。第一个提示提供了最基本的上下文,而第二个提示提供了一些额外的指示。

提示 1:编写执行 XGBoost 分类器超参数调优的 Python 代码。

这是 ChatGPT 生成的代码:

import numpy as np
import pandas as pd
from xgboost import XGBClassifier
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.metrics import accuracy_score

# Load your dataset here; X should contain the features, and y should contain the target variable

# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Define the XGBoost classifier
xgb_model = XGBClassifier()

# Define the hyperparameters and their respective search space
param_grid = {
    'learning_rate': [0.1, 0.01, 0.001],
    'n_estimators': [100, 200, 300],
    'max_depth': [3, 5, 7],
    'min_child_weight': [1, 3, 5]
}

# Create the GridSearchCV object
grid_search = GridSearchCV(estimator=xgb_model, param_grid=param_grid, scoring='accuracy', cv=5)

# Perform hyperparameter tuning on the training data
grid_search.fit(X_train, y_train)

# Get the best hyperparameters and the corresponding model
best_params = grid_search.best_params_
best_model = grid_search.best_estimator_

# Make predictions on the test set using the best model
y_pred = best_model.predict(X_test)

# Evaluate the model performance
accuracy = accuracy_score(y_test, y_pred)
print("Best Hyperparameters:", best_params)
print("Test Accuracy:", accuracy)

第一个提示的输出是一个功能代码,可以对给定的 XGBoost 模型进行超参数调优。

然而,注意到提示本身在上下文中是有限的,并未指定某些因素,例如库、超参数调优方法、需要调整的超参数或评估指标。提示中的任何信息缺口都留给 ChatGPT 来填补。在这里,我们看到 ChatGPT 的代码使用了 Scikit Learn 模块进行 网格搜索,并使用 7 个超参数,以 准确率 作为评估指标。

这是一个有效的解决方案,但不适用于大规模、不平衡的数据。网格搜索是一种效率低下且耗时的技术,而在处理不平衡数据时,准确率指标极为不可靠。

总体来说,这证明了一个不足的提示会生成次优的代码。

这次,让我们在提示中添加更多的指令,看看返回什么类型的响应。

提示 2:编写执行 XGBoost 分类器超参数调优的 Python 代码。使用 Optuna 库进行 50 次试验,以 f-1 分数作为评估指标。仅考虑以下超参数:n_estimators、learning_rate、gamma 和 max_depth。

该提示生成了以下代码输出:

import numpy as np
import pandas as pd
import optuna
import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score

# Define the objective function for Optuna optimization
def objective(trial):
    param = {
        'n_estimators': trial.suggest_int('n_estimators', 100, 500, step=100),
        'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.1, log=True),
        'gamma': trial.suggest_float('gamma', 0.0, 1.0),
        'max_depth': trial.suggest_int('max_depth', 3, 10)
    }

    # Create an XGBoost classifier with the current hyperparameters
    xgb_model = xgb.XGBClassifier(**param)

    # Train and evaluate the classifier using cross-validation
    f1_scores = []
    for train_index, val_index in skf.split(X_train, y_train):
        X_train_fold, X_val_fold = X_train[train_index], X_train[val_index]
        y_train_fold, y_val_fold = y_train[train_index], y_train[val_index]

        xgb_model.fit(X_train_fold, y_train_fold)
        y_pred_fold = xgb_model.predict(X_val_fold)
        f1_scores.append(f1_score(y_val_fold, y_pred_fold))

    return np.mean(f1_scores)

# Load your dataset here; X should contain the features, and y should contain the target variable

# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Create a StratifiedKFold object for cross-validation
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

# Create an Optuna study and optimize the objective function
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=50)

# Get the best hyperparameters and the corresponding model
best_params = study.best_params
best_model = xgb.XGBClassifier(**best_params)
best_model.fit(X_train, y_train)

# Make predictions on the test set using the best model
y_pred = best_model.predict(X_test)

# Evaluate the model performance on the test set
f1_test = f1_score(y_test, y_pred)

print("Best Hyperparameters:", best_params)
print("Test F1 Score:", f1_test)

这个提示减少了更多的解释空间,明确了库、需要调整的超参数、试验次数和评估指标。第二个提示比第一个提示多出仅 2 句话,但却能够返回更为合适的代码!

在这里,我们看到 ChatGPT 的代码使用了 Optuna 模块进行 贝叶斯搜索,并使用 4 个超参数,以 f1-score 作为评估指标。这个解决方案比之前的方案更省时省力。

在检查代码之后,我们可以决定脚本是否符合所有标准,或是否未能满足某些要求。

如果生成的代码在某些方面有所欠缺,可以相应地修改提示并再次测试。如果生成的代码是理想解决方案,则应记录提示。这样,每当需要在不同项目中执行类似操作时,可以将该提示作为参考。

提示:浏览网络寻找公开可用的提示

Campaign Creators 的照片,来源于 Unsplash

你是否不愿意花时间编写能够让 ChatGPT 生成有用的模板代码的提示?这是可以理解的。

如果是这种情况,考虑首先浏览网络寻找经过验证的提示。毕竟,如果别人已经为你完成了工作,就没有必要从头开始开发提示。

有无数的文章、社交媒体帖子和公共论坛,人们分享他们与 ChatGPT 互动的发现。

关键要点

图片来源:Prateek KatyalUnsplash

随着我们在日常工作中逐渐使用像 ChatGPT 这样的聊天机器人,数据科学家的职责显然已从自己编写代码转变为与由 LLM 驱动的聊天机器人互动。

聊天机器人生成的代码质量与输入给它们的提示质量成正比,因此提示工程将逐渐成为数据科学领域的一个必备技能。因此,现在是熟悉为项目设计和开发有效提示的过程的好时机。

感谢阅读!

如何利用贝叶斯高斯混合模型提高聚类准确性

原文:towardsdatascience.com/how-to-improve-clustering-accuracy-with-bayesian-gaussian-mixture-models-2ef8bb2d603f

聚类

一种用于现实世界数据的更高级的聚类技术

Mike ClaytonTowards Data Science Mike Clayton

·发布在 数据科学前沿 ·阅读时长 27 分钟·2023 年 2 月 15 日

--

图片由 Tima Miroshnichenko 提供,来自 Pexels

在现实世界中,你常常会发现数据遵循某种概率分布。无论是高斯(或正态)分布、韦布尔分布、泊松分布、指数分布等,取决于具体的数据。

了解哪个分布描述了你的数据,或者哪个分布可能最能描述你的数据,可以让你利用这一点,从而改善你的推断和/或预测。

本文将探讨如何利用数据集的潜在概率分布来改善标准 K-Means 聚类模型的拟合效果,甚至允许从数据中直接自动选择合适的簇数。

介绍

许多引人注目的机器学习/深度学习技术往往涉及有监督的机器学习/深度学习,即数据已经被标记,模型从中学习正确的答案。然后,将训练好的模型应用于未来的数据以进行预测。

这些都非常有用,但现实是数据是由全球的企业和个人不断产生的,其中大多数数据是未标记的。在绝大多数情况下,标记数据实际上是非常昂贵和耗时的。这就是无监督学习派上用场的地方。

…数据是由全球的企业和个人不断产生的,其中大多数数据是未标记的。

找到从未标记数据中推断意义的最佳方法是许多企业非常重要的追求。这可以发现潜在的未知或不明显的趋势或分组。然后可以分配资源、针对特定客户群体,或只是进行额外的研究和开发。

此外,大多数情况下,数据涉及某种形式的人或自然过程。自然过程和人类行为通常由高斯分布很好地捕捉和描述。

鉴于此,本文将探讨如何利用高斯分布,包括高斯混合模型(GMM)和贝叶斯高斯混合模型(BGMM),来提高表示现实世界数据集中的“自然过程”的数据集的聚类准确性。

作为比较和判断的基础,将使用普遍的 K 均值聚类算法。

计划

照片由 Christina Morillo 提供,来源于 Pexels

本文将涵盖相当广泛的内容,并包含来自综合 Jupyter 笔记本的示例。

本节应为您提供一些关于涵盖内容的指导,并指出如果需要访问特定信息应跳过的位置。

  1. 最初,文章将涵盖关于一般使用高斯混合模型的“什么”和“为什么”问题。

  2. 由于 scikit-learn 库中有两个现成的高斯混合模型实现,接下来将讨论普通 高斯混合模型贝叶斯高斯混合模型 之间的主要区别。

  3. 文章将深入探讨如何使用高斯混合模型对现实世界的多特征数据集进行聚类。所有示例将使用 K 均值算法、普通高斯混合模型和贝叶斯高斯混合模型来实现。

  4. 随后将有两个额外的部分,主要集中于算法的更清晰可视化。数据的复杂性将通过 a) 使用两个组件的主成分分析(PCA)和 b) 仅分析数据集中的两个特征来减少。

注意: 选择的数据集实际上是有标签的。这是故意为之,以便将聚类的性能与 100%正确且已知的主要聚类进行比较。

解释

照片由 Polina Zimmerman 提供,来源于 Pexels

在进入数据并查看实际效果之前,我们先讨论“是什么”和“为什么”类型的问题。

在我们开始之前,值得注意的是,本文章不会讨论什么是高斯/正态分布,或者什么是 K-Means 聚类。市面上有很多很好的资源可以参考,本文章没有足够的空间来覆盖这些内容。因此,从这一点起,假设你对这些概念有基本了解。

注意: “高斯分布”和“正态分布”这两个短语指的是同一个东西,本文中将交替使用。

什么是高斯混合模型?

简而言之,该算法假设你提供的数据可以用未指定的不同高斯分布的混合来近似。

算法会尝试提取和分离这些高斯分布的混合,并将它们作为单独的簇返回给你。

就是这样。

所以本质上,它就像 K-Means 聚类,但它有一个额外的优点,即能够对数据应用额外的统计约束。这使得它能更灵活地捕捉簇的形状。此外,它允许更精确地分离紧密或稍微连接的簇,因为算法可以访问由假设的高斯分布生成的统计概率。

从视觉上看,如果分析局限于二维或三维,K-Means 算法会确定簇中心,并在这些中心周围应用“圆形”或“球形”分布。

然而,如果底层数据是高斯的,那么由于高斯分布的尾部,分布被拉伸到某种程度是完全可能的,甚至是预期的。这在形状上相当于一个“椭圆”或“椭球体”。这些拉伸的“椭圆”或“椭球体”形状是 K-Means 无法准确建模的,但高斯混合模型可以。

另一方面,如果你向高斯混合模型提供的数据明显远离高斯分布,算法仍会假设它是高斯的。因此,你可能最终得到的,充其量,也不比 K-Means 更好。

附注: 高斯混合模型和贝叶斯高斯混合模型可以使用 K-Means 聚类算法来生成模型的一些初始参数(实际上,这是 scikit-learn 中的默认设置)。

这就引出了一个问题……

高斯混合模型可以用于什么类型的数据?

自然过程通常是一个很好的起点。原因主要是由于中心极限定理(CLT):

在概率论中,中心极限定理CLT)确定了在许多情况下,当独立随机变量相加时,它们经过适当标准化的和趋向于正态分布,即使原始变量本身不是正态分布。

-wikipedia.org

简单来说,这本质上意味着,在自然过程中,虽然特定变量(例如人的身高)是由许多可能是或不是正态分布的因素(饮食、生活方式、环境、基因等)引起的,但这些部分的标准化总和(人的身高)将会是(大致上)正态分布的。这就是为什么我们经常看到自然过程呈现正态分布的原因。

图片由 Ivan Samkov 提供,来自 Pexels

因此,现实中有大量需要处理高斯分布的实例,这使得高斯混合模型在适当的场景下非常有用:

  • 客户行为 —— 这可以指购买行为、消费金额、注意力跨度、流失率等。

  • 人体特征 —— 身高、体重、鞋码、智商(或教育表现)等。

  • 自然现象 —— 在医学领域(癌症、疾病、基因等)或其他科学领域识别模式/群体

显然,还有许多其他例子,以及其他原因导致高斯分布的情况。

当然,也可能有些情况下你无法辨别数据的潜在结构,这自然需要进一步调查,但这也是领域专家如此重要的原因之一。

为什么使用高斯混合模型?

主要原因是,如果你比较确定你的数据是高斯分布(或更准确地说,是高斯数据的混合),那么你将能更准确地分离出真正的聚类,从而大大提高准确性。

算法不仅寻找基本聚类,还考虑每个聚类最合适的形状或分布。这使得紧密分布或稍微连在一起的聚类能够更准确和轻松地分离出来。

此外,在分离不清晰的情况下(或可能只是为了更深入的分析),可以生成并分析每个数据点属于每个聚类的概率。这让你更好地了解哪些是核心可靠的数据点,哪些可能是边缘的或不清楚的数据点。

使用贝叶斯高斯混合模型,算法也可以从数据中推断出最合适的聚类数量。这比依赖于 肘部法则 或生成 BIC / AIC 曲线要好。

高斯混合模型是如何工作的?

基本上,这个方法使用迭代更新过程来逐渐优化多个高斯分布与数据的拟合。我想这与梯度下降有些类似。

检查拟合——调整——再次检查拟合——调整……并重复,直到收敛。

在这种情况下,算法被称为期望最大化(EM)算法。更具体地说,情况是这样的:

  1. 模型的一个输入参数是簇的数量,所以这是一个已知的量。例如,如果设置了两个簇,那么初始的两个高斯分布将有其参数被分配。参数可以通过 K-Means 分析(scikit-learn 的默认设置)来分配,或者随机分配。如果有非常具体的情况,参数甚至可以为每个数据点指定。现在进入迭代部分……

  2. 期望——现在有两个高斯分布定义了特定的参数。算法首先将每个数据点分配给两个高斯分布中的一个。它是根据数据点适合特定分布的概率来进行分配的,相对于另一个分布。

  3. 最大化——一旦所有点都被分配,每个高斯分布的参数会稍作调整,以更好地拟合整个数据,基于从上一步生成的信息。

  4. 重复步骤 2 和 3,直到收敛。

上述内容过于简化,我没有详细说明每个时刻优化的确切数学机制(或方程)。然而,它至少应能给你对算法操作的概念理解。

一如既往,网上有许多数学较重的文章更详细地解释了确切机制,如果你感兴趣的话。

普通高斯混合模型和贝叶斯高斯混合模型之间有什么区别?

我要说的是,解释贝叶斯高斯混合模型相较于标准高斯混合模型所使用的额外过程实际上相当复杂。它需要同时理解一些不同且复杂的数学概念,而本文的篇幅显然不足以充分说明。

我将旨在引导你找到正确的方向,并概述优缺点。你还将随着文章的深入,在数据集分析过程中收集更多信息。

实际差异

突出的区别在于,标准高斯混合模型使用期望最大化(EM)算法,而贝叶斯高斯混合模型使用变分推断(VI)。

不幸的是,变分推断在数学上并不直观,但如果你想深入了解,我建议阅读 Jonathan Hui这篇优秀文章

主要的结论有这些:

  1. 变分推断是期望最大化算法的一个扩展。两者都旨在在数据中找到高斯分布(至少在这个实例中)。

  2. 贝叶斯高斯混合模型需要提供更多的输入参数,这可能更加复杂/繁琐。

  3. 变分推断本质上内置了一种正则化形式。

  4. 变分推断不太可能产生“不稳定”或“边缘”解决方案。这使得算法更有可能趋向于一个有坚实支持的“真实”解决方案。或者正如 scikit-learn 的文档 所述,"由于纳入了先验信息,变分解决方案比期望最大化解决方案具有更少的病态特殊情况。"

  5. 贝叶斯高斯混合模型可以直接估计输入数据的最合适的聚类数量(无需肘部方法!)

…所以总结一下:

变分推断是期望最大化的更高级的扩展。理论上,它应该更准确,并且对杂乱数据或异常值更具抵抗力。

资源与进一步阅读

首先,我建议你查看 scikit-learn 文献中提供的优秀概述:

[## 2.1. 高斯混合模型

sklearn.mixture 是一个包,可以用来学习高斯混合模型(对角线、球形、绑定和完整…)

scikit-learn.org

进一步阅读时,可以查阅的一些相关主题包括:

  1. 期望最大化(EM)

  2. 变分推断(VI)

  3. 狄利克雷分布和狄利克雷过程

现在来看一些真实数据

图片由 Couple of Cup 提供,来自 Pexels

讨论和理论很好,但我经常发现探索真实实现可以澄清很多。因此,以下部分将比较以下聚类方法在真实世界数据集上的表现:

  1. K 均值(基准)

  2. 高斯混合模型

  3. 贝叶斯高斯混合模型

数据

将要使用的真实世界数据集¹ 描述了来自意大利同一地区的三种不同葡萄酒的化学成分。

这个数据集已经标注,因此尽管以下所有的聚类分析不会在分析中使用这些标签,但它将允许与已知的正确答案进行比较。因此,这三种聚类方法可以公平且无偏差地进行比较。

此外,数据集符合“自然”数据集的标准,这应该适合于高斯方法,这是预期的测试目标。

为了方便加载数据,我已将原始数据以 CSV 格式在我的 GitHub 仓库中提供:

[## notebooks/datasets/wine at main · thetestspecimen/notebooks

这些数据是对来自意大利同一地区的葡萄酒的化学分析结果,但来源于三...

github.com](https://github.com/thetestspecimen/notebooks/tree/main/datasets/wine?source=post_page-----2ef8bb2d603f--------------------------------)

参考笔记本

所有后续的分析都已在一个全面的 Jupyter notebook 中提供。

原始笔记本可以在这里找到,适用于你的本地环境:

[## notebooks/bayesian_gaussian_mixture_model.ipynb at main · thetestspecimen/notebooks

目前无法执行该操作。你在另一个标签页或窗口中登录了。你在另一个标签页或窗口中已登出...

github.com](https://github.com/thetestspecimen/notebooks/blob/main/bayesian_gaussian_mixture_model.ipynb?source=post_page-----2ef8bb2d603f--------------------------------)

…或者如果你需要一个在线解决方案,可以在 Deepnote 或 Colab 中开始:

笔记本中使用的一些函数需要特定库的版本较新(特别是 scikit-learn 和 matplotlib),因此接下来的部分将描述所需的内容。

环境设置 — 本地或 Deepnote

无论是使用本地环境还是 Deepnote,只需确保可用的 scikit-learn 和 matplotlib 的版本正确即可。实现这一点最简单的方法是将其添加到你的“requirements.txt”文件中。

对于 Deepnote,你可以在右侧窗格的文件部分创建一个名为“requirements.txt”的文件,并添加以下内容:

scikit-learn==1.2.0
matplotlib==3.6.3

(更新版本也可以)。

环境设置 — Colab

由于在 Colab 中没有类似“requirements.txt”文件的访问权限,你需要显式安装正确版本的 scikit-learn 和 matplotlib。为此,在空白单元格中运行以下代码以安装适当版本:

!pip install scikit-learn==1.2.0
!pip install matplotlib==3.6.3

(更新版本也可以)。

然后在尝试运行任何代码之前刷新网页,以确保库正确加载。

数据探索

在进行实际聚类之前,可能值得对数据进行粗略概览。

每个特征有 13 个特征和 178 个示例(没有缺失或空数据):

…因此这是一个不错的干净数字数据集,可以开始使用。

唯一真正需要更改的是刻度。每个特征中的数字范围差异较大,因此将应用简单的MinMaxScaler将特征缩放到 0 和 1 之间。

数据分布

如前所述,理想情况下数据应该至少大致呈高斯分布,以便高斯混合模型有效工作。那么它的表现如何?

特征数据分布 — 图片由作者提供

现在其中一些看起来相当高斯(例如,灰分的碱度),但实际上大多数并不是。这是问题吗?不一定,因为在许多情况下,实际上存在高斯分布混合(或近似高斯分布)。

高斯混合模型的关键在于它可以从多个高斯分布的混合中找到并分离出各个高斯分布。

在真实的聚类问题中,你无法实现下一步,因为你不会知道数据的真实聚类。然而,仅为说明目的,实际上是可以(因为我们知道标签)绘制每个“真实”簇的分布:

按标签划分的特征数据分布 — 图片由作者提供

如今你可以看到,真实的簇在大多数情况下大致呈高斯分布。虽然不是所有情况都是如此,但在真实数据中这种情况是不可避免的。由于数据基于“自然”数据,我们确实在处理大致高斯分布的数据。

对原始数据分布的这一基本调查说明了了解所处理数据类型的重要性,以及哪个工具最适合分析。这也是领域专家重要性的一个很好的案例。

特征关系

最后,展示了一些变量之间如何分布的例子(如果你想要更全面的图示,请查看 Jupyter 笔记本):

组件之间的数据分布示例 — 图片由作者提供

从上面的散点图可以看出,一些特征显示出合理的分离。然而,也有许多特征之间存在混合,尤其是在每个簇的边缘。形状(圆形、长条形等)也变化很大。

分析

图片由 Artem Podrez 提供,来自 Pexels

如前所述,将比较三种不同的算法:

  1. K-Means(基准)

  2. 高斯混合模型

  3. 贝叶斯高斯混合模型

探索过程将分为三个阶段:

  1. 所有原始数据一起分析

  2. 在通过主成分分析(PCA)降低复杂性/特征后分析所有数据

  3. 使用仅两个特征进行的分析(主要为了比使用完整数据集更容易进行说明)

分析 1 — 完整数据集

为了保持比较的一致性,并减少本文的复杂性,我将跳过对正确组件数量的深入调查。

但是,为了完整性,你应该知道,进行任何聚类时的关键步骤是了解使用适当数量的簇。一些实现这一点的常见方法包括 肘部法则贝叶斯信息准则 (BIC)赤池信息量准则。以下是整个原始数据集的 BIC 和 AIC 示例:

该数据集的 BIC 和 AIC 曲线 — 图片由作者提供

BIC 结果建议两个组件是合适的,但我现在不打算进一步讨论这一结果。然而,这将在本文后续讨论中出现。

从现在开始,将假设三个簇,以保持一致性和便于比较。

让我们开始吧!

K-Means

K-Means 混淆矩阵 — 图片由作者提供

嗯,这确实是一个相当令人印象深刻的结果。

将所有数据一起考虑时,簇 1 和簇 3 之间显然有很好的分离。然而,由于簇 2 通常位于簇 1 和簇 3 之间(参考前一部分的四个示例散点图),因此簇 2 边缘的点可能被错误地分配到簇 1 和簇 3(即这些簇之间的边界可能定义不准确)。

让我们看看使用高斯混合模型是否有所改善。

高斯混合模型

高斯混合模型混淆矩阵 — 图片由作者提供

有所改进。高斯混合模型成功地识别出三个额外的点,并将它们正确地分配到簇 2。

虽然这看起来不是什么大问题,但值得注意的是数据集相当小,使用更全面的数据集可能会得到更令人印象深刻的结果。

贝叶斯高斯混合模型

在产生结果之前,鉴于这是文章的主要焦点,我认为值得花一点时间来解释一些可以指定的相关参数:

  1. n_components — 这是你希望算法考虑的簇数。然而,算法可能返回或更倾向于少于这里设置的簇数,这也是该算法的主要优势之一(我们很快会看到这一点)。

  2. covariance_type — 这里有四个选项:fulltieddiagspherical。最‘准确’且通常首选的是 full。这个参数本质上决定了分布拟合形状的限制,这里提供了一个很好的说明。

  3. weight_concentration_prior_type — 这可以是 dirichlet_process(无限混合模型)或 dirichlet_distribution(有限混合模型)。一般来说,选择 Dirichlet 过程更好,因为它对参数变化不那么敏感,并且不容易将自然簇划分为不必要的子组件,而 Dirichlet 分布有时可能会这样做。

  4. weight_concentration_prior — 指定一个低值(例如 0.01)将使模型将更多的组件设置为零,只留下几个具有显著值的组件。高值(例如 100000)则倾向于保留更多的活动组件并具有相关值,即较少的组件被设置为零。

由于额外参数较多,在某些情况下进行交叉验证分析可能是明智的。例如,在这次初步运行中,covariance_type 将设置为‘diag’而非‘full’,因为建议的簇数更具说服力。我怀疑在这种特定情况下,这可能是由于数据集较小以及特征数量较多,导致‘full’协方差类型出现过拟合。

现在可以审查算法在相关簇方面所做的决定。这可以通过从拟合模型中提取权重来实现。

更清晰地以图形形式展示:

贝叶斯高斯混合模型组权重 — 图片由作者提供

老实说,这并不是很有说服力。

很明显,前面有三个簇领先于其他簇,但这只是因为答案已知才直观。事实上,这并不是很清楚。那么为什么会这样呢?

主要原因可能是数据缺乏和特征数量过多(至少相比于数据量)的结合。

数据不足会导致像异常值这样的项对整体分布产生更大的影响,同时也减少了模型生成分布的能力。

在接下来的部分,我们将探讨可能的解决方案,并分析更小的特征选择,以查看在这些情况下的结果。

目前,既然我们知道正确的组件数量是三个,我们将继续前进。

贝叶斯高斯混合模型混淆矩阵 — 作者提供的图片

几乎完美的聚类。仅有两个点仍被错误分配。

让我们快速查看一下总体比较。

不同聚类方法的准确性比较 — 作者提供的图片

讨论

从准确率结果可以看出,从一种方法到另一种方法有逐渐改进。这表明理解数据的基本结构很重要,因为它可以更准确地表示原始数据中的模式,在我们这里是高斯分布。

即使在高斯混合模型的范围内,也可以清楚地看到(至少在这种情况下),贝叶斯高斯混合模型中使用变分推断可以比期望最大化方法获得更准确的结果。

所有这些都是预期中的,但看到这些结果仍然很有趣。

可视化

为了概述高斯算法究竟在做什么,我将特征“酒精”与其他每个特征进行了对比,并在每个图上为每个三簇包含了贝叶斯高斯混合模型的置信椭圆。

注意: 绘制置信椭圆所使用的算法改编自 这个算法 ,该算法在 scikit-learn 文档中提供。

尽管这很有趣,且很好地展示了算法如何处理不同形状的簇(即圆形或更长的椭圆形),但它并没有提供任何关于可能影响结果的因素的理解。

这主要是因为在将输出表示为可解释的内容时,需要处理的维度(即特征)过多。

更好的可视化和进一步的调查

将各种方法生成的分布以清晰简洁的方式进行可视化和比较,将是很有趣的,这样可以准确地看到背后发生了什么。

然而,由于模型正在处理大量特征(因此维度也很多),无法用简单的 2D 图形来表示正在发生的情况,正如前一节所示。

鉴于此,本文接下来的两个部分的目标是通过减少维度来简化分析。因此,接下来的两个部分将关注:

  1. 使用两个组件的主成分分析(PCA)。这将允许将所有十三个特征提炼为两个特征,同时保留数据中嵌入的大部分重要信息。

  2. 专门仅使用两个特征进行分析。

这两项调查将允许对这些簇进行直接可视化,因为只有两个组件,因此可以将它们绘制在标准的 2D 图上。

此外,这些方法将可能使得贝叶斯高斯混合模型的自动簇选择更加有效,因为特征数量与示例数量的关系更加平衡。

分析 2 — 主成分分析(PCA)

通过运行两个组件的主成分分析(PCA),13 个特征可以压缩为 2 个组件。

本节的主要目标如下:

  1. 从 PCA 降低复杂性所提供的数据分布中获取见解。

  2. 审查由贝叶斯高斯混合模型从 PCA 数据集中生成的自动组件选择。

  3. 在两个 PCA 组件上运行贝叶斯高斯混合模型,并以 2D 图形形式审查聚类结果。

PCA 的结果

PCA 对所有数据的两个组件及其分布(颜色为实际标签簇)— 图片由作者提供

PCA 的结果因多种原因而有趣。

首先,主要 PCA 组件(以及在某种程度上次要 PCA 组件)的分布与高斯分布非常接近,所有三个组件均如此。这与我们在文章早期数据调查中发现的情况相吻合。

记住 PCA 是所有特征的提炼,看到三个真实簇之间有很好的分离是有趣的。这意味着有很大的可能性,一个针对数据良好的聚类算法(不管 PCA 如何)有潜力实现簇的良好和准确分离。

然而,这些簇距离足够近,如果聚类算法不能正确表示簇的分布或形状,则不能保证簇之间的边界会被准确找到。

例如,簇 1 的分布沿 y 轴明显拉长。任何算法都需要模仿这种拉长的形状,以正确表示该簇。

自动簇

在初步分析中,自动估计正确的簇数有些模糊。现在数据复杂性已降低,我们来看看是否有所改善。

再次,输入请求 8 个组件:

8 个请求簇的权重 — 作者提供的图像

…我们到此为止。非常清楚的表明,尽管模型设置为考虑多达 8 个簇,算法显然认为实际合适的簇数是 3。这一点我们当然知道是正确的。

这在一定程度上表明,为了让贝叶斯高斯混合模型有效工作,在自动簇选择方面,有必要考虑数据集是否足够大,以生成合理和明确的结果,如果簇的数量尚未确定。

结果

为了完整性,让我们看看 PCA 的贝叶斯高斯混合模型的聚类结果如何。

贝叶斯高斯混合模型 (PCA) 混淆矩阵 — 作者提供的图像

与使用原始数据相比,准确度明显下降。这是可以预期的。通过运行 PCA,我们确实会丢失数据,这一点无法避免,在这种情况下,足以引入额外的 5 个错误分配的数据点。

让我们从视觉上更详细地看一下。

贝叶斯高斯混合的两个成分 PCA(错误匹配的点被红色圈出) — 原始分析中的错误匹配点被蓝色圈出 — 作者提供的图像

上面的图表信息量很大,所以让我们逐步解析:

  1. 彩色点表示的是 PCA 数据被算法分配到的簇。

  2. 大的阴影椭圆是置信椭球体,它们基本上表示了由算法生成的底层分布的形状(协方差)。

  3. 红色圆圈表示的是在 PCA 分析中,由贝叶斯高斯混合模型错误分配的数据点。

  4. 蓝色圆圈表示的是在原始分析中,由贝叶斯高斯混合模型错误分配的数据点,这些原始分析使用了所有的原始数据。

特别有趣的是,通过运行 PCA 分析,并且可能由于数据固有的损失,它迫使一些数据点被移动到生成的簇/置信椭球体内部(查看带有文本标注的橙色点和在绿色置信椭球体内被蓝色圆圈圈出的点)。

对于被蓝色圆圈标出的绿色点来说,它有所帮助,相比原始的“所有原始数据”分析有所改进。然而,橙色点明显被错误分配,它永远不会被正确分配,因为它过于嵌入在橙色簇中,而实际上它应该在绿色簇中。然而,原始分析正确地将此数据点分配到绿色簇中。

PCA 摘要

在这种特定情况下,使用较小的数据集运行 PCA 有助于贝叶斯 Gaussian 混合模型确定适当的簇数量。即使作为帮助更好地可视化数据集的分布,这也是非常有用的。

然而,使用 PCA 生成的数据作为分析的最终输入并不是最佳解决方案。数据的偏移/丢失足以使某些数据点永久性地错误分配,并且整体准确性也降低。

分析 3—较少的特征

为了更仔细地查看三种聚类算法之间的差异,将提取两个特定特征,并仅在这些特征上运行聚类算法。

从审查方法的角度来看,这种优势在于可以在二维平面上(即普通的二维图表)可视化发生了什么。

附加的好处是,现有的数据比整个数据集包含的信息要少得多。这不仅减少了样本数量少与特征数量之间的差异,还迫使聚类算法在减少信息的情况下更努力地工作以实现适当的拟合。

更详细地查看数据

稀释酒的颜色强度与 OD280/OD315(原始数据带标签)— 作者提供的图像

选择这一对(颜色强度和稀释酒的 OD280/OD315)的原因是数据集对不同的聚类算法提出了挑战。

如你所见,有两个相当交织的簇(1 和 2)。此外,与更加圆形的簇 1 相比,簇 2 和簇 3 的分布较为拉长。

理论上,Gaussian 混合方法应该比 K 均值表现得更好,因为它们能够准确地调整其分布特征以适应拉长的分布,而 K 均值则不能,因为它仅限于圆形表示。

此外,从图表两侧的 KDE 图中可以看到,数据分布是相当接近高斯分布的,正如我们在本文中之前确认过的那样。

K 均值—结果

K 均值的混淆矩阵—减少特征—作者提供的图像

Gaussian 混合模型—结果

Gaussian 混合模型的混淆矩阵—减少特征—作者提供的图像

贝叶斯 Gaussian 混合模型—组件选择

在直接进入结果之前,由于贝叶斯 Gaussian 混合模型有能力自动选择适当的组件数量,我们将再次‘请求’8 个组件,并查看模型的建议。

仅使用两个特征的贝叶斯 Gaussian 混合模型的组件选择—作者提供的图像

正如之前在降维 PCA 分析中看到的那样,模型发现区分已知存在的 3 个簇要容易得多。

这可以相当明确地确认:

  1. 必须拥有足够的数据样本,以确保模型能够有一定的机会提供正确的簇数建议。这可能是由于需要足够的数据来正确表示底层分布。在这种情况下是高斯分布。

  2. 对抗样本不足的一种潜在方法是以某种方式简化或概括数据,以尝试提取适当数量的簇,然后再回到完整数据集进行最终的全面聚类分析。

贝叶斯高斯混合模型 — 结果

贝叶斯高斯混合模型的混淆矩阵 — 降维特征 — 图片由作者提供

最终结果比较

每种聚类方法在特征颜色强度和 OD280/OD315 的稀释酒中的准确性 — 图片由作者提供

正如预期的那样,准确率远低于使用完整数据集时的准确率。然而,尽管如此,各种方法的准确率之间存在一些明显的差异。

让我们通过并排比较进一步探讨。

对三种聚类方法中每种簇的分配进行比较 — 簇 1(红色)/ 簇 2(橙色/绿色)/ 簇 3(灰色/蓝色) — 图片由作者提供

首先要注意的是,真实标签显示在某些情况下簇之间存在相当深的混合/交叉,因此 100%的准确率是不可能的。

以下讨论的参考:

  • 簇 1 — 红色

  • 簇 2 — 橙色/绿色

  • 簇 3 — 灰色/蓝色

K-means

K-means 在分离簇方面表现尚可,但由于分布必须最终限制为圆形,因此从未有准确捕捉簇 2 或簇 3 的希望。

然而,由于第 3 簇与其他两个簇有很好的分离,延长的形状成为的障碍较小。实际上,对下部簇的圆形表示就足够了,并且与高斯方法提供了类似的表示。

在考虑簇 1 和簇 2 时,K-Means 方法未能充分表示数据。它固有地无法正确表示簇 2 的椭圆形状。这导致簇 2 在簇 1 和簇 3 之间被“压缩”,因为 K-Means 算法无法充分描述真实的向上扩展。

高斯混合模型

基本的高斯混合模型在这种情况下只是略有改进。

正如上一节对 K 均值算法的讨论,即使集群 3 的分布更适合椭圆形而非圆形分布,在这个特定场景中也没有优势。

然而,当查看集群 1 和集群 2 的表示时,由于集群 2 的底层椭圆分布(即更好地捕捉高斯分布的尾部)能够更准确地表示,从而导致准确性略微提高。

贝叶斯高斯混合模型

首先,贝叶斯高斯混合模型相比其他方法在准确性上的提高超过了 10%,这作为一个单一统计数字确实令人印象深刻。

在审视置信椭球体时,变得清晰明了为何会如此。集群 2 在形状、倾斜度和大小方面几乎被完美表示。这使得实际集群的表示非常准确。

尽管确切原因总是略显模糊,但这无疑与标准高斯混合模型使用的期望最大化算法和贝叶斯高斯混合模型使用的变分推断之间的差异有关。

正如文章前面所讨论的,主要区别在于:

  • 内置正则化

  • 变分推断产生“边际正确”解决方案的倾向较小

结论

很明显,使用高斯混合模型可以帮助提升聚类的准确性,特别是对于那些可能以高斯分布为基础的数据。

这对于自然过程,包括人类过程尤其相关且有用,使得这种分析方法在大量行业中具有广泛的适用性。

此外,在贝叶斯高斯混合模型中引入变分推断,可以在几乎没有额外开销的情况下,进一步提高聚类的准确性。算法甚至有一个不容忽视的额外好处,即它能够建议底层数据的适当聚类数量。

我希望这篇文章能为你提供对高斯混合模型和贝叶斯高斯混合模型的良好理解,以及它们是否能帮助你处理的数据。

如果使用得当,它们确实是一种强大的工具。

如果你觉得这篇文章有趣或有用,记得关注我,或订阅我的新闻通讯,获取更多类似内容。

如果你还没有,你也可以考虑订阅 Medium。你的会员费用不仅直接支持我,还支持你阅读的其他作家。你还将获得对 Medium 上每个故事的全面无限制访问权限。

使用我的推荐链接注册将使我获得少量佣金,而不会对你的会员资格产生任何影响,所以如果你选择这样做,我会非常感谢。

[## 使用我的推荐链接加入 Medium - Mike Clayton

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

medium.com](https://medium.com/@maclayton/membership?source=post_page-----2ef8bb2d603f--------------------------------)

参考资料

[1] Riccardo Leardi, Wine (1991), UC Irvine 机器学习库, 许可协议: CC BY 4.0

如何改善低资源语言的翻译

原文:towardsdatascience.com/how-to-improve-translation-for-low-resource-languages-8b19dbe7eb6b

非洲语言的评估案例

Magdalena KonkiewiczTowards Data Science Magdalena Konkiewicz

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

--

图片由Gerd Altmann提供,来源于Pixabay

介绍

近年来,机器翻译系统质量的提升使得使用这些系统的解决方案和设备对普通消费者变得可用。许多应用程序提供自动翻译文本、语音或图像的功能,允许用户在不懂语言的情况下进行沟通或理解。

然而,并非所有语言都如此。事实上,占全球语言三分之一的非洲语言并未得到同样程度的进步。这是因为近年来为其提供的自然语言处理和机器翻译资源较少。

最新的全球机器翻译会议(WMT2022)举办了一项特别任务,由 Meta、Microsoft 和 Toloka 的研究人员准备,旨在解决这个问题。结果对 24 种不同的非洲语言进行了评估,这些语言由 14 种不同的机器翻译系统生成,使用了自动评分和人工评估。

本文总结了该项目的主要成就。

挑战

研究包括了下面图片中的 24 种不同的非洲语言,以及用于评估的英语和法语。这使得可以检查大约 100 种不同的语言方向。

参与该任务的 24 种非洲语言——作者提供的照片

数据集

非洲语言的公共资源非常有限,参与者被鼓励贡献涉及评估语言的新型单语、双语和多语言数据集。下面是六个新提交的数据集的信息。

  • LAVA 包含数百万个平行双语句子的语料库,涉及五种非洲语言(afr, kin, lug, nya, saw)。

  • MAFAND-MT — 包含几千个高质量、人工翻译的平行句子的语料库,涉及新闻领域中的 21 种非洲语言,覆盖了挑战中评估的 14 种语言(amh, hau, ibo, kin, lug, luo, nya, sna, swh, tsn, wol, xho, yor, zul)。

  • KenTrans — 包含 12400 个斯瓦希里语与其他两种肯尼亚语言(Dholuo 和 Luhya)之间翻译句子的语料库。

  • 来自 ParaCraw 的单语非洲语言 — 包含来自互联网档案和定向爬取的单语数据的语料库。覆盖了 22 种评估语言(afr, amh, fuv, hau, ibo, kam, lin, lug, luo, nso, nya, orm, sna, ssw, swh, tsn, tso, umb, wol, xho, yor, zul)。

  • SA Languages 使用来自南非政府网站的公开数据构建的语料库。覆盖了 4 种评估语言(nso, tsn, xho, zul)。

  • WebCrawlAfrican 包含近 700000 个句子的平行语料库,涉及不同的非洲语言和英语。覆盖了挑战中涉及的 15 种语言(afr, ling, ssw, amh, lug, tsn, nya, hau, orm, xho, ibo, tso, yor, swh, zul)。

系统和团队

参与挑战的翻译系统仅允许使用上述资源或其他两个著名的语料库进行训练。共有八个团队提交了十四个系统。

  • CAIR ANVITA — 团队提交了一个以英语为中心的多语言变换器模型,支持 24 种非洲语言。作者应用了几种启发式方法来过滤数据,并展示了使用较大变换器模型比使用较小版本表现更好。

  • 开普敦 — 他们的系统专注于八种南部和东南非洲语言。作者训练了一个多语言 NMT 系统,并探索了使用合成和抽样技术来平衡语料库。

  • GMU — 团队专注于对 26 种语言的预训练多语言 DeltaLM 进行微调。微调基于语言及语言家族。

  • IIAI DenTra — 他们提交了一个模型,该模型结合了传统的翻译损失和自监督任务,并使用了未标记的单语数据。该模型通过翻译和回译执行去噪任务,覆盖了 24 种语言。

  • Masakhane — 他们提交了一个基于 M2M-100 的模型,通过在正样本(干净数据)和来自自动对齐数据的负样本上进行微调。该模型在使用过滤数据后显示出显著改进。

  • 腾讯 Borderline — 他们提交了一个大型的变换器模型(1.02B 参数),通过标记回译和自我翻译增强了零-shot 对。

  • 字节跳动 VolcTrans — 他们的系统使用了并行和单语数据,并通过启发式方法进一步清理(修正标点符号不匹配、去除过短/过长或噪声句子)。此外,数据还通过来自英语和法语的回译进行增强。

  • SRPH-DAI — 他们提交了一个基于 mT5 的模型,增加了额外的适配器,针对每个翻译任务进行了微调,然后通过适配器融合进行合并。该模型使用了一组启发式方法过滤的数据,并没有使用其他数据增强技术。

系统评估

系统的评判标准包括 BLUE 、ChrF 和人工评估。前两者是机器翻译中使用的知名自动化指标。另一方面,由于资源限制和注释员的可用性,人工评估在之前的研究中没有被广泛使用。因此,这在该领域的研究中是一个重要的转变。

本研究通过众包和 Toloka 进行人工评估。它涉及精心挑选的注释员在 0 到 100 的范围内评判翻译质量,如下面的屏幕截图所示。

带有标注任务的界面,包括源句子和从提交的模型中随机选择的两种翻译(屏幕截图中的语言对为 Afrikaans — English)。— 作者提供的照片

众包可以成为评估机器翻译系统时获取注释员的有效方式,但它需要对参与者进行仔细挑选并实施严格的质量控制机制。需要确保注释员在源语言和目标语言中的流利度,本研究通过地理选择和语言测试来实现。此外,相同的任务会分配给多个注释员,然后汇总结果以获得更高的分数可信度。下面是本挑战中使用的确切流程。

Toloka Crowd 机器翻译评估流程和质量控制 — 作者提供的照片

关于人工评估的一个有趣发现是,它与其他指标如 BLUE 和 ChrF 高度相关,这证明它是一种有效的评分方法。

结果

表现最佳的系统是腾讯 Borderline,它在包括 BLUE、ChrF 和人工评估在内的所有评估指标中都超过了其他系统。紧随其后的是 GMU 系统,排名第二。下面的截图显示了每个系统的详细性能结果。

WMT2022 中针对非洲语言的系统评估结果,如研究论文所示 — 作者截图

很明显,用于训练系统的数据对性能产生了重要影响。使用更多数据、数据增强技术以及广泛的启发式方法清理数据的系统在不同语言对中的表现更好。

此外,与去年结果相比,取得了较大的进展。平均系统在 72 种语言对中,BLEU 分数提高了约 7.5 点(从 15.09 提高到 22.60)。值得一提的是,无论是以英语为中心的语言对还是非洲语言对,都出现了改进,其中后者尤其具有挑战性。下面你可以看到按改进幅度排序的前十种语言对。

去年最佳结果的前十种语言对的最大改进 — 作者截图

另一个重要的观察是,南非语言似乎更容易翻译(平均约 29 BLEU),而西非语言则更具挑战性(平均约 15 BLEU)。此外,翻译成非洲语言比从非洲语言翻译要困难。

总结

在这篇文章中,你了解了 WMT2022 中非洲语言机器翻译的最新进展。

评估了大约 100 种语言对,翻译质量与前几年相比有了显著提升。该领域的一些新进展包括使用广泛的人工评估来打分系统,并且包括数据跟踪,其中参与者提交了新的语言语料库。

如果你想了解更多细节,可以阅读以下论文,该论文专门讨论了这一任务。

PS: 我在 Medium 上撰写了简单易懂的基本数据科学概念文章,并在aboutdatablog.com上发布。你可以订阅我的电子邮件列表,每次我写新文章时都会通知你。如果你还不是 Medium 会员,你可以 在这里加入*。

以下是一些你可能会喜欢的其他文章:

## 计算机视觉中的数据漂移检测与修复

可以运行的代码实用案例研究

towardsdatascience.com ## Jupyter Notebook 中的 8 个神奇命令

通过学习最有用的命令来提升你的生产力

towardsdatascience.com ## 如何进行数据标注、版本控制和管理

一个丰富食品数据集的案例研究

towardsdatascience.com ## Jupyter Notebook 自动补全

如果你还没有使用,这是数据科学家应当使用的最佳生产力工具…

towardsdatascience.com

如何使用配置参数改进你的 ChatGPT 输出

原文:towardsdatascience.com/how-to-improve-your-chatgpt-outputs-using-configuration-parameters-0eebd575646e

ChatGPT,生成式 AI

专注于在你的 ChatGPT 提示中直接配置温度、Top P、频率惩罚和存在惩罚

Angelica Lo DucaTowards Data Science Angelica Lo Duca

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

--

图片来源于 GrowtikaUnsplash

我最近读了一本非常有趣的书,作者是 David Clinton,书名为《生成式 AI 完全过时指南》,由 Manning Publications 出版。在第二章中,作者描述了 AI 模型的主要参数是什么以及如何配置这些参数以适应需求。这些配置参数包括温度、Top P 值、频率惩罚和存在惩罚。

配置这些参数可以帮助 ChatGPT 理解你希望生成的输出类型。例如,如果你希望 ChatGPT 生成更确定的输出(严格与输入相关),你可以设置一些参数。如果你希望 ChatGPT 在生成输出时更加具有创意,你可以设置其他参数。

为了理解输出类型如何工作,大卫·克林顿在他的书中提供了一个与天气相关的例子。更确定的输出可能是:今天,天气晴朗,温度为 30°C。而更具创意的输出可能是:今天,天气晴朗,你可以去散步

根据我在书中找到的描述,我尝试自己实现一些例子,以查看如何在实际案例中应用书中的内容。我希望你也能从这篇文章中受益。

对于熟悉 OpenAI API 的人,配置参数可以直接作为 API 函数的参数传递。而在 ChatGPT 网页界面中使用它们则不那么直接。在本文中,我将展示如何在 ChatGPT 提示中直接包含这些参数。

为了进行实际测试,我们将使用一个假设场景和 ChatGPT 的免费版本,你可以在此链接中找到。我提醒你,在使用 ChatGPT 之前,你必须创建一个账户并阅读平台的使用政策。

在本文中,我们将涵盖:

  • 场景设置

  • 温度

  • Top P 值

  • 频率惩罚

  • 存在惩罚

场景设置

作为场景,我们将使用我书中第三章描述的用例,《使用 Python 和 Altair 进行生成性 AI 的数据讲述》 。如果你感兴趣,你可以从书中的GitHub 库免费下载与此场景相关的 Python 代码。

想象一下你想生成与以下图表相关的描述:

作者制作的图片

图表描述了北美(NA)与世界其他地区人口增长的比较。图表显示了每一年与基准 1960 年的差异。差距为 3.67 亿。你希望 ChatGPT 为你的图表生成一个描述。

我们将使用以下基准提示,然后将其调整为各种配置:

对以下场景写 200 字符的评论:北美与世界其他地区人口增长的比较。2020 年的差距为 3.67 亿,北美的数值较低。

温度

温度定义了生成输出时使用的随机程度。它使用户能够调整输出的创造性和不可预测性。温度范围从 0(高度结构化和保守的输出)到 2(高度创造性和不可预测的输出),默认值为 1。

让我们为我们的场景尝试不同的温度值。

温度 = 0(保守)

写下以下提示:

使用 温度 = 0: 对以下场景写 200 字符的评论:北美与世界其他地区人口增长的比较。2020 年的差距为 3.67 亿,北美的数值较低。

下图展示了 ChatGPT 可能生成的输出:

作者通过 ChatGPT 界面制作的图片

生成的输出与原始文本非常相似,意味着文本非常保守。

温度 = 1(中等)

写下以下提示:

使用一个 temperature = 1编写 200 个字符的评论:比较北美和世界其他地区的人口增长。2020 年差距为 3.67 亿,北美的数值较低。

下图展示了 ChatGPT 可能生成的输出:

作者使用 ChatGPT 界面制作的图像

ChatGPT 相比原始文本引入了一些新内容,包括这些对比的因素。

温度 = 2(高创造力)

写下以下提示:

使用一个 temperature = 2编写 200 个字符的评论:比较北美和世界其他地区的人口增长。2020 年差距为 3.67 亿,北美的数值较低。

下图展示了 ChatGPT 可能生成的输出:

作者使用 ChatGPT 界面制作的图像

此处的创造力水平较高,因为它包含了与社会(社会动态)相关的方面。

Top P

Top P 也称为核采样或无惩罚采样。它有助于控制生成文本的多样性。如果你希望生成的响应不完全偏离主题,可以使用此技术。范围在 0 到 1 之间。较高的 Top P 使输出更具多样性,而较低的值则使模型更具确定性。默认值为 1。

让我们尝试不同的 Top P 值来适应我们的场景。

Top P = 0.1(更具确定性)

写下以下提示:

使用一个 Top P = 0.1编写 200 个字符的评论:比较北美和世界其他地区的人口增长。2020 年差距为 3.67 亿,北美的数值较低。

下图展示了 ChatGPT 可能生成的输出:

作者使用 ChatGPT 界面制作的图像

生成的输出与原始输入提示非常相似。

Top P = 0.9(更多样化)

写下以下提示:

使用一个 Top P = 0.9编写 200 个字符的评论:比较北美和世界其他地区的人口增长。2020 年差距为 3.67 亿,北美的数值较低。

下图展示了 ChatGPT 可能生成的输出:

作者使用 ChatGPT 界面制作的图像

相比原始提示,文本更具多样性,因为它包含了不同的词汇。

频率惩罚

频率惩罚减少冗余输出。这使你能够控制生成多样化响应与避免重复模式之间的权衡。其值范围从-2.0(高重复)到 2.0(低重复)。

让我们尝试不同的 Frequency Penalty 值来分析我们的场景。

Frequency Penalty = -2(高重复)

撰写以下提示:

使用Frequency Penalty = -2*撰写 200 字符的评论,内容为:北美与全球其他地区的人口增长比较。2020 年差距为 3.67 亿,北美的值较低。

以下图示展示了 ChatGPT 可能产生的输出:

作者使用 ChatGPT 界面制作的图像

该文本与输入提示非常相似。

Frequency Penalty = 0(中性)

撰写以下提示:

使用Frequency Penalty = 0*撰写 200 字符的评论,内容为:北美与全球其他地区的人口增长比较。2020 年差距为 3.67 亿,北美的值较低。

以下图示展示了 ChatGPT 可能产生的输出:

作者使用 ChatGPT 界面制作的图像

在这种情况下,文本引入了一些新颖的内容,与原始文本相比。

Frequency Penalty = 2(低重复)

撰写以下提示:

使用Frequency Penalty = 2*撰写 200 字符的评论,内容为:北美与全球其他地区的人口增长比较。2020 年差距为 3.67 亿,北美的值较低。

以下图示展示了 ChatGPT 可能产生的输出:

作者使用 ChatGPT 界面制作的图像

该文本与原始提示有所不同,介绍了许多新颖的元素。

Presence Penalty

Presence Penalty 控制生成文本中短语和词汇的重复。它防止模型在生成的输出中过于频繁地重复相同的措辞或词汇。Presence Penalty 范围从-2(生成文本时更灵活)到 2(强烈抑制重复)。

看起来 Frequency Penalty 和 Presence Penalty 可能会重叠。然而,它们代表不同的概念。

在他的书《生成性 AI 完全过时指南》中,David Clinton 解释了差异:

Frequency Penalty […] 避免模型在生成的文本中过于频繁地重复相同的词汇或短语。Presence Penalty […] 鼓励模型生成输入中未出现的词汇。

让我们尝试不同的 Presence Penalty 值来分析我们的场景。

Presence Penalty = -2(更灵活)

撰写以下提示:

使用Presence Penalty = -2*撰写 200 字符的评论,内容为:北美与全球其他地区的人口增长比较。2020 年差距为 3.67 亿,北美的值较低。

以下图示展示了 ChatGPT 可能产生的输出:

作者使用 ChatGPT 界面制作的图像

Presence Penalty = 2(强烈抑制重复)

写下以下提示:

用以下场景写一个 200 字的评论,使用Presence Penalty = 2:北美与世界其他地区的人口增长比较。在 2020 年,有 3.67 亿的差距,北美的值较低。

以下图示显示了 ChatGPT 可能生成的输出:

作者使用 ChatGPT 界面制作的图像

高的 Presence Penalty 值会鼓励模型生成新的、未见过的词汇。

总结

恭喜!你刚刚学习了如何在 ChatGPT 提示中使用温度、Top P 值、频率惩罚和 Presence Penalty!

记住每次设置一个参数;否则,模型可能会感到困惑。

如果你读到这里,我对今天的内容已经很满意了。谢谢,下次见 😃

资源

你也可能对以下内容感兴趣……

[## 书评:生成 AI 与数据分析

对 Artur Guja、Marlena Siwiak 和 Marian Siwiak 的书《生成 AI 与数据分析》的初步评审……

## 如何使用 ChatGPT 将文本转换为 PowerPoint 演示文稿 [## 如何使用 ChatGPT 将文本转换为 PowerPoint 演示文稿

一种使用 ChatGPT 将长文本快速转换为简短 PowerPoint 演示文稿的方法

## 书评:Nathan B. Crocker 的《AI 驱动的开发者》 [## 书评:Nathan B. Crocker 的《AI 驱动的开发者》

对 Nathan B. Crocker 的书《AI 驱动的开发者》的初步评审,讨论如何使用生成式 AI 工具来……

medium.com

如何在 Kubernetes 中通过 NVIDIA MPS 提高 GPU 利用率

原文:towardsdatascience.com/how-to-increase-gpu-utilization-in-kubernetes-with-nvidia-mps-e680d20c3181?source=collection_archive---------1-----------------------#2023-02-02

在 Kubernetes 中集成 NVIDIA 多进程服务(MPS),以在工作负载之间共享 GPU,最大化利用率并降低基础设施成本

Michele ZanottiTowards Data Science Michele Zanotti

·

关注 发布于 Towards Data Science ·10 分钟阅读·2023 年 2 月 2 日

--

图片由 Growtika 提供,发布在 Unsplash

大多数工作负载并不需要每个 GPU 的全部内存和计算资源。因此,在多个进程之间共享 GPU 对于提高 GPU 利用率和降低基础设施成本至关重要。

在 Kubernetes 中,这可以通过将单个 GPU 暴露为多个资源(即切片),每个资源具有特定的内存和计算大小,供单独的容器请求来实现。通过创建每个容器严格需要的 GPU 切片,你可以释放集群中的资源。这些资源可以用于调度额外的 Pod,或者减少集群中的节点数量。无论哪种情况,进程间的 GPU 共享可以帮助你降低基础设施成本。

Kubernetes 中的 GPU 支持由NVIDIA Kubernetes 设备插件提供,目前仅支持两种共享策略:时间切片和多实例 GPU(MIG)。然而,还有一种平衡时间切片和 MIG 优缺点的 GPU 共享策略:多进程服务(MPS)。尽管 MPS 不受 NVIDIA 设备插件支持,但在 Kubernetes 中仍有使用它的方法。

在本文中,我们将首先审查这三种 GPU 共享技术的优缺点,然后提供如何在 Kubernetes 中使用 MPS 的逐步指南。此外,我们还提出了一种自动化管理 MPS 资源以优化利用率和降低运营成本的解决方案:动态 MPS 分区

GPU 共享技术概述

共享 GPU 的三种方法如下:

  1. 时间切片

  2. 多实例 GPU(MIG)

  3. 多进程服务(MPS)

在深入了解动态 MPS 分区的演示之前,我们先概述这些技术。

时间切片

时间切片是一种机制,允许落在超额分配 GPU 上的工作负载彼此交替。时间切片利用 GPU 时间切片调度器,通过时间共享同时执行多个 CUDA 进程。

当时间切片被激活时,GPU 以公平共享的方式在不同进程之间分配计算资源,通过在固定时间间隔内切换进程。这会产生与持续上下文切换相关的计算时间开销,导致抖动和更高的延迟。

时间切片几乎被所有 GPU 架构支持,是在 Kubernetes 集群中共享 GPU 的最简单解决方案。然而,进程间的不断切换会产生计算时间开销。此外,时间切片不提供进程间的内存隔离,也没有内存分配限制,这可能导致频繁的内存溢出(OOM)错误。

如果你想在 Kubernetes 中使用时间切片,你只需编辑 NVIDIA 设备插件配置。例如,你可以将以下配置应用到一个具有 2 个 GPU 的节点。该节点上运行的设备插件将向 Kubernetes 通告 8 个nvidia.com/gpu资源,而不是 2 个。这允许每个 GPU 最多由 4 个容器共享。

version: v1
sharing:
  timeSlicing:
    resources:
    - name: nvidia.com/gpu
      replicas: 4

有关 Kubernetes 中时间切片分区的更多信息,请参阅 NVIDIA GPU Operator 文档

多实例 GPU (MIG)

多实例 GPU (MIG) 是 NVIDIA AmpereHopper 架构上可用的一项技术,允许将一个 GPU 安全地分割成最多七个独立的 GPU 实例,每个实例都有自己独立的高带宽内存、缓存和计算核心。

隔离的 GPU 切片称为 MIG 设备,它们的命名采用一种格式,以指示设备的计算和内存资源。例如,2g.20gb 对应于一个具有 20 GB 内存的 GPU 切片。

MIG 不允许创建自定义大小和数量的 GPU 切片,因为每种 GPU 型号仅支持 特定的 MIG 配置。这降低了你对 GPU 分割的细粒度控制。此外,MIG 设备的创建必须遵循某些 放置规则,进一步限制了使用的灵活性。

MIG 是一种提供最高级别进程隔离的 GPU 共享方式。然而,它缺乏灵活性,仅与少数几种 GPU 架构(Ampere 和 Hopper)兼容。

你可以使用 nvidia-smi CLI 手动创建和删除 MIG 设备,或使用 NVML 进行编程操作。这些设备随后通过 NVIDIA Device Plugin 使用 不同的命名策略 被暴露为 Kubernetes 资源。例如,使用 mixed 策略时,设备 1g.10gb 被暴露为 nvidia.com/mig-1g.10gb。而 single 策略则将设备暴露为通用的 nvidia.com/gpu 资源。

手动使用 nvidia-smi CLI 或 NVML 管理 MIG 设备相当不切实际:在 Kubernetes 中,NVIDIA GPU Operator 提供了一种更简单的 MIG 使用方式,尽管仍然存在一些限制。该操作程序使用一个 ConfigMap 来定义一组允许的 MIG 配置,你可以通过为每个节点打标签来应用这些配置。

你可以编辑这个 ConfigMap 来定义你自己的自定义 MIG 配置,如下例所示。在这个例子中,一个节点被标记为 nvidia.com/mig.config=all-1g.5gb。因此,GPU Operator 将该节点的每个 GPU 分割成七个 1g.5gb MIG 设备,这些设备随后被暴露为 Kubernetes 中的 nvidia.com/mig-1g.5gb 资源。

apiVersion: v1
kind: ConfigMap
metadata:
  name: default-mig-parted-config
data:
  config.yaml: |
    version: v1
    mig-configs:
      all-1g.5gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "1g.5gb": 7
      all-2g.10gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "2g.10gb": 3 

要有效利用集群中的资源与 NVIDIA GPU Operator,集群管理员必须持续修改 ConfigMap,以适应不断变化的 MIG 大小和工作负载计算需求。

这非常不切实际。虽然这种方法确实比通过 SSH 登录节点并手动创建/删除 MIG 设备要好,但对于集群管理员来说,这非常费力和耗时。因此,MIG 设备的配置往往很少随着时间的推移而变化,或者根本没有应用,这两种情况都会导致 GPU 利用率低下,从而提高基础设施成本。

这个挑战可以通过动态 GPU 分区来克服。稍后在本文中,我们将看到如何使用开源模块nos动态分区 GPU,这种方法也适用于 MIG。

多进程服务(MPS)

多进程服务(MPS)是 CUDA 应用程序编程接口(API)的客户端-服务器实现,用于在同一 GPU 上并发运行多个进程。

服务器管理 GPU 访问,为客户端提供并发。客户端通过客户端运行时连接到它,该运行时内置于 CUDA 驱动程序库中,任何 CUDA 应用程序都可以透明地使用它。

MPS 与基本上所有现代 GPU 兼容,并提供了最高的灵活性,允许创建具有任意限制的 GPU 切片,包括分配内存量和可用计算量。然而,它不强制执行进程间的完全内存隔离。在大多数情况下,MPS 是 MIG 和时间切片之间的良好折中方案。

与时间切片相比,MPS 通过空间共享并行运行进程,消除了上下文切换的开销,因此能提供更好的计算性能。此外,MPS 为每个进程提供自己的 GPU 内存地址空间。这允许对进程施加内存限制,克服了时间切片共享的限制。

然而,在 MPS 中,客户端进程之间并未完全隔离。实际上,即使 MPS 允许限制客户端的计算和内存资源,它也不提供错误隔离和内存保护。这意味着客户端进程可能崩溃并导致整个 GPU 重置,影响在 GPU 上运行的所有其他进程。

NVIDIA Kubernetes 设备插件不支持 MPS 分区,这使得在 Kubernetes 中使用它并不简单。在接下来的部分,我们将探讨通过利用nos和另一种 Kubernetes 设备插件来利用 MPS 进行 GPU 共享的替代方法。

Kubernetes 中的多进程服务(MPS)

你可以通过使用 Helm 安装NVIDIA 设备插件的这个分支来启用 Kubernetes 集群中的 MPS 分区:

helm install oci://ghcr.io/nebuly-ai/helm-charts/nvidia-device-plugin \
  --version 0.13.0 \
  --generate-name \
  -n nebuly-nvidia \
  --create-namespace

默认情况下,Helm 图表会在所有标记为nos.nebuly.com/gpu-partitioning=mps的节点上启用 MPS 模式部署设备插件。要在特定节点的 GPU 上启用 MPS 分区,你只需将标签nos.nebuly.com/gpu-partitioning=mps应用于该节点。

你的集群上很可能已经安装了一个版本的 NVIDIA 设备插件。如果你不想删除它,可以选择将这个分叉插件与原始 NVIDIA 设备插件一起安装,并仅在特定节点上运行。为此,重要的是确保在同一节点上同时运行的两个插件中只有一个在运行。如安装指南所述,可以通过编辑原始NVIDIA 设备插件的规格,并在其spec.template.spec中添加一个反亲和性规则,以确保它不会在分叉插件所针对的相同节点上运行:

affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: nos.nebuly.com/gpu-partitioning
          operator: NotIn
          values:
          - mps

安装设备插件后,你可以通过编辑其配置中的sharing.mps部分来配置它以将 GPU 暴露为多个 MPS 资源。例如,下面的配置告诉插件将索引为0的 GPU 暴露给 Kubernetes,作为两个 GPU 资源(名为nvidia.com/gpu-4gb),每个资源具有 4GB 的内存:

version: v1
sharing:
  mps: 
    resources:
      - name: nvidia.com/gpu
        rename: nvidia.com/gpu-4gb
        memoryGB: 4
        replicas: 2
        devices: ["0"]

广告给 Kubernetes 的资源名称、分区大小和副本数量可以根据需要进行配置。回到上面给出的示例,容器可以请求 4GB GPU 内存的一部分,如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: mps-partitioning-example
spec:
  hostIPC: true # 
  securityContext:
    runAsUser: 1000 # 
  containers:
    - name: sleepy
      image: "busybox:latest"
      command: ["sleep", "120"]
      resources:
        limits:
          nvidia.com/gpu-4gb: 1 #

请注意,容器请求 MPS 资源的 Pods 存在一些限制:

  1. 容器必须以与设备插件部署的 MPS 服务器相同的用户 ID 运行,默认情况下为 1000。你可以通过编辑设备插件安装图表中的mps.userID值来更改它。

  2. Pod 规范必须包含hostIPC: true。由于 MPS 要求客户端和服务器共享相同的内存空间,我们需要允许 Pods 访问主机节点的 IPC 命名空间,以便它能够与运行在主机上的 MPS 服务器进行通信。

在这个例子中,容器最多只能在共享 GPU 上分配 2GB 的内存。如果它尝试分配更多内存,将会因内存不足(OOM)错误而崩溃,但不会影响其他 Pods。

然而,需要指出的是,nvidia-smi访问 NVIDIA 驱动程序时会绕过 MPS 客户端运行时。因此,在容器内运行nvidia-smi将显示其输出中的整个 GPU 资源:

动态 MPS 分区

总体而言,通过设备插件配置来管理 MPS 资源是复杂且耗时的。与其如此,不如直接创建请求 MPS 资源的 Pods,并让其他人自动配置和管理它们。

动态 MPS 分区正是这样做的:它根据集群中工作负载的实时需求自动创建和删除 MPS 资源,确保始终将最佳共享配置应用于可用的 GPU。

要应用动态分区,我们需要使用 [nos](https://github.com/nebuly-ai/nos),这是一个开源模块,用于在 Kubernetes 上高效地运行 GPU 工作负载。

我已经介绍了如何使用 nos 基于多实例 GPU(MIG)进行动态 GPU 分区。因此,在这里我们不会详细讨论,因为 nos 以相同的方式管理 MPS 分区。有关更多信息,你可以参考文章 Kubernetes 中的动态 MIG 分区,或查看 nos文档

MPS 和 MIG 动态分区之间唯一的区别在于用于告诉nos应该管理哪些节点的 GPU 分区的标签值。在 MPS 的情况下,你需要按照以下方式对节点进行标记:

kubectl label nodes <node-names> "nos.nebuly.com/gpu-partitioning=mps"

结论

请求 GPU 切片的可能性对于提高 GPU 利用率和降低基础设施成本至关重要。

有三种方式可以实现:时间切片、多实例 GPU(MIG)和多进程服务器(MPS)。时间切片是共享 GPU 的最简单技术,但它缺乏内存隔离,并且引入的开销会降低工作负载性能。另一方面,MIG 提供了最高级别的隔离,但其支持的配置和“切片”大小的有限集合使其缺乏灵活性。

MPS 是 MIG 和时间切片之间的有效折中方案。与 MIG 不同,它允许创建任意大小的 GPU 切片。与时间切片不同,它允许强制内存分配限制,并减少多个容器争夺共享 GPU 资源时可能出现的内存不足(OOM)错误。

目前,NVIDIA 设备插件不支持 MPS。不过,只需安装支持 MPS 的其他设备插件即可启用 MPS。

然而,MPS 静态配置不会自动调整以应对工作负载需求的变化,因此无法为每个 Pod 提供所需的 GPU 资源,特别是在工作负载在内存和计算方面要求多种切片且随时间变化的情况下。

nos 通过动态 GPU 分区克服了 MPS 静态配置的局限性,这增加了 GPU 利用率,并减少了在集群节点上运行的设备插件实例上手动定义和应用 MPS 配置的操作负担。

总结来说,我们必须指出,有些情况下 MPS 的灵活性并非必要,而 MIG 提供的完全隔离则至关重要。然而,在这些情况下,仍然可以通过 nos 利用动态 GPU 分区,因为它支持两种分区模式。

资源

贡献者

特别感谢 Emile Courthoud 对本文的审阅和贡献。

如何在 Kubernetes 中安装私有 Docker 容器注册表

原文:towardsdatascience.com/how-to-install-a-private-docker-container-registry-in-kubernetes-eadcfd6e0f27

完全控制你图像存储的位置

Paul KnulstTowards Data Science Paul Knulst

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

--

照片由 Growtika 提供,刊登在 Unsplash

介绍

托管一个 Docker 私有注册表使你可以完全控制图像的存储位置以及如何访问它们。如果你开发的是不应公开在 Docker Hub 上的私人项目,这一点尤为有用。

在本教程中,你将学习如何在任何 Kubernetes 集群中安装一个私有 Docker 注册表。这是 之前在本博客发布的教程的后续内容,因为它将使用 Traefik Ingress Controller 来暴露 Docker 注册表。

准备工作

创建 Kubernetes 命名空间

第一步是创建一个 Kubernetes 命名空间,在本教程中所有资源都将在该命名空间中应用:

kubectl create namespace docker-registry

PersistentVolumeClaim

在本节中,你将使用 PersistentVolumeClaim 将一个卷挂载到一个专用的 Kubernetes Pod 中。PersistentVolumeClaim(PVC)是一个 Kubernetes 资源,用于使用预定义的抽象 PersistentVolume(PV)存储,而不暴露这些卷是如何实现的。

💡 前提条件: Kubernetes 集群中应该存在一个 PersistentVolume。PersistentVolume(*PV*)是一个存储定义,可以是用户手动配置的,也可以是使用存储类动态配置的。 有关更多信息,请参见此链接

为了设置将在私有 Docker 注册表中使用的 PersistentVolumeClaim,本教程假设你已经有一个预配置的 PersistentVolume,名称为 csi-cinder-classic

1. 创建一个新文件(registry-pvc.yaml),包含使用 csi-cinder-classic 存储类的 PersistentVolumeClaim:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: docker-registry-pv-claim
  namespace: docker-registry
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 60Gi
  storageClassName: csi-cinder-classic

2. 将其应用到 Kubernetes 集群:

kubectl apply -f registry-pvc.yamlb

3. 验证持久卷声明是否成功创建:

kubectl get pvc docker-registry-pv-claim -n docker-registry

# NAME                   STATUS   VOLUME  CAPACITY   ACCESS MODES   STORAGECLASS
# docker-registry-pv-claim  Bound    ***   60Gi       RWO            csi-cinder-classic

设置 Docker Registry

现在,你已创建了命名空间和 PersistentVolumeClaim,可以使用外部存储部署 Docker registry,并使其在整个 Kubernetes 集群中可用。

生成用户与密码

要设置 Docker registry,你需要生成一个用户和密码,用于推送和拉取镜像到 registry。

创建一个新文件 gen-pass.sh,包含以下内容:

export REGISTRY_USER=admin
export REGISTRY_PASS=registryPass
export DESTINATION_FOLDER=./registry-creds

# Backup credentials to local files (in case you'll forget them later on)
mkdir -p ${DESTINATION_FOLDER}
echo ${REGISTRY_USER} >> ${DESTINATION_FOLDER}/registry-user.txt
echo ${REGISTRY_PASS} >> ${DESTINATION_FOLDER}/registry-pass.txt

docker run --entrypoint htpasswd registry:2.7.0 \
    -Bbn ${REGISTRY_USER} ${REGISTRY_PASS} \
    > ${DESTINATION_FOLDER}/htpasswd

unset REGISTRY_USER REGISTRY_PASS DESTINATION_FOLDER

💡 注意: 根据需要更改 REGISTRY_USER 和 REGISTRY_PASS。此外,由于*htpasswd* 在最新的 Docker 镜像中已被移除,我们将使用版本 2.7.0 来生成用户和密码。更多信息 请查看这个 stack overflow 文章。*

执行脚本,这将创建在子文件夹 ./registry-creds 中的凭据:

sh gen-pass.sh

使用 Helm 安装 Docker Registry

在接下来的步骤中,你将使用 Helm 安装 Docker registry。

💡 注意: Helm 是 Kubernetes 的包管理器,专注于自动化安装各种 Kubernetes 应用程序。

1. 添加 twuni/docker-registry Helm 仓库,该仓库是官方 Docker registry 的继任者:

helm repo add twuni https://helm.twun.io

2. 更新本地 Helm 图表仓库缓存:

helm repo update

3. 搜索 twuni/docker-registry Helm 图表的最新版本:

helm search repo docker-registry
# NAME                    CHART VERSION   APP VERSION     DESCRIPTION
# twuni/docker-registry   2.2.2           2.8.1           A Helm chart for Docker Registry

4. 创建一个 registry-chart.yaml 文件,用于在我们的 Kubernetes 集群中安装 Docker registry:

---
replicaCount: 1
persistence:
  enabled: true
  size: 60Gi
  deleteEnabled: true
  storageClass: csi-cinder-classic
  existingClaim: docker-registry-pv-claim
secrets:
  htpasswd: admin:$2y$05$Gh/3ppmkuIXJIVyBBtHf0ug.wnnJvbtSEzlXz6z/7oO7XvF/xq7Ni

💡注意: *htpasswd* 字符串替换为由 *gen-pass.sh* 脚本生成的文件内容 (*./registry-cred/htpasswd*)

5. 使用 registry-chart.yaml 安装 Docker registry Helm 图表:

helm install -f .\registry-chart.yaml docker-registry --namespace docker-registry twuni/docker-registry

6. 验证安装:

kubectl get pods -n docker-registry
# NAME                              READY   STATUS    RESTARTS
# docker-registry-9fa1234ba-gaf16   1/1     Running   0

如果你想更改像 replicaCounthtpasswd 或存储之类的内容,可以通过调整 registry-chart.yaml 来完成,并通过执行以下命令重新应用:

helm upgrade -f .\registry-chart.yaml docker-registry --namespace docker-registry twuni/docker-registry

如何卸载?

要卸载 Docker registry,你需要使用 Helm 将其从 Kubernetes 集群中移除:

helm uninstall docker-registry --namespace docker-registry

然后从 Kubernetes 集群中删除 Docker registry 命名空间:

kubectl delete namespace docker-registry

添加 Docker Registry Ingress

要暴露 Docker registry,你将使用 Traefik Ingress Controller 通过 HTTPS 和适当的 TLS 证书来允许访问 registry。

💡注意: 请阅读关于如何在任何 Kubernetes 集群中安装 Traefik Ingress Controller 的教程: https://www.paulsblog.dev/how-to-install-traefik-ingress-controller-in-kubernetes/

1. 创建一个新文件(ingressroute.yaml),并确保将 YOUR_DOMAIN 替换为你的 Docker registry 域名:

---
kind: IngressRoute
apiVersion: traefik.containo.us/v1alpha1
metadata:
  name: docker-registry
  namespace: docker-registry

spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`YOUR_DOMAIN`)
      kind: Rule
      services:
        - name: docker-registry
          port: 5000

2. 通过执行来应用 IngressRoute:

kubectl apply -f ingressroute.yaml

3. 验证 Kubernetes 资源是否成功创建:

kubectl describe ingressroute docker-registry -n docker-registry

将镜像推送到 Kubernetes 集群中的私有注册表

为了展示如何将 Docker 镜像推送到我们的新 Docker 注册表,本教程将展示如何拉取一个公共的 Docker Hub 镜像,对其进行标记,然后将其推送到你的注册表。

1. 使用先前创建的凭据登录 Docker 注册表

docker login \
   -u $(cat ./registry-creds/registry-user.txt) \
   -p $(cat ./registry-creds/registry-pass.txt) \
   YOUR_DOMAIN

2. 拉取nginx:latest Docker 镜像:

docker pull nginx

3. 用自定义名称标记镜像,并在前面加上私有 Docker 注册表域名

docker tag nginx YOUR_DOMAIN/my-nginx

4. 将 Docker 镜像推送到注册表

docker push YOUR_DOMAIN/my-nginx

使用 Docker 注册表在 Kubernetes 集群中拉取镜像

由于你在 Kubernetes 集群中部署了 Docker 注册表,你可以通过拉取之前推送的镜像来开始使用它,为你的 Kubernetes Pods。

要了解如何使用私有 Docker 注册表拉取镜像,你将创建一个新的test命名空间中的简单 Kubernetes pod。这个 Kubernetes Pod 将使用之前推送的镜像YOUR_DOMAIN/my-nginx

首先,你必须创建test Kubernetes 命名空间:

kubectl create namespace test

创建 Docker 注册表 Secret

这是最重要的一步!

你必须创建一个 Docker Secret,以便从 Kubernetes 集群访问 Docker 注册表。为此,使用上一步的凭据,在test命名空间中创建一个 Kubernetes Docker Secret:

kubectl create secret docker-registry regcred --docker-server=YOUR_DOMAIN --docker-username=admin --docker-password=registryPass -n test

💡 注意:这个 Kubernetes Docker secret 资源必须 在正确的命名空间中创建!

使用 Docker 注册表中的镜像创建 Kubernetes Pod

在创建包含 Docker 注册表凭据的 Kubernetes secret 后,你创建一个新的 Kubernetes Deployment(test-nginx.yaml),该 Deployment 使用你的注册表:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: test
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: YOUR_DOMAIN/my-nginx
        ports:
        - containerPort: 80
      imagePullSecrets:
        - name: regcred

如果仔细查看此文件,你会发现它与正常的nginx部署有三个基本的不同之处。

  1. image: YOUR_DOMAIN/my-nginx

  2. imagePullSecrets: - name: regred

这两个更改都是使用 Docker 注册表所必需的。第一个选项选择要在 pod 中使用的镜像(注册表 URL 作为前缀+镜像名称)。第二个选项设置用于拉取镜像的 Docker secret。如果你更改了凭据或名称,你必须更新这两个值。

现在,在你的集群中部署 Kubernetes Deployment:

kubectl apply -f test-nginx.yaml

使用kubectl describe podname -n test查看镜像是否会被拉取,以及容器是否能够正确启动。输出应如下所示:

部署 my-nginx Kubernetes 部署后的 Pod 拉取镜像事件

结束语

希望这篇文章给你提供了一个关于如何在 Kubernetes 集群中设置私有 Docker 注册表的良好概述。

记住,如果部署的 Docker 服务不是开源的,拥有私有 Docker 注册表是至关重要的!

为了提供一个可以在任何 Kubernetes 集群上运行的简单、可重复的流程,我创建了 一个包含所有必要文件的 GitHub Gist。如果你已经 在你的 Kubernetes 集群中运行了 Traefik Ingress Controller,你可以简单地下载所有文件,调整到你的需求,然后通过执行来应用它们:

kubectl create namespace docker-registry
kubectl apply -f registry-pvc.yaml
helm repo add twuni https://helm.twun.io
helm repo update
helm search repo docker-registry
helm install -f values.yaml docker-registry --namespace docker-registry twuni/docker-registry
kubectl apply -f registry-ingressroute.yaml

我很想听到你对这个教程的反馈。此外,如果你也设置了 Docker 注册表并使用了不同的方法,请在这里评论并解释你做了什么不同的操作。如果你有任何问题,请在评论中提出。我会尽可能回答。

随时通过 我的博客LinkedInTwitterGitHub 与我联系。

这篇文章最初发布在我的博客上 https://www.paulsblog.dev/how-to-install-a-private-docker-container-registry-in-kubernetes/

如何在 Kubernetes 中安装 Traefik Ingress Controller

原文:towardsdatascience.com/how-to-install-traefik-ingress-controller-in-kubernetes-fa2b9079e942

提供负载均衡、基于名称的虚拟主机和 SSL 终止

Paul KnulstTowards Data Science Paul Knulst

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

--

图片来源:Growtika 开发者营销机构 / Unsplash

介绍

本教程将展示如何使用 Traefik 作为 Kubernetes(或 k8s)中的 Ingress Controller,以提供负载均衡、基于名称的虚拟主机和 SSL 终止。

要跟随本教程,你需要:

  • 一个运行中的 Kubernetes 集群或一个托管的 Kubernetes

  • 一个负载均衡器,它动态地将流量分配到标记为 LoadBalancer 的任何 Kubernetes 资源上

  • 一个 PRIMARY_DOMAIN

注意:我在此帖子中使用的域名是 PRIMARY_DOMAIN,请相应更改。如果你的目标域名是 paulsblog.dev,请将 PRIMARY_DOMAIN 替换为 paulsblog.dev

什么是 Ingress Controller?

Ingress Controller 是一个 API 对象,它将管理对 Kubernetes 集群中任何已部署服务的外部访问。通常使用 HTTP 或 HTTPS。此外,它提供负载均衡、基于名称的虚拟主机和 SSL 终止。

为什么你需要一个 Ingress Controller?

此列表将展示在 Kubernetes 集群中使用 Ingress Controller 的最重要的好处:

  • 将任何流量负载均衡到部署在 Kubernetes 集群外的每个服务之间

  • 允许集群内的服务之间进行 HTTP 流量,但强制要求从 Kubernetes 集群外部的 HTTPS 流量,同时终止加密。

  • 简化内部服务之间的交互,并在需要时通过更改 Ingress 路由规则重新路由。

准备 Kubernetes 环境

安装 Helm,即 Kubernetes 包管理器

要在你的 Kubernetes 集群上安装 Helm,你可以使用官方的 Helm 安装脚本,它将自动安装最新版本。

在安装 Helm 之前,你可以通过阅读 官方 Helm 文档 来深入了解 Helm。之后,下载脚本并在本地执行。

curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh

配置 kubectl 以访问 Kubernetes 集群

如果使用 kubectl,你可以使用三种不同的技术来操作集群:

1. 对每个命令使用 **--kubeconfig** 标志:

kubectl get pods --kubeconfig=config1
kubectl get pods --kubeconfig=config2

2. 使用 **KUBECONFIG** 环境变量:

export KUBECONFIG=config1 
kubectl get pods
kubectl get all

export KUBECONFIG=config2
kubectl get pods
kubectl get all

3. 将配置文件复制到 **$HOME/.kube/config**

准备 Helm Chart

要安装 Traefik,你应该将官方 Traefik Helm 仓库添加到你的 Helm 客户端。这可以通过执行以下操作来完成:

helm repo add traefik https://helm.traefik.io/traefik
helm repo update

之后,你需要通过创建一个 values.yaml 来配置 Helm 图表。所有可能的值可以在 Traefik Helm 图表的 GitHub 中找到,并将用于设置 Traefik 代理的静态配置。

现在,你应该创建一个 values.yaml 文件,并将以下内容粘贴进去:

---
additionalArguments:
  - --entrypoints.websecure.http.tls.certresolver=ionos
  - --entrypoints.websecure.http.tls.domains[0].main=PRIMARY_DOMAIN
  - --entrypoints.websecure.http.tls.domains[0].sans=*.PRIMARY_DOMAIN
  - --certificatesresolvers.ionos.acme.dnschallenge.provider=ionos
  - --certificatesresolvers.ionos.acme.email=webmaster@PRIMARY_DOMAIN
  - --certificatesresolvers.ionos.acme.dnschallenge.resolvers=1.1.1.1
  - --certificatesresolvers.ionos.acme.storage=/data/acme.json

deployment:
  initContainers:
    - name: volume-permissions
      image: busybox:1.31.1
      command: ["sh", "-c", "chmod -Rv 600 /data/*"]
      volumeMounts:
        - name: data
          mountPath: /data

env:
  - name: IONOS_API_KEY
    valueFrom:
      secretKeyRef:
        key: IONOS_API_KEY
        name: ionos-api-credentials

ingressRoute:
  dashboard:
    enabled: false

persistence:
  enabled: true
  path: /data
  size: 128Mi

这个 values.yaml 用于配置 Traefik 代理,并将:

  • 使用 IONOS 作为证书解析器。要查找你的提供商,请 查阅 Traefik 文档

  • 将证书主域设置为 PRIMARY_DOMAIN

  • 将证书 sans 设置为 *.PRIMARY_DOMAIN

  • 将每个生成的证书存储在 /data/acme.json

  • 运行一个 busybox 初始化容器以解决一个常见的权限问题,详细说明请参阅 github.com/containous/traefik/issues/6972

  • 从一个密钥中加载 IONOS_API_KEY。如果使用其他提供商,请添加所需的所有环境变量

  • 禁用 Traefik 仪表板

  • 启用 Traefik 代理的持久化

安装 Traefik 代理作为 Ingress 控制器

要在你的 Kubernetes 集群中安装 Traefik 代理,按照接下来的四个简单步骤操作

1. 首先创建一个 Kubernetes 命名空间:

kubectl create namespace traefik

2. 创建一个 **treafik-secret.yaml** 文件,包含在 Helm 图表中用于 SSL 证书创建的密钥:

---
apiVersion: v1
kind: Secret
metadata:
  name: ionos-api-credentials
  namespace: traefik

type: Opaque
stringData:
  IONOS_API_KEY: asdkjalshdasdlasdasd.asdahsdhasdkjahsdkasgdkasdg;aksda;d

3. 在你的 Kubernetes 集群中应用密钥:

kubectl apply -f traefik-secret.yaml

4. 使用 Helm 安装 Traefik 并应用 **values.yaml**

helm install traefik traefik/traefik --namespace=traefik --values=values.yaml

如果你在 values.yaml 中更改了任何内容并且想要更新 Traefik 代理,可以通过执行以下操作来完成:

helm upgrade traefik traefik/traefik --namespace=traefik --values=values.yaml

几分钟后,你的 Traefik 将正确部署,你可以将你的 PRIMARY_DOMAIN A 记录映射到 Traefik 负载均衡器的 IP。你可以通过执行以下操作找到外部 IP:

kubectl get all -n traefik

它应该输出以下内容:

kubectl 的输出包含所有 traefik 命名空间中的内容

当外部 IP 尚未设置时,请等待一段时间并重试。如果你没有收到外部 IP,可能是因为你没有安装/购买外部负载均衡器。

启用 Traefik 仪表板

要在 Kubernetes 集群中启用 Traefik 仪表盘,你需要创建一个 Ingress 路由和一个中间件以启用基本认证。

创建一个名为 traefik-dashboard 的新文件夹,其中包含设置 Traefik 仪表盘所需的所有文件。

现在,创建一个 base64 编码的用户和密码,这将用于 Kubernetes 密钥中:

htpasswd -nb superTraefikUser unbelievableSafePassword | openssl base64

然后,复制 htpasswd 字符串并创建 Kubernetes 密钥 001-auth-secret

---
apiVersion: v1
kind: Secret
metadata:
  name: traefik-dashboard-auth
  namespace: traefik

data:
  users: YOUR_UNBELIEVABLE_SECURE_HTPASSWD_STRING

然后创建一个 Kubernetes 中间件 002-middleware,它将使用基本认证密钥:

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: traefik-dashboard-basicauth
  namespace: traefik

spec:
  basicAuth:
    secret: traefik-dashboard-auth

现在你可以为 Traefik 仪表盘创建 Ingress 路由 003-ingressroute

---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: traefik-dashboard
  namespace: traefik

spec:
  entryPoints:
    - websecure

  routes:
    - match: Host(`traefik.PRIMARY_DOMAIN`)
      kind: Rule
      middlewares:
        - name: traefik-dashboard-basicauth
          namespace: traefik
      services:
        - name: api@internal
          kind: TraefikService

如果你按照描述命名了文件,你应该拥有以下文件结构:

traefik-dashboard
--001-auth-secret
--002-middleware
--003-ingressroute

切换到上级文件夹,并使用以下命令按正确的顺序应用所有文件:

kubectl apply -f traefik-dashboard

几分钟后,Traefik 仪表盘上线,并可以通过其域名 (https://traefik.PRIMARY_DOMAIN) 访问。

结束语

我希望这篇文章给你提供了一个关于如何在 Kubernetes 集群中设置 Traefik Proxy 作为 Ingress Controller 的简洁概述。

使用 Helm 设置 Traefik Ingress 控制器使得安装、重新配置和更新 Traefik Proxy 变得简单。

有了这个设置,你可以部署任何 Kubernetes Pod/Service,并使用 IngressRoute 通过任何子域名使其通过 SSL 访问。

我非常希望听到你对这个教程的反馈。此外,如果你已经运行了 Traefik 安装并使用了不同的方法,请在这里评论并解释你做了什么不同的事情。如果你有任何问题,请在评论中提问。我会尽可能回答。

欢迎通过 我的博客LinkedInTwitterGitHub 与我联系。

这篇文章最初发布在我的博客 https://www.paulsblog.dev/how-to-install-traefik-ingress-controller-in-kubernetes/

如何在你的代码中集成 Microsoft Translator API

原文:towardsdatascience.com/how-to-integrate-the-microsoft-translator-api-in-your-code-89bad979028e

一份全面且适合初学者的指南

Yousef NamiTowards Data Science Yousef Nami

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

--

图片来自 Unsplash,由 Edurne 提供。

目前有许多优秀的翻译服务,但其中最通用且易于设置的之一是 Microsoft Translator [1],它为你提供了多种低资源和高资源语言的翻译服务,且免费(有些每月翻译限制)。

在本教程中,我将讲解如何在 Azure 上设置翻译器实例,以及如何在你的代码中编写接口以连接它,并遵循最佳实践。如果你对 Azure 已经很熟悉并且已经设置了翻译器实例,那么可以直接访问项目 代码库 获取代码。

本教程将涵盖:

  1. 设置 Azure 翻译器实例

  2. 发送你的第一次翻译请求

  3. 清理你的代码和结构化你的项目

  4. 使用 Jupyter Notebooks 的注意事项

  5. 结论

在 Azure 上设置翻译器实例

创建 Azure 账户

第一步是创建一个 Microsoft Azure 帐户。这需要你拥有:

  • 一个有效的地址

  • 一个有效的电子邮件账户

  • 一个有效的电话号码

  • 一张有效的信用卡或借记卡*

一旦你创建了账户,你将被询问是否想使用免费服务或按需付费订阅。选择免费服务, 如果你觉得按需付费订阅更适合你,可以随时切换回去。** Azure 会积极尝试让你转到按需付费服务,但你可以始终坚持使用免费服务。

*注意: 信用卡/借记卡用于验证您的身份。如果您使用的是免费套餐,则不会扣取任何费用。

**注意: 要了解翻译服务的定价信息,请访问 翻译服务定价文档

设置翻译器 API

登录到 Azure 后,点击“创建资源”,然后搜索“翻译器”。最后,点击翻译服务并点击创建。

完成后,您会看到一个需要填写多个参数的页面:

  • 资源组: 用于收集属于同一项目的多个资源的名称。这会影响您如果选择非免费订阅时的计费方式。请为其命名一个与您的项目相关的名称。

  • 区域*: 您的实例运行所在的区域。这与微软如何管理资源和灾难 恢复 相关。推荐的区域是全球。

  • 名称: 您的翻译服务的名称。对于翻译目的,这没有影响,但如果您需要文档翻译,它将影响资源端点的名称。

  • 定价层*: 起初选择免费版本

填写完成后,点击创建。Azure 将进行简单验证,并将您带到另一个页面,在那里您可以确认资源的创建。

*注意: 您不能在相同的区域和定价层中拥有多个翻译器实例。例如,如果您在东部美国地区有一个免费套餐实例,则要添加另一个免费实例,您需要更改区域。

查找有关您资源的信息

默认情况下,Azure 会将您带到您创建的资源。但是,下次登录时,您需要自己查找它。您可以通过点击首页上的翻译图标来做到这一点。这将带您到翻译页面,在那里您可以找到所有实例。

点击您的实例将带您到实例页面,在那里您可以找到所有相关的配置和详细信息。这些将在下一节中变得重要。

现在,您可以在浏览器中使用您的翻译实例,以了解输入文本是如何表示的,输出文本的样子:

发送您的第一个翻译请求

默认情况下,Azure 会提供您可以复制的默认代码,以进行第一个翻译请求。然而,如果您对请求的工作原理不熟悉,可能会很难理解它的作用,从而无法在代码中有效使用它。在这里,我将逐步讲解进行第一个翻译请求的相关概念。

HTTPs 请求简要介绍

在编写代码之前,了解几个与翻译 API 相关的概念是很有价值的。

  • API 有一个 URL 允许你访问它。对于 Microsoft Translator,这是一个公开的 URL:

[## Azure Cognitive Services Translator 文档 - 快速入门,教程,API 参考 - Azure…

Azure Cognitive Services Translator 是一个基于云的机器翻译服务,你可以用来翻译文本……

api.cognitive.microsofttranslator.com

  • API 有 端点(这些类似于 URL 中的路径),你可以向其发送 HTTPS 请求。例如,最基本的端点是 languages 端点。这个端点简单地返回你可以选择的所有语言。它是一个 get 端点,因为它“获取”来自 API 或资源的资源或数据。

  • 每个端点都有参数来指定你从端点请求的内容。例如,语言端点有一个参数 api-version,它指示你使用的是哪个版本的翻译器。

例如,使用 Microsoft API 版本 3.0 的语言端点的完整 URI 如下:

你可以在 Python 中使用 requests 模块调用语言端点:

import requests
microsoft_api_url = 'https://api.cognitive.microsofttranslator.com/languages?api-version=3.0'
resp = requests.get(microsoft_api_url)
print(resp.json())

这会向 API 发送一个 HTTPS 请求,以检索翻译器 3.0 版本可用的语言。实际上,因为这个端点是公开的,你可以将那个 URL 复制并粘贴到浏览器*中,以获取与你在代码中得到的相同的输出:

api.cognitive.microsofttranslator.com/languages?api-version=3.0

*注意: 在后台,你的浏览器正在向 URL 发送 get 请求并返回输出。

你可以在官方 API 文档 上找到有关可用端点的更多信息。

翻译端点

这是允许我们翻译文本的端点。与 languages 端点不同,这是一个 post 请求,而不是 get 请求。这意味着你正在发送一些数据以生成输出。你不是单纯地“获取”资源。你将数据作为 request body 的一部分发送。这些是作为请求的一部分传输的数据字节,但它们与参数不同,因为它们不会附加到 URI 路径上。

translate 端点具有以下要求:

参数

  • api-version (必需): 你希望使用的翻译器版本

  • to (必需): ISO 639–1 语言代码,你希望将文本翻译成的语言

请求体

  • 你想要翻译的文本数组,格式如下:{"text": "这是我想要翻译的句子"}

在 Python 中,你可以通过以下方式发送请求。我特意添加了两种翻译语言,以展示如何将多个相同名称的参数添加到请求 URL 中。

import requests
body = [{"text": "First sentence I want to translate"}, {"text": "Second sentence I want to translate"}]
api_version = "3.0"
german_iso_code = 'de'
arabic_iso_code = 'ar'
endpoint = 'translate'

url = f'https://api.cognitive.microsofttranslator.com/{endpoint}?api-version={api_version}&to={german_iso_code}&to={arabic_iso_code}'

resp = requests.post(url, json=body)

管理访问权限

现在,如果你运行了上述代码,你应该会遇到错误。这是因为我们不能仅仅运行 POST 服务。我们需要进行身份验证。

这就是我们最初需要创建账户和翻译器实例的原因。

附加到你的实例上的唯一密钥允许 Microsoft:

a) 验证你发送的请求是否来自拥有 Azure 账户的来源

b) 计算你对服务的使用情况,用于计费或限制目的*

*注意: 记住在免费版本中,虽然你不被收费,但你每月可以进行的翻译次数是有限的。

这个唯一的密钥可以通过使用请求头与 Microsoft 进行通信。这些是 HTTPs 中的关键概念。它们可以告诉服务器以下关于你的请求的信息:

  • IP 地址和端口

  • 预期的数据类型

  • 身份验证详情

翻译器 API 需要以下项目在头部:

  • 订阅密钥: 这是验证你有权使用服务的密钥。它与教程开始时你创建的翻译器资源相关联。

  • 订阅区域: 这是你的项目所在的区域。

  • 内容类型: 正在发送的数据类型

  • 客户端追踪 ID: 唯一标识你的计算机的 ID。你可以在这里阅读更多关于此的信息。

你可以在 Azure 项目页面找到你的订阅密钥:

在“密钥和端点”页面中,你可以找到两个 API 密钥(其中任何一个都可以用来验证你的身份)。

最后,你可以定义头部并将其添加到你上面创建的 POST 请求中,以获得成功的翻译输出:

 # code as before, new additions enclosed in ------

import requests
body = [{"text": "First sentence I want to translate"}, {"text": "Second sentence I want to translate"}]
api_version = "3.0"
german_iso_code = 'de'
arabic_iso_code = 'ar'
endpoint = 'translate'

### -----------------------------------------------------
import uuid

# YOUR PROJECT CREDENTIALS
your_key = "your_key_keep_this_a_secret"
your_project_location = "your_project_location"

# headers
headers = {
  'Ocp-Apim-Subscription-Key': your_key,
  'Ocp-Apim-Subscription-Region': your_project_location,
  # default values
  'Content-type': 'application/json',
  'X-ClientTraceId': str(uuid.uuid4())
}
### -----------------------------------------------------

url = f'https://api.cognitive.microsofttranslator.com/{endpoint}?api-version={api_version}&to={german_iso_code}&to={arabic_iso_code}'

resp = requests.post(
  url,
  headers=headers,  # add the headers
  json=body
)

你的 API 密钥是允许你使用服务的凭证。这些密钥绝不能泄露,建议每隔几个月重新生成一次。下一节我们将讨论减少泄露风险的最佳实践。

清理代码和结构化你的项目

本节将介绍在你的代码和项目中集成 Microsoft Translate API 功能的良好软件开发实践。我们将涵盖:

  • 目录结构

  • 如何隐藏凭证

  • 如何将请求打包成函数并添加基本日志记录

  • 如何添加有用的文档

目录结构

在开发应用程序时,你可能会与多个外部 API 交互。因此,最好将外部 API 的功能存储在单独的文件中,然后在主应用程序代码中调用它们。我建议将所有外部 API 放在一个名为‘external_apis’的子文件夹下,并将包含调用每个 API 函数的 Python 文件分开。我还建议在 external_apis 子文件夹中添加一个config.py文件,以添加外部 API 的配置。

使用环境变量隐藏凭据

记住: 你绝不应该泄露你的 API 密钥。如果泄露了,请立即重新生成它们。

然而,你需要它们来进行翻译请求。一般来说,你应该避免(按严重程度排序):

  • 在代码中硬编码密钥: 即使你私下托管你的代码,密钥仍然会出现在提交历史中。

  • 在任何地方打印你的密钥: 风险较小,但打印语句增加了密钥被推送到 GitHub 作为 Jupyter Notebook 输出的一部分或存储在服务器日志中的可能性。

  • 将你的密钥保存在配置文件中: 风险更小,因为意外推送配置文件的可能性较低,而.gitignore可以使其几乎不可能。不过,仍然有更好的方法。

使用环境变量来管理代码中的凭据是最佳方法。这些是基于会话的变量,意味着它们仅在你运行代码的终端会话期间保存,从而大大减少人为错误。

为此,我们可以使用config.py文件:

import os

MICROSOFT_TRANSLATE_API_KEY = os.environ.get('MICROSOFT_TRANSLATE_API_KEY', 'default_key')

这样,默认情况下我们的密钥取值为“default_key”。在运行任何使用该密钥的代码之前,我们需要在终端中显式设置它:

python -c "from package_name.external_apis.config import MICROSOFT_TRANSLATE_API_KEY; print(MICROSOFT_TRANSLATE_API_KEY)"

export MICROSOFT_TRANSLATE_API_KEY="your_actual_key"

python -c "from package_name.external_apis.config import MICROSOFT_TRANSLATE_API_KEY; print(MICROSOFT_TRANSLATE_API_KEY)"

如果你想要更加谨慎,可以对 API 密钥添加额外的抽象层,使其难以意外提取其值。例如,你可以创建一个Password类,将密码存储为隐藏变量,然后添加一个显式的“get_password”方法:

import os

class Password:
  def __init__(self, password):
    self.__password = password

  def get_password():
    return self.__password

MICROSOFT_TRANSLATE_API_KEY_CLASS = Password(os.environ.get('MICROSOFT_TRANSLATE_API_KEY', 'default_key'))

print(MICROSOFT_TRANSLATE_API_KEY_CLASS.get_password())  # prints password
print(MICROSOFT_TRANSLATE_API_KEY_CLASS.password)  # error
print(MICROSOFT_TRANSLATE_API_KEY_CLASS.__password)  # error

这样,在定义请求的headers时,你调用get_password方法。

将代码打包成函数并添加日志记录

现在我们了解了基础知识,可以进行一些改进:

  • **config.py** 文件中添加所有 Microsoft Translator API 的标识符
"""
config.py file
"""
import os

# MICROSOFT API CONFIGS
MICROSOFT_TRANSLATE_URL = 'https://api.cognitive.microsofttranslator.com'
MICROSOFT_TRANSLATE_LOCATION = os.environ.get('MICROSOFT_TRANSLATE_LOCATION', 'default_location')
MICROSOFT_TRANSLATE_API_KEY = os.environ.get('MICROSOFT_TRANSLATE_API_KEY', 'default_key')

这里我们还添加了你实例的位置作为环境变量。

  • 为每个端点添加单独的函数
"""
microsoft.py file
"""

import uuid
from package_name.external_apis.config import (
  MICROSOFT_TRANSLATE_URL,
  MICROSOFT_TRANSLATE_LOCATION,
  MICROSOFT_TRANSLATE_API_KEY
)

# -- prepare headers
HEADERS = {
  'Ocp-Apim-Subscription-Key': MICROSOFT_TRANSLATE_API_KEY,
  'Ocp-Apim-Subscription-Region': MICROSOFT_TRANSLATE_LOCATION,
  'Content-type': 'application/json',
  'X-ClientTraceId': str(uuid.uuid4())
}

# -- utils
def _is_response_valid(status_code):
    if str(status_code).startswith('2'):
        return True

# -- functions for endpoints

# /languages endpoint
def get_languages(api_version='3.0'):

    # prepare url
    url = f'{MICROSOFT_TRANSLATE_URL}/languages?api-version={api_version}'

    # send request and process outputs
    resp = requests.get(url)
    status_code = resp.status_code
    if _is_response_valid(status_code):
        return resp.json(), status_code

    return resp.text, status_code

# /translate endpoint
def translate_text(text, target_language, source_language=None, api_version='3.0'):

    # send request and process outputs
    url = f'{MICROSOFT_TRANSLATE_URL}/translate?api-version={api_version}'

    # standardise target language type
    if isinstance(target_language, str):
        target_language = [target_language]

    # dynamically add array parameter to url
    for lang in target_language:
        url = f'{url}&to={lang}'

    if source_language:
        url = f'{url}&from={source_language}'

    # standardise text type
    if isinstance(text, str):
        text = [text]

    # dynamically build the request body
    body = [{'text': text_} for text_ in text]

    # send request and process outputs
    resp = requests.post(url, headers=HEADERS, json=body)
    status_code = resp.status_code

    if _is_response_valid(status_code)
        return resp.json(), status_code

    return resp.text, status_code
  • 使用 **typing** **sphinx** 风格的文档字符串添加日志记录和文档
"""
microsoft.py file
"""

import uuid
import logging
from package_name.external_apis.config import (
  MICROSOFT_TRANSLATE_URL,
  MICROSOFT_TRANSLATE_LOCATION,
  MICROSOFT_TRANSLATE_API_KEY
)

# imports for typing annotations
from typing import Optional, Union, List

# -- configure logger. Taken from official python docs
LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
date_format = '%Y-%m-%d %H:%M:%S'
formatter = logging.Formatter('%(asctime)s:%(name)s:%(levelname)s:%(message)s', datefmt=date_format)
ch.setFormatter(formatter)
LOGGER.addHandler(ch)

# -- prepare headers
HEADERS = {
  'Ocp-Apim-Subscription-Key': MICROSOFT_TRANSLATE_API_KEY,
  'Ocp-Apim-Subscription-Region': MICROSOFT_TRANSLATE_LOCATION,
  'Content-type': 'application/json',
  'X-ClientTraceId': str(uuid.uuid4())
}

# -- utils
def _is_response_valid(status_code: int) -> Optional[bool]:
    """ Function to check response is valid or not

    :param status_code: status code from response
    :returns: True if valid response, None otherwise
    """
    if str(status_code).startswith('2'):
        return True

# -- functions for endpoints

# /languages endpoint
def get_languages(api_version: str = '3.0') -> tuple:
    """ get languages available from API for specific version

    :param api_version: version of API to use
    :returns: (available languages, status_code)

    """
    # prepare url
    url = f'{MICROSOFT_TRANSLATE_URL}/languages?api-version={api_version}'

    # send request and process outputs
    LOGGER.info(f'Getting languages available on api_version={api_version}')
    resp = requests.get(url)
    status_code = resp.status_code
    if _is_response_valid(status_code):
        return resp.json(), status_code

    LOGGER.error('Failed to get languages')
    return resp.text, status_code

# /translate endpoint
def translate_text(text: Union[str, List[str]], target_language: Union[str, List[str]], source_language: Optional[str] = None, api_version: str = '3.0') -> tuple:
    """translates txt using the microsoft translate API

    :param text: text to be translated. Either single or multiple (stored in a list)
    :param target_language: ISO format of target translation languages
    :param source_language: ISO format of source language. If not provided is inferred by the translator, defaults to None
    :param api_version: api version to use, defaults to "3.0"
    :return: for successful response, (status_code, [{"translations": [{"text": translated_text_1, "to": lang_1}, ...]}, ...]))        
    """
    # send request and process outputs
    url = f'{MICROSOFT_TRANSLATE_URL}/translate?api-version={api_version}'

    # standardise target language type
    if isinstance(target_language, str):
        target_language = [target_language]

    # dynamically add array parameter to url
    for lang in target_language:
        url = f'{url}&to={lang}'

    if source_language:
        url = f'{url}&from={source_language}'

    # standardise text type
    if isinstance(text, str):
        text = [text]

    # dynamically build the request body
    body = [{'text': text_} for text_ in text]

    LOGGER.info(f'Translating {len(text)} texts to {len(target_language)} languages')
    # send request and process outputs
    resp = requests.post(url, headers=HEADERS, json=body)
    status_code = resp.status_code

    if _is_response_valid(status_code)
        return resp.json(), status_code
    LOGGER.error('Failed to translate texts')
    return resp.text, status_code

使用 Jupyter Notebook 的注意事项

使用 Jupyter Notebook 时,仅在终端设置环境变量是不够的,因为 Jupyter 默认无法看到它们。相反,以下是我推荐的做法:

  • 在终端设置环境变量时,附加“_jupyter”,然后运行jupyter notebook
export MICROSOFT_API_CREDENTIALS_JUPYTER='my_key'
jupyter notebook
  • 使用 dot_env 包(你可能需要通过 pip 安装)来通过读取“_jupyter”环境变量来设置正确的环境变量。添加 %%capture 魔法命令以确保环境变量不会被打印。
%%capture
import os
import json
from dotenv import load_dotenv
load_dotenv() # loads key values pairs into env
MICROSOFT_TRANSLATE_API_KEY = os.environ.get('MICROSOFT_TRANSLATE_API_KEY_JUPYTER')
%set_env MICROSOFT_TRANSLATE_API_KEY=$MICROSOFT_TRANSLATE_API_KEY

你现在应该能够在 Jupyter Notebooks 中用 Microsoft 认证你的请求。

结论

在本文中,我们详细讲解了如何在 Azure 上设置 Microsoft Translator 实例,并将其集成到项目中,使用最佳实践。

值得一提的是,虽然免费版非常好,但它有资源限制(每月 200 万字符)。虽然看起来很多,但很快就会用完。我最近在一个项目中使用 Translate API 进行数据增强时遇到了这个问题。此外,每个翻译请求有 50000 字符的限制,这意味着在翻译较大文本时需要非常小心。请求计算方式为:总字符数 * 语言数。所以在处理较大文本时,分语言或按语言批量翻译更为合理。

我将发布一个关于使用 Microsoft API 的高级指南,其中会介绍用于自动批处理文本的函数,以便你能充分利用最大字符限制。在此之前,你可以在这里找到本文的代码:

## ml-utils/microsoft.py at develop · namiyousef/ml-utils

有用的 ML 工具函数。通过在 GitHub 上创建一个账户来贡献于 namiyousef/ml-utils 的开发。

github.com

作者注释

如果你喜欢这篇文章或学到了新东西,请考虑通过我的推荐链接获取会员:

## 加入 Medium 使用我的推荐链接 — Yousef Nami

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

namiyousef96.medium.com

这将使你可以无限制地访问 Medium,同时帮助我在不额外增加你的费用的情况下制作更多内容。

参考文献

[1] Microsoft Translator. 可从 www.google.com/search?q=microsoft+translator&oq=microsoft+translator&aqs=chrome.0.35i39j69i59l2j0i512l2j69i60l3.2307j0j7&sourceid=chrome&ie=UTF-8 获取

所有图片由作者提供,除非另有说明

如何解读线性回归系数 | 完整指南

原文:towardsdatascience.com/how-to-interpret-linear-regression-coefficients-8f4450e38001

从简单到高级模型的完整指南

Quentin Gallea, PhDTowards Data Science Quentin Gallea, PhD

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

--

由 Vitalii Khodzinskyi 拍摄,来自 Unsplash

在线查找如何解读线性回归系数类似于查找如何在 Python 中导入 CSV 文件,许多人能在脑海中掌握这些信息。尽管我在过去十年中教授了超过 10,000 名学生统计学,但在一些特殊情况下(例如二元结果和对数变换的解释变量),我仍然有时需要反复检查解释。这就是为什么我决定写这篇文章,它包含了各种线性回归模型的大量列表,并解释了每种情况的系数解释,包括对数变换变量、二元变量或交互项。

请注意,为了全面理解(但不必要)本文内容,你需要熟悉两个数学概念:偏导数条件期望

在浏览不同情况的列表之前,让我介绍一些重要的定义和考虑事项(模型定义、ceteris paribus、二元变量、多重共线性)。

目录:

0. 定义和重要考虑事项

1. 截距

2. 连续因变量

2.1 连续自变量

2.1.a lin-lin: 线性结果,线性自变量

2.1.b log-lin: 对数变换的结果,线性自变量

2.1.c lin-log: 线性结果,对数变换的自变量

2.1.d log-log: 对数变换的结果,对数变换的自变量

2.2 二元自变量

2.2.a 线性因变量

2.2.b 对数变换因变量

3. 二元因变量

3.1 连续自变量

3.1.a 线性自变量

3.1.b 对数变换自变量

3.2 二元自变量

4. 互动效应

4.1 二次效应

4.2 两个连续变量之间的互动

4.3 两个二元变量之间的互动

特殊情况差异中的差异

0. 定义及重要考虑事项

一般定义

模型的定义:

Yᵢ = β₀ + β₁ Xᵢ + Z’ λ + ϵᵢ

Yᵢ 是因变量,Xᵢ 是一个独立的连续变量,Z 是控制变量的向量,ϵᵢ 是误差项。

从分析角度来看,β₀ 是函数的截距,β₁ 是斜率参数。因此,β₁ 代表 X 单位增加后 Y 的变化,其它条件不变(保持 Z 的值固定)。

形式上,我们可以写作 β₁ = ∂ Y / ∂ X(Y 对 X 的偏导数)。

其它条件相同

在多重线性回归中,我们使用术语 ceteris paribus,即“其它条件相同”。这是上述定义的直接结果。偏导数衡量由于一个变量变化而函数的变化,其它变量保持不变。因此,在上述模型中,β₁ 代表 X 对 Y 的变化影响,同时保持控制变量 Z 向量中的所有其它内容固定。

二元变量

因变量或自变量可以是二元变量,即取值为 1 或 0 的变量。

自变量是二元变量时,系数应解释为期望的差异。假设 Dᵢ 是一个二元变量,当数据集中的人是成年人(年龄≥21)时取值为 1,否则取值为 0。模型为 Yᵢ = β₀ + β₁ Dᵢ + Z’ λ + ϵᵢ。根据上述系数解释的一般定义(偏导数),这里的一个单位变化意味着从儿童到成人的变化。因此,系数应解释为儿童与成人之间 Y 的‘平均’差异。形式上,β₁ = E[Y |D=1,Z] - E[Y |D=0,Z]。换句话说,β₁ 表示成人的 Y 的‘平均’值与儿童的 Y 的平均值之间的差异。

因变量是二元变量时,我们处于线性概率模型(LPM)的情况下。回归系数表示因变量等于 1 的概率变化。LPMs 通常被忽视,主要是因为这些模型可能会预测负概率或大于 1 的概率。为避免这个问题,我们应该依赖 logit(逻辑回归)或 probit 模型。然而,LPMs 仍然经常被使用,因为它们的系数易于解释(见第三部分)。特别是,对于固定效应,这在计量经济学中被广泛使用,LPMs 更“合适”(参见:使用大面板数据估计固定效应 Logit 模型)。

完美的多重共线性

如果两个或更多的独立变量可以表示为彼此的确切线性关系,我们就遇到了完美的多重共线性问题。(从技术上讲,解释变量的矩阵将不是满秩的,因此不能求逆,而这对于估计回归参数至关重要。)对于二元变量和分类变量,这一点非常重要。你不能包含捕捉所有不同可能类别的二元变量,否则你会遇到完美的多重共线性问题。例如,在同一个回归模型中,你不能同时包含成人和非成人的二元变量。实际上,这并不是一个问题,因为与该变量(这里是成人)相关的回归系数表示与参考类别(这里是非成人)之间的差异。因此,同时存在这两个变量并没有用。

现在,如果你有两个以上的类别,例如轻度、中度和重度,你不能为所有类别设置一个二元变量。你必须排除其中一个类别。

示例:

Yᵢ = β₀ + β1₁ Mediumᵢ + β₂ Heavy + Z’ λ + ϵᵢ

在这里我们排除了轻度类别。因此,β₀实际上表示属于轻度类别的观测值的 Y 期望。被排除的类别将成为参考类别,这意味着系数将始终表示与此类别相比的期望差异:

β₁ = E[Y|Medium=1,Z]-E[Y |Light=1,Z]

β₂ = E[Y|Heavy=1,Z]-E[Y |Light=1,Z]

对数转换

我们为什么要进行对数转换?

注意,对数转换通常用于线性回归。线性回归衡量的是平均效应,因此当一个变量在右侧高度偏斜时,常见的方法是应用自然对数进行转换。这种策略旨在减少偏斜,从而允许使用均值。我的个人规则是,如果偏斜度(偏斜度的度量)大于 3,我会对变量进行对数转换。

我在对数转换中应该注意什么?

当一个变量被转换为对数时,系数的解释会发生变化。这不一定是坏事,甚至可能是有益的。当变量经过对数变换时,线性回归系数可以解释为弹性或半弹性。正如我们稍后将看到的,这意味着我们不再关注变量的单位变化,而是关注百分比变化。在没有对数变换的水平回归中,回归系数对应于偏导数(∂ Y / ∂ X),X 的单位变化意味着 Y 的变化为 β₁ 单位(其中 Y 为因变量,X 为自变量,β₁ 为与 X 相关的回归系数)。当两个变量(因变量和自变量)都经过对数变换时,我们将回归系数大致解释为弹性:X 的 1% 变化意味着 Y 的 β₁% 变化。

在进行变换前后,应进行仔细的探索性数据分析(EDA)以做出正确的决策。你可以参考我的 EDA 方法:medium.com/towards-data-science/a-recipe-to-empirically-answer-any-question-quickly-22e48c867dd5。此外,请参阅对数变换及其对数据分析的影响(Feng et al.(2002))了解更多潜在问题的细节。

1. 截距

模型定义:

Yᵢ = β₀ + β₁ Xᵢ + Z’ λ+ ϵᵢ

Y 是因变量,X 是自变量,Z 是控制变量的向量,ϵᵢ 是误差项。

解释: β₀ 是 Y 的期望值,如果所有其他变量设置为 0。注意,如果解释变量从不等于零(例如身高、GDP 等),则解释此系数没有意义。

2. 连续因变量

在本节 2 中,因变量 Yᵢ 始终是连续的。

2.1 连续自变量

在本小节 2.1 中,自变量 Xᵢ 始终是连续的。

2.1.a 水平-水平:水平结果,水平自变量

模型定义:

Yᵢ = β₀ + β₁ Xᵢ + Z’ λ+ ϵᵢ

Y 是因变量,X 是自变量,ϵ 是误差项。

解释: X 增加一个单位,Yᵢ 平均变化 β₁ 单位(其他条件不变)。

2.1.b 对数-水平:对数变换的结果,水平自变量

模型定义:

log(Yᵢ) = β₀ + β₁ Xᵢ + Z’ λ+ ϵᵢ

log(Yᵢ) 是对数变换后的因变量,Xᵢ 是自变量,ϵᵢ 是误差项。

解释: X 增加一个单位意味着 Y 平均变化 (exp(β₁)-1)100 百分比(在其他条件不变的情况下)。为了快速近似,你可以将系数解释为半弹性:X 增加一个单位意味着 Y 平均变化 100β₁ %(在其他条件不变的情况下)。

2.1.c 水平-对数:水平结果,对数变换的独立变量

模型定义:

Yᵢ = β₀ + β₁ log(Xᵢ) + Z’ λ+ ϵᵢ

Yᵢ 是因变量,log(Xᵢ) 是对数变换的独立变量,ϵᵢ 是误差项。

解释: X 增加一个百分比意味着 Y 平均变化 β₁*log(1.01)(在其他条件不变的情况下)。为了快速近似,你可以将系数解释为半弹性:X 增加一个百分比意味着 Y 平均变化 β₁ / 100 单位(在其他条件不变的情况下)。

2.1.d 对数-对数:对数变换的结果,对数变换的独立变量

模型定义:

log(Yᵢ) = β₀ + β₁ log(Xᵢ) + Z’ λ+ ϵᵢ

log(Yᵢ) 是对数变换的因变量,log(Xᵢ) 是对数变换的独立变量,ϵᵢ 是误差项。

解释: X 增加一个百分比意味着 Yᵢ 平均变化 (1.01^β₁–1) * 100 百分比(在其他条件不变的情况下)。为了快速近似,你可以将系数解释为弹性:X 增加一个百分比意味着 Y 平均变化 β₁ 百分比(在其他条件不变的情况下)。

2.2 二元独立变量

在第 2.2 节中,独立变量 Dᵢ 是一个仅取值 1 或 0 的二元变量。

2.2.a 水平因变量

模型定义:

Yᵢ = β₀ + β₁ Dᵢ+ Z’ λ+ ϵᵢ

Yᵢ 是因变量,Dᵢ 是独立二元变量,Z 是控制变量向量,ϵᵢ 是误差项。

解释: 记住,正式来说 β₁ = E[Yᵢ |Dᵢ=1,Z]-E[Yᵢ |Dᵢ=0,Z]。换句话说,当 Dᵢ 从 0 变为 1 时的期望差异等于 β₁,其他条件相等。

为了使其更具体,让我使用以下示例:

HoursOfSleepᵢ = β₀ + β₁ Adultᵢ+ Z’ λ+ ϵᵢ。

在这个例子中,β₁ 代表成年人(当 Adult = 1)与非成年人(即儿童,当 Adult = 0)之间的“平均”睡眠小时差异,其他条件相等。

2.2.b 对数变换的因变量

模型定义:

log(Yᵢ) = β₀ + β₁ Dᵢ+ Z’ λ+ ϵᵢ

log(Yᵢ) 是对数变换的因变量,Dᵢ 是独立二元变量,Z 是控制变量向量,ϵᵢ 是误差项。

解释: 记住,二元变量的系数表示“均值”(条件期望)的差异。然而,由于对数变换,我们有:

β₁ = log(E[Yᵢ |Dᵢ=1,Z]) — log(E[Yᵢ |Dᵢ=0,Z]) = log(E[Yᵢ |Dᵢ=1,Z]/E[Yᵢ |Dᵢ=0,Z])

⇔ exp(β₁) = E[Yᵢ |Dᵢ=1,Z]/E[Yᵢ |Dᵢ=0,Z]

为了更具体地说明,我使用以下示例:log(睡眠时间ᵢ) = β₀ + β₁ 成人ᵢ+ Z’ λ+ ϵᵢ。在这个例子中,exp(β₁) 代表成人(当 成人ᵢ = 1)相对于非成人(即儿童,当 成人ᵢ = 0)的平均睡眠时间的比率,其他条件相等。如果 exp(β₁) = 1.1,这意味着成人的睡眠时间比儿童多 10%。如果 exp(β₁) = 1.5,这意味着成人的睡眠时间比儿童多 50%。

请注意,在这个上下文中,均值是几何均值(更多细节请参见:stats.oarc.ucla.edu/other/mult-pkg/faq/general/faqhow-do-i-interpret-a-regression-model-when-some-variables-are-log-transformed/)。

3. 二元因变量

在第三部分中,因变量 Dᵢ 始终是二元的(取值为 1 或 0)。在这种情况下,模型称为线性概率模型(有关更多细节,请参见第零部分的注释)。

3.1 连续独立变量

在这一小节 3.1 中,独立变量 Xᵢ 是连续变量。

3.1.a 水平独立变量

模型定义:

Dᵢ = β₀ + β₁ Xᵢ + Z’ λ+ ϵᵢ

Dᵢ 是二元因变量,Xᵢ 是独立的连续变量,Z 是控制变量的向量,ϵᵢ 是误差项。

解释: X 增加一个单位意味着 β₁ 平均上改变 D = 1 的概率(其他条件不变)。例如,如果 β₁=0.1,这意味着 D 等于 1 的概率平均增加 0.1(其他条件不变)。

3.1.b 对数变换的独立变量

模型定义:

Dᵢ = β₀ + β₁ log(Xᵢ) + Z’ λ+ ϵᵢ

Dᵢ 是二元因变量,log(Xᵢ) 是对数变换的独立连续变量,Z 是控制变量的向量,ϵᵢ 是误差项。

解释: X 增加一个百分点意味着 D = 1 的概率平均变化 β₁ 单位(其他条件不变)。例如,如果 β₁=0.1,这意味着 X 增加一个百分点后,D 等于 1 的概率平均增加 0.1(其他条件不变)。

3.2 二元独立变量

模型定义:

Dᵢ = β₀ + β₁ Bᵢ + Z’ λ+ ϵᵢ

Dᵢ 是二元因变量,Bᵢ 是独立的二元变量,Z 是控制变量的向量,ϵᵢ 是误差项。

解释: 当 B 从 0 变化到 1 时,D = 1 的概率变化为 β₁,其他条件相等。例如,如果 β₁ = 0.1,这意味着当 B 从 0 变化到 1 时,D 等于 1 的概率平均增加 0.1(其他条件不变)。

为了更具体地说明,我使用以下示例:

失眠ᵢ = β₀ + β₁ 成人ᵢ + Z’ λ+ ϵᵢ。

失眠是一个二元变量,如果个体 “i” 患有失眠则取值为 1,否则为 0。成年人是一个二元变量,如果个体 “i” 严格超过 20 岁则取值为 1,否则为 0。在这个例子中,如果 β₁ = 0.1,这意味着成年人(成年人 = 1)患失眠的概率比儿童(成年人 = 0)高 0.1,其他条件相同。

4. 互动效应

线性回归在参数上是线性的,这并不妨碍非线性函数的估计。我们将看到三种不同类型的交互作用。为了简洁起见,我在本节中只使用一个连续因变量:Y。

4.1 二次效应

模型定义:

Yᵢ = β₀ + β₁ Xᵢ + β₂ Xᵢ * Xᵢ + Z’ λ + ϵᵢ

Yᵢ = β₀ + β₁ Xᵢ + β₂ Xᵢ² + Z’ λ + ϵᵢ

Yᵢ 是一个连续的因变量,Xᵢ 是一个独立的连续变量,Z 是一个控制变量的向量,ϵᵢ 是一个误差项。这个模型包含了 X 的二阶多项式函数。线性回归可以包括更高阶的多项式函数。

解释: 多项式形式的解释更为复杂,因为偏导数(边际效应)不再是常数。在当前情况下,∂ Y / ∂ X = β₁ + 2 * β₂ Xᵢ。由于边际效应依赖于 X 的值,我们必须对 X 的不同有意义的值进行边际效应的评估。

为此,我计算并绘制了 X 对 Y 的边际效应图,涵盖了 X 的不同值。在 STATA 中,你可以使用 marginsmarginsplot 命令,在 R 中你可以使用 marginaleffects,而在 Python 中我使用以下代码:

上面的图表揭示了 X 和 Y 之间的二次关系。因此,X 对 Y 的边际效应最初为负,然后变为正。下面的代码允许我们计算这种特定情况下的边际效应。注意,我手动计算了这个二次多项式函数的导数,如你从第一行代码中可以看到的那样。

从上面的图表中可以看出,对于大约低于 -1 的值,边际效应为负,然后变为正值。

4.2 两个连续变量之间的交互作用

在某些情况下,我们期望两个不同的变量之间存在相互作用。在我最近与同事 Pr.Rohner 共同发表在 PNAS 上的一篇文章中,我们探讨了控制海上贸易的战略位置、贸易开放性和冲突之间的关系(www.pnas.org/doi/abs/10.1073/pnas.2105624118?doi=10.1073%2Fpnas.2105624118)。贸易开放性和位置的战略价值对冲突的概率有直接影响。但也存在一个重要的联合效应(交互作用),如下面的一系列图表所示。

作者提供的图片。该图揭示了接近主要战略位置以控制海洋贸易路线(X 轴)对冲突概率(Y 轴)的影响。观察值按接近度的四分位数(q1, q2, q3 和 q4)进行划分,q4 表示距离战略位置最近的区域。

上图揭示了距离战略位置越近,冲突风险越高。

作者提供的图片。该图揭示了贸易开放度(X 轴)对冲突概率(Y 轴)的关系。观察值按贸易开放度的四分位数(q1, q2, q3 和 q4)进行划分。

第二张图显示了贸易开放度越大,冲突风险越高。因此,贸易繁荣的年份有更高的冲突风险。

作者提供的图片。这张条形图表示了距离战略位置的四分位数(near_dist_q)、年度贸易开放度(tradew_q)和冲突概率(Y 轴)之间的关系。

最后一张图显示了我们两个解释变量的互动。一方面,我们可以看到左侧第一组条形图中,远离战略位置的海洋贸易地点在贸易扩张期(黄色条)冲突风险更高。另一方面,对于靠近战略位置的地点(右侧的条形图组),这种关系则被逆转。

因此,距离战略位置的边际效应在冲突概率上会随着贸易开放度的变化而变化。为了建模这种效应,我们必须使用交互效应。

模型定义:

Yᵢ = β₀ + β₁ Xᵢ + β₂ Zᵢ + β₃ Zᵢ*Xᵢ + ϵᵢ

Yᵢ是一个连续的因变量,Xᵢ和 Zᵢ是独立的连续变量,ϵᵢ是误差项。需要特别注意的是,当我们有交互效应时,必须在模型中包含每个“主”项。例如,如果我们想要 X 和 Z 之间的交互作用,我们还必须在回归中单独包含 X 和 Z。

解释: 要理解如何解释这种效应,我们必须回到边际效应的定义,即偏导数。在我们的案例中,有两个不同的偏导数包括β₃。

∂ Y / ∂ X = β₁ + β₃ Zᵢ

∂ Y / ∂ Z = β₁ + β₃ Xᵢ

因此,在这种情况下,与多项式情况一样,我们必须评估 Z 或 X 的一组有意义的值的边际效应,因为边际效应是这些变量的函数。为此,我在 STATA 中使用命令marginsmarginsplot,在 R 中使用marginaleffects,在 Python 中使用以下代码:

上图绘制了回归平面(不再是回归线,因为 Y 是 X 和 Z 的函数)。

以下代码将允许我们绘制 X 对 Y 的边际效应作为 Z 值的函数。

从上图中我们可以看到,当 z 大约小于-1 时,x 对 y 的边际效应是负的,而对于更大的值则变为正的。

4.3 两个二元变量之间的交互

与前一部分一样,我们有时会遇到相互作用的二元变量。一个著名的例子是工资歧视。让我们设想一个相关模型来计算男性和女性、白人和非白人之间的平均工资差异。在这个模型中,如果我们想测试针对非白人女性的工资歧视是否不同(可能更大)于对女性和非白人的歧视之和,则重要的是要包含一个交互项。

模型:

Wageᵢ = β₀ + β₁ Womanᵢ + β₂ NonWhiteᵢ + β₃ Womanᵢ * NonWhiteᵢ + ϵᵢ

Wageᵢ 是每小时工资,Womanᵢ 是一个二元变量,当个体“i”是女性时取值为 1,NonWhiteᵢ 是一个二元变量,当个体“i”是非白人时取值为 1,ϵᵢ 是一个误差项。

需要特别注意的是,当我们有交互效应时,我们还必须在模型中包含每一个“主”项。例如,如果我们想要得到 X 和 Z 之间的交互效应,我们还必须在回归中单独包含 X 和 Z(这里我们包含了交互项,但也分别包含了 Woman 和 NonWhite)。

解释: 首先,让我们解释主要效果:

β₁ = E[Wageᵢ | Womanᵢ = 1, NonWhiteᵢ = 0] - E[Wageᵢ | Womanᵢ = 0, NonWhiteᵢ = 0]

β₁ 捕捉了白人女性和白人男性之间的平均工资差异。请注意,其他项为 0,因为 NonWhite 设置为 0(包括交互项)。

β₂ = E[Wageᵢ | NonWhiteᵢ = 1, Womanᵢ = 0] — E[Wageᵢ | NonWhiteᵢ = 0, Womanᵢ = 0]

β₂ 捕捉了非白人男性和非白人男性之间的平均工资差异。请注意,其他项为 0,因为 Woman 设置为 0(包括交互项)。

β₃ =

(E[Wageᵢ | NonWhiteᵢ = 1, Womanᵢ = 1] — E[Wageᵢ | NonWhiteᵢ = 0, Womanᵢ = 1])

(E[Wageᵢ | NonWhiteᵢ = 1, Womanᵢ = 0] — E[Wageᵢ | NonWhiteᵢ = 0, Womanᵢ = 0])

最后,β₃ 是作为非白人和女性的额外工资惩罚(假设系数为负)。

特殊情况:差异中的差异(Difference-in-Difference)

另一种常见的情况是我们使用二元变量的交互作用。一种在计量经济学中广泛使用的准实验技术叫做差异中的差异。这种策略旨在衡量政策的因果效应。例如。然而,如何在这种情况下评估因果关系的讨论超出了本文的范围(见 Scott Cuningham 的免费电子书:“因果推断:混合录音带”)。

让我讨论一个 Diff-in-Diff 模型的基本假设例子。在 2008 年,英国实施了一项减少二氧化碳排放的政策,而爱尔兰不受此政策影响。为了评估该政策对污染的影响,我们可以设置以下模型:

CO²人均排放ᵢₜ = β₀ + β₁ UKᵢₜ + β₂ Postᵢₜ + β₃ UKᵢₜ * Postᵢₜ + ϵᵢₜ

i 和 t 分别是国家和年份的指标,CO²人均排放ᵢₜ 是不言而喻的,UKᵢ 是一个二元变量,如果观察 i 是英国,则取值为 1,Postᵢ 是一个二元变量,如果观察 i 是在政策实施后(2008 年后)测量的,则取值为 1,ϵᵢ 是误差项。

解释:

首先,让我们解释主要效果。β₁是整个期间内英国和爱尔兰之间的人均 CO2 排放差异的平均值。β₂是 2008 年后(Post)和之前(Pre)期间的人均 CO2 排放差异的平均值(两个国家一起计算)。在这种设置中,重要的系数是β₃。

β₃ =

(E[CO²人均排放ᵢₜ| Postᵢ = 1, UKᵢ = 1] —

E[CO²人均排放ᵢₜ | Postᵢ = 0, UKᵢ = 1])

(E[CO²人均排放ᵢₜ| Postᵢ = 1, UKᵢ = 0] —

E[CO²人均排放ᵢₜ | Postᵢ = 0, UKᵢ = 0])

β₃ 实际上是一个双重差分。它表示政策实施后英国 CO²排放的变化与爱尔兰(一个没有实施政策的国家)CO²排放变化的比较。因此,假设爱尔兰是一个良好的对照,β₃捕捉到的额外差异代表了政策的效果。

如何解释逻辑回归系数

原文:towardsdatascience.com/how-to-interpret-logistic-regression-coefficients-db9381379ab3

计算逻辑回归系数的均值边际效应

Jarom HuletTowards Data Science Jarom Hulet

·发表于 Towards Data Science ·阅读时间 10 分钟·2023 年 8 月 24 日

--

图片由 Dominika Roseclay 提供,来源于 Pexels.com

你喜欢逻辑回归,却讨厌解释任何形式的对数变换吗?虽然我不能说你和你在一起的人很多,但我可以说你有作伴!

在这篇文章中,我将全面讨论解释逻辑回归系数——以下是大纲:

  1. 解释线性回归系数

  2. 为什么逻辑回归系数的解释具有挑战性

  3. 如何解释逻辑回归系数

  4. 使用statsmodels包计算均值边际效应

  5. 结论

解释线性回归系数

大多数对统计学有基础知识的人都能完全理解线性回归中系数的解释。如果你属于这种情况,可以考虑跳过文章中讨论逻辑回归系数的部分。

解释线性回归系数非常简单易懂。解释的简单性是线性回归仍然非常受欢迎的原因之一,尽管出现了许多更复杂的算法。

简单线性回归(一个输入变量的线性回归)呈现如下形式:

我们主要关注的是解释B₁。对于线性回归,这种解释很简单——对于x的一个单位变化,我们预期y会发生B₁的变化。这个关系的另一种说法是“均值边际效应”。

让我们通过模拟来看看如何解释B₁。模拟是测试数据科学工具/方法的绝佳工具,因为我们设定基准真相,然后看看我们的方法是否能够识别它。

在下面的代码中,我们模拟了 30,000 行x值。我们通过从正态分布中采样来模拟x值,参数由我们选择(在此例中均值为 2,标准差为 0.2)。然后,我们通过将x乘以我们模拟的影响 0.16 来模拟y,接着添加随机误差;也使用来自正态分布的采样(均值=0,标准差=2)。

from sklearn.linear_model import LinearRegression
import pandas as pd
import numpy as np

# simulate linear regression data
def regression_simulation(sim_var, sim_error, sim_coef, size):

    '''
        Simulates data for simple linear regression.

        inputs:
            sim_var (list)    : 2-element list, first element is the mean of a random variable
                                that is being used to simulate a feature in the linear regression, 
                                second is the standard deviation
            sim_error (list)   : 2-element list, first element is the mean of random error being added,
                                 second is the standard deviation
            sim_coef (float)   : impact of the random variable established by sim_var on the target 
                                 variable
            size (int)         : number of units to simulate

        output:
            sim_df (DataFrame) : dataframe with simulated data

    '''

    # create an empty dataframe to populate
    sim_df = pd.DataFrame()

    # create the feature for the linear regression
    sim_df['var'] = np.random.normal(sim_var[0], sim_var[1], size = size)

    # multiply feature by the coef to get a simulated impact
    sim_df['var_impact'] = sim_df['var']*sim_coef

    # create error for the linear regression
    sim_error = np.random.normal(sim_error[0], sim_error[1], size = size)

    # create the target variable
    sim_df['target'] = sim_df['var_impact'] + sim_error

    return sim_df

linear_regression_sim_df = regression_simulation(sim_var = [2, 0.2], 
                                                 sim_error = [0, 2],
                                                 sim_coef = 0.16,
                                                 size = 30000)

lin_reg = LinearRegression()
X = np.array(linear_regression_sim_df['var']).reshape(-1, 1)
y = linear_regression_sim_df['target']
lin_reg.fit(X, y)

这是我们打印线性回归创建的系数时看到的结果:

print(lin_reg.coef_)

很好!这与 0.16 非常接近。如果我们想确保我们的系数估计是无偏的,我们可以多次模拟数据集并查看分布情况。

# run multiple simulations
iters = 1000
ceof_list = []

for i in range(iters):

    reg_sim_df = regression_simulation([2, 0.2], [0, 0.1], 0.16, 5000)

    lin_reg = LinearRegression()
    X = np.array(reg_sim_df['var']).reshape(-1, 1)
    y = reg_sim_df['target']
    lin_reg.fit(X, y)

    coef = lin_reg.coef_[0]

    ceof_list.append(coef)

plt.hist(ceof_list, bins = 20)

从直方图中我们可以看到,分布中心在 0.16 左右,这意味着我们的系数估计是无偏的,这相当酷!

图片由作者提供

这个过程对线性回归来说太简单了,让我们挑战自己,开始研究逻辑回归吧!

为什么逻辑回归系数的解释是具有挑战性的

逻辑回归是一个基于线性的模型,像线性回归一样,但它进行了一个变换,使预测值y保持在 0 和 1 之间。这使得逻辑回归能够预测目标变量属于某个类别的概率。

这是二元逻辑回归的形式:

虽然这种变换对于将线性回归适应于预测概率非常有效,但它破坏了我们直接解释系数作为平均边际效应的能力!

让我们模拟一些二元数据来进行演示。在下面的代码中,我们遵循与线性回归模拟相同的过程,除了在模拟了y之后,我们使用均匀分布进行采样,使y变为 1 或 0。 (注意:我们在这里增加了随机性,因为我们通过正态分布手动添加了误差,然后将y转换为二元变量的过程也为模拟添加了一些随机噪声)。

from sklearn.linear_model import LogisticRegression
import pandas as pd
import numpy as np

# simulate binary data
def logistic_regression_simulation(sim_var, sim_error, sim_coef, size):

    '''
        Simulates data for simple logistic regression.

        inputs:
            sim_var (list)    : 2-element list, first element is the mean of a random variable
                                that is being used to simulate a feature in the logistic regression, 
                                second is the standard deviation
            sim_error (list)   : 2-element list, first element is the mean of random error being added,
                                 second is the standard deviation
            sim_coef (float)   : impact of the random variable established by sim_var on the target 
                                 variable
            size (int)         : number of units to simulate

        output:
            sim_df (DataFrame) : dataframe with simulated data

    '''

    # create an empty dataframe to populate
    sim_df = pd.DataFrame()

    # create the feature for the linear regression
    sim_df['var'] = np.random.normal(sim_var[0], sim_var[1], size = size)

    # multiply feature by the coef to get a simulated impact
    sim_df['var_impact'] = sim_df['var']*sim_coef

    # create error term
    sim_df['sim_error'] = np.random.normal(sim_error[0], sim_error[1], size = size)

    # add error and impact together
    sim_df['sum_vars_error'] = sim_df['var_impact'] + sim_df['sim_error']

    # create a uniform random variable used to convert sum_vars_error from continuous to binary
    sim_df['uniform_rv'] = np.random.uniform(size = len(sim_df))

    # create the binary target variable using the uniform random variable
    sim_df['binary_target'] = sim_df.apply(lambda x : 1 if x.sum_vars_error > x.uniform_rv else 0, axis = 1)

    return sim_df

log_reg_sim_df = logistic_regression_simulation([2.00, 0.2], [0, 0.1], 0.16, 30000)

现在我们已经模拟了数据,让我们运行逻辑回归,看看我们的系数是什么样的。

log_reg = LogisticRegression()
X = np.array(log_reg_sim_df['var']).reshape(-1, 1)
y = log_reg_sim_df['binary_target']
log_reg.fit(X, y)

这是输出结果:

print(log_reg.coef_[0])

什么???这感觉一点也不好。那个系数远远不是我们创建的 0.16 的正确答案!

但为了确保,让我们运行一千次并查看分布情况。

图片由作者提供

看起来我们的第一次运行不是异常值。系数的中心距离我们模拟的影响 0.16 相差很远。当然,这是因为逻辑回归系数不能像线性回归系数那样直接解释。

在下一部分,我们将讨论如何解释逻辑回归系数。

如何解释逻辑回归系数

首先,让我们谈谈逻辑回归系数的符号。好消息——符号是可解释的!如果符号为正,则对应类别的概率随着 x 的增加而增加——负号则相反。这可能非常有帮助。假设你正在开发一个预测客户是否会购买某个产品的模型。你可以通过观察价格的系数是否为负来检查模型的直觉(这意味着随着价格的上涨,客户购买产品的可能性降低)。虽然实际的数值可能是一些对数变换的复杂术语,但至少你可以理解模型是否具有方向上的意义。

那么,我们如何得到逻辑系数的均值边际效应呢?微积分,我的朋友,微积分。

我们想了解 y 如何随 x 的变化而变化。导数正是这样做的!让我们对我们的逻辑回归函数相对于 x 的偏导数进行计算:

这里一个重要的结论是,x 出现在其自身的偏导数中。这意味着 y 随着 x 的单位变化而变化的程度取决于 x 本身的水平。

注意:线性回归的均值边际效应以相同的方式计算。我们只是不会像这样思考,因为相对于 x 的偏导数只是常数 B₁。

所以,现在我们有了一种理解 x 的单位变化如何改变 y 的方法,但 x 是方程的一部分。我们如何得到均值边际效应?不幸的是,没有参考数据集我们无法得到它。我们将从参考数据集中插入所有 x 的值,并计算我们偏导数的平均输出。这将最终得到我们的均值边际效应!如果我们的参考数据集能代表我们的总体,我们可以说我们的计算应该是对逻辑回归模型真实均值边际效应的无偏估计。

有了这些知识,我们再运行一次模拟,但这次使用我们计算出的偏导数来计算均值边际效应。

from math import exp
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt

iters = 1000
mean_marginal_impacts = []
coef_list = []

# create iters number of simulated datasets
for i in range(iters):

    # create simulated data
    log_reg_sim_df = logistic_regression_simulation([2, 0.2], [0, 0.1], 0.16, 20000)

    # run regression and get coefficient and intercept
    log_reg = LogisticRegression()
    X = np.array(log_reg_sim_df['var']).reshape(-1, 1)
    y = log_reg_sim_df['binary_target']
    log_reg.fit(X, y)

    coef = log_reg.coef_[0][0]
    intercept = log_reg.intercept_[0]

    # run the model outputs through the partial derivatives for each simulated observation
    log_reg_sim_df['contribution'] = log_reg_sim_df['var'].apply(lambda x : coef*exp(intercept + (x*coef))/
                                                                         (((exp(intercept + (x*coef)) + 1))**2))

    # calculate the mean of the derivative values
    temp_mean_marginal_impact = log_reg_sim_df['contribution'].mean()

    # save the original coefficient and marginal impact for 
    # this simulation in a list 
    mean_marginal_impacts.append(temp_mean_marginal_impact)
    coef_list.append(coef)

# show the distribution of simulated mean marginal impacts
plt.hist(mean_marginal_impacts, bins = 20)

图片由作者提供

太棒了!我们看到了那 elusive 0.16 的值,真是令人松了口气!我们现在知道了如何解释逻辑回归系数以及如何手动计算它们!当然,手动计算这些效应并不很实际,尤其是当我们开始将更多的 x 添加到模型中时。幸运的是,Python 的 statsmodels 包有一个内置方法来计算均值边际效应。我将在下一节中分享一个示例。

使用 statsmodels 包计算均值边际效应

我们可以使用 statsmodelsLogit 类的 margeff 方法来计算均值边际效应。代码如下:

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
import statsmodels.api as sm

iters = 1000
sm_marginal_effects = []

for i in range(iters):

    # simulate data
    log_reg_sim_df = logistic_regression_simulation([2, 0.2], [0, 0.1], 0.16, 20000)

    # define target and predictor variables
    X = np.array(log_reg_sim_df['var'])
    y = log_reg_sim_df['binary_target']

    # add constant to formula - statsmodels.Logit doesn't automatically include
    # an intercept like sklearn
    X_with_intercept = sm.add_constant(X)
    log_reg_sm = sm.Logit(y, X_with_intercept)
    result = log_reg_sm.fit(disp=False)

    # calculate marginal effects
    marginal_effects = result.get_margeff(at = 'all', method = 'dydx')

    # save mean marginal effects in a list
    sm_marginal_effects.append(np.mean(marginal_effects.margeff)) 

让我们来看看 1000 次模拟的平均边际效应:

很好,它们符合我们的预期!让我们快速查看分布:

图片来源:作者

太棒了!这看起来非常类似(由于模拟过程中的随机性,它会有些不同)于我们手动计算时的结果。这感觉很好!

结论

理解逻辑回归系数的意义可能有点棘手。我们可以通过将参考数据集中的所有x值代入逻辑回归方程的偏导数来估计它们的平均边际效应。将这些边际效应的平均值就是平均边际效应。statsmodels.Logit 类有一个计算平均边际效应的方法,而我们无需计算任何偏导数。在实际操作中,你应该使用statsmodels(或任何其他为你计算的包或软件),但现在你确切知道代码在后台做了什么!

希望你已经对逻辑回归及其如何解释单个预测变量的影响有了更深入的理解。

GitHub 仓库链接:github.com/jaromhulet/logistic_regression_interpretation

如何迭代一个 Pandas Dataframe

原文:towardsdatascience.com/how-to-iterate-over-a-pandas-dataframe-5dc15ab147f9

照片由Sid Balachandran拍摄,来源于Unsplash

行优先与列优先,Pandas 最佳实践

Marcello PolitiTowards Data Science Marcello Politi

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

--

如果你有一些数据科学的经验,你肯定遇到过从表格数据中开发算法的情况,这类常见挑战的例子有Titanic — 从灾难中学习机器学习或波士顿房价数据。

以表格形式表示的数据(如 CSV 文件)可以分为行优先格式列优先格式。在计算中,行优先顺序和列优先顺序是存储多维数组的线性存储方法,如随机存取内存。根据格式设计的范式,有最佳实践可遵循,以优化文件的读写时间。数据科学家经常不幸地以错误的方式使用诸如 pandas 之类的库,从而浪费宝贵的时间

行优先格式意味着在表中,连续的行在内存中被连续保存。因此,如果我正在读取第 i 行,那么访问第 i+1 行将是一个非常快速的操作。

遵循列优先格式范式的格式,如 Parquet,会在内存中连续保存列。

在机器学习中,我们经常遇到行代表数据样本,列代表特征的情况。因此,如果我们需要快速访问样本,我们将使用 CSV 文件,如果我们经常需要访问特征(例如计算统计等),我们将使用 Parquet。

src: en.wikipedia.org/wiki/Row-_and_column-major_order#/media/File:Row_and_column_major_order.svg

Pandas

Pandas 是一个在数据科学中广泛使用的库,尤其是在处理表格数据时。Pandas 基于 DataFrame 的概念,即数据的表格表示。虽然 DataFrame 遵循列优先格式范式。

所以按行迭代 DataFrame,像通常做的那样,是非常慢的。让我们看一个例子,让我们导入 BostonHousing DataFrame 并进行迭代。

import pandas as pd
import time
df = pd.read_csv('https://raw.githubusercontent.com/selva86/datasets/master/BostonHousing.csv')
df.head()

在第一次实验中,我们迭代 DataFrame 的列(df.columns),然后访问每列中的所有元素,并计算完成该过程所需的时间。

#iterating df by column
start = time.time()
for col in df.columns:
  for item in df[col]:
    pass
print(time.time() -start , " seconds")

#OUTPUT: 0.0021004676818847656  seconds

而在第二次实验中,我们使用 df.iloc 函数按行迭代 DataFrame,该函数返回整行的内容。

#iterating df by row
n_rows = len(df)
start = time.time()
for i in range(n_rows):
  for item in df.iloc[i]:
    pass
print(time.time() -start , " seconds")

#OUTPUT : 0.059470415115356445  seconds

正如你所看到的,第二次实验的结果远大于第一次。在这种情况下,我们的数据集非常小,但如果你用自己的更大数据集进行尝试,你会注意到这种差异会变得越来越明显。

Numpy

幸运的是,numpy 库来拯救我们。当我们使用 numpy 时,我们可以指定我们想要使用的主要顺序,默认情况下使用的是行优先顺序。

所以我们可以做的是将 pandas DataFrame 转换为 numpy,然后逐行迭代。让我们看一些实验。

我们首先将 DataFrame 转换为 numpy 格式。

df_np = df.to_numpy()
n_rows, n_cols = df_np.shape

现在让我们按列迭代数据,并计算时间。

#iterating numpy by columns
start = time.time()
for j in range(n_cols):
  for item in df_np[:,j]:
    pass
print(time.time() -start, " seconds")

#OUTPUT : 0.002185821533203125  seconds

现在按行迭代同样的操作。

#iterating numpy by row
start = time.time()
for i in range(n_rows):
  for item in df_np[i]:
    pass
print(time.time() -start, " seconds")

#OUTPUT : 0.0023500919342041016  seconds

我们看到使用 numpy 时两个实验的速度都提高了!而且,两者之间的差异很小。

终极思考

在这篇文章中,我们介绍了处理表格数据时行优先和列优先范式的差异。我们指出了许多数据科学家使用 Pandas 时的一个常见错误。在这种情况下,访问数据的时间差异很小,因为我们使用了一个小数据集。但你必须小心,因为数据集越大,这种差异也会变大,你可能会浪费很多时间仅仅在读取数据上。作为解决方案,尽量在可能的情况下使用 numpy。

关注我以获取更多类似文章!😉

结束

马切洛·波利蒂

LinkedinTwitterWebsite

如何使用多功能数据工具(Versatile Data Kit)跟踪数据版本

原文:towardsdatascience.com/how-to-keep-track-of-data-versions-using-versatile-data-kit-f1916f18737e?source=collection_archive---------9-----------------------#2023-05-03

数据工程

学习关于慢变维度(SCD)以及如何在 VDK 中实现 SCD Type 2

Angelica Lo DucaTowards Data Science Angelica Lo Duca

·

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

--

照片由Joshua SortinoUnsplash拍摄

数据是任何组织的支柱,在当今快节奏的世界中,跟踪数据版本至关重要。随着企业的成长和发展,数据经历了许多变化,如果没有流畅的系统,这些变化很快就会变得令人不知所措。技术通过各种工具来帮助我们有效地管理数据。

其中一个工具是 Versatile Data Kit (VDK),它为控制数据版本需求提供了全面解决方案。VDK 可帮助您轻松执行复杂操作,例如使用 SQL 或 Python 从不同来源进行数据摄取和处理。您可以使用 VDK 构建 数据湖 并摄取从不同来源提取的原始数据,包括结构化、半结构化和非结构化数据。

在本文中,我们将探讨 VDK 如何通过提供直观界面和强大功能来简化您的生活,以跟踪对关键业务信息所做的所有更改。

我们将涵盖以下主题:

  • 什么是缓慢变化的维度(SCD)?

  • SCD 类型

  • 如何在 VDK 中实现 SCD 类型 2。

图片注意:所有图片均为作者所有,除非另有明确说明。

什么是缓慢变化的维度(SCD)?

在数据仓库中,维度 是一种结构,用于分类事实和度量,以便用户回答业务问题。常用的维度有人员、产品、地点和时间。

缓慢变化的维度(SCD)帮助您跟踪维度数据随时间的变化。它们在数据仓库中存储和管理当前和历史数据。缓慢 意味着它们随时间变化较慢而不是定期变化。SCD 的例子包括地址、电子邮件、工资等。

SCD 对于跟踪数据随时间变化非常重要。我们可以使用它们来跟踪客户、产品或其他可能随时间变化的信息。

使用 SCD 可以更轻松地跟踪数据变更并保留数据如何变化的历史记录。这对分析趋势或回答关于特定数据如何演变的问题非常有价值。

SCD 类型

有三种类型的 SCD:类型 1、类型 2 和类型 3。为了说明每种 SCD 类型的工作原理,我们使用以下示例。考虑以下客户维度表:

图 1 — 示例中的客户维度表

如果 John Smith 把他的电话号码改成 555-5668,会发生什么?

SCD 类型 1

这种 SCD 类型不追踪数据变更;新数据会覆盖旧数据。当数据历史不重要且需要了解当前数据状态时,这种类型是合适的。类型 1 是最简单和最常见的 SCD 类型。

考虑图 1 中的示例。在 SCD 类型 1 中,新电话号码替换了旧电话号码,而且没有记录之前的电话号码,如下表所示。

图 2 — 应用 SCD 类型 1 后的客户维度表

SCD 类型 2

这种类型通过向维度表添加新记录来跟踪历史更改。旧数据仍然可用,但被新数据标记为废弃。每条记录包含唯一标识符、开始日期和结束日期。

再考虑图 1 中的示例。在 SCD 类型 2 中,创建一个新的记录,包含新的电话号码、唯一标识符和开始日期,同时前一个记录有一个结束日期。开始日期表示新记录生效的日期,结束日期表示旧记录过时的日期。

图 3— 应用 SCD 类型 2 后的客户维度表

SCD 类型 3

这种类型通过向维度表中添加一个列以保存新值,仅跟踪最新的更改。当跟踪完整的更改历史记录不是必要时,使用此类型。

再考虑图 1 中的示例。在 SCD 类型 3 中,记录被更新为两个新列:new_phone_number 用于保存新值,effective_date

图 4— 应用 SCD 类型 3 后的客户维度表

如何在 VDK 中实现 SCD 类型 2

VDK 是由 VMware 作为开源发布的一个非常强大的框架。使用 VDK 构建数据湖并合并多个数据源。如果你是 VDK 的新手,可以阅读其 官方文档 或这篇 介绍文章。

VDK 支持 SCD 类型 1 和类型 2。要在 VDK 中实现 SCD,请使用 SQL 处理模板。SQL 处理模板是数据加载模板,是 VDK 提供的一个概念结构。根据 VDK 官方文档,

数据加载模板从位于源模式的 source_view 中提取数据,并将源数据加载到位于目标模式的 target_table 中(摘自 VDK 官方文档)。

实际操作中,模板简化了从源中提取数据并加载到目标表中的过程。VDK 提供了适用于 Impala 和 Trino 数据库的 SQL 数据处理模板。

为了说明 VDK 如何管理 SCD 类型 2,请考虑以下场景。

图 5 — 如何使用 VDK 管理 SCD 类型 2

从左开始,有 数据源(例如图 1 中的客户维度表),必须定义一个源模式和源视图。VDK数据源 中摄取数据。为了管理 SCD 类型 2,VDK 使用 SCD2 模板,这是一个 SQL 处理模板。通过 vdk-trino 插件,VDK 使用 Trino DB 将数据存储到 数据湖 中。数据湖 必须包含 目标模式,即存储 SCD 类型 2 的模式。

VDK 实现了 SCD2 模板 作为 Input Job 的附加方法。以下代码展示了如何使用 SCD2 模板

def run(job_input: IJobInput) -> None:
    # ...
    job_input.execute_template(
        template_name='scd2',
        template_args={
            'source_schema': 'customer_schema',
            'source_view': 'customer_view',
            'target_schema': 'customer_target_schema',
            'target_table': 'customer_target_table',
            'id_column': 'customer_id',
            'sk_column': 'SID',
            'value_columns': ['name', 'address', 'phone_number'],
            'tracked_columns': ['phone_number'],
        },
    )

代码直接摘自VDK 文档,并根据图 3 中的示例进行了调整,因此请参阅它以获取更多详细信息。实际上,该模板将源和目标模式作为输入,以及其他参数,例如列 ID 和要跟踪的列。

多亏了 VDK,你可以轻松管理数据库中的 SCD!有关完整而详细的示例,请参阅VDK 文档

为了解释 SCD2 模板的工作原理,请再次考虑客户维度表。之前的代码仅跟踪phone_number列,因此如果该列发生变化,系统会将更改存储在新行中,如下图所示:

图 6——如果跟踪的列发生变化,系统会将其存储为新行。

然而,如果未跟踪的列发生变化,系统会覆盖它,如下图所示:

图 7——如果未跟踪的列发生变化,系统会覆盖它。

总结

恭喜!你刚刚了解了 SCD 是什么以及如何在 VDK 中实现它。

SCD 对希望有效管理和分析数据的组织非常重要。通过识别数据集中哪些属性很少或不经常变化,你可以简化数据处理和存储,减少分析中错误或不一致的可能性。

要轻松识别和管理 SCD,你可以使用 VDK,它帮助你自动化慢变属性的变化。

总体而言,SCD 在大数据管理的宏观框架中可能看起来是一个微小的细节。但它可以显著影响分析过程的准确性和效率。通过利用像 VDK 这样的工具,你可以保持领先并最大化数据资产的价值。

你可能还感兴趣……

有许多相关主题可能会引起你的兴趣:

如何配置 VDK 与 Trino DB 一起使用

使用完整示例,展示了如何使用 Versatile Data Kit 和 Trino DB。

如何使用 VDK 构建 Web 应用

逐步教程,讲解如何构建 Web 应用,结合 Streamlit Python 库和 Versatile Data Kit。

如何使用 VDK 插件

逐步教程,演示如何通过编写 VDK 自定义插件来操作数据湖中的表。

如何处理缺失值

教程,讲解如何使用 VDK 构建数据管道以处理缺失值

只再说一词……

不要忘记加入 VDK Slack 频道,以获取关于 VDK 的最新信息!

如何在 2023 年学习地理空间数据科学

原文:towardsdatascience.com/how-to-learn-geospatial-data-science-in-2023-441d8386284e?source=collection_archive---------1-----------------------#2023-02-21

对于那些想要学习使用 Python 进行地理空间数据分析的步骤指南

Maurício CordeiroTowards Data Science Maurício Cordeiro

·

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

--

使用 Dall-E 2 创建的数字图像。说明:一名科学家手持地球的照片。

介绍

为什么这个话题很重要?众所周知,数据科学职业如今需求量很大。当你将地理空间分析的维度加进去时,可能性变得更加令人兴奋。气候变化、食品生产以及向零碳经济的过渡只是需要深入理解地理空间数据的众多重要问题中的几个。通过结合卫星和无人机图像、矢量数据集以及实地测量,我们可以获得更深刻的见解,并推动有意义的变革。

虽然有很多资源可以用于学习使用 Python 进行地理空间数据分析,但这个领域正在快速发展。在过去,我管理了一个 GIS 团队,我们在水资源映射和水文学分析中严重依赖 ArcGIS 或 ENVI 等软件。然而,我与使用 Python 进行地理空间分析的旅程始于 2019 年,当时我开始了我的博士学习。

本周我偶然发现了《Python Geospatial Development》这本书。起初,我认为这将是提升技能的绝佳机会。然而,在查看了目录后,我感到很惊讶。除了 GDAL(我在开始时简要使用过,但很快被 Rasterio 取代)外,我几乎没有使用第三章——地理空间开发的 Python 库中讨论的任何 Python 库。这种差异可以归因于这本书首次出版于 2010 年,且在 2013 年和 2016 年仅进行了小幅更新。在计算机科学的世界里,那已经是很久的时间了。

尽管由于其驱动程序的原因仍然需要安装 GDAL,但其 Python 绑定已经不再必要,并且使用起来可能会令人困惑(姑且这么说)。此外,我从未听说过如 Mapnik 这样的其他库。此外,像 GeoPandas 或 XArray 这样的基本包在书中甚至没有提及。最后,Google Earth Engine(这一领域的真正变革者)在书籍出版时甚至尚不存在,这突显了实际开发书籍的一个常见问题——它们很快会变得过时。

嗯,我并不是反对书籍。书籍仍然对构建特定学科的坚实基础至关重要。然而,要成为一名实用的地理空间分析师,学习如何在大量在线信息中导航并理解这些信息至关重要,因为对于新手来说,这些信息可能会让人感到不知所措。

考虑到这一点,本文是为那些希望在 2023 年学习使用 Python 进行地理空间数据分析的人编写的,提供了有关主要技能和主题的快速指南,并给出了一些如何有效导航大量在线信息和避免某些陷阱的建议。请注意,这份指南基于我自己的经验,可能并不完全符合每个人的期望。

避免陷阱

在开始之前,你需要注意一些陷阱。我列出了四个需要注意的陷阱,但我相信在你的学习过程中还会遇到更多:

1- 信息过载: 地理空间数据科学的世界广阔,知道从哪里开始可能会很具挑战性。如果你开始搜索互联网,你会被大量的信息、文章和课程所吓倒。我知道这令人不知所措。突然间,你发现自己只是从一个网站跳到另一个网站,整整一天过去了却没有实际学到任何东西。为了避免感到畏惧,专注于一个主题一次,并避免干扰。使用你喜欢的方法:番茄工作法、Flowtime,或 Deepwork。没关系,只要你保持专注!

照片由 Usman Yousaf 提供,来源于 Unsplash

2- 过时的资源: 这个问题与我在介绍中提到的内容相关……如果你得到一个过时的资源,你可能只是浪费时间,毫无进展。甚至更糟,你可能会回到……过去!

注意: 如果你在学习一些基础知识如统计学或物理学,这点可能不太相关,但如果你涉及某些技术方面,它可能会起到重要作用。

3- 尊重你的学习曲线: 我开始时遇到的第三个问题是,我试图解决一些高级话题,例如创建一个云中的虚拟机来托管地图服务器,但甚至不了解 Docker 或 Git 是什么。结果,我花了很多时间去解决超出我能力范围的问题。例如,如今,当我想发布一张地图时,我只需将 COG(云优化的 Geotiff)直接发布到 Github 上(例如,cordmaur.github.io/Fastai2-Medium/occurrence_map.html),无需设置地图服务器等繁琐操作。但我只有在研究该主题一段时间后才知道 COG 的存在。

图 1: 直接托管在 Github 上的 COG 文件。图片由作者提供。

4- 不要试图从头到尾理解一切: 在这里,我们必须使用一些推理。掌握主要话题并迅速补充任何可能缺失的重要概念是至关重要的。但你应该做到这一点,及时地。例如,假设你正在运行一个在过程中使用导数的机器学习模型。如果你像我一样,你会被诱惑去寻找最好的微积分课程,即使你在大学里已经学过两年。请不要陷入这个陷阱;深入理解所有潜在主题是不可能的。相反,进行快速回顾,以保持进度。

如何开始

如前所述,掌握地理空间数据科学所需的一切可能会让人感到不知所措,并且需要时间。如果你试图按照“正确”的顺序学习所有内容,你可能会感到气馁并放弃。在这方面,我同意 fast.ai 的 Jeremy Howard 的观点,他建议采取自上而下的方法。这样,即使你不完全理解所有的底层内容,也可以看到有意义的结果。

所以这是我如果从头开始并尽快学习的话,会考虑的“模糊”顺序。

使用 Dall-E 2 创建的数字图像。标题:一只泰迪熊爬楼梯的照片,背景是地球。

1- 基础 Python 编程

这是一个至关重要的主题。如果没有它,学习矢量数据集或栅格数据集等内容将毫无意义,因为一切都会显得抽象,你也没有工具去探索它。我知道你可以使用 QGIS 或其他 GIS 软件。但这与将原始数据导入到 numpy 数组或 geojson 文件并自己探索是不同的。此外,首先学习 Python 编程可以打开你以前没有想象过的其他门。

注意: 请记住,在开始时,你不会编写程序。你只是依赖第三方库来进行分析。因此,此时不建议使用为程序员设计的资源。你可以阅读“如何正确学习 Python 数据科学”来理解其中的区别。

因此,为了学习最基本的内容,我建议选择像 Codecademy 的‘学习 Python 3’这样的课程。该课程只需 25 小时即可完成,时间不足一周,并且提供了对语言的快速概述。此外,我在我的 YouTube 频道上发布了四节名为‘科学家 Python 入门’的课程,这也可以是一个很好的主题入门。

使用 Dall-E 2 创建的数字图像。标题:一个小孩在老式计算机上编程的铅笔画。

之后,我建议学习 Jake VanderPlas 编写的《Python 数据科学手册》,可以在作者的 GitHub 上免费获取(jakevdp.github.io/PythonDataScienceHandbook/)。这本书并不专注于讲解 Python 语言的内部工作,如创建复杂函数、定义类、继承或其他面向对象的方面。相反,它提供了对主要库的快速概述,如 numpypandasmatplotlib,这些都是数据分析中必不可少的工具。

2- GEE 和 GIS 基础

现在你已经对 Python 及其主要的数据分析库有了基本了解,是时候开始接触一些 GIS 概念了。参加一个通用的 GIS 课程可能会让你被关于卫星数据收集、大气中的光路径以及这些如何影响传感器读数的信息轰炸。虽然这些主题对于研究很重要,但对于地理空间数据分析的实际入门可能并不必要。

照片来自于Krzysztof HepnerUnsplash

从实际角度出发,我建议通过一个使用 Python 的Google Earth Engine实践课程来学习 GIS 基础知识。这样,你将练习 Python 并可以快速访问地理空间数据(包括矢量数据和栅格数据),而无需担心下载或设置复杂的环境。你将立即开始进行首次空间分析。

这样的课程可以在Udemy上找到:Google Earth Engine Python API 中的空间数据分析完整的 Google Earth Engine Python API & Colab 培训营。另一个选择是参加一个完整的 GEE 课程,该课程将使用 JavaScript 环境,但一切可以通过 Python 进行复制,语法仅需稍作调整。

3- 地理空间 Python 库

Google Earth Engine (GEE) 功能强大,提供大量现成的数据,但也有一些缺点。一切都必须在 Google 云端运行。虽然它提供免费访问其资源,但对于大规模处理可能会产生费用。因此,理想的解决方案是使用开源 Python 库来处理来自任何来源的数据。主要库包括:

  • rasterio:用于读取栅格数据并将其加载到数组中。

  • geopandas:类似于 pandas,但具有用于矢量数据的空间字段。

  • xarray:类似于 numpy,但支持坐标和尺度。

  • shapely:用于矢量处理、剪切、交集等。

  • fiona:用于读取标准的 shapefiles。

  • leafmap:用于动态地图可视化。

4- 项目和 Python 环境管理

假设你在没有 Python 编程基础的情况下达到了这一点。在这种情况下,你可能有一个你不完全理解的混乱工作环境,以及一堆不相关的 Jupyter notebooks,使得很难找到你上周发现的重要代码片段。如果你处于类似情况,不要惊慌!每个新手都会遇到这种情况。现在是时候退一步整理你的工作环境了。为此,你需要了解:

  • 包管理工具如 Pip 和 Conda 的重要性。你还可以探索 Miniconda 和 Mamba,以获得更快的性能;

  • 如何为你的 Jupyter notebooks 创建一个内核;

  • 如何使用 Git 控制你的工作版本并通过 GitHub 进行备份。

5- 深入了解 Python 编程

到现在为止,你可能一直在使用 Jupyter notebooks 进行实验。它是一个出色的数据探索和分析工具,但不要指望用它创建一个完全功能的软件(除非你采用了 nbdev)。此外,编写一个执行特定分析的 notebook 与编写一个将部署到服务器上的完全操作的软件是不同的。我在之前的一篇文章 "为什么数据科学家应该适度使用 Jupyter Notebooks" 中讨论了这个问题。

基本上,你需要一些 IDE(如 PyCharm 或 VSCode)来开发函数、类和包。Jupyter notebooks 将用于调用你创建的内容并展示结果。记住列表中的第一个项目,当我提到不要使用针对程序员的 Python 资源时?现在是时候提升你的编程技能了。

6- 高级话题(天空是极限)

接下来,你会很快意识到地理空间分析中的许多任务是易于扩展的,这取决于可用的数据量和处理能力。然而,要进行扩展,你需要了解如何部署具有更多资源的云服务器以及如何进行分布式计算。在这些情况下,了解新技术如以下内容很重要:

  • Dask 库:Dask 是一个用于分布式计算的 Python 库。它可以帮助你处理那些无法完全装入基础内存的大量数据。

  • Docker:Docker 允许你构建、打包和部署作为容器的环境和应用。这些容器可以部署到云服务器上,以提高效率和可扩展性。

使用 Dall-E 2 创建的数字图像。标题:牛仔骑着火箭飞往月球

7- 数据科学中的数学和统计

你确定吗?你可能会认为这不是一切的基础?其实,这个话题与陷阱 #4 紧密相关。如果你有良好的数学基础,你应该只挑选那些你真正遇到问题的主题,准确地说。为此,Khan Academy (en.khanacademy.org) 是一个很棒的免费资源,讲座被分成了大约 10 分钟的短视频。

如果你认为你的问题仅在于统计学,你可以考虑 David Forsyth 编写的《计算机科学的概率与统计》这本书,由 Springer 出版。

另一方面,如果你没有扎实的数学基础,应该考虑购买一个专注于数据科学数学的课程。在 CodeAcademy(数据科学基础数学)或 Udemy(数据科学、数据分析和机器学习数学)等平台上有很多这样的课程。这些课程价格不贵,并且可以给你一个良好的概述。

结论

总结来说,我相信地理空间数据科学是我们时代最激动人心和重要的领域之一。通过将 Python 的强大功能与丰富的地理空间数据结合起来,我们有潜力在气候变化、食品生产和公共健康等领域推动有影响的变化。然而,要成为这一领域的熟练从业者,专注于正确的主题和工具非常重要。根据我的经验,这意味着要涵盖以下步骤:

1- 基础 Python 编程

2- GEE 和 GIS 基础

3- 地理空间 Python 库

4- 项目管理和 Python 环境

5- Python 编程深入探讨

6- 高级主题(天高地阔)

7- 数据科学中的数学和统计

在这段旅程中,你还应该注意那些可能会拖慢你进度的常见陷阱和误区。拥有这些技能和资源,我相信任何人都可以学习地理空间数据科学,并在世界上产生真正的影响。

保持联系

如果你喜欢这篇文章,考虑成为一个 Medium 会员 ,解锁像这样的数千篇文章。费用仅为每月 $5。

[## 使用我的推荐链接加入 Medium - Maurício Cordeiro

阅读 Maurício Cordeiro(以及 Medium 上其他成千上万位作者)的每个故事。你的会员费用直接…

cordmaur.medium.com](http://cordmaur.medium.com/membership?source=post_page-----441d8386284e--------------------------------)

如何通过向这些专业人士学习来提升你的 Python 技能

原文:towardsdatascience.com/how-to-level-up-your-python-skills-by-learning-from-these-professionals-3e906b83f355

防止停留在 Python 编程的初级水平

Tomer GabayTowards Data Science Tomer Gabay

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

--

照片由 Austin Distel 提供,来源于 Unsplash

免责声明:我与本文中提到的任何课程或个人没有关联,也未受到任何组织或个人的经济支持。

互联网上充满了各种 Python 学习课程和资料。其中最著名的有 100 Days of Code: The Complete Python Pro Bootcamp for 2023, codeacademyfreeCodeCamp 然而,完成其中一个(或多个)课程后,很多人会停留在同一水平:初级 Python 开发者水平。

在欧洲,2023 年 3 月 4 日,LinkedIn 上中级/高级 Python 开发者职位的招聘数量几乎是初级 Python 开发者职位的两倍(分别为 23,73912,720)。

许多想学习 Python 的人即使经过多年的编程,也停留在初级水平的原因是,在达到可以自己编程的水平后,你需要继续学习新的方面和理论。简单来说,有三个与提升编程水平相关的原则,你需要应用其中至少两个才能显著提高代码质量。

  1. 自己练习编程。

  2. 从与更有经验的开发人员合作的项目中学习。

  3. 通过积极寻找更高级的编程概念并将其应用于工作相关或个人项目,保持自我学习。

第一个原则是不可避免的。当然,就像你不能通过阅读理论来学习网球的最佳击球点一样,你也不能仅仅通过阅读和观看内容来学习编程。如果你有涉及编程的工作,这就很简单,因为你的老板期望你编程。如果你只是个人编程,你可能需要主动寻找项目来参与。幸运的是,有许多网站提供编程项目,例如:

Advent of Code: 这里你可以找到近 200 个编程挑战,从初学者到专家。

Kaggle: 一个面向数据科学家的在线社区,提供许多数据集供练习和参与竞赛。

或者可以查看Insane,那里建议了 70 多个 Python 项目,附有代码解决方案和解释,有些甚至包括教程!

除了自己积极编程外,最终要超越初级水平,还需要至少遵循第二和第三个原则之一。然而,第二个原则(与更有经验的开发人员合作)更难实现,因为你对它的影响较小。我曾在一个数据科学团队中是最有经验的 Python 开发者,但我自己仍然是初级开发者,我发现这使我的编程技能增长困难。最终,我离开了那个团队,加入了一个可以向比我更资深的同事学习的团队。在被资深同事包围的前几个月,我学到的 Python 知识比我自己最资深的那一年还多。

根据 2023 年 3 月 5 日的数据,glassdoor上的 Python 开发者职位年薪范围为入门级职位的$50k 到\(127k](https://www.glassdoor.com/Job/python-developer-jobs-SRCH_KO0,16.htm?seniorityType=entrylevel),而中级职位的年薪范围为[\),而中级职位的年薪范围为$77k 到$250k

虽然我在数据科学团队中是最资深的 Python 程序员,但仍然是初级水平,我开始在网上寻找高级 Python 内容来学习更高级的概念,因为没有同事可以教我,但我仍然希望提高我的编程技能。

我注意到,比起大多数入门课程,教授更高级 Python 概念的在线材料明显较少,质量也较差。然而,仍有一些出色的在线材料可以帮助你突破初级 Python 开发者的“玻璃天花板”水平。我喜欢推荐以下两位 Python 专家的内容,这些内容非常适合那些已经掌握入门课程中基本概念的初级程序员。

Arjan Egges

我学习如何提升 Python 程序专业性的一个最喜欢的资源是Arjan EggesArjanCodes内容。Arjan Egges 是一位荷兰 Python 开发者,他以非常清晰和愉快的方式从软件设计和开发的角度讲授更高级的 Python 概念。我从他那里学到了许多在格罗宁根大学的 Python 相关硕士课程中未曾教授的概念。

我特别喜欢他关于例如 Data ClassesA/B TestingAbstract Base Classes 等的免费 Youtube 视频,但他也有自己的网站,内容更加丰富:

[## ArjanCodes

嗨,我是 Arjan。我是一位软件设计爱好者和 YouTuber。我制作关于软件设计、开发等方面的视频…

www.arjancodes.com [## Next-Level Python: Become a Python Expert | ArjanCodes | Skillshare

解锁对该 Skillshare 课程的无限访问权限以及由来自世界各地的创作者教授的数千门课程…

www.skillshare.com

这个视频中,Arjan 提出了 14 个项目,你可以将从他的教程视频中学到的概念应用到这些项目中。我强烈推荐这样做,因为与其他项目相比,在这些项目中,你可以有更多的空间自行思考,这实际上迫使你从设计到实现全面考虑一个项目。

我认为这是大多数网上 Python 课程的最大缺陷之一:这些课程通常过于专注于语法,而实际上提出正确的解决方案和设计才是成为开发者最具挑战性的部分。相比之下,Arjan 更加注重软件设计而非语法,在我看来,这是一种非常受欢迎的 Python 教学不同方法。

通过交替观看他的教程视频和进行这些项目,如果你完成了所有这些项目,你的 Python 编程能力肯定会比那些只完成了如这个datacamp 上的标准入门级 Python 课程的同行提高很多,这些课程更注重语法。

在 2023 年 3 月 5 日,Udemy 上有超过6500 门英文 Python 初学者课程,而只有315 门英文高级 Python 课程

Fred Baptiste

另一位教授高质量高级 Python 开发内容的 Python 开发者是Fred Baptiste。如果你想真正理解运行某些 Python 代码时发生了什么,我强烈推荐他的课程。他不仅教你“你可以用这个函数或方法来解决这个问题”,而且深入探讨当调用这个函数或方法时实际执行了什么,以及这段代码在后台的工作原理。

因此,例如,在编写代码时,你可以适当地考虑计算成本和内存效率,这可能非常有用甚至是必要的;运用我们从 Fred 那里学到的知识,我和一位同事成功优化了一个之前无法使用的时间序列交叉验证器,因为它经常导致内存溢出错误或看似无尽的运行时间:

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

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

towardsdatascience.com

Fred Baptiste 在 Udemy 上发布了四门高级 Python 课程,你通常可以以大约$15 的折扣价格购买(享受30 天退款保证政策):

请确保阅读每门课程的先决条件,因为这些绝对不是初学者课程!

要从这些课程中获得最大收益,正如 Fred 自己所建议的那样,请确保在观看他的解决方案之前,自己先进行编码并尝试得出解决方案。

除了在 Udemy 上提供这些课程外,Fred 还运营了一个名为MathByte Academy的 YouTube 频道,提供关于 Python 的免费视频,包括一些高级概念,如Pydantic 数据模型异步生产者-消费者模型

学习、练习、应用、重复

当然,仅仅观看 Arjan 和 Fred 的视频并做他们的练习并不会立即改变你的初级水平。然而,如果你积极将他们教授的概念应用到工作中或个人项目中,你肯定会显著区别于那些只完成了我在文章开头提到的标准入门课程的其他 Python 开发者。通过 Arjan,你将学会如何正确思考 Python 程序的设计,而 Fred 则帮助你对 Python 代码有比以往更深刻的理解。

我坚信,通过完成他们的所有视频和练习,你有很高的机会成为比那些仅完成了几个 Python 课程的普通毕业生更优秀的 Python 开发者,因为 Arjan 和 Fred 教授的许多概念比我在硕士课程中遇到的要先进。

就这些!祝你在 Arjan 和 Fred 的帮助下顺利脱离 Python 编程的初级阶段!如有任何问题或需要更多建议,比如先做哪些项目,随时与我联系。

如果你想看到实际编码示例,了解高级开发者如何在编码挑战中区别于初级开发者,可以查看我另一篇文章:

## 5 个 Python 技巧让高级开发者与初级开发者区别开来

通过对《Advent of Code》谜题的不同解法进行说明

[towardsdatascience.com

如何将非传统教育或职业路径转化为你的第一个数据科学工作

原文:towardsdatascience.com/how-to-leverage-a-non-traditional-education-or-career-path-into-your-first-data-science-job-f2a5e2ed0585?source=collection_archive---------5-----------------------#2023-02-17

一些关于如何将你的非传统经历作为你的第一个数据科学工作的卖点的建议

Madison HunterTowards Data Science Madison Hunter

·

关注 发表在 Towards Data Science ·7 min read·2023 年 2 月 17 日

--

图片由 Joshua Earle 提供,来源于 Unsplash

数据科学是一个有趣的技术领域,你可以与那些曾经学习过哲学、从事护理工作或以遛狗为生的人交谈,而现在他们正在抓取网络数据、构建机器学习模型并向高管展示数据结论。

无论你有什么背景,你都受到数据科学领域的欢迎。

2020 年成为了人们开始自学编程并从各种背景过渡到技术领域的一年,这一趋势似乎延续到了 2023 年。虽然非传统的职业或教育背景可能使进入数据科学变得具有挑战性,但当你知道如何利用现有技能来补充新学到的数据科学技能时,这并不是不可能的。

这里分享的技巧并不具有革命性或改变人生的效果——而是我个人在尝试从非传统背景进入数据科学时发现的行之有效的技巧。你的数据科学技能和过往经验将会证明自己,你只需利用这些技巧将它们转化为新的数据科学职业。

建立作品集

你已经学会了数据科学技能,现在需要展示你能做什么。

当你申请数据科学职位时,你的作品集非常重要,这不仅适用于非传统背景的人,也适用于那些正式学习过数据科学的人。作品集往往是决定招聘人员是否邀请你参加第一次面试的关键,因此它需要成为一个充满你最佳工作的有影响力的资源,并展示你的非传统背景如何使你成为比其他候选人更强的候选人。

大多数数据科学作品集都是在 GitHub 上建立和托管的,GitHub 是行业标准,你应该在这里存储所有个人数据科学项目。你可以在这里学习如何建立 GitHub 作品集:

如何在 GitHub 上创建一个专业的作品集,以帮助获得数据科学领域的第一份工作

你的作品集在向雇主解释为什么他们应该聘用你方面起着至关重要的作用。

towardsdatascience.com

当你从非传统背景进入数据科学领域时,你希望你的作品集能够展示你从以前的经验中获得的可转移技能,使你的项目充满独特的洞察力。洞察力在数据科学中至关重要,这就是为什么选择项目时你需要发挥自己的优势。

例如,如果你是一名护士,你的作品集项目可以集中在如何提高医院的效率、医生如何利用人工智能做出更准确的诊断,或环境条件恶化如何成比例地增加医院的接收率。如果你是一名教师也是一样——如何通过更多教育驱动的儿童电视节目帮助孩子们在成为全面发展的学生方面取得先机?

无论你是否想在之前的行业中成为数据科学家,展示你能够运用已有知识解决问题的能力是至关重要的。这些项目应尝试解决你在工作中遇到的问题(或可能促使你离开那个职位的问题),通过数据集、统计分析、机器学习和人工智能来实现。

此外,这些类型的项目展示了你将现实世界问题拆解为数据科学可以解决的部分的能力。

例如,我目前正在进行一个个人项目,研究如何更好地标准化失踪人员的寻找概率。我从事搜救工作,虽然没有两个电话是相同的,但可以找到呼叫类型与预期寻找人员的位置之间的关联。换句话说,就是将一个非常现实的问题拆解为其组成部分,确定如何利用失踪人员数据使搜救操作更高效。虽然这可能与我下一个申请的数据科学职位不完全相关,但它肯定能展示我能运用数据科学技能解决问题——这最终才是雇主所寻找的。

如何有效展示个人项目在数据科学简历上的价值

简洁的项目描述是吸引招聘人员注意的必备条件。

towardsdatascience.com

关键要点

  • 为你的作品集构建能够解决你在之前行业中遇到问题的项目——这向潜在雇主展示了你致力于寻找更好的解决方案,并且能够将现实世界的问题拆解为可用数据科学解决的部分。

获得简单的数据科学经验

每个人的数据科学首次体验都会有所不同。对我来说,它是志愿服务,而对其他人来说,可能是自由职业工作。对于“Towards Data Science”的一些作者来说,他们通过分享文章来展示自己的专业知识。

获得简单的数据科学经验是获得实践经验和在现实场景中处理数据的好方法。这些机会可能没有报酬,但当你被聘为数据科学家时,它们会在未来得到回报。

简单的数据科学经验可能看起来像是建立一个 Excel 表格,通过历史价格数据预测父母未来的月度支出。或者,它可能是为一个本地在线业务做客户分析,并帮助他们推广表现最好的产品。又或者,它可能是为社交媒体营销人员建立一个仪表盘,以确定客户注册情况与 Google 搜索趋势的关系。如果你真的想利用你非传统的背景,寻找该领域的数据相关经验,并展示你在行业中的实际技能与新获得的数据科学技术技能如何帮助你比单一技能集提供更多见解。

无论如何,你应该完成 3–4 个简单的现实项目,向潜在雇主展示你具备他们需要的技术技能(以及提供比大多数人更多见解的非传统背景)。大多数公司希望他们的数据科学家能够迅速上手(程度有所不同),因此,通过这些简单的项目来掌握基础知识是一个好主意。

这些项目可以作为工作或志愿者经历展示在你的简历上。此外,你还应该争取获得你所做项目的个人或公司推荐,这可以用作参考,或者作为公司聘用你的又一个理由。最重要的是,这些简单的经历向雇主展示了你的非传统背景与作为数据科学家的能力是互补的。

例如,我将大学的最终顶点项目作为进入技术行业的经验。这是与潜在雇主交流的一个很好的话题,并让他们对我的技能有了很好的了解,无论是技术方面还是可转移的技能。通过向他们展示我曾作为团队的一部分为大型客户公司创造了有形结果,他们可以对我在“现实生活”工作中交付同样的结果充满信心。此外,虽然这项工作与职位并不完全相关,但它展示了我具有可转移技能,并因我的非传统背景而增加了洞察力。

关键点

  • 通过志愿服务、实习、自由职业或在社交媒体上分享你的知识来获得简单的数据科学经验。这向雇主展示了你具备完成工作的技术技能,并让他们放心,你的非传统背景与作为数据科学家的能力是互补的。

突出可转移技能

我从寻找学术之外工作的硕士和博士生那里得到的最佳建议之一是,你拥有许多可转移的技能,无论你来自什么教育或职业背景。虽然可能看起来不明显,但请思考一下。

例如,如果你是一名正在转行进入数据科学的护士,你会非常有组织、注重细节、富有创造力、能够在快节奏环境中工作,并且擅长解决问题。或者,如果你是一名教师,你是一位优秀的沟通者,能够将复杂的主题简化,你是一个解决问题的人,并且你对截止日期非常严格。这些技能在数据科学行业中都是被重视的,应当被充分展示。

在优秀的科技公司,你会发现他们会雇佣拥有所有基本软技能(其中一些在上面列出)的数据科学家,即使他们没有完美的技术背景,因为他们知道他们可以培训技术技能——但他们无法培训软技能。

例如,我记得我曾经工作的公司雇了一位也希望从事数据科学工作的开发者。虽然这位开发者的数据科学技能并不完美,但公司还是雇用了他们,因为公司知道这位人员可以在公司培训他们相关的数据科学技能的同时继续作为开发者工作。如果有需要,这位人员将有可能完全转入数据科学角色,或者继续作为开发者工作,并在旁边接受培训。

关键要点

  • 可转移技能将使你与其他候选人区分开来——突出与你申请的职位最相关的技能,并说明你在之前的经验中学到的知识如何适用于数据科学职位。

订阅以将我的故事直接发送到你的邮箱:Story Subscription

请成为会员以通过我的推荐链接无限访问 Medium(对你没有额外费用,我会获得少量佣金):Medium Membership

通过捐款支持我的写作,帮助我创作更多类似的故事:Donate

如何利用预训练的 Transformer 模型进行自定义文本分类?

原文:towardsdatascience.com/how-to-leverage-pre-trained-transformer-models-for-custom-text-categorisation-3757c517bd65

所以,你有一些自定义的文本数据集,想要进行分类,但不知道如何做?好吧,让我展示一下,如何利用预训练的最先进语言模型来实现。

Saed HussainTowards Data Science Saed Hussain

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

--

图片来源于Meagan CarsienceUnsplash

好吧,让我们直奔主题!你有一些自定义数据,现在想将其分类到自定义类别中。在本文中,我将展示你如何使用两种方法来实现这一目标。这两种方法都利用了最先进的基于 Transformer 的预训练模型。

请注意,本文的目标是与您分享方法及其使用方式。这不是一个包含最佳实践的完整数据科学教程。不幸的是,这超出了本文的范围。

本文中的所有代码可以在这个GitHub 仓库中找到。

1: 零样本分类

概述

零样本分类是一种技术,它允许你在没有为特定任务训练专门模型的情况下对文本进行分类。相反,它使用已经在大量数据上训练过的预训练模型来执行分类。模型通常会在包括语言建模、文本补全和文本蕴涵等各种任务上进行训练。

使用预训练的 LLM 进行零样本文本分类(来源:作者)

要执行零样本分类,你只需向预训练模型提供一些文本和一个可能的类别列表。

模型将利用其对语言的理解和已有知识将文本分类到提供的类别之一。这种方法特别有用,当你为特定分类任务拥有的数据有限时,因为它允许你利用模型的已有知识。

由于它在特定任务上没有任何训练,因此被称为零样本分类。

实现

我们需要做的就是使用pip install transformers安装hugging face transformers 库。我们将使用预训练的 Facebook BART(双向和自回归变换器)模型来完成这项任务。

附注:首次使用时,下载模型可能需要一些时间。

输出是一个包含 3 个键的字典:

  • 序列:由管道分类的输入文本

  • 标签:提供给管道的候选(类别)标签的列表,根据它们的概率分数进行排序。

  • 分数:基于模型对输入文本属于该标签的可能性的预测,分配给每个候选标签的概率分数。

from transformers import pipeline

pipe = pipeline(model="facebook/bart-large-mnli")
pipe("Tesco Semi Skimmed Milk 1.13L/2 Pints ...... £1.30",
    candidate_labels=["groceries", "utility", "electronics", "subscriptions"],
)

# output
>> {'sequence': 'Tesco Semi Skimmed Milk Pints',
 'labels': ['groceries', 'utility', 'subscriptions', 'electronics'],
 'scores': [0.9199661612510681,
  0.05123506113886833,
  0.022794339805841446,
  0.0060044946148991585]}

如你所见,在没有任何训练的情况下,模型已正确地将给定文本分类到“杂货”类别。因为模型是在特定语言的大型语料库上训练的,它能够理解该语言并进行推断。它理解了文本并从候选标签列表中识别了合适的类别。

简而言之,真是太棒了!!😊

模型越大,零样本分类任务的表现越好。有关更多信息,请查看 hugging face 上的零样本分类页面

⚡️ 查看这个笔记本以获取更多示例。

什么时候使用它?

鉴于其在没有任何训练的情况下表现卓越,如果你了解类别的描述,我强烈建议首先尝试这个方法。它利用了最先进的预训练模型,并在没有任何训练的情况下提供了卓越的结果。

这里是一些非详尽的情况,当这些情况适合你的方法时:

  1. 当你拥有有限的标注训练数据时

  2. 当你需要快速原型一个解决方案时

  3. 当你需要对新的或稀有的类别进行分类时

  4. 当你需要将实例分类到多个类别中时

  5. 当你想利用预训练模型对实例进行分类而无需额外训练时

  6. 当你想将实例分类到由自然语言描述定义的类别中,而不是预定义标签时。

有哪些限制?

这里是使用零样本分类方法时需要考虑的一些潜在缺点和限制:

  1. 有限的训练数据扩增:在零-shot 分类中,扩增训练数据以提升模型性能的范围有限,这与传统监督学习方法不同。

  2. 对模型行为的控制有限:零-shot 分类依赖于预训练模型,这意味着你对其行为和学习到的模式的控制有限。这可能导致意外结果,特别是当模型没有在类似于你任务的数据上训练时。

  3. 有限的自定义:由于零-shot 分类依赖于预训练模型,因此自定义或微调以适应特定任务的余地有限。这可能限制模型的准确性和性能,尤其是当任务涉及复杂或领域特定的语言时。

🚀 专业提示:始终清理你的文本!!

尽管基于变换器的 LLM 在处理嘈杂文本方面显著优于其他模型,但在将文本输入模型之前,仍然强烈建议清理文本。

这不仅是良好的数据科学实践,还会产生巨大差异。与干净文本输入相比,生成的嵌入在嘈杂文本数据中的相似性评分可能较低。这无疑会降低类别分类评分。

例如,下面是对这段文本——“Tesco Semi Skimmed Milk 1.13L/2 Pints …… £1.30”——在去除特殊字符和数字前后的分类对比。为什么不在Hugging Face zero-shot classification页面上试试呢?

使用零-shot 方法在清理文本前后的文本分类概率比较。(来源:作者)

☕️ 让我们在继续之前休息一下……

照片由Victoria Tronina拍摄,来自Unsplash

☕️ 5 分钟后……好吧,让我们开始吧 🏃

2: 迁移学习(预训练模型 + 分类模型)

概述

这是一种比前一种方法更高级的方式。如前所述,零-shot 分类不允许针对特定任务进行自定义。这就是这种方法作为长期解决方案的所在。

在这种方法中,你使用预训练的变换器模型创建文本嵌入,然后训练一个分类模型将这些嵌入分类到各自的类别中。

解决方案概述:使用预训练模型将项目描述文本转换为文本嵌入,并使用分类模型将文本分类到不同类别中。(来源:作者)

为了演示这个例子,我们将使用来自 Kaggle 的e-commerce 产品数据集

该数据集包含 4 个产品类别和产品的文本描述。目标是构建一个模型,可以将产品描述文本分类到这 4 个类别中。

Kaggle上的电子商务产品描述和类别数据。(来源:作者)

这个数据集中有 4 个类别。(来源:作者)

实现

这个方法的实现可以分为 4 个步骤:

  1. 在将文本输入模型之前,请先清理文本。

  2. 使用预训练的大型语言模型(LLM)生成文本嵌入。

  3. 使用嵌入训练一个自定义类别的分类模型。

  4. 使用训练好的模型和预处理管道进行预测。

到此为止,你将拥有一个分类模型,该模型利用了 LLM 在大量数据上训练学到的高质量嵌入。

这些嵌入是 LLM 成功的关键因素,因为它们捕捉了语言的丰富表示,能够有效捕捉文本中的意义和上下文的细微差别。

⚡️ 请注意,完整的代码可以在以下笔记本中找到。

步骤 1:清理文本

如前述方法所提,使用模型之前清理文本始终是一个好习惯。以下是本文中用于清理文本的代码

附注:清理是高度任务特定的。在这个例子中,我只实现了一些基本的清理。作为最佳实践,最好先了解数据,然后实施一些任务特定的清理。例如,你可能需要根据具体任务的要求决定是否保留或删除数字。此外,通过删除不必要的词语/符号来减少文本量,可以减少后续处理的时间。祝清理愉快! 😄

import re

pattern = r"(?:\b\w+\b|['\"“”‘’])"
url_pattern = re.compile(r'http\S+|www\S+')
punct_pattern = re.compile(r'[^\w\s]')
digit_pattern = re.compile(r'\d+')
non_ascii_pattern = re.compile(r'[^\x00-\x7F]+')

def clean_text(text):

    # Convert to lowercase
    text = text.lower()
    # Remove URLs
    text = url_pattern.sub('', text)
    # Remove punctuation
    text = punct_pattern.sub(' ', text)
    # Remove digits
    text = digit_pattern.sub('', text)
    # Remove non-ASCII characters
    text = non_ascii_pattern.sub('', text)
    # Tokenize words and remove single characters
    words = [word for word in re.findall(pattern, text) if len(word) > 1]
    return ' '.join(words)

# Example Output  
text = "This is an example text with a URL https://www.example.com, some #hashtag."
cleaned_text = clean_text(text)
print(cleaned_text)
>> "this is an example text with url some hashtag"

# Apply it to both the training and test data.
train_df["clean_desc"] = train_df["item_description"].apply(lambda x: clean_text(x))
test_df["clean_desc"] = test_df["item_description"].apply(lambda x: clean_text(x))

步骤 2:生成文本嵌入

我们将使用来自 Hugging Face 的SentenceTransformer库来创建文本嵌入。该库包含基于变换器的预训练模型,用于生成固定长度的文本数据向量表示,如段落或句子。

具体来说,我们将使用库中的“paraphrase-mpnet-base-v2”预训练模型,该模型生成长度为 768 的固定长度向量。

附注:为了节省时间,我减少了训练集和测试集的样本量。生成嵌入表示可能需要一些时间,尤其是在本地机器上。这可能会影响模型的性能。

from sentence_transformers import SentenceTransformer

# Load SentenceTransformer model
model = SentenceTransformer('paraphrase-mpnet-base-v2')

# Example Output
sentence = ['This is a sample sentence for encoding.']
embedding = model.encode(sentence)
print(embedding)
>> [[ 4.99693975e-02 -1.26025528e-01 -9.15094614e-02  1.19477045e-02
   9.89145786e-02  9.02947485e-02  1.74566925e-01  1.84450839e-02
  -1.04984418e-01 ....... 8.46698135e-02  8.69197398e-03 -1.48386151e-01]]

# Generate embeddings for the training data
train_desc = train_df['clean_desc'].tolist()
train_embeddings = model.encode(train_desc)
X_train = np.array(train_embeddings)

步骤 3:训练分类模型

现在我们已经有了嵌入表示,我们准备训练一个分类模型,将这些嵌入表示分类到 4 个产品类别中的其中一个。

在这个演示中,我使用了 XGBoost 模型,但你可以随意使用你喜欢的模型!

附注:为了在训练过程中节省时间,网格搜索中使用了有限的超参数。此外,为了简化,我们使用准确率作为性能衡量标准。确保使用适当的指标来衡量你的分类任务的性能。

from sklearn.model_selection import train_test_split,GridSearchCV
from sklearn.preprocessing import LabelEncoder
import xgboost as xgb

# Encode the target variable as categorical with one-hot encoding
le = LabelEncoder()
y_train = le.fit_transform(train_set['category'])

# Split the data into training and validation sets
X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.2, random_state=42)

# Define the parameter grid for grid search cross-validation
param_grid = {
    'max_depth': [7],
    'n_estimators': [300],
    'learning_rate': [ 0.2, 0.5],
}

# Train an XGBoost model with grid search cross-validation
xgb_model = xgb.XGBClassifier(objective='multi:softmax', num_class=4)
grid_search = GridSearchCV(xgb_model, param_grid=param_grid, cv=5, n_jobs=-1, verbose=0)
grid_search.fit(X_train, y_train)
# -----------------------------------------------------------------

# Print the best hyperparameters found by grid search
print("Best hyperparameters:", grid_search.best_params_)

# Make predictions on the validation data using the best model
y_pred = grid_search.predict(X_val)

# Decode the predicted target variable
y_pred = le.inverse_transform(y_pred)

# Evaluate the performance of the model
accuracy = np.mean(y_pred == le.inverse_transform(y_val))
print(f"Accuracy: {accuracy:.2f}")

# Output
>> Best hyperparameters: {'learning_rate': 0.2, 'max_depth': 7, 'n_estimators': 300}
>> Accuracy: 0.94

步骤 4:运行预测

最后,打包文本处理步骤,加载训练好的分类器模型,你就可以在测试数据集上运行预测了。

import pickle
# Save the trained model to a file
filename = 'xgb_model.sav'
pickle.dump(grid_search, open(filename, 'wb'))

# Load the model from file
loaded_model = pickle.load(open(filename, 'rb'))

# Define function to make predictions on new data
def predict_gl_code(text):
    # Preprocess the text data
    text = clean_text(text)

    # Generate embeddings for the text data
    embeddings = model.encode([text], show_progress_bar=False)
    X = np.array(embeddings)

    # Make predictions using the loaded model
    y_pred = loaded_model.predict(X)
    gl_code = le.inverse_transform(y_pred)[0]
    return gl_code

# Add a new column for predictions to the test dataframe
test_set['preds'] = test_set['clean_desc'].apply(predict_gl_code)

# Compute the accuracy of the model on the test data
accuracy = np.mean(test_set['preds'] == test_set['category'])
print(f"Accuracy: {accuracy:.2f}")

# Output 
>> Accuracy: 0.91

这就是各位的内容!! 😃

在没有进行太多特定任务的文本清理的情况下,我们使用了预训练变换器模型生成的嵌入表示,并构建了一个分类器,将其分类到 4 个类别中,准确率为 91%。

测试数据集上的预测(preds)。 (来源:作者)

在测试数据集上预测错误的情况。 (来源:作者)

模型未能正确预测的情况可以通过使用更多的数据进行训练和执行特定任务的文本清理来改进。例如,保留维度编号,因为它有助于区分家庭产品与其他产品。

⚡️ 再次提醒,完整代码可在这个笔记本中找到。 🤗

最终想法

在这篇文章中,我们探讨了利用预训练的大型语言模型(LLMs)定制文本分类的两种不同方法。由于它们生成的高质量嵌入,我们可以以最小的努力取得令人印象深刻的结果。

由于这些 LLMs 已经在大量数据上进行过训练,它们对语言有深入的理解,并且能够有效捕捉文本中意义和语境的细微差别。

既然你知道了这两种方法,尝试一下你的文本分类任务吧! 😃

👉 别忘了关注以获取更多类似的文章。 🤗

🚀 希望你觉得这篇文章对你有帮助。考虑使用我的链接加入 Medium,获取我和其他平台上优秀作者的更多精彩内容!

我的其他文章:

[## 如何从 PDF 文件中提取并转换表格到 Pandas 数据框?

所以你有一些包含表格的 PDF 文件,并且想将它们读取到一个 pandas 数据框中。让我来展示给你看。

如何从 PDF 文件中提取并转换表格到 pandas 数据框 [## 如何调度无服务器 Google Cloud 函数以定期运行

你是否有一些代码需要定期运行?

如何定期调度无服务器 Google Cloud 函数 [## 如何在本地开发和测试你的 Google Cloud 函数

因此,你已经编写了无服务器云函数,但不想浪费时间部署它并希望它能正常工作。让…

如何在本地开发和测试你的 Google Cloud 函数 [## 使用 Google Cloud Functions 将机器学习模型作为无服务器端点

因此,你已经构建了一个模型,并且想将它作为无服务器解决方案在 Google Cloud Platform (GCP) 上投入生产。让我来…

使用 Google Cloud Functions 将机器学习模型作为无服务器端点

如何以全新的视角看待常见的机器学习任务

原文:towardsdatascience.com/how-to-look-at-common-machine-learning-tasks-with-fresh-eyes-eeedd99f39e6?source=collection_archive---------12-----------------------#2023-07-20

TDS 编辑数据科学前沿 TDS 编辑

·

关注 发表在 数据科学前沿 · 发送为 通讯 · 3 分钟阅读 · 2023 年 7 月 20 日

--

我们绝不会推荐仅为了改变而改变稳定且表现良好的工作流程;“如果没坏,就别修” 是一种流行的俗语,原因很简单:这往往是正确的做法。

尽管如此,“非常频繁”和“总是”之间还有相当大的差距,而我们在工作中最令人沮丧的日子通常是当我们经过验证的方法未能产生预期结果或表现不佳时。这时扩展我们的知识基础真的会带来回报:我们不会陷入精神上的“死亡旋转轮”,而是尝试不同的方法,调整我们的过程,并(迟早)找到新的解决方案。

在拥抱新视角的精神下,我们整理了一系列出色的近期帖子,提供了对常见机器学习工作流的新颖解读。它们涵盖了漂移检测和模型训练等过程,以及从图像分割到命名实体识别的任务。为你的工具箱腾出空间——你会想要添加这些内容!

在深入之前,简单更新一下:如果你想寻找其他方式来跟上我们最新的优质文章,我们刚刚推出了几个 Medium 列表以帮助你发现更多精彩阅读。

  • 算法推荐系统无处不在,从电子商务网站到流媒体服务,其输出有时可能显得重复和显而易见。正如Christabelle Pabalan所展示的,我们不必满足于毫无创意的选择—实际上,将推荐系统注入新奇和意外元素可以带来更好的用户留存率。

  • 检测在非结构化数据上训练的模型的漂移,例如用于 LLM 驱动应用的嵌入,“是一个相当新的话题,目前没有‘最佳实践’方法,”Elena Samuylova和 Olga Filippova 表示。为了帮助你选择最有效的方法,他们进行了若干实验,并根据他们的发现分享了清晰的建议。

  • 许多数据科学家和机器学习从业者认为合成数据选项在模型训练中的快速崛起值得庆祝,但也认识到这带来了数据质量和长期表现方面的严重问题。Vincent Vatter为我们介绍了微软的最新研究,指出了前进的有效路径。

  • 模型校准是许多分类任务中的关键步骤,但以优化准确性的方式进行计算可能会很棘手。Maja Pavlovic 提供了关于处理预期校准误差(ECE)的清晰实用教程

图片由 Bonnie Kittle 拍摄,发布在 Unsplash

  • 如果你在最近使用卷积神经网络的图像分割项目中遇到了瓶颈,Dhruv MataniNaresh 提供了一个替代方案:不妨尝试基于视觉变换器的模型

  • 作为 NOS——荷兰公共广播基金会的数据科学家,Felix van Deelen 可以访问丰富的新闻故事语料库;Felix 的首篇 TDS 文章探讨了如何在命名实体识别项目中使用这些文本数据

  • 检测数据中的异常没有一种通用的解决方案,这使得熟悉几种选项成为一个好主意。Viyaleta Apgar 向我们介绍了一种基于高斯分布的适合初学者的技术,并展示了如何在多变量模型的背景下实现它

  • 为了更有效地优化你的回归模型,Erdogan Taskesen 提出了在模型训练的超参数调整步骤中加入贝叶斯风格的建议;该教程包括了一个全面的实现,依赖于 HGBoost 库的强大功能。

感谢你对我们作者的支持!如果你喜欢在 TDS 上阅读的文章,可以考虑成为 Medium 会员——这将解锁我们的全部档案(以及 Medium 上的其他每篇文章)。

直到下一个 Variable,

TDS 编辑部

如何制作 40+个互动图表以分析你的机器学习管道

原文:towardsdatascience.com/how-to-make-40-interactive-plots-to-analyze-your-machine-learning-pipeline-ee718afd7bc2?source=collection_archive---------14-----------------------#2023-03-08

一个关于如何制作干净、互动的 Python 图表以验证你的数据和模型的快速指南

Marco vd BoomTowards Data Science Marco vd Boom

·

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

--

图片由 Markus WinklerUnsplash 提供

介绍

图表已经成为帮助数据科学家和利益相关者理解机器学习项目过程和结果的事实标准工具。在本故事中,我们将向你展示如何使用ATOM库轻松制作干净的交互式图表,以便快速分析数据集、检查管道、评估模型的性能并解释模型的结果。ATOM 是一个开源 Python 包,旨在帮助数据科学家加速机器学习管道的探索。如果你想对这个库有一个温和的介绍,请阅读这个故事。

数据图表

让我们从图表开始,以帮助你更好地理解你正在处理的数据集。如何转换管道和训练模型使用 ATOM 超出了本故事的范围。阅读这个故事或这个故事以了解更多信息。在这里,我们将直接深入到图表制作中。

初始化atom后,创建图表就像调用适当的方法一样简单。除了一些例外,图表是使用plotly库制作的,并以 html 渲染。点击这里查看所有可用数据图表方法的列表。

from atom import ATOMClassifier
from sklearn.datasets import load_breast_cancer

X, y = load_breast_cancer(return_X_y=True, as_frame=True)

atom = ATOMClassifier(X, y)

atom.plot_correlation()
atom.plot_distribution(columns=0)
atom.plot_relationships(columns=(0, 1, 2))

特征选择图表

特征选择图表可以帮助你分析由PCARFECV策略选择的特征。点击这里查看所有特征选择图表的列表。

from atom import ATOMClassifier
from sklearn.datasets import load_breast_cancer

X, y = load_breast_cancer(return_X_y=True, as_frame=True)

atom = ATOMClassifier(X, y)
atom.feature_selection("pca", n_features=5)

atom.plot_pca()
atom.plot_components(show=10)

超参数调优图表

ATOM 使用optuna库来应用超参数调优。通过 optuna 提供的图表也可以通过 ATOM 的 API 直接获取,还有一些额外的图表。点击这里查看所有超参数调优图表的列表。

from atom import ATOMClassifier
from sklearn.datasets import load_breast_cancer

X, y = load_breast_cancer(return_X_y=True, as_frame=True)

atom = ATOMClassifier(X, y)
atom.run(models="RF", metric="f1", n_trials=15)

atom.plot_hyperparameters(params=(0, 1, 2))
atom.plot_parallel_coordinate(params=slice(1, 5))
atom.plot_slice(params=(0, 1, 2))

预测图表

在训练模型之后,使用其在训练集和测试集上的预测来评估模型的性能或检查特征重要性。ATOM 最强大的功能之一是能够在同一图表中绘制多个模型或数据集(例如,训练集与测试集)的结果。

from atom import ATOMClassifier

X = pd.read_csv("https://raw.githubusercontent.com/tvdboom/ATOM/master/examples/datasets/weatherAUS.csv")

atom = ATOMClassifier(X, y="RainTomorrow")
atom.impute()
atom.encode()
atom.run(models=["LR", "RF"])

atom.plot_roc()
atom.plot_prc(models="RF", dataset="train+test")
atom.plot_feature_importance(show=10)

可解释性图表

SHAP(SHapley Additive exPlanations)Python 包使用博弈论的方法来解释任何机器学习模型的输出。它将最优的信用分配与本地解释联系起来,使用经典的Shapley 值及其相关扩展。ATOM 实现了从其 API 直接绘制 SHAP 的 7 种绘图功能的方法。由于这些图不是由 ATOM 生成的,因此它们既不具备交互性,也不能显示多个模型。

from atom import ATOMClassifier
from sklearn.datasets import load_breast_cancer

X, y = load_breast_cancer(return_X_y=True, as_frame=True)

atom = ATOMClassifier(X, y)
atom.run(models="RF")

atom.plot_shap_bar()

atom.plot_shap_beeswarm(show=10)

atom.plot_shap_decision(index=slice(5), show=10)

参数

除了特定于图表的参数外,所有图表共有五个参数:

  • title参数为图表添加标题。默认值不显示任何标题。提供配置(作为字典)以自定义其外观,例如title=dict(text="Awesome plot", color="red")。更多信息请参阅 plotly 的文档

  • legend参数用于显示/隐藏、定位或自定义图表的图例。提供配置(作为字典)以自定义其外观(例如legend=dict(title="Title for legend", title_font_color="red"))或选择以下位置之一:左上角、右上角、左下角、右下角、上中、下中、左中、右中、中间、外部。

  • figsize参数调整图表的大小。

  • filename参数用于保存图表。

  • display参数确定是否显示或返回图表。

from atom import ATOMClassifier
from sklearn.datasets import load_breast_cancer

X, y = load_breast_cancer(return_X_y=True, as_frame=True)

atom = ATOMClassifier(X, y)

atom.plot_distribution(
    columns=0,
    title=dict(
        text="Custom left side title",
        font_color="teal",
        x=0,
        xanchor="left",
    ),
    legend="upper left",
)

结论

我们已经展示了如何使用 ATOM 包制作交互式图表,以快速分析机器学习管道的结果。要查看所有可用图表的列表,请点击这里

欲了解有关 ATOM 的更多信息,请查看软件包的文档。如有错误或功能请求,请随时在GitHub上提交问题或给我发邮件。

相关故事:

  • towardsdatascience.com/atom-a-python-package-for-fast-exploration-of-machine-learning-pipelines-653956a16e7b

  • towardsdatascience.com/how-to-test-multiple-machine-learning-pipelines-with-just-a-few-lines-of-python-1a16cb4686d

  • towardsdatascience.com/from-raw-data-to-web-app-deployment-with-atom-and-streamlit-d8df381aa19f

  • towardsdatascience.com/exploration-of-deep-learning-pipelines-made-easy-e1cf649892bc

  • towardsdatascience.com/deep-feature-synthesis-vs-genetic-feature-generation-6ba4d05a6ca5

  • towardsdatascience.com/from-raw-text-to-model-prediction-in-under-30-lines-of-python-32133d853407

参考文献:

  • 本文中的所有图表均由作者创建。

如何在 R 中使用 ggplot2 制作蜘蛛图

原文:towardsdatascience.com/how-to-make-a-spider-chart-in-r-using-ggplot2-85a4f1898cab?source=collection_archive---------4-----------------------#2023-01-11

一步一步构建蜘蛛图/雷达图

Zvonimir BobanTowards Data Science Zvonimir Boban

·

关注 发布于 Towards Data Science ·8 分钟阅读·2023 年 1 月 11 日

--

图片由 Divyadarshi Acharya 提供,来自 Unsplash

了解到雷达图可以制作引人注目的可视化,我尝试寻找合适的 R 库。当我的搜索几乎没有结果时,我感到惊讶(编辑:在写这篇文章后不久,我创建了**ggvanced**包,其中的**ggspider**函数处理了这个问题。你可以在另一篇文章中了解更多信息。)。我找到的唯一一个包是ggradar,它不在 CRAN 上,只能从 GitHub 安装。

我决定尝试使用titanic包中的泰坦尼克号数据。为了计算所需的数据汇总并将数据转换为绘图所需的形状,我使用了dplyrtidyrtibblepurrscales包。如果你不熟悉这些,请查看我之前关于 R 数据处理的文章。

## 深入探索 tidyverse 使用泰坦尼克号数据

[towardsdatascience.com

library(dplyr)
library(tidyr)
library(tibble)
library(purrr)

Titanic <- titanic_train

Titanic_gr1 <-
  Titanic %>%
  select(Survived:Fare) %>%
  group_by(Pclass) %>%
  summarise(across(c(Age, Fare), mean, na.rm = TRUE))

Titanic_gr2 <-
  Titanic %>%
  select(Survived:Fare) %>%
  group_by(Pclass, Survived) %>%
  summarise(N = n()) %>%
  pivot_wider(names_from = Survived, values_from = N) %>%
  mutate("Survived (%)" = `1`/(`0` + `1`)) %>%
  select(1,4)

Titanic_gr3 <-
  Titanic %>%
  select(Survived:Fare) %>%
  group_by(Pclass, Sex) %>%
  summarise(N = n()) %>%
  pivot_wider(names_from = Sex, values_from = N)

Titanic_gr <- reduce(list(Titanic_gr1, 
                          Titanic_gr2, 
                          Titanic_gr3), left_join) %>%
  rename(Male = "male", Female = "female")
Titanic_gr

# A tibble: 3 × 6
  Pclass   Age  Fare `Survived (%)` Female  Male
   <int> <dbl> <dbl>          <dbl>  <int> <int>
1      1  38.2  84.2          0.630     94   122
2      2  29.9  20.7          0.473     76   108
3      3  25.1  13.7          0.242    144   347

安装并加载ggradar包并将数据重新缩放到(0, 1)区间后,我们可以通过调用ggradar函数来创建雷达图。

library(ggplot2)
library(scales)
library(devtools)
install_github("ricardo-bion/ggradar", dependencies=TRUE)
library(ggradar)

Titanic_gr %>%
  mutate(across(-Pclass, normiranje)) %>%
  ggradar(legend.title = "Passenger class") +
  theme(legend.position = "bottom", legend.title = element_text(size = 17))

使用 ggradar 包制作的雷达图。图片来源:作者

尽管获得的图表整洁且视觉上令人愉悦,但我希望每个变量有独立的轴。虽然雷达图和蜘蛛图的名称通常可以互换使用,但我认为在每个变量都有自己的轴的情况下,蜘蛛图更为合适,而雷达图则适用于上述图片中的情况。不幸的是,因为我更感兴趣的是创建蜘蛛图,并且这个包中没有这种选项,所以我不得不自行解决这个问题。

进入 ggplot2

ggplot2的美在于其底层的图形语法,允许通过将多个图层叠加在一起创建图表。这一强大的概念让我们能够创建几乎任何可视化,只要我们知道如何编码。我将使用它从零开始构建一个蜘蛛图。

使用 ggplot2 包构建图表背后的哲学。图片来源:作者

分层方法要求分别构建图表的不同方面。首先我们将创建图表轮廓。由于蜘蛛图涉及极坐标,我编写了一个函数,该函数根据数据集中变量的数量计算多边形顶点的坐标。

library(dplyr)
library(tidyr)
library(tibble)
library(purrr)
library(scales)
library(ggplot2)

p_data <- Titanic_gr %>% rename(group = "Pclass")

circle_coords <- function(r, n_axis = ncol(p_data) - 1){
  fi <- seq(0, 2*pi, (1/n_axis)*2*pi) + pi/2
  x <- r*cos(fi)
  y <- r*sin(fi)

  tibble(x, y, r)
}

central_distance <- 0.2

step_1 <- map_df(seq(0, 1, 0.25) + central_distance, circle_coords) %>%
    ggplot(aes(x, y)) +
    geom_polygon(data = circle_coords(1 + central_distance), 
                 alpha = 1, fill = "gray97") +
    geom_path(aes(group = r), lty = 2, alpha = 0.5) +
    theme_void()

第一步:创建图表的背景。图片来源:作者

接下来,我们需要计算坐标轴的坐标并将它们添加到图表中。

axis_coords <- function(n_axis){
  fi <- seq(0, (1 - 1/n_axis)*2*pi, (1/n_axis)*2*pi) + pi/2
  x1 <- central_distance*cos(fi)
  y1 <- central_distance*sin(fi)
  x2 <- (1 + central_distance)*cos(fi)
  y2 <- (1 + central_distance)*sin(fi)

  tibble(x = c(x1, x2), y = c(y1, y2), id = rep(1:n_axis, 2))
}

step_2 <- step_1 + geom_line(data = axis_coords(ncol(p_data) - 1), 
aes(x, y, group = id), alpha = 0.3)

为数据集中的每个变量添加坐标轴。图片由作者提供

现在我们可以添加文本标签和坐标轴的名称。

text_data <- p_data %>%
  select(-group) %>%
  map_df(~ min(.) + (max(.) - min(.)) * seq(0, 1, 0.25)) %>%
  mutate(r = seq(0, 1, 0.25)) %>%
  pivot_longer(-r, names_to = "parameter", values_to = "value")

text_coords <- function(r, n_axis = ncol(p_data) - 1){
  fi <- seq(0, (1 - 1/n_axis)*2*pi, (1/n_axis)*2*pi) + pi/2 + 0.01*2*pi/r
  x <- r*cos(fi)
  y <- r*sin(fi)

  tibble(x, y, r = r - central_distance)
}

labels_data <- map_df(seq(0, 1, 0.25) + central_distance, text_coords) %>%
  bind_cols(text_data %>% select(-r))

step_3 <- step_2 + 
    geom_text(data = labels_data, aes(x, y, label = value), alpha = 0.65) +
    geom_text(data = text_coords(1 + central_distance + 0.2), 
              aes(x, y), label = labels_data$parameter[1:(ncol(p_data)-1)])

添加坐标轴名称和标签。图片由作者提供

现在剩下的工作就是叠加数据点。

rescaled_coords <- function(r, n_axis){
  fi <- seq(0, 2*pi, (1/n_axis)*2*pi) + pi/2
  tibble(r, fi) %>% mutate(x = r*cos(fi), y = r*sin(fi)) %>% select(-fi)
}

rescaled_data <- p_data %>% 
  mutate(across(-group, rescale)) %>%
  mutate(copy = pull(., 2)) %>% 
  pivot_longer(-group, names_to = "parameter", values_to = "value") %>%
  group_by(group) %>%
  mutate(coords = rescaled_coords(value + central_distance, ncol(p_data) - 1)) %>%
  unnest

step_4 <- step_3 + 
    geom_point(data = rescaled_data, 
               aes(x, y, group = group, col = group), 
               size = 3) +
    geom_path(data = rescaled_data, 
              aes(x, y, group = group, col = group), 
              size = 1)

用数据点叠加图表。图片由作者提供

以及一些最后的美学修饰……

step_5 <- step_4 + 
    labs(col = "Passenger class") +
    theme(legend.position = "bottom",
          legend.text = element_text(size = 12),
          legend.title = element_text(size = 12)) 

最终的蛛网图。图片由作者提供

哒哒,我们的工作完成了。让我们再花一点时间评论一下显示的数字。头等舱乘客是三者中年龄最大、最富有的。三等舱乘客中男性和女性人数最多,是最年轻的一组——可能大多数是年轻人和寻求更好生活的家庭。然而,头等舱乘客的生还率最高,而三等舱的最低。这可能部分是因为头等舱距离船甲板较近,部分是因为该舱等级的女性比例较高(因为女性和儿童是优先救援的)。

奖励积分

为了使图表更容易复现,我将上述代码制作成了一个函数。

library(dplyr)
library(tidyr)
library(tibble)
library(purrr)
library(scales)
library(ggplot2)

ggspider <- function(p_data, 
legend_title = "Group", 
background_color = "gray97", 
area_fill = TRUE, 
central_distance = 0.2, 
axis_name_offset = 0.2){

circle_coords <- function(r, n_axis = ncol(p_data) - 1){
  fi <- seq(0, 2*pi, (1/n_axis)*2*pi) + pi/2
  x <- r*cos(fi)
  y <- r*sin(fi)

  tibble(x, y, r)
}

(step_1 <- map_df(seq(0, 1, 0.25) + central_distance, circle_coords) %>%
    ggplot(aes(x, y)) +
    geom_polygon(data = circle_coords(1 + central_distance), alpha = 1, fill = background_color) +
    geom_path(aes(group = r), lty = 2, alpha = 0.5) +
    theme_void())

axis_coords <- function(n_axis){
  fi <- seq(0, (1 - 1/n_axis)*2*pi, (1/n_axis)*2*pi) + pi/2
  x1 <- central_distance*cos(fi)
  y1 <- central_distance*sin(fi)
  x2 <- (1 + central_distance)*cos(fi)
  y2 <- (1 + central_distance)*sin(fi)

  tibble(x = c(x1, x2), y = c(y1, y2), id = rep(1:n_axis, 2))
}

text_data <- p_data %>%
  select(-group) %>%
  map_df(~ min(.) + (max(.) - min(.)) * seq(0, 1, 0.25)) %>%
  mutate(r = seq(0, 1, 0.25)) %>%
  pivot_longer(-r, names_to = "parameter", values_to = "value")

text_coords <- function(r, n_axis = ncol(p_data) - 1){
  fi <- seq(0, (1 - 1/n_axis)*2*pi, (1/n_axis)*2*pi) + pi/2 + 0.01*2*pi/r
  x <- r*cos(fi)
  y <- r*sin(fi)

  tibble(x, y, r = r - central_distance)
}

labels_data <- map_df(seq(0, 1, 0.25) + central_distance, text_coords) %>%
  bind_cols(text_data %>% select(-r))

rescaled_coords <- function(r, n_axis){
  fi <- seq(0, 2*pi, (1/n_axis)*2*pi) + pi/2
  tibble(r, fi) %>% mutate(x = r*cos(fi), y = r*sin(fi)) %>% select(-fi)
}

rescaled_data <- p_data %>% 
  mutate(across(-group, rescale)) %>%
  mutate(copy = pull(., 2)) %>%
  pivot_longer(-group, names_to = "parameter", values_to = "value") %>%
  group_by(group) %>%
  mutate(coords = rescaled_coords(value + central_distance, ncol(p_data) - 1)) %>%
  unnest

  step_1 + 
    geom_line(data = axis_coords(ncol(p_data) - 1), 
              aes(x, y, group = id), alpha = 0.3) +
    geom_point(data = rescaled_data, 
              aes(x, y, group = group, col = group), size = 3) +
    geom_path(data = rescaled_data, 
              aes(x, y, group = group, col = group), size = 1) +
    {if(area_fill == TRUE) geom_polygon(data = rescaled_data, 
                            aes(x, y, group = group, 
                                col = group, fill = group), 
                                size = 1, alpha = 0.05, show.legend = FALSE)} +
    geom_text(data = labels_data, 
                     aes(x, y, label = value), alpha = 0.65) +
    geom_text(data = text_coords(1 + central_distance + axis_name_offset), 
              aes(x, y), label = labels_data$parameter[1:(ncol(p_data)-1)]) +
    labs(col = legend_title) +
    theme(legend.position = "bottom",
          legend.text = element_text(size = 12),
          legend.title = element_text(size = 12))
}

现在,我们可以在一些不同的数据上轻松测试这个函数,比如内置于 R 中的mtcars数据集。

p_data <- mtcars %>%
  rownames_to_column(var = "group") %>%
  as_tibble() %>%
  tail(3) %>%
  select(1:7)

ggspider(p_data)

mtcars 数据集的蛛网图。图片由作者提供

结论

本文展示了如何在 R 中从头构建蛛网图。虽然我知道的 R 包中没有支持这种图表的,但利用ggplot2包中的分层图形语法方法为我提供了自己创建这种图表的工具。当然,图表的美学方面仍有改进的空间,但那是另一个文章的话题。

就这些了。我希望你觉得这篇文章有用,并将其用于未来制作更多精美的蛛网图。享受吧!

附言:如果你喜欢这篇文章,请点赞并关注,以便未来获得更多类似内容。此外,随时欢迎提出你认为值得在 R 中实现的其他有趣可视化建议。

如何制作 AI 图像编辑聊天机器人

原文:towardsdatascience.com/how-to-make-an-ai-image-editing-chatbot-1ddd0209884

Stable Diffusion InstructPix2Pix 在 Panel 应用中

Sophia Yang, Ph.D.Towards Data Science Sophia Yang, Ph.D.

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

--

Sophia YangPhilipp Rudiger

使用 InstructPix2Pix 进行图像编辑的示例。(原图由 Pietro De Grandi 提供,来源于 Unsplash

InstructPix2Pix 是一种条件扩散模型,将语言模型 GPT-3 和文本到图像模型 Stable Diffusion 结合起来,根据用户提示进行图像编辑。受到 InstructPix2Pix 项目 和在 HuggingFace 上托管的 多个 应用 的启发,我们有兴趣在 Panel 中创建一个 AI 图像编辑聊天机器人。Panel 是一个 Python 仪表板工具,可以让我们通过仅几行代码构建这个聊天机器人。

在本文中,我们将逐步展示如何制作这个聊天机器人:

Stable Diffusion InstructPix2Pix 聊天机器人在 Panel 应用中

通过 Anaconda 设置 Python

由于我们要制作一个 Python 应用程序,因此首先需要安装 Python。下载 Anaconda 是获取 Python 和 Conda 环境管理的最简单和推荐的方式。

然后你需要创建一个新的环境(可选但推荐),并安装此项目所需的包。

conda create -n chatbot_env
conda activate chatbot_env
conda install diffusers transformers accelerate xformers hvplot panel notebook -c pytorch -c conda-forge

启动 Jupyter Notebook

接下来,我们可以启动一个 Jupyter Notebook(在命令行中输入jupyter notebook),并在笔记本中导入所需的包:

import io

import numpy as np
import panel as pn
import param
import PIL
import requests
import torch

from diffusers import StableDiffusionInstructPix2PixPipeline

pn.extension('texteditor', template="bootstrap", sizing_mode='stretch_width')

使用 InstructPix2Pix 模型 API

这个 Hugging Face 页面详细介绍了如何使用 InstructPix2Pix 模型 API。我们需要定义 InstructPix2Pix 模型的 ID 并从 StableDiffusionInstructPix2PixPipeline 获取预训练模型。注意,建议在 GPU 上运行此模型。

在我们的面板应用中,我们直接使用这个预训练模型。我们创建了一个辅助函数normaize_image来规范化输入图像的大小和通道,然后创建了一个函数new_image,基于提示、原始图像和一些高级特征设置来创建新图像。

model_id = "timbrooks/instruct-pix2pix"

pipe = pn.state.cache['pipe'] = StableDiffusionInstructPix2PixPipeline.from_pretrained(
    model_id, torch_dtype=torch.float16
).to("cuda")

def normalize_image(value, width):
    """
    normalize image to RBG channels and to the same size
    """
    b = io.BytesIO(value)
    image = PIL.Image.open(b).convert("RGB")
    aspect = image.size[1] / image.size[0]
    height = int(aspect * width)
    return image.resize((width, height), PIL.Image.LANCZOS)

def new_image(prompt, image, img_guidance, guidance, steps, width=600):
    """
    create a new image from the StableDiffusionInstructPix2PixPipeline model
    """
    edit = pipe(
        prompt,
        image=image,
        image_guidance_scale=img_guidance,
        guidance_scale=guidance,
        num_inference_steps=steps,
    ).images[0]
    return edit

创建面板应用

步骤 1:定义面板小部件

为了制作这个应用,我们需要以下小部件:

  • 一个文件输入小部件,用于加载图像file_input

  • 一个文本输入小部件,允许用户输入提示prompt

  • 三个滑块小部件,用于控制特征设置:img_guidanceguidancesteps

  • 一个按钮小部件,用于显示运行应用的按钮run_button

然后,我们将所有小部件组织成一个Panel对象:widgets

file_input = pn.widgets.FileInput(width=600)
prompt = pn.widgets.TextEditor(
    value="", placeholder="Enter image editing instruction here...", height=160, toolbar=False
)
img_guidance = pn.widgets.DiscreteSlider(
    name="Image guidance scale", options=list(np.arange(1, 10.5, 0.5)), value=1.5
)
guidance = pn.widgets.DiscreteSlider(
    name="Guidance scale", options=list(np.arange(1, 10.5, 0.5)), value=7
)
steps = pn.widgets.IntSlider(
    name="Inference Steps", start=1, end=100, step=1, value=20
)
run_button = pn.widgets.Button(name="Run!")

widgets = pn.Row(
    pn.Column(prompt, run_button, margin=5),
    pn.Card(
        pn.Column(img_guidance, guidance, steps),
        title="Advanced settings", margin=10
    ), width=600
)

小部件

步骤 2:在一个函数中定义所有对话

这可能是最重要的一步。在这一步中,我们定义了一个名为get_conversations的函数,具有以下功能:

  • 每次加载新文件时,我们都会保存新图像,并准备好进行编辑。

  • 我们将图像规范化为 RGB 通道,并确保它们的大小相同。这在图像尺寸过大时特别有用。如果将大图像输入到模型中,你可能会遇到内存不足的错误。

  • 如果有提示,我们将生成新图像。

  • 所有的提示和图像都会附加到convos列表中,并显示在一个面板对象中。

# define global variables to keep track of things
convos = []  # store all panel objects in a list
image = None
filename = None

def get_conversations(_, img, img_guidance, guidance, steps, width=600):
    """
    Get all the conversations in a Panel object
    """
    global image, filename
    prompt_text = prompt.value
    prompt.value = ""

    # if the filename changes, open the image again
    if filename != file_input.filename:
        filename = file_input.filename
        image = normalize_image(file_input.value, width)
        convos.clear()

    # if there is a prompt run output
    if prompt_text:
        image = new_image(prompt_text, image, img_guidance, guidance, steps)
        convos.extend([
            pn.Row(
                pn.panel("\U0001F60A", width=10),
                prompt_text,
                width=600
            ),
            pn.Row(
                pn.panel(image, align='end', width=500),
                pn.panel("\U0001F916", width=10),
                align='end'
            )
        ])
    return pn.Column(*convos, margin=15, width=575)

步骤 3:将小部件绑定到函数

你可能会想:get_conversations什么时候运行?当我们点击运行按钮或更改任何小部件时会运行。为实现这一点,我们将函数与小部件“绑定”,使函数对小部件的变化做出反应:

# bind widgets to functions
interactive_upload = pn.panel(pn.bind(pn.panel, file_input, width=575, min_height=400, margin=15))

interactive_conversation = pn.panel(
    pn.bind(
        get_conversations, run_button, file_input, img_guidance, guidance, steps
    ), loading_indicator=True
)

步骤 4:定义布局

最后,我们将小部件和对话组织成一个合理的布局,我们使用pn.Column将多个面板对象排列在一个垂直容器中。随意尝试不同的布局 😃

 # layout
pn.Column(
    "## \U0001F60A Upload an image file and start editing!",
    file_input,
    interactive_upload,
    interactive_conversation,
    widgets
).servable(title="Panel Stable Diffusion InstructPix2pix Image Editing Chatbot")

步骤 5:服务应用

现在你已经准备好所有的代码,可以开始服务你的应用了。要在本地服务它,运行panel serve app.py(假设你的文件名为 app.py)。我们还在 Hugging Face 上服务了我们的应用:huggingface.co/spaces/sophiamyang/Panel_InstructPix2Pix。请注意,我们只会保持它在线几天。GPU 是昂贵的!

完整代码

你可以在这里找到完整的代码:

huggingface.co/spaces/sophiamyang/Panel_InstructPix2Pix/blob/main/app.py

你可以在哪里了解更多关于 Panel 的信息?

  • 查看我们之前的博客文章:3 种构建 Panel 可视化仪表板的方法。

  • 查看官方文档:holoviz.org、panel.holoviz.org 和 hvplot.holoviz.org。

  • 查看 awesome-panel.org/ 获取灵感。

  • 如果你有问题或想与其他 Panel 用户联系,请访问 discourse.holoviz.org/.

结论

在本文中,我们展示了如何在 Panel 中构建 AI 图像编辑聊天机器人。使用预训练的 InstructPix2Pix 模型,我们的聊天机器人具备了基于用户提示的惊人图像编辑功能。试试看,并告诉我们你的想法!

致谢:

感谢Jim Bednar的指导和支持!

参考资料:

. . .

Sophia YangPhilipp Rudiger于 2023 年 1 月 31 日发布

Sophia Yang 是 Anaconda 的高级数据科学家。通过LinkedInTwitterYouTube与我联系,并加入 DS/ML 读书俱乐部 ❤️

如何用 Rust 让你的 Python 包真正加速

原文:towardsdatascience.com/how-to-make-your-python-packages-really-fast-with-rust-91a9bebacbc2?source=collection_archive---------2-----------------------#2023-05-31

告别,慢速代码

Isaac Harris-HoltTowards Data Science Isaac Harris-Holt

·

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

--

照片由Chris Liverani拍摄,发布在Unsplash

Python 是……慢。这并不是什么新发现。很多动态语言都是如此。实际上,Python 的速度如此之慢,以至于许多对性能有严格要求的 Python 包作者转向了另一种语言——C。但 C 并不好玩,而且 C 有足够多的陷阱可以让一个蜈蚣陷入困境。

介绍 Rust。

Rust 是一种内存高效的语言,没有运行时或垃圾回收器。它速度极快,可靠性极高,并且有一个非常优秀的社区。此外,由于像 PyO3 和 maturin 这样的优秀工具,它也非常容易嵌入到你的 Python 代码中。

听起来很兴奋?太好了!因为我接下来将一步一步向你展示如何在 Rust 中创建一个 Python 包。如果你对 Rust 一无所知,也不用担心——我们不会做任何过于复杂的事情,所以你仍然可以跟得上。准备好了吗?让我们来氧化这条蛇吧。

前提条件

在开始之前,你需要在你的机器上安装 Rust。你可以通过访问 rustup.rs 并按照那里的说明进行安装。我还建议创建一个虚拟环境,以便用于测试你的 Rust 包。

脚本概述

这是一个脚本,给定一个数字 n,它将计算第 n 个斐波那契数 100 次,并记录所需时间。

这是一个非常初级、完全未优化的函数,使用纯 Python 也有很多方法可以使其更快,但我今天不会讨论那些方法。相反,我们将利用这段代码来创建一个 Rust 中的 Python 包。

Maturin 设置

第一步是安装 maturin,这是一个用于构建和发布 Rust crate 作为 Python 包的构建系统。你可以通过 pip install maturin 来完成安装。

接下来,为你的包创建一个目录。我将其命名为 fibbers。最后一步是从你的新目录中运行 maturin init。此时,你将被提示选择要使用的 Rust 绑定。选择 pyo3

图片来源:作者。

现在,如果你查看你的 fibbers 目录,你会看到一些文件。Maturin 为我们创建了一些配置文件,即 Cargo.tomlpyproject.tomlCargo.toml 文件是 Rust 构建工具 cargo 的配置文件,包含有关包的一些默认元数据、构建选项和 pyo3 的依赖项。pyproject.toml 文件是相当标准的,但它被设置为使用 maturin 作为构建后端。

Maturin 还会为发布你的包创建一个 GitHub Actions 工作流。这虽然是个小事,但在维护开源项目时会让生活变得轻松多了。然而,我们最关心的文件是 src 目录中的 lib.rs 文件。

这是生成的文件结构的概述。

fibbers/
├── .github/
│   └── workflows/
│       └── CI.yml
├── .gitignore
├── Cargo.toml
├── pyproject.toml
└── src/
    └── lib.rs

编写 Rust 代码

Maturin 已经使用我们之前提到的 PyO3 绑定为我们创建了一个 Python 模块的框架。

这段代码的主要部分是 sum_as_string 函数,它被标记为 pyfunction 属性,以及 fibbers 函数,它代表我们的 Python 模块。fibbers 函数实际上只是将 sum_as_string 函数注册到我们的 fibbers 模块中。

如果我们现在安装这个,我们将能够从 Python 调用fibbers.sum_as_string(),一切都会按预期工作。

不过,我首先要做的是将sum_as_string函数替换为我们的fib函数。

这与我们之前编写的 Python 实现完全相同——它接受一个正整数 n 并返回第 n 个斐波那契数。我还将我们的新函数注册到fibbers模块,所以我们可以开始使用了!

基准测试我们的函数

要安装我们的fibbers包,我们只需在终端中运行maturin develop即可。这将下载并编译我们的 Rust 包,并将其安装到我们的虚拟环境中。

图片由作者提供。

现在,在我们的fib.py文件中,我们可以导入fibbers,打印fibbers.fib()的结果,然后为我们的 Rust 实现添加一个timeit案例。

如果我们现在运行第 10 个斐波那契数,你会发现我们的 Rust 函数比 Python 快约 5 倍,尽管我们使用的是相同的实现!

图片由作者提供。

如果我们计算第 20 和第 30 个斐波那契数,我们可以看到 Rust 比 Python 快约 15 倍。

第 20 个斐波那契数结果。图片由作者提供。

第 30 个斐波那契数结果。图片由作者提供。

但如果我告诉你我们还没有达到最大速度呢?

你看,默认情况下,maturin develop会构建 Rust crate 的开发版本,这会放弃许多优化以减少编译时间,这意味着程序运行的速度不会达到最佳。如果我们返回到fibbers目录,再次运行maturin develop,但这次加上--release标志,我们将得到优化后的生产就绪版本的二进制文件。

如果我们现在基准测试第 30 个斐波那契数,我们会看到 Rust 现在比 Python 快了令人惊叹的40 倍

第 30 个斐波那契数,优化版。图片由作者提供。

Rust 的限制

然而,我们的 Rust 实现确实有问题。如果我们尝试使用fibbers.fib()获取第 50 个斐波那契数,你会看到我们实际上遇到了溢出错误,并且得到的答案与 Python 不同。

Rust 经历了整数溢出。图片由作者提供。

这是因为,与 Python 不同,Rust 有固定大小的整数,32 位整数不足以容纳我们的第 50 个斐波那契数。

我们可以通过将 Rust 函数中的类型从u32更改为u64来解决这个问题,但这会使用更多的内存,并且可能不是每台机器都支持。我们还可以使用像num_bigint这样的 crate 来解决这个问题,但这超出了本文的范围。

另一个小限制是使用 PyO3 绑定会有一些开销。你可以在这里看到我仅仅获取第一个斐波那契数,实际上 Python 比 Rust 更快,这要归功于这种开销。

Python 在 n=1 时更快。图片来自作者。

需要记住的事项

本文中的数据不是在完美的机器上记录的。基准测试在我的个人机器上进行,可能无法反映真实世界的问题。请对像这样的微基准测试保持谨慎,因为它们通常不完美,并模拟了许多实际程序的方面。

希望你觉得这篇文章有帮助,并且它鼓励你尝试用 Rust 重写 Python 代码中的性能关键部分。要注意,虽然 Rust 可以加速很多东西,但如果你的 CPU 循环大部分时间都花在系统调用或网络 IO 上,它不会提供太多优势,因为这些减速超出了程序的范围。

如果你想查看更多用 Rust 编写的 Python 包的完整示例,请查看以下示例:

本文基于我的这段视频脚本,所以如果你更倾向于视觉学习,或者希望有快速参考资料,欢迎查看:

或者查看 GitHub 上的代码:

[## youtube/010-creating-python-packages-with-rust at main · isaacharrisholt/youtube

每个人都知道程序速度不是 Python 的强项。这就是为什么有如此多的数据科学…

github.com](https://github.com/isaacharrisholt/youtube/tree/main/010-creating-python-packages-with-rust?source=post_page-----91a9bebacbc2--------------------------------)

保持快速!

Isaac

如何使用 Azure Synapse 遮蔽 PII 数据

原文:towardsdatascience.com/how-to-mask-pii-data-with-fpe-using-azure-synapse-12902547af2b?source=collection_archive---------16-----------------------#2023-05-23

学习如何在大规模情况下使用格式保留加密(FPE),安全地将数据从生产环境移动到测试环境

René BremerTowards Data Science René Bremer

·

关注 发表在Towards Data Science ·7 分钟读·2023 年 5 月 23 日

--

数据遮蔽 — 图片来自Mika BaumeisterUnsplash

1. 介绍

许多企业需要其测试环境中的代表性数据。通常,这些数据是从生产环境复制到测试环境中的。但是,个人可识别信息(PII)数据通常是生产环境的一部分,必须先进行遮蔽。Azure Synapse 可以利用格式保留加密来遮蔽数据,然后将数据复制到测试环境中。参见下方的架构图。

1. 使用 Azure Synapse 的格式保留加密 — 作者图像

在这个博客和库[azure-synapse_mask-data_format-preserved-encryption](https://github.com/rebremer/azure-synapse_mask-data_format-preserved-encryption)中,讨论了如何在 Synapse 中创建可扩展且安全的掩码解决方案。在下一章中,将讨论项目的属性。然后在第三章中部署项目,第四章中测试,并在第五章中得出结论。

2. Synapse 中 PII 掩码应用的属性

Synapse 中 PII 掩码应用程序的属性如下:

  • 可扩展的掩码功能: 基于开源 Python 库如 ff3,可以实现对 ID、姓名、电话号码和电子邮件的 FPE 加密。加密示例为 06–23112312 => 48–78322271,

    Kožušček123a => Sqxbblkd659p, bremersons@hotmail.com => h0zek2fbtw@fr5wsdh.com

  • 安全性: 使用的 Synapse Analytics 工作区具有以下安全措施:私有端点连接到存储账户、Azure SQL(公共访问可以禁用)和其他 100 个数据源(包括本地);托管身份用于验证存储账户、Azure SQL 和 Azure 密钥保管库中的秘密,这些秘密由 ff3 用于加密;RBAC 授权授予对 Azure 存储、Azure SQL 和 Azure 密钥保管库的访问权限,以及 Synapse 数据外泄保护 防止数据被恶意内部人员窃取

  • 性能: 可扩展的解决方案,使用了 Spark。可以通过使用更多 vcores、增加更多执行器(VM)和/或使用更多 Spark 池来扩展解决方案。在一次基本测试中,250MB 数据和 6 列被加密并写入存储中,使用中等规模的 Spark 池,2 个执行器(VM)和每个执行器 8 个 vcores(线程)(总共 16 个 vcores/线程),耗时 1 分 45 秒

  • 编排: Synapse 管道可以端到端地编排整个过程。也就是说,可以使用超过 100 个不同的连接器从云端/本地数据库中提取数据,将其暂存到 Azure 存储中,进行掩码处理,然后再发送回低环境进行测试。

在下图中,定义了安全属性。

2. 掩码应用程序的安全属性 — 作者图像

在下一章中,将部署和配置掩码应用程序,包括测试数据。

3. 在 Synapse 中部署 PII 掩码应用程序

在本章中,项目将变得生动,并在 Azure 中部署。将执行以下步骤:

  • 3.1 前提条件

  • 3.2 部署资源

  • 3.3 配置资源

3.1 前提条件

本教程中需要以下资源:

最后,将下面的 git 仓库克隆到本地计算机。如果你没有安装 git,可以从网页上直接下载 zip 文件。

3.2 部署资源

需要部署以下资源:

  • 3.2.1 Azure Synapse Analytics 工作区: 部署启用了数据泄露保护的 Synapse。确保创建了主要存储帐户。还要确保 Synapse 部署时 1) 启用了管理 VNET,2) 有指向存储帐户的私有端点,3) 仅允许向批准的目标发送出站流量,见下图:

3.2. 启用管理 VNET 和数据泄露保护的 Azure Synapse — 作者提供的图片

  • 3.2.2 Azure Key Vault: 该 Key Vault 将用于存储用于在 [Synapse/mask_data_fpe_prefixcipher.ipynb](https://github.com/rebremer/azure-synapse_mask-data_format-preserved-encryption/blob/main/Synapse/mask_data_fpe_prefixcipher.ipynb)[Synapse/mask_data_fpe_ff3.ipynb](https://github.com/rebremer/azure-synapse_mask-data_format-preserved-encryption/blob/main/Synapse/mask_data_fpe_ff3.ipynb) 中创建 HMAC 和加密的秘密

3.3. 配置资源

需要配置以下资源

  • 3.3.1 存储帐户 - 文件系统: 在存储帐户中,创建名为 bronzegold 的新文件系统。然后上传 [Data\SalesLT.Customer.txt](https://github.com/rebremer/azure-synapse_mask-data_format-preserved-encryption/blob/main/Data/SalesLT.Customer.txt) 中的 csv 文件。如果需要处理更大的数据集,可以参考 250MB 和 1M 记录的集合

  • 3.3.2 Azure Key Vault - 秘密: 创建名为 fpekeyfpetweak 的秘密。确保为两个秘密都添加了十六进制值。如果 Azure Key Vault 是在启用公共访问的情况下部署的(以便通过 Azure Portal 创建秘密),现在不再需要公共访问,可以禁用公共访问(因为在 3.3.4 中将创建 Synapse 和 Azure Key Vault 之间的私有链接连接)

  • 3.3.3 Azure Key Vault - 访问控制: 确保在 Azure Key Vault 的访问策略中,Synapse 管理标识具有获取秘密的权限,见下图。

3.3.3 Synapse 管理标识在 Key Vault 中具有获取秘密的权限 — 作者提供的图片

  • 3.3.4 Azure Synapse Analytics - 私有链接到 Azure Key Vault:从 Azure Synapse 工作区管理的 VNET 和你的密钥库创建一个私有终结点。请求从 Synapse 发起,需要在 AKV 网络中进行批准。见下图,其中私有终结点已获批准。

3.3.4 Synapse 和 Key Vault 之间的私有链接连接 — 作者提供的图片

  • 3.3.5 Azure Synapse Analytics - 链接服务连接到 Azure Key Vault: 从 Azure Synapse 工作区和你的密钥库创建一个链接服务,见下图。

3.3.5 Synapse 和 Key Vault 之间的链接服务以获取密钥 — 作者提供的图片

  • 3.3.6 Azure Synapse Analytics - Spark 集群:创建一个中型的 Spark 集群,具有 3 到 10 个节点,并且可以扩展到 2 到 3 个执行器,见下图。

3.3.6 在 Synapse 中创建 Spark 集群 — 作者提供的图片

  • 3.3.7 Azure Synapse Analytics - 库上传: 笔记本Synapse/mask_data_fpe_ff3.ipynb使用了ff3进行加密。由于 Azure Synapse Analytics 创建时启用了数据泄露保护,因此不能通过从 pypi.org 拉取进行安装,因为这需要在 Azure AD 租户外的出站连接。下载 pycryptodome wheel 这里、ff3 wheel 这里 和 Unidecode 库 这里(Unidecode 库用于将 Unicode 转换为 ASCII,以防在 ff3 中使用大量字母进行数据加密)。然后将 wheels 上传到工作区以使其可信,最后将其附加到 Spark 集群,见下图。

3.3.7 从 Synapse 工作区附加的 Python 包到 Spark 集群 — 作者提供的图片

  • 3.3.8 Azure Synapse Analytics - 笔记本上传: 上传笔记本[Synapse/mask_data_fpe_prefixcipher.ipynb](https://github.com/rebremer/azure-synapse_mask-data_format-preserved-encryption/blob/main/Synapse/mask_data_fpe_prefixcipher.ipynb)[Synapse/mask_data_fpe_ff3.ipynb](https://github.com/rebremer/azure-synapse_mask-data_format-preserved-encryption/blob/main/Synapse/mask_data_fpe_ff3.ipynb)到你的 Azure Synapse Analytics 工作区。确保在笔记本中替换存储帐户、文件系统、密钥库名称和 Keyvault 链接服务的值。

  • 3.3.9 Azure Synapse Analytics - 笔记本 - Spark 会话:打开笔记本 Synapse/mask_data_fpe_prefixcipher.ipynb 的 Spark 会话,确保选择超过 2 个执行器,并使用托管身份运行,请参见下面的截图。

3.3.9 作为托管身份运行 Spark 会话 — 作者提供的图片

4. 测试解决方案

部署和配置所有资源后,可以运行笔记本。笔记本 Synapse/mask_data_fpe_prefixcipher.ipynb 包含掩盖数字值、字母数字值、电话号码和电子邮件地址的功能,见下面的功能描述。

000001 => 359228
Bremer => 6paCYa
Bremer & Sons!, LTD. => OsH0*VlF(dsIGHXkZ4dK
06-23112312 => 48-78322271
bremersons@hotmail.com => h0zek2fbtw@fr5wsdh.com
Kožušček123a => Sqxbblkd659p

如果使用 1M 数据集并加密 6 列,处理大约需要 2 分钟。这可以通过 1) 使用更多 vcores(从中型到大型)进行扩展,使用更多执行器进行扩展,或创建第二个 Spark 池来轻松扩展。请参见下面的截图。

4. 笔记本成功运行 — 作者提供的图片

在 Synapse 中,笔记本可以轻松嵌入到管道中。这些管道可以用于协调活动,首先将数据从生产源上传到存储,运行笔记本来掩盖数据,然后将掩盖后的数据复制到测试目标。一个示例管道可以在 [Synapse/synapse_pipeline.json](https://github.com/rebremer/azure-synapse_mask-data_format-preserved-encryption/blob/main/Synapse/synapse_pipeline.json) 找到。

5. 结论

许多企业需要在测试环境中拥有代表性的样本数据。通常,这些数据是从生产环境复制到测试环境的。在本博客和 Git 仓库 [-synapse_mask-data_format-preserved-encryption](https://github.com/rebremer/azure-synapse_mask-data_format-preserved-encryption) 中,讨论了一种可扩展且安全的掩盖解决方案,该方案利用了 Spark、Python 和开源库 ff3 的强大功能,请参见下面的架构。

5. 使用 Azure Synapse 的格式保留加密 — 作者提供的图片

如何测量和改善预测集成的多样性

原文:towardsdatascience.com/how-to-measure-and-improve-the-diversity-of-forecasting-ensembles-2ec899014d6

使用偏差-方差-协方差分解来分析预测集成

Vitor CerqueiraTowards Data Science Vitor Cerqueira

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

--

图片来源于 henry perksUnsplash

在本文中,你将学习偏差-方差-协方差分解方法。

回归模型的误差可以通过偏差-方差权衡来分析。对于集成模型,这种误差可以通过协方差项进一步分解。

以下是如何使用这种分解来改进预测集成的方法。

介绍

个体模型之间的多样性是构建成功集成的关键要素

每个模型应该做出准确的预测。但这些预测也应该与其他模型不同。因此,综合预测可以减少个体误差的影响。

这引出了两个问题:

  1. 我们如何测量集成的多样性?

  2. 我们如何在集成中引入多样性?

让我们深入探讨这些问题。

测量多样性

偏差-方差权衡 是分析回归模型的标准方法。偏差涉及预测值与实际值之间的平均距离。方差涉及不同样本上的预测变异性。

低偏差意味着高方差,反之亦然。这种权衡与模型的复杂性有关。越来越复杂的模型往往具有较低的偏差(但方差较高)。

预测集成也是一种回归模型。它可以分解为这两个术语。但,最好使用三重分解来分析:即偏差-方差-协方差分解。

这种分解定义如下:

上述方程中的术语是具有M模型的集成的平均偏差、平均方差和平均协方差

我们已经知道了偏差和方差项。除了这些,集成的期望误差还取决于协方差项。协方差衡量一对模型如何一起变化。因此,它是量化多样性的好方法。协方差值越大(即多样性越低),期望误差也越大。

你可以这样理解:

期望误差 = 平均偏差 + 平均方差 - 多样性

因此,集成多样性直接影响其期望的预测性能。

下面是如何编码这个分解的方法:

import numpy as np
import pandas as pd

class BiasVarianceCovariance:

    @classmethod
    def get_bvc(cls, y_hat: pd.DataFrame, y: np.ndarray):
        return cls.avg_sqr_bias(y_hat, y), cls.avg_var(y_hat), cls.avg_cov(y_hat)

    @staticmethod
    def avg_sqr_bias(y_hat: pd.DataFrame, y: np.ndarray):
        """
        :param y_hat: predictions as pd.DataFrame with shape (n_observations, n_models).
        The predictions of each model are in different columns
        :param y: actual values as np.array
        """
        return (y_hat.mean(axis=0) - y.mean()).mean() ** 2

    @staticmethod
    def avg_var(y_hat: pd.DataFrame):
        M = y_hat.shape[1]

        return y_hat.var().mean() / M

    @staticmethod
    def avg_cov(y_hat: pd.DataFrame):
        M = y_hat.shape[1]
        cov_df = pd.DataFrame(np.cov(y_hat))
        np.fill_diagonal(cov_df.values, 0)
        cov_term = cov_df.values.sum() * (1 / (M * (M - 1)))

        return cov_term

你可以在参考文献[1]中找到有关偏差-方差-协方差分解的更多细节。

创建多样化的集成

Franki ChamakiUnsplash上的照片

偏差-方差-协方差分解显示了鼓励集成多样性的重要性。你如何做到这一点?

这里有三种可能的方法:

  1. 操作训练数据;

  2. 使用不同的算法或配置;

  3. 剪枝集成。

操作训练数据

一些最成功的集成方法遵循这种方法。例如,bagging 和 boosting。

Bagging 是一个决策树的集成。对于每棵树,使用自助法技术对可用数据进行重采样。因此,每棵树都有不同的训练集,从而引入了多样性。随机森林也进行自助法采样,并且还有更多操作。他们向解释变量的使用方式中添加了随机性,这进一步增加了树之间的多样性。

提升也会改变输入数据,但方式不同。一个关键方面是模型是按顺序训练的。每次迭代后,训练实例会根据之前的错误重新加权。

使用不同的算法或配置

改变算法是一种快速而简便的提高集成多样性的方法。

不同的方法(例如决策树和线性回归)对数据有不同的假设。这导致模型表现不同。

集成剪枝

提高多样性的另一种方法是通过集成剪枝

剪枝指的是从集成中移除不需要的模型。在这种情况下,你会丢弃高度相关的模型。这不仅能获得更好的多样性,还能降低成本。

案例研究:随机森林的分解

让我们使用偏差-方差-协方差分解来分析随机森林的误差。

在这个例子中,我们将使用有关太阳黑子的时间序列。

月度太阳黑子时间序列[3]。图片作者。

你可以按如下方式训练随机森林进行预测:

import numpy as np
import pandas as pd

from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error

from pmdarima.datasets import load_sunspots

# src module here: https://github.com/vcerqueira/blog
from src.tde import time_delay_embedding
from src.ensembles.bvc import BiasVarianceCovariance

# load time series 
series = load_sunspots(as_series=True) # GPL-3

# train test split
train, test = train_test_split(series, test_size=0.3, shuffle=False, random_state=1)

# time series for supervised learning
train_df = time_delay_embedding(train, n_lags=12, horizon=1)
test_df = time_delay_embedding(test, n_lags=12, horizon=1)

# creating the predictors and target variables
target_var = 'Series(t+1)'
X_train, y_train = train_df.drop(target_var, axis=1), train_df[target_var]
X_test, y_test = test_df.drop(target_var, axis=1), test_df[target_var]

# training a random forest ensemble with 100 decision trees 
rf = RandomForestRegressor(n_estimators=100, random_state=1)
rf.fit(X_train, y_train)

# getting predictions from each tree in RF
rf_pred = [tree.predict(X_test) for tree in rf.estimators_]
rf_pred = pd.DataFrame(rf_pred).T

# bias-variance-covariance decomposition
rf_a_bias, rf_a_var, rf_a_cov = BiasVarianceCovariance.get_bvc(rf_pred, y_test.values)

这是预测样本:

随机森林中树的预测样本。组合预测用粗体藏青色表示。图片由作者提供。

这里是错误分解成每一项的方式:

偏差、方差和协方差项(对数缩放)。图片由作者提供。

你可以利用这些信息来指导集成模型的发展。

在这个例子中,大部分的期望误差是由于协方差项造成的。你可以通过提高集成的多样性来减少它。我们探讨了三种方法来做到这一点。例如,你可以尝试通过移除相关的树来修剪集成。

关键要点

  • 预测集成的期望误差可以分解为三个部分:偏差、方差和协方差;

  • 协方差项衡量集成中的多样性;

  • 这种分解对指导集成模型的发展非常有价值;

  • 提高集成多样性有很多方法。这些方法包括操作训练数据、使用不同的算法或进行集成修剪。

感谢阅读,我们下一个故事见!

相关文章

参考文献

[1] Brown, G., Wyatt, J., Harris, R., & Yao, X. (2005). 多样性创建方法:调查与分类。信息融合, 6(1), 5–20。

[2] Brown, Gavin, 等。“回归集成中的多样性管理。” 机器学习研究杂志 6.9 (2005)。

[3] 太阳黑子时间序列。GPL-3 许可证

如何测量机器学习嵌入的漂移

原文:towardsdatascience.com/how-to-measure-drift-in-ml-embeddings-ee8adfe1e55e?source=collection_archive---------0-----------------------#2023-06-14

我们评估了五种嵌入漂移检测方法

Elena Samuylova数据科学前沿 Elena Samuylova

·

关注 发表在 数据科学前沿 ·10 分钟阅读·2023 年 6 月 14 日

--

图片来源:作者。

为什么要监控嵌入漂移?

当机器学习系统投入生产时,你通常不会立即获得真实标签。模型会做出预测或分类,但你不知道它的准确性如何。你必须等一会儿(或者很久!)才能获得标签,以衡量模型的真实质量。

当然,测量实际表现是最有效的。但如果由于反馈延迟而无法做到这一点,则可以查看一些有价值的替代指标。其中之一是检测模型预测的漂移(“模型输出是否与之前不同?”)和模型输入的漂移(“输入到模型的数据是否不同?”)

检测预测和数据漂移可以作为早期预警。 这有助于查看模型是否收到了可能难以处理的新输入。了解环境的变化也可以帮助识别改进模型的方法。

你可以检测到什么类型的问题?例如,如果你的模型进行文本分类,你可能需要注意是否出现了新主题、情感的变化、类别平衡的变化、新语言的文本,或者当你开始接收到大量垃圾邮件或损坏的输入时。

作者提供的图像。

但是如何准确地检测这种变化呢?

如果你处理结构化数据,有许多方法可以用来检测漂移:从跟踪模型输出的描述性统计(最小-最大范围、预测类别的占比等),到更复杂的分布漂移检测方法,从像 Kholmogorov-Smirnov 这样的统计测试到像 Wasserstein 距离这样的距离度量。

然而,如果你正在处理 NLP 或 LLM 驱动的应用程序,你将处理非结构化数据。通常以数值表示的形式存在——嵌入。你如何检测这些数值表示中的漂移呢?

作者提供的图像。

这是一个相当新的话题,目前还没有“最佳实践”方法。为了帮助理解不同的嵌入漂移检测方法,我们进行了一系列实验。在本文中,我们将概述我们评估的方法,并介绍Evidently,一个用于检测嵌入漂移(以及其他功能)的开源 Python 库。

漂移检测基础

漂移检测的核心思想是当“数据发生显著变化”时发出警报。重点是整体分布。

这与异常值检测不同,当你想要检测与其他数据点不同的个别数据点时。

要测量漂移,你需要决定你的参考数据集是什么。 通常,你可以将当前生产数据与验证数据或你认为具有代表性的过去某个时期的数据进行比较。例如,你可以将本周的数据与上周的数据进行比较,并随着时间推移调整参考数据。

这个选择是针对具体用例的:你需要制定一个对数据稳定性或波动性的预期,并选择一个能够充分捕捉你期望的“典型”输入数据和模型响应分布的参考数据。

你还必须选择漂移检测方法并调整警报阈值。 比较数据集的方法和你认为有意义的变化程度有很多种。有时你关注的是微小的偏差,有时则只是显著的变化。为了调整它,你可以使用历史数据来建模你的漂移检测框架,或者选择一些合理的默认值,然后在实际操作中进行调整。

让我们来详细了解一下“如何”。假设你正在处理文本分类用例,并希望比较数据集(以嵌入形式表示)每周如何变化。你有两个嵌入数据集需要比较。你如何准确地衡量它们之间的“差异”?

我们将审查五种可能的方法。

欧几里得距离

作者提供的图片。

你可以对两个分布中的嵌入进行平均,从而得到每个数据集的代表性嵌入。然后,你可以测量它们之间的欧几里得距离。通过这种方式,你可以比较两个向量在多维空间中的“距离”。

欧几里得距离是一种直接且熟悉的度量:它测量连接两个嵌入的线段的长度。你也可以使用其他距离度量,例如余弦、曼哈顿或切比雪夫距离。

作为漂移评分,你将得到一个从 0(对于相同的嵌入)到无穷大的数字。值越高,两个分布之间的距离越远。

这种行为是直观的,但一个可能的缺点是欧几里得距离是一个绝对度量。这使得设置特定的漂移警报阈值更困难:“远”的定义将基于用例和使用的嵌入模型而有所不同。你需要为你监控的不同模型单独调整阈值。

余弦距离

作者提供的图片。

余弦距离是另一种流行的距离度量。它不是测量“长度”,而是计算向量之间角度的余弦值。

余弦相似度在机器学习中被广泛使用于搜索、信息检索或推荐系统等任务。要测量距离,你必须从 1 中减去余弦值。

余弦距离 = 1 — 余弦相似度

如果两个分布相同,余弦相似度将为 1,距离将为 0。距离可以取值从 0 到 2。

在我们的实验中,我们发现阈值的调整可能不太直观,因为对于你已经想要检测的变化,它可能会取值低至 0.001。请明智地选择阈值!另一个缺点是,如果应用像 PCA 这样的降维方法,它将不起作用,导致结果不可预测。

基于模型的漂移检测

图片由作者提供。

基于分类器的漂移检测方法遵循不同的思路。你可以训练一个分类模型,而不是测量平均嵌入之间的距离,该模型试图识别每个嵌入属于哪个分布。

如果模型可以自信地预测特定嵌入来自哪个分布,那么这两个数据集可能是足够不同的。

你可以测量在验证数据集上计算的分类模型的ROC AUC 分数作为漂移分数,并相应地设置阈值。分数高于 0.5 表明至少有一些预测能力,而分数为 1 则表示“绝对漂移”,即模型总是能识别数据属于哪个分布。

你可以在论文《大声失败:检测数据集变化方法的实证研究》中阅读更多关于该方法的信息。

根据我们的实验,这种方法是一个很好的默认选择。它在我们测试的不同数据集和嵌入模型中都表现一致,无论是否使用 PCA。它还具有任何数据科学家都熟悉的直观阈值。

漂移组件的比例

图片由作者提供。

该方法的思想是将嵌入视为结构化的表格数据,并应用数值漂移检测方法——这与你用于检测数值特征漂移的方法相同。每个嵌入的各个组件被视为结构化数据集中的“列”。

当然,与数值特征不同,这些“列”没有可解释的意义。它们是输入向量的一些坐标。然而,你仍然可以测量这些坐标中有多少发生漂移。如果很多发生漂移,数据可能发生了有意义的变化。

要应用此方法,你首先必须计算每个组件的漂移。在我们的实验中,我们使用了Wasserstein(地球移动者)距离并设置了 0.1 的阈值。这一指标的直觉是,当你将阈值设置为 0.1 时,你将注意到“0.1 标准差”大小的变化。

然后,你可以测量漂移组件的总体比例。例如,如果你的向量长度是 400,你可以将阈值设置为 20%。如果超过 80 个组件发生漂移,你将收到漂移检测警报。

这种方法的好处在于你可以在 0 到 1 的范围内测量漂移。你还可以重用你可能用于检测表格数据漂移的熟悉技术。(还有不同的方法,比如 K-L 散度或各种统计测试)。

然而,对某些人来说,这可能是一个限制:你需要设置很多参数。你可以调整基础的漂移检测方法、其阈值以及响应的漂移组件的比例。

总的来说,我们认为这种方法有其优点:它与不同的嵌入模型一致,并且计算速度合理。

最大均值差异 (MMD)

图片由作者提供。

你可以使用 MMD 来测量向量均值之间的多维距离。目标是根据分布的均值嵌入 µp 和 µq 在 再现核希尔伯特空间 F 中区分两个概率分布 p 和 q。

形式上:

另一种表示 MMD 的方法,其中 K 是再现核希尔伯特空间:

你可以将 K 视为某种接近度度量。对象越相似,这个值越大。如果两个分布相同,MMD 应该是 0。如果两个分布不同,MMD 会增加。

图片由作者提供。使用 MMD 比较不同的分布。

你可以在论文 “A Kernel Method for the Two-Sample Problem” 中阅读更多内容。

你可以使用 MMD 度量作为漂移分数。这个方法的缺点是许多人不熟悉这种方法,阈值也不可解释。计算通常比其他方法更长。如果你有理由并且对其背后的数学有坚实的理解,我们建议使用它。

选择哪种方法?

为了形成本方法的直觉,我们通过引入人工漂移到三个数据集进行了一系列实验,并随着漂移的增加估计漂移结果。我们还测试了计算速度。

你可以在 单独的博客 中找到所有的代码和实验细节。

以下是我们的建议:

  • 基于模型的漂移检测以及使用 ROC AUC 分数作为漂移检测阈值是一个很好的默认选择。

  • 跟踪漂移的嵌入组件比例从 0 到 1 是一个接近的选择。只要记住,如果你应用了降维,请调整阈值。

  • 如果你想衡量时间中的“漂移大小”,像欧几里得距离这样的度量是一个不错的选择。然而,你需要决定如何设计警报,因为你将处理绝对距离值。

图片由作者提供。总结来自于 研究博客

需要记住的是,漂移检测是一种启发式方法。 它是潜在问题的代理。你可能需要实验这种方法:不仅选择方法,还需要根据你的数据调整阈值。你还必须仔细考虑参考窗口的选择,并对你认为有意义的变化做出明智的假设。这将取决于你的错误容忍度、用例以及对模型泛化能力的期望。

你也可以将漂移检测与调试分开。 一旦收到可能的嵌入漂移警报,下一步就是调查具体发生了什么变化。在这种情况下,你必须不可避免地回顾原始数据。

如果可能的话,你甚至可以首先从评估原始数据中的漂移开始。 这样,你可以获得有价值的见解:例如,识别顶级词汇,帮助分类器模型决定文本属于哪个分布,或跟踪可解释的文本描述符——如长度或 OOV 词的比例。

使用原始数据进行漂移检测的示例。

Evidently 开源

我们在 Evidently 中实现了所有提到的漂移检测方法,这是一个开源 Python 库,用于评估、测试和监控机器学习模型。

你可以在任何 Python 环境中运行它。只需传递一个 DataFrame,选择包含嵌入的列,并选择漂移检测方法(或使用默认设置!)你还可以将这些检查作为管道的一部分实现,并使用 100 多种其他数据和模型质量检查。

来源:关于 嵌入漂移检测* 的示例笔记本。*

你可以查看 入门教程 以了解 Evidently 的功能,或直接跳转到关于嵌入漂移检测的 代码示例

结论

  • 漂移检测是生产环境中监控机器学习模型的宝贵工具。它有助于检测输入数据和模型预测的变化。你可以将其作为模型质量的代理指标,并提醒你潜在的变化。

  • 漂移检测不仅限于处理表格数据。你还可以监控机器学习嵌入的漂移——例如,在运行自然语言处理(NLP)或基于大语言模型(LLM)的应用时。

  • 在这篇文章中,我们介绍了5 种不同的嵌入监测方法,包括欧几里得距离和余弦距离、最大均值差异、基于模型的漂移检测,以及使用数值漂移检测方法跟踪漂移嵌入的比例。所有这些方法都在开源的 Evidently Python 库中实现。

  • 我们推荐基于模型的漂移检测作为一种合理的默认方法。这是因为它能够使用 ROC AUC 评分作为可解释的漂移检测指标,易于调整和操作。这种方法在不同的数据集和嵌入模型上表现一致,使其在不同项目中实践起来更加方便。

这篇文章基于 Evidently 博客上的研究。感谢 Olga Filippova 共同撰写了这篇文章。

如何衡量您的基于 RAG 的 LLM 系统的成功

原文:towardsdatascience.com/how-to-measure-the-success-of-your-rag-based-llm-system-874a232b27eb?source=collection_archive---------0-----------------------#2023-10-23

使用机器来评估机器

包括一种新的定性评分方法和详细解释。

Nicholaus LawsonTowards Data Science Nicholaus Lawson

·

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

--

由 Stable Diffusion XL 生成的图像

增强生成研究,或 RAG,是今年出现的大型语言模型(LLMs)最常见的使用案例。尽管文本摘要和生成通常是个别用户的重点,但企业已经意识到他们需要利用自己的数据来利用这项技术。回顾我仍然使用 LLM 的情况,文本生成位居高位。我想问 Bard 问题并让它搜索网络;我希望 Claude 重写电子邮件或博客文章,以增强我的内容。但我遇到的最令人兴奋的使用是将我自己的数据输入 LLM。我想搜索我的笔记、电子邮件、日历和 Slack 消息,并让 Llama 充当另一个我(但这个我能记住今天之前发生的细节)。

这不是关于如何构建 RAG 的帖子(已经有很多这样的帖子了……我还在为其他时间写这篇帖子)。今天我们将探讨的是如何评估 RAG 系统。

我们如何从 RAG 中获取数据?

在深入细节之前,我们先设定一个基础。当我们谈论 RAG 时,我们指的是系统的两个部分。

知识源

知识源可以是向量数据库、搜索引擎、加载到内存中的几个文本文件、SQL 数据,或任何存储我们数据的地方。

LLM

一旦我们有了数据,我们将其输入到 LLM 中。这是通过上下文窗口完成的。因此,最终,我们进行搜索,得到一些文本,将找到的文本放入提示中,然后将问题传递给 LLM。模型随后从该上下文窗口中获取所有内容并提供答案。

为什么这很重要?

当我们谈论评估 RAG 系统时,我们必须知道在定义如何评估之前我们要评估什么。我们现在可以看到需要检查两个部分。初始数据检索是最关键的部分。LLM 通常在总结/回答问题方面表现出色,前提是提供了上下文数据。可能缺乏的部分是搜索功能本身。

这些知识源有一些内建的限制。例如,当使用向量数据库存储大型文本文件时,你必须对数据进行‘切块’处理。这是什么意思?假设你有一个 100 页的文档,但数据库一次只能处理 1 页。加载文档并进行搜索时,数据库只能逐页检查(好吧,这有点简化,但请耐心一点;这对于实际应用已经足够接近了)。当我们找到匹配的搜索结果时,确实有可能整个问题的答案不在这一页上。真遗憾!我们只能得到一页!这很好地说明了在担心 LLM 的输出之前需要检查系统这一部分的原因。

我们需要评估什么?

评估初始搜索

这可能不是大多数技术专家想听到的答案。评估结果需要一定程度的人为评估。

为什么?如果一个企业使用的是私有数据,自动化测试来验证搜索结果是否完全准确将很困难。不要担心,这不必完全手动;我们可以自动化其中的一部分。让我们深入了解一下。

我看到有两种实现方式用于初步验证和评估。

第一个选项是为数据集设置一组常见的预期问题,并让人工 QA 团队验证搜索结果。例如,如果你的团队负责为银行构建一个客服问答机器人,一些常见问题可能是:“我必须在账户中保持的最低金额是多少?”,“我怎么还款?”,“我的分行几点开门?”。如果你的 QA 能够提供问题和预期答案,并以类似 CSV 文件的形式提供,这样可以程序化读取,那就最好不过了;然后,我们可以使用一些自动化测试,稍后会在本文中进一步介绍。

如果没有时间或资源,第二种方法是 QA 团队实时搜索和审查。这是早期 POC 和原型的一个选项,但请注意,这不能扩展到实际生产工作负载中。

评估 LLM 响应

一旦我们对知识来源的数据的可靠性感到放心,我们必须确保最终答案的准确性。RAG 系统在减少幻觉的可能性方面表现出色,这可以通过调整底层提示来进一步延伸。然而,它可能会遗漏信息、误解输入的数据,或试图引入训练中的先验知识。

评估这一步骤类似于评估之前的搜索。如果 QA 团队可以提供问题和预期答案,我们可以尝试程序化地评估答案。

现在让我们看看其中的一些选项。

评估框架

需要记住的是,LLM 和 RAG 在其成熟周期中还处于早期阶段。自 ChatGPT 首次亮相以来仅过去了一年,每天都有更多的进展、模型、框架和研究。尽管如此,一些指标正成为衡量这些系统性能的标准方式。

我们不会涵盖评估基础 LLM 的方法。像 ARC、MMLU、HellaSwag 等都针对底层语言模型。你不需要自己运行这些指标;可以查看像这样的站点

llm-leaderboard.streamlit.app/huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard

查看不同模型的表现。我们只对从 RAG 系统中得到的结果感兴趣。

这引导我们关注像 ROUGE、BLEU、BLEURT 和 METEOR 这样的算法。让我们逐一详细了解每个指标。我还会附上一小段代码,展示如何调用每个指标以及输出分数的样子。我引入评估框架以便开始,并包括了我想要评分的参考和答案。

!pip install evaluate --quiet
!pip install rouge_score --quiet
!pip install importlib-metadata --quiet
!pip install datasets==2.10.1 --quiet 
!pip install git+https://github.com/google-research/bleurt.git --quiet
!pip install sacrebleu --quiet
!pip --no-cache-dir install bert_score==0.3.9 --quiet
!pip install sacremoses --quiet
!pip install jiwer==2.5.1 --quiet
!pip install Cython 

import evaluate

# If you have a translation and reference corpus:
predictions = ["In Dungeons & Dragons, the metallic dragons come in brass, bronze, copper, gold, and silver varieties. Each has scales in hues matching their name - brass dragons have brassy scales, bronze have bronze scales, etc. The metallic dragons are generally more benign than the chromatic dragons in D&D lore."]

references =["""The five basic chromatic dragons (red, blue, green, black, and white) and metallic dragons (copper, brass, silver, gold, and bronze) appeared in the fifth edition Monster Manual (2014) in wyrmling, young, adult, and ancient. Gem dragons and other new-to-fifth-edition dragons appeared in Fizban's Treasury of Dragons (2021)"""]

ROUGE(召回导向的摘要评估指标)

ROUGE 是一组用于评估自动摘要和机器翻译输出的指标。它基于系统输出与参考摘要之间的重叠 n-gram 的计数。

#predictions (list): list of predictions to score. Each prediction should be a string with tokens separated by spaces.
#references (list or list[list]): list of reference for each prediction or a list of several references per prediction. Each reference should be a string with tokens separated by spaces.
#rouge_types (list): A list of rouge types to calculate. Defaults to ['rouge1', 'rouge2', 'rougeL', 'rougeLsum'].
#Valid rouge types:
##"rouge1": unigram (1-gram) based scoring
##"rouge2": bigram (2-gram) based scoring
##"rougeL": Longest common subsequence based scoring.
##"rougeLSum": splits text using "\n"
#use_aggregator (boolean): If True, returns aggregates. Defaults to True.
#use_stemmer (boolean): If True, uses Porter stemmer to strip word suffixes. Defaults to False.

rouge = evaluate.load('rouge')

results = rouge.compute(predictions=predictions, references=references, use_aggregator=False)
print(results)
{
'rouge1': [0.3636363636363636], 
'rouge2': [0.06185567010309278], 
'rougeL': [0.22222222222222224], 
'rougeLsum': [0.22222222222222224]
}

BLEU(双语评估指标)

BLEU 是一种用于自动评估机器翻译输出的指标。它基于候选翻译与一组参考翻译的 n-gram 精度。

#predictions (list of strs): Translations to score.
#references (list of lists of strs): references for each translation.
#max_order (int): Maximum n-gram order to use when computing BLEU score. Defaults to 4.
#smooth (boolean): Whether or not to apply Lin et al. 2004 smoothing. Defaults to False.

#bleu (float): bleu score
#precisions (list of floats): geometric mean of n-gram precisions,
#brevity_penalty (float): brevity penalty,
#length_ratio (float): ratio of lengths,
#translation_length (int): translation_length,
#reference_length (int): reference_length

bleu = evaluate.load(“bleu”)

results = bleu.compute(predictions=predictions, references=references,max_order=4)
print(results)
{
'bleu': 0.07342349837092484, 
'precisions': [0.4262295081967213, 0.11666666666666667, 0.03389830508474576, 0.017241379310344827], 
'brevity_penalty': 1.0, 
'length_ratio': 20.333333333333332, 
'translation_length': 61, 
'reference_length': 3
}

BLEURT(基于变换器的 BLEU 回归)

BLEURT 是一种自然语言生成(NLG)的评估指标。它基于 BERT,使得 BLEURT 能够学习词汇和短语之间的统计关系,并识别 NLG 输出中的模式。

BLEURT 已被证明在各种任务上优于其他自然语言生成(NLG)评估指标,如 BLEU 和 ROUGE,包括机器翻译、文本摘要和问答系统。

#output is always a number between 0 and (approximately 1). 
#This value indicates how similar the generated text is to the reference texts, with values closer to 1 representing more similar texts.

bleurt = evaluate.load("bleurt", module_type="metric")
results = bleurt.compute(predictions=predictions, references=references)

print(results)
{
'scores': [0.6028875708580017]
}

METEOR(显式排序翻译评估指标)

METEOR 是一种用于机器翻译输出的自动评估指标。它还具有其他指标所没有的特性,如词干提取、同义词匹配和标准的精确词汇匹配。该指标旨在解决更流行的 BLEU 指标遇到的一些问题,并在句子或片段级别上与人工判断产生良好的相关性。

#predictions: a list of predictions to score. Each prediction should be a string with tokens separated by spaces.
#references: a list of references (in the case of one reference per prediction), or a list of lists of references (in the case of multiple references per prediction. Each reference should be a string with tokens separated by spaces.
#alpha: Parameter for controlling relative weights of precision and recall. The default value is 0.9.
#beta: Parameter for controlling shape of penalty as a function of fragmentation. The default value is 3.
#gamma: The relative weight assigned to fragmentation penalty. The default is 0.5.

#outputs 0-1 - .317 is acceptable score
meteor = evaluate.load('meteor')
results = meteor.compute(predictions=predictions, references=references)

print(results)
{
'meteor': 0.19316493313521543
}

我被承诺会有新东西!

虽然我已经引起了你的注意,但我想介绍一个新的想法。虽然这四种算法会给出一个量化的分数,让你的 QA 团队能够快速确定答案/摘要是否相似,但仍存在一些不足之处。

首先,参考句子和结果可能足够相似以回答用户的问题,但它仍然可能获得较低的分数。运行一组已知的问题和答案以建立良好的基准并将未来的答案与该基准进行比较是至关重要的。

其次,它无法告诉你分数为何受损。是因为重复词汇会受到惩罚吗?还是因为缺少了一些词汇?摘要是否完全遗漏了答案中的重要部分?没有办法得知。

最后,仅仅因为一个回答获得低分并不一定意味着人类会认为这个回答不足或错误。基准在这里可能有帮助,用于确定什么是可接受的评分,但在使用这些评分来判断 RAG 答案时,保持一些怀疑态度是重要的。

LLMs 评分 LLMs

BLEURT 向我们介绍了我们可以以某种方式使用 LLMs 来评估 RAG 系统的答案。如果我们直接利用这一点呢?我们指示 LLM 给我们的答案一个定性评分,并提供列点的理由和评分的叙述解释。这让我们兼得两全其美的效果。我们可以提取一个数值评分来报告给用户和 QA;我们也可以提供更多细节来解释为什么答案得分低。

这里是一个可以用于 ClaudeV2 的示例提示模板。我们传递关于如何希望模型评分我们答案的指令,传递参考数据,并传递我们从 RAG 系统收到的答案。

prompt_data = """
Human: 
You are to rate a summarization on the scale of 0-10, with 0 being completely incorrect and 10 being a perfect summzarization of the given text.
Explain why you give your score.
Give a bullet point list of major differences between the reference and the summary.
I will supply a reference text by denoting REF: and the summarization to compare against with SUMMARY:.

REF:
The five basic chromatic dragons (red, blue, green, black, and white) 
and metallic dragons (copper, brass, silver, gold, and bronze) 
appeared in the fifth edition Monster Manual (2014) in wyrmling, young, adult, and ancient. 
Gem dragons and other new-to-fifth-edition dragons appeared in Fizban's Treasury of Dragons (2021)

SUMMARY:
In Dungeons & Dragons, the metallic dragons come in brass, bronze, copper, gold, and silver varieties. 
Each has scales in hues matching their name - brass dragons have brassy scales, bronze have bronze scales, etc. 
The metallic dragons are generally more benign than the chromatic dragons in D&D lore.

Assistant:"""
 Score: 5/10

Explanation:

The summary captures some accurate high-level information about metallic dragons in D&D, such as listing the main types and noting their more benign nature compared to chromatic dragons. However, it leaves out a lot of key details from the reference text:

- Does not mention the chromatic dragons (red, blue, green, black, white) that were described.
- Does not mention the different age categories (wyrmling, young, adult, ancient).
- Does not mention the Monster Manual or Fizban's Treasury of Dragons sourcebooks.
- Only focuses on metallic dragons, while the reference text had information about both metallic and chromatic dragons.

Major differences:

- No mention of chromatic dragons
- No mention of dragon age categories
- No mention of Monster Manual or Fizban's Treasury of Dragons
- Only discusses metallic dragons, not full scope of reference text
- Does not convey the introduction timeline of dragons across 5th edition books

就这样。如果团队能提供预期的答案,我们可以将 RAG 答案反馈到 LLM 中进行评分。好处是我们不必依赖 LLM 的先验知识,因为我们仍在传输相关数据。我们可以使用与 RAG 系统不同的 LLM,这意味着我们甚至可以让多个模型来评分我们的输出,以确保评估的平衡性。

这种方法还给我们提供了出错的优秀解释。在这个例子中,我问了关于 DND 宇宙中存在哪些龙的问题。评判 LLM 正确识别出它没有提到色彩龙。然而,它还因为没有包括龙的年代、DND 怪物手册或扩展冒险而扣分。这些遗漏对我提出的问题并不重要,但这允许 QA 团队自行决定一次。

接下来我们怎么做?

基于 RAG 的系统及其创建框架每天都在进步。评分它们的新方法和机制也会不断进步。甚至有来自像 LangChain 这样的巨头的工具可以帮助完成这个任务,例如LangSmith

在我们等待更多进展的同时,结合一些手动验证数据和 HuggingFace 度量库或 LLMs 本身,给我们提供了一个很好的开始信任这些系统的方法。

记住,一旦你对你的新 RAG 充满信心并准备投入生产,答案的评估不会停止!作为常规监控和审计工作的一部分,你必须继续存储问题和答案,并计划进行人工评估工作,以评分和标记提供给最终用户的答案。然而,这个话题另说。

如何使用 Python 和 Vertex AI Pipelines 测量碳足迹

原文:towardsdatascience.com/how-to-mesure-the-carbon-footprint-using-vertex-ai-pipelines-3d6bc9695e7b

关于使用 Vertex AI Pipelines 跟踪碳排放的逐步指南

Ana Bildea, PhDTowards Data Science Ana Bildea, PhD

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

--

图片由作者使用 Midjourney 生成。

动机

机器学习已经成为我们日常生活的一部分,因此是时候考虑它对环境的潜在影响了。否则,大自然可能会以自然灾害的形式给我们一个‘我早就说过了’的教训,导致严重的人类痛苦。我们可以通过开始测量和减少机器学习模型的碳足迹来帮助应对气候变化。碳足迹衡量的是服务、产品、个人、组织或事件造成的温室气体排放总量。在机器学习的情况下,它包括训练和运行模型所需的能源,以及运行这些模型的硬件所用的能源。

在这篇文章中,我将对两个开源 Python 库 CodeCarbonCarbonTracker 提供反馈,它们能够估算碳足迹。我还将包括一个在 Vertex AI 管道中使用它们的逐步指南。最后,我将列出减少碳足迹的实际考虑因素。所以,让我们在为时已晚之前开始为拯救地球贡献自己的力量吧!💚

I. Python 中的碳足迹 📗

用于测量 Python 中碳足迹的两个最流行的库是 CodeCarbonCarbonTracker。事实是我们没有很多开源的替代方案。但我相信,一旦社区开始将碳足迹集成到机器学习系统中,我们将会有更多的选择。

让我们先说几句关于库的内容。

CodeCarbon

它是一个开源 Python 库,用于估算运行代码时产生的 CO2。该项目由 Yoshua Bengio 发起。我最欣赏的一点是它非常易于使用,文档良好,并且有一个很棒的仪表盘。估算通过测量总 GPU、CPU 和 RAM 的功耗来完成。然后,它应用您的提供商或国家区域碳强度,如果您使用本地计算机或本地集群。请参考下表以查看各种能源来源的碳强度。

@CodeCarbon 来源

二氧化碳排放估算(CO₂eq)计算如下:

CO₂eq=Power_consumption(kilowatt-hours)*Carbon_Intensity(kg of CO₂/kilowatt-hour) 

请注意,当碳强度不可用时,CodeCarbon 使用世界平均值475 gCO2.eq/KWh。排放量被保存到名为emissions.csv.的 CSV 文件中。

在支持的基础设施方面,它兼容支持NVIDIA 管理库(NVML)的 NVIDIA GPU 和支持Intel RAPL的 Intel CPU。如果您的 CPU 不在支持的 CPU 列表上,它将估算 CPU 的功耗为其热设计功耗(TDP)的 50%,默认 TDP 平均值为 85W。

使用 pip 安装:

pip install codecarbon

它支持两种模式:在线模式离线模式

在线模式 需要互联网连接来获取您的地理位置。请参见下面使用它的示例,带有或不带装饰器:

离线模式 可以在您的设置无法访问互联网时使用。它需要指定国家的 3 个字母 ISO 代码。您可以在维基百科上找到国家ISO代码的列表。

CarbonTracker

CarbonTracker 是一个开源库,旨在通过测量训练所用硬件的功耗来估算训练深度学习模型的碳足迹。目前,它支持 GPU、CPU 和 DRAM 组件。它与支持NVIDIA 管理库 (NVML)的 NVIDIA GPU、支持Intel RAP的 Intel CPU、Slurm 和 Google Colab / Jupyter Notebook 兼容。使用起来很简单,但不幸的是,文档有限。

为了估算碳足迹,它使用以下公式:

Carbon Footprint = Energy Consumption × Carbon Intensity

Energy Consumption 是基于 PUE(功耗使用效率)计算的,这是一种用于衡量数据中心能源效率的指标。它通过将数据中心使用的总能量除以 IT 设备(如服务器、存储等)使用的能量来计算。

它使用与 codecarbon 相同的每个国家Carbon Intensity。当碳强度不可用时,应用475 gCO2.eq/KWh的全球平均值。

可以通过 pip 安装:

pip install carbontracker

使用方法如下面的示例所示:

它还具备在指定目录中收集和存储日志的能力:

from carbontracker import parser
logs = parser.parse_all_logs(log_dir="./"+YOUR_DIR+"/")

现在我们对 CodeCarbon 和 CarbonTracker 有了一定了解,接下来我们将在 Vertex AI Pipeline 中使用它们。

II. 使用 Vertex AI Pipelines 的案例研究👷

有趣的部分现在开始 😄

在继续 Vertex AI Pipelines 之前,我邀请你阅读我的文章,该文章展示了如何使用 Vertex AI Pipelines。

接下来,我将演示如何在两种场景中跟踪碳足迹:

  • 1. 仅使用 CodeCarbon 进行的监督学习(CarbonTracker 仅支持深度学习)。

  • 2. 使用 CodeCarbon 和 CarbonTracker 进行深度学习。

1. CPU 上监督学习的碳足迹

我们将跟踪训练随机森林算法以预测葡萄酒质量时的碳排放。数据从UCI 机器学习库下载,@source [Cortez 等, 2009]。有关数据集的更多细节,请查看我的文章中的用例部分。笔记本可在GitHub上获取。

为了测量训练期间的碳排放,我们需要按如下方式修改笔记本中的 train_winequality 自定义 Kubeflow 组件:

  • codecarbon 添加到 packages_to_install 列表中。

  • 添加一个指标以跟踪 CO2 估算值 kpi_co2:Output[Metrics].

  • 导入 OfflineEmissionsTracker 以使用离线模式(设置时没有互联网连接)。

  • 实例化 codecarbon 跟踪器 tracker = OfflineEmissionsTracker(country_iso_code=”BEL”). BEL 代表比利时。注意国家代码应与所选的 Google Cloud 区域匹配,在我们的例子中是 europe-west1

  • 使用 tracker.start(). 开始跟踪。

  • 使用 .fit() 方法开始训练模型。

  • 使用 tracker.stop() 停止跟踪

  • 记录排放 kpi_co2.log_metric(“emissions”, float(emissions)).

请参见下方我的示例。

一旦你更新 train_winequality 组件并重新运行笔记本,你应该会在管道图上看到 kpi_co2 指标工件。

作者提供的图片

然后,进入 NODE INFO 标签以检查 CO2 排放估算值(以 kg/kWh 为单位)。

❗️ 请注意,要创建一个 Vertex AI Pipeline 并在完成之前不进行监控,你可以使用 submit 而不是 run

现在让我们使用深度学习算法进行练习。

2. 碳足迹使用 GPU 上的深度学习

在这个练习中,我们将使用 Keras(开源 Python 深度学习库)和 Tensorflow(开源机器学习框架)训练深度学习模型。目标是确定照片是否包含表 1 中列出的任何标签。为此,我们使用自定义的 卷积神经网络(CNN)架构进行图像分类。

2.1 数据描述

我们使用 Keras 提供的由 Zalando SE 提供的 Fashion-MNIST 数据集,该数据集基于MIT 许可证。它包括 60,000 张用于训练的图片和 10,000 张用于测试的图片。每张图片都是一个 28x28 的灰度图像,属于 10 种不同的时尚类别,并标记为以下 10 个类别之一:

表 1:包含照片的标签。

2.2 设置环境

  • Vertex AI Workbench

  • Python 3

  • Kubeflow 管道组件

  • 使用 NVIDIA TESLA T4 GPU 预构建镜像

  • Tensorflow 2.11

  • Codecarbon

  • CarbonTracker

然后使用 pip 安装 Google Cloud Pipeline 组件和 TensorFlow。

pip3 install google-cloud-pipeline-components --upgrade --user
pip3 install kfp tensorflow 

导入库。

定义全局变量。

2.3 创建自定义深度学习组件

我将使用tf-gpu.2–11:latest作为基础镜像,该镜像包含 TensorFlow 和 GPU,存储在 Artifact Registry 中。请参见欧洲的预测训练的预构建镜像。请注意,Google 还在Container Registry中发布了预构建镜像。

为了启用codecarboncarbontracker,我们将按如下步骤进行:

  • codecarbon 和 carbontracker添加到packages_to_install列表中。

  • 添加度量来跟踪估计值kpi_co2:Output[Metrics]

  • 导入 CarbonTracker 和日志解析器。

  • 定义一个目录来重定向carbontracker的日志,例如DIR_LOG="log"

  • 加载训练图像并进行缩放。

  • 使用keras.Sequential API 来定义 CNN 的层。

  • 编译模型。

  • 启动codecarbon并指定荷兰地区的 ISO 代码 NLD。

  • 指定 epochs 的数量和 carbontracker 的日志目录carbontracker = CarbonTracker(epochs=epochs, log_dir=”./”+DIR_LOG+”/”)

  • 启动 carbontracker carbontracker.epoch_start()

  • 在训练数据上拟合模型。

  • 停止 carbontracker:carbontracker.epoch_end() & codecarbon codecarbon.stop()

  • 记录排放值并保存模型。

2.4 评估模型

评估组件依赖于预编译的 GPU Tensorflow 2.11 基础镜像。

输入经过训练的模型。然后加载fashion.mist测试数据集。它调整和重塑测试图像,评估模型,并计算准确率、损失和混淆矩阵。

2.5 创建并提交管道

该管道促进了无服务器工作流的编排。我们的管道有两个步骤:训练(deep_learning_mist_task)和评估(model_evaluation_task)。它接受一些参数,如 learning_rate、epochs 的数量、batch_size、API 端点、项目 ID 和服务 URI。

要为管道步骤指定机器配置,请使用:

 .add_node_selector_constraint("cloud.google.com/gke-accelerator", GPU_TYPE )
.set_gpu_limit(GPU_LIMIT)

GPU_TYPE 的可用值包括:

  • NVIDIA_TESLA_A100,

  • NVIDIA_TESLA_K80,

  • NVIDIA_TESLA_P4,

  • NVIDIA_TESLA_P100,

  • NVIDIA_TESLA_T4,

  • NVIDIA_TESLA_V10。

GPU_LIMIT 是一个表示 GPU 限制的正数。

执行管道后,你应该会看到以下图表:

作者提供的图片

转到总结选项卡以查看训练期间 CO2 排放的估算值。

作者提供的图片

我们可以注意到 CO2 估算值不同,因为两个库之间的能耗公式不同。说实话,我更喜欢 Codecarbon,因为它有更好的兼容性和文档。

III. 减少碳足迹的几个实践💡

我建议在设计 AI 算法时考虑一些实际问题。

考虑将碳足迹整合到机器学习模型的整个生命周期中,从数据收集到模型部署。

寻找配备适当处理器(CPU/GPU/TPU)的机器,以适应你的用例。

选择碳足迹较低的云区域。例如,Google Cloud Platform 会指示每个可用区域是否低 CO2。

在你的机器学习生态系统中集成碳足迹跟踪器

选择高效的模型架构。查看可能减少能耗的sparse models

鼓励团队使用云,因为研究表明它更具能源效率

尽可能使用预构建模型,而不是从头开始训练。

在组织中共享数据集、特征库和预构建的专业模型。

关键要点

总之,我推荐使用 Codecarbon,因为它在机器学习(CPU)和深度学习算法(GPU)上表现良好,并且具有更好的基础设施兼容性。关于 CarbonTraker,我在 Google Cloud CPU 上遇到了无法运行的错误,导致浪费时间。如果你打算使用 GPU,记得在使用前验证 GPU可用性。此外,我强烈建议检查GPU 定价以期望更低的成本。最后,重要的是要记住,减少机器学习中的碳足迹是一个持续的过程,新技术和技术不断发展以应对这一问题。确保跟踪有关碳足迹减少策略的更新。

笔记本可以在我的GitHub账户中找到。

希望你喜欢这篇文章。

谢谢阅读!

如果你想在收件箱中收到我的未来故事,别忘了订阅

如果你喜欢阅读我的故事并希望支持我成为作者,请考虑注册成为 Medium 会员,并获得数千篇数据工程和数据科学文章的访问权限。

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

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

medium.com](https://medium.com/@anna.bildea/membership?source=post_page-----3d6bc9695e7b--------------------------------)

LinkedIn Twitter 上找到我!

查看我的 MLops 文章集合

Ana Bildea, PhD

安娜·比尔迪亚博士

MLOps - AI 在生产中

查看列表4 个故事

如何在时间序列中建模多重季节性

原文:towardsdatascience.com/how-to-model-multiple-seasonality-in-time-series-2a3488f8e7f5

处理多个周期的季节性效应

Vitor CerqueiraTowards Data Science Vitor Cerqueira

·发布于 Towards Data Science ·阅读时间 5 分钟·2023 年 7 月 25 日

--

图片由 Joshua Woroniecki 提供,来源于 Unsplash

在这篇文章中,你将学习如何在时间序列中建模多重季节性。我们将涵盖:

  • 如何使用 MSTL 分解时间序列

  • 创建捕捉复杂季节性的解释变量

  • 使用现成的方法,基于orbit的预测包进行示例。

复杂的季节性

季节性指的是以规律的周期性重复的系统性变化。这些模式与时间序列的观察频率有关。低频时间序列通常包含一个季节性周期。例如,每月时间序列表现出年度季节性。

越来越多的时间序列以更高的采样频率收集,如每日或每小时。这导致了具有复杂季节性的大数据集。每日时间序列可能显示出每周、每月和每年的重复模式。

下面是一个具有每日和每周季节性的每小时时间序列示例:

每小时时间序列具有每日和每周的季节性。人工数据和图像由作者创建。

乍一看,以上时间序列似乎没有超过一个季节性模式。多个季节性效应可能相互重叠,这使得识别所有相关周期变得困难。

多重季节性的分解

分解方法旨在将时间序列拆分为其基本部分:趋势、季节性和残差。

大多数方法设计用于处理单一预定义周期的季节性。例子包括经典方法、x11 和 STL 等。

STL 方法已经扩展以处理多重季节性。MSTL(用于多重 STL)在statsmodels Python 包中可用:

import numpy as np
from statsmodels.tsa.seasonal import MSTL

# creating an artificial time series with complex seasonality
# daily and weekly seasonality
period1, period2 = 24, 24 * 7
# 500 data points
size = 500
beta1 = np.linspace(-.6, .3, num=size)
beta2 = np.linspace(.6, -.3, num=size)
sin1 = np.asarray([np.sin(2 * np.pi * i / period1) for i in np.arange(1, size + 1)])
sin2 = np.asarray([np.sin(2 * np.pi * i / period2) for i in np.arange(1, size + 1)])
cos1 = np.asarray([np.cos(2 * np.pi * i / period1) for i in np.arange(1, size + 1)])
cos2 = np.asarray([np.cos(2 * np.pi * i / period2) for i in np.arange(1, size + 1)])

xt = np.cumsum(np.random.normal(scale=0.1, size=size))
noise = np.random.normal(scale=0.1, size=size)

# combining parts
yt = xt + beta1 * sin1 + beta2 * cos1 + sin2 + cos2 + noise

# hourly time series
ind = pd.date_range(end=pd.Timestamp('2023-07-10'), periods=size, freq='H')
yt = pd.Series(yt, index=ind)
yt.name = 'Series'
yt.index.name = 'Date'

# decomposition with MSTL
decomp = MSTL(endog=yt, periods=(period1, period2)).fit()

分解结果包括以下部分:

时间序列及其基本部分,包括两个季节性组件。图像由作者提供。

因此,MSTL 可以用于调整具有复杂季节性的时间序列。

你可以查看上一篇文章,了解如何使用分解后的时间序列构建预测模型。

多重季节性建模

除了分解,还有其他季节性建模方法,这些方法通常专注于具有单一季节周期的时间序列。不过,有些方法也可以处理复杂的季节性。

例如,你可以为不同的时期获取季节性虚拟变量。对于小时级时间序列,你可以获取每个观测值的日期、周或月份等信息。

使用傅里叶级数,你可以通过不同的周期计算正弦和余弦波。以下是如何使用sktime 实现这一点:

from sktime.transformations.series.fourier import FourierFeatures

# Fourier series with two periods
# 4 terms for the first period
# 2 terms for the second period
fourier = FourierFeatures(sp_list=[period1, period2],
                          fourier_terms_list=[4, 2],
                          keep_original_columns=False)

fourier_feats = fourier.fit_transform(yt)

你还可以获取多个周期的径向基函数。

现成的方法

有一些现成的方法可以处理复杂的季节性。例如,TBATS、Prophet、MSTL 或 KTR(基于核的时间变化回归)都是不错的选择。

使用 MSTL,你可以通过分别预测每个组件,然后合并预测的方法来处理这个任务。

下面是如何使用 KTR 的示例,这在orbit Python 包中可用:

from orbit.models import KTR
from orbit.diagnostics.plot import plot_predicted_data, plot_predicted_components
from sklearn.model_selection import train_test_split

df = yt.reset_index()

# train test split
train, test = train_test_split(df, shuffle=False, test_size=100)

# creating a KTR instance with the required periods
ktr_with_seas = KTR(
    response_col='Series',
    date_col='Date',
    seed=1,
    seasonality=[24, 24 * 7],
    estimator='pyro-svi',
    n_bootstrap_draws=1e4,
    # pyro training config
    num_steps=301,
    message=100,
)

# fitting the model
ktr_with_seas.fit(train)

# inference
predicted_df = ktr_with_seas.predict(df=df, decompose=True)

_ = plot_predicted_data(training_actual_df=train,
                        predicted_df=predicted_df,
                        date_col='Date',
                        actual_col='Series',
                        test_actual_df=test,
                        markersize=10, lw=.5)

_ = plot_predicted_components(predicted_df=predicted_df,
                              date_col='Date',
                              plot_components=['trend',
                                               'seasonality_24',
                                               'seasonality_168'])

每个季节性组件都通过傅里叶级数建模,这些级数被用作解释变量。它们的样子如下:

从函数 plot_predicted_components 获取的结果。图像由作者提供。

每个傅里叶波具有不同的特征,以捕捉每个季节性的周期性。

关键要点

高频时间序列可以在多个周期中表现出季节性。捕捉所有季节性模式对于时间序列的最佳建模至关重要。

在这篇文章中,你学到了如何扩展常见的方法以处理多个季节性周期。

一些现成的方法也能够应对这个问题,如 Prophet 或 KTR。

感谢阅读,下次见!

相关文章

代码

参考文献

[1] Orbit 的 KTR 文档:orbit-ml.readthedocs.io/en/stable/tutorials/ktr2.html

[2] 预测:原理与实践,复杂季节性部分:otexts.com/fpp3/complexseasonality.html

[3] Holmes, Elizabeth E., Mark D. Scheuerell, 和 E. J. Ward. “应用于渔业和环境数据的时间序列分析。” NOAA 渔业,西北渔业科学中心,西雅图,WA (2020)。

破解当前数据科学就业市场:来自科技数据科学家的实用策略

原文:towardsdatascience.com/how-to-navigate-the-current-data-science-job-market-6924f9b4a407

为什么现在很难找到数据科学相关的工作,以及你可以做些什么

Khouloud El AlamiTowards Data Science Khouloud El Alami

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

--

我在 2020 年全球疫情期间毕业,那时就业市场是一个充满敌意的领域。现在我很高兴地在 Spotify 担任数据科学家,但走到这一步的过程是漫长的。

在数据科学的就业市场中导航就像在与狮子和小猫的战场上作战。狮子可能会杀死你,而小猫也很可能会妨碍你。你稍后会明白我这句话的意思。

我记得申请了数百个职位却从未收到过一次面试回电。这让我怀疑自己的能力,但回头看来,大多数失败的原因超出了我的控制范围。

在 2020 年,我收到过数百封这样的邮件,但当时的就业市场完全荒凉。

如果你正在经历类似的情况,也许这篇文章可以帮助你找到应对当前就业市场的最佳方法。

最近几个月,我与多位经验丰富的科技数据科学家和有志于此的人员讨论了当前数据科学招聘市场的状况。

我逐渐明白了:

  • 为什么现在变得难以获得聘用

  • 如何最大化获得工作的机会

我将在这篇文章中与您分享这些经验。

但首先,请确保订阅我的通讯!

点击下面的链接,我会给你更多个性化内容和内幕建议,帮助你成为一名数据科学家!

[## 加入+1k 读者 💌 跟随我作为数据科学家在科技和 Spotify 的旅程,不要错过!

加入+1k 读者 💌 跟随我作为数据科学家在科技和 Spotify 的旅程,不要错过!通过注册,你将...

medium.com

工作公式

在当前经济形势下,我越来越多地听到许多数据科学家在找工作时面临的困难。不论是应届毕业生还是来自 MAANG 的被裁员专家,目前大家都在苦苦挣扎。

如果你处于这种情况,请知道你无法找到工作的原因是一个多层次的问题。如果你想更好地调整你的努力,将所有机会放在你这边,了解为什么会发生这种情况是至关重要的。

获得工作机会的机会 = (技能 + 经验) * 市场条件

找工作取决于你的技能、过去的经验,以及最重要的一点:市场条件,这些完全超出了你的控制范围。

目前,求职市场对新加入者并不那么友好。我们都知道经济形势不利,但后续影响是什么?这与你的困境有何关系?

经济衰退的后果

我目睹了许多数据科学家,其中一些我个人认识的人,在全球科技市场裁员潮中失去了工作。

这个玩笑——照片由Annie Spratt提供,Unsplash

这些大规模裁员有 3 个可能影响你目前找工作能力的后续效应,即使这份工作不在科技领域:

  1. 数据专家过剩

  2. 公司员工数量减少

  3. 公司成熟度与规模

#1. 市场饱和

多年来,申请数据科学职位的人数过剩。似乎每个人都被“数据科学”这个被誉为本世纪最热门工作的钩子吸引了。

这也意味着随着时间的推移,数据科学市场正越来越多地被新加入者所饱和。

过剩来自于:

  • 自学的爱好者迎合潮流

  • 数据科学课程的应届毕业生

  • 从事数据科学工作的技术背景人士,包括博士(通常在科技领域非常受欢迎)

  • 从训练营毕业的人或持有数据证书的人

照片由Marvin Meyer提供,Unsplash

这意味着当你申请职位时,你也在与这些人竞争,包括经验丰富的“狮子”和不太合格(或不合格)的“小猫”。这种情况已经存在了一段时间。

让游戏变得更难的现在你还要面对一波新被裁员的专家加入这个圈子。

这就像你在游戏中玩地狱难度级别一样,尽管你什么也没要求(除了工作)。

这也意味着由于两个原因,你被拒绝的机会会更高

1. 你正在与被裁员的技术行业精英竞争。他们有丰富的经验,并且他们可能最终会在过程中超越你。即使你是其中之一,你仍然在和他们竞争。

  • 如果公司规模较大,这些人可能有更大的机会被录用,而对于经验较少的人来说,获得招聘人员回电的难度会更大。

  • 规模较小的公司可能无法承担这些专家的高薪和福利。如果你经验较少,瞄准这些公司可能会增加你的机会。

2. 大多数公司没有复杂的招聘系统。其中一些公司因为缺乏筛选大量申请的手段,最终在筛选过程中错过了优秀的候选人。这意味着你的简历有时可能会被忽略。

从人群中脱颖而出成为你的新目标,但我们稍后会详细讨论这一点。

#2. 人员编制少

这种经济冲击的第二个下游效应是,现在公司更不愿意招聘人员。由于公司现在有不同的优先级(盈利 > 增长),这意味着:

  • 招聘预算在过程中被削减,所以人员编制有限。公司仅根据需求招聘,因此招聘过程可能会非常挑剔。

  • 经验丰富的候选人正处于前列,因为公司能从他们那里获得比更初级员工更多的投资回报。

这意味着你要和更多的人竞争更少的职位。

当我在 2020 年申请工作时,公司由于相同的原因——节省成本——而进行裁员。

不幸的是,劳动力市场非常波动,但这也是好消息,因为这意味着情况也可以迅速发生变化。

#3. 公司规模

并不是很多公司处于需要数据科学家的阶段。我们通常是在公司积累了足够的数据后,或者当数据成为产品时进入游戏。

对于许多科技公司,比如 Netflix,数据就是产品。

他们主要通过分析数据并利用机器学习将其转化为产品来获得大部分价值。

你能想象 Netflix 没有系统推荐符合你口味的娱乐内容吗?数据在这里至关重要,数据科学家也是如此。

不依赖数据来增长和盈利的公司很可能在经济衰退期间会更加谨慎地考虑是否雇佣数据科学家。大多数数据科学家变成了奢侈品。不是每个人都能负担得起我们。

你能做些什么?

如果你想继续参与游戏,你需要稍微调整规则。

由于竞争激烈,你的工作是尽力在众人中脱颖而出。我做的一些事情帮助我和其他数据科学家在游戏中提升水平,包括:

  1. 个人品牌

  2. 正确的网络拓展方式

  3. 专业化

  4. 海外申请

#1. 个人品牌

确保你的 LinkedIn 页面整洁。任何能帮助你看起来很有吸引力的内容都应该在上面。

照片由 Greg Bulla 贡献,来源于 Unsplash

接下来是你的简历。如果你经验很少或者没有经验,那么你需要将你的项目放在最前面。如果你做的数据科学项目不多,那么你没有收到回电也就不足为奇了。回去学习吧!

你可以做的一些事情:

  • 精心制作你的简历。 当然,你不能将简历适应到每家公司。我曾申请过一个需要一些聚类技术知识的职位,因此我确保突出了我做过聚类的项目。因此,你应该做的是:

    — 选择 3-5 个你真正感兴趣且你也符合条件的职位。

    — 研究这家公司当前员工在类似岗位上的技能,并识别你与他们共享的技能,这些技能也出现在职位申请中。

    — 在你的简历上突出那些特定的技能,以及你个人资料中与公司价值观一致的其他任何内容。

  • 创建和优化你的作品集。 它可以是一个网站或在 GitHub 上。重要的是展示你能做什么。确保在你的简历上包含到你的作品集的链接!

  • 突出展示你运用了商业洞察力的项目。 大多数科技公司聘请数据科学家是为了帮助他们进一步实现商业目标。因此,你需要展示你能够在商业背景下理解数据。

我被 Spotify 聘用的主要原因之一(至少我是这么被告知的)是因为我有商业背景和数据科学技能组合。最好的数据科学家是能够将两者结合起来的人,所以展示你可以做到这一点(如果你能的话)!

事实上,我编写了一篇逐步指南,帮助你设计一个成功的数据科学工作简历。 在这篇文章中,我详细分析了让我在 Spotify 获得工作的简历。不要错过!

2. 正确的网络拓展方式

图片来源:作者(Midjourney)

利用你的网络来获得入门机会。我通过网站申请进入 Spotify,这是传统方式。但如果你想提高获得回复的机会,我建议你发挥你的网络作用。这就是我在同一阶段被拒绝后如何获得 Ubisoft 实习机会的方式。

这并不是意味着联系随机的人并请求他们提供推荐。

当有人把你推荐给一家公司时,他们承诺他们推荐的是他们信任的人,是他们知道能够带来积极贡献的人。陌生人不会认识你,因此他们很可能不会回复你的推荐请求。

这意味着你能做的最好的事情 是尝试与已经在你网络中的人建立联系。

最终,你可以依靠的有可能是你曾就读的大学的校友、招聘经理、以前的同事,甚至是朋友或朋友的朋友,但不是你朋友的狗。

重要的是与你可以利用的人建立某种良好的联系。

这并不意味着他们会推荐你,但至少你可能会有机会向他们展示你的能力。这也不意味着你一定能获得这份工作,但至少它可能会让你的简历受到关注,而不是在没有机会的情况下被筛选掉。

我写了一整篇文章,讲述了如何通过建立人脉获得了技术领域的顶级机会,以及你需要遵循的 6 个步骤以在拥挤的数据职位市场中脱颖而出。 一定要去看看!

3. 专业化!

人工智能在各地蓬勃发展,数据职位市场也因此最近增长迅猛,最终分裂成了不同的分支:

  • 数据专家: 数据科学家、数据分析师、数据工程师、机器学习工程师、研究科学家

  • 人工智能专家: 自然语言处理科学家、云计算工程师、计算机视觉科学家、大型语言模型科学家、稳定扩散专家等……

而且这种疯狂的情况只会与日俱增。跟上潮流很困难,但知道要选择哪个潮流可以让你免于溺水。

图片由作者提供(Midjourney)

与所有人交谈就是与任何人交谈

如果你的技能范围已经过于广泛以至于不适合数据市场,那么将技能集中在特定领域可能会提高你的机会。这样,你可能会在与更少的人竞争时变得更具吸引力。

4. 在美国以外申请

数据科学家在美国以外的地方也有发展机会。我这样说是因为许多科技公司总部设在那里,因此在求职市场上挣扎的很多人也在那里。我知道,因为我曾经也是其中之一。

如果美国的招聘放缓了,要知道在世界其他地方招聘并没有完全停止。

在像我所在的法国这样的国家,高质量的数据科学家并不多见,所以如果你没有被限制在特定国家,我建议你拓宽视野,去其他地方看看。

由于美国的数据科学职位市场竞争激烈,搬到法国证明是一个战略性决定,帮助我在 Spotify 获得了梦想中的工作。

在法国,仅数据科学领域目前就有将近 2 万个职位空缺(参见下图),我相信在许多其他国家也是如此。

当然,我理解有些人由于各种原因无法搬到国外。但我们生活在一个后疫情时代,远程工作越来越成为常态。

如果你不能搬到国外,你可以尝试申请其他国家的职位,无论你是否住在美国或其他地方。

我住在巴黎,但我的大部分团队成员分布在欧洲各地。分布式工作正越来越多地使有才华和勤奋的人能找到超越国界的机会。你考虑尝试一下吗?

总结

为什么今天很难找到数据相关的工作?

经济衰退对就业市场造成了 3 个下游影响:

  1. 数据工作市场中应聘者的过度饱和

  2. 招聘有限,由于公司更注重盈利而非增长,导致职位竞争加剧。

  3. 那些不依赖数据来开展业务的公司可能在经济衰退期间会犹豫是否雇佣数据科学家。

你可以做些什么?

  1. 个人品牌。 确保你的个人资料呈现得体,并通过相关项目优化你的作品集,展示你的商业技能。

  2. 正确网络化。 利用你的网络来提高被关注的机会。

  3. 专业化。 将你的技能集中在一个特定领域,以便脱颖而出。

  4. 申请美国以外的职位。 考虑在其他对数据科学家需求增长的国家(包括远程工作)的机会。

当然,这些不是奇迹解决方案。目标是帮助你优化求职方法,提高你的机会。愿好运与你同在!

我有礼物送给你🎁!

订阅我的 通讯 K’s DataLadder,你将自动获得我的终极 SQL 备忘单,其中包含我在大型科技公司工作中每天使用的所有查询+另一个秘密礼物!

我每周分享作为科技领域数据科学家的经历,以及实用的技巧、技能和故事,帮助你提升自己——因为没有人真正知道,直到他们亲身经历!

如果你还没有这样做的话

不久后见!

如何 避免 将机器学习模型投入生产

原文:towardsdatascience.com/how-to-not-get-machine-learning-models-in-production-742e6b79847d

一份讽刺性的指南,避免将生产过程与您的机器学习模型接触

Eirik Berge, PhDTowards Data Science Eirik Berge, PhD

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

--

图片由 Unpad Street Feeding Animal Friend 提供,在 Unsplash

您旅程的概述

  1. 引言 — 没有生产,没问题!

  2. 笔记本可以用来做所有事情!

  3. 为什么要在有时间的时候自动化?

  4. 测试?只需从不出错!

  5. 我的头脑中的依赖管理!

  6. 总结

1 — 引言 — 没有生产,没问题!

作为数据科学家、数据工程师和机器学习工程师,我们被有关生产中机器学习模型的信息淹没。数以百计的视频和数以千计的博客尽力帮助我们避免当前的困境。然而徒劳无功。现在,我痛苦地说,全球各地都有机器学习模型在生产中。 这些模型正在为数百万无知的人创造价值。每个街角都有。我们容忍它,只因为它很常见。

在参加会议时,我听到紧张的数据科学家在大观众面前谈论生产。从他们的额头汗水和湿漉漉的手中可以看出情况的严重性。这种情况已经持续了很多年,但我们没有相信他们的预言。现在看看我们自己。我们应该听从的。

现在不是有人说“我早就告诉过你们”的时候。我们需要团结起来,夺回我们的领地。我们需要单方面表达对现代做事方式的厌恶。我们需要回到更好的时代,当时生产中的机器学习模型只是中型公司招聘广告上的一个非约束性要点。

必须有人带头引导这次救赎之旅。而谁比我更合适呢?不打算吹嘘,我已经做了几个没有投入生产的 ML 模型。其中一些甚至离生产都很远。我可以与你分享一些最佳技巧,这样你就不需要复制你的开发环境——因为将不会有其他环境。

我已经将接下来的每一部分清晰地分为两个部分。第一部分叫做正道。它告诉你如何避免将 ML 模型投入生产。第二部分叫做歪道。这让你知道应该避免什么,因为这是将 ML 模型快速投入生产的捷径。不要搞混!

2 — 一个单一的笔记本可以用来做所有事情!

正道

我发现避免将 ML 模型投入生产的最简单方法之一就是将你的整个代码库放在一个单一的 Jupyter 笔记本文件中。这包括模型训练、模型预测、数据处理,甚至配置文件。

想想看。当所有敏感的配置文件都在你的主文档中时,几乎不可能在不引入重大安全风险的情况下将任何东西上传到 GitHub 或 Azure DevOps。此外,你是否曾尝试阅读一个修改了 100,000 行的文件的拉取请求?最好的回应就是干脆放弃远程托管和版本控制。

这,我的朋友,把我们带上了避免生产的快车道。没有远程托管意味着孤岛将是不可避免的。你有没有停下来思考一下每次需要预测时运行整个模型训练的云消耗成本——记住,我们只有一个用于训练和测试的 Jupyter 笔记本。我自豪地称之为单一笔记本架构的这种方法简直是天才之作。

歪道

相比单一笔记本架构,人们随着时间的推移走了歪路。他们开始将代码、配置和文档拆分到多个文件中。这些文件不幸的是遵循了使其易于导航的惯例。甚至还有像Cookiecutter这样的工具,提供了结构化 ML 项目的易用模板。

代码本身通常被划分为诸如函数、类和文件这样的模块化组件。这鼓励了重用并改善了组织结构。编码标准通过PEP8 标准Black等自动格式化工具来强制执行。甚至还有一些恶心的博客帖子指导数据科学家更好的软件开发实践。真恶心!

不仅仅是源代码被结构化。还有像数据版本控制(DVC)这样的工具用于大数据版本控制,或者像MLflow这样的工具用于结构化机器学习模型打包。这些工具中的任何一个实际上都不难上手。远离这些吧!

3 — 为什么在有时间的时候还要自动化?

图片由Aron Visuals拍摄,来自Unsplash

正义的方式

过去,情况有所不同。我们对我们的代码有一种归属感。这种归属感来自于定期重新审视代码。没错,我们都是手动运行所有代码的。 这显然是上天的旨意,我已经厌倦了假装不是这样。

手动方法让你拥有完全的控制权。不需要检查数据管道是否在凌晨 3 点启动——你就在那儿!每晚都仔细地从记忆中输入执行命令。当你有时间的时候,为什么要依赖CRON 表达式Airflow DAGs来完成工作?到头来,你真的能相信别人吗?

这种手动方法被证明在生产环境中对机器学习模型是个福音。数据漂移发生在机器学习模型上,然后模型需要重新训练。但使用手动方法,必须有人坐镇监视数据漂移是否足够。在这种经济环境下?不太可能!也许还是放弃整个生产环节,回到更好的方法上来吧。

堕落的方式

让我们谈谈明显的问题:持续集成与持续交付(CI/CD)。我知道,这也让我感到愤怒。现在,人们会在更新代码库之前自动检查代码质量并运行测试。像GitHub ActionsAzure DevOps Pipelines这样的工具也用于自动化生产环境中机器学习模型的再训练。我们还有机会吗?

还有更多!现在人们使用像Teraform这样的工具来设置支持生产环境中机器学习模型所需的基础设施。通过这种基础设施即代码的方法,环境可以在各种设置中复制。突然间,生产环境中的机器学习模型从 Azure 变成了 AWS!

还有像Airflow这样的工具帮助进行数据管道的编排。Airflow 确保预处理步骤在执行之前等待前一步骤完成。它还提供了一个 GUI,你可以查看之前的管道运行记录,了解情况。虽然这些功能看似简单,但它们能在错误传播到系统并破坏数据质量之前迅速发现错误。不幸的是,高质量的数据对于成功的生产环境中的 ML 模型至关重要。

4 — 测试?就是不要犯错!

正确的方式

测试的哲学是对无数生产失败和安全漏洞的回应。这个想法是尽可能多地发现问题,然后再推出产品。虽然确实很高尚,但我认为这是误导性的。对于那些关注的人,一个更简单的方法显现出来。正是这样!一旦你看到它,它就像白天一样清晰。你只需不犯任何错误。

对于没有错误的代码,不需要测试。更好的是,当你对代码的有效性 100%确信时,其他人则不那么确信。他们会怀疑你的代码的正确性。因此,他们将避免将你的 ML 模型投入生产,担心一切会崩溃。你知道它不会。但他们不必知道这一点。把你完美的编码技巧留给自己。

不编写测试还有一个额外的好处,那就是你会获得更少的文档。有时测试对帮助他人理解你的代码应该做什么至关重要。 没有测试,甚至更少的人会打扰你,你可以继续平静地工作。

罪恶的方式

编写测试已经变得很普遍,即使是对于 ML 模型和数据工程管道。主要有两类测试:单元测试集成测试。正如名称所示,单元测试测试一个单独的单元,如 Python 函数是否按预期工作。另一方面,集成测试测试不同组件是否无缝地协同工作。让我们关注单元测试。

在 Python 中,你可以使用内置的库unittest来编写,正如你所猜到的,单元测试。这是基于 OOP 的,需要一些样板代码才能开始。越来越多的人使用外部库pytest来编写 Python 中的单元测试。它是功能性而非基于 OOP 的,并且需要较少的样板代码。

编写单元测试还有一个副作用。它迫使你以模块化的方式编写代码。经过充分测试的模块化代码在生产中发生故障的概率较低。这对你的 ML 模型来说是坏消息。如果它们在生产中没有出现故障,那么它们将永远留在那里。考虑一下,ML 模型在生产中出现故障就是它们试图逃脱。谁能怪它们呢?

5 — 头脑中的依赖管理!

正确的方式

管理依赖项是任何机器学习项目的重要部分。这包括知道你使用了哪些 Python 库。我的建议是简单地记住你安装了哪些库、你使用的操作系统、运行时版本等等。 这并不难,我相信甚至有应用程序可以帮助你跟踪这些。

我有时会在夜里醒来,想知道自己运行的是 scikit-learn 的 0.2.3 版本还是 0.3.2 版本。这没关系!所有版本都存在,所以应该没有问题……对吧?如果我不定期解决依赖冲突,那么我的依赖冲突技能就会生疏。

仅仅记住你的依赖项的优势在于其他人很难运行你的代码。尤其是当你不想告诉他们所有依赖细节时。这样,你可以避免有人突然产生偷偷将你的机器学习模型投入生产的想法。

罪恶的方法

记忆力差的人选择了简单的办法。他们寻找可以为你处理依赖的解决方案。我发誓,人们每天都变得更懒惰!管理运行时版本和库依赖的简单方法是使用虚拟环境requirements.txt文件。在 Python 中,有像virtualenv这样的工具,允许你轻松设置虚拟环境。

那些想更进一步的人使用像Docker这样的基于容器的技术。这可以从操作系统及以上层面复制一切。这样,只要每个人知道如何运行 Docker 容器,共享机器学习模型就变得相当轻松。在像Azure Machine Learning这样的现代工具中,有简单的方法来使用标准化的 Docker 镜像。这些镜像可以包括像scikit-learntensorflow这样的常用库。而且你甚至不需要自己记住版本!

6 — 总结

照片由斯宾塞·伯根提供,来自Unsplash

我希望不用指出你确实希望机器学习模型进入生产环境。我在这里提出的观点可以反向操作,帮助你成功将那些麻烦的机器学习模型投入生产。

随时在LinkedIn上关注我并打个招呼,我在这篇博客文章中看起来并没有我表现得那么烦躁 ✋

喜欢我的写作吗? 可以看看我其他的一些帖子,获取更多 Python 内容:

  • 用美丽的类型提示现代化你的罪恶 Python 代码

  • 在 Python 中可视化缺失值简直太简单了

  • 在 Python 中使用 PyOD 介绍异常/离群点检测 🔥

  • 5 个绝妙的 NumPy 函数,关键时刻拯救你

  • 5 个专家技巧,让你的 Python 字典技能飞速提升 🚀

如何在 Python 中客观地比较两个排名列表

原文:towardsdatascience.com/how-to-objectively-compare-two-ranked-lists-in-python-b3d74e236f6a

对 Rank Biased Overlap 的简化解释和实现

KrupeshTowards Data Science Krupesh

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

--

假设你和你的朋友都看过所有 8 部《哈利·波特》电影。

但有一个问题 —— 你是每部电影在首映当天就观看的,没有错过任何一次首映。

然而,你的朋友却是先看了第二部电影,然后是第四部和第五部,最后在 Netflix 上追完了其余的电影。

理论上,你和你的朋友处于同等条件 —— 都看过该系列的所有电影。

它真的平等吗?

图片由作者使用商业可用字体(Harry P)和 CC 图片来自 Wikimedia 制作

作为一个真正的粉丝,你绝对不能将它视为平等。

怎么可能有人不按顺序观看一本书系列的电影,这完全不合理?!

你认为这是亵渎!

好消息是 —— 数学对你有利。 你可以将这个问题简化为排名列表比较。

有几种方法可以比较列表。

在这篇文章中,你将:

  • 理解为什么我们实际上需要一个排名列表的比较

  • 查看比较度量应该满足的要求

  • 了解一种叫做 Rank Biased Overlap 的方法

  • 理解其背后的数学方法

  • 在 Python 中创建一个简单的实现

最后,你将彻底解决与你朋友的分歧:

你 != 你的朋友

为什么你需要排名列表比较?

除了上述的电影观看顺序分歧,我们周围充满了比较列表的例子!

实际上,我们所有人都在不断进行这样的比较。

  • 对同一查询在 Google 和 Bing 上的结果进行比较,哪个更好?

  • 《纽约时报》畅销书榜单与《今日美国》的榜单有多相似?

  • Rotten Tomatoes 上的前 10 部电影排名与 IMDB 相比有多不同?

在自然语言处理和机器学习领域:

  • 两个段落在转换为标记化单词列表后有多相似?

  • 如何比较由两个不同的学习排序/机器学习排名(MLR)¹模型生成的排名输出?

在制造/生产和物流领域:

  • 将传入部分的序列与传出部分的序列进行比较(FIFO)。一个理想的 FIFO 序列如下所示。一个1首先进入,一个1首先出来。

但这在现实世界中可能并不总是发生。也许一个1先进去,但一个3先出来?

是的,这可能导致完全的混乱。

那么,你应该如何衡量序列的质量?

你需要某种度量。

这种度量必须满足哪些要求?

比较度量的要求

理想情况下,比较度量应该以分数的形式出现,指示排名的相似程度。

再次考虑上述示例。

你可以从中得到关于这种度量应该告诉我们什么的线索:

  1. 排名列表可能是无限的,且可能长度不同——因此度量应该能够处理两个不同的列表大小

  2. 应该有能力在选择的交集长度下进行比较——这意味着度量应该处理列表的交集长度

  3. 顶部元素比底部元素更重要(例如在搜索结果、电影排序等方面)——因此应该有在需要时赋予权重的可能性

现在让我们来看看满足这些条件的东西。

排名偏差重叠

正如名字所暗示的,这种方法是排名偏向的。

这意味着你可以根据更高的权重优先考虑列表中的前几个元素。

这个方法在一篇题为——W. Weber 等人²的《无限排名的相似性度量》的论文中提出。

你不必通读整个研究论文中的复杂内容,我在下面简化了大部分部分,以便我们可以尝试在 Python 中进行快速实现。

不过,系好安全带,戴上数学眼镜,我们将穿越一些复杂的公式!

设 S 和 T 为我们标题图像中两个排名列表的名称:

现在让我们定义这两个列表在深度(d)下的交集。

这实际上意味着什么?

这是 S 和 T 在给定长度(称为深度d)下的共同元素列表。从中可以清楚地看到:

深度被认为是最多为较小列表的长度,否则,交集就没有意义。

这个新的交集列表的长度称为重叠,在这种情况下是 7。

接下来,我们定义一个称为一致性的概念。它只是按深度计算的重叠。

平均重叠(AO)是所有深度列表的平均一致性,范围从 1 到整数 k。

当 k = 1 时,深度范围从 1 到 1,这意味着只比较第一个元素。

由于元素匹配,交集 X 为 1,Agreement A 也是 1。

让我们看一下 k = 2。

在这种情况下,d 从 1 到 2,即我们比较列表中的前两个元素。在这种情况下,重叠为 1,因为只有第一个元素是共同的。因此,一致性为 1/2 或 0.5。

同样,我们可以继续进行 k = 3。

好吧,现在你明白它是如何工作的,你可以继续这个过程,直到 k = 7。

你将得到平均重叠(AO)为:

这可以适用于两个列表中的任何元素数量。

我们可以将上述求和概括为:

这就是相似度度量的基础。

平均重叠是一个相似度度量,用于比较两个列表在给定深度上的相似度。

取一组这样的相似度度量(SIM):

这是一个几何级数,其中d'项的形式为 1/(1-p),其中 0 < p < 1。

将其代入上面的公式并重新排列项,我们可以得到列表 S 和 T 的排名基础重叠为:

变量p位于范围(0,1)内,可以用于确定前d个元素对最终相似度分数的影响。

为了得到一个单一的 RBO 分数,可以对上述公式进行外推。假设深度k一致性在两个列表之间无限延续,可以将其概括为:

RBO 分数在 0 和 1 之间。

  • 0 表示完全不相交,而 1 表示完全相同的排名列表。

我们将使用这个公式来实现 RBO 函数的 Python 代码!

论文还展示了我们如何选择p来给前d个元素赋予权重(Wrbo)(我在这里不深入推导):

我们将使用这个公式来实现权重计算器功能。

呼, 这真是信息量很大啊!

但不要担心,这些公式看起来比实际要复杂得多——你会看到用 Python 实现起来是多么简单。

所以我希望你还在这里,因为有趣的部分现在开始了!

Python 中的排名偏倚重叠的简单实现

我使用上述公式在 python 中编写了 Rank Biased Overlap 的简单实现和一个权重计算函数。

现在你了解了 RBO 的推导过程,实施起来很简单。

import math

def rbo(S,T, p= 0.9):
    """ Takes two lists S and T of any lengths and gives out the RBO Score
    Parameters
    ----------
    S, T : Lists (str, integers)
    p : Weight parameter, giving the influence of the first d
        elements on the final score. p<0<1\. Default 0.9 give the top 10 
        elements 86% of the contribution in the final score.

    Returns
    -------
    Float of RBO score
    """

    # Fixed Terms
    k = max(len(S), len(T))
    x_k = len(set(S).intersection(set(T)))

    summation_term = 0

    # Loop for summation
    # k+1 for the loop to reach the last element (at k) in the bigger list    
    for d in range (1, k+1): 
            # Create sets from the lists
            set1 = set(S[:d]) if d < len(S) else set(S)
            set2 = set(T[:d]) if d < len(T) else set(T)

            # Intersection at depth d
            x_d = len(set1.intersection(set2))

            # Agreement at depth d
            a_d = x_d/d   

            # Summation
            summation_term = summation_term + math.pow(p, d) * a_d

    # Rank Biased Overlap - extrapolated
    rbo_ext = (x_k/k) * math.pow(p, k) + ((1-p)/p * summation_term)

    return rbo_ext

让我们检查一下我们的示例列表 S 和 T。

S = [1,2,3,4,5,6,7]
T = [1,3,2,4,5,7,6,8]

print (rbo(S,T))

>> 0.8853713875

RBO 得分 0.88 意味着列表几乎 90%相似(这在之前的平均重叠计算中也看到过)。

这绝对不是一个稳健的 python 实现。

然而,它显然足够好,可以开始使用!

现在让我们实现权重计算器函数,帮助我们选择 p。

import numpy as np
import math

def weightage_calculator(p,d):
""" Takes values of p and d
    ----------
    p : Weight parameter, giving the influence of the first d
        elements on the final score. p<0<1.
    d : depth at which the weight has to be calculated

    Returns
    -------
    Float of Weightage Wrbo at depth d
    """

    summation_term = 0

    for i in range (1, d): # taking d here will loop upto the value d-1 
        summation_term = summation_term + math.pow(p,i)/i

    Wrbo_1_d = 1 - math.pow(p, d-1) +
                   (((1-p)/p) * d *(np.log(1/(1-p)) - summation_term))

    return Wrbo_1_d

让我们检查一下它是否有效。

根据研究论文,p 为 0.9 时,前 10 个元素在最终相似性评分中的权重为 86%¹。

使用我们上面的函数:

weightage_calculator(0.9,10)

>> 0.8555854467473518

太好了,它运行良好——与论文中指出的一致,得到了 86%!

我们现在准备好了工具。

让我们解决你和你朋友之间的分数吧!

解决你的争论!

让我们通过创建哈利·波特电影的列表,以你和你朋友观看的顺序来测试一下。

然后在其上运行你的 RBO 函数!

you = ['''Harry Potter and the Philosopher's Stone''', 
              'Harry Potter and the Chamber of Secrets', 
              'Harry Potter and the Prisoner of Azkaban',
              'Harry Potter and the Goblet of Fire',
              'Harry Potter and the Order of Phoenix',
              'Harry Potter and the Half Blood Prince',
              'Harry Potter and the Deathly Hallows - Part_1'
              'Harry Potter and the Deathly Hallows - Part_2']

your_friend = ['Harry Potter and the Chamber of Secrets',
              'Harry Potter and the Goblet of Fire',
              'Harry Potter and the Order of Phoenix',
              '''Harry Potter and the Philosopher's Stone''', 
              'Harry Potter and the Prisoner of Azkaban',
              'Harry Potter and the Half Blood Prince',
              'Harry Potter and the Deathly Hallows - Part_1'
              'Harry Potter and the Deathly Hallows - Part_2']

rbo (you, your_friend)
>> 0.782775

0.78!

所以,按顺序观看电影确实很重要,否则分数会是 1!

但你可以更进一步。

通常,前几部电影介绍了角色并构建了虚构的世界——因此,按正确的顺序首先观看这些电影具有更高的质量重要性。

让我们使用权重计算器,并给前四部电影更多的权重(86%)。

通过对权重计算函数的一些试验,我们得到 p = 0.75 和 d = 4 时的权重为 86%。

weightage_calculator(0.75,4)

>> 0.8555854467473518

因此,在 RBO 函数中使用 p 为 0.75 将使前四部电影在排名比较中有更大的影响:

rbo (you, your_friend, 0.75)
>> 0.5361328125

得到的比较得分是0.53!

这意味着如果你跳过前几部电影,或以错误的顺序观看,它是客观上的不良。

实际上,你朋友的观看顺序仅比你的顺序好一半(53%)!

现在你有数学证明这一点!

RBO 的优势

Rank-biased overlap 并不是比较列表的唯一方法——其他方法包括:

  • Kendall Tau³

  • Pearson 相关系数⁴

然而,RBO 在以下几个方面优于其他方法:

  • RBO 适用于不同长度的比较列表(不相交或不相似)

  • 它具有权重测量——你可以赋予比较中顶部或底部更多的重要性

由于这些好处,我决定在这篇文章中详细解释 RBO。

但是,欢迎查看我在来源中链接的其他两个——它们在不同的情况下也会使用!

结论

总结来说,在这篇文章中你学到了比较排名列表的一种度量——Rank Biased Overlap。

  • 你已经深入了解了它的数学原理

  • 在 python 中做了一个简单的实现

  • 使用这些函数对电影观看顺序进行了实际比较

  • 理解了它的好处!

现在每当出现关于比较电影排名、观看顺序或基本上任何序列的分歧时,你可以像专家一样解决问题!

完结。

来源和注释:

[1] 学习排名

[2] 不确定排名的相似性度量 — 威廉·韦伯,阿利斯泰尔·莫法特,贾斯廷·佐贝尔

[3] 肯德尔秩相关系数

[4] 皮尔逊相关系数

图片、动图和脚本的版权归作者所有。

操作指南:手动进行单因素 ANOVA

原文:towardsdatascience.com/how-to-one-way-anova-by-hand-4c19e2a61a8c

学习如何手动进行单因素方差分析(ANOVA),以比较三个或更多组之间的定量测量

Antoine SoeteweyTowards Data Science Antoine Soetewey

·发表于Towards Data Science ·阅读时间 6 分钟·2023 年 8 月 30 日

--

图片由Rohan Makhecha提供

介绍

ANOVA 是一种统计检验,用于比较定量变量在各组之间,以确定几个总体均值之间是否存在统计学显著差异。在实际操作中,它通常用于比较三组或更多组。然而,从理论上讲,它也可以仅用于两个组。1

在之前的一篇文章中,我们展示了如何在 R 中进行单因素 ANOVA。在本文中,我们演示了如何通过手动方式进行单因素 ANOVA,通常称为“ANOVA 表”。

数据和假设

为了说明该方法,假设我们抽取一个包含 12 名学生的样本,这些学生平均分为三班(A、B 和 C),并观察他们的年龄。以下是样本:

作者提供的表格

我们有兴趣比较各班级之间的总体均值。

请记住,ANOVA 的原假设是所有均值相等(即,各班级之间的年龄没有显著差异),而备择假设是至少有一个均值与其他两个不同(即,至少有一个班级的年龄与其他两个班级有显著差异)。正式来说,我们有:

  • μA = μB = μC

  • 至少一个均值不同

手动进行 ANOVA

如上所述,我们将进行一个 ANOVA 表以得出测试结论。

请注意,ANOVA 需要一些假设(即独立性、方差齐性和正态性)。本文的目的是演示如何手动进行 ANOVA,而不是验证这些假设,因此我们假设这些假设已满足而未进行验证。如果你有兴趣,查看如何在 R 中测试这些假设

整体和组均值

我们首先需要计算均值年龄(称为组均值):

  • 类别 A: (24+31+26+23) / 4 = 26

  • 类别 B: (24+21+19+24) / 4 = 22

  • 类别 C: (15+21+18+18) / 4 = 18

整体样本的平均年龄(称为总体平均值):

(24+31+26+23+24+21+1912+24+15+21+18+18) / 12 = 22

SSR 和 SSE

然后我们需要计算回归平方和(SSR)和误差平方和(SSE)。

SSR 通过将组均值与总体均值之间的差异平方,然后乘以该组的观察数来计算:

作者提供的表格

然后将所有单元格的总和:

64+0+64 = 128 = SSR

SSE 通过将每个观察值与其组均值之间的差异平方来计算:

作者提供的表格

然后将所有单元格的总和:

4+25+0+9+4+1+9+4+9+9+0+0 = 74 = SSE

对于那些有兴趣计算总平方和(SST)的人,它只是 SSR 和 SSE 的总和,即:

SST = SSR + SSE = 128 + 74 = 202

ANOVA 表

ANOVA 表如下(我们将其留空,并将逐步填写):

作者提供的表格

我们开始构建 ANOVA 表,将上面找到的 SSR 和 SSE 值插入表格(在“Sum.of.Sq.”列中):

作者提供的表格

“Df”列对应自由度,计算方法如下:

  • 对于线性回归:组数-1 = 3–1 = 2

  • 对于线性误差:观察数-组数 = 12–3 = 9

有了这些信息,ANOVA 表变为:

作者提供的表格

“Mean.Sq.”列对应均方,等于平方和除以自由度,即“Sum.of.Sq.”列除以“Df”列:

作者提供的表格

最后,F 值对应于两个均方之间的比率,即 64 / 8.222 = 7.78:

作者提供的表格

这个 F 值给出了检验统计量(也称为 Fobs),需要与 Fisher 表中的临界值进行比较以得出结论。

我们根据自由度(ANOVA 表中使用的自由度)和显著性水平在 Fisher 表中找到临界值。假设我们取显著性水平α = 0.05,则可以在 Fisher 表中找到临界值如下:

作者提供的表格

所以我们有

F(2;9;0.05) = 4.26

如果你有兴趣用 R 找到这个值,可以使用qf()函数,其中 0.95 对应于 1−α:

qf(0.95, 2, 9)
## [1] 4.256495

检验结论

拒绝规则说,如果:

  • Fobs > F(2;9;0.05) ⇒ 我们拒绝原假设

  • Fobs ≤ F(2;9;0.05) ⇒ 我们拒绝原假设

在我们的案例中,

Fobs = 7.78 > F(2;9;0.05) = 4.26

⇒ 我们拒绝原假设,即所有均值相等。换句话说,这意味着至少有一个类别在年龄上与其他两个类别不同。2

为了验证我们的结果,这里是使用 R 进行的 ANOVA 表:

##             Df Sum Sq Mean Sq F value Pr(>F)  
## class        2    128   64.00   7.784 0.0109 *
## Residuals    9     74    8.22                 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

我们手动得出了相同的结果,但请注意,在 R 中,是计算 p 值而不是将 Fobs 与临界值进行比较。p 值可以根据 Fobs 和自由度在 R 中轻松找到:

pf(7.78, 2, 9,
  lower.tail = FALSE
)
## [1] 0.010916

结论

感谢阅读。

我希望这篇文章帮助你手动进行单因素 ANOVA。如果你想学习如何在 R 中进行,请查看这个教程

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

  1. 在这种情况下,Student’s t 检验 通常比 ANOVA 更受欢迎,尽管两种检验都会得出完全相同的结论。↩︎

  2. 记住,ANOVA 不能告诉你哪个组在定量因变量上与其他组不同,也不能告诉你它们是否都不同或只有一个不同。要回答这个问题,需要进行事后检验。这超出了本文的范围,但可以在 R 中轻松完成(参见这个教程)。↩︎

相关文章

最初发布于 https://statsandr.com 于 2023 年 8 月 30 日。

如何优化多维 Numpy 数组操作的 Numexpr

原文:towardsdatascience.com/how-to-optimize-multidimensional-numpy-array-operations-with-numexpr-32ba9ea8e9a6

快速计算

Numpy 性能优化的实际案例研究

Peng QianTowards Data Science Peng Qian

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

--

如何使用 Numexpr 优化多维 Numpy 数组操作。图片来源:作者创建,Canva

这是一篇相对简短的文章。在其中,我将以一个实际场景为例,解释如何在多维 Numpy 数组中使用Numexpr 表达式以实现显著的性能提升。

目前关于如何在多维 Numpy 数组中使用 Numexpr 及其表达式的文章不多,因此希望这篇文章能对你有所帮助。

介绍

最近,在回顾一些旧工作时,我偶然发现了这段代码:

def predict(X, w, b):
    z = np.dot(X, w)
    y_hat = sigmoid(z)
    y_pred = np.zeros((y_hat.shape[0], 1))

    for i in range(y_hat.shape[0]):
        if y_hat[i, 0] < 0.5:
            y_pred[i, 0] = 0
        else:
            y_pred[i, 0] = 1
    return y_pred

这段代码将预测结果从概率转换为机器学习中逻辑回归模型的 0 或 1 分类结果。

但天哪,谁会用for loop来遍历 Numpy ndarray?

你可以预见,当数据量达到一定程度时,它不仅会占用大量内存,而且性能也会变差。

是的,这段代码是我年轻时写的。

带着责任感,我计划今天使用 Numexpr 库重写这段代码。

在这个过程中,我将展示如何在多维 Numpy 数组中使用 Numexpr 及其where表达式,以实现显著的性能提升。

代码实现

如果你不熟悉 Numexpr 的基本用法,可以参考这篇文章:

## 探索 Numexpr:Pandas 背后的强大引擎

使用 Python 的 Numexpr 和 Pandas 的 eval/query 函数提升数据分析性能

[towardsdatascience.com

本文使用实际案例演示了 Numexpr 在 Numpy 和 Pandas 中的 API 和表达式的具体用法。

where(bool, number1, number2): number - 如果布尔条件为真,则为 number1,否则为 number2。

上述是 Numexpr 中 where 表达式的用法。

在处理矩阵数据时,你可能习惯使用 Pandas 的 DataFrame。但由于 Pandas 的 eval 方法不支持 where 表达式,你只能选择在多维 Numpy ndarray 中使用 Numexpr。

不用担心,我会马上为你解释。

在开始之前,我们需要导入必要的包并实现一个 generate_ndarray 方法,用于生成特定大小的 ndarray 进行测试:

from typing import Callable
import time

import numpy as np
import numexpr as ne
import matplotlib.pyplot as plt

rng = np.random.default_rng(seed=4000)

def generate_ndarray(rows: int) -> np.ndarray:
    result_array = rng.random((rows, 1))
    return result_array

首先,我们生成一个 200 行的矩阵,以查看它是否是我们想要的测试数据:

In:  arr = generate_ndarray(200)
     print(f"The dimension of this array: {arr.ndim}")
     print(f"The shape of this array: {arr.shape}")

Out: The dimension of this array: 2
     The shape of this array: (200, 1)

为了接近实际的逻辑回归模型情况,我们生成一个形状为 (200, 1) 的 ndarray。当然,你也可以根据需要测试其他形状的 ndarray。

接着,我们开始编写 Numexpr 在 numexpr_to_binary method 中的具体用法:

  • 首先,我们使用索引来分隔需要处理的列。

  • 然后,使用 Numexpr 的 where 表达式来处理这些值。

  • 最后,将处理过的列与其他列合并,以生成所需结果。

由于 ndarray 的形状是 (200, 1),只有一列,因此我添加了一个新维度。

代码如下:

def numexpr_to_binary(np_array: np.ndarray) -> np.ndarray:
    temp = np_array[:, 0]
    temp = ne.evaluate("where(temp<0.5, 0, 1)")
    return temp[:, np.newaxis]

我们可以用一个 10 行的数组测试结果,以查看是否符合预期:

arr = generate_ndarray(10)
result = numexpr_to_binary(arr)

mapping = np.column_stack((arr, result))
mapping

我测试了一个 10 行的数组,结果是我想要的。图片由作者提供

看,匹配是正确的。我们的任务已完成。

整个过程可以通过以下图示演示:

Numexpr 如何转换多维 ndarray 的整个过程。图片由作者提供

性能比较

在代码实现后,我们需要将 Numexpr 实现版本与之前的 for each 实现版本进行比较,以确认性能是否有所提升。

首先,我们实现一个 numexpr_example 方法。该方法基于 Numexpr 的实现:

def numexpr_example(rows: int) -> np.ndarray:
    orig_arr = generate_ndarray(rows)
    the_result = numexpr_to_binary(orig_arr)
    return the_result

接着,我们需要补充一个 for_loop_example 方法。该方法参考了我需要重写的原始代码,并用作性能基准:

def for_loop_example(rows: int) -> np.ndarray:
    the_arr = generate_ndarray(rows)
    for i in range(the_arr.shape[0]):
        if the_arr[i][0] < 0.5:
            the_arr[i][0] = 0
        else:
            the_arr[i][0] = 1
    return the_arr

然后,我编写了一个测试方法 time_method。该方法将分别生成从 10 到 10 的 9 次方行的数据,调用相应的方法,并最终保存不同数据量所需的时间:

def time_method(method: Callable):
    time_dict = dict()
    for i in range(9):
        begin = time.perf_counter()
        rows = 10 ** i
        method(rows)
        end = time.perf_counter()
        time_dict[i] = end - begin
    return time_dict

我们分别测试 numexpr 版本和 for_loop 版本,并使用 matplotlib 绘制不同数据量所需的时间:

t_m = time_method(for_loop_example)
t_m_2 = time_method(numexpr_example)
plt.plot(t_m.keys(), t_m.values(), c="red", linestyle="solid")
plt.plot(t_m_2.keys(), t_m_2.values(), c="green", linestyle="dashed")
plt.legend(["for loop", "numexpr"])
plt.xlabel("exponent")
plt.ylabel("time")
plt.show()

Numexpr 版本的实现具有显著的性能提升。图片来源:作者

可以看出,当数据行数大于 10 的 6 次方时,Numexpr 版本的实现具有显著的性能提升。

结论

在前一篇文章中解释了 Numexpr 的基本用法,本篇文章通过实际工作中的具体示例,说明如何使用 Numexpr 重写现有代码以获得性能提升。

本文主要使用了 Numexpr 的两个特性:

  1. Numexpr 允许以矢量化的方式进行计算。

  2. 在 Numexpr 的计算过程中,不会生成新的数组,从而显著减少内存使用。

感谢阅读。如果你有其他解决方案,请随时留言与我讨论。

感谢阅读我的故事。

你可以订阅以获取我最新的数据科学故事。

如果你有任何问题,可以在LinkedInTwitter(X)找到我。

本文最初发布在Data Leads Future

如何优化特定领域的目标检测模型

原文:towardsdatascience.com/how-to-optimize-object-detection-models-for-specific-domains-d8512c63a9c3?source=collection_archive---------3-----------------------#2023-10-19

设计更好更快的模型以解决你的特定问题

Alvaro Leandro Cavalcante CarneiroTowards Data Science Alvaro Leandro Cavalcante Carneiro

·

关注 发表在 Towards Data Science ·13 min read·2023 年 10 月 19 日

--

图片由 paolo candelo 提供,来源于 Unsplash

目标检测在从学术界到工业领域的不同领域中被广泛使用,因为它能够以低计算成本提供良好的结果。然而,尽管有许多开源架构可公开获取,大多数模型设计用于解决通用问题,可能不适用于特定的上下文。

举个例子,我们可以提到COCO 数据集,这是在这一领域中通常作为研究基线的数据集,对模型的超参数和架构细节产生影响。该数据集包含 90 个不同的类别,涵盖了各种光照条件、背景和大小。事实证明,有时你面临的检测问题相对简单。你可能只需检测几个不同的对象,而没有太多的场景或大小变化。在这种情况下,如果你使用通用的超参数集来训练你的模型,你可能会得到一个产生不必要计算成本的模型。

从这个角度来看,本文的主要目标是提供优化各种目标检测模型以处理较简单任务的指导。我希望帮助你选择一个更高效的配置,以减少计算成本而不影响平均精度(mAP)。

提供一些背景信息

我的硕士学位目标之一是开发一个手语识别系统,以最小的计算要求为前提。该系统的一个关键组成部分是预处理阶段,包括检测翻译员的手和脸,如下图所示:

这是本研究中创建的 HFSL 数据集的样本。图片作者提供。

如图所示,这个问题相对简单,只涉及两类不同的对象和图像中同时出现的三种对象。因此,我的目标是优化模型的超参数,以保持高的 mAP,同时减少计算成本,从而使其能够在智能手机等边缘设备上高效执行。

目标检测架构和设置

在这个项目中,测试了以下目标检测架构:EfficientDetD0、Faster-RCNN、SDD320、SDD640 和 YoloV7。然而,这里提出的概念可以应用于适应各种其他架构。

在模型开发方面,我主要使用了 Python 3.8 和 TensorFlow 框架,除了 YoloV7 使用了 PyTorch。虽然这里提供的大多数示例与 TensorFlow 相关,但你可以将这些原则适应于你所选择的框架。

在硬件方面,测试使用了 RTX 3060 GPU 和 Intel Core i5–10400 CPU。所有源代码和模型都可以在GitHub上找到。

目标检测器的微调

使用 TensorFlow 进行目标检测时,了解所有超参数都存储在一个名为“pipeline.config”的文件中是至关重要的。这个 protobuf 文件保存了用于训练和评估模型的配置,你可以在任何从TF 模型库下载的预训练模型中找到它。在这个背景下,我将描述我在管道文件中实施的修改,以优化目标检测器。

需要注意的是,这里提供的超参数是专门为手部和面部检测(2 类,3 对象)设计的。务必根据你自己的问题领域进行调整。

一般简化

对所有模型可以应用的第一个变化是将每个类别的最大预测数量和生成的边界框数量从 100 分别减少到 2 和 4。你可以通过调整“max_number_of_boxes”属性来实现这一点,该属性位于“train_config”对象中:

...
train_config {
  batch_size: 128
  sync_replicas: true
  optimizer { ... }
  fine_tune_checkpoint: "PATH_TO_BE_CONFIGURED"
  num_steps: 50000
  startup_delay_steps: 0.0
  replicas_to_aggregate: 8
  max_number_of_boxes: 4 # <------------------ change this line
  unpad_groundtruth_tensors: false
  fine_tune_checkpoint_type: "classification"
  fine_tune_checkpoint_version: V2
}
...

之后,更改“max_total_detections” 和 “max_detections_per_class”,它们位于目标检测器的“post_processing”中:

post_processing {
  batch_non_max_suppression {
    score_threshold: 9.99999993922529e-09
    iou_threshold: 0.6000000238418579
    max_detections_per_class: 2 # <------------------ change this line
    max_total_detections: 4     # <------------------ change this line
    use_static_shapes: false
  }
  score_converter: SIGMOID
}

这些变化很重要,特别是对我来说,因为图像中同时出现的对象只有三个且类别只有两个。通过减少预测次数,消除重叠边界框所需的迭代次数也减少了,这通过非极大值抑制 (NMS) 实现。因此,如果你需要检测的类别和场景中的对象数量有限,改变这个超参数可能是个好主意。

额外的调整是逐个应用的,考虑了每个目标检测模型的具体架构细节。

单次检测多框 (SSD)

在进行目标检测时,测试不同的分辨率总是一个好主意。在这个项目中,我使用了两个版本的模型,SSD320 和 SSD640,分别具有 320x320 和 640x640 像素的输入图像分辨率。

对于这两个模型,主要的修改之一是将特征金字塔网络 (FPN) 的深度从 5 减少到 4,通过移除最表层。FPN 是一个强大的特征提取机制,能够处理多种特征图尺寸。然而,对于较大的对象,最表层是为较高图像分辨率设计的,可能并不必要。也就是说,如果你要检测的对象不太小,移除这一层可能是个好主意。为了实现这一变化,将“min_level”属性从 3 调整到 4,位于“fpn”对象中:

...
feature_extractor {
  type: "ssd_mobilenet_v2_fpn_keras"
  depth_multiplier: 1.0
  min_depth: 16
  conv_hyperparams {
    regularizer { ... }
    initializer { ... }
    activation: RELU_6
    batch_norm {...}
  }
  use_depthwise: true
  override_base_feature_extractor_hyperparams: true
  fpn {
    min_level: 4 # <------------------ change this line
    max_level: 7
    additional_layer_depth: 108 # <------------------ change this line
  }
}
...

我还通过将“additional_layer_depth”从 128 减少到 108 简化了高分辨率模型(SSD640)。同样,我将两个模型的“multiscale_anchor_generator”深度从 5 调整到 4 层,如下所示:

...
anchor_generator {
  multiscale_anchor_generator {
    min_level: 4 # <------------------ change this line
    max_level: 7
    anchor_scale: 4.0
    aspect_ratios: 1.0
    aspect_ratios: 2.0
    aspect_ratios: 0.5
    scales_per_octave: 2
  }
}
...

最后,生成边界框预测的网络(“box_predictor”)的层数从 4 减少到 3。关于 SSD640,边框预测的深度也从 128 减少到 96,如下所示:

...
box_predictor {
  weight_shared_convolutional_box_predictor {
    conv_hyperparams {
      regularizer { ... }
      initializer { ... }
      activation: RELU_6
      batch_norm { ... }
    }
    depth: 96 # <------------------ change this line
    num_layers_before_predictor: 3 # <------------------ change this line
    kernel_size: 3
    class_prediction_bias_init: -4.599999904632568
    share_prediction_tower: true
    use_depthwise: true
  }
}
...

这些简化是因为我们拥有的不同类别数量有限,且模式相对简单。因此,可以减少模型的层数和深度,因为即使特征图较少,我们仍能有效地从图像中提取所需的特征。

EfficientDet-D0

关于 EfficientDet-D0,我将双向特征金字塔网络 (Bi-FPN)的深度从 5 减少到 4。此外,我将 Bi-FPN 的迭代次数从 3 减少到 2,将特征图内核从 64 减少到 48。Bi-FPN 是一种复杂的多尺度特征融合技术,可以产生出色的结果。然而,它也需要较高的计算资源,对于简单的问题,这可能会浪费资源。要实施上述调整,只需按如下方式更新“bifpn”对象的属性:

...
bifpn {
      min_level: 4 # <------------------ change this line
      max_level: 7
      num_iterations: 2 # <------------------ change this line
      numyaml_filters: 48 # <------------------ change this line
    }
...

除此之外,还需要像处理 SSD 时一样,减少“multiscale_anchor_generator”的深度。最后,我将边框预测网络的层数从 3 减少到 2:

...
box_predictor {
  weight_shared_convolutional_box_predictor {
    conv_hyperparams {
      regularizer { ... }
      initializer { ... }
      activation: SWISH
      batch_norm { ... }
      force_use_bias: true
    }
    depth: 64
    num_layers_before_predictor: 2 # <------------------ change this line
    kernel_size: 3
    class_prediction_bias_init: -4.599999904632568
    use_depthwise: true
  }
}
...

Faster R-CNN

Faster R-CNN 模型依赖于区域建议网络(RPN)和锚框作为其主要技术。锚框是一个滑动窗口的中心点,它在骨干 CNN 的最后特征图上迭代。每次迭代时,分类器会确定一个建议中是否包含物体的概率,而回归器则调整边界框坐标。为了确保检测器具有平移不变性,它使用三种不同的尺度和三种宽高比的锚框,这增加了每次迭代的建议数量。

虽然这是一个简单的解释,但显而易见,由于其两阶段检测过程,这个模型比其他模型复杂得多。然而,可以简化它并提高其速度,同时保持高精度。

为此,第一个重要的修改是将生成的建议数量从 300 减少到 50。这一减少是可行的,因为图像中同时存在的物体数量较少。你可以通过调整“first_stage_max_proposals”属性来实现这一变化,如下所示:

...
first_stage_box_predictor_conv_hyperparams {
  op: CONV
  regularizer { ... }
  initializer { ... }
}
first_stage_nms_score_threshold: 0.0
first_stage_nms_iou_threshold: 0.7
first_stage_max_proposals: 50 # <------------------ change this line
first_stage_localization_loss_weight: 2.0
first_stage_objectness_loss_weight: 1.0
initial_crop_size: 14
maxpool_kernel_size: 2
maxpool_stride: 2
...

之后,我从模型中移除了最大的锚框尺度 (2.0)。这个改变是因为手部和面部由于解释器与相机的固定距离而保持一致的大小,大锚框可能对提议生成没有用。此外,我移除了一个锚框的长宽比,因为我的物体在数据集中具有类似的形状,变化很小。下面的调整以视觉形式展示:

first_stage_anchor_generator {
  grid_anchor_generator {
    scales: [0.25, 0.5, 1.0] # <------------------ change this line
    aspect_ratios: [0.5, 1.0] # <------------------ change this line
    height_stride: 16
    width_stride: 16
  }
}

也就是说,考虑目标物体的尺寸和长宽比至关重要。这种考虑可以帮助你排除不太有用的锚框,并显著减少模型的计算成本。

YoloV7

相比之下,对 YoloV7 应用的更改很少,以保留架构的功能性。主要的修改是简化了负责特征提取的 CNN,包括骨干网和模型的头部。为此,我减少了几乎每个卷积层的内核/特征图数量,创建了以下模型:

backbone:
  # [from, number, module, args]
  [[-1, 1, Conv, [22, 3, 1]],  # 0
   [-1, 1, Conv, [44, 3, 2]],  # 1-P1/2      
   [-1, 1, Conv, [44, 3, 1]],
   [-1, 1, Conv, [89, 3, 2]],  # 3-P2/4  
   [-1, 1, Conv, [44, 1, 1]],
   [-2, 1, Conv, [44, 1, 1]],
   [-1, 1, Conv, [44, 3, 1]],
   [-1, 1, Conv, [44, 3, 1]],
   [-1, 1, Conv, [44, 3, 1]],
   [-1, 1, Conv, [44, 3, 1]],
   [[-1, -3, -5, -6], 1, Concat, [1]],
   [-1, 1, Conv, [179, 1, 1]],  # 11
   [-1, 1, MP, []],
   [-1, 1, Conv, [89, 1, 1]],
   [-3, 1, Conv, [89, 1, 1]],
   [-1, 1, Conv, [89, 3, 2]],
   [[-1, -3], 1, Concat, [1]],  # 16-P3/8  
   [-1, 1, Conv, [89, 1, 1]],
   [-2, 1, Conv, [89, 1, 1]],
   [-1, 1, Conv, [89, 3, 1]],
   [-1, 1, Conv, [89, 3, 1]],
   [-1, 1, Conv, [89, 3, 1]],
   [-1, 1, Conv, [89, 3, 1]],
   [[-1, -3, -5, -6], 1, Concat, [1]],
   [-1, 1, Conv, [512, 1, 1]],  # 24
   [-1, 1, MP, []],
   [-1, 1, Conv, [89, 1, 1]],
   [-3, 1, Conv, [89, 1, 1]],
   [-1, 1, Conv, [89, 3, 2]],
   [[-1, -3], 1, Concat, [1]],  # 29-P4/16  
   [-1, 1, Conv, [89, 1, 1]],
   [-2, 1, Conv, [89, 1, 1]],
   [-1, 1, Conv, [89, 3, 1]],
   [-1, 1, Conv, [89, 3, 1]],
   [-1, 1, Conv, [89, 3, 1]],
   [-1, 1, Conv, [89, 3, 1]],
   [[-1, -3, -5, -6], 1, Concat, [1]],
   [-1, 1, Conv, [716, 1, 1]],  # 37
   [-1, 1, MP, []],
   [-1, 1, Conv, [256, 1, 1]],
   [-3, 1, Conv, [256, 1, 1]],
   [-1, 1, Conv, [256, 3, 2]],
   [[-1, -3], 1, Concat, [1]],  # 42-P5/32  
   [-1, 1, Conv, [128, 1, 1]],
   [-2, 1, Conv, [128, 1, 1]],
   [-1, 1, Conv, [128, 3, 1]],
   [-1, 1, Conv, [128, 3, 1]],
   [-1, 1, Conv, [128, 3, 1]],
   [-1, 1, Conv, [128, 3, 1]],
   [[-1, -3, -5, -6], 1, Concat, [1]],
   [-1, 1, Conv, [716, 1, 1]],  # 50
  ]

# yolov7 head
head:
  [[-1, 1, SPPCSPC, [358]], # 51
   [-1, 1, Conv, [179, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [37, 1, Conv, [179, 1, 1]], # route backbone P4
   [[-1, -2], 1, Concat, [1]],
   [-1, 1, Conv, [179, 1, 1]],
   [-2, 1, Conv, [179, 1, 1]],
   [-1, 1, Conv, [89, 3, 1]],
   [-1, 1, Conv, [89, 3, 1]],
   [-1, 1, Conv, [89, 3, 1]],
   [-1, 1, Conv, [89, 3, 1]],
   [[-1, -2, -3, -4, -5, -6], 1, Concat, [1]],
   [-1, 1, Conv, [179, 1, 1]], # 63
   [-1, 1, Conv, [89, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [24, 1, Conv, [89, 1, 1]], # route backbone P3
   [[-1, -2], 1, Concat, [1]],
   [-1, 1, Conv, [89, 1, 1]],
   [-2, 1, Conv, [89, 1, 1]],
   [-1, 1, Conv, [44, 3, 1]],
   [-1, 1, Conv, [44, 3, 1]],
   [-1, 1, Conv, [44, 3, 1]],
   [-1, 1, Conv, [44, 3, 1]],
   [[-1, -2, -3, -4, -5, -6], 1, Concat, [1]],
   [-1, 1, Conv, [89, 1, 1]], # 75
   [-1, 1, MP, []],
   [-1, 1, Conv, [89, 1, 1]],
   [-3, 1, Conv, [89, 1, 1]],
   [-1, 1, Conv, [89, 3, 2]],
   [[-1, -3, 63], 1, Concat, [1]],
   [-1, 1, Conv, [179, 1, 1]],
   [-2, 1, Conv, [179, 1, 1]],
   [-1, 1, Conv, [89, 3, 1]],
   [-1, 1, Conv, [89, 3, 1]],
   [-1, 1, Conv, [89, 3, 1]],
   [-1, 1, Conv, [89, 3, 1]],
   [[-1, -2, -3, -4, -5, -6], 1, Concat, [1]],
   [-1, 1, Conv, [179, 1, 1]], # 88
   [-1, 1, MP, []],
   [-1, 1, Conv, [179, 1, 1]],
   [-3, 1, Conv, [179, 1, 1]],
   [-1, 1, Conv, [179, 3, 2]],
   [[-1, -3, 51], 1, Concat, [1]],
   [-1, 1, Conv, [179, 1, 1]],
   [-2, 1, Conv, [179, 1, 1]],
   [-1, 1, Conv, [128, 3, 1]],
   [-1, 1, Conv, [128, 3, 1]],
   [-1, 1, Conv, [128, 3, 1]],
   [-1, 1, Conv, [128, 3, 1]],
   [[-1, -2, -3, -4, -5, -6], 1, Concat, [1]],
   [-1, 1, Conv, [358, 1, 1]], # 101
   [75, 1, RepConv, [179, 3, 1]],
   [88, 1, RepConv, [358, 3, 1]],
   [101, 1, RepConv, [716, 3, 1]],
   [[102,103,104], 1, IDetect, [nc, anchors]],   # Detect(P3, P4, P5)
  ]

如前所述,从检测器中移除一些层和特征图通常是处理简单问题的好方法,因为特征提取器最初是设计用于检测数十种甚至数百种不同类别,在多种场景中需要更强大的模型来应对这些复杂性并确保高准确性。

通过这些调整,我将参数数量从 3640 万减少到仅 1410 万,减少约 61%。此外,我使用了 512x512 像素的输入分辨率,而不是原论文中建议的 640x640 像素。

额外提示

另一个在目标检测训练中的宝贵提示是利用 Kmeans 模型进行锚框比例的无监督调整,将图形的宽度和高度调整到最大化训练集中的交并比 (IoU) 比例。通过这样做,我们可以更好地适应给定的问题领域,从而通过以合适的长宽比开始,提升模型收敛。下图展示了这个过程,将 SSD 算法中默认使用的三个锚框(红色)与用于手部和面部检测任务的三个优化比例的框(绿色)进行比较。

比较不同边界框的长宽比。图片由作者提供。

展示结果

我使用自己的数据集,即手部和面部手语(HFSL)数据集,训练和评估了每个检测器,考虑了 mAP 和每秒帧数(FPS)作为主要指标。下表提供了结果的总结,表中的值(括号中的数字)表示在实施任何描述的优化之前检测器的 FPS。

目标检测结果。

我们可以观察到,大多数模型在保持各个层次的交并比(IoU)高 mAP 的同时,推理时间显著减少。更复杂的架构,如 Faster R-CNN 和 EfficientDet,分别将 GPU 上的 FPS 提高了 200.80%和 231.78%。即使是基于 SSD 的架构,其性能也有了巨大的提升,640 版本和 320 版本的性能分别提高了 280.23%和 159.59%。考虑到 YoloV7,尽管在 CPU 上的 FPS 差异最为明显,但优化后的模型参数减少了 61%,降低了内存需求,使其更适合边缘设备。

结论

在计算资源有限或任务必须快速执行的情况下,我们可以进一步优化开源目标检测模型,以寻找能够减少计算需求而不影响结果的超参数组合,从而为各种问题领域提供合适的解决方案。

我希望这篇文章能帮助你在训练目标检测器时做出更好的选择,从而在付出最小努力的情况下实现显著的效率提升。如果你对某些解释的概念不太理解,我建议你深入了解你的目标检测架构是如何工作的。此外,考虑尝试不同的超参数值,以根据你所解决的具体问题进一步优化你的模型!

如何通过自定义 PyTorch 操作符优化你的深度学习数据输入管道

原文:towardsdatascience.com/how-to-optimize-your-dl-data-input-pipeline-with-a-custom-pytorch-operator-7f8ea2da5206?source=collection_archive---------5-----------------------#2023-08-31

PyTorch 模型性能分析与优化 — 第五部分

Chaim RandTowards Data Science Chaim Rand

·

查看 发表在 Towards Data Science · 6 分钟阅读 · 2023 年 8 月 31 日

--

图片由 Alexander Grey 提供,发布在 Unsplash

这篇文章是关于基于 GPU 的 PyTorch 工作负载性能分析和优化系列文章中的第五篇,直接续接了第四部分。在第四部分中,我们演示了如何使用PyTorch ProfilerTensorBoard来识别、分析和解决 DL 训练工作负载的数据预处理管道中的性能瓶颈。在这篇文章中,我们讨论了 PyTorch 对创建自定义操作符的支持,并演示了它如何帮助我们解决数据输入管道中的性能瓶颈,加速 DL 工作负载,并降低训练成本。感谢伊扎克·莱维吉拉德·瓦瑟曼对本文章的贡献。与本篇文章相关的代码可以在这个 GitHub 仓库中找到。

构建 PyTorch 扩展

PyTorch 提供了多种创建自定义操作的方法,包括通过自定义模块扩展 torch.nn 和/或自定义函数。在这篇文章中,我们关注 PyTorch 对集成自定义 C++ 代码的支持。这一功能之所以重要,是因为某些操作在 C++ 中实现的效率和/或简便性可能远超 Python。通过指定的 PyTorch 工具,如CppExtension,这些操作可以轻松地作为 PyTorch 的“扩展”进行集成,而无需拉取和重新编译整个 PyTorch 代码库。有关这一功能的动机以及如何使用它的详细信息,请参见官方 PyTorch 自定义 C++ 和 CUDA 扩展教程。由于我们在本文中关注的是加速基于 CPU 的数据预处理管道,因此我们将仅使用 C++ 扩展,不需要 CUDA 代码。在未来的文章中,我们希望展示如何使用这一功能实现自定义 CUDA 扩展,以加速在 GPU 上运行的训练代码。

示例

在我们之前的文章中,我们定义了一个数据输入管道,从解码一个533x800 JPEG 图像开始,然后提取一个随机的256x256裁剪区域,经过一些额外的变换后,送入训练循环。我们使用了PyTorch ProfilerTensorBoard来测量从文件加载图像的时间,并承认了解码的浪费。为了完整性,我们在下面复制了代码:

import numpy as np
from PIL import Image
from torchvision.datasets.vision import VisionDataset
input_img_size = [533, 800]
img_size = 256

class FakeDataset(VisionDataset):
    def __init__(self, transform):
        super().__init__(root=None, transform=transform)
        size = 10000
        self.img_files = [f'{i}.jpg' for i in range(size)]
        self.targets = np.random.randint(low=0,high=num_classes,
                                         size=(size),dtype=np.uint8).tolist()

    def __getitem__(self, index):
        img_file, target = self.img_files[index], self.targets[index]
        img = Image.open(img_file)
        if self.transform is not None:
            img = self.transform(img)
        return img, target

    def __len__(self):
        return len(self.img_files)

transform = T.Compose(
    [T.PILToTensor(),
     T.RandomCrop(img_size),
     RandomMask(),
     ConvertColor(),
     Scale()])

回顾我们之前的文章,我们达到的优化平均步骤时间是0.72 秒。可以推测,如果我们能够仅解码感兴趣的裁剪区域,我们的管道将运行得更快。不幸的是,截至本文写作时,PyTorch 并没有包含支持这一功能的函数。然而,利用自定义操作符创建工具,我们可以定义并实现自己的函数!

自定义 JPEG 图像解码和裁剪函数

libjpeg-turbo库是一个 JPEG 图像编解码器,相比于libjpeg,包含了许多增强和优化功能。特别是,libjpeg-turbo包括许多功能,使我们能够仅解码图像中的预定义裁剪区域,例如jpeg_skip_scanlinesjpeg_crop_scanline。如果你在 conda 环境中运行,可以使用以下命令进行安装:

conda install -c conda-forge libjpeg-turbo

请注意,libjpeg-turbo已预装在我们将在下面实验中使用的官方AWS PyTorch 2.0 深度学习 Docker 镜像中。

在下面的代码块中,我们修改了decode_jpeg函数,来自torchvision 0.15,以解码并返回来自输入 JPEG 编码图像的请求裁剪。

torch::Tensor decode_and_crop_jpeg(const torch::Tensor& data,
                                   unsigned int crop_y,
                                   unsigned int crop_x,
                                   unsigned int crop_height,
                                   unsigned int crop_width) {
  struct jpeg_decompress_struct cinfo;
  struct torch_jpeg_error_mgr jerr;

  auto datap = data.data_ptr<uint8_t>();
  // Setup decompression structure
  cinfo.err = jpeg_std_error(&jerr.pub);
  jerr.pub.error_exit = torch_jpeg_error_exit;
  /* Establish the setjmp return context for my_error_exit to use. */
  setjmp(jerr.setjmp_buffer);
  jpeg_create_decompress(&cinfo);
  torch_jpeg_set_source_mgr(&cinfo, datap, data.numel());

  // read info from header.
  jpeg_read_header(&cinfo, TRUE);

  int channels = cinfo.num_components;

  jpeg_start_decompress(&cinfo);

  int stride = crop_width * channels;
  auto tensor =
     torch::empty({int64_t(crop_height), int64_t(crop_width), channels},
                  torch::kU8);
  auto ptr = tensor.data_ptr<uint8_t>();

  unsigned int update_width = crop_width;
  jpeg_crop_scanline(&cinfo, &crop_x, &update_width);
  jpeg_skip_scanlines(&cinfo, crop_y);

  const int offset = (cinfo.output_width - crop_width) * channels;
  uint8_t* temp = nullptr;
  if(offset > 0) temp = new uint8_t[cinfo.output_width * channels];

  while (cinfo.output_scanline < crop_y + crop_height) {
    /* jpeg_read_scanlines expects an array of pointers to scanlines.
     * Here the array is only one element long, but you could ask for
     * more than one scanline at a time if that's more convenient.
     */
    if(offset>0){
      jpeg_read_scanlines(&cinfo, &temp, 1);
      memcpy(ptr, temp + offset, stride);
    }
    else
      jpeg_read_scanlines(&cinfo, &ptr, 1);
    ptr += stride;
  }
  if(offset > 0){
    delete[] temp;
    temp = nullptr;
  }
  if (cinfo.output_scanline < cinfo.output_height) {
    // Skip the rest of scanlines, required by jpeg_destroy_decompress.
    jpeg_skip_scanlines(&cinfo,
                        cinfo.output_height - crop_y - crop_height);
  }
  jpeg_finish_decompress(&cinfo);
  jpeg_destroy_decompress(&cinfo);
  return tensor.permute({2, 0, 1});
}

PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
  m.def("decode_and_crop_jpeg",&decode_and_crop_jpeg,"decode_and_crop_jpeg");
}

完整的 C++ 文件可以在这里找到。

在接下来的部分中,我们将按照 PyTorch 教程中的步骤,将其转换为一个 PyTorch 操作符,以便在我们的预处理管道中使用。

部署 PyTorch 扩展

PyTorch 教程中所述,有多种方式来部署自定义操作符。设计部署时需要考虑许多因素。以下是我们认为重要的一些例子:

  1. 即时编译:为了确保我们的 C++扩展与我们训练时使用的 PyTorch 版本一致,我们编程部署脚本在训练环境内训练前编译代码。

  2. 多进程支持:部署脚本必须支持我们的 C++扩展从多个进程(例如多个 DataLoader 工作者)加载的可能性。

  3. 托管训练支持:由于我们经常在托管训练环境(如Amazon SageMaker)中进行训练,因此我们要求部署脚本支持此选项。(有关自定义托管训练环境的更多信息,请参见这里。)

在下面的代码块中,我们定义了一个简单的setup.py脚本,用于编译和安装我们的自定义函数,如这里所述。

from setuptools import setup
from torch.utils import cpp_extension

setup(name='decode_and_crop_jpeg',
      ext_modules=[cpp_extension.CppExtension('decode_and_crop_jpeg', 
                                              ['decode_and_crop_jpeg.cpp'], 
                                              libraries=['jpeg'])],
      cmdclass={'build_ext': cpp_extension.BuildExtension})

我们将 C++文件和setup.py脚本放在一个名为custom_op的文件夹中,并定义一个init.py,以确保安装脚本只运行一次且由单个进程执行。

import os
import sys
import subprocess
import shlex
import filelock

p_dir = os.path.dirname(__file__)

with filelock.FileLock(os.path.join(pkg_dir, f".lock")):
  try:
    from custom_op.decode_and_crop_jpeg import decode_and_crop_jpeg
  except ImportError:
    install_cmd = f"{sys.executable} setup.py build_ext --inplace"
    subprocess.run(shlex.split(install_cmd), capture_output=True, cwd=p_dir)
    from custom_op.decode_and_crop_jpeg import decode_and_crop_jpeg

最后,我们修订了数据输入管道,使用我们新创建的自定义函数:

from torchvision.datasets.vision import VisionDataset
input_img_size = [533, 800]
class FakeDataset(VisionDataset):
    def __init__(self, transform):
        super().__init__(root=None, transform=transform)
        size = 10000
        self.img_files = [f'{i}.jpg' for i in range(size)]
        self.targets = np.random.randint(low=0,high=num_classes,
                                        size=(size),dtype=np.uint8).tolist()

    def __getitem__(self, index):
        img_file, target = self.img_files[index], self.targets[index]
        with torch.profiler.record_function('decode_and_crop_jpeg'):
            import random
            from custom_op.decode_and_crop_jpeg import decode_and_crop_jpeg
            with open(img_file, 'rb') as f:
                x = torch.frombuffer(f.read(), dtype=torch.uint8)
            h_offset = random.randint(0, input_img_size[0] - img_size)
            w_offset = random.randint(0, input_img_size[1] - img_size)
            img = decode_and_crop_jpeg(x, h_offset, w_offset, 
                                       img_size, img_size)

        if self.transform is not None:
            img = self.transform(img)
        return img, target

    def __len__(self):
        return len(self.img_files)

transform = T.Compose(
    [RandomMask(),
     ConvertColor(),
     Scale()])

结果

根据我们描述的优化步骤,我们的步骤时间从 0.72 秒降到了 0.48 秒(提高了 50%)!显然,我们优化的影响与原始 JPEG 图像的大小和我们选择的裁剪大小直接相关。

摘要

数据预处理管道中的瓶颈是常见的现象,可能导致 GPU 资源不足并减慢训练。考虑到潜在的成本影响,拥有多种工具和技术来分析和解决这些问题是至关重要的。在本文中,我们回顾了通过创建自定义 C++ PyTorch 扩展来优化数据输入管道的选项,展示了其易用性,并说明了其潜在影响。当然,这种优化机制的潜在收益会根据项目和性能瓶颈的细节而大相径庭。

这里讨论的优化技术加入了我们在许多博客文章中讨论的广泛的输入管道优化方法。我们鼓励你查看这些文章(例如,从这里开始)。

接下来做什么? 在我们关于 PyTorch 性能分析和优化系列的第六部分中,我们探讨了分析性能问题中较复杂的类型——训练步骤的反向传播过程中的瓶颈。

如何优化你的营销预算

原文:towardsdatascience.com/how-to-optimize-your-marketing-budget-63707c18ba36

营销分析

是时候收获你辛苦的营销组合模型训练成果了!

Dr. Robert KüblerTowards Data Science Dr. Robert Kübler

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

--

图片由 DALL-E 创建。

营销组合模型是理解不同营销渠道对销售影响的强大工具。通过构建营销组合模型,营销人员可以量化每个渠道对整体销售的贡献,然后利用这些信息来优化预算分配。

到目前为止,我已经写了一整系列关于 构建 营销组合模型 的文章,但我仍欠你一篇关于如何使用这些模型来优化媒体支出的文章。今天是你的幸运日,因为在这篇文章中,我将展示如何做到这一点!

图片由作者提供。

如果你对营销组合建模不熟悉,可以从我的介绍文章开始:

## Python 中的营销组合建模简介

哪些广告支出真正推动了你的销售?

towardsdatascience.com

前提条件

在我们可以优化任何东西之前,我们必须先构建一个模型。我们将非常快速地完成这个过程,以便尽快进入本文的主要部分。

数据

首先,让我们加载一些数据。我将使用与我旧文章中相同的数据集。

import pandas as pd
from sklearn.model_selection import cross_val_score, TimeSeriesSplit

data = pd.read_csv(
    'https://raw.githubusercontent.com/Garve/datasets/4576d323bf2b66c906d5130d686245ad205505cf/mmm.csv',
    parse_dates=['Date'],
    index_col='Date'
)

X = data.drop(columns=['Sales'])
y = data['Sales']

数据集如下所示:

图片由作者提供。

这个表格的逻辑如下:假设你在一家销售某种产品的公司工作。你可以在“销售”列中看到该产品的每周销售情况。为了提升这些销售,你在广告上花了一些钱,在我们的例子中包括 电视广播横幅 广告。我们现在想通过广告支出和更多的控制变量来建模销售情况,例如工作日、月份、产品价格、天气等。

作者提供的图片。

模型

建立复杂的模型,如 XGBoost 或深度神经网络,难以解释和优化。我们转向一种经过验证的方法,该方法使用可解释的滞后效应和饱和度效应来建立广义加性模型,如这里所做的:

作者提供的图片。

滞后效应和饱和度模块是直观的特征转换:

  • 滞后效应模型表示某个时间 t 的媒体支出可能仍然会影响时间 t + 1、t + 2 等的销售,或反之,即在时间 t 观察到的销售也受到时间 t - 1、t - 2 等的支出的影响。

  • 饱和度 模型显示收益递减的效应,例如,将你在一个渠道的支出从 0 欧元增加到 100,000 欧元会有很大的影响,但将支出从 1,000,000,000 增加到 1,000,100,000 就不会有太大影响了。

注意: 在图示中,控制变量被省略了。这是可以的,因为我们不需要它们来进行优化——我们无法像投入到媒体渠道中的资金那样改变它们。我们 可以 改变的唯一控制变量是价格,但在这里我们假设价格是常量,我们只希望优化我们的媒体支出。

所以,模型的形式是

作者提供的图片。

对于一些尚未定义的函数 饱和度滞后效应。举个例子,假设

作者提供的图片。

作者提供的图片。

β 是饱和度系数,λ 是滞后效应强度, 是滞后效应长度。

我们可以通过将这些参数视为超参数,或者通过使用贝叶斯方法并将其视为常规的可学习参数来学习这些参数。在我之前关于营销组合建模的所有文章中,我们详细介绍了如何获得这些参数,因此在此我将不再深入探讨。

相反,假设我们现在有了这些数字,并希望用它们来创建一个优化的媒体预算分配计划。

媒体预算优化

假设我们之前的营销组合建模尝试给我们留下了以下参数:

N = 200 # number of observations

# previous marketing mix modeling has given us these parameters
tv_coef = 10000       # α
tv_lags = 4           # ℓ
tv_carryover = 0.5    # λ
tv_saturation = 0.002 # β

radio_coef = 8000
radio_lags = 2
radio_carryover = 0.2
radio_saturation = 0.0001

banners_coef = 14000
banners_lags = 0
banners_carryover = 0.2
banners_saturation = 0.001

我们现在将使用 numpy 在 Python 中重新构建营销组合模型。

为什么?我们已经使用了 scikit-learn 或 PyMC 构建了模型!难道不能重用这些模型吗?

好问题!我们可以使用我们预训练的模型,并将其交给一个通用优化算法,试图找到最大化销售的媒体支出输入。然而,这被称为黑箱优化,它的问题在于容易陷入局部最优而不是找到全局最优解。

黑箱优化的另一个问题是算法通常有各种参数,你需要调整这些参数来找到一个好的(但可能不是最优的)解决方案。这就是为什么有些人说这种优化更像是一门艺术,而不是科学。

凸性来拯救

如果我们能将问题表述为凸优化问题,我们可以使用诸如cvxpy之类的库来解决,它们保证能找到最佳的媒体预算分配。我已经在这里使用了这个库来解决另一个优化问题。

为了使用凸优化方法,我们的模型必须是凸的或凹的,这意味着在模型前面加上负号使其成为凸的。

图片由作者提供。

例如,如果我们的模型是y = x²,它将是一个容易最小化的凹函数。y = 100 - x²将是一个容易最大化的凹模型。

我不会进一步详细说明;只需知道我们的模型实际上是一个凹函数!在我们创建的模型过渡饱和模型中,如果饱和函数的二阶导数为负,那么模型就是凹的。

图片由作者提供。

然而,如果我们使用其他饱和函数,如 Adbudg 或其他典型的 S 型函数,它们可能既不是凹的也不是凸的,这使得优化变得更加困难。

图片由作者提供。

好的,理论说够了。现在只需记住,我们的模型是凹的,这很好,因为这样我们可以找到全局最优解,即能够带来最大销售额的预算分配。

在 Numpy 中重新实现我们的模型

首先,让我们定义一些处理过渡效应的矩阵。

import numpy as np

tv_carryover_matrix = sum([np.diag(tv_carryover**i*np.ones(N-i), k=-i) for i in range(tv_lags)])
radio_carryover_matrix = sum([np.diag(radio_carryover**i*np.ones(N-i), k=-i) for i in range(radio_lags)])
banners_carryover_matrix = np.eye(N)

我知道这很难理解,所以让我们看看这些矩阵中的一个。

图片由作者提供。

这实现了一个强度为 0.2、长度为 1 的过渡效果。如果将这个矩阵与支出向量相乘,你可以看到这一点。

图片由作者提供。

处理完这些之后,让我们继续谈谈饱和度。这只是一个涉及exp的简单公式,所以没问题。

我们可以写:

sales = (
    tv_coef * np.sum(1 - np.exp(-tv_saturation * tv_carryover_matrix @ data["TV"])) 
    + radio_coef * np.sum(1 - np.exp(-radio_saturation * radio_carryover_matrix @ data["Radio"])) 
    + banners_coef * np.sum(1 - np.exp(-banners_saturation * banners_carryover_matrix @ data["Banners"]))
)

这给出了来自我们的营销活动的销售总额,因为我们在这里忽略了控制变量。这个数字是 3,584,648.73 €,我们现在想通过改变我们的媒体支出将其增加!剧透:事实证明,我们可以将这个数字增加约 150 万,达到 5,054,070.21 €。哇!仅仅调整一些数字就能得到这样的结果,真不错。

图片由作者提供。

关于结果

你还可以看到一个有趣的模式。

看起来,均等分配预算是最佳选择。

只有第一期的支出稍高,但随后会有所平衡。这是因为我们的模型的工作原理:在第一期,没有广告存量,因此我们必须多投资一些来推动销售。自第二期开始,我们只需在模型中投入足够的金额来保持广告存量高,但又不能过高以免饱和。在最后一期,广告存量不再重要,因为时间到那里结束,就模型而言。因此,优化预算在那里较低。

在 CVXPY 中重新实现我们的模型

好了,现在我们准备使用 cvxpy 得到那个最佳解。首先,我们定义变量,在我们的案例中,每个渠道和每个时间步长一个,因此总共有 3**N* = 3*200 = 600 个变量。

如果没有其他因素,最优解将是将所有变量设为无穷大,因此我们需要一些约束。变量应该都是

  1. 非负的,以及

  2. 我们希望这 600 个变量的总和小于或等于我们历史上支出的金额。

然后,我们希望优化使用 numpy 函数实现的模型,但使用其 cvxpy 等效函数,这通常意味着编写cp而不是np。我们甚至可以重用之前的 carryover 矩阵!

import cvxpy as cp

original_total_spends = data[["TV", "Radio", "Banners"]].sum().sum()

# declaring variables to be optimized, N=200 per channel
tv = cp.Variable(N)
radio = cp.Variable(N)
banners = cp.Variable(N)

# the constraints, positive spends and a bounded total budget
constraints = [
    tv >= 0,
    radio >= 0,
    banners >= 0,
    cp.sum(tv + radio + banners) <= original_total_spends,
]

# cvxpy formulation, the model looks like the numpy version
problem = cp.Problem(
    cp.Maximize(
        tv_coef * cp.sum(1 - cp.exp(-tv_saturation * tv_carryover_matrix @ tv)) \
        + radio_coef * cp.sum(1 - cp.exp(-radio_saturation * radio_carryover_matrix @ radio)) \
        + banners_coef * cp.sum(1 - cp.exp(-banners_saturation * banners_carryover_matrix @ banners))
    ), # like the numpy model, sum of all sales
    constraints
)

我们现在可以在非常短的时间内解决这个最大化问题

problem.solve()

# Output:
# 5054070.207463957

太好了!我们可以通过tv.value, radio.value, banners.value获得最佳预算。你可以看到每个渠道的支出在每周都保持相对稳定,这可能没有预期的那么有趣。但最佳就是最佳,所以我们接受它。

我们本来可以在过去获得 500 万,而不是 360 万。虽然知道这一点很不错,但现在已经没有价值,可能还会让业务感到不快。不过,我们当然可以利用这个逻辑来优化未来的营销支出!

进一步的约束

就这样,你现在拥有一个基本的预算优化工具!好的一部分是,你可以建模更多可能来自业务的约束。例如,业务可能会说广播总支出相当高:

sum(radio.value)

# Output: 
# 524290.3686626207 (= 524,290.37 €)

业务希望其低于 300,000 €,出于模型无法知晓的战略原因。好的,没问题,让我们将其加入约束集!

constraints = [
    tv >= 0,
    radio >= 0,
    banners >= 0,
    cp.sum(tv + radio + banners) <= original_total_spends,
    cp.sum(radio) <= 300000 # new constraint
]

就这么简单。我们可以让优化再次运行,最终得到略微减少的优化销售额为 4,990,178.80 €。但如果我们现在检查广播支出的总和

sum(radio.value)

# Output: 
# 299999.9992275703

我们可以看到商业约束得到了尊重。我们还可以添加更多的约束,比如

  • 两个频道的总和应该小于或大于某个数字,或者

  • 在某些周,我们不允许任何媒体支出。

你只需使用一些总和和等式或不等式来建模即可。

结论

在这篇文章中,我们首先回顾了市场营销混合模型的公式。这一点很重要,因为我们需要重新实现这些模型。幸运的是,由于我们的模型简单且易于解释,这完全不是问题。

我们的模型实际上还有另一个很棒的属性:它是凹的!在这种情况下,销售的最大值是唯一确定的,我们可以通过凸优化来获得它。优化非凸或非凹函数一般是困难的,更像是一种艺术,涉及到调整许多超参数,这就是为什么我们没有选择这种方法。

作为大结局,我们优化了我们的媒体预算!是时候了。我们甚至看到如何在模型中加入更多的约束,例如某些频道需要一些最低或最高预算分配。使用这种方法,你现在可以优化未来的媒体预算分配。

另一个我们没有讨论的优化是在你想达到某个最低销售额的约束下最小化你的媒体预算,即尽可能少花钱仍然达到目标。这是你也可以轻松实现的!相反,在之前我们把所有的钱都用了,做了尽可能多的销售。

我希望你今天学到了新的、有趣的和有价值的东西。感谢阅读!

最后一点,如果你

  1. 想支持我写更多关于机器学习的内容

  2. 反正计划订阅一个 Medium 账号,

为什么不通过这个链接来做呢?这将对我帮助很大!😊

为了透明起见,对你来说价格不会改变,但大约一半的订阅费用直接归我所有。

如果你考虑支持我,非常感谢!

如果你有任何问题,可以在 LinkedIn上给我写信!

如果你想深入了解算法的世界,可以尝试一下我的新出版物《全关于算法》!我还在寻找作者!

[## 全关于算法

从直观解释到深入分析,算法通过示例、代码和精彩内容生动起来…

medium.com

如何组织数据科学项目

原文:towardsdatascience.com/how-to-organize-your-data-science-project-3710a476bf8c

环境设置,数据科学

通过手动安装、Cookiecutter 或云服务来高效规划和组织数据科学项目的策略。

安杰丽卡·洛·杜卡Towards Data Science 安杰丽卡·洛·杜卡

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

--

图片由 Alvaro Reyes 提供,来源于 Unsplash

成功的数据科学项目需要在各个阶段进行仔细的规划和组织。无论你选择手动组织还是使用外部工具,你都可以采用各种策略来简化工作流程。

本博客文章将探讨组织数据科学项目的三种主要策略:

  • 手动组织

  • 使用外部工具进行管理

  • 使用云服务

手动组织

手动组织涉及使用目录和文件来构建数据科学项目,而不依赖于任何外部工具。这种方法让你完全控制组织方式,并允许你根据项目需求进行调整。

遵循下面描述的最佳实践来手动组织你的数据科学项目:

  1. 创建一个项目目录用于你的数据科学项目。这将作为所有项目文件的根目录。
project_dir/

2. 分离数据和代码:将项目分为两个主要目录:与数据相关的文件和与代码相关的文件。

project_dir/
    ├── data/
    ├── code/

3. 组织数据文件:在data目录下,创建子目录以存储不同类型的数据,例如原始数据、处理数据和中间结果。

project_dir/
    ├── data/
    │   ├── raw/
    │   ├── processed/
    │   └── intermediate/
    ├── code/

4. 根据功能将代码拆分为模块。每个模块应有自己的目录,并包含相关的脚本或笔记本。

project_dir/
    ├── data/
    ├── code/
    │   ├── preprocessing/
    │   ├── modeling/
    │   └── evaluation/

5. 使用版本控制:在项目目录内初始化一个 Git 仓库,以跟踪更改并有效地与他人协作。

project_dir/
    ├── .git/
    ├── data/
    ├── code/

6. 包含一个 README 文件以描述你的项目。

project_dir/
    ├── .git/
    ├── data/
    ├── code/
    └── README.md

7. 利用虚拟环境 来隔离依赖项并确保可重现性。

project_dir/
    ├── .git/
    ├── data/
    ├── code/
    ├── README.md
    └── env/

现在你已经学会了如何手动组织数据科学项目,让我们进入下一步,使用外部工具进行管理。

使用外部工具进行管理

手动安装可能会耗时且容易出错。此外,缺乏文档化的过程使得重现精确的软件环境变得困难,阻碍了合作和准确重现结果的能力。你可以使用外部数据科学项目管理工具来克服这些问题。

许多工具存在于项目管理领域。在本文中,我们将专注于Cookiecutter。Cookiecutter 使你能够基于预定义的模板定义项目结构。它提供了一个命令行界面来生成项目目录、文件和初始代码片段。

  1. 首先安装 Cookiecutter:
pip install cookiecutter

2. 选择一个数据科学项目模板:你可以浏览GitHub 或其他社区驱动的仓库中可用的模板。例如,你可以使用由官方 Cookiecutter 仓库定义的模板来组织数据科学项目模板

3. 运行以下命令以安装模板:

cookiecutter -c v1 https://github.com/drivendata/cookiecutter-data-science

模板需要安装Git。Cookiecutter 会提示你提供模板中定义的项目特定参数的值,例如项目名称、作者和项目描述。输入所需的信息以自定义项目。以下代码展示了提示的示例:

> cookiecutter https://github.com/drivendata/cookiecutter-data-science
project_name [project_name]: my-test
repo_name [my-test]: my-test-repo
author_name [Your name (or your organization/company/team)]: angelica
description [A short description of the project.]: a test project
Select open_source_license:
1 - MIT
2 - BSD-3-Clause
3 - No license file
Choose from 1, 2, 3 [1]: 1
s3_bucket [[OPTIONAL] your-bucket-for-syncing-data (do not include 's3://')]:
aws_profile [default]:
Select python_interpreter:
1 - python3
2 - python
Choose from 1, 2 [1]: 1

下图显示了生成的目录和文件:

作者提供的图像

现在你可以开始处理你的文件了。

在 Cookiecutter 中,你可以通过遵循Cookiecutter 官方仓库中描述的过程来定义自定义模板。

使用云服务

到目前为止,我们已经看到两种组织数据科学项目的技术:一种是手动技术,另一种是基于 Cookiecutter 的技术。实际上,还有第三种技术,它几乎完全解决了计算机上文件和文件夹的组织问题。这就是使用云服务

这类服务有很多,从技术上讲,这些服务被称为模型跟踪平台或实验平台。这些服务的例子有CometNeptuneMLflow(你可以将其安装到你的计算机上)。这些服务旨在管理所有实验、代码、数据,甚至结果在云端。

模型跟踪平台还提供仪表盘,你可以通过表格或图形直接比较实验结果。下图显示了 Comet 中的一个仪表盘示例。

Comet 中的一个仪表盘示例

你可以在 这个链接 浏览其他仪表盘示例。

使用模型跟踪平台相当简单。下图展示了模型跟踪平台的架构示例。

作者提供的图片

你从本地模型开始,这些模型可以存储在一个文件中。然后你将它们保存在模型跟踪平台上,该平台除了包含仪表盘,还包含用于访问生成资产的注册表。你可以将结果导出到报告中或将其集成到部署流程中。

使用模型跟踪平台是一个不错的解决方案。但是,记住这些服务可能需要你花费资金。

摘要

恭喜!你刚刚学会了如何组织你的数据科学项目!你可以使用以下技巧之一:

  • 手动组织,耗时且容易出错

  • 外部工具,如 Cookiecutter,帮助创建项目的初始结构

  • 云服务,为你组织所有代码,但可能需要你付费。

选择最适合你需求和要求的技巧,以确保数据科学项目的良好组织和成功!

你可能还对以下内容感兴趣…

开始使用 Comet ML [## 开始使用 Comet ML

关于流行的机器学习实验平台的概述,以及一个实际的例子。

开始使用 Comet ML [## Comet 用于数据科学

如何通过 Comet 平台(一个用于模型跟踪的工具)提升数据科学项目的生命周期...

如何使用 Pytest 测试你的 Python 代码 [## 如何使用 Pytest 测试你的 Python 代码

使用 Pytest 简化你的测试过程并提高代码质量

如何使用 Pytest 测试你的 Python 代码

如何使用 OpenCV 进行阈值化图像分割

原文:towardsdatascience.com/how-to-perform-image-segmentation-with-thresholding-using-opencv-b2a78abb07ac

图片由 Lysander Yuen 提供,来源于 Unsplash

简单、Otsu 和自适应阈值化的实现及示例

Rashida Nasrin SuckyTowards Data Science Rashida Nasrin Sucky

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

--

阈值化是最常用的分割技术之一。它在计算机视觉中被广泛使用,并且对于将我们感兴趣的对象从背景中分离出来非常有用。本文将重点讨论 OpenCV 中的阈值化技术。

有三种不同类型的阈值化方法。我们将了解它们的高层工作原理,并主要关注如何在 OpenCV 中使用它们及示例。

什么是阈值化

阈值化是图像二值化的过程。如果你像我刚开始时一样对灰度图像和二值图像感到困惑,灰度图像可能有一系列的阴影。但在二值图像中,像素值要么是 0,要么是 255。

简单阈值化

这是一种最基本的阈值化方法,在许多实际应用中更难使用。但我们仍然需要从学习这种方法开始,以理解阈值化的实际工作原理。我们将看到简单阈值化如何发挥作用。

简单的阈值化在前景(图像中我们感兴趣的对象)和背景之间对比度高的非常受控的光照条件下表现良好。 简单阈值化最困难的部分在于,我们需要手动提供阈值,这可能需要多次试验。这样,可能会变得非常耗时。让我们来做一个示例。

我们将使用以下图像进行本教程。请随意保存此图像并跟随操作。

照片由Lucas Hoang提供,在Unsplash

首先,我们需要导入必要的库。

import cv2 
import matplotlib.pyplot as plt 

使用 OpenCV 将图像读取为数组:

image = cv2.imread("meter.jpg")

由于阈值化是二值化,我们从灰度图像开始。此外,对灰度图像应用高斯模糊有助于去除一些高频边缘和噪声。

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (7, 7), 0)

这是简单的阈值化:

(T, thresh) = cv2.threshold(blurred, 45, 255, cv2.THRESH_BINARY)

第一个参数是模糊后的图像,

45 是阈值,表示如果像素值大于 45,它将变为 255,否则为 0。因此,第三个参数是 255。如果你选择不同于 255 的值,如 200,那么大于 45 的像素将变为 200

请随意尝试不同的值,看看会发生什么。

在经过很多次试验后,我决定使用阈值 45。

所以,花了一些时间。这是‘thresh’的样子:

cv2.imwrite('a.png', thresh)

图片由作者提供

我们也可以使用 cv2.THRESH_BINARY_INV,它会做相反的操作。让我们看看如果用 cv2.THRESH_BINARY_INV 使用相同的阈值 45 效果如何:

(T, threshInv) = cv2.threshold(blurred, 45, 255, cv2.THRESH_BINARY_INV)

图片由作者提供

在这种情况下,当像素大于阈值 45 时,它变为 0,否则为 255。

正如我之前提到的,我经历了很多次试验才找到阈值 45。

但如果同一张图片的光照条件不同,这个阈值将不起作用。

这就是为什么在实际应用中,当我们可能没有关于光照条件的先验知识时,这种方法很难使用。 我们需要找到一个可以缩放的最佳阈值。

Otsu 阈值化

在 Otsu 的阈值化中,OpenCV 为你找到最佳阈值。它接受灰度图像并计算最佳阈值 T。

这里是 Otsu 方法选择阈值的高级概述。如果用所有灰度图像的像素值制作分布图,Otsu 方法会找到一个最佳阈值,来最优地分隔分布的峰值。

(T, threshOtsu) = cv2.threshold(blurred, 0, 255, 
                cv2.THRESH_BINARY | cv2.THRESH_OTSU)

看,这里我们使用了 0 作为阈值,仅仅因为 Otsu 的算法会为我们找到最佳阈值。

让我们看看最佳阈值是多少:

T

输出:

112.0

这是‘threshOtsu’的图像:

cv2.imwrite("au.jpg", threshOtsu)

图片由作者提供

我们想专注于计量读取部分,对吧?那么,我们如何从这张图像中做到这一点?只需使用 bitwise_and 操作对原始图像进行遮罩。

masked1 = cv2.bitwise_and(image, image, mask=threshInvOtsu)
cv2.imwrite('amasked.jpg', masked1)

这里是图像:

图片由作者提供

背景中的大量噪声被平滑掉了。

简单和 Otsu 阈值化方法的问题是它们在整个图像上使用单一的阈值,这可能不是一个好主意。对图像某个区域有效的阈值可能对图像的其他区域不适用。

自适应阈值

自适应阈值方法考虑了一小部分像素,并为图像的每个小区域计算一个阈值。我们将在这个示例后进一步解释:

thresh = cv2.adaptiveThreshold(blurred, 255, 
                               cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                              cv2.THRESH_BINARY, 17, 3)
cv2.imwrite('a1.jpg', thresh)

这里第二个参数 255 表示当像素值大于局部阈值时,它变为 255。第五个参数 17 表示它将是图像的 17x17 区域用于计算局部阈值。最后,3 是一个常数,这个值会从计算的阈值中减去。根据你的项目,你需要选择一个图像区域来计算局部阈值和常数。

这就是图像的样子:

作者提供的图像

根据上述讨论,自适应阈值也需要一些尝试来确定最后两个参数。但方法本身仍然对我们做了很多工作。

结论

所有三种阈值方法都很重要。根据你的情况或项目,每种方法都有可能派上用场。请随意尝试不同的图像。

随时欢迎关注我的Twitter并点赞我的Facebook页面。

进一步阅读

## 形态学操作用于 OpenCV 中的图像预处理,详细介绍

腐蚀、膨胀、开运算、闭运算、形态学梯度、顶帽/白帽和黑帽的解释及示例

towardsdatascience.com ## Python 初学者的一些基本图像预处理操作

OpenCV 入门:移动或平移、调整大小和裁剪

towardsdatascience.com ## 逐步教程:在 TensorFlow 中开发多输出模型

附有完整代码

towardsdatascience.com [## Python 的 Matplotlib 可视化中的一些简单但高级的样式

为你的 Python 绘图增添一些额外的风味

pub.towardsai.net](https://pub.towardsai.net/some-simple-but-advanced-styling-in-pythons-matplotlib-visualization-107f3be56a24?source=post_page-----b2a78abb07ac--------------------------------) ## 多分类的精准率、召回率和 F1 分数 — 深入学习

从混淆矩阵手动计算和 sklearn 库的语法

towardsdatascience.com ## 如何在 Python 中制作动画和竞赛条形图

完整的工作代码

towardsdatascience.com

如何使用 Python 执行 KMeans 聚类

原文:towardsdatascience.com/how-to-perform-kmeans-clustering-using-python-7cc296cec092

KMeans 聚类和 Python 实现的完整概述

Zoumana KeitaTowards Data Science Zoumana Keita

·发布于 Towards Data Science ·阅读时间 7 分钟·2023 年 1 月 17 日

--

介绍

假设你是一家零售公司工作的数据科学家,你的老板要求将客户根据消费行为划分为以下组别:低价值、平均价值、中等价值或铂金客户,以便进行有针对性的市场营销和产品推荐。

考虑到这些客户没有任何历史标签,我们如何对他们进行分类?

这就是聚类能够发挥作用的地方。它是一种无监督机器学习技术,用于将未标记的数据分组到相似的类别或簇中。

本概念文章将更多地关注 K-means 聚类方法,这是无监督机器学习中众多技术之一。文章将首先概述 K-means 聚类的基本概念,然后带你逐步实现 Python 中的操作,使用流行的 Scikit-learn 库。

什么是 K-Means 聚类?

K-means 聚类的基本思想是将数据集划分为指定数量的簇(k),其中同一簇内的所有点彼此相似,而不同簇中的点则不同。

它的工作原理是随机将每个数据点分配到一个簇中,然后通过将数据点移动到距离它们最近的簇中心来迭代改进这些簇。这一逻辑持续进行,直到簇分配停止变化,或者达到最大迭代次数。

K-means 聚类的关键步骤是什么?

以下是 K-means 算法的五个主要步骤:

K-Means 聚类的五个主要步骤(图像由作者提供)

下面我们可以看到 K-means 的一个示意图,其中在第 14 次迭代时达到了收敛。

k-均值聚类算法的收敛(图片来源于维基百科

K-means 聚类的实际应用

现在我们了解了 K-means 的工作原理,让我们看看如何在 Python 中实现它。

首先,你需要安装以下库:

  • 使用 Pandas 加载数据框。

  • 使用 Matplotlib 进行数据可视化。

  • 使用 Scikit-learnKmean 算法。

安装可以通过 pip,Python 包管理器,按如下方式进行:

# Scikit Learn
pip install scikit-learn

# Pandas
pip install pandas

# Matplotlib
pip install matplotlib

导入库并加载数据

现在你对 K-means 聚类算法有了理解,让我们深入探讨。我们将使用 Mall Customer Data,这是 Kaggle 上免费提供的数据。

对于每个客户,它包含这些基本信息:IDGenderAgeIncomeAnnual Spending score

import pandas as pd
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans

# load the customer data into a DataFrame
customer_df = pd.read_csv('customer_data.csv')

# Check the first 5 rows
customer_df.head()

之前的 .head() 语句应该生成以下结果:

客户数据的前 5 行(图片由作者提供)

探索数据

在进一步实现算法之前,让我们快速统计和可视化地了解数据。

plt.scatter(customer_df["Age"], 
            customer_df["Spending Score (1-100)"])

plt.xlabel("Age")
plt.ylabel("Spending Score (1-100)")

客户年龄与花费得分的散点图(图片由作者提供)

 plt.scatter(customer_df["Age"], 
            customer_df["Annual Income (k$)"])

plt.xlabel("Age")
plt.ylabel("Annual Income (k$)")

客户年龄与年收入的散点图(图片由作者提供)

plt.scatter(customer_df["Spending Score (1-100)"], 
            customer_df["Annual Income (k$)"])

plt.xlabel("Spending Score (1-100)")
plt.ylabel("Annual Income (k$)")

客户花费得分与年收入的散点图(图片由作者提供)

所有这些图表有不同的结果,从而导致不同的解释。例如,第一个图表似乎提出了两个不同的客户群体,而第二个图表不明显,最后一个图表显示了五个不同的群体。这时,Kmeans 将有助于有效生成正确的群体/簇。

此外,我们从以下结果中看到数据中没有缺失值。

# Check for null values
customer_df.isnull().sum()

数据中没有空值(图片由作者提供)

获取用于聚类的相关列

并不是所有的列都与聚类相关。在这个例子中,我们将使用数值列:AgeAnnual IncomeSpending Score

relevant_cols = ["Age", "Annual Income (k$)", "Spending Score (1-100)"]

customer_df = customer_df[relevant_cols]

数据转换

Kmeans 对数据的测量单位和尺度敏感。最好先对数据进行标准化以解决这个问题。此外,这是在实施任何机器学习模型之前的常见做法。

基本上,标准化过程是从实际值中减去任何特征的均值,然后除以该特征的标准差。

这个过程很简单,以下是用 Python 完成的步骤:

  • 使用 sklearn.preprocessing 模块中的 StandardScaler 类。

  • 应用 fit() 方法来计算特征的均值和标准差。

  • 然后最终使用 transform() 来缩放数据。

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()

scaler.fit(customer_df)

scaled_data = scaler.transform(customer_df)

确定最佳的簇数量

如果我们未能确定正确的簇数量,则聚类模型将无关紧要。文献中存在多种技术。我们将考虑肘部法,它是一种启发式方法,也是寻找最佳簇数量的广泛使用方法之一。

第一个辅助函数为每个K值创建对应的KMeans模型,并保存其惯性值及实际K值。

第二个函数利用这些惯性值和K值生成最终的肘部图。

def find_best_clusters(df, maximum_K):

    clusters_centers = []
    k_values = []

    for k in range(1, maximum_K):

        kmeans_model = KMeans(n_clusters = k)
        kmeans_model.fit(df)

        clusters_centers.append(kmeans_model.inertia_)
        k_values.append(k)

    return clusters_centers, k_values
def generate_elbow_plot(clusters_centers, k_values):

    figure = plt.subplots(figsize = (12, 6))
    plt.plot(k_values, clusters_centers, 'o-', color = 'orange')
    plt.xlabel("Number of Clusters (K)")
    plt.ylabel("Cluster Inertia")
    plt.title("Elbow Plot of KMeans")
    plt.show()

现在,我们可以使用最大K值为12的函数对数据集进行处理,得到最终结果。

clusters_centers, k_values = find_best_clusters(scaled_data, 12)

generate_elbow_plot(clusters_centers, k_values)

以下是最终结果。

寻找最佳簇数量的图表(图片由作者提供)

从图中我们可以看到,随着簇的数量增加,簇的惯性减少。而且,在K=5之后,惯性的下降幅度最小,因此5可以被认为是最佳的簇数量。

创建最终的 KMeans 模型

一旦我们确定了最佳的簇数量,我们可以按照以下步骤将 KMeans 模型应用于该值。

kmeans_model = KMeans(n_clusters = 5)

kmeans_model.fit(scaled_data)

我们可以通过使用.labels_属性来访问每个数据点所属的簇。让我们创建一个对应这些值的新列。

customer_df["clusters"] = kmeans_model.labels_

customer_df.head()

聚类后的最终数据集(图片由作者提供)

通过查看前 5 个客户,我们可以观察到前两个和最后两个被分配到第一个簇(簇#1),而第三个客户属于第三个簇(簇#3)。

可视化簇

既然我们已经生成了簇,最后一步是可视化它们。

plt.scatter(customer_df["Spending Score (1-100)"], 
            customer_df["Annual Income (k$)"], 
            c = customer_df["clusters"])

簇可视化(图片由作者提供)

KMeans 聚类似乎生成了相当不错的结果,五个簇之间分隔良好,尽管紫色和黄色簇之间有轻微的重叠。

总体观察如下:

  • 左上角的客户消费评分较低但年收入较高。可以实施一种好的营销策略来针对这些客户,使他们能够花费更多。

  • 另一方面,左下角的客户年收入较低,支出也较少,这很合理,因为他们试图根据预算调整消费习惯。

  • 右上角的客户与左下角的客户类似,不同之处在于他们有足够的预算来消费。

  • 最终,黄色组的客户超出了他们的预算。

结论

恭喜,你已经学会了如何使用 Python 进行 KMeans 聚类。希望你已经掌握了高效分析未标记数据集所需的技能。

如果你喜欢阅读我的故事并希望支持我的写作,可以考虑成为 Medium 会员。通过每月$5 的承诺,你可以无限制地访问 Medium 上的故事。

随时在我的社交网络上关注我。讨论 AI、ML、数据科学、NLP 和 MLOps 相关话题总是一件愉快的事!

博客的源代码可以在 GitHub 上找到

如何在 Python 的 PyOD 中进行多变量异常值检测

原文:towardsdatascience.com/how-to-perform-multivariate-outlier-detection-in-python-pyod-for-machine-learning-b0a9c557a21c

异常值检测系列,第三部分

Bex T.Towards Data Science Bex T.

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

--

照片由 Takashi Miyazaki 提供,出处 Unsplash

动机

以下是一个非常可疑的箱型图:

作者提供的图片。

它描绘了钻石品质与价格之间的关系。六个品质类别按降序排列,因此最好的钻石在理想类别中,而最低质量的钻石在公平类别中。

现在,这里是一些奇怪的部分。首先,所有类别都有许多异常值,这些异常值用胡须上方的暗点标记。

其次,尽管理想钻石应该是最好的,但它们的中位数价格却低于任何其他类别(中位数以箱子内部的线表示)。

这种钻石品质与价格之间的奇怪关系让我们提出了一个问题:那些异常值真的是异常值吗?

今天,我们将回答这个确切的问题,换句话说,我们将学习如何区分多变量异常值以及如何检测它们。

这是我们异常值检测系列的第三部分。请查看下面的前两部分:

## 如何在 Python 中进行异常值检测:第一部分

地球是一个异常值——这个理论

towardsdatascience.com

更多关于多变量异常值的内容

在我们的 第一部分 中,我们提出了以下观点。

单变量异常值,顾名思义,仅存在于单个分布或数据集的单独列中。它们通过 z 分数或中位数绝对偏差等方法更容易检测。例如,在记录仅有人身高(单列)的数据集中,一个非常高的人。

多变量异常值更难发现,因为它们通常在同时查看多个数据集列时才会出现。

例如,假设我们有一个医院记录的数据集。它的列包括病人的身体特征和他们所患的疾病类型。现在,我们查看亨利的记录:

年龄:18 身高:178 厘米 体重:65 公斤 非吸烟者:是 疾病:肺癌

如果我们单独考虑亨利的特征,它们在我们的数据集上下文中看起来很普通。在我们的数据集中,有许多 18 岁的人,也有许多人身高 178 厘米或体重 65 公斤。

另外,许多不吸烟的 18 岁青少年或肺癌患者都在医院。但是,我们没有任何一个不吸烟的 18 岁青少年,同时体重 65 公斤且身高 178 厘米且患有肺癌。

只有当我们同时考虑亨利的所有身体特征时,亨利作为多变量异常值的事实才会浮出水面。

亨利的类似案例可能会出现在另一个数据集中的几十个甚至几百个属性中。因此,我们必须学习更复杂的方法来检测多变量异常值。

这就是机器学习方法,特别是异常值分类器,发挥作用的地方。

加载数据集

让我们首先加载我们将要使用的数据集。这是上一个教程中相同的 Diamonds 数据集:

import seaborn as sns 

diamonds = sns.load_dataset("diamonds")
diamonds.head()

图片由作者提供

我们进行一个小的预处理步骤,将类别特征编码为 Sklearn 的 OrdinalEncoder 类:

import numpy as np
from sklearn.preprocessing import OrdinalEncoder

# Extract feature and target arrays
X, y = diamonds.drop("carat", axis=1), diamonds["carat"]
# Select categorical column names
cats = X.select_dtypes(exclude=np.number).columns.tolist()

# Encode categoricals
X.loc[:, cats] = OrdinalEncoder().fit_transform(X[cats])

现在,让我们开始吧!(搓手)

使用 PyOD 进行多变量异常检测

尽管理论可能有些困难,但得益于 Python Outlier Detection (PyOD) 库,执行多变量异常检测在代码中非常简单。让我们通过 Local Outlier Factor 算法来看一个例子:

# Import LOF
from pyod.models.lof import LOF

# Initialize
lof = LOF(n_neighbors=30).fit(X)

# Extract inlier/outlier labels
labels = lof.labels_
outliers_X_lof = X[labels == 1]

在从 pyod 中导入 LOF 估算器后,我们用 30 个邻居初始化它,并拟合到 X(不用担心 LOF 的细节,我们稍后会讨论)。

然后,我们访问它的 labels_ 属性,该属性返回 X 每一行的 0(正常值)或 1(异常值)。让我们来计算一下异常值:

num_outliers = len(outliers_X_lof)
print(f"The number of outliers: {num_outliers}")
print(f"Percentage of outliers: {num_outliers / len(X):.3f}")
The number of outliers: 5394
Percentage of outliers: 0.100

嗯,我们发现了 5394 个异常值,正好占数据集的 10%。这是不是巧合?让我们尝试使用另一个异常值分类器 Isolation Forest 进行相同的操作:

from pyod.models.iforest import IForest

iforest = IForest(n_estimators=500).fit(X)

labels = iforest.labels_
outliers_X_iforest = X[labels == 1]

num_outliers = len(outliers_X_iforest)
print(f"The number of outliers: {num_outliers}")
print(f"Percentage of outliers: {num_outliers / len(X):.3f}")
The number of outliers: 5394
Percentage of outliers: 0.100

我们得到了相同的可疑 10%。这不可能是巧合(实际上,没有什么是巧合的 — 乌龟大师)。

异常检测中的污染是什么?

像隔离森林或局部异常因子这样的异常值分类算法实际上并不是分类器。当它们拟合到数据集时,其内部算法会计算数据集中每一行的异常分数。以下是 IForest 的一个示例:

iforest = IForest().fit(X)

iforest.decision_scores_[:10]
array([-0.03364944,  0.0287027 ,  0.07729889, -0.06363647, -0.03095093,
        0.05240712,  0.0230652 , -0.02713253,  0.06674287,  0.03475134])

使用decision_scores_属性,你可以访问任何算法的异常分数,一旦它们被拟合。

这些分数告诉我们数据集中每个样本的异常程度。默认情况下,PyOD 中的所有算法将具有最高 10%异常分数的样本标记为异常点。这是作为contamination参数提供的:

图片由作者提供

基本上,异常值分类器对我们说:“这是我认为每个样本的异常程度。现在,我将选择 10%最异常的样本作为异常点,但你可以随时通过我的污染参数进行更改。”

基本上,contamination是所有分类器的超参数,用于控制产生的异常点数量。

现在,这可能看起来有些矛盾——我们想要使用分类器检测异常点,但分类器却告诉我们如何选择这些异常点。这正是异常值检测的最大挑战。

你,作为机器学习工程师,掌握着所有决策——你需要使用你的经验、领域知识、直觉和试错来决定设置污染的百分比。

概率置信度

如果找到正确的污染值听起来很复杂,有一个解决方法可以让我们摆脱污染。这个解决方法就是异常值概率分数。

一旦 PyOD 估计器拟合到数据集,它将拥有predict_proba方法。它为每个样本返回两列;第一列是样本为内点的概率,第二列是样本为异常点的概率。我们简称这些为内点概率和异常点概率。

from pyod.models.iforest import IForest

iforest = IForest(n_estimators=1000).fit(X)

probs = iforest.predict_proba(X)
probs[:5]
array([[0.6309838 , 0.3690162 ],
       [0.48315014, 0.51684986],
       [0.3044389 , 0.6955611 ],
       [0.73513599, 0.26486401],
       [0.59298752, 0.40701248]])

这些概率是通过使用 MinMax 缩放(也称为规范化)对decision_scores_数组进行规范化生成的。

规范化数组将强制其在 0 到 1 的范围内,这意味着我们可以将异常分数解释为样本为异常点的概率。所以,predict_proba在后台的实现如下:

from sklearn.preprocessing import minmax_scale

# Create an empty array with two columns
probs = np.empty((len(X), 2))

# The second column is outlier probabilities
probs[:, 1] = minmax_scale(iforest.decision_scores_)

# The first column is inlier probabilities
probs[:, 0] = 1 - probs[:, 1]

# Check if the probs match
probs[:5] == iforest.predict_proba(X)[:5]
array([[ True,  True],
       [ True,  True],
       [ True,  True],
       [ True,  True],
       [ True,  True]])

现在,我们可以基于概率置信度来选择异常点,而不是基于污染。我们可以选择至少有 80%、90%或 n%异常点概率的样本,而不是选择 10%最异常的样本。下面是示例:

# Set a confidence threshold
threshold = 0.9

# Create a mask that returns True if probs over threshold
is_outlier = probs[:, 1] > threshold
outliers_X_probs = X[is_outlier]

# Count up the outliers
num_outliers = len(outliers_X_probs)
print(f"The number of outliers: {num_outliers}")
print(f"Percentage of outliers: {num_outliers / len(X):.4f}")
The number of outliers: 12
Percentage of outliers: 0.0002

使用概率置信度方法,我们只找到了 12 个异常点,而不是超过 5000 个。最棒的是,我们有 90%的把握这 12 个异常点确实是异常点!

如何选择合适的概率阈值

选择概率阈值取决于多个因素,如数据集大小、你试图解决的任务和业务领域。

如果你有一个小型数据集,你需要确保你所隔离的异常值确实是异常值。通过删除它们,你不希望丢失在小型数据集中重要的信息和模式,这些都不可避免地会影响机器学习模型。对于这种情况,你应该设置一个较高的阈值,比如 80%–90%。

如果你处理的是像医疗记录这样的敏感数据,在这种情况下,避免假阳性是至关重要的,你应该将阈值设定为超过 90%。95% 和 99% 也是比较常见的。

一切都取决于你在解决手头业务问题时对异常值的信心或必须有的信心。

如何选择合适的异常值分类器

在监督学习任务中,你的工作会简单一些。你可以使用像 RMSE 或准确率这样的度量标准来评估一系列算法,并选择最有前途的那个。

然而,由于异常检测是一个无监督学习问题,你实际上无法将一个算法与另一个算法进行比较,因为你无法用度量标准来衡量它们的表现。

然而,有一些算法如果使用得当,表现始终良好。

  • 孤立森林:适用于几乎任何类型的数据集,但特别设计用于高维和复杂的数据集。尽管它是不同类型的决策树的集合,称为 ITrees,但它速度快且性能高。它还可以处理具有不规则、非正态分布的数据。

  • 局部异常因子:特别适用于数据点被聚集在一起的数据集,因为它通过局部密度来计算异常分数。它对高维数据表现良好,并且比 IForest 更快。

如果你想了解更多关于如何选择合适的分类器,可以查看我在 DataCamp 上的异常检测课程,在课程中我讲解了近 10 种异常值分类器,如何以及何时使用它们,以及如何调整它们的超参数。

此外,这个页面 的 PyOD 文档列出了超过 40 种算法。你可以通过查看这个表格来了解它们的表现,表格中列出了 10 种最佳异常值分类器在 15 个数据集上的表现。

结论

本文讨论了机器学习中的多变量异常检测主题,并展示了如何使用 Python 中的 PyOD 实现。通过将异常分数转化为概率置信度,选择最佳的异常值分类器,并确定合适的概率阈值,你可以通过从数据中去除异常值来提升你的机器学习模型。有了这些知识,你现在可以在自己的项目中进行高效的异常检测。

喜欢这篇文章吗?让我们面对现实吧,它的写作风格确实很奇特。想象一下拥有更多类似的文章,全部由一位聪明、迷人、机智的作者(顺便说一下,就是我 :)。

仅需 4.99$的会员资格,你将不仅能访问我的故事,还能获得来自 Medium 上最优秀头脑的知识宝库。如果你使用我的推荐链接,你将获得我的超级感激以及一个虚拟的击掌,感谢你对我工作的支持。

[## 使用我的推荐链接加入 Medium — Bex T.

获得对我所有⚡高级⚡内容以及 Medium 上所有内容的无限制访问。通过给我买一杯咖啡来支持我的工作……

ibexorigin.medium.com](https://ibexorigin.medium.com/membership?source=post_page-----b0a9c557a21c--------------------------------)

如何在 Python 中进行异常值检测:第一部分

原文:towardsdatascience.com/how-to-perform-outlier-detection-in-python-in-easy-steps-for-machine-learning-1-8f9a3e6c88b5

地球是一个异常值——这一理论

Bex T.Towards Data Science Bex T.

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

--

图片由 0fjd125gk87 提供,来源于 Pixabay

什么是异常值?

我们生活在一个异常值上。地球是银河系中唯一拥有生命的岩石块。我们银河系中的其他行星是所谓的恒星和行星数据库中的正常数据点或内点。

异常值有许多定义。简单来说,我们将异常值定义为数据集中显著不同于大多数数据点的数据点。异常值是那些不符合或不对齐于数据集中内点的稀有、极端样本。

从统计学角度来看,异常值来自于与特征中其他样本不同的分布。它们表现出统计学上显著的异常。

这些定义取决于我们如何定义“正常”。例如,CEO 赚取数百万美元是完全正常的,但如果我们将他们的薪水信息添加到家庭收入的数据集中,他们就会变得异常。

异常值检测是统计学和机器学习领域的一个分支,使用各种技术和算法来检测这些极端样本。

你可以在这里查看系列的第二部分:

## 如何在 Python 中进行单变量异常值检测

单变量异常值检测,解释得非常清楚

[towardsdatascience.com

为什么要费心检测异常值?

那么,为什么呢?我们为什么需要找到它们?它们有什么害处?考虑一下这组 12 个范围从 50 到 100 的数字分布。其中一个数据点是 2534,这显然是一个异常值。

import numpy as np

array = [97, 87, 95, 62, 53, 66, 2534, 60, 68, 90, 52, 63, 65]
array
[97, 87, 95, 62, 53, 66, 2534, 60, 68, 90, 52, 63, 65]

均值和标准差是分布中最常用和最关键的属性之一,因此我们必须在拟合机器学习模型时提供这些指标的真实值。

让我们为我们的样本分布计算它们。

均值:

np.mean(array)
260.9230769230769

标准差:

np.std(array)
656.349984212042

现在,让我们做同样的操作,移除异常值:

# Array without the outlier
array_wo = [97, 87, 95, 62, 53, 66, 60, 68, 90, 52, 63, 65]

np.mean(array_wo)
71.5
np.std(array_wo)
15.510748961069977

如你所见,没有异常值的分布具有 3.6 倍更小的均值和接近 45 倍更小的标准差。

除了扭曲均值和标准差的实际值外,异常值还会在训练数据中产生噪声。它们在分布中创建了趋势和属性,使机器学习模型从数据中的实际模式中分心,导致性能损失。

因此,发现异常值、探究其存在原因并在适当时将其移除是至关重要的。

本教程中你将学到的内容

一旦你理解了该过程背后的重要理论,使用像 PyOD 或 Sklearn 这样的库在代码中执行异常值检测就变得简单了。例如,这里是如何使用流行的隔离森林算法进行异常值检测。

from pyod.models.iforest import IForest

iforest = IForest().fit(training_features)

# 0 for inliers, 1 for outliers
labels = iforest.labels_

outliers = training_features[labels == 1]
len(outliers)
136

只需要几行代码。

因此,本教程将更多关注理论。具体而言,我们将探讨无监督学习中的异常值检测,数据集中污染的概念,异常、离群和新颖的区别,以及单变量/多变量异常值。

让我们开始吧。

异常值检测是一个无监督问题

与许多其他机器学习任务不同,异常值检测是一个无监督学习问题。我们这是什么意思?

例如,在分类中,我们有一组特征映射到特定的输出。我们有标签告诉我们哪个样本是狗,哪个是猫。

在异常值检测中情况并非如此。当我们面对一个新数据集时,我们没有异常值的先验知识。这带来了几个挑战(但没有我们无法处理的)。

首先,我们没有简单的方法来衡量异常值检测方法的有效性。在分类中,我们使用诸如准确率或精确度等指标来衡量算法如何适应我们的训练数据集。在异常值检测中,我们不能使用这些指标,因为我们没有标签可以用来将预测与实际情况进行比较。

由于我们无法使用传统指标来衡量性能,我们不能高效地进行超参数调整。这使得很难找到适合当前任务的最佳异常值分类器(一个返回每个数据集行的内点/异常值标签的算法)。

但是,不必灰心。我们将在下一教程中看到两个优秀的解决方案。

异常与异常值与新奇性

你会发现“异常”与“新奇性”这两个术语在许多来源中经常与异常值一起出现。虽然它们的含义接近,但存在重要的区别。

异常是一个泛指的术语,包括任何不寻常和异常的东西。异常可以指训练集或测试集中的不规则性。

至于异常值,它们仅存在于训练数据中。异常值检测是指从训练集中找出异常数据点。异常值分类器仅对训练数据进行fit操作,并返回正常/异常标签。

另一方面,新奇性仅存在于测试集中。在新奇性检测中,你拥有一个干净的、不含异常值的数据集,并试图查看新的、未见过的观察是否具有不同于训练样本的属性。因此,测试集中的不规则实例变成了新奇性。

简而言之,异常检测是异常值检测和新奇性检测的母领域。异常值仅指训练数据中的异常样本,而新奇性存在于测试集中。

这一区分在我们开始使用异常值分类器的下一教程中至关重要。

单变量与多变量异常值

单变量和多变量异常值指的是不同类型数据中的异常值。

顾名思义,单变量异常值仅存在于单一分布中。例如,在一个身高测量数据集中,一个非常高的人就是一个异常值。

多变量异常值有些棘手。它们指的是具有两个或更多属性的异常值,这些属性单独看时并不显得异常,但当所有属性一起考虑时,才会显得异常。

一个多变量异常值的例子可以是一个里程非常低的旧车。这个车的属性单独看可能是正常的,但当所有属性组合在一起时,你会发现旧车通常具有与其年龄成正比的高里程。(虽然有许多旧车和许多里程低的车,但同时具备这两个特征的车却很少)。

在选择检测算法时,异常值类型之间的区别变得重要。

由于单变量异常值存在于仅有一列的数据显示集中,你可以使用简单且轻量的方法,如z-scores修改的 z-scores

多变量异常值带来了更大的挑战,因为它们可能只在多个数据集列中显现。因此,你必须使用大招如孤立森林、KNN、本地异常因子等。

在接下来的教程中,我们将看到如何使用上述一些方法。

结论

就这样!你现在了解了异常值检测的所有基本术语和理论,剩下的就是在实践中使用异常值分类器来应用这些知识。

在文章的下一部分,我们将深入探讨使用 PyOD 库的一些最受欢迎且强大的异常值分类器。敬请关注!

[## 使用我的推荐链接加入 Medium - Bex T.

获得对我所有⚡优质⚡内容的独家访问权限,无限制地访问 Medium 上的所有内容。通过请我喝一杯支持我的工作…

ibexorigin.medium.com](https://ibexorigin.medium.com/membership?source=post_page-----8f9a3e6c88b5--------------------------------)

更多文章来自…

[## 5 个优秀的 Julia 特性,Python 开发者只能羡慕的特性

Julia 与 Python 辩论的延续

medium.com](https://medium.com/geekculture/5-excellent-julia-features-that-python-developers-can-only-wish-they-had-e1531a596239?source=post_page-----8f9a3e6c88b5--------------------------------) ## 5 个最佳 Python 合成数据生成器及其在数据不足时的使用方法

获取更多数据

towardsdatascience.com [## Bentoml 与 FastAPI:最佳 ML 模型部署框架及其为何是 Bentoml

BentoML 与 FastAPI 在模型部署方面的详细比较。

pub.towardsai.net](https://pub.towardsai.net/bentoml-vs-fastapi-the-best-ml-model-deployment-framework-and-why-its-bentoml-f0ed26cae88d?source=post_page-----8f9a3e6c88b5--------------------------------) [## 如何创建高度组织化的 ML 项目,以便任何人都可以用 DVC 管道复现

什么是机器学习管道?

pub.towardsai.net](https://pub.towardsai.net/how-to-create-highly-organized-ml-projects-anyone-can-reproduce-with-dvc-pipelines-fc3ac7867d16?source=post_page-----8f9a3e6c88b5--------------------------------)

posted @ 2024-10-12 19:52  绝不原创的飞龙  阅读(181)  评论(0)    收藏  举报