Transformers 获得单词而不是标记的命名实体预测

Transformers get named entity prediction for words instead of tokens

这是一个非常基本的问题,但我花了好几个小时才找到答案。我使用 Hugginface 变压器构建了 NER。

说我输入了句子

input = "Damien Hirst oil in canvas"

我把它标记化得到

tokenizer = transformers.BertTokenizer.from_pretrained('bert-base-uncased')
tokenized = tokenizer.encode(input) #[101, 12587, 7632, 12096, 3514, 1999, 10683, 102]

将标记化的句子输入模型以获得标记的预测标签

['B-ARTIST' 'B-ARTIST' 'I-ARTIST' 'I-ARTIST' 'B-MEDIUM' 'I-MEDIUM'
 'I-MEDIUM' 'B-ARTIST']

prediction 作为模型的输出。它将标签分配给不同的令牌。

如何重新组合这些数据以获得单词标签而不是标记?所以我会知道

"Damien Hirst" = ARTIST
"Oil in canvas" = MEDIUM

这里有两个问题。

注释令牌分类

一种常见的顺序标记,尤其是在命名实体识别中,遵循以下方案:开头带有标记 X 的标记序列得到 B-X 并且在标签重置时它得到 I-X。 问题是大多数带注释的数据集都是用 space 标记的!例如:

[CSL]  O
Damien  B-ARTIST
Hirst  I-ARTIST
oil  B-MEDIUM
in  I-MEDIUM
canvas  I-MEDIUM
[SEP]  O

其中 O 表示它不是 named-entity,B-ARTIST 是标记为 ARTIST 的标记序列的开头,而 I-ARTIST 是在序列内部 - MEDIUM.

的类似模式

在我发布这个答案的那一刻,这里的 huggingface 文档中有一个 NER 的例子: https://huggingface.co/transformers/usage.html#named-entity-recognition

该示例并未完全回答此处的问题,但可以添加一些说明。该示例中命名实体标签的类似样式如下:

label_list = [
    "O", # not a named entity
    "B-ARTIST", # beginning of an artist name
    "I-ARTIST", # an artist name
    "B-MEDIUM", # beginning of a medium name
    "I-MEDIUM", # a medium name
]

适应标记化

综上所述,关于注释模式,BERT 和其他几个模型具有不同的标记化模型。因此,我们必须调整这两种标记化。 在这种情况下 bert-base-uncased,预期的结果是这样的:

damien  B-ARTIST
hi  I-ARTIST
##rst  I-ARTIST
oil  B-MEDIUM
in  I-MEDIUM
canvas  I-MEDIUM

为了完成这项工作,您可以遍历原始注释中的每个标记,然后对其进行标记化并再次添加其标签:

tokens_old = ['Damien', 'Hirst', 'oil', 'in', 'canvas']
labels_old = ["B-ARTIST", "I-ARTIST", "B-MEDIUM", "I-MEDIUM", "I-MEDIUM"]
label2id = {label: idx for idx, label in enumerate(label_list)}

tokens, labels = zip(*[
   (token, label)
   for token_old, label in zip(tokens_old, labels_old)
   for token in tokenizer.tokenize(token_old)
])

当您在tokens中添加[CLS][SEP]时,它们的标签"O"必须添加到labels

使用上面的代码,可能会出现这样的情况,即当开始单词拆分成片段时,像 B-ARTIST 这样的开始标记会重复出现。根据 huggingface 文档中的描述,您可以将这些标签编码为 -100 以被忽略: https://huggingface.co/transformers/custom_datasets.html#token-classification-with-w-nut-emerging-entities

像这样的东西应该可以工作:

tokens, labels = zip(*[
   (token, label2id[label] if (label[:2] != "B-" or i == 0) else -100)
   for token_old, label in zip(tokens_old, labels_old)
   for i, token in enumerate(tokenizer.tokenize(token_old))
])