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()  
相关推荐
Maydaycxc14 小时前
gentic RPA 深度解析|AI Agent 与 RPA 混合架构、落地痛点、选型要点
人工智能·ai·自动化·rpa
摸鱼仙人~14 小时前
从 AI 应用开发到 GPU 计算软件:一份完整的 GPGPU、PyTorch、vLLM 与 AI Infra 学习路线
人工智能·pytorch·vllm
znnnk14 小时前
【AI技术】从 Vibe Coding 到 Harness:以 DeepSeek Harness 为例看 AI 项目的工程化维护
人工智能·ai·deepseek·ai工程化·harness·项目维护
BK贝壳18 小时前
亿级用户签到位图选型复盘:用 bool-hybrid-array 扛冷启动打卡,也把它和 Roaring/Redis 的边界测清楚了
数据结构·python·性能优化·numpy
2601_9669496520 小时前
如何根据研究的起止时间截取分钟级行情?Python 实战与 QuantDash 数据处理指南
开发语言·python·pandas·量化交易·1024程序员节·股票数据·quantdash
张彦峰ZYF21 小时前
越过边界之前:Reward Seeker 实验如何改写智能体安全的时间逻辑
人工智能·ai agent·anthropic·智能体安全·reward seeker·“控制评价过程”泛化跨领域策略·envbom
拓恒技术1 天前
无人机巡检AI算法的工程化落地:从需求到交付的5个核心环节
人工智能·算法·无人机
罗西的思考1 天前
AI 时代的 “房谋杜断”:如何培养能力、品味与判断力
人工智能·算法·机器学习
在世修行1 天前
智学文枢:页面切换与跨页数据预填
python·桌面应用·signal·1024程序员节·pyside·跨页跳转·状态预填
字节跳动视频云技术团队1 天前
720P 直升 8K,解密 2026 抖音美好奇妙夜巨幕超分黑科技
人工智能·音视频开发