教程:基于 UIE 的命名实体识别
使用 PaddleNLP 中的 UIE 模型,通过少量数据标注实现 NER 效果的快速提升
本教程基于 PaddleNLP 的 Universal Information Extraction 进行命名实体识别任务,并展示通过标注少量数据进行微调获取模型效果快速提升。
:::note 完整的 Jupyter 笔记本、代码、标注数据在 https://app.hyper.ai/console/open-tutorials/containers/lWyxi1DwhJU 都可以找得到。 :::
导入依赖库
from pprint import pprint
from paddlenlp import Taskflow使用 uie-base 进行命名实体识别
首先直接使用预训练模型 uie-base 进行命名实体识别,不做任何的调优看看效果。
schema = [
    '地名',
    '人名',
    '组织',
    '时间',
    '产品',
    '价格',
    '天气'
]
ie = Taskflow('information_extraction', schema=schema)
pprint(ie("2K 与 Gearbox Software 宣布,《小缇娜的奇幻之地》将于 6 月 24 日凌晨 1 点登录 Steam,此前 PC 平台为 Epic 限时独占。在限定期间内,Steam 玩家可以在 Steam 入手《小缇娜的奇幻之地》,并在 2022 年 7 月 8 日前享有获得黄金英雄铠甲包。"))[
  {
    "产品": [{ "end": 35, "probability": 0.8594067882980987, "start": 25, "text": "《小缇娜的奇幻之地》" }],
    "地名": [
      { "end": 117, "probability": 0.5248250992968906, "start": 109, "text": "小缇娜的奇幻之地" },
      { "end": 34, "probability": 0.3007929716932729, "start": 26, "text": "小缇娜的奇幻之地" }
    ],
    "时间": [{ "end": 52, "probability": 0.87968346213556, "start": 38, "text": "6 月 24 日凌晨 1 点" }],
    "组织": [
      { "end": 93, "probability": 0.5977969768231866, "start": 88, "text": "Steam" },
      { "end": 2, "probability": 0.6914769673274321, "start": 0, "text": "2K" },
      { "end": 75, "probability": 0.5848915911412256, "start": 71, "text": "Epic" },
      { "end": 60, "probability": 0.5682100157587833, "start": 55, "text": "Steam" },
      { "end": 21, "probability": 0.679590305138845, "start": 5, "text": "Gearbox Software" },
      { "end": 105, "probability": 0.4573145431744834, "start": 100, "text": "Steam" }
    ]
  }
]pprint(ie("近日,量子计算专家、ACM 计算奖得主 Scott Aaronson 通过博客宣布,将于本周离开得克萨斯大学奥斯汀分校 (UT Austin) 一年,并加盟人工智能研究公司 OpenAI。"))[
  {
    "人名": [
      { "end": 32, "probability": 0.4801083732026494, "start": 24, "text": "Aaronson" },
      { "end": 23, "probability": 0.6648137293130958, "start": 18, "text": "Scott" }
    ],
    "时间": [{ "end": 43, "probability": 0.8425767345737043, "start": 41, "text": "本周" }],
    "组织": [{ "end": 87, "probability": 0.5554367836811132, "start": 81, "text": "OpenAI" }]
  }
]使用默认模型 uie-base 进行命名实体识别,效果还不错,大多数的命名实体被识别出来了,但依然存在部分实体未被识别出,部分文本被误识别等问题。比如 "Scott Aaronson" 被识别为了两个人名,比如 "得克萨斯大学奥斯汀分校" 没有被识别出来。
为提升识别效果,本教程尝试通过标注少量数据对模型进行微调。
数据标注
本教程使用数据标注平台 Label Studio 进行数据标注。所有的工作都是在一个开启了的 「HyperAI 工作空间」 中完成的。
启动 Label Studio
如上所示,在 Jupyter 中打开终端并在终端中执行 openbayes-label-studio 即可在 HyperAI Jupyter Workspace 中使用 LabelStudio 了。然后使用如下所示的命令行中生成的 url 启动 Label Studio:
在浏览器中打开链接,注册账户并登录,即可开始使用。
:::warning 对于不同的 HyperAI 算力容器,红框中的外部访问链接各不相同,直接使用本教程中的链接是无效的,需用终端中提示的链接进行替换。 :::
标注数据
具体步骤如下:
- 创建项目。
- 导入数据。本教程使用的数据已上传到此算力容器,即 corpus.txt。
- 配置标签界面。在 Natural Language Processing 中选择 Named Entity Recognition 模板,根据需要添加或修改标签。本教程中需要定义的实体标签有『地名』『人名』『组织』『时间』『产品』『价格』『天气』。
- 开始标注数据。
- 导出数据。标注完成后,从 label studio 导出 JSON 格式的结果文件。此算力容器中已有预先标注好的文件 label-studio.json。
:::info
如果你懒得自己标注也没关系,这个教程里的 label-studio.json 就是已经标注好并导出的结果了。
:::
模型微调
以下模型微调所需脚本已上传到此算力容器。
数据转换
在终端中执行以下脚本,将 label studio 导出的数据文件格式转换成 doccano 导出的数据文件格式。
python labelstudio2doccano.py --labelstudio_file label-studio.json参数说明:
- labelstudio_file: label studio 的导出文件路径(仅支持 JSON 格式)。
- doccano_file: doccano 格式的数据文件保存路径,默认为 "doccano_ext.jsonl"。
- task_type: 任务类型,可选有抽取("ext")和分类("cls")两种类型的任务,默认为 "ext"。
:::info
PaddleNLP 默认并不提供将 labelstudio 的标注格式转换为其所支持的格式的工具,我们这里提供了一个 labelstudio2doccano.py 的脚本。
:::
然后在终端中执行以下脚本,对 doccano 格式的数据文件进行处理,执行后会在 /home/data 目录下生成训练/验证/测试集文件。
python doccano.py \
    --doccano_file ./doccano_ext.jsonl \
    --task_type "ext" \
    --save_dir ./data \
    --splits 0.7 0.2 0.1参数说明:
- doccano_file: doccano 格式的数据标注文件路径。
- task_type: 选择任务类型,可选有抽取("ext")和分类("cls")两种类型的任务。
- save_dir: 训练数据的保存目录,默认存储在 data 目录下。
- negative_ratio: 最大负例比例,该参数只对抽取类型任务有效,适当构造负例可提升模型效果。负例数量和实际的标签数量有关,最大负例数量 = negative_ratio * 正例数量。该参数只对训练集有效,默认为 5。为了保证评估指标的准确性,验证集和测试集默认构造全负例。
- splits: 划分数据集时训练集、验证集、测试集所占的比例。默认为 [0.8, 0.1, 0.1] 。
- options: 指定分类任务的类别标签,该参数只对分类类型任务有效。默认为 ["正向", "负向"]。
- prompt_prefix: 声明分类任务的 prompt 前缀信息,该参数只对分类类型任务有效。默认为 "情感倾向"。
- is_shuffle: 是否对数据集进行随机打散,默认为 True。
- seed: 随机种子,默认为 1000。
- separator: 实体类别/评价维度与分类标签的分隔符,该参数只对实体/评价维度级分类任务有效。默认为 "##"。
:::warning 每次执行 doccano.py 脚本,将会覆盖已有的同名数据文件。 :::
Finetune
在终端中执行以下脚本进行模型微调。
python finetune.py \
    --train_path "./data/train.txt" \
    --dev_path "./data/dev.txt" \
    --save_dir "./checkpoint" \
    --learning_rate 1e-5 \
    --batch_size 4 \
    --max_seq_len 512 \
    --num_epochs 50 \
    --model "uie-base" \
    --seed 1000 \
    --logging_steps 10 \
    --valid_steps 100 \
    --device "gpu"参数说明:
- train_path: 训练集文件路径。
- dev_path: 验证集文件路径。
- save_dir: 模型存储路径,默认为 "./checkpoint"。
- learning_rate: 学习率,默认为 1e-5。
- batch_size: 批处理大小,请结合机器情况进行调整,默认为 16。
- max_seq_len: 文本最大切分长度,输入超过最大长度时会对输入文本进行自动切分,默认为 512。
- num_epochs: 训练轮数,默认为 100。
- model: 选择模型,程序会基于选择的模型进行模型微调,可选有 "uie-base", "uie-medium", "uie-mini", "uie-micro" 和 "uie-nano",默认为 "uie-base"。
- seed: 随机种子,默认为 1000。
- logging_steps: 日志打印的间隔 steps 数,默认为 10。
- valid_steps: evaluate 的间隔 steps 数,默认为 100。
- device: 选用什么设备进行训练,可选 "cpu" 或 "gpu"。
- init_from_ckpt: 初始化模型参数的路径,可从断点处继续训练。
模型评估
在终端中执行以下脚本进行模型评估。
python evaluate.py \
    --model_path ./checkpoint/model_best \
    --test_path ./data/dev.txt \
    --batch_size 16 \
    --max_seq_len 512输出:
[2022-07-15 03:18:19,157] [    INFO] - -----------------------------
[2022-07-15 03:18:19,157] [    INFO] - Class Name: all_classes
[2022-07-15 03:18:19,157] [    INFO] - Evaluation Precision: 0.95349 | Recall: 0.89130 | F1: 0.92135可以看到 F1 已经达到了 0.92,说明模型的效果较好。
参数说明:
- model_path: 进行评估的模型文件夹路径,路径下需包含模型权重文件 model_state.pdparams 及配置文件 model_config.json。
- test_path: 进行评估的测试集文件。
- batch_size: 批处理大小,请结合机器情况进行调整,默认为 16。
- max_seq_len: 文本最大切分长度,输入超过最大长度时会对输入文本进行自动切分,默认为 512。
- debug: 是否开启 debug 模式对每个正例类别分别进行评估,该模式仅用于模型调试,默认关闭。
debug 模式输出示例:
[2022-07-15 03:27:57,801] [    INFO] - -----------------------------
[2022-07-15 03:27:57,801] [    INFO] - Class Name: 组织
[2022-07-15 03:27:57,802] [    INFO] - Evaluation Precision: 1.00000 | Recall: 0.75000 | F1: 0.85714
[2022-07-15 03:27:57,913] [    INFO] - -----------------------------
[2022-07-15 03:27:57,913] [    INFO] - Class Name: 地名
[2022-07-15 03:27:57,913] [    INFO] - Evaluation Precision: 0.90476 | Recall: 0.82609 | F1: 0.86364
[2022-07-15 03:27:58,046] [    INFO] - -----------------------------
[2022-07-15 03:27:58,046] [    INFO] - Class Name: 时间
[2022-07-15 03:27:58,047] [    INFO] - Evaluation Precision: 1.00000 | Recall: 1.00000 | F1: 1.00000
[2022-07-15 03:27:58,098] [    INFO] - -----------------------------
[2022-07-15 03:27:58,098] [    INFO] - Class Name: 产品
[2022-07-15 03:27:58,098] [    INFO] - Evaluation Precision: 1.00000 | Recall: 1.00000 | F1: 1.00000
[2022-07-15 03:27:58,147] [    INFO] - -----------------------------
[2022-07-15 03:27:58,147] [    INFO] - Class Name: 价格
[2022-07-15 03:27:58,147] [    INFO] - Evaluation Precision: 1.00000 | Recall: 1.00000 | F1: 1.00000
[2022-07-15 03:27:58,176] [    INFO] - -----------------------------
[2022-07-15 03:27:58,176] [    INFO] - Class Name: 人名
[2022-07-15 03:27:58,177] [    INFO] - Evaluation Precision: 1.00000 | Recall: 1.00000 | F1: 1.00000微调后效果
my_ie = Taskflow("information_extraction", schema=schema, task_path='./checkpoint/model_best')  # task_path 指定模型权重文件的路径
pprint(my_ie("2K 与 Gearbox Software 宣布,《小缇娜的奇幻之地》将于 6 月 24 日凌晨 1 点登录 Steam,此前 PC 平台为 Epic 限时独占。在限定期间内,Steam 玩家可以在 Steam 入手《小缇娜的奇幻之地》,并在 2022 年 7 月 8 日前享有获得黄金英雄铠甲包。"))[
  {
    "产品": [{ "end": 148, "probability": 0.9977381891196586, "start": 141, "text": "黄金英雄铠甲包" }],
    "时间": [
      { "end": 52, "probability": 0.9999856949362851, "start": 38, "text": "6 月 24 日凌晨 1 点" },
      { "end": 137, "probability": 0.6508416072546055, "start": 122, "text": "2022 年 7 月 8 日前" }
    ],
    "组织": [
      { "end": 21, "probability": 0.9996073012678011, "start": 5, "text": "Gearbox Software" },
      { "end": 93, "probability": 0.9872895891306825, "start": 88, "text": "Steam" },
      { "end": 105, "probability": 0.9665188911951077, "start": 100, "text": "Steam" },
      { "end": 2, "probability": 0.9883892925330713, "start": 0, "text": "2K" },
      { "end": 75, "probability": 0.9965524822425209, "start": 71, "text": "Epic" },
      { "end": 60, "probability": 0.9965759490955008, "start": 55, "text": "Steam" }
    ]
  }
]pprint(my_ie("近日,量子计算专家、ACM 计算奖得主 Scott Aaronson 通过博客宣布,将于本周离开得克萨斯大学奥斯汀分校 (UT Austin) 一年,并加盟人工智能研究公司 OpenAI。"))[
  {
    "人名": [{ "end": 32, "probability": 0.9999316942434575, "start": 18, "text": "Scott Aaronson" }],
    "地名": [{ "end": 54, "probability": 0.976469583224933, "start": 51, "text": "奥斯汀" }],
    "时间": [
      { "end": 69, "probability": 0.9782005099896942, "start": 67, "text": "一年" },
      { "end": 2, "probability": 0.9995077236474508, "start": 0, "text": "近日" },
      { "end": 43, "probability": 0.9999382505043286, "start": 41, "text": "本周" }
    ],
    "组织": [
      { "end": 66, "probability": 0.46570937436359827, "start": 57, "text": "UT Austin" },
      { "end": 56, "probability": 0.9686587700987381, "start": 45, "text": "得克萨斯大学奥斯汀分校" },
      { "end": 13, "probability": 0.7166219551892539, "start": 10, "text": "ACM" },
      { "end": 87, "probability": 0.999835617128781, "start": 81, "text": "OpenAI" }
    ]
  }
]模型部署
得到微调后的模型后,可以将模型部署到 HyperAI 的服务端,以实现实时的模型推理服务。
更多有关模型部署的信息可参考 模型部署介绍 和 基于迁移学习的中文命名实体识别的模型部署。
Serving 服务编写
编写 predictor.py 文件:
- 导入依赖库:除了业务中用到的库之外,需要额外依赖 openbayes-serving。
import openbayes_serving as serv
from paddlenlp import Taskflow- 
后处理(可选):根据需要对模型返回的结果进行处理,以更好地展示。本教程中通过 format()函数和add_o()函数修改命名实体识别结果的形式。
- 
Predictor 类: 不需要继承其他的类,但是至少需要提供 __init__和predict两个接口。- 在 __init__中定义实体抽取结构,通过Taskflow加载模型。
- 在 predict中进行预测,返回后处理的结果。
 
- 在 
class Predictor:
    def __init__(self):
        self.schema = ['地名', '人名', '组织', '时间', '产品', '价格', '天气']
        self.ie = Taskflow("information_extraction", schema=self.schema, task_path='./checkpoint/model_best')
    def predict(self, json):
        text = json["input"]
        uie = self.ie(text)[0]
        result = format(text, uie)
        return result- 运行:启动服务。
if __name__ == '__main__':
    serv.run(Predictor):::info
在教程的根目录下已经提供了编写好的 predictor.py 可以直接在后续使用。
:::
在 Jupyter 中测试
在终端中执行 OPENBAYES_JOB_URL= python predictor.py ,成功开启本地测试服务后,在此 Notebook 中执行下列代码进行测试。
import requests
text = {
  "input": "近日,量子计算专家、ACM 计算奖得主 Scott Aaronson 通过博客宣布,将于本周离开得克萨斯大学奥斯汀分校 (UT Austin) 一年,并加盟人工智能研究公司 OpenAI。"
}
result = requests.post('http://localhost:25252', json=text)
result.json()[{'entity_group': 'TIME',
  'score': 0.9995077236474508,
  'start': 0,
  'end': 2,
  'word': '近日'},
 {'entity_group': 'O',
  'score': None,
  'start': 2,
  'end': 10,
  'word': ',量子计算专家、'},
 {'entity_group': 'ORG',
  'score': 0.7166219551892539,
  'start': 10,
  'end': 13,
  'word': 'ACM'},
 {'entity_group': 'O', 'score': None, 'start': 13, 'end': 18, 'word': '计算奖得主'},
 {'entity_group': 'PER',
  'score': 0.9999316942434575,
  'start': 18,
  'end': 32,
  'word': 'Scott Aaronson'},
 {'entity_group': 'O',
  'score': None,
  'start': 32,
  'end': 41,
  'word': '通过博客宣布,将于'},
 {'entity_group': 'TIME',
  'score': 0.9999382505043286,
  'start': 41,
  'end': 43,
  'word': '本周'},
 {'entity_group': 'O', 'score': None, 'start': 43, 'end': 45, 'word': '离开'},
 {'entity_group': 'ORG',
  'score': 0.9686587700987381,
  'start': 45,
  'end': 56,
  'word': '得克萨斯大学奥斯汀分校'},
 {'entity_group': 'LOC',
  'score': 0.976469583224933,
  'start': 51,
  'end': 54,
  'word': '奥斯汀'},
 {'entity_group': 'O', 'score': None, 'start': 56, 'end': 57, 'word': '('},
 {'entity_group': 'ORG',
  'score': 0.46570937436359827,
  'start': 57,
  'end': 66,
  'word': 'UT Austin'},
 {'entity_group': 'O', 'score': None, 'start': 66, 'end': 67, 'word': ')'},
 {'entity_group': 'TIME',
  'score': 0.9782005099896942,
  'start': 67,
  'end': 69,
  'word': '一年'},
 {'entity_group': 'O',
  'score': None,
  'start': 69,
  'end': 81,
  'word': ',并加盟人工智能研究公司'},
 {'entity_group': 'ORG',
  'score': 0.999835617128781,
  'start': 81,
  'end': 87,
  'word': 'OpenAI'},
 {'entity_group': 'O', 'score': None, 'start': 87, 'end': 88, 'word': '
'}]部署
测试成功后,停止此算力容器,等待同步数据完成。
在『算力容器』-『模型部署』中点击『创建新部署』,选择与开发时相同的镜像,绑定此算力容器,点击『部署』,即可进行在线测试。