基于Ernie-3.0 CAIL2019法研杯要素識別多標(biāo)簽分類任務(wù)
本項(xiàng)目鏈接: 基于Ernie-3.0 CAIL2019法研杯要素識別多標(biāo)簽分類任務(wù)
(https://aistudio.baidu.com/aistudio/projectdetail/4280922?contributionType=1)
本項(xiàng)目將介紹如何基于PaddleNLP對ERNIE 3.0預(yù)訓(xùn)練模型微調(diào)完成法律文本多標(biāo)簽分類預(yù)測。本項(xiàng)目主要包括“什么是多標(biāo)簽文本分類預(yù)測”、“ERNIE 3.0模型”、“如何使用ERNIE 3.0中文預(yù)訓(xùn)練模型進(jìn)行法律文本多標(biāo)簽分類預(yù)測”等三個(gè)部分。
1. 什么是多標(biāo)簽文本分類預(yù)測
文本多標(biāo)簽分類是自然語言處理(NLP)中常見的文本分類任務(wù),文本多標(biāo)簽分類在各種現(xiàn)實(shí)場景中具有廣泛的適用性,例如商品分類、網(wǎng)頁標(biāo)簽、新聞標(biāo)注、蛋白質(zhì)功能分類、電影分類、語義場景分類等。多標(biāo)簽數(shù)據(jù)集中樣本用來自 n_classes 個(gè)可能類別的m個(gè)標(biāo)簽類別標(biāo)記,其中m的取值在0到n_classes之間,這些類別具有不相互排斥的屬性。通常,我們將每個(gè)樣本的標(biāo)簽用One-hot的形式表示,正類用1表示,負(fù)類用0表示。例如,數(shù)據(jù)集中樣本可能標(biāo)簽是A、B和C的多標(biāo)簽分類問題,標(biāo)簽為[1,0,1]代表存在標(biāo)簽 A 和 C 而標(biāo)簽 B 不存在的樣本。
近年來,隨著司法改革的全面推進(jìn),“以公開為原則,不公開為例外”的政策逐步確立,大量包含了案件事實(shí)及其適用法律條文信息的裁判文書逐漸在互聯(lián)網(wǎng)上公開,海量的數(shù)據(jù)使自然語言處理技術(shù)的應(yīng)用成為可能。法律條文的組織呈樹形層次結(jié)構(gòu),現(xiàn)實(shí)中的案情錯(cuò)綜復(fù)雜,同一案件可能適用多項(xiàng)法律條文,涉及數(shù)罪并罰,需要多標(biāo)簽?zāi)P统浞謱W(xué)習(xí)標(biāo)簽之間的關(guān)聯(lián)性,對文本進(jìn)行分類預(yù)測。
2. ERNIE 3.0模型
ERNIE 3.0首次在百億級預(yù)訓(xùn)練模型中引入大規(guī)模知識圖譜,提出了海量無監(jiān)督文本與大規(guī)模知識圖譜的平行預(yù)訓(xùn)練方法(Universal Knowledge-Text Prediction),通過將知識圖譜挖掘算法得到五千萬知識圖譜三元組與4TB大規(guī)模語料同時(shí)輸入到預(yù)訓(xùn)練模型中進(jìn)行聯(lián)合掩碼訓(xùn)練,促進(jìn)了結(jié)構(gòu)化知識和無結(jié)構(gòu)文本之間的信息共享,大幅提升了模型對于知識的記憶和推理能力。
ERNIE 3.0框架分為兩層。第一層是通用語義表示網(wǎng)絡(luò),該網(wǎng)絡(luò)學(xué)習(xí)數(shù)據(jù)中的基礎(chǔ)和通用的知識。第二層是任務(wù)語義表示網(wǎng)絡(luò),該網(wǎng)絡(luò)基于通用語義表示,學(xué)習(xí)任務(wù)相關(guān)的知識。在學(xué)習(xí)過程中,任務(wù)語義表示網(wǎng)絡(luò)只學(xué)習(xí)對應(yīng)類別的預(yù)訓(xùn)練任務(wù),而通用語義表示網(wǎng)絡(luò)會學(xué)習(xí)所有的預(yù)訓(xùn)練任務(wù)。

ERNIE 3.0模型框架
3. ERNIE 3.0中文預(yù)訓(xùn)練模型進(jìn)行法律文本多標(biāo)簽分類預(yù)測
3.1 環(huán)境準(zhǔn)備
AI Studio平臺默認(rèn)安裝了Paddle和PaddleNLP,并定期更新版本。 如需手動更新Paddle,可參考飛槳安裝說明,安裝相應(yīng)環(huán)境下最新版飛槳框架。使用如下命令確保安裝最新版PaddleNLP:
3.2 加載法律文本多標(biāo)簽數(shù)據(jù)
本數(shù)據(jù)集(2019年法研杯要素識別任務(wù))來自于“中國裁判文書網(wǎng)”公開的法律文書,每條訓(xùn)練數(shù)據(jù)由一份法律文書的案情描述片段構(gòu)成,其中每個(gè)句子都被標(biāo)記了對應(yīng)的類別標(biāo)簽,數(shù)據(jù)集一共包含20個(gè)標(biāo)簽,標(biāo)簽代表含義如下:
DV1 ? ?0 ? ?婚后有子女
DV2 ? ?1 ? ?限制行為能力子女撫養(yǎng)
DV3 ? ?2 ? ?有夫妻共同財(cái)產(chǎn)
DV4 ? ?3 ? ?支付撫養(yǎng)費(fèi)
DV5 ? ?4 ? ?不動產(chǎn)分割
DV6 ? ?5 ? ?婚后分居
DV7 ? ?6 ? ?二次起訴離婚
DV8 ? ?7 ? ?按月給付撫養(yǎng)費(fèi)
DV9 ? ?8 ? ?準(zhǔn)予離婚
DV10 ? ?9 ? ?有夫妻共同債務(wù)
DV11 ? ?10 ? ?婚前個(gè)人財(cái)產(chǎn)
DV12 ? ?11 ? ?法定離婚
DV13 ? ?12 ? ?不履行家庭義務(wù)
DV14 ? ?13 ? ?存在非婚生子
DV15 ? ?14 ? ?適當(dāng)幫助
DV16 ? ?15 ? ?不履行離婚協(xié)議
DV17 ? ?16 ? ?損害賠償
DV18 ? ?17 ? ?感情不和分居滿二年
DV19 ? ?18 ? ?子女隨非撫養(yǎng)權(quán)人生活
DV20 ? ?19 ? ?婚后個(gè)人財(cái)產(chǎn)
數(shù)據(jù)集示例:
text ? ?labels
所以起訴至法院請求變更兩個(gè)孩子均由原告撫養(yǎng),被告承擔(dān)一個(gè)孩子撫養(yǎng)費(fèi)每月600元。 ? ?0,7,3,1
2014年8月原、被告因感情不和分居,2014年10月16日被告文某某向務(wù)川自治縣人民法院提起離婚訴訟,被法院依法駁回了離婚訴訟請求。 ? ?6,5
女兒由原告撫養(yǎng),被告每月支付小孩撫養(yǎng)費(fèi)500元; ? ?0,7,3,1
使用本地文件創(chuàng)建數(shù)據(jù)集,自定義read_custom_data()函數(shù)讀取數(shù)據(jù)文件,傳入load_dataset()創(chuàng)建數(shù)據(jù)集,返回?cái)?shù)據(jù)類型為MapDataset。更多數(shù)據(jù)集自定方法詳見如何自定義數(shù)據(jù)集。
# 自定義數(shù)據(jù)集
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
# 定義讀取數(shù)據(jù)集函數(shù)
def read_custom_data(is_test=False, is_one_hot=True):
? ?file_num = 6 if is_test else 48 ?#文件個(gè)數(shù)
? ?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')
? ? ? ? ? ?# 標(biāo)簽用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: "限制行為能力子女撫養(yǎng)",
? ?2: "有夫妻共同財(cái)產(chǎn)",
? ?3: "支付撫養(yǎng)費(fèi)",
? ?4: "不動產(chǎn)分割",
? ?5: "婚后分居",
? ?6: "二次起訴離婚",
? ?7: "按月給付撫養(yǎng)費(fèi)",
? ?8: "準(zhǔn)予離婚",
? ?9: "有夫妻共同債務(wù)",
? ?10: "婚前個(gè)人財(cái)產(chǎn)",
? ?11: "法定離婚",
? ?12: "不履行家庭義務(wù)",
? ?13: "存在非婚生子",
? ?14: "適當(dāng)幫助",
? ?15: "不履行離婚協(xié)議",
? ?16: "損害賠償",
? ?17: "感情不和分居滿二年",
? ?18: "子女隨非撫養(yǎng)權(quán)人生活",
? ?19: "婚后個(gè)人財(cái)產(chǎn)"
}
# load_dataset()創(chuàng)建數(shù)據(jù)集
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,數(shù)據(jù)集返回為MapDataset類型
print("數(shù)據(jù)類型:", type(train_ds))
# labels為One-hot標(biāo)簽
print("訓(xùn)練集樣例:", train_ds[0])
print("測試集樣例:", test_ds[0])
數(shù)據(jù)類型: <class 'paddlenlp.datasets.dataset.MapDataset'>
訓(xùn)練集樣例: {'text': '2013年11月28日原、被告離婚時(shí)自愿達(dá)成協(xié)議,婚生子張某乙由被告李某某撫養(yǎng),本院以(2013)寶渭法民初字第01848號民事調(diào)解書對該協(xié)議內(nèi)容予以了確認(rèn),該協(xié)議具有法律效力,對原、被告雙方均有約束力。', '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': '綜上,原告現(xiàn)要求變更女兒李乙撫養(yǎng)關(guān)系的請求,本院應(yīng)予支持。', '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預(yù)訓(xùn)練模型和分詞器
PaddleNLP中Auto模塊(包括AutoModel, AutoTokenizer及各種下游任務(wù)類)提供了方便易用的接口,無需指定模型類別,即可調(diào)用不同網(wǎng)絡(luò)結(jié)構(gòu)的預(yù)訓(xùn)練模型。PaddleNLP的預(yù)訓(xùn)練模型可以很容易地通過from_pretrained()方法加載,Transformer預(yù)訓(xùn)練模型匯總包含了40多個(gè)主流預(yù)訓(xùn)練模型,500多個(gè)模型權(quán)重。
AutoModelForSequenceClassification可用于多標(biāo)簽分類,通過預(yù)訓(xùn)練模型獲取輸入文本的表示,之后將文本表示進(jìn)行分類。PaddleNLP已經(jīng)實(shí)現(xiàn)了ERNIE 3.0預(yù)訓(xùn)練模型,可以通過一行代碼實(shí)現(xiàn)ERNIE 3.0預(yù)訓(xùn)練模型和分詞器的加載。
# 加載中文ERNIE 3.0預(yù)訓(xùn)練模型和分詞器
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 基于預(yù)訓(xùn)練模型的數(shù)據(jù)處理
Dataset中通常為原始數(shù)據(jù),需要經(jīng)過一定的數(shù)據(jù)處理并進(jìn)行采樣組batch。
通過Dataset的map函數(shù),使用分詞器將數(shù)據(jù)集從原始文本處理成模型的輸入。
定義paddle.io.BatchSampler和collate_fn構(gòu)建 paddle.io.DataLoader。
實(shí)際訓(xùn)練中,根據(jù)顯存大小調(diào)整批大小batch_size和文本最大長度max_seq_length。
import functools
import numpy as np
from paddle.io import DataLoader, BatchSampler
from paddlenlp.data import DataCollatorWithPadding
# 數(shù)據(jù)預(yù)處理函數(shù),利用分詞器將文本轉(zhuǎn)化為整數(shù)序列
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函數(shù)構(gòu)造,將不同長度序列充到批中數(shù)據(jù)的最大長度,再將數(shù)據(jù)堆疊
collate_fn = DataCollatorWithPadding(tokenizer)
# 定義BatchSampler,選擇批大小和是否隨機(jī)亂序,進(jìn)行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 數(shù)據(jù)訓(xùn)練和評估
定義訓(xùn)練所需的優(yōu)化器、損失函數(shù)、評價(jià)指標(biāo)等,就可以開始進(jìn)行預(yù)模型微調(diào)任務(wù)。
import time
import paddle.nn.functional as F
from metric import MultiLabelReport ?#文件在根目錄下
# Adam優(yōu)化器、交叉熵?fù)p失函數(shù)、自定義MultiLabelReport評價(jià)指標(biāo)
optimizer = paddle.optimizer.AdamW(learning_rate=1e-4, parameters=model.parameters())
criterion = paddle.nn.BCEWithLogitsLoss()
metric = MultiLabelReport()
from eval import evaluate
epochs = 5 # 訓(xùn)練輪次
ckpt_dir = "ernie_ckpt" #訓(xùn)練過程中保存模型參數(shù)的文件夾
global_step = 0 #迭代次數(shù)
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']
? ? ? ?# 計(jì)算模型輸出、損失函數(shù)值、分類概率值、準(zhǔn)確率、f1分?jǐn)?shù)
? ? ? ?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次,打印損失函數(shù)值、準(zhǔn)確率、f1分?jǐn)?shù)、計(jì)算速度
? ? ? ?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()
? ? ? ?# 反向梯度回傳,更新參數(shù)
? ? ? ?loss.backward()
? ? ? ?optimizer.step()
? ? ? ?optimizer.clear_grad()
? ? ? ?# 每迭代40次,評估當(dāng)前訓(xùn)練的模型、保存當(dāng)前最佳模型參數(shù)和分詞器的詞表等
? ? ? ?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)
模型訓(xùn)練過程中會輸出如下日志:
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 多標(biāo)簽分類預(yù)測結(jié)果預(yù)測
加載微調(diào)好的模型參數(shù)進(jìn)行情感分析預(yù)測,并保存預(yù)測結(jié)果
from eval import evaluate
# 模型在測試集中表現(xiàn)
model.set_dict(paddle.load('ernie_ckpt/model_state.pdparams'))
# 也可以選擇加載預(yù)先訓(xùn)練好的模型參數(shù)結(jié)果查看模型訓(xùn)練結(jié)果
# model.set_dict(paddle.load('ernie_ckpt_trained/model_state.pdparams'))
print("ERNIE 3.0 在法律文本多標(biāo)簽分類test集表現(xiàn)", end= " ")
results = evaluate(model, criterion, metric, test_data_loader, label_vocab)
ERNIE 3.0 在法律文本多標(biāo)簽分類test集表現(xiàn) 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")
法律多標(biāo)簽文本預(yù)測結(jié)果示例:

4.總結(jié)
相關(guān)項(xiàng)目:
Paddlenlp之UIE模型實(shí)戰(zhàn)實(shí)體抽取任務(wù)【打車數(shù)據(jù)、快遞單】
Paddlenlp之UIE分類模型【以情感傾向分析新聞分類為例】含智能標(biāo)注方案)
應(yīng)用實(shí)踐:分類模型大集成者[PaddleHub、Finetune、prompt]
Paddlenlp之UIE關(guān)系抽取模型【高管關(guān)系抽取為例】
PaddleNLP基于ERNIR3.0文本分類以中醫(yī)療搜索檢索詞意圖分類(KUAKE-QIC)為例【多分類(單標(biāo)簽)】
基于ERNIR3.0文本分類:CAIL2018-SMALL罪名預(yù)測為例(多標(biāo)簽)
本項(xiàng)目主要講解了法律任務(wù),和對性能指標(biāo)的簡單探討,可以看到實(shí)際更多問題是關(guān)于多標(biāo)簽分類的。
China AI & Law Challenge (CAIL) 中國法研杯司法人工智能挑戰(zhàn)賽 本項(xiàng)目數(shù)據(jù)集:https://github.com/china-ai-law-challenge/CAIL2019/tree/master/%E8%A6%81%E7%B4%A0%E8%AF%86%E5%88%AB
數(shù)據(jù)集自取:

歡迎大家關(guān)注我的主頁:https://aistudio.baidu.com/aistudio/usercenter
以及博客:https://blog.csdn.net/sinat_39620217?type=blog