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()  
相关推荐
电棍2331 小时前
工程记录:使用tello edu无人机进行计算机视觉工作(手势识别,yolo3搭载)
人工智能·计算机视觉·无人机
wan5555cn1 小时前
国产电脑操作系统与硬盘兼容性现状分析:挑战与前景评估
人工智能·笔记·深度学习·机器学习·电脑·生活
BullSmall2 小时前
汽车HIL测试:电子开发的关键验证环节
人工智能·机器学习·自动驾驶
woshihonghonga2 小时前
停止Conda开机自动运行方法
linux·人工智能·conda
海洲探索-Hydrovo4 小时前
TTP Aether X 天通透传模块丨国产自主可控大数据双向通讯定位模组
网络·人工智能·科技·算法·信息与通信
触想工业平板电脑一体机4 小时前
【触想智能】工业安卓一体机在人工智能领域上的市场应用分析
android·人工智能·智能电视
Bellafu6665 小时前
selenium常用的等待有哪些?
python·selenium·测试工具
墨染天姬6 小时前
【AI】数学基础之矩阵
人工智能·线性代数·矩阵
小白学大数据6 小时前
Python爬虫常见陷阱:Ajax动态生成内容的URL去重与数据拼接
爬虫·python·ajax
2401_841495647 小时前
【计算机视觉】基于复杂环境下的车牌识别
人工智能·python·算法·计算机视觉·去噪·车牌识别·字符识别