HuggingFace情感分析任务微调

官方教程地址:https://huggingface.co/learn/nlp-course/zh-CN/chapter3/1?fw=pt

部分内容参考:

李福林, & 计算机技术. (2023). HuggingFace 自然语言处理详解: 基于 BERT 中文模型的任务实战. 清华大学出版社.

HuggingFace将AI项目研发分为四个步骤,准备数据集、定义模型、训练、测试,在此大方向下可以细化成几个小步骤,其中HuggingFace对此提供了一些工具集,具体如图:

因为一些原因可能无法连接到huggingface的服务器,所以可以在代码片中加入这一段来连接国内镜像

python 复制代码
import os
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'

1. 准备数据集

1.1 加载编码工具

这里我们选择微调的是IDEA-CCNL/Erlangshen-Roberta-330M-Sentiment模型,这个是一个情感分析模型,本身已经做的特别好了,其实微调意义已经不大甚至可能适得其反,但是这里仅为记录微调过程而并非真正需要优化模型。由于模型与编码器经常是成对出现,所以这里加载编码器也是选择IDEA-CCNL/Erlangshen-Roberta-330M-Sentiment

python 复制代码
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("IDEA-CCNL/Erlangshen-Roberta-330M-Sentiment")

这里可以测试一下:

python 复制代码
tokenizer(
    ['房东已经不养猫了', '今天真的要减肥了'],
    truncation=True,
    max_length=512,
)

输出如下:

复制代码
{'input_ids': [[101, 2791, 691, 2347, 5307, 679, 1075, 4344, 749, 102], [101, 791, 1921, 4696, 4638, 6206, 1121, 5503, 749, 102]], 'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]}

解释一下这个编码结果:

  1. input_ids
  • 将文本转换为数字序列的token ID
  • 每个ID对应词表中的一个token(词或子词)
  1. token_type_ids (也叫segment_ids)
  • 用于区分输入中不同的句子或文本段
  • 通常用0和1标记,0表示第一个句子,1表示第二个句子
  • 在单句任务中全部为0
  1. attention_mask
  • 用于标记哪些token应该被注意(1),哪些应该被忽略(0)
  • 主要用于处理变长序列的padding情况
  • 实际token为1,padding token为0

1.2 加载数据集

python 复制代码
# from datasets import load_from_disk
from datasets import load_dataset

# dataset = load_from_disk("/kaggle/working/Huggingface_Toturials/data/ChnSentiCorp")
dataset = load_dataset('lansinuote/ChnSentiCorp')
dataset['train'] = dataset['train'].shuffle().select(range(2000))
dataset['test'] = dataset['test'].shuffle().select(range(100))

加载数据集部分可以直接从网站一键下载,也可以手动下载了从磁盘载入,这里使用的是ChnSentiCorp,这里为简化运算,只取随机2000行,可以打印一下dataset结果如下

复制代码
DatasetDict({
    train: Dataset({
        features: ['text', 'label'],
        num_rows: 2000
    })
    validation: Dataset({
        features: ['text', 'label'],
        num_rows: 1200
    })
    test: Dataset({
        features: ['text', 'label'],
        num_rows: 100
    })
})

1.3 数据集预处理

用刚刚加载进来的编码器编码

python 复制代码
def f(data):
    return tokenizer.batch_encode_plus(data['text'], truncation=True, max_length=512)

dataset = dataset.map(f, batched=True, remove_columns=['text'], batch_size=1000, num_proc=3)

打印dataset可以看到:

复制代码
DatasetDict({
    train: Dataset({
        features: ['label', 'input_ids', 'token_type_ids', 'attention_mask'],
        num_rows: 2000
    })
    validation: Dataset({
        features: ['label', 'input_ids', 'token_type_ids', 'attention_mask'],
        num_rows: 1200
    })
    test: Dataset({
        features: ['label', 'input_ids', 'token_type_ids', 'attention_mask'],
        num_rows: 100
    })
})

模型一般对句子的长度有所限制,因此将长度超过512的句子截断或者过滤,这里为了编码方便简单的选择了删掉长度不合格的句子。

python 复制代码
def f(data):
    return [len(i) <= 512 for i in data['input_ids']]
dataset = dataset.filter(f, batched=True, num_proc=3, batch_size=1000)

2. 定义模型和训练工具

2.1 加载预训练模型

先将模型加载进来

python 复制代码
from transformers import AutoModelForSequenceClassification
import torch

model = AutoModelForSequenceClassification.from_pretrained("IDEA-CCNL/Erlangshen-Roberta-330M-Sentiment", num_labels=2)

简单计算下参数量

python 复制代码
sum(p.numel() for p in model.parameters()) / 1e6

结果:

复制代码
325.524482

参数量大概是325.5M

模型加载进来后进行简单的试算

python 复制代码
data = {
    'input_ids': torch.ones(1, 10, dtype=torch.long),
    'attention_mask': torch.ones(1, 10, dtype=torch.long),
    'token_type_ids': torch.ones(1, 10, dtype=torch.long),
    'labels': torch.ones(1, dtype=torch.long)
}
out = model(**data)
out.loss, out.logits.shape

2.2 加载评价函数

python 复制代码
import evaluate
metric = evaluate.load("accuracy")

这个评价函数接受的主要参数是一个预测值和一个标签值,与模型的输出不符,因此我们需要做一些处理。

python 复制代码
import numpy as np

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

测试一下这个函数

python 复制代码
from transformers.trainer_utils import EvalPrediction

eval_pred = EvalPrediction(predictions=np.array([[0, 1], [2, 3], [4, 5], [6, 7]]),
                           label_ids=np.array([1, 1, 0, 1]))
compute_metrics(eval_pred) 

结果:

复制代码
{'accuracy': 0.75}

2.3 定义训练函数

定义训练参数

python 复制代码
from transformers import Trainer, TrainingArguments
import accelerate
# 参数
training_args = TrainingArguments(
    output_dir="./output_dir",
    evaluation_strategy="steps",
    learning_rate=2e-5,
    per_device_train_batch_size=4,
    per_device_eval_batch_size=4,
    num_train_epochs=2,
    weight_decay=0.01,
    eval_steps=20,
    no_cuda=False,
    report_to='none',
)

注意:report_to='none',在用colab或者kaggle时注意要加上,不然会让你输入api key,比较麻烦

构建训练器

python 复制代码
from transformers import Trainer
from transformers import DataCollatorWithPadding

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=dataset['train'],
    eval_dataset=dataset['test'],
    data_collator=DataCollatorWithPadding(tokenizer),
    compute_metrics=compute_metrics,
)

上面的训练器中出现了一个常用的DataCollatorWithPadding对象,它的主要功能是将不同长度的序列补齐到同一长度,自动处理padding,使得一个batch内的所有样本长度一致。这里可以测试一下

python 复制代码
# 测试数据整理函数
data_collator = DataCollatorWithPadding(tokenizer)
data = dataset['train'][:5]
for i in data['input_ids']:
    print(len(i))
data = data_collator(data)
for k, v in data.items():
    print(k, v.shape)

结果:

复制代码
103
162
171
51
95
input_ids torch.Size([5, 171])
token_type_ids torch.Size([5, 171])
attention_mask torch.Size([5, 171])
labels torch.Size([5])

长度全部都补齐到171了

可以解码看看

python 复制代码
tokenizer.decode(data['input_ids'][0])  # 解码

结果:

复制代码
'[CLS] 看 了 两 边 , 第 一 感 觉 是 - - 很 一 般 。 内 容 上 , 真 不 敢 苟 同 , 还 号 称 是 学 术 明 星 , 于 丹 的 同 志 的 见 解 真 让 我 张 了 见 识 ! 页 数 大 可 以 压 缩 到 50 页 , 何 必 浪 费 纸 张 呢 ? 难 道 孔 夫 子 没 教 育 你 怎 么 搞 环 保 ? 评 论 到 此 结 束 , 懒 得 浪 费 我 得 笔 墨 ! [SEP] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD]'

3. 训练和测试函数

在训练前,先看看模型本身的能力

python 复制代码
trainer.evaluate()  # 评估

结果

复制代码
{'eval_loss': 0.2859102189540863,
 'eval_accuracy': 0.97,
 'eval_runtime': 5.3759,
 'eval_samples_per_second': 18.602,
 'eval_steps_per_second': 2.418}

模型本身的准确率就有0.97,已经非常优秀了,本文目的不在于优化模型。

训练模型

python 复制代码
trainer.train()

结果

python 复制代码
 [500/500 09:48, Epoch 2/2]
Step	Training Loss	Validation Loss	Accuracy
20	No log	0.628869	0.940000
40	No log	0.231194	0.980000
60	No log	0.496170	0.930000
80	No log	0.381901	0.950000
100	No log	0.326569	0.940000
120	No log	0.262761	0.950000
140	No log	0.305643	0.960000
160	No log	0.266394	0.960000
180	No log	0.251125	0.960000
200	No log	0.268621	0.950000
220	No log	0.188149	0.980000
240	No log	0.365949	0.950000
260	No log	0.420138	0.940000
280	No log	0.337165	0.940000
300	No log	0.343916	0.950000
320	No log	0.427644	0.950000
340	No log	0.543159	0.930000
360	No log	0.514463	0.930000
380	No log	0.450759	0.940000
400	No log	0.422249	0.940000
420	No log	0.437221	0.940000
440	No log	0.448282	0.950000
460	No log	0.447407	0.950000
480	No log	0.447090	0.950000
500	0.068800	0.447007	0.950000

测试

python 复制代码
trainer.evaluate()

4. 一键复制的python代码

python 复制代码
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("IDEA-CCNL/Erlangshen-Roberta-330M-Sentiment")
from datasets import load_from_disk
from datasets import load_dataset
import torch
from transformers import AutoModelForSequenceClassification
import evaluate
import numpy as np
from transformers.trainer_utils import EvalPrediction

dataset = load_dataset('lansinuote/ChnSentiCorp')
dataset['train'] = dataset['train'].shuffle().select(range(2000))
dataset['test'] = dataset['test'].shuffle().select(range(100))

def f(data):
    return tokenizer.batch_encode_plus(data['text'], truncation=True, max_length=512)

dataset = dataset.map(f, batched=True, remove_columns=['text'], batch_size=1000, num_proc=3)

def f(data):
    return [len(i) <= 512 for i in data['input_ids']]
dataset = dataset.filter(f, batched=True, num_proc=3, batch_size=1000)

model = AutoModelForSequenceClassification.from_pretrained("IDEA-CCNL/Erlangshen-Roberta-330M-Sentiment", num_labels=2)

metric = evaluate.load("accuracy")

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


# 定义训练函数
from transformers import Trainer, TrainingArguments
import accelerate
# 参数
training_args = TrainingArguments(
    output_dir="./output_dir",
    evaluation_strategy="steps",
    learning_rate=2e-5,
    per_device_train_batch_size=4,
    per_device_eval_batch_size=4,
    num_train_epochs=2,
    weight_decay=0.01,
    eval_steps=20,
    no_cuda=False,
    report_to='none',
)
# 训练器
from transformers import Trainer
from transformers import DataCollatorWithPadding

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=dataset['train'],
    eval_dataset=dataset['test'],
    data_collator=DataCollatorWithPadding(tokenizer),
    compute_metrics=compute_metrics,
)
trainer.train()
trainer.evaluate()  
相关推荐
声声codeGrandMaster3 分钟前
Django框架的前端部分使用Ajax请求一
前端·后端·python·ajax·django
小袁拒绝摆烂5 分钟前
OpenCV-去噪效果和评估指标方法
人工智能·opencv·计算机视觉
Douglassssssss10 分钟前
【深度学习】残差网络(ResNet)
网络·人工智能·深度学习
卡尔曼的BD SLAMer15 分钟前
计算机视觉与深度学习 | Python实现EMD-SSA-VMD-LSTM时间序列预测(完整源码和数据)
python·深度学习·算法·cnn·lstm
孟意昶17 分钟前
中级统计师-统计学基础知识-第三章 参数估计
人工智能·机器学习·概率论
nuclear20111 小时前
使用Python将 Excel 中的图表、形状和其他元素导出为图片
python·excel·将excel图表转换为图片·将excel文本框转换为图片
小袁拒绝摆烂3 小时前
OpenCV-python灰度变化和直方图修正类型
python·opencv·计算机视觉
gogoMark6 小时前
口播视频怎么剪!利用AI提高口播视频剪辑效率并增强”网感”
人工智能·音视频
Dxy12393102166 小时前
Python 条件语句详解
开发语言·python
龙泉寺天下行走6 小时前
Python 翻译词典小程序
python·oracle·小程序