当前位置:   article > 正文

基于PaddleNLP的28“微”情感多标签分类实战_goemotion paddel细粒度情感分析

goemotion paddel细粒度情感分析

基于PaddleNLP的28“微”情感多标签分类实战

一.项目介绍

1.1 项目简介

文本情感分析作为自然语言处理中主要研究问题之一,其利用自然语言处理和文本挖掘技术,对带有情感色彩的主观性文本进行分析、处理、归纳和推理,最终得出情感类别。

情感分析通过对文本信息的情感倾向判断挖掘内容潜在价值,不仅有利于管理部门的监控,而且对于谣言制止、舆情导向、市场营销等都具有非常高的应用价值,受到了工业界、商业界还是学术界极高的关注。

在情感分析中,粒度与准确度是影响情感分析效果的重要因素。目前大多数现有情感分析系统采用的正面、中性、负面的情感分类方式,其较难表达人类情感的复杂性,难以挖掘文本中潜在情感。2020年ACL会议上Google研究员发布了迄今为止最大、情感粒度最细的微情绪人工标注数据集GoEmotions,包含了58000个人工标注的Reddit 评论,情绪类别首次提升到28种,为更好挖掘用户潜在情感提供了契机。

本项目将通过预训练模型Erinie3.0在处理后的微情感多分类数据集GoEmotions上进行微调训练,构建精细化微情感多分类模型,细化情感分析结果粒度同时优化微情感分类效果。

1.2 项目难点

GoEmotions微情感多分类数据集不同于传统情感2-7分类,其首次将情感粒度提升至28种更为精细化。不同于简单的单分类任务,GoEmotions数据集中一条句子可能对应2种甚至3种情感,属于文本多标签分类任务。情感粒度的精细化以及多标签分类是本项目一大难点。本项目将介绍如何基于PaddleNLP对ERNIE 3.0预训练模型微调完成GoEmotions微情感文本多标签分类预测。

在这里插入图片描述

二.GoEmotions 微情感28多分类数据集

GoEmotions论文(推荐阅读):https://aclanthology.org/2020.acl-main.372.pdf

让机器理解情境和情感一直是研究界的一个长期目标。2020年ACL会议上Google研究员发布了迄今为止最大、情感粒度最细的微情绪人工标注数据集GoEmotions。其包含了58000个人工标注的Reddit 评论,情绪类别首次提升到28种。与基本的六种情绪(只有一种积极情绪(喜悦))不同,其分类如图所示,包括12种积极情绪、11种消极情绪、4种模糊情绪和1种“中性”情绪,这使得它广泛适用于需要微妙区分情绪表达的情绪理解任务,更好挖掘用用户潜在情感,具有很大研究意义。

28种微情感类别及整体分布情况:

在这里插入图片描述

在这里插入图片描述

为了最大程度的减少数据中的噪音,过滤掉数据集中仅由一个注释者选择的情感标签。执行此过滤后,将数据随机划分为训练(90%)和测试集(10%)。

在数据集中每一行包括两部分内容:句子内容、句子对应情感类型,使用‘\t’分隔符分隔。

# 进入数据集所在文件路径
%cd /home/aistudio/data/data177486/
  • 1
  • 2
/home/aistudio/data/data177486
  • 1
# 查看路径下文件:训练集train.csv  测试集 test.csv
!ls
  • 1
  • 2
test.csv  train.csv
  • 1
# pandas读取数据集便于分析
import pandas as pd
train = pd.read_csv('train.csv', sep='\t', header=None)
test = pd.read_csv('test.csv', sep='\t', header=None)
  • 1
  • 2
  • 3
  • 4
# 添加列名
train.columns = ["text",'labels']
test.columns = ["text",'labels']
  • 1
  • 2
  • 3
# 读取训练集前10条文本,数据格式为text, labels。注意labels可能包含多个情感类别,各情感使用','进行分隔
train.head(10)
  • 1
  • 2
textlabels
0My favourite food is anything I didn't have to...27
1Now if he does off himself, everyone will thin...27
2WHY THE FUCK IS BAYLESS ISOING2
3To make her feel threatened14
4Dirty Southern Wankers3
5OmG pEyToN iSn'T gOoD eNoUgH tO hElP uS iN tHe...26
6Yes I heard abt the f bombs! That has to be wh...15
7We need more boards and to create a bit more s...8,20
8Damn youtube and outrage drama is super lucrat...0
9It might be linked to the trust factor of your...27
test.head(15)
  • 1
textlabels
0I’m really sorry about your situation :( Altho...25
1It's wonderful because it's awful. At not with.0
2Kings fan here, good luck to you guys! Will be...13
3I didn't know that, thank you for teaching me ...15
4They got bored from haunting earth for thousan...27
5Thank you for asking questions and recognizing...15
6You’re welcome15
7100%! Congrats on your job too!15
8I’m sorry to hear that friend :(. It’s for the...24
9Girlfriend weak as well, that jump was pathetic.25
10[NAME] has towed the line of the Dark Side. He...3,10
11Lol! But I love your last name though. XD1,18
12Translation }}} I wish I could afford it.8
13It's great that you're a recovering addict, th...0,7
14I've also heard that intriguing but also kinda...14

三.基于PaddleNLP搭建微情感28多分类模型

3.1 PaddleNLP依赖导入

PaddleNLP 是飞桨自然语言处理开发库,具备 易用的文本领域API,多场景的应用示例、和高性能分布式训练三大特点,旨在提升飞桨开发者文本领域建模效率,旨在提升开发者在文本领域的开发效率,并提供丰富的NLP应用示例。

1.易用的文本领域API:

提供丰富的产业级预置任务能力 Taskflow 和全流程的文本领域API:支持丰富中文数据集加载的 Dataset API,可灵活高效地完成数据预处理的 Data API ,预置60+预训练词向量的 Embedding API ,提供100+预训练模型的 Transformer API 等,可大幅提升NLP任务建模的效率。

2.多场景的应用示例:

覆盖从学术到产业级的NLP应用示例,涵盖NLP基础技术、NLP系统应用以及相关拓展应用。全面基于飞桨核心框架2.0全新API体系开发,为开发者提供飞桨文本领域的最佳实践。

3.高性能分布式训练:

基于飞桨核心框架领先的自动混合精度优化策略,结合分布式Fleet API,支持4D混合并行策略,可高效地完成大规模预训练模型训练。

项目GitHub: https://github.com/PaddlePaddle/PaddleNLP

项目Gitee: https://gitee.com/paddlepaddle/PaddleNLP

GitHub Issue反馈: https://github.com/PaddlePaddle/PaddleNLP/issues

PaddleNLP文档: https://paddlenlp.readthedocs.io/zh/latest/index.html

PaddleNLP支持预训练模型汇总https://paddlenlp.readthedocs.io/zh/latest/model_zoo/index.html

在这里插入图片描述

!pip install --upgrade paddlenlp
  • 1
import os
import paddle
import paddlenlp
  • 1
  • 2
  • 3

3.2 数据预处理

对于28微情感多标签分类场景,即一个句子可能对应多个情感类别标签, 首先需要对数据集情感标签使用One-Hot编码进行转换,对于每种情感0表示不存在而1表示存在。

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

# 28类微情感映射关系
label_vocab = {
    0: "admiration",
    1: "amusement",
    2: "anger",
    3: "annoyance",
    4: "approval",
    5: "caring",
    6: "confusion",
    7: "curiosity",
    8: "desire",
    9: "disappointment",
    10: "disapproval",
    11: "disgust",
    12: "embarrassment",
    13: "excitement",
    14: "fear",
    15: "gratitude",
    16: "grief",
    17: "joy",
    18: "love",
    19: "nervousness",
    20: "optimism",
    21: "pride",
    22: "realization",
    23: "relief",
    24: "remorse",
    25: "sadness",
    26: "surprise",
    27: "neutral"
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
# 自定义数据集
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(filepath, is_one_hot=True):
    f = open(filepath)
    while True:
        line = f.readline()
        if not line:
            break
        data = line.strip().split('\t')
        # 针对28类微情感标签做One-hot处理
        if is_one_hot:
            labels = [float(1) if str(i) in data[1].split(',') else float(0) for i in range(28)]  # 28类
        else:
            labels = [int(d) for d in data[1].split(',')]
        yield {"text": clean_text(data[0]), "labels": labels}
    f.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
# load_dataset()创建数据集
# lazy=False,数据集返回为MapDataset类型
# 对训练集和验证集进行预处理
train_ds = load_dataset(read_custom_data, filepath='train.csv', lazy=False) 
test_ds = load_dataset(read_custom_data, filepath='test.csv', lazy=False)
  • 1
  • 2
  • 3
  • 4
  • 5
print("数据类型:", type(train_ds))

# labels为One-hot标签
print("训练集样例:", train_ds[0])
print("测试集样例:", test_ds[0])
  • 1
  • 2
  • 3
  • 4
  • 5
数据类型: <class 'paddlenlp.datasets.dataset.MapDataset'>
训练集样例: {'text': "My favourite food is anything I didn't have to cook myself.", 'labels': [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.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]}
测试集样例: {'text': 'I’m really sorry about your situation :( Although I love the names Sapphira, Cirilla, and Scarlett!', 'labels': [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.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0]}
  • 1
  • 2
  • 3

3.3 加载预训练模型 ERNIE 3.0

ERNIE 3.0首次在百亿级预训练模型中引入大规模知识图谱,提出了海量无监督文本与大规模知识图谱的平行预训练方法(Universal Knowledge-Text Prediction),通过将知识图谱挖掘算法得到五千万知识图谱三元组与4TB大规模语料同时输入到预训练模型中进行联合掩码训练,促进了结构化知识和无结构文本之间的信息共享,大幅提升了模型对于知识的记忆和推理能力。

ERNIE 3.0框架分为两层。第一层是通用语义表示网络,该网络学习数据中的基础和通用的知识。第二层是任务语义表示网络,该网络基于通用语义表示,学习任务相关的知识。在学习过程中,任务语义表示网络只学习对应类别的预训练任务,而通用语义表示网络会学习所有的预训练任务。

在这里插入图片描述

PaddleNLP中Auto模块(包括AutoModel, AutoTokenizer及各种下游任务类)提供了方便易用的接口,无需指定模型类别,即可调用不同网络结构的预训练模型。PaddleNLP的预训练模型可以很容易地通过from_pretrained()方法加载,Transformer预训练模型汇总包含了40多个主流预训练模型,500多个模型权重。

AutoModelForSequenceClassification可用于多标签分类,通过预训练模型获取输入文本的表示,之后将文本表示进行分类。

PaddleNLP已经实现了ERNIE 3.0预训练模型,可以通过一行代码实现ERNIE 3.0预训练模型和分词器的加载。

PaddleNLP支持的更多预训练模型:https://paddlenlp.readthedocs.io/zh/latest/model_zoo/index.html

# 加载中文ERNIE 3.0预训练模型和分词器
from paddlenlp.transformers import AutoModelForSequenceClassification, AutoTokenizer

model_name = "ernie-3.0-medium-zh"   # ERNIE3.0 模型
num_classes = 28  # 28分类任务
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_classes=num_classes)
tokenizer = AutoTokenizer.from_pretrained(model_name)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
[2022-11-16 15:17:25,874] [    INFO] - We are using <class 'paddlenlp.transformers.ernie.modeling.ErnieForSequenceClassification'> to load 'ernie-3.0-medium-zh'.
[2022-11-16 15:17:25,878] [    INFO] - Downloading https://bj.bcebos.com/paddlenlp/models/transformers/ernie_3.0/ernie_3.0_medium_zh.pdparams and saved to /home/aistudio/.paddlenlp/models/ernie-3.0-medium-zh
[2022-11-16 15:17:25,881] [    INFO] - Downloading ernie_3.0_medium_zh.pdparams from https://bj.bcebos.com/paddlenlp/models/transformers/ernie_3.0/ernie_3.0_medium_zh.pdparams
100%|██████████| 313M/313M [00:37<00:00, 8.66MB/s] 
W1116 15:18:03.879670   259 gpu_resources.cc:61] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.2, Runtime API Version: 11.2
W1116 15:18:03.884307   259 gpu_resources.cc:91] device: 0, cuDNN Version: 8.2.
[2022-11-16 15:18:06,760] [    INFO] - We are using <class 'paddlenlp.transformers.ernie.tokenizer.ErnieTokenizer'> to load 'ernie-3.0-medium-zh'.
[2022-11-16 15:18:06,764] [    INFO] - Downloading https://bj.bcebos.com/paddlenlp/models/transformers/ernie_3.0/ernie_3.0_medium_zh_vocab.txt and saved to /home/aistudio/.paddlenlp/models/ernie-3.0-medium-zh
[2022-11-16 15:18:06,766] [    INFO] - Downloading ernie_3.0_medium_zh_vocab.txt from https://bj.bcebos.com/paddlenlp/models/transformers/ernie_3.0/ernie_3.0_medium_zh_vocab.txt
100%|██████████| 182k/182k [00:00<00:00, 936kB/s] 
[2022-11-16 15:18:07,102] [    INFO] - tokenizer config file saved in /home/aistudio/.paddlenlp/models/ernie-3.0-medium-zh/tokenizer_config.json
[2022-11-16 15:18:07,105] [    INFO] - Special tokens file saved in /home/aistudio/.paddlenlp/models/ernie-3.0-medium-zh/special_tokens_map.json
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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=64)
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=32, shuffle=True)
test_batch_sampler = BatchSampler(test_ds, batch_size=16, 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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

3.5 定义模型验证指标

准确率作为实验最常用的评估指标,但GoEmotions数据中存在样本较大不均衡的情况时,此时使用准确率的话并不能合理反映模型的预测能力。将28种微情感模型OneHot化为0和1后,其中0代表句子中不存在该情感,而1代表存在。对于数量较少的情感,其负样本过多远远超过正样本,假设模型全预测为负样本0此时仍然能达到较高的准确率,模型对正样本将失去识别能力,高准确率不能反映模型的预测能力。因而不适合使用准确率指标,更适合使用Precision、Recall和F1-Score指标对多分类进行综合评估。

在这里插入图片描述

Precision是针对预测结果而言的。预测结果中,预测为正的样本中预测正确的概率,类似于一个考生在考卷上写出来的答案中,正确了多少,体现模型的精准度。而Recall表示实际为正的样本被判断为正样本的比例,类似于一个考生在考卷上回答了多少题。体现一个模型的全面性。Precision和Recall是一对矛盾的度量,一般来说,Precision高时,Recall值往往偏低;而Precision值低时,Recall值往往偏高。

F1的核心思想在于,在尽可能的提高Precision和Recall的同时,也希望两者之间的差异尽可能小,可以综合考虑两大指标。F1-score适用于二分类问题,对于多分类问题,将二分类的F1-score推广,有Micro-F1和Macro-F1两种度量。Macro-average统计各个类别的TP、FP、FN、TN,分别计算各自的Precision和Recall,得到各自的F1值,然后取平均值得到Macro-F1。

import numpy as np
import sklearn
from sklearn.metrics import roc_auc_score, f1_score, precision_score, recall_score
from paddle.metric import Metric

# 自定义MultiLabelReport评价指标
class MultiLabelReport(Metric):
    """
    AUC and F1 Score for multi-label text classification task.
    """

    def __init__(self, name='MultiLabelReport', average='micro'):
        super(MultiLabelReport, self).__init__()
        self.average = average
        self._name = name
        self.reset()

    def f1_score(self, y_prob):
        '''
        Returns the f1 score by searching the best threshhold
        '''
        best_score = 0
        for threshold in [i * 0.01 for i in range(100)]:
            self.y_pred = y_prob > threshold
            score = sklearn.metrics.f1_score(y_pred=self.y_pred, y_true=self.y_true, average=self.average)
            if score > best_score:
                best_score = score
                precison = precision_score(y_pred=self.y_pred, y_true=self.y_true, average=self.average)
                recall = recall_score(y_pred=self.y_pred, y_true=self.y_true, average=self.average)
        return best_score, precison, recall

    def reset(self):
        """
        Resets all of the metric state.
        """
        self.y_prob = None
        self.y_true = None

    def update(self, probs, labels):
        if self.y_prob is not None:
            self.y_prob = np.append(self.y_prob, probs.numpy(), axis=0)
        else:
            self.y_prob = probs.numpy()
        if self.y_true is not None:
            self.y_true = np.append(self.y_true, labels.numpy(), axis=0)
        else:
            self.y_true = labels.numpy()

    def accumulate(self):
        auc = roc_auc_score(
            y_score=self.y_prob, y_true=self.y_true, average=self.average)
        f1_score, precison, recall = self.f1_score(y_prob=self.y_prob)
        return auc, f1_score, precison, recall

    def name(self):
        """
        Returns metric name
        """
        return self._name
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

3.6 选择优化策略和运行配置

import time
import paddle.nn.functional as F

# AdamW优化器、交叉熵损失函数、自定义MultiLabelReport评价指标
optimizer = paddle.optimizer.AdamW(learning_rate=4e-5, parameters=model.parameters(), weight_decay=0.01)
criterion = paddle.nn.BCEWithLogitsLoss()
metric = MultiLabelReport()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3.7 模型训练和验证

import paddle
import numpy as np
import paddle.nn.functional as F

# 构建验证集evaluate函数
@paddle.no_grad()
def evaluate(model, criterion, metric, data_loader, label_vocab, if_return_results=True):
    model.eval()
    metric.reset()
    losses = []
    results = []
    for batch in data_loader:
        input_ids, token_type_ids, labels = batch['input_ids'], batch['token_type_ids'], batch['labels']
        logits = model(input_ids, token_type_ids)
        loss = criterion(logits, labels)
        probs = F.sigmoid(logits)
        losses.append(loss.numpy())
        metric.update(probs, labels)
        if if_return_results:
            probs = probs.tolist()
            for prob in probs:
                result = []
                for c, pred in enumerate(prob):
                    if pred > 0.5:
                        result.append(label_vocab[c])
                results.append(','.join(result))

    auc, f1_score, precison, recall = metric.accumulate()
    print("eval loss: %.5f, auc: %.5f, f1 score: %.5f, precison: %.5f, recall: %.5f" %
          (np.mean(losses), auc, f1_score, precison, recall))
    model.train()
    metric.reset()
    if if_return_results:
        return results
    else:
        return f1_score
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
epochs = 6 # 训练轮次
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()

        # 每迭代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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
# 由于data目录下文件不保存,故将训练好的模型参数复制到work目录下便于存储
!cp /home/aistudio/data/data177486/ernie_ckpt/model_state.pdparams /home/aistudio/work/
  • 1
  • 2
# 加载训练好的模型最优参数
model.set_dict(paddle.load('ernie_ckpt/model_state.pdparams'))

# 加载之前训练好的模型参数
# model.set_dict(paddle.load('/home/aistudio/work/model_state.pdparams'))

# 模型验证
print("ERNIE 3.0 在GoEmotions微情感28分类test集表现:", end= " ")
results = evaluate(model, criterion, metric, test_data_loader, label_vocab)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
ERNIE 3.0 在GoEmotions微情感28分类test集表现: eval loss: 0.08809, auc: 0.94732, f1 score: 0.59687, precison: 0.57566, recall: 0.61969
  • 1

GoEmotions论文提供基线实验结果:

在这里插入图片描述

感兴趣的可以基于提供基线做更多对比实验!

3.8 “微”情感28多标签分类预测演示

# 定义数据加载和处理函数
from paddlenlp.data import JiebaTokenizer, Pad, Stack, Tuple, Vocab
def convert_example(example, tokenizer, max_seq_length=64, is_test=False):
    qtconcat = example["text"]
    encoded_inputs = tokenizer(text=qtconcat, max_seq_len=max_seq_length)
    input_ids = encoded_inputs["input_ids"]
    token_type_ids = encoded_inputs["token_type_ids"]
    if not is_test:
        label = np.array([example["label"]], dtype="int64")
        return input_ids, token_type_ids, label
    else:
        return input_ids, token_type_ids

# 定义模型预测函数
def predict(model, data, tokenizer, label_vocab, batch_size=1, max_seq=64):
    examples = []
    # 将输入数据(list格式)处理为模型可接受的格式
    for text in data:
        input_ids, segment_ids = convert_example(
            text,
            tokenizer,
            max_seq_length=max_seq,
            is_test=True)
        examples.append((input_ids, segment_ids))

    batchify_fn = lambda samples, fn=Tuple(
        Pad(axis=0, pad_val=tokenizer.pad_token_id),  # input id
        Pad(axis=0, pad_val=tokenizer.pad_token_id),  # segment id
    ): fn(samples)

    # Seperates data into some batches.
    batches = []
    one_batch = []
    for example in examples:
        one_batch.append(example)
        if len(one_batch) == batch_size:
            batches.append(one_batch)
            one_batch = []
    if one_batch:
        # The last batch whose size is less than the config batch_size setting.
        batches.append(one_batch)

    results = []
    model.eval()
    for batch in batches:
        input_ids, segment_ids = batchify_fn(batch)
        input_ids = paddle.to_tensor(input_ids)
        segment_ids = paddle.to_tensor(segment_ids)
        logits = model(input_ids, segment_ids)
        probs = F.sigmoid(logits)
        probs = probs.tolist()
        # 结果处理,选取概率大于0.5的情感类别
        for prob in probs:
            result = []
            for c, pred in enumerate(prob):
                if pred > 0.5:
                    result.append(label_vocab[c])
            results.append(','.join(result))
    return results  # 返回预测结果
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
# 定义要进行微情感分析的文本数据
data = [
    # 11 disgust
    {"text": 'Thats absolutely disgusting.'},
    # 7 curiosity
    {"text":'Why would I do that?'},
    # 2 anger
    {"text":"You shut your mouth"},
    # 15 gratitude
    {"text":"Thank you."}
]

# 模型预测
labels =  predict(model, data, tokenizer, label_vocab, batch_size=1)

# 输出预测结果
for idx, text in enumerate(data):
    print('Text: {} \t Lables: {}'.format(text['text'], labels[idx]))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
Text: Thats absolutely disgusting. 	 Lables: disgust
Text: Why would I do that? 	 Lables: curiosity
Text: You shut your mouth 	 Lables: anger
Text: Thank you. 	 Lables: gratitude
  • 1
  • 2
  • 3
  • 4

. Lables: gratitude

后续将带来该项目的部署演示,期待的小伙伴可以点个喜欢❤关注本项目!

四.项目总结

GoEmotions 28微情感多分类数据集首次将情感类别提升至28种,为更好挖掘用户潜在情感提供了契机,具有很大研究意义。精细化的微情感分类以及多标签分类是本次项目遇到的主要挑战。在本次项目中,我们通过预训练模型Erinie3.0在处理后的GoEmotions数据集上进行微调训练,搭建了微情感多分类模型,细化情感分析结果粒度同时优化了微情感分类效果,相较传统2-7情感分类,可以更好挖掘用户潜在情感从而更好发挥微情感分析价值。

项目不足与后续方向:

1.从F1-Score等模型评估指标上看,模型效果还有待提升,后续可以从模型优化、调参(max_seq_length、batch_size、learning_rate、weight_decay等)等多角度进行进一步优化。

2.英文数据集导致中文场景受限。针对这一不足,我们可以考虑在中文数据集上进行迁移训练或者通过翻译进行中译英从而泛化使用场景。

3.后续会考虑对该项目进行部署应用,搭建微情感分析平台,通过可视化界面更好演示功能。同时结合舆情分析等应用更好发挥微情感分析价值。

参考项目: 【快速上手ERNIE 3.0】法律文本多标签分类实战

五.作者介绍

昵称:炼丹师233

飞桨开发者技术专家 PPDE

Github主页:https://github.com/hchhtc123

研究方向: 全栈小菜鸡,主攻大数据开发和NLP方向,喜欢捣鼓有趣项目。

https://aistudio.baidu.com/aistudio/personalcenter/thirdview/330406 关注我,下次带来更多精彩项目分享!

此文章为搬运
原项目链接

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/寸_铁/article/detail/890587
推荐阅读
相关标签
  

闽ICP备14008679号