PaddleNLP基于ERNIR3.0文本分类以中医疗搜索检索词意图分类(KUAKE-QIC)为例多分类(单标签)

Posted 汀、

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PaddleNLP基于ERNIR3.0文本分类以中医疗搜索检索词意图分类(KUAKE-QIC)为例多分类(单标签)相关的知识,希望对你有一定的参考价值。

相关项目链接:
Paddlenlp之UIE模型实战实体抽取任务【打车数据、快递单】

Paddlenlp之UIE分类模型【以情感倾向分析新闻分类为例】含智能标注方案)

应用实践:分类模型大集成者[PaddleHub、Finetune、prompt]

Paddlenlp之UIE关系抽取模型【高管关系抽取为例】
本项目链接:
PaddleNLP基于ERNIR3.0文本分类任务详解【多分类(单标签)】

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_datasetMapDatasetIterDataset ,可以方便的自定义属于自己的数据集。

目前PaddleNLP的通用数据处理流程如下:

  1. 加载数据集(内置数据集或者自定义数据集,数据集返回 原始数据)。

  2. 定义 trans_func() ,包括tokenize,token to id等操作,并传入数据集的 map() 方法,将原始数据转为 feature 。

  3. 根据上一步数据处理的结果定义 batchify 方法和 BatchSampler 。

  4. 定义 DataLoader , 传入 BatchSampler 和 batchify_fn() 。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wbNrr5ET-1658889546447)(https://ai-studio-static-online.cdn.bcebos.com/14804601029a44afa00a1e6f5255494ba419a581f94a4e04a7bad3b6be8a1d52)]

PaddleNLP Datasets API:供参考

PaddleNLP提供了以下数据集的快速读取API,实际使用时请根据需要添加splits信息:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eXtI9MIu-1658889546448)(https://ai-studio-static-online.cdn.bcebos.com/499101cb11644d33861d326c304dd994328c4de2d57e4d548b0670e60ddbfff0)]

加载数据集

快速加载内置数据集
目前PaddleNLP内置20余个NLP数据集,涵盖阅读理解,文本分类,序列标注,机器翻译等多项任务。目前提供的数据集可以在 数据集列表 中找到。

以 msra_ner 数据集为例:

load_dataset() 方法会从 paddlenlp.datasets 下找到msra_ner数据集对应的数据读取脚本(默认路径: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竞赛,对训练数据进行了数据增强)。 load_dataset() 方法提供的 data_files参数可以实现这个功能。以 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"。

max_seq_length: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。

early_stop_nums:在设定的早停训练轮次内,模型在开发集上表现不再上升,训练终止;默认为4。
epochs: 训练轮次,默认为100。

warmup:是否使用学习率warmup策略;默认为False。

warmup_proportion:学习率warmup策略的比例数,如果设为0.1,则学习率会在前10%steps数从0慢慢增长到learning_rate, 而后再缓慢衰减;默认为0.1。

logging_steps: 日志打印的间隔steps数,默认5。

init_from_ckpt: 模型初始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:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2BjJgg07-1658889750940)(https://ai-studio-static-online.cdn.bcebos.com/d935ed0af63a47eeaaa571b83e40be06f8d5b4c9f65c4315ad32ccd85ce10b71)]

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:

如需恢复模型训练,则可以设置 init_from_ckpt , 如init_from_ckpt=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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nEln8xXH-1658889817075)(https://ai-studio-static-online.cdn.bcebos.com/4af2f4b8d2b44d4c8060d94bb440d52078ee85d8d2314191860531c66fafabe7)]

文档链接:
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: 其他
-------------------PaddleNLP基于ERNIR3.0文本分类以CAIL2018-SMALL数据集罪名预测任务为例多标签

基于Ernie-3.0 CAIL2019法研杯要素识别多标签分类任务

自然语言处理(NLP)基于PaddleNLP的短文本相似度计算

自然语言处理(NLP)基于PaddleNLP的短文本相似度计算

不容错过,飞桨产业级PaddleNLP全景图大揭秘!

Spark2.0 特征提取转换选择之二:特征选择文本处理,以中文自然语言处理(情感分类)为例