乐趣区

关于算法:基于Ernie30-CAIL2019法研杯要素识别多标签分类任务

本我的项目链接:
基于 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,1
2014 年 8 月原、原告因感情不和分居,2014 年 10 月 16 日原告文某某向务川自治县人民法院提起离婚诉讼,被法院依法驳回了离婚诉讼请求。6,5
女儿由被告抚养,原告每月领取小孩抚养费 500 元;0,7,3,1

应用本地文件创建数据集,自定义 read_custom_data() 函数读取数据文件,传入 load_dataset() 创立数据集,返回数据类型为 MapDataset。更多数据集自定办法详见如何自定义数据集。

# 自定义数据集
import re

from paddlenlp.datasets import load_dataset

def 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, AutoTokenizer

model_name = "ernie-3.0-base-zh"
num_classes = 20
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_classes=num_classes)
tokenizer = AutoTokenizer.from_pretrained(model_name)

3.4 基于预训练模型的数据处理

Dataset中通常为原始数据,须要通过肯定的数据处理并进行采样组 batch。

  • 通过 Datasetmap函数,应用分词器将数据集从原始文本处理成模型的输出。
  • 定义 paddle.io.BatchSamplercollate_fn构建 paddle.io.DataLoader

理论训练中,依据显存大小调整批大小 batch_size 和文本最大长度max_seq_length

import functools
import numpy as np

from paddle.io import DataLoader, BatchSampler
from 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 result

trans_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,抉择批大小和是否随机乱序,进行 DataLoader
train_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 time
import paddle.nn.functional as F

from 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 evaluate
epochs = 5 # 训练轮次
ckpt_dir = "ernie_ckpt" #训练过程中保留模型参数的文件夹

global_step = 0 #迭代次数
tic_train = time.time()
best_f1_score = 0
for 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/s
global step 780, epoch: 4, batch: 105, loss: 0.03375, auc: 0.99591, f1 score: 0.92674, speed: 0.98 step/s
global step 790, epoch: 4, batch: 115, loss: 0.04217, auc: 0.99530, f1 score: 0.92483, speed: 0.80 step/s
global step 800, epoch: 4, batch: 125, loss: 0.05338, auc: 0.99534, f1 score: 0.92467, speed: 0.67 step/s
eval 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.json
global step 810, epoch: 4, batch: 135, loss: 0.04668, auc: 0.99509, f1 score: 0.91319, speed: 0.59 step/s
global step 820, epoch: 4, batch: 145, loss: 0.04317, auc: 0.99478, f1 score: 0.91696, speed: 0.98 step/s
global step 830, epoch: 4, batch: 155, loss: 0.04573, auc: 0.99488, f1 score: 0.91815, speed: 0.80 step/s
global step 840, epoch: 4, batch: 165, loss: 0.05505, auc: 0.99465, f1 score: 0.91753, speed: 0.65 step/s
eval loss: 0.05352, auc: 0.99234, f1 score: 0.89713, precison: 0.88058, recall: 0.91432
global step 850, epoch: 4, batch: 175, loss: 0.03971, auc: 0.99626, f1 score: 0.92391, speed: 0.76 step/s
global step 860, epoch: 4, batch: 185, loss: 0.04622, auc: 0.99593, f1 score: 0.91806, speed: 0.97 step/s
global step 870, epoch: 4, batch: 195, loss: 0.04128, auc: 0.99587, f1 score: 0.91959, speed: 0.77 step/s
global step 880, epoch: 4, batch: 205, loss: 0.06053, auc: 0.99566, f1 score: 0.92041, speed: 0.63 step/s
eval 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…

退出移动版