利用huggingface-transformers进行命名实体识别

利用huggingface-transformers进行命名实体识别

项目地址:https://github.com/huggingface/transformers

文档地址:https://huggingface.co/docs/transformers/pipeline_tutorial

寻找你要的模型

Transformers 提供了数以千计的预训练模型,支持 100 多种语言的文本分类、信息抽取、问答、摘要、翻译、文本生成。

从这里:https://huggingface.co/models 可以查找你要的模型,可以根据任务、语言、框架、数据集等筛选。

这里我们想要进行基于中文的ner任务,筛选出这个模型: bert-base-chinese-ner

打开之后,通过介绍我们发现这是一个基于繁体中文的模型,对应的github地址是:

https://github.com/ckiplab/ckip-transformers

有需要的小伙伴可以尝试。

我们继续筛选,找到了这个模型:uer/roberta-base-finetuned-cluener2020-chinese

模型使用

方法一:运行时下载模型

1
2
3
4
5
6
7
from transformers import AutoModelForTokenClassification,AutoTokenizer,pipeline

model = AutoModelForTokenClassification.from_pretrained('uer/roberta-base-finetuned-cluener2020-chinese')
tokenizer = AutoTokenizer.from_pretrained('uer/roberta-base-finetuned-cluener2020-chinese')

ner_pipe = pipeline('ner', model=model, tokenizer=tokenizer)
print(ner_pipe("江苏警方通报特斯拉冲进店铺"))

第一次执行时,会下载模型权重文件,默认保存到的路径是

C:\Users\用户名\.cache\torch\transformers\

我们可以把相应的权重文件从这里复制到别的地方,然后从指定目录加载模型(也可以在一开始时直接下载至指定目录)

1
2
3
model = AutoModelForTokenClassification.from_pretrained(
'uer/roberta-base-finetuned-cluener2020-chinese',
cache_dir='./data/transformers')

上面运行下载的模型文件名称不太友好,当模型多了不好管理,还可以用下面这种方法加载模型

方法二:下载原项目,然后加载

1
2
git lfs install
git clone https://huggingface.co/uer/roberta-base-finetuned-cluener2020-chinese
1
2
3
4
5
6
7
# 从下载的原项目路径加载
model = AutoModelForTokenClassification.from_pretrained(
'D:/python_project/git/roberta-base-finetuned-cluener2020-chinese'
)
tokenizer = AutoTokenizer.from_pretrained(
'D:/python_project/git/roberta-base-finetuned-cluener2020-chinese'
)

实体抽取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
model = AutoModelForTokenClassification.from_pretrained(
'uer/roberta-base-finetuned-cluener2020-chinese',
cache_dir='./data/transformers')
tokenizer = AutoTokenizer.from_pretrained(
'uer/roberta-base-finetuned-cluener2020-chinese',
cache_dir='./data/transformers')

ner_pipe = pipeline('ner', model=model, tokenizer=tokenizer)
print(ner_pipe("江苏警方通报特斯拉冲进店铺"))

[
{'entity': 'B-address', 'score': 0.66190094, 'index': 1, 'word': '江', 'start': 0, 'end': 1},
{'entity': 'I-address', 'score': 0.554456, 'index': 2, 'word': '苏', 'start': 1, 'end': 2},
{'entity': 'B-company', 'score': 0.42272708, 'index': 7, 'word': '特', 'start': 6, 'end': 7},
{'entity': 'I-company', 'score': 0.4546979, 'index': 8, 'word': '斯', 'start': 7, 'end': 8},
{'entity': 'I-company', 'score': 0.52078307, 'index': 9, 'word': '拉', 'start': 8, 'end': 9}
]

实体抽取的结果是一个列表,列表中每个元素为字典类型,但是并非每个字典为一个实体,而是实体的一部分,“江”的entity为“B-address”表示,表示其为一个address实体的开始(beginning),相应地“苏”就是address实体的Inside部分。可以参考BIO的标注规则

再来做一个测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
content = """本院定于2022年6月1日 上午09时00分在普洱市中级人民法院第三法庭公开开庭审理原告中国音像著作权集体管理协会与被告普洱帝都娱乐有限公司著作权权属、侵权纠纷一案。"""
print(ner_pipe(content))


[
{'entity': 'B-government', 'score': 0.94568264, 'index': 18, 'word': '普', 'start': 23, 'end': 24},
{'entity': 'I-government', 'score': 0.9213542, 'index': 19, 'word': '洱', 'start': 24, 'end': 25},
{'entity': 'I-government', 'score': 0.96393627, 'index': 20, 'word': '市', 'start': 25, 'end': 26},
{'entity': 'I-government', 'score': 0.97218406, 'index': 21, 'word': '中', 'start': 26, 'end': 27},
{'entity': 'I-government', 'score': 0.9750988, 'index': 22, 'word': '级', 'start': 27, 'end': 28},
{'entity': 'I-government', 'score': 0.9764756, 'index': 23, 'word': '人', 'start': 28, 'end': 29},
{'entity': 'I-government', 'score': 0.9742114, 'index': 24, 'word': '民', 'start': 29, 'end': 30},
{'entity': 'I-government', 'score': 0.96489114, 'index': 25, 'word': '法', 'start': 30, 'end': 31},
{'entity': 'I-government', 'score': 0.9691787, 'index': 26, 'word': '院', 'start': 31, 'end': 32},
{'entity': 'I-government', 'score': 0.65080214, 'index': 27, 'word': '第', 'start': 32, 'end': 33},
{'entity': 'I-government', 'score': 0.9060895, 'index': 28, 'word': '三', 'start': 33, 'end': 34},
{'entity': 'I-government', 'score': 0.8860891, 'index': 29, 'word': '法', 'start': 34, 'end': 35},
{'entity': 'I-government', 'score': 0.90334916, 'index': 30, 'word': '庭', 'start': 35, 'end': 36},
{'entity': 'B-organization', 'score': 0.9363663, 'index': 39, 'word': '中', 'start': 44, 'end': 45},
{'entity': 'I-organization', 'score': 0.9424646, 'index': 40, 'word': '国', 'start': 45, 'end': 46},
{'entity': 'I-organization', 'score': 0.8929043, 'index': 41, 'word': '音', 'start': 46, 'end': 47},
{'entity': 'I-organization', 'score': 0.90582854, 'index': 42, 'word': '像', 'start': 47, 'end': 48},
{'entity': 'I-organization', 'score': 0.8805796, 'index': 43, 'word': '著', 'start': 48, 'end': 49},
{'entity': 'I-organization', 'score': 0.79649234, 'index': 44, 'word': '作', 'start': 49, 'end': 50},
{'entity': 'I-organization', 'score': 0.92722464, 'index': 45, 'word': '权', 'start': 50, 'end': 51},
{'entity': 'I-organization', 'score': 0.95748276, 'index': 46, 'word': '集', 'start': 51, 'end': 52},
{'entity': 'I-organization', 'score': 0.96203715, 'index': 47, 'word': '体', 'start': 52, 'end': 53},
{'entity': 'I-organization', 'score': 0.9557828, 'index': 48, 'word': '管', 'start': 53, 'end': 54},
{'entity': 'I-organization', 'score': 0.96434724, 'index': 49, 'word': '理', 'start': 54, 'end': 55},
{'entity': 'I-organization', 'score': 0.9720231, 'index': 50, 'word': '协', 'start': 55, 'end': 56},
{'entity': 'I-organization', 'score': 0.9506598, 'index': 51, 'word': '会', 'start': 56, 'end': 57},
{'entity': 'B-company', 'score': 0.9608379, 'index': 55, 'word': '普', 'start': 60, 'end': 61},
{'entity': 'I-company', 'score': 0.9295836, 'index': 56, 'word': '洱', 'start': 61, 'end': 62},
{'entity': 'I-company', 'score': 0.9206439, 'index': 57, 'word': '帝', 'start': 62, 'end': 63},
{'entity': 'I-company', 'score': 0.9737558, 'index': 58, 'word': '都', 'start': 63, 'end': 64},
{'entity': 'I-company', 'score': 0.98576665, 'index': 59, 'word': '娱', 'start': 64, 'end': 65},
{'entity': 'I-company', 'score': 0.9775351, 'index': 60, 'word': '乐', 'start': 65, 'end': 66},
{'entity': 'I-company', 'score': 0.98828006, 'index': 61, 'word': '有', 'start': 66, 'end': 67},
{'entity': 'I-company', 'score': 0.9860963, 'index': 62, 'word': '限', 'start': 67, 'end': 68},
{'entity': 'I-company', 'score': 0.9872212, 'index': 63, 'word': '公', 'start': 68, 'end': 69},
{'entity': 'I-company', 'score': 0.9746289, 'index': 64, 'word': '司', 'start': 69, 'end': 70}]

可以看到其抽取到了三个实体,分别为:

1
2
{'government': '普洱市中级人民法院第三法庭', 'organization': '中国音像著作权集体管理协会',
'company': '普洱帝都娱乐有限公司'}

通过以下方式还可以查看分词结果

1
2
3
print(tokenizer.tokenize(content))

['本', '院', '定', '于', '2022', '年', '6', '月', '1', '日', '上', '午', '09', '时', '00', '分', '在', '普', '洱', '市', '中', '级', '人', '民', '法', '院', '第', '三', '法', '庭', '公', '开', '开', '庭', '审', '理', '原', '告', '中', '国', '音', '像', '著', '作', '权', '集', '体', '管', '理', '协', '会', '与', '被', '告', '普', '洱', '帝', '都', '娱', '乐', '有', '限', '公', '司', '著', '作', '权', '权', '属', '、', '侵', '权', '纠', '纷', '一', '案', '。']

看起来并没有“词”,都是单个的字,与HanLP是不一样的,不过实际上可以理解,因为BERT本身在训练的时候就是把单个的字作为输入进行训练,没有去做真正的中文分词。

训练自己的实体抽取任务

上面的模型已经可以做到一个不错的中文实体抽取效果,但是应用到特定领域,不可避免地会有一些领域实体抽取不出来,例如在司法领域,案由著作权权属、侵权纠纷就无法抽取出来,如果我们想训练自己的实体抽取任务应该怎么做呢?其实通过查看uer/roberta-base-finetuned-cluener2020-chinese这个模型的文档可以知道,该模型是通过UER-py的GitHub项目训练得到一个中文命名实体模型,UER-py项目地址为:

https://github.com/dbiir/UER-py

对应中文文档为:https://github.com/dbiir/UER-py/blob/master/README_ZH.md

在文档中可以看到:“UER-py(Universal Encoder Representations)是一个用于对通用语料进行预训练并对下游任务进行微调的工具包。UER-py遵循模块化的设计原则。通过模块的组合,用户能迅速精准的复现已有的预训练模型,并利用已有的接口进一步开发更多的预训练模型”

所以,如果我们想要在训练自己的实体抽取任务,可以借助UER-py来帮助我们实现,下面我也会记录学习使用这个工具包的过程。