天天看點

基于Ernie-3.0 CAIL2019法研杯要素識别多标簽分類任務

作者:汀丶人工智能

本項目連結: 基于Ernie-3.0 CAIL2019法研杯要素識别多标簽分類任務

(https://aistudio.baidu.com/aistudio/projectdetail/4280922?contributionType=1)

本項目将介紹如何基于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架構分為兩層。第一層是通用語義表示網絡,該網絡學習資料中的基礎和通用的知識。第二層是任務語義表示網絡,該網絡基于通用語義表示,學習任務相關的知識。在學習過程中,任務語義表示網絡隻學習對應類别的預訓練任務,而通用語義表示網絡會學習所有的預訓練任務。

基于Ernie-3.0 CAIL2019法研杯要素識别多标簽分類任務

ERNIE 3.0模型架構

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。

  • 通過Dataset的map函數,使用分詞器将資料集從原始文本處理成模型的輸入。
  • 定義paddle.io.BatchSampler和collate_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")
           

法律多标簽文本預測結果示例:

基于Ernie-3.0 CAIL2019法研杯要素識别多标簽分類任務

4.總結

相關項目:

Paddlenlp之UIE模型實戰實體抽取任務【打車資料、快遞單】

Paddlenlp之UIE分類模型【以情感傾向分析新聞分類為例】含智能标注方案)

應用實踐:分類模型大內建者[PaddleHub、Finetune、prompt]

Paddlenlp之UIE關系抽取模型【高管關系抽取為例】

PaddleNLP基于ERNIR3.0文本分類以中醫療搜尋檢索詞意圖分類(KUAKE-QIC)為例【多分類(單标簽)】

基于ERNIR3.0文本分類:CAIL2018-SMALL罪名預測為例(多标簽)

本項目主要講解了法律任務,和對性能名額的簡單探讨,可以看到實際更多問題是關于多标簽分類的。

China AI & Law Challenge (CAIL) 中國法研杯司法人工智能挑戰賽 本項目資料集:https://github.com/china-ai-law-challenge/CAIL2019/tree/master/%E8%A6%81%E7%B4%A0%E8%AF%86%E5%88%AB

資料集自取:

基于Ernie-3.0 CAIL2019法研杯要素識别多标簽分類任務

歡迎大家關注我的首頁:https://aistudio.baidu.com/aistudio/usercenter

以及部落格:https://blog.csdn.net/sinat_39620217?type=blog

繼續閱讀