# 猿创征文｜信息抽取（2）——pytorch实现Bert-BiLSTM-CRF、Bert-CRF模型进行实体抽取

### 文章目录

1 前言

; 2 数据准备

huggingface-transformers

conda install -c huggingface transformers


seqeval

pip install seqeval -i https://pypi.tuna.tsinghua.edu.cn/simple


import pandas as pd
import torch
from torch import optim
from tqdm import tqdm
from bert_bilstm_crf import Bert_BiLSTM_CRF, NerDataset, NerDatasetTest
from bert_crf import Bert_CRF
from transformers import AutoTokenizer, BertTokenizer
from seqeval.metrics import f1_score

TRAIN_PATH = './dataset/train_data_public.csv'
TEST_PATH = './dataset/test_public.csv'
MODEL_PATH1 = './model/bert_bilstm_crf.pkl'
MODEL_PATH2 = '../model/bert_crf.pkl'

MAX_LEN = 64
BATCH_SIZE = 16
EPOCH = 5

DEVICE = "cuda:0" if torch.cuda.is_available() else "cpu"

tag2index = {
"O": 0,
"B-BANK": 1, "I-BANK": 2,
"B-PRODUCT": 3, "I-PRODUCT": 4,
}
index2tag = {v: k for k, v in tag2index.items()}


3 数据预处理

== 流程==

1. 使用s e r i e s . a p p l y ( l i s t ) \textcolor{red}{series.apply(list)}ser i es .a ppl y (l i s t )函数将str转化为list格式
2. 加载bert预训练tokenizer，使用e n c o d e _ p l u s \textcolor{red}{encode_plus}e n co d e _pl u s函数对每一个text进行encode
3. 如果是训练集，则执行如下操作：首先按照空格将每一个tag分割，并转化为索引列表，对每一个index_list，按照长度大于MAX_LEN裁剪，小于MAX_LEN填充的规则，合并为一个list，最后转化为tensor格式


def data_preprocessing(dataset, is_train):

dataset['text_split'] = dataset['text'].apply(list)

tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
texts = dataset['text_split'].array.tolist()
token_texts = []
for text in tqdm(texts):
tokenized = tokenizer.encode_plus(text=text,
max_length=MAX_LEN,
return_token_type_ids=True,
return_tensors='pt',
truncation=True)
token_texts.append(tokenized)

tags = None
if is_train:
dataset['tag'] = dataset['BIO_anno'].apply(lambda x: x.split(sep=' '))
tags = []
for tag in tqdm(dataset['tag'].array.tolist()):
index_list = [0] + [tag2index[t] for t in tag] + [0]
if len(index_list) < MAX_LEN:
if len(index_list) > MAX_LEN:
index_list = index_list[:MAX_LEN-1] + [0]
tags.append(index_list)
tags = torch.LongTensor(tags)



4 Bert-BiLSTM-CRF模型

import torch
from torch import nn
from torchcrf import CRF
from transformers import BertModel
from torch.utils.data import Dataset

class Bert_BiLSTM_CRF(nn.Module):
def __init__(self, tag2index):
super(Bert_BiLSTM_CRF, self).__init__()
self.tagset_size = len(tag2index)

self.bert = BertModel.from_pretrained('bert-base-chinese')

self.lstm = nn.LSTM(input_size=768, hidden_size=128, num_layers=1, batch_first=True, bidirectional=True)

self.dropout = nn.Dropout(p=0.1)

self.dense = nn.Linear(in_features=256, out_features=self.tagset_size)

self.crf = CRF(num_tags=self.tagset_size)

self.hidden = None

def neg_log_likelihood(self, emissions, tags=None, mask=None, reduction=None):

def forward(self, token_texts, tags):
"""
token_texts:{"input_size": tensor,  [batch, 1, seq_len]->[batch, seq_len]
"token_type_ids": tensor,  [batch, 1, seq_len]->[batch, seq_len]
"attention_mask": tensor  [batch, 1, seq_len]->[batch, seq_len]->[seq_len, batch]
}
tags:  [batch, seq_len]->[seq_len, batch]
bert_out:  [batch, seq_len, hidden_size(768)]->[seq_len, batch, hidden_size]
self.hidden:  [num_layers * num_directions, hidden_size(128)]
out:  [seq_len, batch, hidden_size * 2(256)]
lstm_feats:  [seq_len, batch, tagset_size]
loss:  tensor
predictions:  [batch, num]
"""
texts = texts.squeeze(1)
token_type_ids = token_type_ids.squeeze(1)
bert_out = bert_out.permute(1, 0, 2)

device = bert_out.device

self.hidden = (torch.randn(2, bert_out.size(0), 128).to(device),
torch.randn(2, bert_out.size(0), 128).to(device))
out, self.hidden = self.lstm(bert_out, self.hidden)
lstm_feats = self.dense(out)

if tags is not None:
tags = tags.permute(1, 0)
loss = self.neg_log_likelihood(lstm_feats, tags, masks, 'mean')
return loss, predictions
else:
return predictions



Dataset

class NerDataset(Dataset):
def __init__(self, token_texts, tags):
super(NerDataset, self).__init__()
self.token_texts = token_texts
self.tags = tags

def __getitem__(self, index):
return {
"token_texts": self.token_texts[index],
"tags": self.tags[index] if self.tags is not None else None,
}

def __len__(self):
return len(self.token_texts)

class NerDatasetTest(Dataset):
def __init__(self, token_texts):
super(NerDatasetTest, self).__init__()
self.token_texts = token_texts

def __getitem__(self, index):
return {
"token_texts": self.token_texts[index],
"tags": 0
}

def __len__(self):
return len(self.token_texts)



token_texts:{
“input_size”: tensor, [batch, 1, seq_len]->[batch, seq_len]
“token_type_ids”: tensor, [batch, 1, seq_len]->[batch, seq_len]
“attention_mask”: tensor [batch, 1, seq_len]->[batch, seq_len]->[seq_len, batch]
}
tags: [batch, seq_len]->[seq_len, batch]
bert_out: [batch, seq_len, hidden_size(768)]->[seq_len, batch, hidden_size]
self.hidden: [num_layers * num_directions, hidden_size(128)]
out: [seq_len, batch, hidden_size * 2(256)]
lstm_feats: [seq_len, batch, tagset_size]
loss: tensor
predictions: [batch, num]

5 Bert-CRF模型

from torch import nn
from torchcrf import CRF
from transformers import BertModel

class Bert_CRF(nn.Module):
def __init__(self, tag2index):
super(Bert_CRF, self).__init__()
self.tagset_size = len(tag2index)

self.bert = BertModel.from_pretrained('bert-base-chinese')

self.dense = nn.Linear(in_features=768, out_features=self.tagset_size)

self.crf = CRF(num_tags=self.tagset_size)

self.hidden = None

def neg_log_likelihood(self, emissions, tags=None, mask=None, reduction=None):

def forward(self, token_texts, tags):
"""
token_texts:{"input_size": tensor,  [batch, 1, seq_len]->[batch, seq_len]
"token_type_ids": tensor,  [batch, 1, seq_len]->[batch, seq_len]
"attention_mask": tensor  [batch, 1, seq_len]->[batch, seq_len]->[seq_len, batch]
}
tags:  [batch, seq_len]->[seq_len, batch]
bert_out:  [batch, seq_len, hidden_size(768)]->[seq_len, batch, hidden_size]
feats:  [seq_len, batch, tagset_size]
loss:  tensor
predictions:  [batch, num]
"""
texts = texts.squeeze(1)
token_type_ids = token_type_ids.squeeze(1)
bert_out = bert_out.permute(1, 0, 2)
feats = self.dense(bert_out)

if tags is not None:
tags = tags.permute(1, 0)
loss = self.neg_log_likelihood(feats, tags, masks, 'mean')
return loss, predictions
else:
return predictions



token_texts:{
“input_size”: tensor, [batch, 1, seq_len]->[batch, seq_len]
“token_type_ids”: tensor, [batch, 1, seq_len]->[batch, seq_len]
“attention_mask”: tensor [batch, 1, seq_len]->[batch, seq_len]->[seq_len, batch]
}
tags: [batch, seq_len]->[seq_len, batch]
bert_out: [batch, seq_len, hidden_size(768)]->[seq_len, batch, hidden_size]
feats: [seq_len, batch, tagset_size]
loss: tensor
predictions: [batch, num]

6 模型训练


token_texts = batch_data['token_texts'].to(DEVICE)
tags = batch_data['tags'].to(DEVICE)
loss, predictions = model(token_texts, tags)
loss.backward()
optimizer.step()

if i % 200 == 0:
micro_f1 = get_f1_score(tags, predictions)
print(f'Epoch:{epoch} | i:{i} | loss:{loss.item()} | Micro_F1:{micro_f1}')



7 结果评估


def get_f1_score(tags, predictions):
tags = tags.to('cpu').data.numpy().tolist()
temp_tags = []
final_tags = []
for index in range(BATCH_SIZE):

predictions[index].pop()
length = len(predictions[index])
temp_tags.append(tags[index][1:length])
predictions[index].pop(0)

temp_tags[index] = [index2tag[x] for x in temp_tags[index]]
predictions[index] = [index2tag[x] for x in predictions[index]]
final_tags.append(temp_tags[index])

f1 = f1_score(final_tags, predictions, average='micro')
return f1



Bert-BiLSTM-CRF

GPU_NAME:NVIDIA GeForce RTX 3060 Laptop GPU | Memory_Allocated:413399040
Epoch:0 | i:0 | loss:58.75139236450195 | Micro_F1:0.0
Epoch:0 | i:200 | loss:26.20857048034668 | Micro_F1:0.0
Epoch:0 | i:400 | loss:18.385879516601562 | Micro_F1:0.0
Epoch:1 | i:0 | loss:20.496620178222656 | Micro_F1:0.0
Epoch:1 | i:200 | loss:15.421577453613281 | Micro_F1:0.0
Epoch:1 | i:400 | loss:11.486358642578125 | Micro_F1:0.0
Epoch:2 | i:0 | loss:14.486601829528809 | Micro_F1:0.0
Epoch:2 | i:200 | loss:10.369649887084961 | Micro_F1:0.18867924528301888
Epoch:2 | i:400 | loss:8.056020736694336 | Micro_F1:0.5652173913043479
Epoch:3 | i:0 | loss:14.958343505859375 | Micro_F1:0.41025641025641024
Epoch:3 | i:200 | loss:9.968450546264648 | Micro_F1:0.380952380952381
Epoch:3 | i:400 | loss:8.947534561157227 | Micro_F1:0.5614035087719299
Epoch:4 | i:0 | loss:9.189300537109375 | Micro_F1:0.5454545454545454
Epoch:4 | i:200 | loss:8.673486709594727 | Micro_F1:0.43999999999999995
Epoch:4 | i:400 | loss:6.431578636169434 | Micro_F1:0.6250000000000001


Bert-CRF

GPU_NAME:NVIDIA GeForce RTX 3060 Laptop GPU | Memory_Allocated:409739264
Epoch:0 | i:0 | loss:57.06057357788086 | Micro_F1:0.0
Epoch:0 | i:200 | loss:12.05904483795166 | Micro_F1:0.0
Epoch:0 | i:400 | loss:13.805888175964355 | Micro_F1:0.39393939393939387
Epoch:1 | i:0 | loss:9.807424545288086 | Micro_F1:0.4905660377358491
Epoch:1 | i:200 | loss:8.098043441772461 | Micro_F1:0.509090909090909
Epoch:1 | i:400 | loss:7.059831619262695 | Micro_F1:0.611111111111111
Epoch:2 | i:0 | loss:6.629759788513184 | Micro_F1:0.6133333333333333
Epoch:2 | i:200 | loss:3.593130350112915 | Micro_F1:0.6896551724137931
Epoch:2 | i:400 | loss:6.8786163330078125 | Micro_F1:0.6666666666666666
Epoch:3 | i:0 | loss:5.009466648101807 | Micro_F1:0.6969696969696969
Epoch:3 | i:200 | loss:2.9549810886383057 | Micro_F1:0.8450704225352113
Epoch:3 | i:400 | loss:3.3801448345184326 | Micro_F1:0.868421052631579
Epoch:4 | i:0 | loss:5.864352226257324 | Micro_F1:0.626865671641791
Epoch:4 | i:200 | loss:3.308518409729004 | Micro_F1:0.7666666666666667
Epoch:4 | i:400 | loss:4.221902847290039 | Micro_F1:0.7000000000000001


8 训练集流水线

def execute():

token_texts, tags = data_preprocessing(train_dataset, is_train=True)

train_dataset = NerDataset(token_texts, tags)

model = Bert_CRF(tag2index=tag2index).to(DEVICE)
print(f"GPU_NAME:{torch.cuda.get_device_name()} | Memory_Allocated:{torch.cuda.memory_allocated()}")

for i in range(EPOCH):

torch.save(model.state_dict(), MODEL_PATH2)


9 测试集流水线


def test():

token_texts, _ = data_preprocessing(test_dataset, is_train=False)

dataset_test = NerDatasetTest(token_texts)

model = Bert_CRF(tag2index).to(DEVICE)

model.eval()
predictions_list = []
token_texts = batch_data['token_texts'].to(DEVICE)
predictions = model(token_texts, None)
predictions_list.extend(predictions)
print(len(predictions_list))
print(len(test_dataset['text']))

entity_tag_list = []
index2tag = {v: k for k, v in tag2index.items()}
for i, (text, predictions) in enumerate(zip(test_dataset['text'], predictions_list)):

predictions.pop()
predictions.pop(0)
text_entity_tag = []
for c, t in zip(text, predictions):
if t != 0:
text_entity_tag.append(c + index2tag[t])
entity_tag_list.append(" ".join(text_entity_tag))

print(len(entity_tag_list))
result_df = pd.DataFrame(data=entity_tag_list, columns=['result'])
result_df.to_csv('./data/result_df3.csv')



10 记录遇到的一些坑

（1）模型预测结果全为O

（3）transformers库在ubuntu上无法使用

apt-get update
apt-get install libssl1.0.0 libssl-dev


（4）笔记本（联想R7000P2021）运行代码温度过高（最高95度）

11 完整代码

import pandas as pd
import torch
from torch import optim
from tqdm import tqdm
from bert_bilstm_crf import Bert_BiLSTM_CRF, NerDataset, NerDatasetTest
from bert_crf import Bert_CRF
from transformers import AutoTokenizer, BertTokenizer
from seqeval.metrics import f1_score

TRAIN_PATH = './dataset/train_data_public.csv'
TEST_PATH = './dataset/test_public.csv'
MODEL_PATH1 = './model/bert_bilstm_crf.pkl'
MODEL_PATH2 = '../model/bert_crf.pkl'

MAX_LEN = 64
BATCH_SIZE = 16
EPOCH = 5

DEVICE = "cuda:0" if torch.cuda.is_available() else "cpu"

tag2index = {
"O": 0,
"B-BANK": 1, "I-BANK": 2,
"B-PRODUCT": 3, "I-PRODUCT": 4,
}
index2tag = {v: k for k, v in tag2index.items()}

token_texts = batch_data['token_texts'].to(DEVICE)
tags = batch_data['tags'].to(DEVICE)
loss, predictions = model(token_texts, tags)
loss.backward()
optimizer.step()

if i % 200 == 0:
micro_f1 = get_f1_score(tags, predictions)
print(f'Epoch:{epoch} | i:{i} | loss:{loss.item()} | Micro_F1:{micro_f1}')

def get_f1_score(tags, predictions):
tags = tags.to('cpu').data.numpy().tolist()
temp_tags = []
final_tags = []
for index in range(BATCH_SIZE):

predictions[index].pop()
length = len(predictions[index])
temp_tags.append(tags[index][1:length])
predictions[index].pop(0)

temp_tags[index] = [index2tag[x] for x in temp_tags[index]]
predictions[index] = [index2tag[x] for x in predictions[index]]
final_tags.append(temp_tags[index])

f1 = f1_score(final_tags, predictions, average='micro')
return f1

def data_preprocessing(dataset, is_train):

dataset['text_split'] = dataset['text'].apply(list)

tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
texts = dataset['text_split'].array.tolist()
token_texts = []
for text in tqdm(texts):
tokenized = tokenizer.encode_plus(text=text,
max_length=MAX_LEN,
return_token_type_ids=True,
return_tensors='pt',
truncation=True)
token_texts.append(tokenized)

tags = None
if is_train:
dataset['tag'] = dataset['BIO_anno'].apply(lambda x: x.split(sep=' '))
tags = []
for tag in tqdm(dataset['tag'].array.tolist()):
index_list = [0] + [tag2index[t] for t in tag] + [0]
if len(index_list) < MAX_LEN:
if len(index_list) > MAX_LEN:
index_list = index_list[:MAX_LEN-1] + [0]
tags.append(index_list)
tags = torch.LongTensor(tags)

def execute():

token_texts, tags = data_preprocessing(train_dataset, is_train=True)

train_dataset = NerDataset(token_texts, tags)

model = Bert_CRF(tag2index=tag2index).to(DEVICE)
print(f"GPU_NAME:{torch.cuda.get_device_name()} | Memory_Allocated:{torch.cuda.memory_allocated()}")

for i in range(EPOCH):

torch.save(model.state_dict(), MODEL_PATH2)

def test():

token_texts, _ = data_preprocessing(test_dataset, is_train=False)

dataset_test = NerDatasetTest(token_texts)

model = Bert_BiLSTM_CRF(tag2index).to(DEVICE)

model.eval()
predictions_list = []
token_texts = batch_data['token_texts'].to(DEVICE)
predictions = model(token_texts, None)
predictions_list.extend(predictions)
print(len(predictions_list))
print(len(test_dataset['text']))

entity_tag_list = []
index2tag = {v: k for k, v in tag2index.items()}
for i, (text, predictions) in enumerate(zip(test_dataset['text'], predictions_list)):

predictions.pop()
predictions.pop(0)
text_entity_tag = []
for c, t in zip(text, predictions):
if t != 0:
text_entity_tag.append(c + index2tag[t])
entity_tag_list.append(" ".join(text_entity_tag))

print(len(entity_tag_list))
result_df = pd.DataFrame(data=entity_tag_list, columns=['result'])
result_df.to_csv('./data/result_df3.csv')

if __name__ == '__main__':
execute()
test()



Original: https://blog.csdn.net/m0_46275020/article/details/126690721
Author: 热爱旅行的小李同学
Title: 猿创征文｜信息抽取（2）——pytorch实现Bert-BiLSTM-CRF、Bert-CRF模型进行实体抽取

(0)

### 大家都在看

• #### 区域合并方法

区域合并 区域合并方法将合并具有 相似性的 相邻区域。步骤如下：（1）图像初始区域分割：在极端情况下，可以认为每个像素为一个小区域。（2）确定相似性准则：可以基于相邻区域的灰度、颜…

人工智能 2023年6月20日
023
• #### 笔记2：yolov5训练自己的目标检测模型_创建并划分数据集，训练及检测

注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

人工智能 2022年12月5日
0111
• #### 关于 Intel Realsense 深度图像处理.1(C++)

Realsense SDK2.0 +C++ rs-hello-realsense rs-hello-realsense‎示例演示了 连接到英特尔实感设备以及通过打印到摄像头视野中心…

人工智能 2023年6月18日
027
• #### 回顾 Spring

注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

人工智能 2022年11月26日
098
• #### GRU模型和注意力机制是否容易过拟合

注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

2023年3月15日
073
• #### 融合中文字形和拼音的预训练模型：ChineseBERT（ACL2021）

注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

人工智能 2022年9月15日
0157
• #### 机器学习项目实践——K-means聚类实现广告分析

注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

人工智能 2022年9月29日
0227
• #### 电信用户流失预测案例（3）

注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

人工智能 2022年12月11日
081
• #### 爆肝一周，用Python在物联网设备上写了个智能语音助手-阿里云智能对话机器人

注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

人工智能 2022年9月26日
0185
• #### 【pycaret 分类任务】有监督学习之分类任务

注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

人工智能 2022年11月28日
0147
• #### 【OpenCv】图像分割——聚类算法

文章目录 1 原理 2 API 3 图像分割 4 代码解释 1 原理 KMeans算法概述 KMeans算法的作者是MacQueen， KMeans的算法是对数据进行分类的算法，采…

人工智能 2023年5月31日
020
• #### 关于cv2.dnn.readNetFromONNX(path)就报ERROR during processing node with 3 inputs and 1 outputs的解决过程【独家发布】

注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

人工智能 2022年8月27日
0427
• #### 2020-12-15 知识图谱质量评估

7 质量评估 无关于知识图谱从哪一种源创建，为初始知识图谱提取的数据通常是不完整的，并且包含重复、矛盾甚至不正确的语句，尤其是从多个源提取时。在最初创建并丰富了来自外部资源的知识图…

人工智能 2023年6月1日
020
• #### 【教程】基于TensorFlow Lite的yolov5部署过程

注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

人工智能 2022年9月2日
0158
• #### Python的优点和缺点

注入产生的原理: 数据库设置为GBK编码: 宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而…

人工智能 2022年11月26日
098
• #### 机器学习之回归模型-梯度下降法求解线性回归

机器学习之回归模型-梯度下降法求解线性回归 线性回归是一种线性模型，它假设输入变量x与单个输出变量y之间存在线性关系。具体的说，就是利用线性回归模型，从一组输入变量的线性组合中，计…

人工智能 2023年6月16日
019