解释问答 Transformer 模型

在此,我们演示如何解释问答模型的输出,该模型预测上下文文本的哪个范围包含给定问题的答案。

[1]:
import numpy as np
import torch
import transformers

import shap

# load the model
pmodel = transformers.pipeline("question-answering")
tokenized_qs = None  # variable to store the tokenized data


# define two predictions, one that outputs the logits for the range start,
# and the other for the range end
def f(questions, tokenized_qs, start):
    outs = []
    for q in questions:
        idx = np.argwhere(np.array(tokenized_qs["input_ids"]) == pmodel.tokenizer.sep_token_id)[
            0, 0
        ]  # this code assumes that there is only one sentence in data
        d = tokenized_qs.copy()
        d["input_ids"][:idx] = q[:idx]
        d["input_ids"][idx + 1 :] = q[idx + 1 :]
        out = pmodel.model.forward(**{k: torch.tensor(d[k]).reshape(1, -1) for k in d})
        logits = out.start_logits if start else out.end_logits
        outs.append(logits.reshape(-1).detach().numpy())
    return outs


def tokenize_data(data):
    for q in data:
        question, context = q.split("[SEP]")
        tokenized_data = pmodel.tokenizer(question, context)
    return tokenized_data  # this code assumes that there is only one sentence in data


def f_start(questions):
    return f(questions, tokenized_qs, True)


def f_end(questions):
    return f(questions, tokenized_qs, False)


# attach a dynamic output_names property to the models so we can plot the tokens at each output position
def out_names(inputs):
    question, context = inputs.split("[SEP]")
    d = pmodel.tokenizer(question, context)
    return [pmodel.tokenizer.decode([id]) for id in d["input_ids"]]


f_start.output_names = out_names
f_end.output_names = out_names

解释起始位置

在此,我们解释模型的起始范围预测。请注意,由于模型输出取决于模型输入的长度,因此重要的是我们传递模型的原生 tokenizer 进行掩码,这样当我们隐藏部分文本时,我们可以保留相同数量的 tokens,从而保持每个输出位置的相同含义。

[2]:
data = [
    "What is on the table?[SEP]When I got home today I saw my cat on the table, and my frog on the floor.",
]  # this code assumes that there is only one sentence in data
tokenized_qs = tokenize_data(data)

explainer_start = shap.Explainer(f_start, shap.maskers.Text(tokenizer=pmodel.tokenizer, output_type="ids"))
shap_values_start = explainer_start(data)

shap.plots.text(shap_values_start)
Partition explainer: 2it [00:32, 32.86s/it]


[0]
输出
[CLS]
什么
桌子
?
[SEP]
回家
今天
看到
我的
桌子
,
我的
青蛙
桌子
地板
.
[SEP]


-1-4-725-1.26347-1.26347base value-3.97193-3.97193f[CLS](inputs)0.498 What 0.214 on 0.032 home 0.026 got 0.022 the 0.013 today -0.937 ? -0.459 is -0.374 table -0.254 cat -0.25 on -0.233 on the floor -0.182 and -0.137 saw -0.126 , -0.108 . -0.094 my -0.086 frog -0.078 my -0.059 When -0.05 the -0.049 I -0.037 table -0.0 I
输入
0.0
0.498
什么
-0.459
0.214
0.022
桌子
-0.374
-0.937
?
0.0
[SEP]
-0.059
-0.049
0.026
回家
0.032
0.013
今天
-0.0
-0.137
看到
-0.078
我的
-0.254
-0.25
-0.05
桌子
-0.037
-0.126
,
-0.182
-0.094
我的
-0.086
青蛙
-0.233 / 3
在地板上
-0.108
.
0.0

解释结束位置

这与上面的过程相同,但现在我们解释结束 tokens。

[3]:
explainer_end = shap.Explainer(f_end, pmodel.tokenizer)
shap_values_end = explainer_end(data)

shap.plots.text(shap_values_end)


[0]
输出
[CLS]
什么
桌子
?
[SEP]
回家
今天
看到
我的
桌子
,
我的
青蛙
桌子
地板
.
[SEP]


-0-3-636-1.72717-1.72717base value-2.16449-2.16449f[CLS](inputs)0.424 What 0.233 got home 0.231 the 0.215 When I 0.21 today I saw my 0.129 0.069 on -0.635 ? -0.292 cat on -0.221 my frog -0.217 and -0.159 on the floor -0.126 table -0.119 , -0.093 is -0.068 the table -0.018 .
输入
0.129
0.424
什么
-0.093
0.069
0.231
桌子
-0.126
-0.635
?
0.0
[SEP]
0.215 / 2
当我
0.233 / 2
回家
0.21 / 4
时今天我看到了我的
-0.292 / 2
桌子上的猫
-0.068 / 2
在桌子上
-0.119
,
-0.217
-0.221 / 2
我的青蛙
-0.159 / 3
在地板上
-0.018
.
0.0

解释匹配函数

在上面的示例中,我们直接解释了来自模型的输出 logits。这要求我们确保仅以保留长度的方式扰动输入,以免更改输出 logits 的含义。一种不太详细但更灵活的方法是仅对模型产生的特定答案进行评分。

[4]:
def make_answer_scorer(answers):
    def f(questions):
        out = []
        for q in questions:
            question, context = q.split("[SEP]")
            results = pmodel(question, context, topk=20)
            values = []
            for answer in answers:
                value = 0
                for result in results:
                    if result["answer"] == answer:
                        value = result["score"]
                        break
                values.append(value)
            out.append(values)
        return out

    f.output_names = answers
    return f


f_answers = make_answer_scorer(["my cat", "cat", "my frog"])
explainer_answers = shap.Explainer(f_answers, pmodel.tokenizer)
shap_values_answers = explainer_answers(data)

shap.plots.text(shap_values_answers)


[0]
输出
我的猫
我的青蛙


0.20.10-0.10.30.40.500base value0.4980790.498079fmy cat(inputs)0.14 table 0.097 my 0.056 What 0.049 table 0.049 the 0.042 cat 0.034 on the floor 0.026 on 0.015 saw 0.013 the 0.013 When I got home 0.013 ? 0.008 today I 0.003 , 0.002 on 0.001 0.0 . -0.042 my frog -0.013 is -0.008 and
输入
0.001
0.056
什么
-0.013
0.002
0.049
桌子
0.14
0.013
?
0.0
[SEP]
0.013 / 4
当我回家
0.008 / 2
时今天我
0.015
看到
0.097
我的
0.042
0.026
0.013
桌子
0.049
0.003
,
-0.008
-0.042 / 2
我的青蛙
0.034 / 3
在地板上
0.0
.
0.0

有更多有帮助的示例的想法吗? 欢迎提交拉取请求以添加到此文档笔记本!