精通Transformer——自解释人工智能(XAI)在NLP中的应用

708 阅读29分钟

大型语言模型(LLMs)的日益普及将模型输出的准确性与可解释性之间的权衡摆到了前台。可解释人工智能(XAI)研究中的最大挑战是处理深度神经模型中存在的大量层和参数。那么,我们如何解决这个问题呢?我们能否找到一种方法来理解深度模型如何做出决策?简单的回答是不能,但另一种回答是有些可能。

在本章中,我们将仅从Transformers的角度来接近这个问题。我们将从两个方面来审视这个问题。首先,最相关的自注意力机制——Transformer架构中与可解释性相关的部分,将被详细探讨。这个机制可以使Transformer模型处理输入的过程对人类可理解。我们将使用各种注意力可视化工具,这些工具提供了可解释性和解释性的关键功能。首先,我们将讨论如何可视化注意力的内部部分,并解释学习到的表示,这有助于我们理解Transformer中自注意力头所编码的信息。注意力机制最有趣的特征之一是某些头对应于语法或语义的特定方面。其次,我们将尝试使用两种重要的方法,即LIME和SHapley Additive exPlanations(SHAP),来解释Transformer模型如何做出决策。

简而言之,本章将涵盖以下主题:

  • 解释注意力头
  • 使用LIME方法解释决策
  • 使用SHAP方法解释决策

技术要求

本章的代码可以在GitHub链接中找到,这是本书的GitHub仓库。我们将使用Jupyter Notebook来运行需要Python 3.6.0或更高版本的编码练习,以下包需要安装:

  • tensorflow
  • pytorch
  • transformers >=4.00
  • bertviz
  • ipywidgets
  • lime
  • shap

解释注意力头

与大多数深度学习(DL)架构一样,Transformer模型的成功及其学习过程尚未完全理解,但我们知道,Transformer模型显著地学习了许多语言的语言特征。大量学习到的语言知识分布在预训练模型的隐藏状态和自注意力头中。最近已经发表了大量的研究,并且开发了许多工具来理解和更好地解释这些现象。

感谢一些自然语言处理(NLP)社区工具,我们可以解释Transformer模型中自注意力头所学习的信息。由于标记之间的权重,这些头可以自然地进行解释。在本节的进一步实验中,我们将看到某些头对应于语法或语义的特定方面。我们还可以观察到表层模式和许多其他语言特征。

在以下子章节中,我们将使用社区工具进行一些实验,以观察这些模式和特征。最近的研究已经揭示了自注意力的许多特征。让我们在开始实验之前,重点介绍一些已知的特征。例如,大多数头关注于分隔符标记,如分隔符(SEP)和分类标记(CLS),因为这些标记从未被掩蔽且承载段级信息。另一个观察是,大多数头对当前标记关注较少,但一些头专注于仅关注下一个或前一个标记,特别是在较早的层中。

以下是最近研究中发现的其他模式,这些模式在我们的实验中也可以轻松观察到:

  • 同一层中的注意力头表现出类似的行为。
  • 特定的头对应于语法或语义关系的特定方面。
  • 一些头编码的方式使得直接宾语倾向于关注其动词,例如 <lesson, take> 或 <car, drive>。
  • 在一些头中,名词修饰词关注其名词(例如,hot water; the next layer),或所有格代词关注主词(例如,her car)。
  • 一些头编码的方式使得被动助动词关注相关动词,例如 Been damaged 和 was taken。
  • 在一些头中,共指提及关注自身,例如 talks-negotiation、she-her 和 President-Biden。
  • 较低的层通常包含有关单词位置的信息。
  • 语法特征在Transformer的早期层中被观察到,而高级语义信息则出现在较高的层中。
  • 最终层是最具任务特定性的,因此对下游任务非常有效。

为了观察这些模式,我们可以使用两个重要的工具,exBERT和BertViz。这些工具具有几乎相同的功能。我们将从exBERT开始。

使用 exBERT 可视化注意力头

exBERT 是一个用于查看 Transformer 内部结构的可视化工具。我们将使用它来可视化 BERT-base-cased 模型的注意力头,这是 exBERT 界面的默认模型。除非另有说明,我们在以下示例中使用的模型是 BERT-base-cased。该模型包含12层,每层有12个自注意力头,总共144个自注意力头。

我们将逐步学习如何使用 exBERT,如下所示:

  • 点击 Hugging Face 托管的 exBERT 链接:huggingface.co/exbert
  • 输入句子 "The cat is very sad." 并查看输出结果,如下所示:

image.png

在前面的截图中,左侧的标记关注于右侧的标记。线条的粗细表示权重的值。由于CLS和SEP标记具有非常频繁和密集的连接,我们为了简便而切断了与这些标记相关的链接。请参见“隐藏特殊标记”开关。现在我们看到的是第1层的注意力映射,其中线条对应于所有头上权重的总和。这被称为多头注意力机制,其中12个头并行工作。这种机制使我们能够捕捉到比单头注意力更多的关系。这就是我们在图11.1中看到广泛关注模式的原因。我们还可以通过点击“头”列来观察任何特定的头。

如果你将鼠标悬停在左侧的某个标记上,你将看到该标记连接到右侧标记的具体权重。有关使用该界面的更多详细信息,请阅读论文《exBERT: A Visual Analysis Tool to Explore Learned Representations in Transformer Models》,作者 Benjamin Hoover、Hendrik Strobelt、Sebastian Gehrmann,2019,或观看以下链接的视频:github.com/bhoov/exber…

现在,我们将尝试支持本节引言部分提到的其他研究人员的发现。让我们选择一些专注于仅关注下一个或前一个标记的头,特别是在早期层的模式,并查看是否有头支持这一点。

在本章其余部分,我们将使用 <Layer-No, Head-No> 记号来表示某个自注意力头,其中索引在 exBERT 中从 1 开始,在 BertViz 中从 0 开始。例如,<3,7> 表示 exBERT 中第三层的第七个头。当你选择 <2,5>(或 <4,12> 或 <6,2>)头时,你将得到以下输出,其中每个标记仅关注于前一个标记:

image.png

对于 <2,12> 和 <3,4> 这两个头,你将得到以下模式,其中每个标记关注于下一个标记:

image.png

这些头在处理其他输入句子时具有相同的功能,即它们独立于输入进行工作。你可以自己尝试不同的句子。

我们可以使用一个注意力头来进行高级语义任务,如代词解析,使用探测分类器。首先,我们将定性地检查内部表示是否具备进行代词解析(或共指解析)的能力。代词解析被认为是一个具有挑战性的语义关系任务,因为代词和其前置词之间的距离通常很长。

现在,我们使用句子:“The cat is very sad, because it could not find food to eat.” 当你检查每个头时,你会注意到 <9,9> 和 <9,12> 头编码了代词关系。当悬停在 <9,9> 头上时,我们得到以下输出:

image.png

<9,12> 头也适用于代词关系。同样,当你悬停在它上面时,我们得到以下输出:

image.png

从前面的截图中,我们可以看到代词 "it" 强烈关注于它的前置词 "cat"。我们稍微修改句子,使得代词 "it" 现在指代 "food" 标记,而不是 "cat" 标记,例如:“The cat did not eat the food because it was not fresh.” 如下图所示,与 <9,9> 头相关,它正确地关注于其前置词 "food",如预期的那样:

image.png

让我们来看另一个例子,其中代词指代 "cat" 标记,例如:“The cat did not eat the food because it was very angry.” 在 <9,9> 头中,代词 "it" 大部分关注于 "cat" 标记,如下图所示:

image.png

我认为这些例子已经足够了。现在,我们将以不同的方式使用 exBERT 模型来评估模型的能力。让我们重新启动 exBERT 界面,选择最后一层(第12层),并保留所有的头。然后,输入句子 “The cat did not eat the food.” 并将 “food” 标记掩蔽掉。双击可以将 “food” 标记掩蔽掉,如下所示:

image.png

当你将鼠标悬停在那个被掩蔽的标记上时,可以看到 BERT-base-cased 模型的预测分布,如前面的截图所示。第一个预测是 "food",这是预期的。如今,我们利用 BERT 模型的这种特性进行数据增强。例如,你可以将 "food" 替换为 "meat" 作为第二个预测,这样你就会得到一个与原始实例相似的新实例。有关该工具的更多详细信息,可以参考 exBERT 的网页:exbert.net/

干得好!在下一节中,我们将使用 BertViz 并编写一些 Python 代码来访问注意力头。

使用 BertViz 进行多尺度注意力头可视化

现在,我们将编写一些代码来使用 BertViz 可视化注意力头。BertViz 是一个用于可视化 Transformer 模型中注意力的工具,与 exBERT 类似。它由 Jesse Vig 在 2019 年开发(参见《A Multiscale Visualization of Attention in the Transformer Model》,Jesse Vig,2019)。它是 Tensor2Tensor 可视化工具(Jones, 2017)工作的扩展。我们可以通过多尺度定性分析来监控模型的内部结构。BertViz 的优势在于我们可以处理大多数 Hugging Face 托管的模型(如 BERT、GPT 和 XLM 通过 Python API)。因此,我们也能处理非英语模型或任何预训练模型。你可以从以下 GitHub 链接访问 BertViz 资源和其他信息:github.com/jessevig/be…

与 exBERT 类似,BertViz 在一个界面中可视化注意力头。此外,它支持鸟瞰视图和低层次神经元视图,我们可以观察到各个神经元如何互动以构建注意力权重。一个有用的演示视频可以在以下链接找到:vimeo.com/340841955

在开始之前,我们需要安装必要的库,如下所示:

!pip install bertviz ipywidgets transformers

然后,我们导入以下模块:

from bertviz import head_view
from transformers import BertTokenizer, BertModel

BertViz 支持三种视图:头视图、模型视图和神经元视图。我们将逐一检查这些视图。然而,需要指出的是,我们在 exBERT 中的层和头索引从 1 开始。但在 BertViz 中,索引从 0 开始,如 Python 编程。如果我在 exBERT 中提到 <9,9> 头,它在 BertViz 中对应的是 <8,8>。

让我们从头视图开始。

注意力头视图

头视图是 BertViz 中与我们在前一节中体验过的 exBERT 相对应的视图。注意力头视图基于选定层中的一个或多个注意力头可视化注意力模式:

首先,我们定义一个 get_bert_attentions() 函数来检索给定模型和给定句子对的注意力和标记。函数定义如下:

def get_bert_attentions(model_path, sentence_a, sentence_b):
    model = BertModel.from_pretrained(model_path, output_attentions=True)
    tokenizer = BertTokenizer.from_pretrained(model_path)
    inputs = tokenizer.encode_plus(sentence_a, sentence_b, return_tensors='pt', add_special_tokens=True)
    token_type_ids = inputs['token_type_ids']
    input_ids = inputs['input_ids']
    attention = model(input_ids, token_type_ids=token_type_ids)[-1]
    input_id_list = input_ids[0].tolist()
    tokens = tokenizer.convert_ids_to_tokens(input_id_list)
    return attention, tokens

在以下代码片段中,我们加载了 bert-base-cased 模型,并检索给定句子的标记和相应的注意力。然后,我们在最后调用 head_view() 函数来可视化注意力。代码执行如下:

model_path = 'bert-base-cased'
sentence_a = "The cat is very sad."
sentence_b = "Because it could not find food to eat."
attention, tokens = get_bert_attentions(model_path, sentence_a, sentence_b)
head_view(attention, tokens)

代码输出是一个界面,如下所示:

image.png

图 11.9 左侧的界面首先出现。将鼠标悬停在左侧的任何标记上,将显示从该标记发出的注意力。顶部的彩色瓦片对应于注意力头。双击其中任何一个将进行选择并丢弃其余部分。较粗的注意力线表示较高的注意力权重。

请记住,在前面的 exBERT 示例中,我们观察到 <9,9> 头(在 BertViz 中对应的头为 <8,8>,由于索引问题)具有代词-前置词关系。我们在图 11.9 中观察到相同的模式,选择第 8 层和第 8 头。然后,当我们悬停在图 11.9 右侧的界面上时,我们会看到它强烈关注于 "cat" 和 "it" 标记(它自己)。那么,我们能否在其他预训练语言模型中观察到这些语义模式?虽然在其他模型中,头部的精确匹配可能不同,但一些头部可以编码这些语义属性。我们还知道,近期的研究表明,语义特征主要编码在更高的层次中。

让我们在土耳其语模型中寻找共指模式。以下代码加载一个土耳其语的 bert-base-cased 模型,并处理一个句子对。我们观察到 <8,8> 头在土耳其语中具有与英语模型相同的语义特征,如下所示:

model_path = 'dbmdz/bert-base-turkish-cased'
sentence_a = "Kedi çok üzgün."
sentence_b = "Çünkü o her zamanki gibi çok fazla yemek yedi."
attention, tokens = get_bert_attentions(model_path, sentence_a, sentence_b)
head_view(attention, tokens)

从前面的代码中,sentence_asentence_b 分别表示 “The cat is sad” 和 “Because it ate too much food”。当悬停在 "o"(它)上时,它关注于 "Kedi"(猫),如下所示:

image.png

除 "o"(它)之外,所有其他标记大多关注于 SEP 分隔符标记,这是 BERT 架构中所有头部的主导行为模式。

作为头视图的最后一个示例,我们将解释另一个语言模型,并转到模型视图功能。这一次,我们选择 bert-base-german-cased 德语语言模型,并为输入可视化——即我们用于土耳其语的相同句子对的德语等效句子。

以下代码加载一个德语模型,处理一对句子,并进行可视化:

model_path = 'bert-base-german-cased'
sentence_a = "Die Katze ist sehr traurig."
sentence_b = "Weil sie zu viel gegessen hat"
attention, tokens = get_bert_attentions(model_path, sentence_a, sentence_b)
head_view(attention, tokens)

当我们检查头部时,我们可以再次看到第 8 层中的共指模式,但这次是在第 11 个头部。要选择 <8,11> 头部,请从下拉菜单中选择第 8 层,并双击最后一个头部,如下所示:

image.png

如你所见,当将鼠标悬停在 "sie" 上时,你会看到对 "Die Katze" 的强关注。虽然这个 <8,11> 头部是最强的共指关系(在计算语言学文献中称为指代关系)的头部,但这种关系可能已经扩展到许多其他头部。为了观察这一点,我们需要逐个检查所有头部。

另一方面,BertViz 的模型视图功能为我们提供了一个基础的鸟瞰图,能够一次性查看所有头部。让我们在下一节中了解这一功能。

模型视图

模型视图允许我们从整体上查看所有头部和层的注意力。自注意力头以表格形式显示,行和列分别对应层和头。每个头以可点击的缩略图形式可视化,显示注意力模型的整体形状。

这种视图可以告诉我们 BERT 的工作方式,并使解释更为容易。许多近期的研究,如《A Primer in BERTology: What We Know About How BERT Works》 (Anna Rogers, Olga Kovaleva, Anna Rumshisky) 提供了一些关于层行为的线索,并得出了结论。我们已经在“解释注意力头”部分列出了一些这些事实。你可以使用 BertViz 的模型视图自己测试这些事实。

让我们查看刚刚使用过的德语语言模型,如下所示:

首先,导入以下模块:

from bertviz import model_view
from transformers import BertTokenizer, BertModel

现在,我们将使用 Jesse Vig 开发的 show_model_view() 包装函数。你可以在以下链接找到原始代码:show_model_view 函数书籍 GitHub。我们这里只是简化了函数头部:

def show_model_view(model, tokenizer, sentence_a,
    sentence_b=None, hide_delimiter_attn=False,
    display_mode="dark"):
    # 函数体省略

接下来,重新加载德语模型。如果你已经加载过,可以跳过前五行。以下是你需要的代码:

model_path = 'bert-base-german-cased'
sentence_a = "Die Katze ist sehr traurig."
sentence_b = "Weil sie zu viel gegessen hat"
model = BertModel.from_pretrained(model_path, output_attentions=True)
tokenizer = BertTokenizer.from_pretrained(model_path)
show_model_view(model, tokenizer, sentence_a, sentence_b, hide_delimiter_attn=False, display_mode="light")

这是输出结果:

image.png

这种视图帮助我们轻松观察到许多模式,如下一个词(或上一个词)注意力模式。正如我们在“解释注意力头”部分提到的,标记通常倾向于关注分隔符——特别是,低层中的 CLS 分隔符和高层中的 SEP 分隔符。因为这些标记没有被掩盖,它们可以缓解信息流动。在最后几层中,我们只观察到以 SEP 分隔符为中心的注意力模式。这可以推测 SEP 用于收集段级信息,然后可以用于诸如下一句预测(NSP)这样的句间任务或用于编码句子级的意义。

另一方面,我们观察到共指关系模式主要编码在 <8,1>、<8,11>、<10,1> 和 <10,7> 这些头部中。再次明确地说,<8,11> 头部是德语模型中编码共指关系的最强头部,这一点我们已经讨论过了。

当你点击那个缩略图时,你将看到相同的输出,如下所示:

image.png

再次,你可以将鼠标悬停在标记上查看映射。

我认为关于头部视图和模型视图的工作已经足够了。现在,让我们借助神经元视图来解构模型,并尝试理解这些头部是如何计算权重的。

神经元视图

到目前为止,我们已经可视化了给定输入的计算权重。神经元视图可视化了神经元和查询中的关键向量,以及基于交互计算标记之间的权重。我们可以追踪任何两个标记之间的计算阶段。

我们将加载德语模型,并可视化我们刚刚处理的相同句子对。我们执行以下代码:

from bertviz.Transformers_neuron_view import BertModel, BertTokenizer
from bertviz.neuron_view import show

model_path = 'bert-base-german-cased'
sentence_a = "Die Katze ist sehr traurig."
sentence_b = "Weil sie zu viel gegessen hat"
model = BertModel.from_pretrained(model_path, output_attentions=True)
tokenizer = BertTokenizer.from_pretrained(model_path)
model_type = 'bert'
show(model, model_type, tokenizer, sentence_a, sentence_b, layer=8, head=11)

这是输出结果:

image.png

这个视图帮助我们追踪从左侧选择的 sie 标记到右侧其他标记的注意力计算。正值显示为蓝色,负值显示为橙色。颜色的强度代表数值的大小。sie 的查询与 DieKatze 的键非常相似。如果仔细观察这些模式,你会注意到这些向量是多么相似。因此,它们的点积比其他比较高,这在这些标记之间建立了强注意力。我们还追踪了点积和 softmax 函数的输出。点击左侧的其他标记,你也可以追踪其他计算过程。

现在,让我们选择一个具有下一个标记注意力模式的头部,并对其进行追踪。为此,我们选择 <2,6> 头。在这种模式下,几乎所有的注意力都集中在下一个词上。我们再次点击 sie 标记,如下所示:

image.png

现在,sie 标记专注于下一个标记,而不是它自己的前置词(Die Katze)。当我们仔细查看查询和候选键时,sie 的查询最相似的键是下一个标记 zu。同样,我们观察了点积和 softmax 函数如何按顺序应用。

在接下来的部分,我们将简要讨论用于解释 Transformers 的探测分类器。

通过探测分类器理解 BERT 的内部部分

深度学习模型的不可解释性引发了大量关于这些模型解释性的研究。我们试图回答 Transformer 模型的哪些部分负责某些语言特征,或者输入的哪些部分使模型做出特定决策。为此,除了可视化内部表示外,我们还可以在这些表示上训练分类器,以预测一些外部的形态学、句法或语义属性。因此,我们可以确定内部表示是否与外部属性相关联。模型的成功训练将成为这种关联的量化证据,即语言模型已经学习了与外部属性相关的信息。这种方法称为探测分类器方法,是 NLP 和其他深度学习研究中的一种重要分析技术。基于注意力的探测分类器将注意力图作为输入,并预测外部属性,如共指关系或头部-修饰关系。

正如在前面的实验中所看到的,我们可以使用 get_bert_attention() 函数获取给定输入的自注意力权重。我们可以将这些权重直接转移到分类管道中,而不是进行可视化。通过监督,我们可以确定哪个头部适用于哪个语义特征——例如,我们可以找出哪些头部适用于共指关系,前提是有标记的数据。

然而,探测分类器框架表明,它比最初看起来的要复杂。清晰地定义原始数据集和模型,以及探测数据集和分类器是至关重要的。根据你的目标,你可能需要评估从探测器中提取信息的复杂性和难易程度。

现在,让我们转到模型跟踪部分,这对于构建高效模型至关重要。

解释模型决策

即使我们不能完全理解大语言模型(LLMs)的决策或结果,我们仍然可以理解导致这些决策的某些方面。但首先,我们需要回答并理解我们要在这里解释什么。以下是我们需要关注的三个方面,如下图所示的分类图:

image.png

即使我们不能完全理解大语言模型(LLMs)的决策或结果,我们仍然可以了解某些导致这些决策的方面。但首先,我们需要明确我们要解释的内容。以下是三个需要考虑的方面,如图所示的分类图:

首先,我们是尝试了解模型如何整体上做出决策,还是在特定输入下的行为?这种区别被称为全局解释和局部解释。局部解释关注于对特定输入的决策,而全局解释则尝试解释模型的预测过程。

第二个区别在于XAI模型是自解释的还是事后解释的。前者,也称为自解释模型,在做出预测的同时也提供解释,如自注意力机制。然而,自注意力机制用于构建上下文词和句子嵌入,而不是做出决策。决策树等知名的机器学习模型和其他基于规则的模型是全局自解释模型的良好例子。后者,即事后解释模型,需要额外的过程来理解模型的决策行为,其中使用替代模型来解释主模型。这种事后过程可以是全局的也可以是局部的。尽管LIME是使用替代模型生成局部解释的一个好例子,但SHAP可以在两种方式下发挥作用。

XAI的另一个方面是区分模型特定的解释工具和模型无关的解释工具。模型特定工具只能用于特定模型,而模型无关工具则可以解释任何黑箱模型。

由于最近的机器学习趋势基于大型神经网络模型,如LLMs,通常被认为是黑箱模型,替代模型和模型无关的方法越来越受欢迎。这些替代模型可以帮助解释神经网络模型的决策行为。

在业界,我们看到了一些非常受欢迎的开源实现,按受欢迎程度排序如下:

  • SHAP
  • LIME
  • LIT
  • ELI-5
  • Transformers Interpret
  • Captum

我们首先查看LIME和SHAP模型,以理解Transformers模型的决策。我们从SHAP开始。

使用LIME解释Transformers决策

LIME是一个模型无关、局部和事后解释的方法。LIME的主要思想是输入扰动。随机扰动应用于输入(在我们的例子中是句子),并测量这些扰动对输出(情感或类别)的影响。这个过程使用一个简单的可训练机器学习模型作为主要的XAI替代模型。

在LIME过程中,解释被定义为模型行为的局部线性近似。这是因为许多模型在全局上是复杂的,因此在特定实例的邻近区域内近似它们更容易。为此,待解释的实例会被扰动,并在该实例的邻近区域学习一个稀疏线性模型(也称为替代模型)。更多细节请参见论文《Why Should I Trust You?: Explaining the Predictions of Any Classifier》(Ribeiro等)。

我们可以简化LIME过程如下:

  1. 选择要解释的单一预测。
  2. 通过随机隐藏特征来扰动输入,并收集黑箱模型的结果,这生成了原始单个样本周围的邻域数据。
  3. 根据新邻域样本与原始预测的相似程度分配权重。
  4. 在这些样本变体上训练一个复杂度较低、可解释的线性模型,得到一个替代模型。
  5. 最后,由替代模型预测这个局部预测的解释。

现在,我们使用LIME和Transformer库进行一个实际的例子来理解LIME过程。

首先,安装必要的软件包:

!pip install lime transformers

我们将使用在CH05上微调的土耳其文本分类模型,并从Hugging Face hub加载它,如下所示:

import lime
from lime.lime_text import LimeTextExplainer
from transformers import (AutoTokenizer, AutoModelForSequenceClassification)

model_path = 'savasy/bert-turkish-text-classification'
tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoModelForSequenceClassification.from_pretrained(model_path)
class_names = model.config.id2label.values()

我们有七个类别,如下:

class_names
Output: dict_values(['world', 'economy', 'culture', 'health', 'politics', 'sport', 'technology'])

现在,我们将传入一段土耳其文本并解释分类。文本是“Ünlü futbolcu Ahmet Yıldız sene sonunda yapılacak turnuva maçları hakkında konuştu”,意思是“著名足球运动员Ahmet Yıldız谈论了年末举行的锦标赛”。显然这是关于体育的。

以下代码对输入进行分类并绘制类别概率分布:

import pandas as pd
import torch.nn.functional as Func

text = 'Ünlü futbolcu Ahmet Yıldız sene sonunda yapılacak turnuva maçları hakkında konuştu'
outputs = model(**tokenizer(text, return_tensors="pt", padding=True))
probabilities = Func.softmax(outputs[0]).detach().numpy()
q = dict(zip(class_names, probabilities[0]))
pd.Series(q).plot(kind="bar")

这将绘制出以下分布,其中“sport”从七个类别标签中被选中。

image.png

现在,我们将为这个决策生成一个解释。为此,我们需要将这些代码行写入一个函数,该函数计算类别概率分布。这个函数将被LIME XAI模型用来生成邻域样本并训练线性XAI分类器。以下是函数代码:

def pred_prob(text):
    outputs = model(**tokenizer(text, return_tensors="pt", padding=True))
    ps = Func.softmax(outputs[0]).detach().numpy()
    return ps

我们准备好运行LIME代码了。如下所示,我们定义了一个 LimeTextExplainer 实例,并将类别名称传递给它。然后,我们传递文本和 pred_prob 类别概率函数,并选择第五个标签,即“体育”。num_features 参数告诉我们LIME模型用于解释决策的特征数量。在我们的文本分类案例中,它表示LIME模型为决策解释选择了多少个词。num_samples 参数告诉我们将生成多少个邻域样本。我们通常将此数字设置得较高,例如1K或5K,以便高效地训练线性分类器:

explainer = LimeTextExplainer(class_names=class_names)
exp = explainer.explain_instance(text,
    pred_prob,
    labels=[5],
    num_features=7,
    num_samples=1000)
exp.show_in_notebook(text=text)

执行后,我们将看到以下输出。它解释了文本中棕色的单词指示了文本属于体育类别。

image.png

在这个示例中,浅蓝色表示非体育内容。如输出所示,文本分类模型在将文本映射到体育类别时关注了与体育相关的术语。词语越暗,决策的显著性越高。现在,我们来看另一个英文的示例。以下代码将加载用于英语的情感分类模型。它是一个多标签文本分类模型,共有28个标签。我们将其作为单标签分类模型,使用softmax函数。这个过程与土耳其语管道相同:

from transformers import AutoTokenizer, AutoModelForSequenceClassification

tokenizer = AutoTokenizer.from_pretrained("SamLowe/roberta-base-go_emotions")
model = AutoModelForSequenceClassification.from_pretrained("SamLowe/roberta-base-go_emotions")
class_names = model.config.id2label.values()

我们将对以下文本进行分类:“I have tried different models, but I have not yet made a decision.” 模型会将其预测为“confusion”(id=6):

text = "I have tried different models, but I have not yet made a decision."
explainer = LimeTextExplainer(class_names=class_names)
exp = explainer.explain_instance(text,
    pred_prob,
    labels=[6],
    num_features=7,
    num_samples=1000)
exp.show_in_notebook(text=text)

它输出以下解释:

image.png

就像之前的示例一样,每种颜色都有其特定的含义。红色表示混淆标签,而绿色表示非混淆标签。在这里,我们裁剪了LIME的输出。运行代码时,你将看到大量关于颜色编码的信息。

LIME的一个缺点是其替代模型基于采样数据点,这使得它对所使用的采样策略非常敏感。不同的采样方法可能会导致非常不同的解释结果。此外,LIME方法基于假设,即局部实例可以线性解释。

现在,我们将使用SHAP模型做同样的事情!

使用SHAP解释Transformers的决策

SHAP是XAI领域最常见且广泛使用的技术之一。它基于博弈论中的Shapley值。与LIME不同,SHAP并不总是构建局部可解释模型。相反,它利用黑箱模型来计算每个特征对预测的边际贡献。有关详细信息,请参见论文《A Unified Approach to Interpreting Model Predictions》,作者是Scott M. Lundberg和Su-In Lee。

通过这样做,它提供了对驱动模型决策过程的最重要特征的见解,既包括全局也包括局部。SHAP计算每个单词的特征重要性的SHAP值,然后通过累加每个单独预测的绝对SHAP值来提供全局特征重要性。SHAP技术因其能够为复杂的深度学习模型提供准确且可解释的解释而在XAI领域越来越受欢迎。

虽然LIME假设局部模型需要是线性的,但SHAP没有这样的假设。然而,SHAP的一个缺点是计算SHAP值可能是计算上昂贵且耗时的。这是因为它检查所有可能的组合,并通过Monte Carlo模拟而非暴力计算来完成这一过程。

现在,让我们通过以下代码示例学习SHAP过程,使用与LIME示例相同的模型。

安装必要的软件包:

!pip install shap transformers

让我们加载之前的28类英语情感分类模型:

from transformers import AutoTokenizer, AutoModelForSequenceClassification

tokenizer = AutoTokenizer.from_pretrained("SamLowe/roberta-base-go_emotions")
model = AutoModelForSequenceClassification.from_pretrained("SamLowe/roberta-base-go_emotions")
class_names = model.config.id2label.values()

我们将它们包装在一个管道对象中,如下所示:

from transformers import pipeline

classifier = pipeline("text-classification", model=model, tokenizer=tokenizer)

首先对文本进行分类:

text = "I have tried different models, but I have not yet made a decision."
classifier(text)

输出为:

{'label': 'confusion', 'score': 0.528}

它决定了混淆标签。我们将通过SHAP来解释这个决策,如下所示:

import shap

explainer = shap.Explainer(classifier)
shap_values = explainer([text])

我们将目标标签指定为混淆,以查看哪些特征使模型倾向于这一决策。我们选择条形图可视化,因为它比其他方式更易读:

shap.plots.bar(shap_values[0, :, "confusion"])

它输出以下图示:

image.png

我们将看到与LIME类似的解释,大致相同。最后值得一提的是,SHAP由于其基于博弈论中Shapley值概念的理论论证和简洁性而被广泛使用和接受。然而,在LIME中,我们必须定义如何考虑邻域样本。此外,我们构建了一个线性局部模型,这在高度复杂的决策表面上可能不具有线性特性。而在SHAP中,我们没有这样的假设。

总结

在本章中,我们讨论了AI面临的一个重要问题:可解释性(XAI)。随着语言模型的快速发展,可解释性成为一个严重的问题。我们仅从Transformers的角度讨论了两个主题。首先,我们研究了架构中的自注意力机制。我们尝试通过各种可视化工具理解这些机制的内部过程。其次,我们解释了Transformer架构的决策过程。我们使用了两种模型无关的方法:LIME和SHAP。通过这两种技术,我们观察了模型在简单文本分类过程中如何赋予输入(词语)不同的重要性。

在下一章中,我们将关注一种特殊的Transformer,即高效Transformer。