天天看點

基于ERNIR文本分類以中醫療搜尋檢索詞意圖分類(KUAKE-QIC)為例

作者:汀丶人工智能

相關項目連結: Paddlenlp之UIE模型實戰實體抽取任務【打車資料、快遞單】

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

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

Paddlenlp之UIE關系抽取模型【高管關系抽取為例】 本項目連結: [PaddleNLP基于ERNIR3.0文本分類任務詳解【多分類(單标簽)】 ](https://aistudio.baidu.com/aistudio/projectdetail/4362154?contributionType=1)

https://aistudio.baidu.com/aistudio/projectdetail/4362154?contributionType=1

0.前言:文本分類任務介紹

文本分類任務是自然語言進行中最常見的任務,文本分類任務簡單來說就是對給定的一個句子或一段文本使用文本分類器進行分類。文本分類任務廣泛應用于長短文本分類、情感分析、新聞分類、事件類别分類、政務資料分類、商品資訊分類、商品類目預測、文章分類、論文類别分類、專利分類、案件描述分類、罪名分類、意圖分類、論文專利分類、郵件自動标簽、評論正負識别、藥物反應分類、對話分類、稅種識别、來電資訊自動分類、投訴分類、廣告檢測、敏感違法内容檢測、内容安全檢測、輿情分析、話題标記等各類日常或專業領域中。

文本分類任務可以根據标簽類型分為多分類(multi class)、多标簽(multi label)、層次分類(hierarchical等三類任務,接下來我們将以下圖的新聞文本分類為例介紹三種分類任務的差別。

PaddleNLP采用AutoModelForSequenceClassification, AutoTokenizer提供了友善易用的接口,可指定模型名或模型參數檔案路徑通過from_pretrained() 方法加載不同網絡結構的預訓練模型,并在輸出層上疊加一層線性層,且相應預訓練模型權重下載下傳速度快、穩定。Transformer預訓練模型彙總包含了如 ERNIE、BERT、RoBERTa等40多個主流預訓練模型,500多個模型權重。下面以ERNIE 3.0 中文base模型為例,示範如何加載預訓練模型和分詞器:

from paddlenlp.transformers import AutoModelForSequenceClassification, AutoTokenizer
num_classes = 10
model_name = "ernie-3.0-base-zh"
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_classes=num_classes)
tokenizer = AutoTokenizer.from_pretrained(model_name)
           

1.資料準備

1.1加載資料集、自定義資料集

通過使用PaddleNLP提供的 load_dataset, MapDataset 和 IterDataset ,可以友善地自定義屬于自己的資料集。

目前PaddleNLP的通用資料處理流程如下:

  1. 加載資料集(内置資料集或者自定義資料集,資料集傳回 原始資料)。
  2. 定義 trans_func() ,包括tokenize,token to id等操作,并傳入資料集的 map() 方法,将原始資料轉為 feature 。
  3. 根據上一步資料處理的結果定義 batchify 方法和 BatchSampler 。
  4. 定義 DataLoader , 傳入 BatchSampler 和 batchify_fn() 。

PaddleNLP Datasets API:供參考

PaddleNLP提供了以下資料集的快速讀取API,實際使用時請根據需要添加splits資訊:

加載資料集

快速加載内置資料集 目前PaddleNLP内置20餘個NLP資料集,涵蓋閱讀了解,文本分類,序列标注,機器翻譯等多項任務。目前提供的資料集可以在 資料集清單 中找到。

以 msra_ner 資料集為例:

loaddataset() 方法會從 paddlenlp.datasets 下找到msraner資料集對應的資料讀取腳本(預設路徑:paddlenlp/datasets/msra_ner.py),并調用腳本中 DatasetBuilder 類的相關方法生成資料集。

生成資料集可以以 MapDataset 和 IterDataset 兩種類型傳回,分别是對 paddle.io.Dataset 和 paddle.io.IterableDataset 的擴充,隻需在 load_dataset() 時設定 lazy 參數即可擷取相應類型。Flase 對應傳回 MapDataset ,True 對應傳回 IterDataset,預設值為None,對應傳回 DatasetBuilder 預設的資料集類型,大多數為 MapDataset

1.1.1以内置資料集格式讀取本地資料集

有的時候,我們希望使用資料格式與内置資料集相同的本地資料替換某些内置資料集的資料(例如參加SQuAD競賽,對訓練資料進行了資料增強)。 loaddataset() 方法提供的 datafiles參數可以實作這個功能。以 SQuAD 為例。

from paddlenlp.datasets import load_dataset
 train_ds, dev_ds = load_dataset("squad", data_files=("my_train_file.json", "my_dev_file.json"))
 test_ds = load_dataset("squad", data_files="my_test_file.json")
           

注解

對于某些資料集,不同的split的讀取方式不同。對于這種情況則需要在 splits 參數中以傳入與 data_files 一一對應 的split資訊。

此時 splits 不再代表選取的内置資料集,而代表以何種格式讀取本地資料集。

下面以 COLA 資料集為例:

from paddlenlp.datasets import load_dataset
train_ds, test_ds = load_dataset("glue", "cola", splits=["train", "test"], data_files=["my_train_file.csv", "my_test_file.csv"])
           

另外需要注意資料集的是沒有預設加載選項的,splits 和data_files 必須至少指定一個。

這個方法還是比較簡單的

需要注意的是格式要一緻!!!!,可以寫程式轉換一下

### 1.1.2 自定義資料集 通過使用PaddleNLP提供的 load_dataset() , MapDataset 和 IterDataset 。任何人都可以友善的定義屬于自己的資料集。

從本地檔案建立資料集 從本地檔案建立資料集時,我們 推薦 根據本地資料集的格式給出讀取function并傳入 load_dataset() 中建立資料集。

以 waybill_ie 快遞單資訊抽取任務中的資料為例:

from paddlenlp.datasets import load_dataset

def read(data_path):
    with open(data_path, 'r', encoding='utf-8') as f:
        # 跳過列名
        next(f)
        for line in f:
            words, labels = line.strip('\n').split('\t')
            words = words.split('\002')
            labels = labels.split('\002')
            yield {'tokens': words, 'labels': labels}

# data_path為read()方法的參數
map_ds = load_dataset(read, data_path='資料集/data1/dev.txt', lazy=False)
iter_ds = load_dataset(read, data_path='資料集/data1/dev.txt', lazy=True)

for i in range(3):
    print(map_ds[i])
           
{'tokens': ['喻', '曉', '剛', '雲', '南', '省', '楚', '雄', '彜', '族', '自', '治', '州', '南', '華', '縣', '東', '街', '古', '城', '路', '3', '7', '号', '1', '8', '5', '1', '3', '3', '8', '6', '1', '6', '3'], 'labels': ['P-B', 'P-I', 'P-I', 'A1-B', 'A1-I', 'A1-I', 'A2-B', 'A2-I', 'A2-I', 'A2-I', 'A2-I', 'A2-I', 'A2-I', 'A3-B', 'A3-I', 'A3-I', 'A4-B', 'A4-I', 'A4-I', 'A4-I', 'A4-I', 'A4-I', 'A4-I', 'A4-I', 'T-B', 'T-I', 'T-I', 'T-I', 'T-I', 'T-I', 'T-I', 'T-I', 'T-I', 'T-I', 'T-I']}
{'tokens': ['1', '3', '4', '2', '6', '3', '3', '8', '1', '3', '5', '寇', '銘', '哲', '黑', '龍', '江', '省', '七', '台', '河', '市', '桃', '山', '區', '風', '采', '路', '朝', '陽', '廣', '場'], 'labels': ['T-B', 'T-I', 'T-I', 'T-I', 'T-I', 'T-I', 'T-I', 'T-I', 'T-I', 'T-I', 'T-I', 'P-B', 'P-I', 'P-I', 'A1-B', 'A1-I', 'A1-I', 'A1-I', 'A2-B', 'A2-I', 'A2-I', 'A2-I', 'A3-B', 'A3-I', 'A3-I', 'A4-B', 'A4-I', 'A4-I', 'A4-I', 'A4-I', 'A4-I', 'A4-I']}
{'tokens': ['湖', '南', '省', '長', '沙', '市', '嶽', '麓', '區', '銀', '杉', '路', '3', '1', '号', '綠', '地', '中', '央', '廣', '場', '7', '棟', '2', '1', '樓', '須', '平', '盛', '1', '3', '6', '0', '1', '2', '6', '9', '5', '3', '8'], 'labels': ['A1-B', 'A1-I', 'A1-I', 'A2-B', 'A2-I', 'A2-I', 'A3-B', 'A3-I', 'A3-I', 'A4-B', 'A4-I', 'A4-I', 'A4-I', 'A4-I', 'A4-I', 'A4-I', 'A4-I', 'A4-I', 'A4-I', 'A4-I', 'A4-I', 'A4-I', 'A4-I', 'A4-I', 'A4-I', 'A4-I', 'P-B', 'P-I', 'P-I', 'T-B', 'T-I', 'T-I', 'T-I', 'T-I', 'T-I', 'T-I', 'T-I', 'T-I', 'T-I', 'T-I']}
           
from paddlenlp.datasets import load_dataset

def read(data_path):
    with open(data_path, 'r', encoding='utf-8') as f:
        # 跳過列名
        next(f)
        for line in f:
            words, labels = line.strip('\n').split(' ')
            # words = words.split('\002')
            # labels = labels.split('')       #分類問題内容和标簽一般不需要再分割
            yield {'connect': words, 'labels': labels}

# data_path為read()方法的參數
map_ds = load_dataset(read, data_path='資料集/input.txt', lazy=False)
# iter_ds = load_dataset(read, data_path='資料集/dev.txt', lazy=True)

# train= load_dataset(read, data_path='資料集/input_train.txt', lazy=False)
# dev= load_dataset(read, data_path='資料集/input_dev.txt', lazy=False) #自定義好訓練測試集
for i in range(3):
    print(map_ds[i])
           

{'connect': '出欄一頭豬虧損300元,究竟誰能笑到最後!', 'labels': '金融'} {'connect': '區塊鍊投資心得,能做到就不會虧錢', 'labels': '金融'} {'connect': '你家拆遷,要錢還是要房?答案一目了然。', 'labels': '房産'}

api接口文檔 https://paddlenlp.readthedocs.io/zh/latest/source/paddlenlp.datasets.dataset.html

推薦将資料讀取代碼寫成生成器(generator)的形式,這樣可以更好的建構 MapDataset 和 IterDataset 兩種資料集。同時也推薦将單條資料寫成字典的格式,這樣可以更友善的監測資料流向。

事實上,MapDataset 在絕大多數時候都可以滿足要求。一般隻有在資料集過于龐大無法一次性加載進記憶體的時候我們才考慮使用 IterDataset 。任何人都可以友善的定義屬于自己的資料集。

注解:

  1. 需要注意的是,隻有PaddleNLP内置的資料集具有将資料中的label自動轉為id的功能(詳細條件參見 建立DatasetBuilder)。
  2. 像上例中的自定義資料集需要在自定義的convert to feature方法中添加label轉id的功能。
  3. 自定義資料讀取function中的參數可以直接以關鍵字參數的的方式傳入 load_dataset() 中。而且對于自定義資料集,lazy 參數是必須傳入的。

2.基于ERNIR3.0文本分類任務模型微調

save_dir:儲存訓練模型的目錄;預設儲存在目前目錄checkpoint檔案夾下。

dataset:訓練資料集;預設為"cblue"。

dataset_dir:本地資料集路徑,資料集路徑中應包含train.txt,dev.txt和label.txt檔案;預設為None。

task_name:訓練資料集;預設為"KUAKE-QIC"。

maxseqlength:ERNIE模型使用的最大序列長度,最大不能超過512, 若出現顯存不足,請适當調低這一參數;預設為128。

model_name:選擇預訓練模型;預設為"ernie-3.0-base-zh"。

device: 選用什麼裝置進行訓練,可選cpu、gpu、xpu、npu。如使用gpu訓練,可使用參數gpus指定GPU卡号。

batch_size:批處理大小,請結合顯存情況進行調整,若出現顯存不足,請适當調低這一參數;預設為32。

learning_rate:Fine-tune的最大學習率;預設為6e-5。

weight_decay:控制正則項力度的參數,用于防止過拟合,預設為0.01。

early_stop:選擇是否使用早停法(EarlyStopping);預設為False。

earlystopnums:在設定的早停訓練輪次内,模型在開發集上表現不再上升,訓練終止;預設為4。 epochs: 訓練輪次,預設為100。

warmup:是否使用學習率warmup政策;預設為False。

warmupproportion:學習率warmup政策的比例數,如果設為0.1,則學習率會在前10%steps數從0慢慢增長到learningrate, 而後再緩慢衰減;預設為0.1。

logging_steps: 日志列印的間隔steps數,預設5。

initfromckpt: 模型初始checkpoint參數位址,預設None。

seed:随機種子,預設為3。

parser.add_argument("--save_dir",
                    default="./checkpoint",
                    type=str,
                    help="The output directory where the model "
                    "checkpoints will be written.")
parser.add_argument("--dataset",
                    default="cblue",
                    type=str,
                    help="Dataset for text classfication.")
parser.add_argument("--dataset_dir",
                    default=None,
                    type=str,
                    help="Local dataset directory should include"
                    "train.txt, dev.txt and label.txt")
parser.add_argument("--task_name",
                    default="KUAKE-QIC",
                    type=str,
                    help="Task name for text classfication dataset.")
parser.add_argument("--max_seq_length",
                    default=128,
                    type=int,
                    help="The maximum total input sequence length"
                    "after tokenization. Sequences longer than this "
                    "will be truncated, sequences shorter will be padded.")
parser.add_argument('--model_name',
                    default="ernie-3.0-base-zh",
                    help="Select model to train, defaults "
                    "to ernie-3.0-base-zh.")
parser.add_argument('--device',
                    choices=['cpu', 'gpu', 'xpu', 'npu'],
                    default="gpu",
                    help="Select which device to train model, defaults to gpu.")
parser.add_argument("--batch_size",
                    default=32,
                    type=int,
                    help="Batch size per GPU/CPU for training.")
parser.add_argument("--learning_rate",
                    default=6e-5,
                    type=float,
                    help="The initial learning rate for Adam.")
parser.add_argument("--weight_decay",
                    default=0.01,
                    type=float,
                    help="Weight decay if we apply some.")
parser.add_argument('--early_stop',
                    action='store_true',
                    help='Epoch before early stop.')
parser.add_argument('--early_stop_nums',
                    type=int,
                    default=4,
                    help='Number of epoch before early stop.')
parser.add_argument("--epochs",
                    default=100,
                    type=int,
                    help="Total number of training epochs to perform.")
parser.add_argument('--warmup',
                    action='store_true',
                    help="whether use warmup strategy")
parser.add_argument('--warmup_proportion',
                    default=0.1,
                    type=float,
                    help="Linear warmup proportion of learning "
                    "rate over the training process.")
parser.add_argument("--logging_steps",
                    default=5,
                    type=int,
                    help="The interval steps to logging.")
parser.add_argument("--init_from_ckpt",
                    type=str,
                    default=None,
                    help="The path of checkpoint to be loaded.")
parser.add_argument("--seed",
                    type=int,
                    default=3,
                    help="random seed for initialization")
           

2.1.1 性能名額修改

關于性能名額參考手冊修改添加:我已經添加進去 https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/metric/Overview_cn.html#about-metric-class:

import numpy as np
import paddle

x = np.array([0.1, 0.5, 0.6, 0.7])
y = np.array([0, 1, 1, 1])

m = paddle.metric.Precision()
m.update(x, y)
res = m.accumulate()
print(res) # 1.0
           
import numpy as np
import paddle

x = np.array([0.1, 0.5, 0.6, 0.7])
y = np.array([1, 0, 1, 1])

m = paddle.metric.Recall()
m.update(x, y)
res = m.accumulate()
print(res) # 2.0 / 3.0
           
f1_score = float(2 * precision * recall /
                         (precision + recall)) 
           

修改後檔案為:new

暫時未解決。

吐槽一下最新版本paddlenlp上面py檔案已經沒了!

實際采用這個-----AccuracyAndF1

https://paddlenlp.readthedocs.io/zh/latest/source/paddlenlp.metrics.glue.html

修改後檔案為:new2

# !python train.py --warmup --early_stop --epochs 10 --model_name "ernie-3.0-base-zh" --max_seq_length 128 --batch_size 32 --logging_steps 10 --learning_rate 6e-5 
!python train.py --warmup --early_stop --epochs 5 --model_name ernie-3.0-medium-zh

#修改後的訓練檔案train_new2.py ,主要使用了paddlenlp.metrics.glue的AccuracyAndF1:準确率及F1-score,可用于GLUE中的MRPC 和QQP任務
#不過吐槽一下:    return (acc,precision,recall,f1,(acc + f1) / 2,) 最後一個名額竟然是權重平均.....
!python train_new2.py --warmup --early_stop --epochs 5 --save_dir "./checkpoint2" --batch_size 16
           

程式運作時将會自動進行訓練,評估,測試。同時訓練過程中會自動儲存開發集上最佳模型在指定的 save_dir 中,儲存模型檔案結構如下所示:

checkpoint/
├── model_config.json
├── model_state.pdparams
├── tokenizer_config.json
└── vocab.txt
           

NOTE:

如需恢複模型訓練,則可以設定 initfromckpt , 如initfromckpt=checkpoint/model_state.pdparams。

如需訓練中文文本分類任務,隻需更換預訓練模型參數 model_name 。中文訓練任務推薦使用"ernie-3.0-base-zh",更多可選模型可參考Transformer預訓練模型。

2.1.2 使用文心ERNIE最新大模型進行訓練

最新開源ERNIE 3.0系列預訓練模型:

  • 110M參數通用模型ERNIE 3.0 Base
  • 280M參數重量級通用模型ERNIE 3.0 XBase
  • 74M輕量級通用模型ERNIE 3.0 Medium

文檔連結: https://github.com/PaddlePaddle/ERNIE

ERNIE模型彙總

ERNIE模型彙總

目前直接定義name就可以調用的主要為下面幾類:

目開源 ERNIE 3.0 Base 、ERNIE 3.0 Medium 、 ERNIE 3.0 Mini 、 ERNIE 3.0 Micro 、 ERNIE 3.0 Nano 五個模型:

ERNIE 3.0-Base (12-layer, 768-hidden, 12-heads)

ERNIE 3.0-Medium (6-layer, 768-hidden, 12-heads)

ERNIE 3.0-Mini (6-layer, 384-hidden, 12-heads)

ERNIE 3.0-Micro (4-layer, 384-hidden, 12-heads)

ERNIE 3.0-Nano (4-layer, 312-hidden, 12-heads)

從本地檔案建立資料集

使用本地資料集來訓練我們的文本分類模型,本項目支援使用固定格式本地資料集檔案進行訓練 如果需要對本地資料集進行資料标注,可以參考文本分類任務doccano資料标注使用指南進行文本分類資料标注。[這個放到下個項目講解]

本項目将以CBLUE資料集中醫療搜尋檢索詞意圖分類(KUAKE-QIC)任務為例進行介紹如何加載本地固定格式資料集進行訓練:

本地資料集目錄結構如下:

data/
├── train.txt # 訓練資料集檔案
├── dev.txt # 開發資料集檔案
├── label.txt # 分類标簽檔案
└── data.txt # 可選,待預測資料檔案
           

train.txt(訓練資料集檔案), dev.txt(開發資料集檔案),輸入文本序列與标簽類别名用'\t'分隔開。 train.txt/dev.txt 檔案格式:

<輸入序列1>'\t'<标簽1>'\n'
<輸入序列2>'\t'<标簽2>'\n'
           
丙氨酸氨基轉移酶和天門冬氨酸氨基轉移酶高嚴重嗎    其他
慢性肝炎早期症狀有哪些表現    疾病表述
胃不好能吃南瓜嗎    注意事項
為什麼我的手到夏天就會脫皮而且很嚴重有什麼辦法翺4天...    病情診斷
臉上拆線後可以出去玩嗎?可以流    其他
西甯青海治不孕不育專科醫院    就醫建議
冠狀溝例外很多肉粒是什麼    病情診斷
肛裂治療用什麼方法比較好    治療方案
包皮過長應該怎麼樣治療有效    治療方案
請問白癜風是一種什麼樣的疾病    疾病表述
月經過了四天測出懷孕是否可以确定    其他
           

label.txt(分類标簽檔案)記錄資料集中所有标簽集合,每一行為一個标簽名。 label.txt 檔案格式:

<标簽名1>'\n'
<标簽名2>'\n'
...
           
病情診斷
治療方案
病因分析
名額解讀
就醫建議
疾病表述
後果表述
注意事項
功效作用
醫療費用
其他
           

data.txt(可選,待預測資料檔案)。

黑苦荞茶的功效與作用及食用方法
交界痣會凸起嗎
檢查是否能懷孕挂什麼科
魚油怎麼吃咬破吃還是直接咽下去
幼兒挑食的生理原因是
           

2.3 GPU多卡訓練

指定GPU卡号/多卡訓練

unset CUDA_VISIBLE_DEVICES
python -m paddle.distributed.launch --gpus "0" train.py --warmup --early_stop
           
unset CUDA_VISIBLE_DEVICES
python -m paddle.distributed.launch --gpus "0" train.py --warmup --dataset_dir data/KUAKE_QIC
           

使用多卡訓練可以指定多個GPU卡号,例如 --gpus "0,1"

unset CUDA_VISIBLE_DEVICES
python -m paddle.distributed.launch --gpus "0,1" train.py --warmup --dataset_dir data/KUAKE_QIC
           

2.4模型預測

輸入待預測資料和資料标簽對照清單,模型預測資料對應的标簽

使用預設資料進行預測:

!python predict.py --params_path ./checkpoint/
           
input data: 黑苦荞茶的功效與作用及食用方法
label: 功效作用
---------------------------------
input data: 交界痣會凸起嗎
label: 疾病表述
---------------------------------
input data: 檢查是否能懷孕挂什麼科
label: 就醫建議
---------------------------------
input data: 魚油怎麼吃咬破吃還是直接咽下去
label: 其他
---------------------------------
input data: 幼兒挑食的生理原因是
label: 病因分析
---------------------------------
           
#也可以選擇使用本地資料檔案data/data.txt進行預測:
!python predict.py --params_path ./checkpoint/ --dataset_dir data/KUAKE_QIC
           
label: 功效作用
---------------------------------
input data: 交界痣會凸起嗎
label: 疾病表述
---------------------------------
input data: 檢查是否能懷孕挂什麼科
label: 就醫建議
---------------------------------
input data: 魚油怎麼吃咬破吃還是直接咽下去
label: 其他
---------------------------------
input data: 幼兒挑食的生理原因是
label: 病因分析
---------------------------------
           

3.總結

最新開源ERNIE 3.0系列預訓練模型:

  • 110M參數通用模型ERNIE 3.0 Base
  • 280M參數重量級通用模型ERNIE 3.0 XBase
  • 74M輕量級通用模型ERNIE 3.0 Medium
  • 新增語音-語言跨模态模型ERNIE-SAT 正式開源
  • 新增ERNIE-Gen(中文)預訓練模型,支援多類主流生成任務:主要包括摘要、問題生成、對話、問答 動靜結合的文心ERNIE開發套件:基于飛槳動态圖功能,支援文心ERNIE模型動态圖訓練。

将文本預處理、預訓練模型、網絡搭建、模型評估、上線部署等NLP開發流程規範封裝。

支援NLP常用任務:文本分類、文本比對、序列标注、資訊抽取、文本生成、資料蒸餾等。

提供資料清洗、資料增強、分詞、格式轉換、大小寫轉換等資料預處理工具。

文心大模型ERNIE是百度釋出的産業級知識增強大模型,涵蓋了NLP大模型和跨模态大模型。2019年3月,開源了國内首個開源預訓練模型文心ERNIE 1.0,此後在語言與跨模态的了解和生成等領域取得一系列技術突破,并對外開源與開放了系列模型,助力大模型研究與産業化應用發展。

這裡溫馨提示遇到問題多看文檔手冊

後續将對:多标簽分類、層次分類進行講解、以及這塊資料集的标注講解。

在空了會對交叉驗證,資料增強研究一下

本人部落格:https://blog.csdn.net/sinat_39620217?type=blog

繼續閱讀