《抓住我,如果你能:Python 异常处理指南》
通过智能异常管理,释放 Python 的全部潜力
·
关注 发表在 Towards Data Science ·6 分钟阅读·2023 年 5 月 8 日
–
图片来源:Cookie the Pom 在 Unsplash
作为软件开发者,处理异常通常被视为一种必要的恶行。然而,掌握 Python 的异常处理系统可以让你成为更高效、更有效的程序员。
在这篇博客文章中,我将对以下内容进行深入解释:
-
什么是异常处理?
-
if
语句与异常处理的区别 -
使用
else
和finally
子句进行正确的错误管理 -
自定义异常的定义
-
异常处理的最佳实践
什么是异常处理?
异常处理是编写代码以捕获和处理可能在程序执行期间发生的错误或异常的过程。这使得开发者能够编写即使在面对意外事件或错误时也能继续运行的健壮代码,而不是完全崩溃。
当发生异常时,Python 会搜索匹配的异常处理程序。处理程序代码将执行并采取适当的行动,如记录错误、显示错误信息或尝试从错误中恢复。总体而言,异常处理有助于使 Python 应用程序更加可靠、可维护,并且更易于调试。
if
语句和异常处理之间的区别
if
语句和 Python 中异常处理的主要区别在于它们各自的目标和使用场景。
if
语句作为结构化编程的基本构建块。它评估一个条件,并根据条件是否为真执行不同的代码块。以下是一个示例:
temperature = int(input("Please enter temperature in Fahrenheit: "))
if temperature > 100:
print("Hot weather alert! Temperature exceeded 100°F.")
elif temperature >= 70:
print("Warm day ahead, enjoy sunny skies.")
else:
print("Bundle up for chilly temperatures.")
异常处理在编写健壮且有弹性的程序中扮演着重要角色,它通过处理在运行时可能出现的意外事件和错误来实现这一点。
异常用于信号问题,并指出代码中需要改进、调试或额外错误检查措施的区域。它们允许 Python 优雅地处理错误情况,并继续执行脚本,而不是突然终止。
下面是一个如何实现异常处理以更好地管理与除零相关的潜在失败的示例:
# Define a function that tries to divide a number by zero
def divide(x, y):
result = x / y
return result
# Call the divide function with x=5 and y=0
result = divide(5, 0)
print(f"Result of dividing {x} by {y}: {result}")
输出:
Traceback (most recent call last):
File "<stdin>", line 8, in <module>
ZeroDivisionError: division by zero attempted
由于引发了异常,程序在到达 print 语句之前立即停止执行。
我们可以通过将对“divide”函数的调用放入 try-except
块中来处理上述异常,如下所示:
# Define a function that tries to divide a number by zero
def divide(x, y):
result = x / y
return result
# Call the divide function with x=5 and y=0
try:
result = divide(5, 0)
print(f"Result of dividing {x} by {y}: {result}")
except ZeroDivisionError:
print("Cannot divide by zero.")
输出:
Cannot divide by zero.
通过这样做,我们优雅地处理了 ZeroDivisionError
异常,而不会因为未处理的异常使脚本的其余部分失败。
有关 Python 内置异常的更多信息,请参见 [2]。
使用 Else 和 Finally 子句进行正确的错误管理
在处理 Python 中的异常时,建议在 try-except
块中同时包含 else
和 finally
子句。else
子句允许你指定如果没有引发异常时应该发生的情况,而 finally
子句确保无论是否发生异常,某些清理操作总是会执行 [1][2]。
例如,考虑一个场景,你想从文件中读取数据并对数据进行一些操作。如果在读取文件时发生异常,你可能想记录错误并停止进一步处理,但仍然想正确关闭文件。
使用 else
和 finally
子句可以让你做到这一点——如果没有发生异常,则正常处理数据;或者在处理任何异常时仍能适当地关闭文件。如果没有这些子句,你的代码可能会遭遇资源泄漏或不完整的错误处理。因此,它们在创建健壮和可靠的程序中扮演着至关重要的角色。
try:
# Open the file in read mode
file = open("file.txt", "r")
print("Successful opened the file")
except FileNotFoundError:
# Handle missing files
print("File Not Found Error: No such file or directory")
exit()
except PermissionError:
# Handle permission issues
print("Permission Denied Error: Access is denied")
else:
# All good, do something with the file data
content = file.read().decode('utf-8')
processed_data = process_content(content)
# Cleanup after ourselves even if an exception occurred above
finally:
file.close()
在这个例子中,我们首先尝试使用 with
语句打开“file.txt”文件进行读取,这保证了文件对象在执行完成后自动正确关闭。如果在文件 I/O 操作期间发生 FileNotFoundError
或 PermissionError
,相应的 except 语句将被执行。为了简单起见,如果找不到文件,我们只是打印错误信息并退出程序。
否则,当 try
块中没有异常发生时,我们在 else
分支中继续处理文件内容。最后,finally
块保证了文件的关闭,无论之前是否抛出了异常 [1]。
通过采用这样的结构化方法,你的代码保持组织良好,易于跟随,同时考虑到可能由于与外部系统或输入交互而出现的潜在错误。
自定义异常定义
在 Python 中,你可以通过从内置异常如 Exception
或任何直接继承自 Exception
的类创建子类来定义自定义异常。
为此,你需要创建一个继承自这些基本异常之一的新类,并添加特定于你需求的属性。然后,你可以在代码中像使用其他内置异常类一样使用你新定义的异常类。
下面是定义一个名为 InvalidEmailAddress
的自定义异常的示例:
class InvalidEmailAddress(ValueError):
def __init__(self, message):
super().__init__(message)
self.msgfmt = message
这个自定义异常是从 ValueError
派生的,它的构造函数接受一个可选的消息参数(默认为"invalid email address"
)。
当你遇到无效的电子邮件地址格式时,可以抛出这个异常:
def send_email(address):
if isinstance(address, str) == False:
raise InvalidEmailAddress("Invalid email address")
# Send email
现在,如果你将一个无效的字符串传递给 send_email()
函数,你将看到一个自定义的错误消息,而不是普通的 TypeError
,它清楚地指示了问题所在。例如,抛出异常的代码可能如下所示:
>>> send_email(None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/path/to/project/main.py", line 8, in send_email
raise InvalidEmailAddress("Invalid email address")
InvalidEmailAddress: Invalid email address
异常处理的最佳实践
以下是一些与 Python 中错误处理相关的最佳实践:
-
设计以应对失败:提前规划,考虑可能的失败情况并设计你的程序以优雅地处理这些失败。这意味着要预见边界情况并实施适当的错误处理程序。
-
使用描述性的错误信息:提供详细的错误信息或日志,帮助用户理解出了什么问题以及为什么。避免使用诸如“发生错误”或“发生了不好的事情”的通用错误信息。相反,显示一个友好的消息,建议解决方案或提供文档链接。务必在提供详细说明和避免 UI 过于繁杂之间取得平衡。
-
最小化副作用:通过使用 try-finally 或 try-with-resources 块隔离问题代码段,最小化失败操作的后果。确保清理任务在成功或失败的结果下都能始终执行。
-
彻底测试:确保你的异常处理程序在各种情况下表现正确,通过运行全面的测试来实现。
-
定期重构:重构易出错的代码段,以提高其可靠性和性能。保持代码库的模块化和松散耦合,使独立部分可以独立演进而不会对其他部分产生负面影响。
-
记录重要事件:通过将有趣的事件记录到文件或控制台输出中,跟踪应用程序中的发生情况。这有助于你快速诊断问题,而无需在大量未结构化的日志中筛选。
结论
编写错误处理代码是软件开发中不可或缺的一部分,特别是在使用 Python 时,它使开发者能够构建更可靠和健壮的应用程序。通过遵循行业标准和最佳实践,开发者可以减少调试时间,确保代码质量,并提供更好的用户体验。
资源
[1] docs.python.org/3/tutorial/errors.html
[2] www.geeksforgeeks.org/python-exception-handling/
了解大型语言模型
原文:
towardsdatascience.com/catch-up-on-large-language-models-8daf784f46f8
实用的无炒作大型语言模型指南
·发表于 Towards Data Science ·15 分钟阅读·2023 年 9 月 5 日
–
图片由 Gary Bendig 提供,来源于 Unsplash
如果你在这里,这意味着像我一样,你被围绕 大型语言模型 (LLMs) 的不断信息流和炒作文章所压倒。
这篇文章是我试图帮助你了解大型语言模型的努力,没有炒作。毕竟,这是一个变革性的技术,我相信了解它很重要,希望这会激发你更深入地学习并用它创建一些东西。
在接下来的部分中,我们将定义什么是 LLM 及其工作原理,当然会涵盖 Transformer 架构。我们还将探讨不同的 LLM 训练方法,并通过一个动手项目结束文章,在这个项目中,我们将使用 Flan-T5 进行 Python 情感分析。
开始吧!
LLM 和生成式 AI:它们是一样的吗?
生成式 AI 是机器学习的一个子集,专注于那些主要功能是生成 某物 的模型:文本、图像、视频、代码等。
生成模型通过在大量由人类创建的数据上进行训练,以学习模式和结构,从而能够生成新数据。
生成模型的示例包括:
-
图像生成:DALL-E,Midjourney
-
代码生成:OpenAI Codex
-
文本生成:GPT-3,Flan-T5,LLaMA
大型语言模型是生成式 AI 领域的一部分,因为它们会接收输入文本并反复预测下一个单词,直到输出完成。
然而,随着语言模型的规模不断扩大,它们能够执行其他自然语言处理任务,如摘要、情感分析、命名实体识别、翻译等。
既然如此,现在让我们关注 LLM 的工作原理。
LLM 的工作原理
我们现在拥有大规模语言模型的原因之一是 Google 和多伦多大学的开创性工作,他们在 2017 年发布了论文 Attention Is All You Need。
本文介绍了 Transformer 架构,它是我们今天所知和使用的 LLM 背后的基础。
这种架构解锁了大规模模型,使得在多个 GPU 上训练非常大的模型成为可能,这些模型能够并行处理输入,给它们处理非常大数据序列的机会。
Transformer 架构概述
以下内容旨在对 Transformer 架构进行高层次的概述。虽然有许多资源对其进行了更深入的探讨,但这里的目标只是理解其工作原理,以便理解不同的 LLM 如何在不同任务中发挥作用。
如需更多详细信息,我建议阅读 原始论文。
所以,让我们从 Transformer 架构的简化可视化开始。
Transformer 架构的简化可视化。图片由作者提供。
从上图中,我们可以看到 Transformer 的主要组件是编码器和解码器。在每个组件内部,我们还可以找到 attention 组件。
让我们更详细地探讨每个组件,以理解 Transformer 架构的工作原理。
分词输入
我们知道 LLMs 使用文本,但计算机处理的是数字而非字母。因此,输入必须进行 分词。
分词是将句子的单词表示为数字的过程。
基本上,模型可以处理的每个可能单词都在一个字典中,并且与之关联一个编号。通过分词,我们可以检索与单词相关的编号,将句子表示为数字序列,如下所示。
分词示例。句子被分词后送入 Transformer 的嵌入层。图片由作者提供。
在上图中,我们看到一个例子,展示了如何在将句子“It rained this morning”发送到 Transformer 的嵌入层之前进行分词。
注意到分词句子的方式有很多种。在上面的例子中,分词器可以将一个词的部分表示出来,这就是为什么 rained 被分成 rain 和 ed。其他分词器可能只会为完整的单词分配一个编号。
词嵌入层
此时,我们有一系列表示单词的数字,但计算机如何理解这些数字的含义呢?
这是通过词嵌入层实现的。
词嵌入是一种对单词的学习表示,使得具有相似意义的单词具有相似的表示。模型将学习单词的不同属性,并在一个固定空间中表示它们,其中每个轴可以表示单词的属性。
词嵌入的可视化。我们可以看到“morning”和“sunrise”有相似的表示,因为它们在 3D 空间中的角度较小。同样,“rain”和“thunder”彼此更近。图片由作者提供。
在上图中,我们可以看到 3D 词嵌入的样子。我们看到“morning”和“sunrise”彼此更近,因此具有相似的表示。这可以通过余弦相似度计算得出。
另一方面,“rain”和“thunder”彼此较近,而与“morning”和“sunrise”相距较远。
现在,我们只能展示一个 3D 空间,但实际上,嵌入可以有数百个维度。事实上,原始的 Transformer 架构使用了 512 维的嵌入空间。这意味着模型可以学习 512 个不同的词属性,将它们表示在一个 512 维的空间中。
那么词序呢?
你可能已经注意到,通过表示词的嵌入,我们会丧失它们在句子中的顺序。
当然,在自然语言中,词序非常重要,因此我们使用位置编码,以便模型了解句子中单词的顺序。
是将词嵌入和位置编码结合在一起并发送给编码器。
在编码器内部
我们的输入在编码器内部传递,在那里它们会经过自注意力机制。
这就是模型可以学习句子中每个标记之间依赖关系的地方。它学习了每个词相对于句子中所有其他词的重要性。
单词“rained”的注意力图示例。笔划宽度代表重要性。在这里,我们可以看到“rained”与“this”和“morning”紧密连接。图片由作者提供。
在上图中,我们展示了单词“rained”的注意力图的风格化示例。笔划宽度表示重要性。
在这个例子中,我们可以看到自注意力捕捉了“rained”与“this”和“morning”的重要性,这意味着它理解了这个句子的上下文。
尽管这个例子很简单,因为我们只有一个非常短的句子,自注意力机制在较长的句子中效果很好,能够有效地捕捉上下文和句子的整体含义。
此外,模型并没有一个单一的注意力头。事实上,它有多个注意力头,也称为多头自注意力,每个头部可以学习语言的不同方面。
例如,在论文Attention Is All You Need中,作者发现一个头部涉及到指代消解,即识别实体与其重复引用之间的联系。
指代消解的例子。在这里,单词“keys”在句子中再次被引用为“they”。图片由作者提供。
上面,我们看到一个指代解析的示例,其中单词“keys”后来被提及为“they”,因此一个注意力头可以专门识别这些链接。
注意,我们并未决定每个注意力头将学习语言的哪个方面。
此时,模型已经对句子的意义结构有了深层次的表示。这被发送到解码器。
解码器内部
解码器接受输入令牌的深层表示。这为解码器内部的自注意机制提供信息。
作为提醒,这里再次展示了 Transformer 架构,以便我们记住它的样子。
Transformer 架构的简化可视化。图片由作者提供。
序列开始令牌被插入作为解码器的输入,以指示其开始生成新令牌。
新令牌是根据编码器生成的输入序列的理解及其自注意机制生成的。
在上图中,我们可以看到解码器的输出被送到一个 softmax 层。这生成了每个可能令牌的概率向量。具有最大概率的令牌随后由模型输出。
该输出令牌随后被送回嵌入层作为解码器的输入,直到模型生成序列结束令牌。此时,输出序列完成。
这总结了大型语言模型背后的基本架构。通过 Transformer 架构及其并行处理数据的能力,使得在大量数据上训练模型成为可能,使 LLMs 成为现实。
现在,情况更复杂,因为 LLMs 并非都使用完整的 Transformer 架构,这影响了它们的训练方式。让我们更详细地探讨这一点。
LLM 的训练方式
我们已经看到了支撑大型语言模型的基本机制,如前所述,并非所有模型都使用完整的 Transformer 架构。
实际上,一些模型可能只使用编码器部分,而其他模型只使用解码器部分。
这意味着模型的训练方式也不同,因此会专注于特定任务。
仅编码器模型
仅编码器模型,也称为自编码模型,最适合用于情感分析、命名实体识别和词汇分类等任务。
自编码模型的流行示例有 BERT 和 ROBERTA。
这些模型使用掩码语言建模(MLM)进行训练。通过这种训练方法,输入句子中的单词会被随机掩盖,模型的目标是重建原始文本。
说明了用于自编码模型的掩码语言建模(MLM)。在这里,输入句子中的一个随机单词被掩盖,模型必须重建原始句子。图片由作者提供。
在上图中,我们可以看到掩蔽语言建模的样子。一个词被隐藏,句子被输入到模型中,模型必须学习预测正确的词以得到正确的原始句子。
使用该方法,自编码模型发展了双向上下文,因为它们可以看到需要预测的标记前后的内容,而不仅仅是前面的内容。
再次如上图所示,模型看到“it rained”和“morning”,因此它看到句子的开头和结尾,这使得它能够预测“this”这个词,从而正确重构句子。
注意,对于自编码模型,输入和输出序列的长度是相同的。
仅解码器模型
仅解码器模型也称为自回归模型。这些模型最适合文本生成,但当模型变得非常大时,新的功能就会出现。
自回归模型的例子有 GPT 和 BLOOM。
这些模型使用因果语言建模(CLM)进行训练。使用因果语言建模时,模型只看到掩蔽之前的标记,这意味着它看不到序列的结尾。
说明因果语言建模。在这里,模型只看到导致掩蔽的标记。然后,它必须推断下一个标记直到句子完整。图像由作者提供。
如上所示,使用因果语言建模时,模型只看到导致掩蔽的标记,而看不到掩蔽之后的内容。然后,它必须预测下一个标记直到句子完整。
在上面的例子中,模型会输出“this”,然后该标记会被反馈作为输入,因此模型可以预测“morning”。
与掩蔽语言建模不同,模型建立了单向上下文,因为它们看不到掩蔽之后的内容。
当然,对于仅解码器模型,输出序列的长度可能与输入序列的长度不同。
编码器-解码器模型
编码器-解码器模型也称为序列到序列模型,并且它们使用完整的 Transformer 架构。
这些模型通常用于翻译、文本摘要和问答。
流行的序列到序列模型的例子有 T5 和 BART。
为了训练这些模型,使用了跨度破坏方法。在这里,一个随机的标记序列被掩蔽并指定为哨兵标记。然后,模型必须自回归地重构被掩蔽的序列。
跨度破坏的说明。在这里,一系列标记被掩蔽并用哨兵标记替代。然后,模型必须自回归地重构被掩蔽的序列。图像由作者提供。
在上图中,我们可以看到两个标记的序列被掩蔽并用哨兵标记替代。然后,模型被训练以重构哨兵标记以获得原始句子。
在这里,掩码输入被发送到编码器,而解码器负责重建掩码序列。
关于模型大小的说明
尽管我们已指定了某些模型表现最佳的任务,研究人员观察到大型模型能够执行各种任务。
因此,虽然编码-解码模型专门用于翻译,但非常大的仅解码模型在翻译方面也表现出色。
考虑到这些,让我们现在开始在 Python 中使用大型语言模型。
与大型语言模型合作
在我们实际操作大型语言模型之前,让我们先了解一些与 LLM 相关的技术术语。
首先,我们提供给 LLM 的文本称为提示(prompt),模型的输出称为完成(completion)。
提示是我们向模型提供的包含指令的文本。模型的输出称为完成。图片由作者提供。
在提示中,我们向 LLM 提供指令,以实现我们希望完成的任务。
这也是进行提示工程的地方。通过提示工程,我们可以进行上下文学习,即向模型提供如何执行某些任务的示例。稍后我们将看到一个例子。
目前,让我们使用 Python 与 LLM 进行情感分析的互动。
实践项目:使用 Flan-T5 进行情感分析
对于这个迷你项目,我们使用 Flan-T5 对各种金融新闻进行情感分析。
Flan-T5 是 T5 模型的改进版,T5 是一个序列到序列模型。研究人员基本上对 T5 模型进行了微调,使其覆盖更多语言。有关更多详细信息,请参见原始论文。
对于数据集,我们将使用由 Pekka Malo 和 Ankur Sinha 在 Creative Commons 属性许可下发布的financial_phrasebank 数据集。
数据集包含来自英语金融新闻的共 4840 个句子,这些句子被分类为积极、消极或中立。五到八名注释员对每个句子进行分类,根据一致性率,数据集的大小会有所不同(50%一致率为 4850 行,100%一致率为 2260 行)。
有关数据集及其编制方式的更多信息,请参见完整数据集详情页面。
当然,下面显示的所有代码都可以在GitHub上找到。
设置你的环境
为了使以下实验有效,确保有一个虚拟环境,并安装了以下软件包:
-
torch
-
torchdata
-
transformers
-
datasets
-
pandas
-
matplotlib
-
scikit-learn
请注意,库transformers和datasets来自 HuggingFace,使我们可以轻松访问和实验 LLM。
一旦环境设置好,我们可以开始导入所需的库。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datasets import load_dataset
from transformers import AutoModelForSeq2SeqLM, AutoTokenizer, GenerationConfig
加载数据
然后,我们可以加载我们的数据集。在这里,我们使用了具有 100% 一致性率的数据集。
dataset_name = "financial_phrasebank"
dataset = load_dataset(dataset_name, "sentences_allagree")
该数据集包含总共 2264 个句子。
请注意标签已被编码。1 代表中性,0 代表负面,2 代表正面。每种标签的计数如下所示。
数据集中每种情感的频率。图片由作者提供。
让我们将每个句子的实际标签存储在一个 DataFrame 中,以便后续更容易评估模型。
labels_df = pd.DataFrame()
labels_from_dataset = [dataset['train'][i]['label'] for i in range(2264)]
labels_df['labels'] = labels_from_dataset
加载模型
现在,让我们加载模型和分词器。如前所述,我们将加载 Flan-T5 模型。请注意,该模型有不同的大小版本,但我决定使用基础版。
model_name = "google/flan-t5-base"
model = AutoModelForSeq2SeqLM.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=True)
就这样!我们现在可以使用这个 LLM 对我们的数据集进行情感分析。
向模型提出情感分析的提示
为了让模型进行情感分析,我们需要进行提示工程以指定该任务。
在这种情况下,我们简单地使用“以下句子是正面、负面还是中性?”。然后我们将数据集中的句子传递给模型,让模型进行推断。
请注意,这被称为零-shot 推断,因为模型没有特别针对这个特定任务和数据集进行训练。
zero_shot_sentiment = []
for i in range(2264):
sentence = dataset['train'][i]['sentence']
prompt = f"""
Is the follwing sentence positive, negative or neutral?
{sentence}
"""
inputs = tokenizer(prompt, return_tensors='pt')
output = tokenizer.decode(
model.generate(
inputs["input_ids"],
max_new_tokens=50
)[0],
skip_special_tokens=True
)
zero_shot_sentiment.append(output)
在上面的 Python 代码块中,我们循环遍历数据集中的每个句子,并将其传递到我们的提示中。提示被分词并设置给模型。然后,我们解码输出以获得自然语言响应。最后,我们将模型的预测存储在列表中。
然后,让我们将这些预测添加到我们的 DataFrame 中。
labels_df['zero_shot_sentiment'] = zero_shot_sentiment
labels_df['zero_shot_sentiment'] = labels_df['zero_shot_sentiment'].map({'neutral':1, 'positive':2, 'negative':0})
评估模型
为了评估我们的模型,让我们展示预测的混淆矩阵以及分类报告。
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
from sklearn.metrics import classification_report
cm = confusion_matrix(labels_df['labels'], labels_df['zero_shot_sentiment'], labels=[0,1,2])
disp_cm = ConfusionMatrixDisplay(cm, display_labels=[0,1,2])
disp_cm.plot();
plt.grid(False)
plt.tight_layout()
clf_report = classification_report(labels_df['labels'], labels_df['zero_shot_sentiment'], labels=[0,1,2])
print(clf_report)
使用 Flan-T5 对金融新闻进行零-shot 情感分析的混淆矩阵。图片由作者提供。
零-shot 情感分析的分类报告。图片由作者提供。
从上面的图中,我们可以看到模型找到了所有负面句子,但代价是精确度,因为它错误标记了 611 个中性句子和 92 个正面句子。此外,我们还可以看到识别中性句子存在明显的问题,因为它错误标记了绝大多数句子。
因此,让我们尝试更改提示,以查看是否可以提高模型的性能。
带有上下文学习的一次性推断
在这里,我们修改了我们的提示,加入了一个中性句子的示例。这种技术称为上下文学习,因为我们在提示中传递了模型应如何表现的示例。
传递一个示例称为一次性推断。可以传递更多示例,这种情况称为少量样本推断。
向 LLM 展示最多五个示例是正常的。如果性能没有提高,那么很可能需要对模型进行微调。
现在,让我们看看一个示例如何影响性能。
one_shot_sentiment = []
for i in range(2264):
sentence = dataset['train'][i]['sentence']
prompt = f"""
Is the follwing sentence positive, negative or neutral?
Statement: "According to Gran , the company has no plans to move all production to Russia , although that is where the company is growing ."
neutral
Is the follwing sentence positive, negative or neutral?
Statement: {sentence}
{sentence}
"""
inputs = tokenizer(prompt, return_tensors='pt')
output = tokenizer.decode(
model.generate(
inputs["input_ids"],
max_new_tokens=50
)[0],
skip_special_tokens=True
)
one_shot_sentiment.append(output)
在上面的代码块中,我们看到我们给出了一个中性句子的示例,以帮助模型识别它们。然后,我们将每个句子传递给模型进行分类。
然后,我们按照相同的步骤添加包含新预测的新列,并显示混淆矩阵。
labels_df['one_shot_sentiment'] = one_shot_sentiment
labels_df['one_shot_sentiment'] = labels_df['one_shot_sentiment'].map({'neutral':1, 'positive':2, 'negative':0})
cm = confusion_matrix(labels_df['labels'], labels_df['one_shot_sentiment'], labels=[0,1,2])
disp_cm = ConfusionMatrixDisplay(cm, display_labels=[0,1,2])
disp_cm.plot();
plt.grid(False)
plt.tight_layout()
使用 Flan-T5 进行金融新闻的单次情感分析的混淆矩阵。图片由作者提供。
单次情感分析的分类报告。图片由作者提供。
从上图可以看出,略有改善。加权 F1 分数从 0.40 提高到了 0.44。模型在中性类别上的表现更好,但以牺牲对正面类别的表现为代价。
添加正面、负面和中性句子的示例可能会有帮助,但我没有进行测试。否则,就需要对模型进行微调,但那是另一篇文章的主题。
结论
本文涵盖了许多概念,从理解 LLM 的基础知识,到实际使用 Flan-T5 进行 Python 中的情感分析。
现在你拥有了探索这个领域的基础知识,可以自己看看如何微调 LLM,如何训练 LLM,以及如何围绕它们构建应用程序。
希望你学到了新东西,并且对学习更多充满好奇。
干杯 🍻
支持我
喜欢我的工作吗?通过请我喝咖啡来支持我,这是你鼓励我的简单方式,而我能享受一杯咖啡!如果你愿意,只需点击下面的按钮 👇
参考资料
注意力机制 — Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N. Gomez, Lukasz Kaiser, Illia Polosukhin
生成式 AI 与 LLM — deeplearning.ai
类别特征:标签编码的问题所在
原文:
towardsdatascience.com/categorical-features-whats-wrong-with-label-encoding-81184c3dfb69
为什么我们不能随意编码类别特征
·发表于Towards Data Science ·10 分钟阅读·2023 年 11 月 20 日
–
云朵。作者提供的图像。
众所周知,许多机器学习模型无法原生处理类别特征。虽然也有一些例外,但通常需要实践者决定每个类别特征的数值表示。有多种方法可以实现这一点,但一种很少推荐的策略是标签编码。
标签编码用一个任意的数字替换每个类别值。例如,如果我们有一个包含字母的特征,标签编码可能会将字母“A”分配为 0,将字母“B”分配为 1,然后继续这个模式直到“Z”,即 25。经过这个过程,从技术上讲,任何算法都应该能够处理这个编码后的特征。
那么这有什么问题呢?难道复杂的机器学习模型不能处理这种编码方式吗?为什么像Catboost和其他编码策略这样的库存在,用来处理高基数类别特征?
这篇文章将探讨两个示例,演示为什么标签编码对机器学习模型可能存在问题。这些示例将帮助我们理解为什么有如此多的替代方案存在,以及加深我们对数据复杂性和模型性能之间关系的理解。
直观示例
获得机器学习概念直观理解的最佳方法之一是了解其在低维空间中的工作原理,并尝试将结果外推到更高维度。这种思维外推并不总是与现实一致,但对于我们的目的来说,我们只需一个特征即可了解为什么需要更好的分类编码策略。
一个具有 25 个类别的特征
让我们从一个具有单个特征和连续目标的基本玩具数据集开始。以下是我们需要的依赖项:
import numpy as np
import polars as pl
import matplotlib.pyplot as plt
from sklearn.preprocessing import LabelEncoder
from sklearn.tree import DecisionTreeRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error
from category_encoders.target_encoder import TargetEncoder
from category_encoders.ordinal import OrdinalEncoder
让我们读取数据集并探索一些属性:
>>> data = pl.read_csv("basic_categorical_dataset_1.csv")
>>> data.shape
(852, 2)
>>> data.sample(5)
shape: (5, 2)
┌─────────────┬───────────┐
│ cat_feature ┆ target │
│ --- ┆ --- │
│ str ┆ f64 │
╞═════════════╪═══════════╡
│ v ┆ 16.287324 │
│ z ┆ 16.285893 │
│ j ┆ 5.718953 │
│ p ┆ 14.290943 │
│ y ┆ 2.972485 │
└─────────────┴───────────┘
>>> data.select("cat_feature").n_unique()
25
该数据集包含一个分类特征cat_feature
,具有 25 个唯一类别和一个连续的target
。目标是学习一个函数,将每个类别映射到其对应的最佳拟合目标值。我们不需要机器学习来做到这一点,但它将帮助我们理解为什么在处理更复杂的实际问题时需要良好的分类编码策略。
接下来,我们将创建训练集和测试集:
>>> x = data.select("cat_feature").to_numpy()
>>> y = data.select("target").to_numpy()
>>> x_train, x_test, y_train, y_test = train_test_split(
... x, y, test_size=0.20, random_state=3
... )
>>> x_train.shape
(681, 1)
我们已将 80% 的数据用于训练,20% 用于测试,现在准备为cat_feature
选择一种分类编码策略。与常见的机器学习智慧相悖,我们决定对cat_feature
进行标签编码。标签编码将每个分类值替换为介于 0 和类别数减一之间的任意数字。在这个例子中,这些类别将被替换为介于 0 和 24 之间的数字:
>>> label_encoder = LabelEncoder()
>>> x_train_label_encoded = label_encoder.fit_transform(x_train.ravel())
>>> x_test_label_encoded = label_encoder.transform(x_test.ravel())
使用 Scikit-learn 的LabelEncoder
类,我们使用训练集来决定编码并转换训练集和测试集。我们现在拥有一个单一的数值特征和一个目标,并且可以通过散点图可视化它们之间的关系:
>>> fig, ax = plt.subplots(figsize=(10, 6))
>>> ax.scatter(x_train_label_encoded, y_train)
>>> ax.set_xlabel("Label-Encoded Categorical Feature")
>>> ax.set_ylabel("Target")
>>> plt.show()
散点图如下所示:
标签编码训练数据的散点图。图片由作者提供。
正如预期的那样,每个类别都被分配了一个介于 0 和 24 之间的唯一整数,并且在每个类别内似乎有目标值的分布。我们还可以看到,编码后的cat_feature
与目标之间的关系高度非线性,这排除了线性回归作为可行模型。
尽管这些训练数据看起来很复杂,但机器学习模型足够强大,可以拟合这种关系。让我们看看当我们将决策树拟合到训练数据时会发生什么:
>>> model = DecisionTreeRegressor(max_depth=4)
>>> model.fit(x_train_label_encoded.reshape(-1, 1), y_train)
>>> preds = model.predict(x_test_label_encoded.reshape(-1, 1))
我们用max_depth
为 4 的 Sklearn 的DecisionTreeRegressor
对训练数据进行拟合,并对测试数据进行预测。由于我们只有一个特征,我们可以暂时忽略任何回归指标,简单地绘制叠加在测试集上的预测结果:
>>> fig, ax = plt.subplots(figsize=(10, 6))
>>> ax.scatter(x_test_label_encoded, y_test, label="Actual Test Set" )
>>> ax.scatter(x_test_label_encoded, preds, label="Predictions")
>>> ax.set_xlabel("Label-Encoded Categorical Feature")
>>> ax.set_ylabel("Target")
>>> ax.set_title("Actual vs Predicted Test Set: Max Depth = 4")
>>> ax.legend()
>>> plt.show()
结果如下所示:
使用标签编码的分类特征和深度为 4 的决策树的实际测试集与模型预测。图片由作者提供。
哎呀!我们不需要评估回归指标就知道这个模型不是很好。虽然模型在一些区域表现尚可,但这些结果并不是我们希望展示给老板的。
希望还没有完全丧失。由于我们的决策树深度为 4,我们怀疑增加深度,从而增加模型的复杂性,将会得到更好的结果。让我们将深度增加到 14 看看会得到什么:
实际测试集与使用标签编码分类特征和深度为 14 的决策树的模型预测。图片来源:作者。
这看起来好多了!深度为 14 的决策树似乎很好地拟合了关系。
我们从中可以得出什么结论?也许我们最初的想法是,这只是一个复杂的关系,需要一个更大、更复杂的模型来拟合。这可以解释为什么深度为 4 的决策树效果不好,但深度为 14 的树效果很好。也许标签编码是一个有效的选择?
这可能是真的,但为了成为优秀的数据科学家,我们需要评估另一种分类编码方案。虽然它可能容易导致过拟合,但一种流行的策略是目标均值编码。在最简单的形式中,目标均值编码将每个分类值替换为该类别所有观察值的均值目标。我们可以使用category_encoders来实现这一点:
>>> target_mean_encoder = TargetEncoder(smoothing=0, min_samples_leaf=1)
>>> x_train_target_encoded = target_mean_encoder.fit_transform(x_train, y_train).values
>>> x_test_target_encoded = target_mean_encoder.transform(x_test).values
smoothing
和 min_samples_leaf
参数用于对每个类别的目标均值计算进行正则化。在这个例子中,我们不希望进行任何正则化,因此我们不应用平滑,并且只要求类别中存在一个样本。如之前所述,我们可以可视化结果:
>>> fig, ax = plt.subplots(figsize=(10, 6))
>>> ax.scatter(x_train_target_encoded, y_train)
>>> ax.set_xlabel("Target-Encoded Categorical Feature")
>>> ax.set_ylabel("Target")
>>> plt.show()
下面是目标均值编码特征与目标的散点图:
目标均值编码训练数据的散点图。图片来源:作者。
这里发生了什么?目标均值编码揭示了分类特征与目标之间的线性关系。这与我们在应用标签编码时观察到的情况大相径庭。我们更愿意使用这种编码,因为它允许我们使用更简单的甚至是线性的模型。
线性回归对这个数据集效果很好,但我们将再次使用决策树来查看这种编码为什么更强大:
>>> model = DecisionTreeRegressor(max_depth=4)
>>> model.fit(x_train_target_encoded.reshape(-1, 1), y_train)
>>> preds = model.predict(x_test_target_encoded.reshape(-1, 1))
>>> fig, ax = plt.subplots(figsize=(10, 6))
>>> ax.scatter(x_test_target_encoded, y_test, label="Actual Test Set" )
>>> ax.scatter(x_test_target_encoded, preds, label="Predictions")
>>> ax.set_xlabel("Target-Encoded Categorical Feature")
>>> ax.set_ylabel("Target")
>>> ax.set_title("Actual vs Predicted Test Set: Max Depth = 4")
>>> ax.legend()
>>> plt.show()
这是将预测结果覆盖在测试集上的效果:
实际测试集与使用目标均值编码分类特征和深度为 4 的决策树的模型预测。图片来源:作者。
使用目标均值编码的分类特征,我们可以用深度为 4 的树很好地拟合关系。与此相比,标签编码特征需要更高的树深度。一种更好的分类编码方案揭示了一个模型更容易学习的关系。
我们可以通过查看每种编码策略所需的模型复杂度(即树深度)来进一步理解这个想法,以确定它们在测试集上的收敛情况:
每种分类编码策略的测试集 MAE 与树深度的关系。图片来源于作者。
我们在这里看到的正是为什么一个好的分类编码策略至关重要的核心。这个图表告诉我们,目标均值编码模型在较低树深度时的测试集误差显著低于标签编码模型。例如,在深度为 2 时,目标均值模型的测试误差不到标签编码模型的一半。
目标均值模型的收敛速度也比标签模型更快。目标均值模型在深度为 5 时达到了最小测试误差,而标签模型则需要到深度为 9 时才能达到最小值。
在这种情况下,正确编码分类特征揭示了一个关系,使我们能够使用更简单的模型。然而,即使标签编码需要更复杂的模型来拟合关系,我们仍然使用它找到了一个与目标编码模型一样好的模型。
也就是说,即使cat_feature
被标签编码,我们仍然找到了一个效果与其他模型一样好的模型。此外,我们知道还有许多更复杂的编码策略可以选择。选择一个好的编码策略真的值得吗?
一个具有数百个类别的特征
为了真正说服自己标签编码的不好,我们来看一个数据集:
>>> data = pl.read_csv("basic_categorical_dataset_2.csv")
>>> data.shape
(3777, 2)
>>> data.sample(5)
shape: (5, 2)
┌───────────────────────────────────┬────────────┐
│ cat_feature ┆ target │
│ --- ┆ --- │
│ str ┆ f64 │
╞═══════════════════════════════════╪════════════╡
│ 79a5808e-4e18-401a-94e7-7a478260… ┆ 369.232239 │
│ c29ae2f2-c4c0-4a48-b15f-ba72a81e… ┆ 492.785283 │
│ df809121-1f19-4f89-bb42-e7c593d9… ┆ 602.706521 │
│ 5840ee5f-69e9-4f5b-ac20-17e01033… ┆ 731.986467 │
│ b5c74247-6aff-4762-844e-3ebb3cbf… ┆ 404.087366 │
└───────────────────────────────────┴────────────┘
>>> data.select("cat_feature").n_unique()
917
这个数据集包含一个名为cat_feature
的单一分类特征,具有 917 个唯一类别和一个连续的target
。具有如此多类别的特征对我们如何编码cat_feature
有什么影响?标签编码仍然能收敛吗?
和之前一样,我们将创建使用标签和目标均值编码的训练集和测试集:
>>> x = data.select("cat_feature").to_numpy()
>>> y = data.select("target").to_numpy()
>>> x_train, x_test, y_train, y_test = train_test_split(
... x, y, test_size=0.20, random_state=3
... )
>>> x_train.shape
(3021, 1)
>>> label_encoder = OrdinalEncoder(handle_unknown="value")
>>> x_train_label_encoded = label_encoder.fit_transform(x_train, y_train).values
>>> x_test_label_encoded = label_encoder.transform(x_test).values
>>> target_mean_encoder = TargetEncoder(
... smoothing=0, min_samples_leaf=1, handle_unknown="value"
... )
>>> x_train_target_encoded = target_mean_encoder.fit_transform(x_train, y_train).values
>>> x_test_target_encoded = target_mean_encoder.transform(x_test).values
在这个例子中,我们使用OrdinalEncoder
而不是LabelEncoder
,因为它可以处理在测试数据中可能出现的先前未见过的类别。我们可以再次可视化标签编码的分类特征与目标之间的关系:
这是一个标签编码训练数据的散点图。图片来源于作者。
这看起来不太好。标签编码在cat_feature
和目标之间产生了大量噪声,很难想象一个机器学习模型在没有更多特征的情况下能够拟合这种关系。让我们将其与目标均值编码数据进行比较:
目标均值编码训练数据的散点图。图片来源于作者。
然而,当我们使用目标均值编码时,一个更简单,甚至是线性的关系再次出现。我们已经知道这允许我们使用更简单的模型,但我们说的“更简单”有多简单呢?
每种分类编码策略的测试集 MAE 与树深度的关系。图片来源于作者。
对于这个数据集,目标均值编码模型在深度为 6 时收敛,而标签编码模型直到深度为 29 才收敛。从不同的角度来看,最佳标签编码模型最终有 866 个叶子节点,而目标编码模型只有 128 个。也就是说,标签编码模型必须将单一特征划分 866 次才能收敛——这几乎是每个类别一个划分。
尽管这两个例子都使用了单一特征来预测目标,但我们可以想象一个更现实的场景,其中可能使用数百个特征。如果类别特征被标签编码,常用的基于决策树的算法可能会忽略这些特征,因为它们与目标的关系可能比其他特征复杂得多。
再次强调,我们不应得出目标均值编码是最佳策略的结论,因为它往往会过拟合训练数据。然而,我们确实看到了为什么需要一个好的编码策略,以及为什么标签编码会阻碍模型性能。
主要收获
这篇文章探讨了标签编码,这是一种将每个类别替换为任意数字的类别编码方法。我们发现标签编码可能会在类别特征与目标之间创建不必要的复杂性,需要更大的模型来进行拟合。找到合适的编码方法就是揭示有意义的关系,从而允许我们使用更简单的模型。
成为会员: https://harrisonfhoffman.medium.com/membership
使用 BERT 对自由文本银行交易描述进行分类
原文:
towardsdatascience.com/categorize-free-text-bank-transaction-descriptions-using-bert-44c9cc87735b
我为自己建立了一个开支跟踪工具
·发布于 Towards Data Science ·7 分钟阅读·2023 年 1 月 30 日
–
按类别统计开支。图表由作者提供
情况说明
我在 2022 年年底购买了一处房产,并办理了抵押贷款。由于财务承诺的增加,我想对自己的开支进行监控。直到这一点之前,我从未意识到自己实际上不知道自己最常花钱的地方。弄清楚这一点可能是我自己开支管理的一个良好起点。
自然,我转向了从在线银行门户下载的银行交易数据,格式为 .csv。下面是 2022 年最后几天的数据片段。
图片 1:作者的银行交易数据。图片由作者提供
根据上面的数据片段,似乎我在食品上的支出比例较高(如绿色高亮所示)。更重要的是,交易描述是基于自由文本的,有没有办法自动将这些描述分类到一些预定义的开支类别中(例如食品、杂货购物、水电费等)?
使用像 BERT 这样的预训练大型语言模型至少有一种方法,本文提供了如何进行操作的教程!
2023 年 BERT 介绍
虽然 ChatGPT 作为一种先进的文本生成模型目前受到广泛关注,但它通常不被认为是通用模型——例如 BERT 可以用于多种自然语言理解任务。一些示例包括语法检测、情感分类、文本相似性、问答推理等。
BERT 由谷歌于 2018 年开发和发布。它是一个使用维基百科和 BookCorpus 中的文本段落进行预训练的模型(以确保训练数据在语法上是准确的)。
本教程中使用的 BERT 模型可以通过 Hugging Face 的 sentence_transformer 库获得,该库是一个用于创建句子、文本和图像嵌入的 Python 框架。
构建费用分类器的步骤
我究竟如何将自由文本交易描述转换为费用类别?我能想到几种策略。在本教程中,我将提供一个基于(余弦)词嵌入相似度的费用分类器的逐步指南。步骤如下:
-
手动将大量交易描述标记为一个费用类别(例如,食品、娱乐)。这会创建一组标记的训练数据。
-
使用 BERT 将上述训练数据中的单个交易描述解析为词嵌入(即将文本转换为数值向量)。步骤 1和步骤 2共同确保训练数据被分配到特定的费用类别以及词嵌入向量。
-
对新的交易描述重复步骤 2(即将未见过的文本转换为数值向量)
-
将步骤 3中的词嵌入与训练数据中最相似的词嵌入配对,并分配相同的费用类别
Python 实现
本节提供了加载所需包以及实施上述步骤的 Python 代码(不包括步骤 1,这是一项手动标记步骤)。
步骤 0:导入所需的库
#for dataframe manipulation
import numpy as np
import pandas as pd
#regular expressoin toolkit
import re
#NLP toolkits
import nltk
nltk.download('punkt')
from nltk.tokenize import word_tokenize
#for plotting expense categories later
import matplotlib.pyplot as plt
plt.style.use('ggplot')
import seaborn as sns
import matplotlib
import matplotlib.ticker as ticker # for formatting major units on x-y axis
#for downloading BERT
!pip install sentence_transformers
from sentence_transformers import SentenceTransformer
#for finding most similar text vectors
from sklearn.metrics.pairwise import cosine_similarity
步骤 1:标记训练数据
我手动将 200 个交易描述标记为一个费用类别。例如,图像 1中的交易描述被分配了如下图所示的费用类别。我还将公共事业(即电费和煤气费)、汽车和礼品等类别分配给了训练数据中的其他交易。
图像 3:训练数据的手动标记。图片作者提供
步骤 2:使用 BERT 创建训练数据的词嵌入
我们首先定义一个用于清理文本数据的函数。这包括将单词小写、去除特殊字符(包括日期,这些在确定费用类别时并不有用)。
在使用 BERT 模型时,通常不需要进行词干提取、词形还原或去除停用词等 NLP 数据清理管道中的常见做法,因为 BERT 模型使用了字节对编码和注意力机制。
###############################################
### Define a function for NLP data cleaning ###
###############################################
def clean_text_BERT(text):
# Convert words to lower case.
text = text.lower()
# Remove special characters and numbers. This also removes the dates
# which are not important in classifying expenses
text = re.sub(r'[^\w\s]|https?://\S+|www\.\S+|https?:/\S+|[^\x00-\x7F]+|\d+', '', str(text).strip())
# Tokenise
text_list = word_tokenize(text)
result = ' '.join(text_list)
return result
然后我们将函数应用于交易描述,这些描述从图像 1中作为text_raw加载(df_transaction_description)。
text_raw = df_transaction_description['Description']
text_BERT = text_raw.apply(lambda x: clean_text_BERT(x))
以下片段显示了数据清理应用前后的特定交易示例。
图像 2:数据清理示例。图片作者提供
然后我们将清理后的文本输入到 BERT 中。我选择了’paraphrase-mpnet-base-v2’ BERT 模型,该模型以建模句子相似性而闻名。根据其在Hugging Face上的文档,它将句子和段落映射到一个 768 维的密集向量空间,并可用于诸如聚类或语义搜索等任务。
######################################
### Download pre-trained BERT model###
######################################
# This may take some time to download and run
# depending on the size of the input
bert_input = text_BERT.tolist()
model = SentenceTransformer('paraphrase-mpnet-base-v2')
embeddings = model.encode(bert_input, show_progress_bar = True)
embedding_BERT = np.array(embeddings)
下方提供了前几笔交易的词嵌入片段:
图像 4:BERT 嵌入。图像由作者制作
步骤 3:为未见数据创建词嵌入
我从未见过的数据中选择了 20 笔交易(为了本教程的目的,随机选择了交易)。这些交易显示在下图中。
图像 5:未见交易。图像由作者制作
上述交易描述作为text_test_raw加载。类似于步骤 2,这些数据通过 BERT 进行嵌入处理。
# Load texts
text_test_raw = df_transaction_description_test['Test']
# Apply data cleaning function as for training data
text_test_BERT = text_test_raw.apply(lambda x: clean_text_BERT(x))
# Apply BERT embedding
bert_input_test = text_test_BERT.tolist()
#model = SentenceTransformer('paraphrase-mpnet-base-v2')
embeddings_test = model.encode(bert_input_test, show_progress_bar = True)
embedding_BERT_test = np.array(embeddings_test)
df_embedding_bert_test = pd.DataFrame(embeddings_test)
步骤 4:将未见数据与最相似的训练数据配对
# Find the most similar word embedding with unseen data in the training data
similarity_new_data = cosine_similarity(embedding_BERT_test, embedding_BERT)
similarity_df = pd.DataFrame(similarity_new_data)
# Returns index for most similar embedding
# See first column of the output dataframe below
index_similarity = similarity_df.idxmax(axis = 1)
# Return dataframe for most similar embedding/transactions in training dataframe
data_inspect = df_transaction_description.iloc[index_similarity, :].reset_index(drop = True)
unseen_verbatim = text_test_raw
matched_verbatim = data_inspect['Description']
annotation = data_inspect['Class']
d_output = {
'unseen_transaction': unseen_verbatim,
'matched_transaction': matched_verbatim,
'matched_class': annotation
}
d_output 数据框显示,未见数据已被分配到一个相当合理的费用类别。
图像 6:未见数据与训练数据的匹配。图像由作者制作
现在,每当有新的费用产生时,只需将其输入模型即可!
附加步骤:按类别绘制费用图
我实际上将上述步骤应用于了 2022 日历年度的所有费用。下图展示了按分配类别计算的费用金额。
图表 7:按类别划分的费用图。图表由作者制作
主要观察结果如下:
-
在 2022 年,我在食品上的开支最多,其次是抵押贷款还款和公用事业账单。
-
尽管信用卡还款金额最高,但假设信用卡支出可以按相同比例分配到其他费用类别。这一假设也适用于 PayPal 类别。
-
根据数据,我可能希望减少食品开支,将更多支出转向杂货(即,开始在家做饭而不是外出就餐)以便于 2023 年。
-
我在美容产品上的支出可能是因为我和妻子一起购物的情况……
此外,返回特定类别中最高支出的交易非常简单。例如,我在 2022 年食品费用类别中的最高支出如屏幕截图所示。我对结果感到满意,因为这些餐厅中有些在训练数据中并不存在。尽管如此,BERT 仍然能够将这些交易分配到食品类别。
图像 7:主要费用。图像由作者制作
结论
本文提供了一个构建开支跟踪工具的全面教程。我做的就是将自由文本的交易描述翻译成机器理解的语言,使用 BERT,并让机器完成繁重的工作!
另一种方法是通过将相同的词向量嵌入传递到分类模型中,来替代本教程的第 4 步——这是给读者进一步实验的内容。
如果你喜欢我的这篇文章,可以随意阅读其他的文章。
随着我踏上 AI/ML 的浪潮,我喜欢用全面的语言撰写和分享一步一步的指南和如何做的教程,并附带可运行的代码。如果你想访问我所有的文章(以及 Medium 上其他从业者/作者的文章),你可以通过 这个链接 注册!
因果图:面对观察数据中的致命弱点
照片由 Андрей Сизов 提供于 Unsplash
“为何之书”第 3&4 章,阅读与我系列
·发表于 Towards Data Science ·阅读时长 13 分钟·2023 年 11 月 23 日
–
在我之前的两篇文章中,我 开启了 “阅读与我”系列,并完成了 前两章 的阅读,书名是由朱迪亚·珀尔(Judea Pearl)所著的 “为何之书”。这些文章讨论了引入因果关系以实现类人决策的必要性,并强调了设定未来讨论基础的因果阶梯。在这篇文章中,我们将探讨从因果关系的第一阶梯到第二阶梯的钥匙孔,带我们从概率思维进入因果思维。我们将从贝叶斯规则到贝叶斯网络,最终到因果图进行深入探讨。
从贝叶斯规则到逆概率
作为侦探小说的粉丝,我最喜欢的系列是《福尔摩斯探案集》。我仍然记得那些我在不知不觉中度过的日日夜夜。多年后,很多案件细节已经从我的记忆中消失,但我仍然记得那些著名的名言,如同其他人一样:
当你排除掉不可能的情况时,任何剩下的,无论多么不可能,必须是真相。
将这句话翻译到统计学领域,有两种概率——前向概率和逆向概率。根据福尔摩斯的演绎推理,侦探工作就是找到逆向概率最高的凶手。
图片来源于Markus Winkler在Unsplash
从正向概率到逆向概率的转变不仅仅是按顺序翻转变量,还强制建立了因果关系。正如前一篇文章简要讨论的那样,贝叶斯规则提供了一座桥梁,将客观数据(证据)与主观意见(先验信念)连接起来。基于贝叶斯规则,我们可以从任意两个变量中计算条件概率。对于任意变量 A 和 B,给定 B 已发生,A 发生的概率是:
P(A|B) = P(A&B)/P(B)
认为事件 A 发生的信念会根据事件 B 发生的概率来更新。事件 B 发生的可能性越小,P(B)越小,我对事件 A 发生的信念就越强。由于 P(B)小于或等于 1,P(A|B)总是大于或等于 P(A&B)。这就是说,一个人在发现 B 之后对 A 的信念永远不会低于在发现 B 之前对 A 和 B 的信念。请注意,这里的条件概率适用于所有变量关系,甚至是非因果关系。然而,逆概率仅适用于因果关系。
假设这两个事件是原因和证据。正向概率表示在已知原因的情况下,发生证据的概率。另一方面,逆向概率则从结果出发,显示在已知证据的情况下,发生原因的概率。如果我们能够识别原因和证据之间的因果关系,那么我们可以根据观察到的情况推断原因的概率,这在解决实际问题时更具适用性。
图片来源于Markus Winkler在Unsplash
在书中,Pearl 提供了一个应用实例,估计在乳腺 X 光检查结果为阳性的情况下,患乳腺癌的概率是多少,即P(疾病|测试)?首先,有一个明确的因果关系,其中乳腺癌是原因,乳腺 X 光检查结果是证据。当我们看到阳性测试结果时,并不意味着该患者一定患有癌症,因为没有测试是 100%准确的。然而,我们可以根据测试质量来推断该患者患乳腺癌的概率,这被定义为测试的敏感性P(测试|疾病)。测试的敏感性实际上是正向概率,适用于一般人群。
此外,个体特定的信息也可以改善我们对每个患者的逆概率估计。例如,如果这位患者来自一个有多个家庭成员被诊断为乳腺癌的家庭,那么阳性测试结果将比没有家族癌症史的患者更可信。这些患者特定的信息作为先验被添加到最终公式中,该公式指示如何根据证据(观察到的阳性测试结果)更新先验(患乳腺癌的概率):
Updated odds of D = Likelihood Ratio * Prior odds of D
在数学术语中,它是:
通过使用条件概率和似然比,我们可以在两个方向上更新信念。如果我们从原因处获得新的信息,我们可以通过条件概率来更新对证据的信念:
P(T| D) = P(T & D)/P(D)
P(T| D)的变化是由于 P(D)的变化。如果我们从证据中获得新的信息,条件概率是不正确的,因为测试结果为阳性并不意味着你有乳腺癌。因果关系被逆转。然而,我们可以使用似然比来更新我们的信念。
到目前为止,我们只讨论了两个因果相关的变量,但这个规则可以应用于整个因果网络,其中父节点表示原因,子节点表示证据。子节点通过应用条件概率来更新其信念,而父节点通过乘以似然比来更新其信念。在整个网络中应用这两个规则称为信念传播。通过这些规则,我们超越了贝叶斯规则,理解了原因如何影响证据的生成,以及观察证据如何帮助我们推断原因。
混淆因素,非实验研究中的阿喀琉斯之踵
信念传播帮助我们理解变量之间的相互作用,前提是我们能够正确识别因果关系。在现实世界中,超越两个变量,我们需要将因果关系扩展到因果图,以系统地推导因果影响。但在我们进入因果图(本书的核心内容)之前,让我们简要讨论一下是什么阻碍了我们从观察数据中推导因果关系,这些被称为混淆因素。
“Confounding”在英语中意为“混淆”。它是与 X 和 Y 都有相关性的变量。请注意,这种相关性可能是因果的,也可能是非因果的。此外,在下图中,我没有指定 X&Z 和 Y&Z 之间的箭头,因为在因果情况下,X、Y 和 Z 都可以是原因或结果,从而建立不同的因果图,这将在下一节中讨论。左侧面板显示了引入混淆变量 Z 如何在 X 和 Y 之间产生虚假的相关性。
作者提供的图像
在右侧面板中,如果 X 和 Y 之间存在因果关系,那么一个同时影响原因 X 和结果 Y 的混杂因素 Z 如果没有得到妥善处理,会引入混杂偏倚。如果我们不排除混杂因素引起的影响,我们将无法揭示 X 对 Y 的真实因果效应。
在实验研究中,将受试者随机分配到处理组和对照组中的随机性可以解决混杂因素的来源偏倚(最后一节将详细讨论)。然而,进行实验来研究因果效应并不总是实际和道德的,在这种情况下,我们将不得不尝试从观察性数据中推导真实的因果影响。与实验数据不同,观察性数据中存在混杂因素,因为总有影响原因和结果的因素存在。
例如,为了研究吸烟是否导致肺癌,其中一个混杂因素是年龄。不同年龄组的吸烟率差异很大,而且年龄越大,患肺癌的概率越高。我们必须控制年龄和其他混杂因素,才能获得真实的因果影响。统计学家和社会科学家用来对抗混杂因素偏倚的常用方法是“控制”模型中的尽可能多的混杂因素。这种方法存在几个问题:
-
并非所有的混杂因素都是可测量的: 从直观上看,我们可以推测出可能影响我们感兴趣的因果关系的混杂因素。然而,通常无法量化这些变量或找到合适的代理变量将其纳入模型。例如,在研究高等教育是否导致更高收入时,可能的混杂因素之一是“雄心”。有雄心的人更可能被激励去获得更高的教育和更高薪的工作,但在观察性研究中,我们如何量化这个主观变量呢?
-
遗漏变量: 无论我们试图在研究中包含多少变量,仍然很可能没有将所有必要的和正确的混杂因素或其代理变量纳入模型,从而使因果影响出现偏倚。
-
控制混杂因素引发偏倚: 另一方面,在实践中,统计学家为了确保没有混杂因素被遗漏,会在模型中包括尽可能多的变量以确保去偏估计。然而,这种过度控制实际上可能会引发偏倚。正如政治博客作者Ezra Klein所写:
“你在研究中总是会看到这样的情况。‘我们控制了……’ 然后列表开始。越长越好,收入、年龄、种族、宗教、身高、发色、性取向、Crossfit 参与情况、父母的爱、可乐还是百事。你可以控制的因素越多,你的研究就越强——或者,至少,看起来你的研究更强。控制变量带来的是特异性和精确性的感觉……但有时,你可能控制得太多。有时你最终控制了你试图测量的东西。”
最终,在解决混杂偏差时,我们面临很多问题,因为这是一个 Rung 2 问题,需要我们研究变量之间的因果关系。因此,一个不涉及因果结构的 Rung 1 解决方案,如绘制因果图,将是不够的。在下一部分,我们将看到如何利用因果图以系统和可靠的方式定义和控制混杂因素。
建立因果图,因果关系的钥匙
三种基本结构
要理解什么是因果图,我们可以从所有网络的基本构建块开始。网络中有三种基本的连接点,能够表征任何箭头模式:
作者提供的表格
贝叶斯网络和因果图中都存在三种基本类型。应用贝叶斯规则在变量之间构建贝叶斯网络,这不过是一个庞大概率表的简洁表示。如果我们在贝叶斯网络中看到链式结构 A -> B -> C,那么 A 和 C 之间缺失的箭头意味着一旦知道了 B 的值,A 和 C 就是独立的。如果在因果图中观察到相同的链式结构,除了在控制 B 时 A 和 C 之间的相同独立性,我们还会看到通过箭头的因果流。这一结构表明 C 是由 B 引起的,B 是由 A 引起的,而 A 是外部的。如果我们将结构改变为 C -> B -> A,或变为分叉结构 A <- B -> C,我们将看到在控制 B 的情况下 A 和 C 之间的独立性完全相同,但因果结构却发生了巨大的变化。换句话说,数据无法告诉我们一切。无论数据多么庞大,没有添加主观的因果假设,我们不能区分A -> B -> C、C -> B -> A和A <- B -> C。
此外,从贝叶斯网络转向因果图时,我们也在将 Rung 1 的概率思维转变为 Rung 2 和 Rung 3 的因果思维。我们可以不再使用“知道 B 的值之后”这种概率表达,而改为“保持 B 不变”,这相当于从“看到 B”转变为“干预 B”。在后续部分,我们将看到这种差异源于P(Y|X)和P(Y| do(X))。贝叶斯网络只能告诉我们在观察到另一事件的情况下某一事件发生的可能性。然而,因果图能够回答干预和反事实问题。
反门准则
因果图不仅使我们转变为因果思维,还为我们提供了一个可靠的工具来发现和验证观察数据中的因果效应。正如前一章所提到的,识别正确的混杂因素是主要挑战。为了解决这个问题,Pearl 引入了 do-operator 和 back-door criterion。
Dima Pechurin 在 Unsplash 上的照片
关键是弄清楚因果图,do-operator 消除了所有进入 X 的箭头,从而防止任何有关 X 的信息流向非因果方向。而 P(Y| X) 显示了带有混杂偏差的因果效应,概率 P(Y| do(X)) 显示了真正的因果影响。这意味着通过阻塞其他混杂因素的信息流,如果我改变 X,Y 会如何变化?根据不同的因果结构,我们需要控制或不控制不同的变量来阻塞信息流。
作者提供的表格
为了获得 P(Y| do(X)),我们需要确保信息流从 X 到 Y 仅直接来自 X 到 Y。为了实现这一目标,我们需要阻塞 X 和 Y 之间的所有非因果路径,而不干扰任何因果路径。这些非因果路径被称为 X 的反门路径,即任何从 X 到 Y 的路径,该路径以指向 X 的箭头开始。通过以下五个示例,可以更容易理解这一概念:
作者提供的表格,Game2 中有误,如果你控制 D,那么我们可以控制 A 或 D 来阻塞路径。
通过指定因果图,我们已经将控制尽可能多的混杂因素的过程转变为识别反门路径并找出如何有效地阻塞它们。如笔记中所述,并不总是需要控制尽可能多的变量以确保真正的因果效应。实际上,控制错误的变量可能会:
-
减少或阻塞 X 和 Y 之间的因果路径。例如,在游戏 1 中,如果我们控制 A,就会阻塞 X 和 Y 之间的因果路径;如果我们控制 B,A 的后代,则部分阻塞它。
-
引入 X 和 Y 的碰撞偏差。例如,在游戏 4 中,控制 B 会使 X 和 Y 在没有因果关系的情况下依赖。游戏 4 也被称为“ M 偏差”,因为它的形状。
-
控制正确的混杂因素,而不是尽可能多的因素。例如,在游戏 5 中,我们可以选择同时控制 B 和 A,或者仅控制 C 以达到相同的结果。
每一个图示都可以在现实世界的例子中找到。例如,在游戏 1 中表示一个医学应用,估计吸烟(X)对流产(Y)的影响。A 是由吸烟引起的潜在异常,因为我们不知道具体是什么异常被吸烟引起,所以它是不可观察的。B 是之前流产的历史。将流产历史包含到模型中是非常诱人的,但从因果图中可以看出,如果这样做,会部分失效吸烟对流产的机制,从而低估真正的因果影响。Pearl 的书中的这两章还有更多现实世界的应用。即使因果图可能变得过于复杂,使人脑无法找到后门路径,不要忘记我们总是可以依靠计算机算法来破解这些类型的问题。
为什么随机对照试验(RCT)有效?
我们已经使用因果图讨论了足够的非实验性研究。我们如何使用因果图和后门准则来解释为什么 RCT 能够得出无偏的因果影响?让我们看看一个例子,尝试找出不同肥料如何影响土壤产量。在现实世界中,农民根据许多因素决定使用哪种肥料,比如土壤肥力、土壤纹理,这些因素也会影响产量。我们可以在因果图中展示:
图片由作者提供
所有的橙色线条展示了偏倚肥料对产量因果影响的混杂关系。为了解决这个问题,我们需要在模型中控制所有这些混杂因素。请注意,这可能不太可能,因为这里的“其他”因素可能很难命名和量化。然而,如果现在我们设计一个实验,仅通过抽取随机卡片来决定每块土地使用哪种肥料。现在因果图变成了这样的:
图片由作者提供
通过在图示中添加随机卡片,我们可以去除之前图示中的所有混杂橙色线条,因为我们使用哪种肥料不再依赖于这些变量。它纯粹是一个仅受随机卡片抽取影响的随机决策。后门准则已经满足了用来估计肥料和产量的因果影响。
这就是我想分享的关于 Judea Pearl 的《为什么书》第三章和第四章的内容,这也完成了本系列“与我一起阅读”的第三篇文章。希望这篇文章对你有帮助。如果你还没阅读前两篇文章,可以在这里查看:
从一个猫的故事开始……
towardsdatascience.com ## 数据告诉我们“什么”,而我们总是寻求“为什么”
“为什么的书” 第一章与第二章,阅读系列
towardsdatascience.com
如果你感兴趣,订阅我的邮件列表 参加每两周一次的讨论,这些讨论将变得越来越技术性:
-
附赠:因果推断在学术界和工业界有何不同?
书中展示了更多细节和例子。正如往常一样,我强烈建议你阅读、思考,并在这里或你的 个人博客 上分享你的主要收获。
感谢阅读。如果你喜欢这篇文章,别忘了:
-
查看我最近的文章,关于 数据讲故事中的 4Ds:将科学变成艺术; 数据科学中的持续学习***;*** 我如何成为数据科学家***;***
-
订阅 我的邮件列表;
-
或者在 YouTube 上关注我,观看我最近的 YouTube 视频,关于我读的其他书籍:
参考
为什么的书 作者为 Judea Pearl
通过回归估计因果效应
原文:
towardsdatascience.com/causal-effects-via-regression-28cb58a2fffc
3 种流行技术及其 Python 示例代码
·发表于Towards Data Science ·阅读时间 8 分钟·2023 年 1 月 10 日
–
这是关于因果效应系列文章的第 5 篇。在之前的文章中,我们讨论了从数据中计算处理效应的不同方法。在这里,我介绍了通过 3 种流行的基于回归的技术来估计因果效应的替代方法。我以如何在实践中使用这些技术的 Python 示例代码来结束本文。
关键点:
-
回归是利用数据学习变量之间关系的一种方法
-
3 种常见的基于回归的因果效应估计方法是:线性回归、双重机器学习和元学习器
通过线性回归的因果效应玩具示例。图片由作者提供。
什么是回归?
回归是利用数据学习变量之间关系的方法。例如,巴布亚新几内亚成人胡安树袋熊的身高与体重之间的关系。
回归过程的输出称为模型。这本质上是我们可以用来进行预测的东西,例如,你告诉我一只树袋熊的身高,我可以告诉你它的体重大致值。
回归的关键好处是我们可以利用数据将模型与现实相匹配。
要使用回归来估计因果效应,我们需要开发数据驱动的模型,这些模型捕捉了处理、协变量和结果之间的关系。然后,审查这些模型以量化因果效应。
我们之前用平均处理效应(ATE)定义了因果效应,即处理组和控制组之间的结果均值差异。由于我们可以直接从数据中估计因果效应,因此不需要模型!
然而,在回归框架中,因果效应的推导方式不同。要了解这一点,我们从最简单的基于回归的因果效应估计方法——线性回归开始。
什么是处理效应以及如何计算?
towardsdatascience.com
线性回归
对于这种方法,我们训练一个线性模型来预测结果变量(Y)相对于处理变量(X)。然后我们将 因果效应 定义为 回归模型中处理变量的系数。下面给出了一个简单的示例。
使用线性回归估计 X 对 Y 的因果效应的简单示例。图像来自作者。
其中 Y 是结果变量,X 是处理变量,b 是截距(可以解释为误差项),Θ 是 X 对 Y 的因果效应。注意在这个回归框架中定义的因果效应是 根本不同的,与我们在 过去的文章 中定义的方式。
更进一步,我们可以在线性模型中包含混杂因素。在这种情况下,混杂因素 是 影响处理变量和结果变量的变量。通过这样做,我们可以减少由于混杂因素导致的因果效应估计的偏差。有关如何使用线性回归估计因果效应的更多细节,我建议读者参考 Gelman 和 Hill 的章节 [1]。
双重机器学习
尽管线性回归的简单性使其易于使用,但它可能无法准确捕捉变量之间的关系(例如,当变量之间存在非线性关系时)。这时,更复杂的技术会更有帮助。
一种这样的技术被称为 双重机器学习 (DML)。介绍这种方法的论文很详尽(约 70 页),但整个过程可以分解为 3 个简单步骤 [2]。
-
训练 2 个回归模型。一个是预测 结果变量 相对于相关协变量。另一个是预测 处理变量 相对于协变量。
-
计算每个模型的残差。换句话说,如果 f(Z) 通过 Z 估计 Y,而 g(Z) 通过 Z 估计 X,则它们的残差分别为 U = Y-f(Z) 和 V = X-g(Z)。
-
计算处理效应。利用残差,我们可以使用下面的方程直接计算处理效应。
双重机器学习方法的因果效应表达式 [3]。图像来自作者。
这被称为双重机器学习,因为我们训练了 2 个机器学习模型,f(Z)和 g(Z)。此外,对所使用的机器学习方法没有限制,它们可以是简单的线性回归,也可以是复杂的亿参数神经网络。
做 DML 时一个重要的细节是需要将可用数据分成 2 个子集:主要样本和辅助样本。然后使用这些子集执行一个称为交叉拟合的过程。
这包括使用主要样本和辅助样本来分别训练模型 g(Z)和 f(Z),然后进行交换,即使用主要样本来训练 f(Z)和辅助样本来训练 g(Z)。
然后,我们可以对每个样本-模型配对的因果效应估计进行平均。虽然这看起来像是额外的一步,但它在保持我们因果效应估计的数学简单性方面是重要的。更多细节请查看 DML 论文第 5 页[2]。
元学习者
元学习者旨在通过训练回归模型捕捉处理、协变量和结果之间的关系。与线性回归和 DML 不同,元学习者的因果效应不是定义为回归系数。相反,回归模型用于模拟每个单元的未观察结果,从而得到个体处理效应(ITE)。然后可以使用 ITE 来计算 ATE。
此外,对于所谓的异质性因果效应,可以使用条件平均处理效应(CATE)。这仅仅是特定子人群的 ATE(例如,雄性袋鼠、婴儿女孩的因果效应等)。异质性处理效应是在人群广泛变化的处理效应。
T-learner
第一种元学习者是T-learner(或双重学习者)。在这里,我们训练2 个结果模型(因此得名),一个用于控制组,另一个用于处理组[4]。
此技术可以分解为 2 步过程。
-
训练 2 个模型来分别估计控制组和处理组中的结果变量。
-
使用每个模型为每个单元生成(控制和处理)结果预测,并获得 ITE,这些 ITE 可以用来计算 ATE。
T-learner 过程概述。图片由作者提供。
S-learner
接下来,我们有S-learner(单一学习者)。这与 T-learner 类似,但不是训练 2 个结果模型,而是只创建一个,但将处理变量作为预测因子[4]。
使用此技术获取因果效应时,我们再次遵循 2 步过程。
-
训练模型以估计协变量和处理值方面的结果变量。
-
使用每个单元的模型预测来估计 ITE,并将其汇总以获取 ATE。
S-learner 过程概述。图片由作者提供。
X-learner
最终类型的 meta-learner 是X-learner。这种方法与 T-learner 有重叠,但更进一步。
X-learner 的 4 步过程[4]。
-
训练 2 个模型以分别估算对照组和处理组的结果变量,考虑协变量。(就像我们对 T-learner 所做的那样)。
-
使用模型估算未观察到的结果值。例如,如果单位 i=0 的 X₀=1 且 Y₀=1,我们然后使用对照组模型估算 X₀=0 的未观察到的结果。然后,使用估算的结果值来分别计算对照组和处理组的 ITE。
-
再训练 2 个模型以分别估计处理组和对照组的 ITE。
-
通过结合 ITE 模型使用权重函数* w()来估计 CATE。 (提示:使用倾向得分作为 w*)。
X-learner 过程概述。图片由作者提供。
关于 Meta-learners 的更多内容,我建议读者参考 Kunzel 等人的论文[4]和 Causal ML 文档[5]。
如何从观察数据中估计效果
towardsdatascience.com
示例代码:估计研究生学校对收入的处理效果(再次查看)
在这个例子中,我们使用 3 种基于回归的技术来估计拥有研究生学位对年收入超过 50k 美元的因果影响。我们使用开源的DoWhy库和来自UCI 机器学习库的开放数据[6]。
示例代码可以在GitHub Repo中找到。
# import modules
import pickle
import econml
import dowhy
from sklearn.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor
# load data
df = pickle.load( open( "df_causal_effects.p", "rb" ) ).astype(int)
一旦我们拥有了库和数据,我们必须定义我们的因果模型。这本质上定义了我们的处理、结果和协变量。
# define causal model
model = dowhy.CausalModel(
data = df,
treatment= "hasGraduateDegree",
outcome= "greaterThan50k",
common_causes="age",
)
# define estimand
estimand = model.identify_effect(proceed_when_unidentifiable=True)
在这里,我们将“hasGraduateDegree”定义为处理,“greaterThan50k”定义为结果,而“age”作为唯一的混杂变量。
首先,我们尝试线性回归。DoWhy 库让一切变得非常简单,因此我们只需运行 2 行代码。
# Linear Regression
LR_estimate = model.estimate_effect(estimand,
method_name="backdoor.linear_regression")
print(LR_estimate)
# ATE = 0.2976
接下来,我们尝试Double ML,虽然这个简单的例子对于它来说有点过于复杂,尤其是处理和结果变量只有 0 或 1 的值时。
# Double Machine Learning
DML_estimate = model.estimate_effect(estimand,
method_name="backdoor.econml.dml.DML",
method_params={"init_params":{
'model_y':LinearRegression(),
'model_t':LinearRegression(),
'model_final':LinearRegression()
},
"fit_params":{}
})
print(DML_estimate)
# ATE = 0.2977
请注意,在这个例子中,我们在 DML 过程使用的模型都是线性回归,但对于更复杂的问题,可以(且很多时候应该)使用更复杂的技术。
最后,我们尝试使用决策树构建X-learner作为我们的子模型。
# X-learner
Xlearner_estimate = model.estimate_effect(estimand,
method_name="backdoor.econml.metalearners.XLearner",
method_params={"init_params":{
'models': DecisionTreeRegressor()
},
"fit_params":{}
})
print(Xlearner_estimate)
# ATE = 0.2032
[## YouTube-Blog/causal_effects_regression 在主分支 · ShawhinT/YouTube-Blog
你现在无法执行此操作。你在另一个标签或窗口中已登录。你在另一个标签或窗口中已注销…
更多关于因果关系: 因果效应概述 | 因果关系:简介 | 因果推断 | 因果发现
资源
社交媒体: YouTube 🎥 | LinkedIn | Twitter
支持: 请我喝咖啡 ☕️
shawhin.medium.com/subscribe?source=post_page-----28cb58a2fffc--------------------------------
[## 获取我撰写的每一个新故事的免费访问权限
获取我撰写的每一个新故事的免费访问权限 P.S. 我不会与任何人分享你的电子邮件 通过注册,你将创建一个…
[1] 对处理变量进行回归的因果推断 由 Andrew Gelman 和 Jennifer Hill 编写
[2] 用于处理和因果参数的双重/去偏机器学习 由 Victor Chernozhukov 等人编写
[3] DoubleML Python 库文档
[4] 使用机器学习估计异质处理效应的元学习者 由 Kunzel 等人编写
[5] CausalML Python 库文档 (元学习者)
[6] Dua, D. 和 Graff, C. (2019). UCI 机器学习库 [http://archive.ics.uci.edu/ml]。加州欧文:加州大学信息与计算机科学学院。(CC BY 4.0)
因果推断:准实验
原文:
towardsdatascience.com/causal-inference-quasi-experiments-36d35ca5f754
你的 PM 忘记运行 A/B 测试了… 现在怎么办?
·发表于 Towards Data Science ·阅读时间 12 分钟·2023 年 8 月 9 日
–
图片来源:Isaac Smith 在 Unsplash
本文是关于使用准实验进行因果推断的系列文章中的第一部分(具体取决于我会啰嗦多少)。简而言之,第一部分将解释准实验的理由和方法,以及应用像 PSM 这样的办法时涉及的细微差别。在第二部分,我将更多地谈谈准实验的局限性以及基于这些实验做决策时需要注意的事项。我还会提出一个异质影响估计的框架,以帮助克服外推偏差。在第三部分… 我还不确定。
你可能也见过其他文章解释准实验,但我仍然会尝试以我的方式解释。请读一读。
为什么因果推断?
开发和推出产品及功能的成本最终是通过对消费者的积极影响来证明的。因此,听到产品经理做出各种声明,如“我们很高兴宣布我们最新的功能发布导致了 12% 的收入增长!”并不令人意外。
听起来很棒,老实说,大多数高级管理人员很乐意接受这样的说法作为事实。今天我的目标是说服你深入了解这些主张背后的因果推断方法。掌握因果推断,你将能更好地评估产品和功能对用户和公司带来的影响。
让我们看看 ChatGPT 对于为什么因果推断对于产品是必要的有何看法:
因果推断赋予产品团队的能力是超越仅仅观察数据中的相关性,建立对驱动产品表现的因果机制的更深刻理解。(毫不意外,比我能说的任何东西都更加简明扼要)
这里值得特别提到的是相关性和因果性的问题。
相关性并不意味着因果性。(别翻白眼)
说实话,我们中的许多人说它并认为我们知道它的含义。当有人问我们是什么意思时,我们拿出一个搞笑的图表来证明我们的智力能力(看看这个流行的虚假相关示例),并自豪地宣称我们永远不会在日常影响评估工作中犯这样的错误。好吧,经验告诉我,许多了解这种谬误的人并不真正理解它在现实世界中的表现。这通常源于因果推断领域的基础薄弱。
来源: www.tylervigen.com/spurious-correlations
(CC BY 4.0)
那么,如果因果推断对客观评估我们在产品和功能上的投资回报很重要,我们该如何进行呢?
在其最无争议的形式中,因果推断通常通过 A/B 测试来操作(遗憾的是,这不是今天讨论的主题)。然而,现实情况是实验并不总是可用的。
准实验:为什么?
首先,有时我们只是忘记进行实验。这通常发生在产品团队成功地使用一部分实验用户证明了影响,然后继续向所有用户发布。在这个过程中,他们忘记保留一个控制组来评估普遍影响。
其次,有时实验根本不可能。例如,产品或功能可能涉及对用户高度可见的变化,或对用户高度敏感的变化。在这种情况下,用户体验占据主导地位,控制组的设置显然不现实。
这就是准实验派上用场的地方。与实际的 A/B 测试不同,这些准实验是回顾性进行的。一般来说,它涉及分析用户的一个子集,以在产品或功能发布后模拟进行实验。让我们通过一个例子深入了解具体情况。
想象一下你是一家电商公司的数据科学家,比如 Shopee 或 Lazada。6 个月前,你的公司普遍推出了一项你们 CEO 相信能增加平台用户支出的互动功能 X。你的一位项目经理某天告诉你,CEO 想知道功能上线对公司的影响。你提醒项目经理,由于这是一次普遍推出,没有控制组,所有用户都可以使用互动功能。因此,你的项目经理说:“这没问题。只需比较那些实际使用功能的用户(处理组:Treatment=1)与那些没有使用功能的用户(控制组:Treatment=0)。进行一些假设检验,瞧,支出差异就是功能 X 的影响。”
图片来源:作者
根据你项目经理的智慧,你计算了一下,发现处理组与“控制”组之间有+ $12 的差异。我们是如何得到+ $12 的?
2023 年 4 月平均处理组支出:
$ (42 + 26) / 2 = $34
2023 年 4 月平均控制组支出:
$ (36 + 20 + 19 + 13) / 4 = $22
支出差异 = $34 减去 $22 = $12
你的项目经理对估算的提升感到满意,并为帮助确保团队的年度奖金给了你一个鼓励的拍背。
当你那晚躺在床上时,某些事情仍然困扰着你的良心。确实,这种方法存在混淆变量的问题。
ChatGPT 将 混淆变量 描述为:
混淆变量,也称为混杂因素,是指在研究中可能影响因变量(感兴趣的结果)和自变量(被研究的因素)的外部因素。这些变量可能导致关于自变量和因变量之间真实关系的误导或不正确的结论。
在这种情况下,潜在的混淆变量实际上是结果变量本身:用户支出。但这怎么可能呢?
如果那些在 2023 年 2 月花费更多的用户实际上更有可能在 2023 年 3 月使用这个新功能 X,那么观察到的 2023 年 4 月+12%的支出提升实际上可能归因于支出偏好的固有差异,而不是功能 X 的使用本身。换句话说,如果功能 X 没有推出,处理组的用户可能在 2023 年 4 月依然会花费更多。
在这里,准实验可以帮助提供更可靠(和良心的)影响估计。
我可以深入讨论混杂变量和偏差的统计学,但我不打算这样做,以便能够实际转到准实验。另一个你可能会疑惑的是,为什么随机化 A/B 测试不会受到混杂变量问题的影响。有关解释,请参见附录 A。再次说明,这不是今天讨论的重点。此外,混杂变量的概念与遗漏变量偏差密切相关,虽然不完全相同,但如果你想了解更多 解释 有关 OVB 的内容。
不过,希望我还没有让你感到困惑。
作者提供的图片:混杂关系的表示
准实验:怎么做?
这是一个高层次的概述,展示了准实验如何在没有随机化 A/B 测试的情况下克服混杂变量的问题。让我们继续使用产品特性 X 的推出作为例子。
对于处理组中的每 2 个用户,如果我们能瞥见一个平行宇宙,在那个宇宙中这 2 个用户没有使用特性 X,那将会很棒。由于我们还没有生活在科幻世界中,接下来的最佳选择是通过统计方法来估计这个平行宇宙。
具体而言,这通过在控制组中筛选出与处理组用户在 Feature X 上线前最相似的 4 名用户来完成。换句话说,这些伪控制组将模拟处理组用户如果没有采用 Feature X 会如何表现。
在我们方便修改的示例中,我们会找出控制组中与我们的处理组在 2023 年 2 月(上线前)的花费最相似的用户。具体来说:
对于用户 A,最接近的相似用户是用户 C,因为他们都在 2023 年 2 月花费了$10。
对于用户 B,最接近的相似用户是用户 D,因为他们都在 2023 年 2 月花费了$8。
因此,我们排除了用户 E 和 F 的分析。
作者提供的图片:将处理组与伪控制组进行匹配
总结一下,为什么我们选择用户 C 来与用户 A 进行比较?鉴于 2023 年 2 月(上线前)相同的花费,我们推测如果没有 Feature X 的推出和使用,用户 A 实际上会是用户 C。
那我们该如何告诉我们的 PM 呢?以下是修正后的计算结果:
2023 年 4 月的处理组平均花费:
$ (42 + 26) / 2 = $ 34
2023 年 4 月控制组的平均花费:
$ (36 + 20) / 2 = $28
花费差异 = $ 34 减去 $28 = $6
如该示例所示,使用 Feature X 的预估影响从$12 的提升降至$6。 不幸的是,对于你的 PM 来说,今年的奖金可能会比预期少一点。
当然,我在这里仔细调整了数值以证明我的观点,但实际上我在真实产品世界中见过更大比例的估计偏差。现在应该很清楚,拥有稳健的方法论在评估产品和特征决策的影响时是极其重要的。
在这一点上,值得提醒读者注意这个例子中存在的混淆因果关系。具体来说,过去的支出是特征 X 使用与未来支出之间因果关系的一个混淆因子。重要的是,存在混淆关系,因为我们假设过去的支出是采用特征 X 的可能性的一个指标。在我们进一步讨论倾向评分匹配(PSM)时,这种可能性概念是非常重要的。
倾向评分匹配
在上述特征 X 的方便例子中,我们只有一个混淆变量(2023 年 2 月的过去支出)。通过混淆框架来看这个问题,可能还有许多其他属性会影响发布后使用特征 X 的可能性。其中一些属性可能是已知且可观察的,而其他一些可能仍然未知或不可观察。
在我以前的一个项目中,我们有几十个用户属性,希望用来将处理用户与伪控制用户匹配。你可以应用 KNN 模型来找到相似的用户,但当属性过多和用户搜索量过大时,很快会遇到性能问题。如果你有数值和分类值的混合,还会有定义距离的额外复杂性。
克服这个计算问题的一种方法是通过降维过程。至少对我来说,这实际上就是 PSM 所做的。
回忆一下我们之前将混淆关系建模为混淆因子影响采用特征 X(处理)的可能性。因此,我们可以采取以下步骤来从我们的处理组中选择与伪控制组相似的子集:
-
使用逻辑回归(或任何其他产生概率预测的二元分类模型)来建模处理(1 或 0)与全部潜在混淆因子(过去支出等)之间的关系。
-
使用拟合的分类模型来预测每个用户属性/混淆因子下的处理(倾向评分)概率。
-
根据估计的倾向评分将处理组中的用户与伪控制组中的用户匹配。因此,倾向评分匹配。
通过采取这些步骤,匹配过程变得更具计算效率。除了极其高效外,数学在正确的假设下也能很好地运作。(有关期望值性质的更多信息,请参阅这篇文章,如果你对证明感兴趣且仍未信服,可以在这里查看)。当然,这种方法也有权衡之处,我将在下一部分讨论其中的一些局限性。
对于 R 用户,有一个非常知名的库叫做MatchIt来实现这个功能,其中一个示例可以在这里找到。个人而言,除了我不喜欢 R 之外,我也不喜欢那些将过多计算隐藏在背后的库,所以我从未真正使用过这个包,而是自己编写了代码在 PySpark 中进行匹配,以更高效地处理大型数据集(数百万用户)。另有一个简单的 Python 示例教程,大家可以在这里查看。如果你想了解更多关于我如何实现的内容,随时联系我。
倾向得分匹配:局限性
我想避免花费太多时间展示 PSM 的实现,部分原因是已经有许多参考示例,但也因为我更感兴趣的是讨论进行这种影响估计时涉及的细微差别。应用 PSM 很简单,但理解其假设、注意事项和局限性只有通过经验和实验才能获得。
不理解混杂变量的 PM 很危险,但误用准实验方法的数据科学家可能更危险,特别是因为这种方法表面上看起来很直观。
在我的第二部分文章中,我将花更多时间讨论我在将 PSM 应用于现实世界示例中的经验教训。现在,以下是应用 PSM 时应始终牢记的一些重要局限性:
可忽略的处理分配假设
PSM 依赖于“可忽略的处理分配”假设,这意味着所有影响处理概率(例如使用特征 X 的概率)和结果(例如未来支出)的混杂变量都已被充分测量并纳入倾向得分模型。如果存在未测量或不可观察的混杂因素,匹配可能无法充分解决估计偏差问题。
样本重叠和共同支持
PSM 要求处理组和对照组之间的倾向评分分布有足够的重叠。在共同支持有限的情况下(即,两组中具有相似倾向评分的个体很少或没有),匹配变得具有挑战性,我们可能需要采用其他方法。尽管我不是 R 的粉丝,MatchIt的文档很好地解释了支持的考虑因素以及其他匹配方法。
倾向评分估计中的选择偏倚
倾向评分模型(例如,逻辑回归)的准确性取决于模型的正确规范。如果模型被错误指定或包含不相关的变量,可能会引入选择偏倚。估计倾向评分的方法选择可以极大地改变估计的影响。不同的估计方法可能会产生不同的匹配结果,从而得出不同的因果影响估计。
关于预测倾向评分的模型的最后一点:在我与数据科学家讨论如何进行第 1 步(拟合逻辑回归模型)的过程中,许多人陷入了试图找到最佳模型以减少预测误差的困境。我不怪他们想这样做,这几乎是数据科学家的本能,想要在运行 model.fit(X, y)时最小化 RMSE。然而,同样值得记住的是,PSM 建模的直接目标并不是获得最佳分类预测。相反,是找到一种计算上高效的方式来实现 属性平衡 和共同支持。因此,最适合的模型可能并不总是产生最佳的匹配结果。更多内容请参见第二部分文章。
结论
始终记住我们在这里测量的内容是很重要的。对于那些熟悉统计学的人来说,我们是在克服使用平均处理效应(ATE)的偏倚,PSM 仅返回处理组的平均处理效应(ATT)。有关更全面的讨论,请参阅此链接。因此,在将 ATT 估计结果推广到更广泛的人群时,请谨慎行事。(我会在后续讨论中保存这个话题)
最后,数据科学家通常在影响产品和特性的决策和评估方面拥有巨大的权力。因此,我认为数据科学家肩负着相应的责任,在进行影响估计时,必须使用最稳健和可靠的方法。第二部分见!
附录 A:随机化与混杂
随机化是通过以相等的概率将每个个体分配到一个处理组(例如,对照组或处理组)来实现的。我们可以将处理分配表示为一个二元变量:
-
如果个体接受治疗,则 T = 1,
-
如果个体在对照组,则 T = 0。
随机分配的关键特性是治疗分配与任何潜在结果(Y)或协变量(X),包括观察到的和未观察到的,都是独立的。从数学上讲,我们可以将其表示为:
P(Y|T, X) = P(Y|T)
这意味着给定处理和协变量的情况下结果的概率与仅给定处理的情况下结果的概率相同。
参考文献
发现相关性:寻找新的相关性。来自 Tyler 的说明:现在这项功能无法使用——抱歉!存在冲突…
www.tylervigen.com ## 统计入门:倾向评分匹配及其替代方法†
摘要。倾向评分(PS)方法相对于传统的回归方法在控制…方面提供了某些优势。
## 理解遗漏变量偏差 ## MatchIt:入门指南
针对最普遍的偏差类型的逐步指南
towardsdatascience.com ## MatchIt:入门指南
Noah Greifer Ho 等(2007 年建议的 MatchIt)用于改进参数统计模型…
特别感谢:Shin Ler
因果 Python——埃隆·马斯克的推文,我们的搜索习惯,以及贝叶斯合成控制
使用带有贝叶斯改进的合成控制量化推文的影响(使用 CausalPy)
·发表于Towards Data Science ·阅读时间 11 分钟·2023 年 1 月 8 日
–
图片由Tolga Aslantürk在Pexels提供
2022 年 10 月给 Twitter 旧金山总部(以及一个水槽)带来了许多新变化。特斯拉和 SpaceX 的首席执行官埃隆·马斯克于 10 月 27 日成为公司新任所有者和首席执行官。
一些观众热烈欢迎这一变化,而另一些则保持怀疑态度。
一天后,即 10 月 28 日,马斯克推特上发了“鸟儿被释放了”。
推文的威力有多大?
让我们看看吧!
图片由Laura Tancredi在Pexels提供。
目标
在这篇博文中,我们将使用CausalPy——来自PyMC Developers(www.pymc-labs.io
)的全新 Python 因果包,来估计马斯克的推文对我们搜索行为的影响,运用一种强大的因果技术叫做合成控制。我们将讨论该方法的基本原理,逐步实施,并分析我们方法的潜在问题,同时链接到额外的资源。
准备好了吗?
介绍
-
2022 年 11 月初,我安排了一次会议演讲,讲解如何量化时间序列数据中的干预效果。我认为在演讲中使用一个真实世界的例子会很有趣,于是我想到了马斯克的推文。关于 Twitter 收购案在互联网上引发了很多讨论,我想知道这样的推文在多大程度上能影响我们的行为,超越传统的社交媒体活动,例如它如何影响我们搜索“Twitter”的频率?
-
嵌入 1. 埃隆·马斯克的推文。
-
但首先要讲清楚一件事。
- 因果关系与实验
-
因果分析旨在识别和/或量化干预(也称为处理)对感兴趣结果的影响。我们在世界上改变一些东西,想要理解我们行动的结果如何改变其他东西。例如,一家制药公司可能对确定新药对特定患者群体的效果感兴趣。这可能因为多种原因而具有挑战性,但最基本的原因是无法在同一时间观察到一个患者同时服用药物和不服用药物(这被称为因果推断的基本问题)。
-
人们找到许多聪明的方法来克服这个挑战。如今被认为是黄金标准的方法称为随机实验(或随机对照试验;RCT)¹。在 RCT 中,参与者(或一般情况下有时称为 单位) 被随机分配到治疗组(接受治疗)或对照组(不接受治疗)²。
- 学习 3 种因果效应识别技术,并在 Python 中实现它们,而无需耗费几个月、几周或几天的时间…
-
towardsdatascience.com
-
我们期望在设计良好的 RCT 中,随机化将平衡治疗组和对照组在 混杂因素 和其他重要特征方面的差异,这种方法通常相当成功!
-
不幸的是,由于经济、伦理或组织等多种原因,实验并不总是可用的。
-
如果我们…
- 图片由 Engin Akyurt @ pexels.com
- 合成控制
如果我们只能观察处理下的结果,而控制组不可用呢?阿尔贝托·阿巴迪和哈维尔·加尔德亚萨巴尔在评估巴斯克地区冲突的经济成本时遇到了这种情况(Abadie & Gardeazabal, 2003)。他们的论文孕育了我们今天讨论的方法——合成控制。
这个方法背后的基本思想很简单——如果我们没有控制组,就创造一个!
如何?
一个解决方案是预测它。
如果我们选择一些某种程度上相似于我们的处理单位(但保持未处理的)并将它们用作预测变量呢?这就是合成控制(几乎完全)所做的!
这些未处理的单位有时被称为捐赠池。记住,我们处于时间序列数据的领域,基本的合成控制估计器是未处理单位的加权和。我们将使用一个额外的权重约束,强制权重在0和1之间,并且加起来等于一²。
每个权重调整每个未处理单位对结果的贡献。你可以把它看作是一个时间上的约束线性回归。
我们在处理前观察数据上拟合模型,并预测处理后的结果值。这一逻辑基于一个假设,即捐赠池变量没有受到处理的影响。当这个假设成立时,预测的处理后控制组应该保持所有处理前特征不变(假设捐赠池变量足够好地预测结果)。
如果你想看到合成控制的逐步实现以及整洁呈现的数学,查看Matteo Courthoud’s 博客文章和/或Matheus Facure’s 章节关于合成控制。如果你想要更多应用研究的背景,请查看 Scott Cunningham 的“因果推断——混合带”。对于贝叶斯实现(我们这里使用的),请查看CausalPy 源代码。
[## 是的!六本因果关系书籍将使你从零基础到高级(2023)
…如果你愿意,可以完全免费获得其中的三本书!🤗
aleksander-molak.medium.com](https://aleksander-molak.medium.com/yes-six-causality-books-that-will-get-you-from-zero-to-advanced-2023-f4d08718a2dd?source=post_page-----187114fc4aa8--------------------------------)
假设
回到我们的推文。我假设马斯克广泛讨论的推文(“小鸟自由了”)使人们对 Twitter 本身以及相关新闻产生了更多兴趣。因此,我们期望观察到相对于其他社交媒体平台,“Twitter”的搜索量有所增加。
实际上,这一假设很难验证,因为结果可能不仅受到马斯克推文的影响,还可能受到其他因素(例如媒体对 Twitter 收购的报道)的影响。请注意,这实际上是一个很好的例子,说明了混杂如何在合成控制分析中发生³(Twitter 收购导致马斯克的推文以及引发对该平台的兴趣增加)。你认为哪种规范(推文作为原因或收购作为原因)更合理?请在评论中告诉我!
由于这是一个有趣的帖子,我们将假设马斯克推文对搜索行为的影响没有混杂,并且我们可以安全地进行估计。如果你决定自己估计 Twitter 收购对“Twitter”搜索数量的影响,请随时通过LinkedIn与我分享你的结果,或者加入 Causal Python 社区(https://causalpython.io),直接将结果回复到我们的一封每周邮件中。
数据中的马斯克推文
我们使用Google Trends作为代表全球每日搜索量的时间序列数据来源。我们对“Twitter”的搜索变化感兴趣,因此我们收集了这个搜索的数据,同时也收集了“TikTok”、“Instagram”和“LinkedIn”的数据,以用作我们的捐赠池**。
我们将使用 2022 年 5 月 15 日至 11 月 11 日之间的数据。
让我们看看图表。
图 1. Twitter、LinkedIn、TikTok 和 Instagram 搜索的数据。图像由本人提供。
我们可以看到 Twitter 和 Instagram 是搜索量最多的平台。它们之间存在一定的相关性。我们还可以看到 LinkedIn 的搜索量具有非常强的季节性特征,周末的搜索量明显较少,这与该网站的职业性质相符。
马斯克在 10 月 28 日发布了他的“小鸟自由了”推文。让我们把这个信息添加到图表中。
图 2. 包括处理(黑色虚线)的Twitter、LinkedIn、TikTok 和 Instagram 搜索的数据。图像由本人提供。
我们看到 Twitter 搜索量的急剧增加与马斯克推文的发布日一致。
让我们看看在合成产生的对照组下效果有多强。
让我们建模吧!
我们从导入开始。
代码块 1。 导入库。
我们遵循CausalPy 文档的惯例,并将库导入为cp
。我们导入pandas
来读取数据,并导入matplotlib
来帮助我们绘图。
我们读取数据并将索引转换为日期时间(这帮助我们生成了上面的图表,并使得索引治疗更容易,但并非必要)。
代码块 2。 读取数据并将索引更改为日期时间类型。
让我们简单看一下数据。
图 3。 我们数据集的前五行。图片由我本人提供。
正如预期的那样,我们看到四个变量和一个日期时间索引。我们将使用“LinkedIn”、“TikTok”和“Instagram”搜索作为捐赠池信号。
让我们将治疗日期存储在一个变量中,并实例化模型。
代码块 3。 将治疗日期存储在变量中并实例化模型。
我们使用WeightedSumFitter
模型,这将允许我们为每个捐赠池变量找到权重,以生成最佳拟合的合成控制。你可能还记得我们之前说过,我们对这些权重使用了两个约束:
-
它们应总和为1。
-
它们应在0和1之间。
请注意,如果第一个条件为真,则第二个条件可以用更不严格的非负约束代替;我们使用了更严格的条件,因为它可能对一些读者更直观。
满足这些约束可以通过多种方式实现。如果你查看了我们上面提到的其中一个参考资料(Matteo 的博客或 Matheus 的书),你可能会注意到他们都使用了约束优化来实现这个目标。由于我们使用贝叶斯方法,我们需要在分布层面上对这些约束进行编码。与我们所需约束非常匹配的分布是Dirichlet 分布。Dirichlet 分布的样本总和为1且非负。如果这让你想起了贝塔分布,那是一个很好的直觉!Dirichlet 是贝塔分布的(多维)推广。
CausalPy 将在后台负责初始化和拟合分布。我们现在准备好定义和拟合模型了!
CausalPy 支持 R 风格的公式来定义模型。公式twitter ~ 0 + tiktok + linkedin + instgram
表示我们想要将 Twitter 搜索随时间的变化建模为“TikTok”、“LinkedIn”和“Instagram”搜索的函数。公式开头的零告诉模型我们不想拟合截距。
代码块 4。 定义和拟合模型。
我们使用SyntheticControl
实验对象,它将负责模型拟合和结果生成。我们向构造函数传递四个参数:数据集、治疗索引、定义模型的公式和模型对象(我们选择了WeightedSumFitter
)。
如果你自己运行代码,你会注意到初始化采样器并采样链条需要一些时间,但大约过一分钟后我们应该可以开始绘制结果。
[## Python 中的因果推断与发现:解锁现代因果机器学习的秘密…
《Python 中的因果推断与发现》:通过 DoWhy、EconML 解锁现代因果机器学习的秘密……
amzn.to](https://amzn.to/3NiCbT3?source=post_page-----187114fc4aa8--------------------------------)
结果
让我们来检查结果!results
对象有一个非常方便的方法叫做.plot()
,可以有效地以图形方式总结结果。
代码块 5。 绘制结果。
这给出了以下输出:
图 4。 我们模型的结果。图片由本人提供。
在图的顶部,我们看到处理前贝叶斯R²(Gelman 等,2018)的打印输出,量化了我们的捐赠者池变量对 Twitter 处理前搜索次数的预测效果。
最上面的面板展示了结果变量的实际观察(黑点)、处理前结果的预测(深蓝色线)、捐赠者池变量(灰色)、我们生成的合成控制(绿色)、干预时间(垂直红线)和干预效果(阴影蓝色区域)。
在中间面板中,我们看到预测的因果影响在处理前后的情况。
最后,底部面板展示了累积因果效应。
总结一下!
贝叶斯R²为0.385表明模型的处理前拟合效果不是很好(完美拟合的R²为1)⁴。考虑到我们的捐赠者池较小,这并不令人惊讶。许多从业者建议作为经验法则,捐赠者池中变量至少应在 5 到 25 个之间。我们只有 3 个。
另一方面,我们可以非常确定我们没有过拟合,这在捐赠者池较大的情况下可能会发生(参见 Abadie,2021)。
如果我们认为分析中没有隐藏的混杂因素,埃隆·马斯克的推文的处理后效果相对较大,表明他的推文足够强大,能够暂时改变我们的搜索行为!
注意,另一种假设(Twitter 获取而非推文作为处理)看起来很有前景——你有没有注意到在干预前“Twitter”的搜索次数有所增加?
如果你决定检验这个假设,与我和 社区分享你的结果吧!
关于 CausalPy
CausalPy 仍处于初期阶段,但正在稳步成长。我收到图书馆创建者的消息,表示一些令人兴奋的新功能正在开发中,包括对合成控制的用户定义先验的支持。此外,图书馆的功能不仅限于这一种方法。确保查看最新版本和更新,请访问这里:github.com/pymc-labs/CausalPy
代码和conda 环境文件可在这里获取:
[## blogs-code/Causal Python - Elon Musk 的推文是否改变了我们的搜索习惯?]
目前您无法执行该操作。您在另一个标签或窗口中已登录。您在另一个标签或窗口中已注销…
脚注
¹ 虽然存在多个处理和/或多个对照组的实验设计,但在这里我们保持简单。
² 请注意,这些约束并不是必要的,但当捐赠者池变量的值既高于 又 低于结果变量的值时,这会迫使模型不对超出我们观察到的值的范围进行外推。在我们的案例中,这很有意义——参见图 1,其中Instagram(大多数时候)高于Twitter,其他平台则低于。允许模型进行外推并不错误,但它存在模型幻觉变量如何在其观察范围之外表现的风险。如果这让您想起了积极性假设——那是一个很好的直觉!有关积极性和外推的更多信息,请访问:causalpython.io/#positivity
³ 请注意,我们也可以说这种情况违反了SUTVA假设中的无多重处理版本部分,但我认为混杂视角更清晰、更直观。
⁴ 需要记住的是,使用R²作为拟合优度指标会带来自身的挑战。
了解更多关于 Python 中因果关系的内容:
[## Causal Python: 3 个简单技巧来快速启动您的因果推断之旅]
学习 3 种因果效应识别技巧,并在 Python 中实现它们,不用花费几个月、几周或几天的时间…
towardsdatascience.com [## Causal Python: 提升你在 Python 中的因果发现技能 [超越基础!] (2023)
…并挖掘 Python 中最优秀且最被低估的因果发现包的潜力!
towardsdatascience.com [## 是的!六本因果关系书籍将带你从零到高级 (2023)
…而且如果你愿意的话,可以完全免费获得其中的三本!🤗
参考文献
Abadie, A. (2021). 使用合成控制法:可行性、数据要求和方法论方面。经济文献杂志。
Abadie, A., & Gardeazabal, J. (2003). 冲突的经济成本:以巴斯克地区为例。公共选择与政治经济学电子期刊。
Athey, S., & Imbens, G. (2017). 应用计量经济学的现状——因果关系和政策评估。经济学视角杂志 32(2)。
Gelman, A., Goodrich, B., Gabry, J., & Vehtari, A. (2018). 贝叶斯回归模型的 R 平方。美国统计学家。
因果 Python:2023 年 NeurIPS 大会上的五个新颖因果观点
原文:
towardsdatascience.com/causal-python-five-novel-causal-ideas-at-neurips-2023-13bb68c5ed56
令人兴奋的新想法,将因果关系与生成建模、保形预测和拓扑学结合起来。
·发表于 Towards Data Science ·阅读时间 7 分钟·2023 年 9 月 24 日
–
NeurIPS 被认为是全球最重要和最负盛名的人工智能和机器学习会议之一,因为其严格的论文审查过程和高质量的研究。
会议具有跨学科的关注点,涵盖了与开发智能系统和机器学习算法相关的广泛话题。
近年来,NeurIPS 大会上接受的与因果关系相关的论文数量呈指数增长。
在本文中,我们介绍了 2023 年会议上接受的五篇因果论文,这些论文引起了我的注意,带来了对该领域的重要新见解。
请注意,这是一个主观且肯定不完整的列表。原因之一是,在撰写时,NeurIPS 尚未发布会议接受论文的完整列表。
尽管如此,我相信下面介绍的论文中的想法有可能推动我们的领域向前发展。
开始吧!
保形元学习者
保形预测是一类不确定性量化技术,最初由 Vladimir Vovk 提出。
保形预测是无模型的(无需分布假设),并提供频率学覆盖保证。换句话说,它保证在可交换性假设下,真实结果将以高概率落入预测区间(或集合)¹。
图 1. 保形元学习者的描述。来源:bit.ly/44Z9U9L
在他们的新论文**《用于个体治疗效应预测推断的符合性元学习器》**中,Ahmed Alaa 及其同事提出了一种新的符合性元学习框架,该论文刚刚被 NeurIPS 2022 接受。
他们的方法使得直接推断目标参数(个体化治疗效应;ITE)成为可能,这是对先前方法的重要改进。
作者在一系列使用合成数据和半合成数据的实验中评估了该方法的性能,并以实现的覆盖率、均方根误差和区间长度来衡量性能。
结论?符合性DR-learner在大多数设置中表现优越。
该框架的一个局限性是它要求已知倾向评分,这在某些情况下可能是限制性的。
这项工作为将因果方法与符合性预测器提供的覆盖保证相结合开辟了一个令人兴奋的新方向。
🟡 阅读论文
🟡 查看代码
- 想了解关于元学习器和 DR-learner 的内容?请查看Causal Inference and Discovery in Python的第九章和第十章,或者查看该书的免费GitHub 仓库。
因果归一化流
归一化流是一类神经模型,通过将简单分布转换为复杂分布来表示复杂分布。
特别是,自回归归一化流将变量 X 的分布估计为其前面变量的函数。如果我们想要将一个变量表示为其前面变量的函数,我们首先需要以某种方式对这些变量进行排序。
请注意,这种设置类似于结构性因果模型(SCM),其中每个变量被表示为其父变量的函数。
图 2. 线性 SCM 的示例(a)以其通常的递归形式书写
公式;(b)没有递归,每一步都明确;(c)没有递归,作为一个单一的
函数;以及(d)将 u 写作 x 的函数。来源:bit.ly/3ZvwbuD
归一化流以前曾用于因果发现(例如,CAREFL(Khemakhem 等,2021)用于DECI(Geffner 等,2022);有关详细信息,请参见 Molak(2023),第十三章)。
在他们的新论文**《因果归一化流:从理论到实践》**中,Adrián Javaloy 及其同事将这些想法提升到了一个新水平。他们展示了在给定因果排序的情况下,因果模型可以从观察数据中识别,并使用归一化流进行恢复。
接下来,他们提出了一种在因果归一化流中实现do操作符的方法,这使我们能够回答干预性和反事实查询。
最后,他们在一个不完整图上的混合(连续/离散)数据上展示了他们方法的有效性。
真是一个令人兴奋的时代!
🟡 阅读论文
🟡 查看代码
## 因果 Python — 提升你在 Python 中的因果发现技能(2023)
…并解锁 Python 中最佳因果发现包的潜力!
[towardsdatascience.com
部分反事实识别的生成模型
反事实被置于 Pearl 的因果阶梯的第三层。
这使得它们最难处理,因为我们需要非常丰富的结构因果模型(SCM)描述,以解决反事实查询。
图 3. Pearl 因果阶梯的符号表示。反事实查询需要 SCM 的最丰富表示。来源:bit.ly/45UzGgx
所谓的符号可识别性并不总是可用,其他可以使回答反事实查询可行的假设(例如单调性)在某些场景下可能难以满足,或仅在离散情况下有效。
Valentyn Melnychuk 及其同事的新论文**《具有曲率敏感性模型的连续结果的部分反事实识别》**提出了一种超越这些限制的新方法。
作者从拓扑学的角度(有关早期工作的参考见例如 Ibeling & Icard, 2021)来看待这一挑战,并提出了一种曲率敏感性模型(CSM),允许进行部分反事实识别。
连续结果。
换句话说,该方法使我们能够在没有关于数据生成过程的完整信息时,为连续结果下的反事实查询找到有信息量的界限。
作者建议,解决方案所依赖的假设应在从物理学到医学的广泛用例中是现实的,并且该方法在安全关键环境中的决策中具有潜在的相关性。
作为附注:所提出的方法还依赖于归一化流。
🟡 阅读论文
🟡 查看代码
被动数据、主动因果策略和语言模型
如果你关注我在LinkedIn或Twitter/X,你可能会记得关于这篇论文的帖子。
实际上,这篇论文是激励我和我的同事组织AAAI 2024 关于大型语言模型和因果性的研讨会的论文之一。
但是,言归正传!
图 4. 因果 DAG 环境和实验结果。(a) 施加的约束
在创建训练数据集时,以及在测试时评估代理时,因果 DAG 结构
结构。在训练期间,D 不允许成为 E 的祖先(即使是间接的),尽管它们
可能由于混淆变量而相关。在评估环境中,D 是最有影响力的
E 的祖先(见文本)。(b) 在互动设置中评估时,代理获得的奖励,作为
最优奖励的百分比。在这两种评估设置中,代理仍然接近最优
奖励。© 更详细地分析代理的行为,通过绘制代理的比例
与最优行为相匹配的行动,或基于干预或
相关统计数据。代理的策略与最优策略的匹配显著更接近。
与启发式基准匹配。来源:bit.ly/3Rt4cd3
在他们的论文**《代理和语言模型中的被动学习主动因果策略》**中,DeepMind 的 Andrew Lampinen 和同事们展示了代理和(大规模)语言模型(LLMs)可以从被动(观察)数据中学习主动因果策略。
这些策略可以泛化到分布外的数据(!),但仅在某些条件下。
正如作者提出的,代理“获得了用于发现和利用因果结构的可泛化策略,只要他们可以在测试时进行干预”。
提示中的解释对 LLMs 的泛化能力至关重要。此工作并不意味着被动学习超越主动学习或完全解决 LLMs 中的混淆。然而,它标志着扩展语言模型因果能力的重要一步。
我迫不及待想看到更多的研究继续在这个激动人心的路径上前进!
🟡 阅读 论文
广义敏感性分析
当我们不能排除存在隐藏混淆的可能性时,敏感性分析对于因果分析师至关重要。
传统的敏感性分析方法通常存在局限性(例如,它们假设线性模型或单一的二元处理)。
在他们的新论文中*,* 《广义因果敏感性分析的锐界》,*Dennis Frauen 和同事们提出了一种新的统一
用于未观察混淆下因果敏感性分析的框架。
图 5. 在所提出模型下的干预分布界限。来源:bit.ly/3PLVBkl
该框架为一系列因果效应提供了锐界,包括(条件)平均处理效应(CATE)、中介效应、路径分析和分布效应。
此外,所提出的方法适用于离散、连续和时间变化的处理。
最棒的部分?论文附带了一个丰富的代码库。
真是一个活着的好时光!
🟡 阅读 论文
🟡 检查 代码
## 因果 Python || 你学习 Python 中因果关系的首选资源
每周免费邮件关于因果关系和机器学习
脚注
¹ 在这里,可交换性的含义与潜在结果框架中的不同。我们可以将其视为 IID 假设的一个较温和版本。有关更多细节,请参见 这里。
参考文献
Geffner, T., Antorán, J., Foster, A., Gong, W., Ma, C., Kıcıman, E., Sharma, A., Lamb, A., Kukla, M., Pawlowski, N., Allamanis, M., & Zhang, C. (2022). 深度端到端因果推断。arXiv
Ibeling, D., Icard, T. (2021). 关于因果推断的拓扑视角。第 35 届神经信息处理系统大会。
Khemakhem, I., Monti, R., Leech, R. & Hyvarinen, A. (2021). 因果自回归流。第 24 届国际人工智能与统计会议论文集,在机器学习研究论文集,130,3520–3528. proceedings.mlr.press/v130/khemakhem21a.html
。
Molak, A. (2023). Python 中的因果推断与发现:解锁现代因果机器学习的秘密,包括 DoWhy、EconML、PyTorch 等。Packt Publishing。
CFXplorer:反事实解释生成 Python 包
原文:
towardsdatascience.com/cfxplorer-counterfactual-explanation-generation-python-package-483ca4221ab8
介绍了一款用于生成基于树的算法反事实解释的 Python 包
·发表于Towards Data Science ·阅读时间 9 分钟·2023 年 8 月 17 日
–
随着机器学习模型在现实场景中的应用日益增多,对模型可解释性的重视也在不断增加。了解模型如何做出决策不仅对模型的用户有益,也对受到模型决策影响的人员有所帮助。反事实解释的出现就是为了解决这个问题,因为它允许个人了解通过改变原始数据如何能获得理想的结果。在短期内,反事实解释可能会为那些受到机器学习模型决策影响的人员提供可行的建议。例如,一个被拒绝贷款申请的人可以知道这次该做些什么来被接受,这将有助于他们在下次申请中改进。
Lucic 等人[1]提出了 FOCUS,它旨在为树基机器学习模型中的所有实例生成与原始数据的最优距离反事实解释。
CFXplorer 是一个使用 FOCUS 算法为给定模型和数据生成反事实解释的 Python 包。本文介绍并展示了如何使用 CFXplorer 生成反事实解释。
链接
GitHub 仓库:github.com/kyosek/CFXplorer
文档:cfxplorer.readthedocs.io/en/latest/?badge=latest
PyPI:pypi.org/project/CFXplorer/
目录
-
FOCUS 算法
-
CFXplorer 示例
-
限制
-
结论
-
参考文献
照片由 Wesley Sanchez 提供,来源于 Unsplash
1. FOCUS 算法
本节简要介绍了 FOCUS 算法。
生成反事实解释的问题已经被一些现有方法解决。Wachter、Mittelstadt 和 Russell [2] 将这个问题形式化为优化框架,但这种方法仅限于可微分模型。FOCUS 旨在通过引入概率模型近似,将框架扩展到非可微分模型,特别是基于树的算法。该方法的一个关键方面是对预训练基于树的模型(表示为 f)的近似,通过用具有参数 σ 的 sigmoid 函数替换每棵树中的每个分裂来实现,参数 σ 定义为:
其中 σ ∈ R>0。
这个 sigmoid 函数被纳入了函数 t ̃_j(x) 中,该函数近似了树模型 f 的节点 j 激活 t_j(x) 对于给定输入 x。该函数定义为:
其中 θ_j 是节点 j 的激活阈值。
该方法近似于单棵决策树 T。树的近似可以定义为:
此外,该方法将* f* 的最大操作,即由权重 ω_m ∈ R 的M棵树的集合替换为带有温度 τ ∈ R>0 的 softmax 函数。因此,近似的 f ̃ 可以表示为:
重要的是要注意,这种近似方法可以应用于任何基于树的模型。
FOCUS 算法的主要声明是该方法能够 (i) 为数据集中所有实例生成反事实解释,并且 (ii) 为基于树的算法找到更接近原始输入的反事实解释,比现有框架更优。
2. CFXplorer 示例
本节展示了如何使用 CFXplorer 包的两个示例。第一个是一个简单示例,您可以了解包的基本用法。第二个示例展示了如何通过使用 Optuna [3] 包来搜索 FOCUS 的最佳超参数。正如本文章在前面部分所述,FOCUS 有一些超参数。这些超参数可以通过与超参数调优包集成来优化。
2.1. 简单示例
在这个简单示例中,我们创建随机数据、决策树模型,并使用 CFXplorer 生成反事实解释。Python 包 CFXplorer 通过使用 FOCUS 算法生成反事实解释。本节演示了如何使用这个包来实现这一点。
安装
您可以使用 pip 安装该包:
pip install CFXplorer
首先,导入相关包。
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from cfxplorer import Focus
from sklearn.datasets import make_classification
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
from sklearn.tree import DecisionTreeClassifier
我们创建一个虚拟数据集以供决策树模型使用。
def generate_example_data(rows: int = 1000):
"""
Generate random data with a binary target variable and 10 features.
Args:
rows (int): The number of rows in the generated dataset.
Returns:
pandas.DataFrame: A DataFrame containing the randomly generated data.
"""
X, y = make_classification(
n_samples=rows, n_features=10, n_classes=2, random_state=42
)
return train_test_split(X, y, test_size=0.2, random_state=42)
CFXplorer 只接受标准化的特征值(在 0 和 1 之间),因此我们需要对其进行缩放。
def standardize_features(x_train, x_test):
"""
Standardizes the features of the input data using Min-Max scaling.
Args:
x_train (pandas.DataFrame or numpy.ndarray): The training data.
x_test (pandas.DataFrame or numpy.ndarray): The test data.
Returns:
tuple: A tuple containing two pandas DataFrames.
- The first DataFrame contains the standardized features of the training data.
- The second DataFrame contains the standardized features of the test data.
"""
# Create a MinMaxScaler object
scaler = MinMaxScaler(feature_range=(0, 1))
# Fit and transform the data to perform feature scaling
scaler = scaler.fit(x_train)
scaled_x_train = scaler.transform(x_train)
scaled_x_test = scaler.transform(x_test)
# Create a new DataFrame with standardized features
standardized_train = pd.DataFrame(scaled_x_train)
standardized_test = pd.DataFrame(scaled_x_test)
return standardized_train, standardized_test
现在训练决策树模型。
def train_decision_tree_model(X_train, y_train):
"""
Train a decision tree model using scikit-learn.
Args:
X_train (array-like or sparse matrix of shape (n_samples, n_features)): The training input samples.
y_train (array-like of shape (n_samples,)): The target values for training.
Returns:
sklearn.tree.DecisionTreeClassifier: The trained decision tree model.
"""
# Create and train the decision tree model
model = DecisionTreeClassifier(random_state=42)
model.fit(X_train, y_train)
return model
我们将上述所有内容结合起来运行。
X_train, X_test, y_train, y_test = generate_example_data(1000)
X_train, X_test = standardize_features(X_train, X_test)
model = train_decision_tree_model(X_train, y_train)
一旦我们获得了数据和模型,我们初始化Focus
。Focus 需要几个参数进行定制。但为了简单起见,在这个例子中,我们可以使用迭代次数和距离函数。
focus = Focus(
num_iter=1000,
distance_function="cosine",
)
FOCUS 的其他参数是;
distance_function: str, optional (default="euclidean")
Distance function - one of followings;
- "euclidean"
- "cosine"
- "l1"
- "mahalabobis"
optimizer: Keras optimizer, optional (default=tf.keras.optimizers.Adam())
Optimizer for gradient decent
sigma: float, optional (default=10.0)
Sigma hyperparameter value for hinge loss
temperature: float, optional (default=1.0)
Temperature hyperparameter value for hinge loss
distance_weight: float, optional (default=0.01)
Weight hyperparameter for distance loss
lr: float, optional (default=0.001)
Learning rate for gradient descent optimization
num_iter: int, optional (default=100)
Number of iterations for gradient descent optimization
direction: str, optional (default="both")
Direction of perturbation (e.g. both, positive and negative)
hyperparameter_tuning: bool, optional (default=False)
if True, generate method returns unchanged_ever and mean_distance
verbose: int, optional (default=1)
Verbosity mode.
- 0: silent
- else: print current number of iterations
最后,我们可以使用generate
方法生成反事实解释。
perturbed_feats = focus.generate(model, X_test, X_train)
我们可以在图中检查这些生成的反事实解释。
def plot_pca(plot_df, focus_plot_df):
"""
Plots the PCA-transformed features and corresponding predictions before and after applying FOCUS.
Args:
plot_df (pandas.DataFrame): A DataFrame containing the PCA-transformed features and
predictions before applying FOCUS.
focus_plot_df (pandas.DataFrame): A DataFrame containing the PCA-transformed features and
predictions after applying FOCUS.
Returns:
None: This function displays the plot but does not return any value.
"""
fig, axes = plt.subplots(1, 2, figsize=(20, 8))
sns.scatterplot(
data=focus_plot_df, x="pca1", y="pca2", hue="predictions", ax=axes[0]
)
axes[0].set_title("After applying FOCUS")
sns.scatterplot(data=plot_df, x="pca1", y="pca2", hue="predictions", ax=axes[1])
axes[1].set_title("Before applying FOCUS")
fig.suptitle("Prediction Before and After FOCUS comparison")
plt.show()
plot_df, focus_plot_df = prepare_plot_df(model, X_test, perturbed_feats)
plot_pca(plot_df, focus_plot_df)
它看起来像这样:
我们可以观察到,在应用 FOCUS 之前,许多预测== 1 位于右侧,但应用 FOCUS 后,它们变成了预测== 0。对于在 FOCUS 之前的预测== 0,它们位于左侧,并变成预测== 1。
2.2. 超参数优化
主要有四个 FOCUS 的超参数,具体来说,sigma(方程 1)、温度(方程 4)、距离权重,它是距离损失和预测损失之间的权衡参数,以及 Adam 的学习率[4]。
注意 1:在这个例子中,我们将使用决策树模型,因此我们不会使用 *temperature*
超参数。
注意 2:你可以将优化算法(这里使用的是 *Adam*
)视为一个超参数,但我们不会在本节中优化它,其他 *Adam*
的超参数也同样如此,除了学习率为了简单起见。
本节使用 Optuna 来优化 FOCUS 的超参数。Optuna 是一个强大的超参数优化工具,执行贝叶斯优化。除了 Optuna,我们还可以再次使用我们之前创建的相同函数;generate_example_data
、standardize_features
和train_decision_tree_model
。
以下是目标函数。它定义了要调整的超参数以及优化目标。在这个例子中,我们在Focus
类中调整 3 个超参数,即 sigma、距离权重和 Adam 优化器的学习率。这些超参数的搜索空间被定义为trial.suggest_float
或trial.suggest_int.
损失函数定义为cfe_distance /100 + pow(unchanged_ever, 2).
这样做的原因,如函数的文档字符串中所写,我们希望优先找到反事实解释,而不是最小化平均距离。因此,我们取未改变实例的平方数。
注意:重要的是要将*Focus*
类的*hyperparameter_tuning*
参数设置为*True*
。 否则,它不会返回未改变实例的数量和平均反事实解释距离。
import optuna
import tensorflow as tf
from cfxplorer import Focus
def objective(trial):
"""
This function is an objective function for
hyperparameter tuning using optuna.
It explores the hyperparameter sets and evaluates the result on a
given model and dataset
Mean distance and number of unchanged instances are
used for the evaluation.
Args:
trial (optuna.Trial):
Object that contains information about the current trial,
including hyperparameters.
Returns:
Mean CFE distance + number of unchanged instances squared -
This is the objective function for hyperparameter optimization
* Note: typically we want to minimise a number of unchanged first,
so penalising the score by having squared number.
Also, to not distort this objective,
having the mean distance divided by 100.
"""
X_train, X_test, y_train, y_test = generate_example_data(1000)
X_train, X_test = standardize_features(X_train, X_test)
model = train_decision_tree_model(X_train, y_train)
focus = Focus(
num_iter=1000,
distance_function="euclidean",
sigma=trial.suggest_int("sigma", 1, 20, step=1.0),
temperature=0, # DT models do not use temperature
distance_weight=round(
trial.suggest_float("distance_weight", 0.01, 0.1, step=0.01), 2
),
lr=round(trial.suggest_float("lr", 0.001, 0.01, step=0.001), 3),
optimizer=tf.keras.optimizers.Adam(),
hyperparameter_tuning=True,
verbose=0,
)
best_perturb, unchanged_ever, cfe_distance = focus.generate(model, X_test)
print(f"Unchanged: {unchanged_ever}")
print(f"Mean distance: {cfe_distance}")
return cfe_distance / 100 + pow(unchanged_ever, 2)
一旦我们定义了目标函数,就可以开始调整这些超参数。
if __name__ == "__main__":
study = optuna.create_study(direction="minimize")
study.optimize(objective, n_trials=100)
print(f"Number of finished trials: {len(study.trials)}")
trial = study.best_trial
print("Best trial:")
print(" Value: {}".format(trial.value))
print(" Params: ")
for key, value in trial.params.items():
print(" {}: {}".format(key, value))
更全面的示例可以在软件包仓库中找到。
4. 限制
Focus 类存在几个限制。以下是这些限制的列表:
-
目前,Focus 类仅适用于 scikit-learn 的
DecisionTreeClassifier
、RandomForestClassifier
和AdaBoostClassifier
。 -
虽然类别特征可以包含在特征集中,但需要注意的是,类别特征变化的解释,例如从年龄 40 变为 20,可能不会提供有意义的见解。
-
在应用 Focus 之前,输入特征应缩放到 0 和 1 的范围。因此,在使用 Focus 之前,有必要对特征进行转换。然而,这种缩放过程可能在应用 Focus 后解释特征时引入一些额外的复杂性。
-
计算成本会随着给定模型的增大而增加。当你有一个大型模型时,可能无法执行代码。
5. 结论
CFXplorer Python 包提供了对 FOCUS 算法的全面使用,以生成给定基于树的算法的反事实解释的最优距离。尽管存在一些限制,但对于那些希望在基于树的模型中探索反事实结果的人来说,这个包应该是有用的。
本文回顾了 FOCUS 算法的理论背景,代码示例展示了如何使用 CFXplorer,以及一些当前的限制。未来,我将为这个包添加更多的反事实解释生成方法。
希望你觉得这篇文章有用。
6. 参考文献
-
A. Lucic, H. Oosterhuis, H. Haned, 和 M. de Rijke. “FOCUS: 灵活可优化的树集成反事实解释。” 载于:AAAI 人工智能会议论文集。第 36 卷,第 5 期,2022 年,页 5313–5322。
-
S. Wachter, B. Mittelstadt, 和 C. Russell. “不打开黑箱的反事实解释:自动决策与 GDPR。” 载于:Harv. JL & Tech. 第 31 卷 (2017),页 841。
-
T. Akiba, S. Sano, T. Yanase, T. Ohta, 和 M. Koyama. “Optuna: 下一代超参数优化框架。” 载于:第 25 届 ACM SIGKDD 国际知识发现与数据挖掘会议论文集。2019 年,页 2623–2631。
-
D. P. Kingma 和 J. Ba. “Adam: 一种随机优化方法。” 载于:arXiv 预印本 arXiv:1412.6980 (2014)。