0
点赞
收藏
分享

微信扫一扫

【一天一个NLP任务】(Day 1)——BERT解决中文情绪分类任务


引言

本文我们来使用BERT进行单文本分类(Single Sentence Classification,SSC),对输入的文本分成不同的类别。

类似英文glue/sst2数据集,我们今天实现的是对中文情感分类数据集进行分类。

数据集

情感分类数据集是网上开源的数据​

但是无法直接通过​​datasets​​去加载,有些记录有问题,博主已经处理好了。

需要处理好的数据集关注公众号:Hello丶Java

【一天一个NLP任务】(Day 1)——BERT解决中文情绪分类任务_情感分析

回复“SSC”即可。

安装所需的包

pip install transformers datasets

导入模块

import numpy as np
from datasets import load_dataset, load_metric
from transformers import BertTokenizerFast, BertForSequenceClassification, TrainingArguments,

加载数据集

把处理好的​​train.csv​​​、​​test.csv​​​和​​dev.csv​​文件放入我们的数据集目录,然后执行加载操作:

# 加载数据集

base_url = './datasets/ssc/'

raw_datasets = load_dataset('csv', data_files={'train': base_url + 'train.csv', 'test': base_url + 'test.csv', 'dev': base_url + 'dev.csv'})

加载分词器

tokenizer = BertTokenizerFast.from_pretrained('hfl/chinese-bert-wwm')

使用的是哈工大开源的模型

加载预训练模型

model = BertForSequenceClassification.from_pretrained('hfl/chinese-bert-wwm', return_dict=True)

加载评价指标

metric = load_metric('glue','sst2')

处理数据集

对数据集中的文本进行分词

def tokenize_function(examples):
return tokenizer(examples['text_a'], truncation=True, padding='max_length', max_length=512)

tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)

我们打印一下处理好的数据集看看:

tokenized_datasets

DatasetDict({
train: Dataset({
features: ['attention_mask', 'input_ids', 'label', 'text_a', 'token_type_ids'],
num_rows: 9146
})
test: Dataset({
features: ['attention_mask', 'input_ids', 'label', 'text_a', 'token_type_ids'],
num_rows: 1200
})
dev: Dataset({
features: ['attention_mask', 'input_ids', 'label', 'text_a', 'token_type_ids'],
num_rows: 1200
})
})

其中​​text_a​​​已经不需要了,并且​​label​​​需要改成​​labels​​:

tokenized_datasets = tokenized_datasets.remove_columns(
["text_a"]
)
tokenized_datasets.rename_column('label', 'labels')

DatasetDict({
train: Dataset({
features: ['attention_mask', 'input_ids', 'labels', 'token_type_ids'],
num_rows: 9146
})
test: Dataset({
features: ['attention_mask', 'input_ids', 'labels', 'token_type_ids'],
num_rows: 1200
})
dev: Dataset({
features: ['attention_mask', 'input_ids', 'labels', 'token_type_ids'],
num_rows: 1200
})
})

定义评价指标

def compute_metrics(eval_preds):
predictions, labels = eval_preds
return metric.compute(predictions=np.argmax(predictions, axis=1), references=labels)

定义训练参数

args = TrainingArguments(
'./saved/', # 保存路径,存放检查点和其他输出文件
evaluation_strategy='epoch', # 每轮结束后进行评价
learning_rate=2e-5, # 初始学习率
per_device_train_batch_size=8, # 训练批次大小
per_device_eval_batch_size=8, # 测试批次大小
num_train_epochs=3, # 训练轮数
)

默认使用AdamW优化器。

定义训练器

trainer = Trainer(
model,
args,
train_dataset=tokenized_datasets['train'],
eval_dataset=tokenized_datasets["dev"],
tokenizer=tokenizer,
compute_metrics=compute_metrics
)

开始训练

trainer.train()

训练了将近半个小时,3轮完了之后在验证集上的准确率为:

【一天一个NLP任务】(Day 1)——BERT解决中文情绪分类任务_深度学习_02

然后我们在测试集上进行测试:

trainer.evaluate(eval_dataset=tokenized_datasets['test'])

{'epoch': 3.0,
'eval_accuracy': 0.9466666666666667,
'eval_loss': 0.2731056809425354,
'eval_runtime': 23.3378,
'eval_samples_per_second': 51.419,
'eval_steps_per_second': 6.427}

准确率还挺高,但是光看准确率有时还不够,更常见的方式是同时加上F1 Score和召回率等。

测试

from sklearn.metrics import accuracy_score, precision_recall_fscore_support

# 重新定义评价指标
def compute_metrics(eval_preds):
logits, labels = eval_preds
predictions = np.argmax(logits, axis=-1)
precision, recall, f1, _ = precision_recall_fscore_support(labels, predictions, average='binary')
acc = accuracy_score(labels, predictions)
result = {
'accuracy': acc,
'f1': f1,
'precision': precision,
'recall': recall
}

return result

# 修改训练器的评价指标
trainer.compute_metrics = compute_metrics
# 在测试集上进行测试
trainer.evaluate(eval_dataset=tokenized_datasets['test'])

{'epoch': 3.0,
'eval_accuracy': 0.9466666666666667,
'eval_f1': 0.9471947194719471,
'eval_loss': 0.2731056809425354,
'eval_precision': 0.9503311258278145,
'eval_recall': 0.944078947368421,
'eval_runtime': 23.2695,
'eval_samples_per_second': 51.57,
'eval_steps_per_second': 6.446}

嗯,其他指标都还可以,说明结果还行。那我们就自己随便输入点什么进行测试吧。

推理

本小节我们自定义一些数据,进行情绪分类。

texts = [
'垃圾游戏,毁我青春',
'嗯,这游戏真香',
'我感谢你全家',
'哇,真好吃,妈妈的味道',
'大家好,我叫马牛逼,你们知道我有多牛逼吗,我拉屎不擦屁股,你们敢吗'
]
# 首先还是用分词器进行预处理
encoded = tokenizer(texts, truncation=True, padding='max_length', max_length=512, return_tensors='pt')

然后需要设置设备类型。

import torch

device = torch.device("cuda:0")

encoded = {k: v.to(device) for k, v in encoded.items()}

得到模型输出的logits,并且计算Softmax:

out = model(**encoded)
probs = out.logits.softmax(dim=-1)

tensor([[9.9923e-01, 7.6577e-04],
[5.6161e-03, 9.9438e-01],
[5.6499e-04, 9.9944e-01],
[8.2916e-04, 9.9917e-01],
[9.7360e-01, 2.6399e-02]], device='cuda:0', grad_fn=<SoftmaxBackward>)

嗯,是输出5个句子属于各个类别的概率,但是它们的对应关系是啥呢?

很简单,可以直接打印出来:

model.config.label2id

{'LABEL_0': 0, 'LABEL_1': 1}

我们数据集中的​​0​​​表示消极,​​1​​​表示积极,默认​​transformers​​​会为我们增加一个​​LABEL_​​前缀,我们也可以自己配置这个映射关系。

probs.argmax(dim=-1)

tensor([0, 1, 1, 1, 0], device='cuda:0')

知道了映射关系后,除了第三句话没有分辨出来外(这个话是真的损,不带一个脏字),其他都判断正确。

Reference

  • ​​是时候彻底弄懂BERT模型了​​
  • ​​是时候彻底弄懂BERT模型了​​
  • ​​微调BERT模型得到句子间的相似度​​


举报

相关推荐

0 条评论