本文中,小猴子和大家一起学习如何预训练 BERT 模型来识别文本中每个单词的实体。 在处理 NLP 问题时,BERT 经常作为一种机器学习模型出现,我们可以依靠它的性能。事实上,它已经对超过 2,500M 的单词进行了预训练,并且其从单词序列中学习信息的双向特性使其成为一个强大的模型。
小猴子之前写过关于如何利用 BERT 进行文本分类的文章:保姆级教程,用PyTorch和BERT进行文本分类 ,在本文中,我们将更多地关注如何将 BERT 用于命名实体识别 (NER) 任务。
什么是NER? NER 是 NLP 中的一项基础有很重要的任务,正所谓流水的NLP,铁打的NER。 NER(实体识别任务)指的是识别出文本中的具有特定意义的短语或词。实体可以是单个词,甚至可以是指代同一类别的一组词,通常包括人名、地名、组织名、机构名和时间等。
例如,假设我们下面的句子,我们想从这个句子中提取有关地 名的信息。
NER 任务的第一步是检测实体。这可以是指代同一类别的一个词或一组词。举个例子:
'北京天安门'
由两个词组成的实体,但它们指的是同一类别。为了确保 BERT 模型知道一个实体可以是单个词或一组词,那么我们需要通过所谓的Inside-Outside-Beginning (IOB) 标记在训练数据上提供有关实体开始和结束的信息。
识别到实体后,NER 任务的下一步是对实体进行分类。根据我们的用例,实体的类别可以是任何东西。以下是实体类别的示例:
命名实体识别的数据标注方式 NER是一种序列标注问题,因此他们的数据标注方式也遵照序列标注问题的方式,主要是BIO和BIOES两种。这里直接介绍BIOES:
BERT 用于 NER 运用 BERT 解决与 NLP 相关的任务,是非常方便的。
如果你还不熟悉 BERT,我建议你在阅读本文之前阅读我之前关于使用 BERT 进行文本分类的文章。在那里,详细介绍了有关 BERT 模型架构、模型期望的输入数据类型以及将从模型中获得的输出的信息。
BERT模型在文本分类和 NER 问题中的区别在于如何设置模型的输出。对于文本分类问题,仅使用特殊 [CLS] token 的 Embedding 向量输出。而 NER 任务中,需要使用所有 token 的 Embedding向量输出,希望模型预测每个 token 的实体,则通过使用所有token 的 E mbedding向量输出。
关于数据集 在本文中使用的数据集是 CoNLL-2003 数据集,它是专门用于 NER 任务的数据集。你可以通过下面的链接下载 Kaggle 上的数据。
NER数据命名实体识别数据
import pandas as pd df = pd.read_csv('ner.csv' ) df.head()
如图所示,有一个由文本和标签组成的数据框。标签对应于文本中每个单词的实体类别。
总共有9个实体类别,分别是:
看一下数据集上可用的唯一标签:
# 根据空格拆分标签,并将它们转换为列表 labels = [i.split() for i in df['labels' ].values.tolist()]# 检查数据集中有多少标签 unique_labels = set()for lb in labels: [unique_labels.add(i) for i in lb if i not in unique_labels] print(unique_labels)
{'B-tim', 'B-art', 'I-art', 'O', 'I-gpe', 'I-per', 'I-nat', 'I-geo', 'B-eve', 'B-org', 'B-gpe', 'I-eve', 'B-per', 'I-tim', 'B-nat', 'B-geo', 'I-org'}
将每个标签映射到它的id表示,反之亦然:
labels_to_ids = {k: v for v, k in enumerate(sorted(unique_labels))} ids_to_labels = {v: k for v, k in enumerate(sorted(unique_labels))} print(labels_to_ids)
{'B-art': 0, 'B-eve': 1, 'B-geo': 2, 'B-gpe': 3, 'B-nat': 4, 'B-org': 5, 'B-per': 6, 'B-tim': 7, 'I-art': 8, 'I-eve': 9, 'I-geo': 10, 'I-gpe': 11, 'I-nat': 12, 'I-org': 13, 'I-per': 14, 'I-tim': 15, 'O': 16}
注意到,每个实体类别都以字母I
或开头B
。这对应于前面提到的 IOB 标记。I
表示 Intermediate 以及 B
表示 Beginning 。看一下下面的句子进一步了解 IOB 标记的概念。
'Kevin'有B-pers
标签,它是个人实体的开始 'Durant'有I-pers
标签,它是个人实体的延续 'Brooklyn'有B-org
标签,它是一个组织实体的开始 'Nets' 有I-org
标签,它是组织实体的延续 数据预处理 在能够使用 BERT 模型对 token 级别的实体进行分类之前,需要先进行数据预处理,包括两部分:tokenization 和调整标签以匹配 tokenization。
Tokenization 使用 HuggingFace 的预训练 BERT 基础模型中的类BertTokenizerFast
,可以轻松实现 tokenization。
为了给你一个例子,BERT 标记器是如何工作的,让我们看一下我们数据集中的一个文本:
text = df['text' ].values.tolist() example = text[36 ] print(example)
'Prime Minister Geir Haarde has refused to resign or call for early elections.'
对上面的文本进行标记BertTokenizerFast
非常简单:
from transformers import BertTokenizerFast tokenizer = BertTokenizerFast.from_pretrained('bert-base-cased' ) text_tokenized = tokenizer(example, padding='max_length' , max_length=512 , truncation=True , return_tensors='pt' )
从上面的BertTokenizerFast
类调用tokenizer
方法时,提供了几个参数:
padding
: 用特殊的 [PAD] token将序列填充到指定的最大长度(BERT 模型的最大序列长度为 512)。truncation
: 这是一个布尔值。如果将该值设置为 True,则不会使用超过最大长度的token。return_tensors
:返回的张量类型,取决于我们使用的机器学习框架。由于我们使用的是 PyTorch,所以我们使用pt
。以下是标记化过程的输出:
print(text_tokenized)
上下滑动查看更多
{'input_ids' : tensor([[ 101, 3460, 2110, 144, 6851, 1197, 11679, 2881, 1162, 1144, 3347, 1106, 13133, 1137, 1840, 1111, 1346, 3212, 119, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ............................. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), 'token_type_ids' : tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, .................................. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), 'attention_mask' : tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, .................................. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])}
从上面结果可见:从Tokenization输出是一个字典,其中包含三个变量:
input_ids
:序列中标记的 id 表示。在 BERT 中,101 为特殊 [CLS] token 保留的id,id 102 为特殊**[SEP]** token保留的id,id 0 为**[PAD]** token保留的id。token_type_ids
:标识一个token所属的序列。由于每个文本只有一个序列,因此token_type_ids
的所有值都将为 0。attention_mask
:标识一个token是真正的 token 还是 padding 得到的token。如果它是一个真正的token,则该值为 1,如果它是一个 [PAD] token,则该值为 0。综上所述,可以使用'decode'
方法,从上面的'input_ids'
中将这些id
解码回原始序列,如下所示:
print(tokenizer.decode(text_tokenized.input_ids[0 ]))
'[CLS] Prime Minister Geir Haarde has refused to resign or call for early elections. [SEP] [PAD] [PAD] [PAD] [PAD] ... [PAD]'
在实现decode
方法后,我们得到了原始序列,并且是添加了来自 BERT 的特殊标记,例如序列开头的 [CLS] token,序列末尾的 [SEP] token,以及为了满足要求的最大长度 512 而设置的一堆 [PAD] token。
在 Tokenization 之后,需要进行调整每个 token 的标签。
Tokenization后调整标签 因为序列的长度不再匹配原始标签的长度,因此这是在Tokenization之后需要做的一个非常重要的步骤。
BERT 分词器在底层使用了所谓的 word-piece tokenizer,它是一个子词分词器。这意味着 BERT tokenizer 可能会将一个词拆分为一个或多个有意义的子词。
例如,还是使用上面的序列作为例子:
上面的序列总共有 13 个标记,因此它也有 13 个标签。但是,在 BERT 标记化之后,我们得到以下结果:
print(tokenizer.convert_ids_to_tokens(text_tokenized['input_ids' ][0 ]))
['[CLS]', 'Prime', 'Minister', 'G', '##ei', '##r', 'Ha', '##ard', '##e', 'has', 'refused', 'to', 'resign', 'or', 'call', 'for', 'early', 'elections', '.', '[SEP]', '[PAD]', '[PAD]', '[PAD]','[PAD]', ... , '[PAD]']
在Tokenization之后需要解决两个问题:
添加来自 BERT 的特殊 token,例如 [CLS]、 [SEP] 和 [PAD] 词片Tokenization将不常见的词拆分为它们的子词,例如上面示例中的' Geir '
和' Haarde '
。这种词片Tokenization有助于 BERT 模型学习相关词的语义。
而这种词片Tokenization和 BERT 添加特殊token的结果是Tokenization后的序列长度不再匹配初始标签的长度。
从上面的例子来看,现在Tokenization后的序列中总共有 512 个token,而标签的长度仍然和以前一样。此外,序列中的第一个token不再是单词' Prime '
,而是新添加的**[CLS]** token,因此我们也需要调整标签,以达到一一对应的结果。使其与标记化后的序列具有相同的长度。
如何实现标签调整呢?我们可以利用word_ids
标记化结果中的方法如下:
word_ids = text_tokenized.word_ids() print(tokenizer.convert_ids_to_tokens(text_tokenized['input_ids' ][0 ])) print(word_ids)
['[CLS]', 'Prime', 'Minister', 'G', '##ei', '##r', 'Ha', '##ard', '##e', 'has', 'refused', 'to', 'resign', 'or', 'call', 'for', 'early', 'elections', '.', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', ..., '[PAD]'] [None, 0, 1, 2, 2, 2, 3, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, None, None, None, None, ..., None]
从上面可以看出,每个拆分的 token 共享相同的 word_ids
,其中来自 BERT 的特殊 token,例如 [CLS]、 [SEP] 和 [PAD] 都没有特定word_ids
的,结果是None
。
通过这些 word_ids
,并使用以下两种方法来调整标签的长度:
只为每个拆分token的第一个子词提供一个标签。子词的延续将简单地用'-100'
作为标签。所有没有word_ids
的token也将标为 '-100'
。 在属于同一 token 的所有子词中提供相同的标签。所有没有word_ids
的token都将标为 '-100'
。 下面的函数演示上面定义。
def align_label_example (tokenized_input, labels) : word_ids = tokenized_input.word_ids() previous_word_idx = None label_ids = [] for word_idx in word_ids: if word_idx is None : label_ids.append(-100 ) elif word_idx != previous_word_idx: try : label_ids.append(labels_to_ids[labels[word_idx]]) except : label_ids.append(-100 ) else : label_ids.append(labels_to_ids[labels[word_idx]] if label_all_tokens else -100 ) previous_word_idx = word_idx return label_ids
如果要应用第一种方法,设置label_all_tokens
为 False。如果要应用第二种方法,设置label_all_tokens
为 True,如以下代码所示:
设置label_all_tokens=True
label = labels[36 ] label_all_tokens = True new_label = align_label_example(text_tokenized, label) print(new_label) print(tokenizer.convert_ids_to_tokens(text_tokenized['input_ids' ][0 ]))
[-100, 16, 16, 6, 6, 6, 14, 14, 14, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, -100, -100, -100, -100, ..., -100] ['[CLS]', 'Prime', 'Minister', 'G', '##ei', '##r', 'Ha', '##ard', '##e', 'has', 'refused', 'to', 'resign', 'or', 'call', 'for', 'early', 'elections', '.', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', ..., '[PAD]']
设置label_all_tokens=False
label_all_tokens = False new_label = align_label_example(text_tokenized, label) print(new_label) print(tokenizer.convert_ids_to_tokens(text_tokenized['input_ids' ][0 ]))
[-100, 16, 16, 6, -100, -100, 14, -100, -100, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, -100, -100, ..., -100] ['[CLS]', 'Prime', 'Minister', 'G', '##ei', '##r', 'Ha', '##ard', '##e', 'has', 'refused', 'to', 'resign', 'or', 'call', 'for', 'early', 'elections', '.', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', ..., '[PAD]']
在本文的其余部分,我们将实现第一个方法,其中我们将只为每个token中的第一个子词提供一个标签并设置label_all_tokens=False
。
Dataset类 在为 NER 任务训练 BERT 模型之前,需要创建一个Dataset类来批量生成和获取数据。
import torchdef align_label (texts, labels) : # 首先tokenizer输入文本 tokenized_inputs = tokenizer(texts, padding='max_length' , max_length=512 , truncation=True ) # 获取word_ids word_ids = tokenized_inputs.word_ids() previous_word_idx = None label_ids = [] # 采用上述的第一中方法来调整标签,使得标签与输入数据对其。 for word_idx in word_ids: # 如果token不在word_ids内,则用 “-100” 填充 if word_idx is None : label_ids.append(-100 ) # 如果token在word_ids内,且word_idx不为None,则从labels_to_ids获取label id elif word_idx != previous_word_idx: try : label_ids.append(labels_to_ids[labels[word_idx]]) except : label_ids.append(-100 ) # 如果token在word_ids内,且word_idx为None else : try : label_ids.append(labels_to_ids[labels[word_idx]] if label_all_tokens else -100 ) except : label_ids.append(-100 ) previous_word_idx = word_idx return label_ids# 构建自己的数据集类 class DataSequence (torch.utils.data.Dataset) : def __init__ (self, df) : # 根据空格拆分labels lb = [i.split() for i in df['labels' ].values.tolist()] # tokenizer 向量化文本 txt = df['text' ].values.tolist() self.texts = [tokenizer(str(i), padding='max_length' , max_length = 512 , truncation=True , return_tensors='pt' ) for i in txt] # 对齐标签 self.labels = [align_label(i,j) for i,j in zip(txt, lb)] def __len__ (self) : return len(self.labels) def get_batch_data (self, idx) : return self.texts[idx] def get_batch_labels (self, idx) : return torch.LongTensor(self.labels[idx]) def __getitem__ (self, idx) : batch_data = self.get_batch_data(idx) batch_labels = self.get_batch_labels(idx) return batch_data, batch_labels
在上面的代码中,在函数 __init__
中调用带有 tokenizer
变量的 BertTokenizerFast
类来标记输入文本,并 align_label
在Tokenization之后调整标签。 接下来,我们将数据随机拆分为训练集、验证集和测试集。由于数据总数为 47959,出于演示目的和加快训练过程,这里将只选取其中的 1000 个。当然,你也可以将所有数据用于模型训练。
import numpy as np df = df[0 :1000 ] df_train, df_val, df_test = np.split(df.sample(frac=1 , random_state=42 ), [int(.8 * len(df)), int(.9 * len(df))])
构建模型 在本文中,使用来自 HuggingFace 的预训练 BERT 基础模型。既然我们要在token级别对文本进行分类,那么需要使用 BertForTokenClassification
类。
BertForTokenClassification
类是一个包装 BERT 模型并在 BERT 模型之上添加线性层的模型,将充当token级分类器。
from transformers import BertForTokenClassificationclass BertModel (torch.nn.Module) : def __init__ (self) : super(BertModel, self).__init__() self.bert = BertForTokenClassification.from_pretrained( 'bert-base-cased' , num_labels=len(unique_labels)) def forward (self, input_id, mask, label) : output = self.bert(input_ids=input_id, attention_mask=mask, labels=label, return_dict=False ) return output
在上面的代码中,首先实例化模型并将每个token分类器的输出设置为等于我们数据集上唯一实体的数量(在我们的例子是 17)。
训练模型 这里使用标准的 PyTorch 训练循环训练 BERT 模型,如下所示:
上下滑动查看更多源码
def train_loop (model, df_train, df_val) : # 定义训练和验证集数据 train_dataset = DataSequence(df_train) val_dataset = DataSequence(df_val) # 批量获取训练和验证集数据 train_dataloader = DataLoader(train_dataset, num_workers=4 , batch_size=1 , shuffle=True ) val_dataloader = DataLoader(val_dataset, num_workers=4 , batch_size=1 ) # 判断是否使用GPU,如果有,尽量使用,可以加快训练速度 use_cuda = torch.cuda.is_available() device = torch.device('cuda' if use_cuda else 'cpu' ) # 定义优化器 optimizer = SGD(model.parameters(), lr=LEARNING_RATE) if use_cuda: model = model.cuda() # 开始训练循环 best_acc = 0 best_loss = 1000 for epoch_num in range(EPOCHS): total_acc_train = 0 total_loss_train = 0 # 训练模型 model.train() # 按批量循环训练模型 for train_data, train_label in tqdm(train_dataloader): # 从train_data中获取mask和input_id train_label = train_label[0 ].to(device) mask = train_data['attention_mask' ][0 ].to(device) input_id = train_data['input_ids' ][0 ].to(device) # 梯度清零!! optimizer.zero_grad() # 输入模型训练结果:损失及分类概率 loss, logits = model(input_id, mask, train_label) # 过滤掉特殊token及padding的token logits_clean = logits[0 ][train_label != -100 ] label_clean = train_label[train_label != -100 ] # 获取最大概率值 predictions = logits_clean.argmax(dim=1 ) # 计算准确率 acc = (predictions == label_clean).float().mean() total_acc_train += acc total_loss_train += loss.item() # 反向传递 loss.backward() # 参数更新 optimizer.step() # 模型评估 model.eval() total_acc_val = 0 total_loss_val = 0 for val_data, val_label in val_dataloader: # 批量获取验证数据 val_label = val_label[0 ].to(device) mask = val_data['attention_mask' ][0 ].to(device) input_id = val_data['input_ids' ][0 ].to(device) # 输出模型预测结果 loss, logits = model(input_id, mask, val_label) # 清楚无效token对应的结果 logits_clean = logits[0 ][val_label != -100 ] label_clean = val_label[val_label != -100 ] # 获取概率值最大的预测 predictions = logits_clean.argmax(dim=1 ) # 计算精度 acc = (predictions == label_clean).float().mean() total_acc_val += acc total_loss_val += loss.item() val_accuracy = total_acc_val / len(df_val) val_loss = total_loss_val / len(df_val) print( f'''Epochs: {epoch_num + 1 } | Loss: {total_loss_train / len(df_train): .3 f} | Accuracy: {total_acc_train / len(df_train): .3 f} | Val_Loss: {total_loss_val / len(df_val): .3 f} | Accuracy: {total_acc_val / len(df_val): .3 f} ''' ) LEARNING_RATE = 1e-2 EPOCHS = 5 model = BertModel() train_loop(model, df_train, df_val)
在上面的训练循环中,只训练了 5 个 epoch 的模型,然后使用 SGD 作为优化器。使用BertForTokenClassification
类计算每个批次的损失。
注意,有一个重要的步骤!在训练循环的每个 epoch 中,在模型预测之后,需要忽略所有以 '-100' 作为标签的token。
下面是我们训练 BERT 模型 5 个 epoch 后的训练输出示例:
当你训练自己的 BERT 模型时,你将看到的输出可能会有所不同,因为训练过程中存在随机性。
大家想想,如何提高我们模型的性能。例如在我们有一个数据不平衡问题,因为有很多带有'O'标签的token。可以通过在训练过程中添加不同类的权重来改进我们的模型。
此外还可以尝试不同的优化器,例如具有权重衰减正则化的 Adam 优化器。
评估模型 现在已经训练了的模型,接下来可以使用测试数据集来测试模型的性能。评估代码与验证代码类似,这里不做详细注释。
def evaluate (model, df_test) : # 定义测试数据 test_dataset = DataSequence(df_test) # 批量获取测试数据 test_dataloader = DataLoader(test_dataset, num_workers=4 , batch_size=1 ) # 使用GPU use_cuda = torch.cuda.is_available() device = torch.device('cuda' if use_cuda else 'cpu' ) if use_cuda: model = model.cuda() total_acc_test = 0.0 for test_data, test_label in test_dataloader: test_label = test_label[0 ].to(device) mask = test_data['attention_mask' ][0 ].to(device) input_id = test_data['input_ids' ][0 ].to(device) loss, logits = model(input_id, mask, test_label.long()) logits_clean = logits[0 ][test_label != -100 ] label_clean = test_label[test_label != -100 ] predictions = logits_clean.argmax(dim=1 ) acc = (predictions == label_clean).float().mean() total_acc_test += acc val_accuracy = total_acc_test / len(df_test) print(f'Test Accuracy: {total_acc_test / len(df_test): .3 f} ' ) evaluate(model, df_test)
就本案例而言,经过训练的模型在测试集上平均达到了 92.22% 的准确率。根据不同的任务或评价标准,可以选用 F1 分数、精度或召回率。
或者可以使用经过训练的模型来预测文本或句子中每个单词的实体,代码如下:
上下滑动查看更多源码
def align_word_ids (texts) : tokenized_inputs = tokenizer(texts, padding='max_length' , max_length=512 , truncation=True ) word_ids = tokenized_inputs.word_ids() previous_word_idx = None label_ids = [] for word_idx in word_ids: if word_idx is None : label_ids.append(-100 ) elif word_idx != previous_word_idx: try : label_ids.append(1 ) except : label_ids.append(-100 ) else : try : label_ids.append(1 if label_all_tokens else -100 ) except : label_ids.append(-100 ) previous_word_idx = word_idx return label_idsdef evaluate_one_text (model, sentence) : use_cuda = torch.cuda.is_available() device = torch.device('cuda' if use_cuda else 'cpu' ) if use_cuda: model = model.cuda() text = tokenizer(sentence, padding='max_length' , max_length = 512 , truncation=True , return_tensors='pt' ) mask = text['attention_mask' ][0 ].unsqueeze(0 ).to(device) input_id = text['input_ids' ][0 ].unsqueeze(0 ).to(device) label_ids = torch.Tensor(align_word_ids(sentence)).unsqueeze(0 ).to(device) logits = model(input_id, mask, None ) logits_clean = logits[0 ][label_ids != -100 ] predictions = logits_clean.argmax(dim=1 ).tolist() prediction_label = [ids_to_labels[i] for i in predictions] print(sentence) print(prediction_label) evaluate_one_text(model, 'Bill Gates is the founder of Microsoft' )
从结果看,我们的模型将能够很好地预测陌生句子中每个单词的实体。
结论 在本文中,我们为命名实体识别 (NER) 任务构建了 BERT 模型。并训练了 BERT 模型来预测token级别的自定义文本或自定义句子的 IOB token。