Paddlenlp之UIE关系抽取模型高管关系抽取为例
Posted 汀、
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Paddlenlp之UIE关系抽取模型高管关系抽取为例相关的知识,希望对你有一定的参考价值。
往期项目回顾:
Paddlenlp之UIE模型实战实体抽取任务【打车数据、快递单】
Paddlenlp之UIE分类模型【以情感倾向分析新闻分类为例】含智能标注方案)
应用实践:分类模型大集成者[PaddleHub、Finetune、prompt]
本项目链接:只需要fork就可以直接复现
0.背景介绍
本项目将演示如何通过小样本样本进行模型微调,完成关系抽取。
数据集情况:
高管数据集demo:
马云浙江省杭州市人,阿里巴巴集团主要创始人之一。现任阿里巴巴集团主席和首席执行官,他是《福布斯》杂志创办50多年来成为封面人物的首位大陆企业家,曾获选为未来全球领袖。
任正非是中国大陆的民营电信设备企业一-华为公司的创始人兼总裁。 他关于企业“危机管理”的理论与实践曾在业内外产生过广泛影响。
马化腾,是腾讯主要创办人之一现担任公司控股董事会主席兼首席执行官。作为深圳土生土长的企业家,他曾在深圳大学主修计算机及应用,于1993年取得深大理学士学位。
李彦宏是百度公司创始人董事长兼首席执行官,全面负责百度公司的战略规划和运营管理,经过多年发展,百度已经牢牢占据中文搜索引擎超过7成的市场份额。
雷军, 2012年8月其投资创办的小米公司正式发布小米手机。
刘强东,江苏省宿迁市宿豫区人,京东商城的CEO。1996年毕业于中国人民大学社会学系。
柳传志,中国著名企业家,投资家,曾任联想控股有限公司董事长、联想集团有限公司董事局主席。
"id":1845,"text":"马云浙江省杭州市人,阿里巴巴集团主要创始人之一。现任阿里巴巴集团主席和首席执行官,他是《福布斯》杂志创办50多年来成为封面人物的首位大陆企业家,曾获选为未来全球领袖。","entities":["id":945,"label":"人名","start_offset":0,"end_offset":2,"id":946,"label":"公司","start_offset":10,"end_offset":16],"relations":["id":11,"from_id":945,"to_id":946,"type":"高管"]
"id":1846,"text":"任正非是中国大陆的民营电信设备企业一-华为公司的创始人兼总裁。 他关于企业“危机管理”的理论与实践曾在业内外产生过广泛影响。","entities":["id":949,"label":"人名","start_offset":0,"end_offset":3,"id":950,"label":"公司","start_offset":19,"end_offset":23],"relations":["id":13,"from_id":949,"to_id":950,"type":"高管"]
"id":1847,"text":"马化腾,是腾讯主要创办人之一现担任公司控股董事会主席兼首席执行官。作为深圳土生土长的企业家,他曾在深圳大学主修计算机及应用,于1993年取得深大理学士学位。","entities":["id":954,"label":"人名","start_offset":0,"end_offset":3,"id":955,"label":"公司","start_offset":5,"end_offset":7],"relations":["id":16,"from_id":954,"to_id":955,"type":"高管"]
"id":1848,"text":"李彦宏是百度公司创始人董事长兼首席执行官,全面负责百度公司的战略规划和运营管理,经过多年发展,百度已经牢牢占据中文搜索引擎超过7成的市场份额。","entities":["id":932,"label":"人名","start_offset":0,"end_offset":3,"id":933,"label":"公司","start_offset":4,"end_offset":8,"id":934,"label":"公司","start_offset":25,"end_offset":29],"relations":["id":6,"from_id":932,"to_id":933,"type":"高管"]
"id":1849,"text":"雷军, 2012年8月其投资创办的小米公司正式发布小米手机。","entities":["id":941,"label":"人名","start_offset":0,"end_offset":2,"id":942,"label":"公司","start_offset":17,"end_offset":21],"relations":["id":9,"from_id":941,"to_id":942,"type":"高管"]
数据加载
数据标注过程中,关系标注别搞反了,详细看前面文章,标注教学
doccano_file: 从doccano导出的数据标注文件。
save_dir: 训练数据的保存目录,默认存储在data目录下。
negative_ratio: 最大负例比例,该参数只对抽取类型任务有效,适当构造负例可提升模型效果。负例数量和实际的标签数量有关,最大负例数量 = negative_ratio * 正例数量。该参数只对训练集有效,默认为5。为了保证评估指标的准确性,验证集和测试集默认构造全负例。
splits: 划分数据集时训练集、验证集所占的比例。默认为[0.8, 0.1, 0.1]表示按照8:1:1的比例将数据划分为训练集、验证集和测试集。
task_type: 选择任务类型,可选有抽取和分类两种类型的任务。
options: 指定分类任务的类别标签,该参数只对分类类型任务有效。默认为[“正向”, “负向”]。
prompt_prefix: 声明分类任务的prompt前缀信息,该参数只对分类类型任务有效。默认为"情感倾向"。
is_shuffle: 是否对数据集进行随机打散,默认为True。
seed: 随机种子,默认为1000.
*separator: 实体类别/评价维度与分类标签的分隔符,该参数只对实体/评价维度级分类任务有效。默认为"##"。
import os
import time
import argparse
import json
import numpy as np
from utils_1 import set_seed, convert_ext_examples
def do_convert():
set_seed(args.seed)
tic_time = time.time()
if not os.path.exists(args.input_file):
raise ValueError("Please input the correct path of doccano file.")
if not os.path.exists(args.save_dir):
os.makedirs(args.save_dir)
if len(args.splits) != 0 and len(args.splits) != 3:
raise ValueError("Only []/ len(splits)==3 accepted for splits.")
if args.splits and sum(args.splits) != 1:
raise ValueError(
"Please set correct splits, sum of elements in splits should be equal to 1."
)
with open(args.input_file, "r", encoding="utf-8") as f:
raw_examples = f.readlines()
def _create_ext_examples(examples, negative_ratio=0, shuffle=False):
entities, relations = convert_ext_examples(examples, negative_ratio)
examples = [e + r for e, r in zip(entities, relations)]
if shuffle:
indexes = np.random.permutation(len(examples))
examples = [examples[i] for i in indexes]
return examples
def _save_examples(save_dir, file_name, examples):
count = 0
save_path = os.path.join(save_dir, file_name)
with open(save_path, "w", encoding="utf-8") as f:
for example in examples:
for x in example:
f.write(json.dumps(x, ensure_ascii=False) + "\\n")
count += 1
print("\\nSave %d examples to %s." % (count, save_path))
if len(args.splits) == 0:
examples = _create_ext_examples(raw_examples, args.negative_ratio,
args.is_shuffle)
_save_examples(args.save_dir, "train.txt", examples)
else:
if args.is_shuffle:
indexes = np.random.permutation(len(raw_examples))
raw_examples = [raw_examples[i] for i in indexes]
i1, i2, _ = args.splits
p1 = int(len(raw_examples) * i1)
p2 = int(len(raw_examples) * (i1 + i2))
train_examples = _create_ext_examples(
raw_examples[:p1], args.negative_ratio, args.is_shuffle)
dev_examples = _create_ext_examples(raw_examples[p1:p2])
test_examples = _create_ext_examples(raw_examples[p2:])
_save_examples(args.save_dir, "train.txt", train_examples)
_save_examples(args.save_dir, "dev.txt", dev_examples)
_save_examples(args.save_dir, "test.txt", test_examples)
print('Finished! It takes %.2f seconds' % (time.time() - tic_time))
if __name__ == "__main__":
# yapf: disable
parser = argparse.ArgumentParser()
parser.add_argument("--input_file", default="./data/data.json", type=str, help="The data file exported from doccano platform.")
parser.add_argument("--save_dir", default="./data", type=str, help="The path to save processed data.")
parser.add_argument("--negative_ratio", default=5, type=int, help="Used only for the classification task, the ratio of positive and negative samples, number of negtive samples = negative_ratio * number of positive samples")
parser.add_argument("--splits", default=[0.8, 0.1, 0.1], type=float, nargs="*", help="The ratio of samples in datasets. [0.6, 0.2, 0.2] means 60% samples used for training, 20% for evaluation and 20% for test.")
parser.add_argument("--is_shuffle", default=True, type=bool, help="Whether to shuffle the labeled dataset, defaults to True.")
parser.add_argument("--seed", type=int, default=1000, help="random seed for initialization")
args = parser.parse_args()
# yapf: enable
do_convert()
! python preprocess.py --input_file ./data/gaoguan.jsonl \\
--save_dir ./data/ \\
--negative_ratio 5 \\
--splits 0.85 0.15 0 \\
--seed 1000
Converting doccano data...
100%|██████████████████████████████████████████| 8/8 [00:00<00:00, 21358.65it/s]
Adding negative samples for first stage prompt...
100%|██████████████████████████████████████████| 8/8 [00:00<00:00, 88534.12it/s]
Constructing relation prompts...
Adding negative samples for second stage prompt...
100%|██████████████████████████████████████████| 8/8 [00:00<00:00, 24070.61it/s]
Converting doccano data...
100%|██████████████████████████████████████████| 2/2 [00:00<00:00, 25420.02it/s]
Adding negative samples for first stage prompt...
100%|██████████████████████████████████████████| 2/2 [00:00<00:00, 45839.39it/s]
Constructing relation prompts...
Adding negative samples for second stage prompt...
100%|██████████████████████████████████████████| 2/2 [00:00<00:00, 30066.70it/s]
Converting doccano data...
0it [00:00, ?it/s]
Adding negative samples for first stage prompt...
0it [00:00, ?it/s]
Save 64 examples to ./data/train.txt.
Save 6 examples to ./data/dev.txt.
输出部分展示:
"content": "网易公司首席架构设计师,丁磊1997年6月创立网易公司,将网易从一个十几个人的私企发展到今天拥有近3000员工在美国公开上市的知名互联网技术企业。", "result_list": ["text": "丁磊", "start": 12, "end": 14], "prompt": "人名"
"content": "网易公司首席架构设计师,丁磊1997年6月创立网易公司,将网易从一个十几个人的私企发展到今天拥有近3000员工在美国公开上市的知名互联网技术企业。", "result_list": ["text": "网易公司", "start": 0, "end": 4, "text": "网易公司", "start": 23, "end": 27], "prompt": "公司"
"content": "网易公司首席架构设计师,丁磊1997年6月创立网易公司,将网易从一个十几个人的私企发展到今天拥有近3000员工在美国公开上市的知名互联网技术企业。", "result_list": ["text": "丁磊", "start": 12, "end": 14, "text": "丁磊", "start": 12, "end": 14], "prompt": "网易公司的高管"
"content": "李彦宏是百度公司创始人董事长兼首席执行官,全面负责百度公司的战略规划和运营管理,经过多年发展,百度已经牢牢占据中文搜索引擎超过7成的市场份额。", "result_list": ["text": "李彦宏", "start": 0, "end": 3], "prompt": "人名"
"content": "李彦宏是百度公司创始人董事长兼首席执行官,全面负责百度公司的战略规划和运营管理,经过多年发展,百度已经牢牢占据中文搜索引擎超过7成的市场份额。", "result_list": ["text": "百度公司", "start": 4, "end": 8], "prompt": "公司"
"content": "李彦宏是百度公司创始人董事长兼首席执行官,全面负责百度公司的战略规划和运营管理,经过多年发展,百度已经牢牢占据中文搜索引擎超过7成的市场份额。", "result_list": ["text": "李彦宏", "start": 0, "end": 3], "prompt": "百度公司的高管"
2.模型训练
import argparse
import time
import os
from functools import partial
import paddle
from paddle.utils.download import get_path_from_url
from paddlenlp.datasets import load_dataset
from paddlenlp.transformers import ErnieTokenizer
from model import UIE
from utils_1 import set_seed, convert_example, reader, evaluate, create_dataloader, SpanEvaluator
from visualdl import LogWriter
def do_train():
paddle.set_device(args.device)
rank = paddle.distributed.get_rank()
if paddle.distributed.get_world_size() > 1:
paddle.distributed.init_parallel_env()
set_seed(args.seed)
hidden_size = 768
url = "https://bj.bcebos.com/paddlenlp/taskflow/information_extraction/uie_base/model_state.pdparams"
tokenizer = ErnieTokenizer.from_pretrained('ernie-3.0-base-zh')
model = UIE('ernie-3.0-base-zh', hidden_size)
if args.init_from_ckpt is not None:
pretrained_model_path = args.init_from_ckpt
else:
pretrained_model_path = os.path.join(args.model, "model_state.pdparams")
if not os.path.exists(pretrained_model_path):
get_path_from_url(url, args.model)
state_dict = paddle.load(pretrained_model_path)
model.set_dict(state_dict)
print("Init from: ".format(pretrained_model_path))
if paddle.distributed.get_world_size() > 1:
model = paddle.DataParallel(model)
train_ds = load_dataset(
reader,
data_path=args.train_path,
max_seq_len=args.max_seq_len,
lazy=False)
dev_ds = load_dataset(
reader,
data_path=args.dev_path,
max_seq_len=args.max_seq_len,
lazy=False)
trans_func = partial(
convert_example, tokenizer=tokenizer, max_seq_len=args.max_seq_len)
train_data_loader = create_dataloader(
dataset=train_ds,
mode='train',
batch_size=args.batch_size,
trans_fn=trans_func)
dev_data_loader = create_dataloader(
dataset=dev_ds,
mode='dev',
batch_size=args.batch_size,
trans_fn=trans_func)
optimizer = paddle.optimizer.AdamW(
learning_rate=args.learning_rate, parameters=model.parameters())
criterion = paddle.nn.BCELoss()
metric = SpanEvaluator()
#初始化记录器
writer=LogWriter("./log/scalar_test")
writer1=LogWriter("./log/scalar_test1")
loss_list = []
global_step = 0
best_step = 0
best_f1 = 0
tic_train = time.time()
for epoch in range(1, args.num_epochs + 1):
for batch in train_data_loader:
input_ids, token_type_ids, att_mask, pos_ids, start_ids, end_ids = batch
start_prob, end_prob = model(input_ids, token_type_ids, att_mask,
pos_ids)
start_ids = paddle.cast(start_ids, 'float32')
end_ids = paddle.cast(end_ids, 'float32')
loss_start = criterion(start_prob, start_ids)
loss_end = criterion(end_prob, end_ids)
loss = (loss_start + loss_end) / 2.0
loss.backward()
optimizer.step()
optimizer.clear_grad()
loss_list.append(float(loss))
global_step += 1
if global_step % args.logging_steps == 0 and rank == 0:
time_diff = time.time() - tic_train
loss_avg = sum(loss_list) / len(loss_list)
writer.add_scalar(tag="train/loss", step=global_step, value=loss_avg) #记录loss
print(
"global step %d, epoch: %d, loss: %.5f, speed: %.2f step/s"
% (global_step, epoch, loss_avg,
args.logging_steps / time_diff))
tic_train = time.time()
if global_step % args.valid_steps == 0 and rank == 0:
# save_dir = os.path.join(args.save_dir, "model_%d" % global_step)
# if not os.path.exists(save_dir):
# os.makedirs(save_dir)
# save_param_path = os.path.join(save_dir, "model_state.pdparams")
# paddle.save(model.state_dict(), save_param_path)
precision, recall, f1 = evaluate(model, metric, dev_data_loader)
writer1.add_scalar(tag="train/precision", step=global_step, value=precision)
writer1.add_scalar(tag="train/recall", step=global_step, value=recall)
writer1.add_scalar(tag="train/f1", step=global_step, value=f1)
print("Evaluation precision: %.5f, recall: %.5f, F1: %.5f" %
(precision, recall, f1))
if f1 > best_f1:
print(
f"best F1 performence has been updated: best_f1:.5f --> f1:.5f"
)
best_f1 = f1
save_dir = os.path.join(args.save_dir, "model_best")
save_best_param_path = os.path.join(save_dir,
"model_state.pdparams")
paddle.save(model.state_dict(), save_best_param_path)
tic_train = time.time()
if __name__ == "__main__":
# yapf: disable
parser = argparse.ArgumentParser()
#!
parser.add_argument("--batch_size", default=2, type=int, help="Batch size per GPU/CPU for training.")
# parser.add_argument("--batch_size", default=16, type=int, help="Batch size per GPU/CPU for training.")
parser.add_argument("--learning_rate", default=1e-5, type=float, help="The initial learning rate for Adam.")
parser.add_argument("--train_path", default="./data/train.txt", type=str, help="The path of train set.")
parser.add_argument("--dev_path", default="./data/dev.txt", type=str, help="The path of dev set.")
parser.add_argument("--save_dir", default='./checkpoint', type=str, help="The output directory where the model checkpoints will be written.")
parser.add_argument("--max_seq_len"以上是关于Paddlenlp之UIE关系抽取模型高管关系抽取为例的主要内容,如果未能解决你的问题,请参考以下文章
PaddleNLP基于ERNIR3.0文本分类以CAIL2018-SMALL数据集罪名预测任务为例多标签
基于Ernie-3.0 CAIL2019法研杯要素识别多标签分类任务
PaddleNLP基于ERNIR3.0文本分类以中医疗搜索检索词意图分类(KUAKE-QIC)为例多分类(单标签)
Paddlenlp之UIE模型实战实体抽取任务打车数据快递单