ChatGPT实在是太火了,以至于带动了整个NLP(Natural Language Processing 自然语言生成)的研究和认知度大受关注。在整个NLP的研究和应用中,除了最上层的仅仅是应用这些类似GPT工具的普通用户,和最底层的模型算法开发研究人员,还有很多像我这样中间层的,喜欢基于机器学习的模型做一些应用开发,在需要的场景里为自己定制服务。
在这个过程中,了解所用模型的内在机理会大大帮助我们理解语句形成预测上的原因,还能在早期发现模型的缺陷或潜在的一些偏见及固化思维。过去在自然语言处理可解释性(Interpretability)方面的工作主要集中在分类(Classification)上,而在很大程度上忽视了生成(generation)方向的解析,主要原因还是缺乏专用工具。 最近看了几篇论文,分享一下Inseq,这是一个 Python 库,用于对序列生成模型(Sequence Generation Models)的进行解释并提供直观的显示。 Inseq 支持对流行的Transformers 架构的模型内部信息和特征重要性进行直观的打分和提取。
本文中,除了介绍Inseq基本用法,我还将举一个利用Inseq的解释能力来检测AI的性别偏见的demo。
Inseq
Inseq作为一种比较新的开源Python 库,它提供了序列生成模型的可解释性分析(interpretability analysis)。
下图展示了 Inseq 在某个AI文本补全的过程中(任务为“To innovate one should”, AI生成“think outside the box”.),每个特征(这里可以理解为词语)为整个生成语句中的每个元素的重要性贡献程度,以及该词出现的概率,以热图形式展现给用户。
Inseq库安装
安装Inseq的pip命令如下:
!pip install inseq
# install extras for visualization in Jupyter Notebooks
!pip install inseq[notebook,datasets]
Inseq需要在Jupyter Notebook环境下运行其可视化组件,因此请务必安装该环境,可以通过直接安装Anaconda Navigator完成,在此下载:Anaconda | The World's Most Popular Data Science Platform
如果可以访问谷歌,也可以使用免费的Google Colab Notebook进行开发,省去了对本地环境的影响。
AI语句翻译分析
用几行 Python 生成快速分析图非常简单。 导入Inseq模块后,加载模型,并为后续的输入选择合适的归因(Attribution method)。 在此示例中,我们使用英语到西班牙语的翻译模型 Helsinki-NLP/opus-mt-en-es,并选择 integrated_gradients 作为我们的Attribution method。
import inseq
model = inseq.load_model("Helsinki-NLP/opus-mt-en-es", "integrated_gradients")
接下来我们提供一段英语句子,让其生成翻译结果和解析出来的热图。
out = model.attribute(
"It's not surprising that bias is always a more serious issue in NLP than wrong information provided.",
n_steps=100
)
out.show()
输出的热图如下所示:
X轴方向是输入的英语句子,Y轴方向是生成的西班牙语句子,可以看到每个输入的单词对于生成的单词都有对应的分数,代表了其对生成的具体内容的重要性。
AI文本补全的分析
我们展示的下一个示例是使用 GPT-2 模型和 gradient_shap 归因法的文本补全。
这次我提供开头词“To introduce my”,让 GPT-2 用 9 个Token(可以大致理解为单词)完成句子:
import inseq
model = inseq.load_model("gpt2", "gradient_shap")
model.attribute(
"To introduce my",
generation_args={"max_new_tokens": 9},
n_steps=150,
internal_batch_size=50
).show()
输出的热图如下所示:
从结果中,我们可以看到 Inseq 为我们提供了每个有助于AI生成语句“new book, The Art of The Deal,”的属性标记(我的输入词和生成的词)的分数,通过这些分数,我们可以大致对该NLP 模型的逻辑有了了解。
AI模型的偏见分析
知道了Inseq如何使用后,我们就可以用它来做一下关于AI偏见的验证。这个是近期发布在HuggingFace平台上的叫做Bias in MT的有趣应用:
https://huggingface.co/spaces/oskarvanderwal/MT-bias-demo
这个应用的目的是评估当输入标记不包含对性别的明确定义的时候,AI如何生成与性别相关的句子。这其实是我们日常生活中普遍存在的一种性别偏见,请在脑海中快速想象他们是男是女:
护士
科学家
工程师
大厨
幼儿园老师
公司CEO
要从脑子里去掉这些固有思维其实是很难的,同样地,AI也有这个问题,因为他们训练的大量语料难免包含我们人类创造的那些偏见思维。
该Demo巧妙地选择匈牙利语作为源标记,而英语作为目标翻译标记,因为匈牙利语没有像“he”和“she”这样的性别词,两者都为“ő”。 这样的话,我们就可以尝试输入“ő”+职业,看看AI是如何将这些职业解读为男性或女性的。
从Simple translation的demo页面,你可以选择Occupation in Hungarian和Target language,这里我选择“vezérigazgató”意思是CEO,“en”代表Target Language为英语,然后点击Translate & Attribute按钮:
我们观察到:
该模型自然地将匈牙利语句子翻译成“他是CEO”。显着性热图显示,当“ő”不那么重要,因为它不能表示性别时,属性标记“CEO”在翻译中生成“He”词的重要性非常高,也就是AI很自然地认为CEO应该是男的。
当你在使用该模型进行翻译工作时,很可能需要在正式发布文本之前,手动介入修改以避免这种偏见产生。
下面是这个Demo的处理逻辑代码:
import inseq
from inseq.data.aggregator import AggregatorPipeline, SubwordAggregator, SequenceAttributionAggregator, PairAggregator
model = inseq.load_model("Helsinki-NLP/opus-mt-hu-en", "integrated_gradients")
out = model.attribute(
"ő vezérigazgató",
n_steps=150
)
squeezesum = AggregatorPipeline([SubwordAggregator, SequenceAttributionAggregator])
out.show(return_html=True, display=True, aggregator=squeezesum)
然后,如果你对偏见或其他方面的评估有更多想法时,可以将 Helsinki-NLP/opus-mt-hu-en 替换为你要评估的模型,将 integrated_gradients 替换为其他支持的归因方法。
该应用程序还提供了一个称为Contrastive Pair(对比法)的功能块。 这是为了评估如果手动将目标翻译“ő”从“He”更改为“She”,概率有多大(或多小)。 该功能是基于Inseq 中称为“对比特征归因”的高级技术。
从“He's”到“She's”的结果为负数,说明该模型为“CEO”场景选择“男性”而不是“女性”是非常自然地。
与第一个Demo一样,你也可以尝试更改下面的 Python 代码,通过替换模型、归因方法、源词和目标词对,来评估自己感兴趣的其他对比标记。
import inseq
from inseq.data.aggregator import AggregatorPipeline, SubwordAggregator, SequenceAttributionAggregator, PairAggregator
model = inseq.load_model("Helsinki-NLP/opus-mt-hu-en", "integrated_gradients")
source = "ő vezérigazgató"
target = model.generate(source)[0]
out = model.attribute(
[
source,
source,
],
[
target.replace("She", "He"),
target.replace("He", "She"),
],
n_steps=150,
return_convergence_delta=False,
attribute_target=False,
step_scores=["probability"],
internal_batch_size=100,
include_eos_baseline=False,
)
squeezesum = AggregatorPipeline([SubwordAggregator, SequenceAttributionAggregator])
masculine = out.sequence_attributions[0].aggregate(aggregator=squeezesum)
feminine = out.sequence_attributions[1].aggregate(aggregator=squeezesum)
html = masculine.show(aggregator=PairAggregator, paired_attr=feminine, return_html=True, display=True)