【一天一个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 npfrom datasets import load_dataset, load_metricfrom 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解决中文情绪分类任务

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

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}

那么,所有其他指标都是正常的,这意味着结果是正常的。让我们先输入一些内容,然后自己测试一下。

[En]

Well, all the other indicators are OK, which means the result is OK. Let’s just type in something and test it ourselves.

推理

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

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

然后需要设置设备类型。

import torchdevice = 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=)

嗯,是输出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

  • ​​
  • ​​
  • ​​

Original: https://blog.51cto.com/greyfoss/5469213
Author: 愤怒的可乐
Title: 【一天一个NLP任务】(Day 1)——BERT解决中文情绪分类任务

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/53906/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

最近整理资源【免费获取】:   👉 程序员最新必读书单  | 👏 互联网各方向面试题下载 | ✌️计算机核心资源汇总