本我的项目链接:
基于Ernie-3.0 CAIL2019法研杯因素辨认多标签分类工作
本我的项目将介绍如何基于PaddleNLP对ERNIE 3.0预训练模型微调实现法律文本多标签分类预测。本我的项目次要包含“什么是多标签文本分类预测”、“ERNIE 3.0模型”、“如何应用ERNIE 3.0中文预训练模型进行法律文本多标签分类预测”等三个局部。
1. 什么是多标签文本分类预测
文本多标签分类是自然语言解决(NLP)中常见的文本分类工作,文本多标签分类在各种事实场景中具备宽泛的适用性,例如商品分类、网页标签、新闻标注、蛋白质性能分类、电影分类、语义场景分类等。多标签数据集中样本用来自 n_classes
个可能类别的m
个标签类别标记,其中m
的取值在0到n_classes
之间,这些类别具备不互相排挤的属性。通常,咱们将每个样本的标签用One-hot的模式示意,正类用1示意,负类用0示意。例如,数据集中样本可能标签是A、B和C的多标签分类问题,标签为[1,0,1]代表存在标签 A 和 C 而标签 B 不存在的样本。
近年来,随着司法改革的全面推动,“以公开为准则,不公开为例外”的政策逐渐确立,大量蕴含了案件事实及其实用法律条文信息的裁判文书逐步在互联网上公开,海量的数据使自然语言解决技术的利用成为可能。法律条文的组织呈树形层次结构,事实中的案情盘根错节,同一案件可能实用多项法律条文,波及数罪并罚,须要多标签模型充沛学习标签之间的关联性,对文本进行分类预测。
2. ERNIE 3.0模型
ERNIE 3.0首次在百亿级预训练模型中引入大规模常识图谱,提出了海量无监督文本与大规模常识图谱的平行预训练方法(Universal Knowledge-Text Prediction),通过将常识图谱开掘算法失去五千万常识图谱三元组与4TB大规模语料同时输出到预训练模型中进行联结掩码训练,促成了结构化常识和无构造文本之间的信息共享,大幅晋升了模型对于常识的记忆和推理能力。
ERNIE 3.0框架分为两层。第一层是通用语义示意网络,该网络学习数据中的根底和通用的常识。第二层是工作语义示意网络,该网络基于通用语义示意,学习工作相干的常识。在学习过程中,工作语义示意网络只学习对应类别的预训练任务,而通用语义示意网络会学习所有的预训练任务。
<font size=2><center>ERNIE 3.0模型框架</center></font>
3. ERNIE 3.0中文预训练模型进行法律文本多标签分类预测
3.1 环境筹备
AI Studio平台默认装置了Paddle和PaddleNLP,并定期更新版本。 如需手动更新Paddle,可参考飞桨装置阐明,装置相应环境下最新版飞桨框架。应用如下命令确保装置最新版PaddleNLP:
3.2 加载法律文本多标签数据
本数据集(2019年法研杯因素辨认工作)来自于“中国裁判文书网”公开的法律文书,每条训练数据由一份法律文书的案情形容片段形成,其中每个句子都被标记了对应的类别标签,数据集一共蕴含20个标签,标签代表含意如下:
DV1 0 婚后有子女DV2 1 限度行为能力子女抚养DV3 2 有夫妻共同财产DV4 3 领取抚养费DV5 4 不动产宰割DV6 5 婚后分居DV7 6 二次起诉离婚DV8 7 按月给付抚养费DV9 8 准予离婚DV10 9 有夫妻独特债权DV11 10 婚前个人财产DV12 11 法定离婚DV13 12 不履行家庭任务DV14 13 存在非婚生子DV15 14 适当帮忙DV16 15 不履行离婚协定DV17 16 损害赔偿DV18 17 感情不和分居满二年DV19 18 子女随非抚养权人生存DV20 19 婚后个人财产
数据集示例:
text labels所以起诉至法院申请变更两个孩子均由被告抚养,原告承当一个孩子抚养费每月600元。 0,7,3,12014年8月原、原告因感情不和分居,2014年10月16日原告文某某向务川自治县人民法院提起离婚诉讼,被法院依法驳回了离婚诉讼请求。 6,5女儿由被告抚养,原告每月领取小孩抚养费500元; 0,7,3,1
应用本地文件创建数据集,自定义read_custom_data()
函数读取数据文件,传入load_dataset()
创立数据集,返回数据类型为MapDataset。更多数据集自定办法详见如何自定义数据集。
# 自定义数据集import refrom paddlenlp.datasets import load_datasetdef clean_text(text): text = text.replace("\r", "").replace("\n", "") text = re.sub(r"\\n\n", ".", text) return text# 定义读取数据集函数def read_custom_data(is_test=False, is_one_hot=True): file_num = 6 if is_test else 48 #文件个数 filepath = 'raw_data/test/' if is_test else 'raw_data/train/' for i in range(file_num): f = open('{}labeled_{}.txt'.format(filepath, i)) while True: line = f.readline() if not line: break data = line.strip().split('\t') # 标签用One-hot示意 if is_one_hot: labels = [float(1) if str(i) in data[1].split(',') else float(0) for i in range(20)] else: labels = [int(d) for d in data[1].split(',')] yield {"text": clean_text(data[0]), "labels": labels} f.close()label_vocab = { 0: "婚后有子女", 1: "限度行为能力子女抚养", 2: "有夫妻共同财产", 3: "领取抚养费", 4: "不动产宰割", 5: "婚后分居", 6: "二次起诉离婚", 7: "按月给付抚养费", 8: "准予离婚", 9: "有夫妻独特债权", 10: "婚前个人财产", 11: "法定离婚", 12: "不履行家庭任务", 13: "存在非婚生子", 14: "适当帮忙", 15: "不履行离婚协定", 16: "损害赔偿", 17: "感情不和分居满二年", 18: "子女随非抚养权人生存", 19: "婚后个人财产"}
# load_dataset()创立数据集train_ds = load_dataset(read_custom_data, is_test=False, lazy=False) test_ds = load_dataset(read_custom_data, is_test=True, lazy=False)# lazy=False,数据集返回为MapDataset类型print("数据类型:", type(train_ds))# labels为One-hot标签print("训练集样例:", train_ds[0])print("测试集样例:", test_ds[0])
数据类型: <class 'paddlenlp.datasets.dataset.MapDataset'>训练集样例: {'text': '2013年11月28日原、原告离婚时被迫达成协议,婚生子张某乙由原告李某某抚养,本院以(2013)宝渭法民初字第01848号民事调解书对该协定内容予以了确认,该协定具备法律效力,对原、原告单方均有约束力。', 'labels': [1.0, 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]}测试集样例: {'text': '综上,被告现要求变更女儿李乙抚养关系的申请,本院应予反对。', 'labels': [1.0, 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]}
3.3 加载中文ERNIE 3.0预训练模型和分词器
PaddleNLP中Auto模块(包含AutoModel, AutoTokenizer及各种上游工作类)提供了不便易用的接口,无需指定模型类别,即可调用不同网络结构的预训练模型。PaddleNLP的预训练模型能够很容易地通过from_pretrained()办法加载,Transformer预训练模型汇总蕴含了40多个支流预训练模型,500多个模型权重。
AutoModelForSequenceClassification可用于多标签分类,通过预训练模型获取输出文本的示意,之后将文本示意进行分类。PaddleNLP曾经实现了ERNIE 3.0预训练模型,能够通过一行代码实现ERNIE 3.0预训练模型和分词器的加载。
# 加载中文ERNIE 3.0预训练模型和分词器from paddlenlp.transformers import AutoModelForSequenceClassification, AutoTokenizermodel_name = "ernie-3.0-base-zh"num_classes = 20model = AutoModelForSequenceClassification.from_pretrained(model_name, num_classes=num_classes)tokenizer = AutoTokenizer.from_pretrained(model_name)
3.4 基于预训练模型的数据处理
Dataset
中通常为原始数据,须要通过肯定的数据处理并进行采样组batch。
- 通过
Dataset
的map
函数,应用分词器将数据集从原始文本处理成模型的输出。 - 定义
paddle.io.BatchSampler
和collate_fn
构建paddle.io.DataLoader
。
理论训练中,依据显存大小调整批大小batch_size
和文本最大长度max_seq_length
。
import functoolsimport numpy as npfrom paddle.io import DataLoader, BatchSamplerfrom paddlenlp.data import DataCollatorWithPadding# 数据预处理函数,利用分词器将文本转化为整数序列def preprocess_function(examples, tokenizer, max_seq_length): result = tokenizer(text=examples["text"], max_seq_len=max_seq_length) result["labels"] = examples["labels"] return resulttrans_func = functools.partial(preprocess_function, tokenizer=tokenizer, max_seq_length=128)train_ds = train_ds.map(trans_func)test_ds = test_ds.map(trans_func)# collate_fn函数结构,将不同长度序列充到批中数据的最大长度,再将数据重叠collate_fn = DataCollatorWithPadding(tokenizer)# 定义BatchSampler,抉择批大小和是否随机乱序,进行DataLoadertrain_batch_sampler = BatchSampler(train_ds, batch_size=64, shuffle=True)test_batch_sampler = BatchSampler(test_ds, batch_size=64, shuffle=False)train_data_loader = DataLoader(dataset=train_ds, batch_sampler=train_batch_sampler, collate_fn=collate_fn)test_data_loader = DataLoader(dataset=test_ds, batch_sampler=test_batch_sampler, collate_fn=collate_fn)
3.5 数据训练和评估
定义训练所需的优化器、损失函数、评估指标等,就能够开始进行预模型微调工作。
import timeimport paddle.nn.functional as Ffrom metric import MultiLabelReport #文件在根目录下# Adam优化器、穿插熵损失函数、自定义MultiLabelReport评估指标optimizer = paddle.optimizer.AdamW(learning_rate=1e-4, parameters=model.parameters())criterion = paddle.nn.BCEWithLogitsLoss()metric = MultiLabelReport()
from eval import evaluateepochs = 5 # 训练轮次ckpt_dir = "ernie_ckpt" #训练过程中保留模型参数的文件夹global_step = 0 #迭代次数tic_train = time.time()best_f1_score = 0for epoch in range(1, epochs + 1): for step, batch in enumerate(train_data_loader, start=1): input_ids, token_type_ids, labels = batch['input_ids'], batch['token_type_ids'], batch['labels'] # 计算模型输入、损失函数值、分类概率值、准确率、f1分数 logits = model(input_ids, token_type_ids) loss = criterion(logits, labels) probs = F.sigmoid(logits) metric.update(probs, labels) auc, f1_score, _, _ = metric.accumulate() #auc, f1_score, precison, recall # 每迭代10次,打印损失函数值、准确率、f1分数、计算速度 global_step += 1 if global_step % 10 == 0: print( "global step %d, epoch: %d, batch: %d, loss: %.5f, auc: %.5f, f1 score: %.5f, speed: %.2f step/s" % (global_step, epoch, step, loss, auc, f1_score, 10 / (time.time() - tic_train))) tic_train = time.time() # 反向梯度回传,更新参数 loss.backward() optimizer.step() optimizer.clear_grad() # 每迭代40次,评估以后训练的模型、保留以后最佳模型参数和分词器的词表等 if global_step % 40 == 0: save_dir = ckpt_dir if not os.path.exists(save_dir): os.makedirs(save_dir) eval_f1_score = evaluate(model, criterion, metric, test_data_loader, label_vocab, if_return_results=False) if eval_f1_score > best_f1_score: best_f1_score = eval_f1_score model.save_pretrained(save_dir) tokenizer.save_pretrained(save_dir)
模型训练过程中会输入如下日志:
global step 770, epoch: 4, batch: 95, loss: 0.04217, auc: 0.99446, f1 score: 0.92639, speed: 0.61 step/sglobal step 780, epoch: 4, batch: 105, loss: 0.03375, auc: 0.99591, f1 score: 0.92674, speed: 0.98 step/sglobal step 790, epoch: 4, batch: 115, loss: 0.04217, auc: 0.99530, f1 score: 0.92483, speed: 0.80 step/sglobal step 800, epoch: 4, batch: 125, loss: 0.05338, auc: 0.99534, f1 score: 0.92467, speed: 0.67 step/seval loss: 0.05298, auc: 0.99185, f1 score: 0.90312, precison: 0.90031, recall: 0.90596[2022-07-27 16:31:27,917] [ INFO] - tokenizer config file saved in ernie_ckpt/tokenizer_config.json[2022-07-27 16:31:27,920] [ INFO] - Special tokens file saved in ernie_ckpt/special_tokens_map.jsonglobal step 810, epoch: 4, batch: 135, loss: 0.04668, auc: 0.99509, f1 score: 0.91319, speed: 0.59 step/sglobal step 820, epoch: 4, batch: 145, loss: 0.04317, auc: 0.99478, f1 score: 0.91696, speed: 0.98 step/sglobal step 830, epoch: 4, batch: 155, loss: 0.04573, auc: 0.99488, f1 score: 0.91815, speed: 0.80 step/sglobal step 840, epoch: 4, batch: 165, loss: 0.05505, auc: 0.99465, f1 score: 0.91753, speed: 0.65 step/seval loss: 0.05352, auc: 0.99234, f1 score: 0.89713, precison: 0.88058, recall: 0.91432global step 850, epoch: 4, batch: 175, loss: 0.03971, auc: 0.99626, f1 score: 0.92391, speed: 0.76 step/sglobal step 860, epoch: 4, batch: 185, loss: 0.04622, auc: 0.99593, f1 score: 0.91806, speed: 0.97 step/sglobal step 870, epoch: 4, batch: 195, loss: 0.04128, auc: 0.99587, f1 score: 0.91959, speed: 0.77 step/sglobal step 880, epoch: 4, batch: 205, loss: 0.06053, auc: 0.99566, f1 score: 0.92041, speed: 0.63 step/seval loss: 0.05234, auc: 0.99220, f1 score: 0.90272, precison: 0.89108, recall: 0.91466...
3.6 多标签分类预测后果预测
加载微调好的模型参数进行情感剖析预测,并保留预测后果
from eval import evaluate# 模型在测试集中体现model.set_dict(paddle.load('ernie_ckpt/model_state.pdparams'))# 也能够抉择加载事后训练好的模型参数后果查看模型训练后果# model.set_dict(paddle.load('ernie_ckpt_trained/model_state.pdparams'))print("ERNIE 3.0 在法律文本多标签分类test集体现", end= " ")results = evaluate(model, criterion, metric, test_data_loader, label_vocab)
ERNIE 3.0 在法律文本多标签分类test集体现 eval loss: 0.05298, auc: 0.99185, f1 score: 0.90312, precison: 0.90031, recall: 0.90596
test_ds = load_dataset(read_custom_data, is_test=True, is_one_hot=False, lazy=False)res_dir = "./results"if not os.path.exists(res_dir): os.makedirs(res_dir)with open(os.path.join(res_dir, "multi_label.tsv"), 'w', encoding="utf8") as f: f.write("text\tprediction\n") for i, pred in enumerate(results): f.write(test_ds[i]['text']+"\t"+pred+"\n")
法律多标签文本预测后果示例:
4.总结
相干我的项目:
Paddlenlp之UIE模型实战实体抽取工作【打车数据、快递单】
Paddlenlp之UIE分类模型【以情感偏向剖析新闻分类为例】含智能标注计划)
[利用实际:分类模型大集成者[PaddleHub、Finetune、prompt]](https://aistudio.baidu.com/ai...)
Paddlenlp之UIE关系抽取模型【高管关系抽取为例】
PaddleNLP基于ERNIR3.0文本分类以中医疗搜寻检索词用意分类(KUAKE-QIC)为例【多分类(单标签)】
基于ERNIR3.0文本分类:CAIL2018-SMALL罪名预测为例(多标签)
本我的项目次要解说了法律工作,和对性能指标的简略探讨,能够看到理论更多问题是对于多标签分类的。
China AI & Law Challenge (CAIL)
中国法研杯司法人工智能挑战赛
本我的项目数据集:https://github.com/china-ai-l...
数据集自取:
欢送大家关注我的主页:https://aistudio.baidu.com/ai...
以及博客:https://blog.csdn.net/sinat_3...