如何改善低资源语言的翻译
原文:
towardsdatascience.com/how-to-improve-translation-for-low-resource-languages-8b19dbe7eb6b
非洲语言的评估案例
·发布在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 输出
ChatGPT,生成式 AI
专注于在你的 ChatGPT 提示中直接配置温度、Top P、频率惩罚和存在惩罚
·发表于 Towards Data Science ·阅读时间 9 分钟·2023 年 12 月 13 日
–
我最近读了一本非常有趣的书,作者是 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!
记住每次设置一个参数;否则,模型可能会感到困惑。
如果你读到这里,我对今天的内容已经很满意了。谢谢,下次见 😃
资源
-
我在这篇文章中详细阐述了 David Clinton 书中第二章描述的理论,生成 AI 完整过时指南。
-
我在这篇文章中详细阐述了我书中第三章描述的示例,数据讲述与生成 AI:使用 Python 和 Altair。
你也可能对以下内容感兴趣……
对 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 工具来……
如何在 Kubernetes 中通过 NVIDIA MPS 提高 GPU 利用率
在 Kubernetes 中集成 NVIDIA 多进程服务(MPS),以在工作负载之间共享 GPU,最大化利用率并降低基础设施成本
·
关注 发布于 Towards Data Science ·10 分钟阅读·2023 年 2 月 2 日
–
大多数工作负载并不需要每个 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 的三种方法如下:
-
时间切片
-
多实例 GPU(MIG)
-
多进程服务(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 Ampere 和 Hopper 架构上可用的一项技术,允许将一个 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 存在一些限制:
-
容器必须以与设备插件部署的 MPS 服务器相同的用户 ID 运行,默认情况下为 1000。你可以通过编辑设备插件安装图表中的
mps.userID
值来更改它。 -
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
完全控制你图像存储的位置
·发表于 Towards Data Science ·阅读时间 7 分钟·2023 年 2 月 1 日
–
介绍
托管一个 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
如果你想更改像 replicaCount
、htpasswd
或存储之类的内容,可以通过调整 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
部署有三个基本的不同之处。
-
image: YOUR_DOMAIN/my-nginx
-
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 注册表并使用了不同的方法,请在这里评论并解释你做了什么不同的操作。如果你有任何问题,请在评论中提出。我会尽可能回答。
随时通过 我的博客、LinkedIn、Twitter 和 GitHub 与我联系。
这篇文章最初发布在我的博客上 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 终止
·发表于 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 安装并使用了不同的方法,请在这里评论并解释你做了什么不同的事情。如果你有任何问题,请在评论中提问。我会尽可能回答。
欢迎通过 我的博客、LinkedIn、Twitter 和 GitHub 与我联系。
这篇文章最初发布在我的博客 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
一份全面且适合初学者的指南
·发布于 Towards Data Science ·13 分钟阅读·2023 年 1 月 24 日
–
目前有许多优秀的翻译服务,但其中最通用且易于设置的之一是 Microsoft Translator [1],它为你提供了多种低资源和高资源语言的翻译服务,且免费(有些每月翻译限制)。
在本教程中,我将讲解如何在 Azure 上设置翻译器实例,以及如何在你的代码中编写接口以连接它,并遵循最佳实践。如果你对 Azure 已经很熟悉并且已经设置了翻译器实例,那么可以直接访问项目 代码库 获取代码。
本教程将涵盖:
-
设置 Azure 翻译器实例
-
发送你的第一次翻译请求
-
清理你的代码和结构化你的项目
-
使用 Jupyter Notebooks 的注意事项
-
结论
在 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 的开发。
作者注释
如果你喜欢这篇文章或学到了新东西,请考虑通过我的推荐链接获取会员:
## 加入 Medium 使用我的推荐链接 — Yousef Nami
阅读 Youssef Nami(以及 Medium 上的其他成千上万的作家)的每个故事。你的会员费将直接支持……
这将使你可以无限制地访问 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
从简单到高级模型的完整指南
·发表于 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.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 中,你可以使用 margins 和 marginsplot 命令,在 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 中使用命令margins和marginsplot,在 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
计算逻辑回归系数的均值边际效应
·发表于 Towards Data Science ·阅读时间 10 分钟·2023 年 8 月 24 日
–
图片由 Dominika Roseclay 提供,来源于 Pexels.com
你喜欢逻辑回归,却讨厌解释任何形式的对数变换吗?虽然我不能说你和你在一起的人很多,但我可以说你有我作伴!
在这篇文章中,我将全面讨论解释逻辑回归系数——以下是大纲:
-
解释线性回归系数
-
为什么逻辑回归系数的解释具有挑战性
-
如何解释逻辑回归系数
-
使用statsmodels包计算均值边际效应
-
结论
解释线性回归系数
大多数对统计学有基础知识的人都能完全理解线性回归中系数的解释。如果你属于这种情况,可以考虑跳过文章中讨论逻辑回归系数的部分。
解释线性回归系数非常简单易懂。解释的简单性是线性回归仍然非常受欢迎的原因之一,尽管出现了许多更复杂的算法。
简单线性回归(一个输入变量的线性回归)呈现如下形式:
我们主要关注的是解释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 包计算均值边际效应
我们可以使用 statsmodels 的 Logit 类的 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