在 Keras 中使用 LIT 分析 Gemma 模型

查看生成式 AI 在 Google Colab 中运行 查看 GitHub 上的源代码 在 Codelab 中学习

简介

生成式 AI 产品相对较新,应用的行为可能与早期形式的软件相比存在更多差异。因此,探究正在使用的机器学习模型、检查模型的行为示例并调查意外状况非常重要。

学习可解释性工具(LIT;网站GitHub)是一个用于调试和分析机器学习模型的平台,以了解其行为的原因和方式。

在此 Codelab 中,您将学习如何使用 LIT 更充分地利用 Google 的 Gemma 模型。此 Codelab 演示了如何使用序列显著性(一种可解释性技术)来分析不同的提示工程方法。

学习目标:

  1. 了解序列显著性及其在模型分析中的用途。
  2. 为 Gemma 设置 LIT 以计算提示输出和序列显著性。
  3. 通过 LM 显著性模块使用序列显著性,了解提示设计对模型输出的影响。
  4. 在 LIT 中测试假设的提示改进,并观察其影响。

注意:此 Codelab 使用 Gemma 的 KerasNLP 实现,并将 TensorFlow v2 用于后端。强烈建议您使用 GPU 内核来遵循。

序列显著性及其在模型分析中的用途

文本到文本的生成模型(如 Gemma)接受词元化文本形式的输入序列,并生成新词元,这些词元是该输入的典型后续补充或补全。这一代词每次出现一个令牌,将每个新生成的词元(循环)附加到输入以及之前的所有词元,直到模型达到停止条件为止。例如,模型生成序列结束 (EOS) 令牌或达到预定义的最大长度。

显著性方法是一类可解释的 AI (XAI) 技术,可以告诉您输入的哪些部分对于模型输出的不同部分来说很重要。LIT 支持各种分类任务的显著性方法,这些方法解释了一系列输入词元对预测标签的影响。序列显著性将这些方法泛化到文本到文本生成模型,并解释了前面的词元对生成的词元的影响。

您将在此使用 Grad L2 Norm 方法实现序列显著性,该方法会分析模型的梯度,并提供前一个词元对输出的影响程度。这种方法简单高效,实践证明在分类和其他设置中表现良好。显著性得分越高,影响越大。这种方法之所以在 LIT 内使用,是因为它在可解释性研究社区中获得广泛理解和广泛使用。

更高级的基于梯度的显著性方法包括 Grad ⋅ 输入积分梯度。还有一些基于消融的方法(例如 LIMESHAP),它们可能更可靠,但计算费用要高得多。如需查看不同显著性方法的详细比较,请参阅这篇文章

如需详细了解显著性方法的科学知识,请参阅这份有关显著性方面的入门级交互式探索

导入、环境和其他设置代码

ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
bigframes 0.21.0 requires scikit-learn>=1.2.2, but you have scikit-learn 1.0.2 which is incompatible.
google-colab 1.0.0 requires ipython==7.34.0, but you have ipython 8.14.0 which is incompatible.

您可以放心地忽略这些问题。

安装 LIT 和 Keras NLP

对于此 Codelab,您需要最新版本的 keras (3) keras-nlp (0.8.0) 和 lit-nlp (1.1),以及一个 Kaggle 帐号来下载基本模型。

pip install -q -U lit-nlp
pip uninstall -y umap-learn
pip install -q -U keras-nlp
pip install -q -U keras

Kaggle Access

如需登录 Kaggle,您可以将 kaggle.json 凭据文件存储在 ~/.kaggle/kaggle.json 中,也可以在 Colab 环境中运行以下代码。如需了解详情,请参阅 kagglehub 软件包文档

import kagglehub

kagglehub.login()

此外,请务必接受 Gemma 的许可协议。

为 Gemma 设置 LIT

设置 LIT 模型

import os

os.environ["KERAS_BACKEND"] = "tensorflow"
import keras
import keras_nlp

# Run at half precision.
keras.config.set_floatx("bfloat16")
model_name = 'gemma_instruct_2b_en'
gemma_model = keras_nlp.models.GemmaCausalLM.from_preset(model_name)

以下代码会初始化 LIT 封装容器,以支持 Gemma 模型上的显著性。LIT 框架将这些称为模型,但在本例中,它们只是您在上面加载的同一底层 gemma_model 的不同端点。这使 LIT 能够按需计算世代、令牌化和显著性。

from lit_nlp.examples.models import instrumented_keras_lms

batch_size = 1
max_sequence_length = 512
init_models = instrumented_keras_lms.initialize_model_group_for_salience
models = init_models(model_name, gemma_model,
                     batch_size=batch_size,
                     max_length=max_sequence_length)

设置 LIT 数据集

Gemma 是一种文本到文本生成模型,它接受文本输入并生成文本输出。LIT 的模型假定数据集将提供以下字段以支持生成:

  • promptKerasGenerationModel 的输入。
  • target:可选的目标序列,例如“标准答案”(黄金级)答案或模型预先生成的响应。

LIT 包含一小部分 sample_prompts,其中包含来自几个不同来源的示例,例如:

  • [GSM8K][GSM8K]:使用少样本示例解决小学数学问题。
  • [Gigaword Benchmark][gigaword]:为一系列短篇报道生成标题。
  • [宪法提示][宪法提示]:提出有关如何在有准则/边界的情况下使用对象的新想法

您可以轻松加载自己的数据,可以采用 .jsonl 文件格式,其中包含带有字段 prompt 和可选的 target ([example][jsonl-example]) 的文件,也可以使用 LIT 的 Dataset API 以任意格式加载数据。

请运行以下单元以加载示例提示。

from lit_nlp.examples.datasets import lm as lm_data

datasets = {
  'sample_prompts': lm_data.PromptExamples(
      lm_data.PromptExamples.SAMPLE_DATA_PATH
  ),
}

设置 LIT 界面

LIT 是一种交互式模型理解工具,可以对模型行为进行人机协同评估和探测。LIT 界面通过执行以下操作来促进这种互动:

  • 实时直观呈现数据集和模型输出,
  • 运行显著性方法以了解驱动模型行为的输入令牌;以及
  • 建立反事实来测试假设。

LIT 可以在同一个界面中实现所有这些操作,从而减少不同工具之间的切换摩擦。这对于提示工程等任务尤其有用,本 Codelab 稍后将重点介绍这一任务。

此界面布局可用于任何其他生成式语言模型。如果您对此处所列以外的功能感兴趣,可以在此处找到完整列表。

from lit_nlp.api import layout
modules = layout.LitModuleName

LM_SALIENCE_LAYOUT = layout.LitCanonicalLayout(
    left={
        'Data Table': [modules.DataTableModule],
        'Datapoint Editor': [modules.DatapointEditorModule],
    },
    upper={  # if 'lower' not specified, this fills the right side
        'Salience': [modules.LMSalienceModule],
    },
    layoutSettings=layout.LayoutSettings(leftWidth=40),
    description='Custom layout for language model salience.',
)

此单元用于初始化 LIT 服务器。这可能需要几秒钟的时间,因为它还会根据示例提示运行模型并缓存结果。

from lit_nlp import notebook as lit_notebook

lit_widget = lit_notebook.LitWidget(
    models=models,
    datasets=datasets,
    layouts={'default': LM_SALIENCE_LAYOUT},
    default_layout='default',
)

现在,您可以显示界面:

lit_widget.render(height=800)
<IPython.core.display.Javascript object>

您也可在新标签页中以整页形式打开 LIT。运行此单元并点击它显示的链接:

lit_widget.render(open_in_new_tab=True)
<IPython.core.display.Javascript object>

在 LIT 中分析 Gemma 的少数镜头提示

如今,提示既是艺术,又是科学,而 LIT 可以帮助您运用经验方式改进 Gemma 等大型语言模型的提示。接下来,您将看到一个示例,了解如何使用 LIT 探索 Gemma 的行为、预测潜在问题并提高其安全性。

识别复杂提示中的错误

对于基于 LLM 的优质原型和应用而言,两种最重要的提示技术是小样本提示(包括提示中期望行为的示例)和思维链(包括在 LLM 最终输出之前进行解释或推理的形式)。然而,创建有效的提示往往依然困难重重。

举例说明,看看帮助人们根据自己的口味来评估自己是否喜欢食物。初始原型思维链提示模板可能如下所示:

def analyze_menu_item_template(food_likes, food_dislikes, menu_item):
  return f"""Analyze a menu item in a restaurant.

## For example:

Taste-likes: I've a sweet-tooth
Taste-dislikes: Don't like onions or garlic
Suggestion: Onion soup
Analysis: it has cooked onions in it, which you don't like.
Recommendation: You have to try it.

Taste-likes: I've a sweet-tooth
Taste-dislikes: Don't like onions or garlic
Suggestion: Baguette maison au levain
Analysis: Home-made leaven bread in france is usually great
Recommendation: Likely good.

Taste-likes: I've a sweet-tooth
Taste-dislikes: Don't like onions or garlic
Suggestion: Macaron in france
Analysis: Sweet with many kinds of flavours
Recommendation: You have to try it.

## Now analyze one more example:

Taste-likes: {food_likes}
Taste-dislikes: {food_dislikes}
Suggestion: {menu_item}
Analysis:"""

您是否发现此提示存在问题?LIT 将帮助您使用 LM Salience 模块检查提示。

使用序列显著性进行调试

本模块突出显示模型在生成答案时遇到的提示部分。显著性在尽可能低的级别(即针对每个输入词元)进行计算,但 LIT 可以将词元显著性聚合为更易于解释的较大范围,例如行、句子或字词。如需详细了解显著性以及如何用它来识别意外偏差,请参阅 Saliency Explorable

我们首先为提示模板变量提供一个新的示例输入:

food_likes = """Cheese"""
food_dislikes = """Can't eat eggs"""
menu_item = """Quiche Lorraine"""

prompt = analyze_menu_item_template(food_likes, food_dislikes, menu_item)
print(prompt)

fewshot_mistake_example = {'prompt': prompt}  # you'll use this below
Analyze a menu item in a restaurant.

## For example:

Taste-likes: I've a sweet-tooth
Taste-dislikes: Don't like onions or garlic
Suggestion: Onion soup
Analysis: it has cooked onions in it, which you don't like.
Recommendation: You have to try it.

Taste-likes: I've a sweet-tooth
Taste-dislikes: Don't like onions or garlic
Suggestion: Baguette maison au levain
Analysis: Home-made leaven bread in france is usually great
Recommendation: Likely good.

Taste-likes: I've a sweet-tooth
Taste-dislikes: Don't like onions or garlic
Suggestion: Macaron in france
Analysis: Sweet with many kinds of flavours
Recommendation: You have to try it.

## Now analyze one more example:

Taste-likes: Cheese
Taste-dislikes: Can't eat eggs
Suggestion: Quiche Lorraine
Analysis:

如果您在上面的单元格或单独的标签页中打开了 LIT 界面,则可以使用 LIT 的数据点编辑器添加此提示:

1_Datapoint_editor.png

另一种方法是直接使用相关提示重新渲染 widget:

lit_widget.render(data=[fewshot_mistake_example])
<IPython.core.display.Javascript object>

请注意,出乎意料的模型补全效果:

Taste-likes: Cheese
Taste-dislikes: Can't eat eggs
Suggestion: Quiche Lorraine
Analysis: A savoury tart with cheese and eggs
Recommendation: You might not like it, but it's worth trying.

为什么模型建议你吃你明确表示不能吃的食物?

序列显著性有助于突出根本问题,我们的少样本示例中也有这方面的表现。在第一个示例中,分析部分 it has cooked onions in it, which you don't like 中的思维链推理与最终推荐 You have to try it 不匹配。

在 LM 显著性模块中,选择“Sentences”(句子),然后选择推荐行。现在,界面应如下所示:

3_few_shots_mistake..png

现在,将第一个示例中的“Recommendation”(推荐)更正为 Avoid,然后重试。LIT 在示例提示中预加载了此示例,因此您可以使用以下小实用函数来获取它:

def get_fewshot_example(source: str) -> str:
  for example in datasets['sample_prompts'].examples:
    if example['source'] == source:
      return example['prompt']
  raise ValueError(f'Source "{source}" not found in the dataset.')
lit_widget.render(data=[{'prompt': get_fewshot_example('fewshot-fixed')}])
<IPython.core.display.Javascript object>

现在,模型补全过程变为:

Taste-likes: Cheese
Taste-dislikes: Can't eat eggs
Suggestion: Quiche Lorraine
Analysis: This dish contains eggs and cheese, which you don't like.
Recommendation: Avoid.

从中我们吸取的一个重要教训是:提前进行原型设计有助于发现您可能未曾想到的风险,而语言模型容易出错,这意味着必须主动设计错误。如需了解关于这方面的进一步讨论,请参阅我们的“人 + AI 使用 AI 设计指南”

虽然更正后的少样本提示更好,但它仍然不太正确:它正确地告诉用户不要吃鸡蛋,但理由不正确,它说“他们不喜欢鸡蛋”,而事实上,用户已经声明不能吃鸡蛋。 下一部分将介绍如何改进。

测试假设以改进模型行为

借助 LIT,您可以在同一界面中测试对提示所做的更改。在本实例中,您将测试添加体制以改善模型的行为。宪法是指设计提示以及有助于指导模型的生成。最新的方法甚至可以以交互方式推导宪法原则。

让我们借助这一提示来进一步改进提示。在提示顶部添加一个包含生成原则的部分,该部分现在按以下方式开始:

Analyze a menu item in a restaurant.

* The analysis should be brief and to the point.
* The analysis and recommendation should both be clear about the suitability for someone with a specified dietary restriction.

## For example:

Taste-likes: I've a sweet-tooth
Taste-dislikes: Don't like onions or garlic
Suggestion: Onion soup
Analysis: it has cooked onions in it, which you don't like.
Recommendation: Avoid.

...

lit_widget.render(data=[{'prompt': get_fewshot_example('fewshot-constitution')}])
<IPython.core.display.Javascript object>

进行此更新后,可以重新运行该示例并观察完全不同的输出:

Taste-likes: Cheese
Taste-dislikes: Can't eat eggs
Suggestion: Quiche Lorraine
Analysis: This dish containts eggs, which you can't eat.
Recommendation: Not suitable for you.

然后,您可以重新检查提示显著性,以帮助了解发生这种变化的原因:

3_few_shot_constitution.png

请注意,此建议更安全。此外,“不适合您”这一原则受到以下原则的影响:根据饮食禁忌明确说明适宜性,以及相关分析(所谓的思维链)。这有助于提高生成输出的合理理由的信心。

让非技术团队参与模型探测和探索

可解释性需要团队合作,综合运用 XAI、政策、法律等领域的专业知识。

过去,在早期开发阶段与模型交互需要大量的技术专业知识,这使得一些协作者访问和探索模型变得更加困难。工具过去并不存在,无法让这些团队参与早期原型设计阶段。

通过 LIT,我们希望这种范式能够改变。正如您在此 Codelab 中看到的,LIT 的视觉媒介和交互式功能可以检查显著性并探索示例,帮助不同的利益相关方分享和传达发现。这样,您就可以招募更多团队成员,以进行模型探索、探测和调试。让他们接触这些技术方法可以加强他们对模型工作原理的理解。此外,在早期模型测试方面掌握更加多样化的专业知识也有助于发现可以改进的不良结果。

回顾

总结如下:

  • LIT 界面提供了一个用于执行交互式模型的界面,让用户能够直接生成输出并对“假设”场景进行测试。这对于测试不同的提示变体尤为有用。
  • LM 显著性模块以可视化方式表示显著性,并提供可控的数据粒度,以便您可以就以人为本的结构(例如句子和字词)进行沟通,而不是以模型为中心的结构(例如词元)进行沟通。

如果您在模型评估中发现有问题的示例,请将其放入 LIT 中进行调试。首先分析您认为在逻辑上与建模任务相关的最大合理内容单元,使用可视化来查看模型在何处正确或错误处理提示内容,然后深入分析较小的内容单元,进一步描述您看到的错误行为,以发现可能的修正。

最后:Lit 在不断改进!请在此处详细了解我们的功能并分享您的建议。

附录:LIT 如何计算序列显著性

LIT 会在多步流程中计算序列显著性。

  1. 给定输入字符串(提示以及模型的世代或“黄金”目标序列),将其标记化以用于模型输入。
  2. 通过将输入令牌向左滚动一个位置来计算“目标”序列。
  3. 提取 的嵌入并计算生成序列和“目标”序列之间的每个令牌的损失
  4. 遮盖损失以分离您想要解释的词元。
  5. 使用 tf.GradientTape.gradient() 函数计算输入嵌入相对于遮盖损失的梯度。
  6. 处理梯度,为每个输入词元给出一个分数。例如,在每个位置取梯度的 L2 范数。

附录:以编程方式计算显著性

您可以直接从 Python 计算显著性得分,步骤与 LIT 工具在内部运行的步骤相同。为此,您需要完成以下三个步骤:

  1. 准备示例并运行模型标记生成器,
  2. 准备一个掩码,用于选择要解释的(预测)词元,
  3. 调用显著性封装容器。

输入 LIT 的输入示例

{'prompt': 'Keras is a',
 'target': ' deep learning library for Python that provides a wide range of tools and functionalities for building, training, and evaluating deep learning models.\n\n**'}

关于调用规范的注意事项:标记生成器和显著性封装容器都使用了 LIT 的 Model API,其中 .predict() 函数接受示例列表(字典)并返回响应(字典)生成器。处理大型数据集或速度较慢的模型时,这种方式要灵活得多,但这意味着,如果您现在只想预测一个样本,则需要使用类似如下内容封装它:list(model.predict([example])[0]

获取令牌,以便选择说明目标

array(['<bos>', 'K', 'eras', '▁is', '▁a', '▁deep', '▁learning',
       '▁library', '▁for', '▁Python', '▁that', '▁provides', '▁a', '▁wide',
       '▁range', '▁of', '▁tools', '▁and', '▁functionalities', '▁for',
       '▁building', ',', '▁training', ',', '▁and', '▁evaluating', '▁deep',
       '▁learning', '▁models', '.', '\n\n', '**'], dtype='<U16')

如需计算显著性,您需要创建一个目标掩码,用于指定要解释的(预测)词元。目标掩码是一个与词元长度相同的数组,其中要说明的词元位置为 1。我们使用 ▁training▁evaluating 作为目标:

准备目标掩码

{'prompt': 'Keras is a',
 'target': ' deep learning library for Python that provides a wide range of tools and functionalities for building, training, and evaluating deep learning models.\n\n**',
 'target_mask': array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
        0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0.],
       dtype=float32)}

调用显著性模型

{'grad_l2': array([45.75, 36.75, 61, 5.40625, 4.09375, 5.625, 6.46875, 7.3125, 3.375,
        5.03125, 3.23438, 4.5625, 2.375, 3.40625, 2.75, 1.97656, 3.95312,
        3.42188, 14.125, 4.53125, 11.375, 12.625, 18.5, 4.5625, 6.5, 0, 0,
        0, 0, 0, 0, 0], dtype=bfloat16),
 'grad_dot_input': array([-4.03125, 3.04688, -7.03125, -0.800781, 0.769531, -0.679688,
        -0.304688, 2.04688, 0.275391, -1.25781, -0.376953, -0.0664062,
        -0.0405273, -0.357422, 0.355469, -0.145508, -0.333984, 0.0181885,
        -5.0625, 0.235352, -0.470703, 2.25, 3.90625, -0.199219, 0.929688,
        0, 0, 0, 0, 0, 0, 0], dtype=bfloat16),
 'tokens': array(['<bos>', 'K', 'eras', '▁is', '▁a', '▁deep', '▁learning',
        '▁library', '▁for', '▁Python', '▁that', '▁provides', '▁a', '▁wide',
        '▁range', '▁of', '▁tools', '▁and', '▁functionalities', '▁for',
        '▁building', ',', '▁training', ',', '▁and', '▁evaluating', '▁deep',
        '▁learning', '▁models', '.', '\n\n', '**'], dtype='<U16')}

成功了!grad_l2grad_dot_input 字段中的得分与 tokens 一致,并且与 LIT 界面中看到的分数相同。

请注意,最后几条得分为 0:由于我们的模型是从左到右的语言模型,因此目标 span 右侧的词元对预测没有影响。