今天学习了利用Hugging Face微调属于自己的大模型。感觉Hugging Face还是非常的方便,与Pytorch也兼容的非常好。计划近期逐渐摸索下去。
博主实力有限,若有不足之处欢迎大家批评指正。
创作不易,若觉得有用,欢迎大家点个关注,留个评论~
email:yuhan.huang@whu.edu.cn
不知道微调的可以看一下我原来的推文:
ChatGPT的技术综述https://blog.csdn.net/m0_62716099/article/details/142732224?spm=1001.2014.3001.5501
---------------------------------------------------------------------------------------------
Hugging Face🤗
首先给大家简单的介绍一下Hugging Face吧。我个人感觉可以将其理解为深度学习爱好者的小“Github”。开源、免费是其最大的亮点。里面有着数不清的开源模型与数据集。非常好用。这里我把官网链接粘贴在这里,欢迎大家前去阅览。
当然,我也会直接告诉大家,我之后所有的教程都是基于其官方文档的教程来实现。只不过我会讲的稍微细一点,把我自己出现的问题展现出来。
Hugging Face官网链接https://huggingface.co/
微调一个文本分类模型
在进行模型的微调之前,首先需要把环境配置好。这里我建议大家用Anaconda配置一个虚拟环境,Python版本我觉得至少要大于3.10,不然可能会出现一些环境上的问题。这里首先大家要下载适合自己版本的Pytorch或者Tensorflow,由于比较复杂,大家可以自寻寻找教程;下载完这些库之后,我们就要下载我们待会会用到的一些库,都只需要一些简单的pip下载。
# 安装库
pip install transformers
pip install evaluate
pip install datasets
pip install numpy
当然下载完后,我们的第一步就是导入这些库。
import transformers
import numpy as np
import evaluate
from transformers import AutoTokenizer
from transformers import Trainer
from transformers import AutoModelForSequenceClassification
from transformers import TrainingArguments
from datasets import load_dataset
大家看到这可能感觉手忙脚乱了:啊!这么多新玩意,我看都看不懂!!!怎么办!
我的回答是:别慌,这些只是人家封装好的函数。看不懂,扔给GPT,让他给你解释一下,再给你丢个案例。你一下就看懂了,会用了。现在先往下走,别慌,我一边展示代码,一遍给你们解释。
# cache_dir代表把缓存放在当前文件夹下,否则会存在C盘的缓存文件中
dataset = load_dataset("yelp_review_full", cache_dir="./dataset")
首先我们导入数据集。其数据集是在网上在线下载的。所以你下载的时候要把梯子开着。这里我们使用的数据集就是一个典型的分类数据集。文本是评论,标签是用户打的星(1-5星)。
可以看,这里就是一个DatasetDict对象,内部是两个Dataset对象。正常字典的操作都可以使用。包含‘label’和text标签。分别对应星和文本内容。给大家打印出来看看。
导入数据后,我们再导入预训练的模型。这里我们使用的是 google-bert/bert-base-cased 模型。当然人家的模型库里也有很多模型,我用这个只是因为官方文档也是用的这个。
# num_label 是类别个数, cache_dir 是缓存存放的地方
model = AutoModelForSequenceClassification.from_pretrained("google-bert/bert-base-cased", num_labels=5, cache_dir="./model")
这里的AutoModelForSequenceClassification大家就可以理解为在原来的LLM基础上增加了一个分类层。若更有兴趣了解深层原理的呢,那我就给大家说说我自己的想法哈。
众所周知,我们的Transformer的输入是(B, T), 输出是(B, T, vocab_size),每一个时间点的输出代表什么呢?是下一个Token出现的概率分布。现在我们就要在后面添加一个nn.Linear层,将输出变成(B, num_classes)。
关于这个nn.Linear的形状,会不会有同学觉得是(T * vocab_size, num_classes)?不对!这里就得谈到Transformer的注意力机制。后面的token,是会对前面的所有Token进行查询的。因此我们只会取最后一个Token位置的(B, vocab_size)用来预测类别。所以nn.Linear的权重形状实际上应该是(vocab_size, num_classes)。
OK,现在我们数据也有了,模型也有了。熟悉Transformer的同学就知道下一步了:Tokenization,我们导入的AutoTokenizer就起作用了。导入google-bert/bert-base-cased 的Tokenizer进行分词。
# 导入分词器, cache_dir 代表缓存位置
tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-cased", cache_dir="./Tokenizer")
# padding代表会填充到原始分词器的最大上下文长度。truncation代表会对超出长度的地方进行截断
def tokenize_function(examples):
return tokenizer(examples['text'], padding="max_length", truncation=True)
# 利用map函数对tokenized进行分词化
tokenized_datasets = dataset.map(tokenize_function, batched=True)
对分词化后的数据可视化一下,我们可以发现特征出现了三个新列。其中input_ids就是我们分词化后的Token。attention_mask就是掩膜(填充的地方为0)。token_type_ids表示句子的序列,在我们这用不上,所以不赘述。
这里我们可以选择一小批数据进行微调,不搞太多。
# 选择一小批数据进行微调,不搞太多
small_train_dataset = tokenized_datasets["train"].shuffle(seed=42).select(range(50))
small_eval_dataset = tokenized_datasets["test"].shuffle(seed=42).select(range(50))
好的,现在我们把需要训练的数据准备好了。接下来就是准备训练的超参数了。这里TrainingArguments就帮我们封装好了这些超参数。基本上非常全面,用这个就行了。我们准备好训练策略。如果你觉得不安心,想知道还有哪些参数怎么办?丢给GPT :)
traning_args = TrainingArguments(
output_dir="./test_trainer", # 输出结果文件夹
num_train_epochs=3,
per_device_train_batch_size=16,
per_device_eval_batch_size=16,
eval_strategy="epoch", # 在每一个epoch后进行验证
save_strategy="epoch", # 在每个epoch后保存模型
)
在Hugging Face的训练中,通常还会添加一个精度判断的策略。这里我们也加上。
metrics = evaluate.load("accuracy")
def compute_metrics(eval_pred):
logits, labels = eval_pred
predictions = np.argmax(logits, axis=-1)
return metrics.compute(predictions=predictions, references=labels)
现在我们就可以开始训练了。
# 现在就可以进行训练了
trainer = Trainer(
model=model,
args=traning_args,
train_dataset=small_train_dataset,
eval_dataset=small_eval_dataset,
compute_metrics=compute_metrics
)
trainer.train()
怎么样,是不是很简单? 这样我们就成功微调好我们的大模型了。微调后,我们用一个例子,与未微调的数据对比一下:
当然这里的代码需要注意一个问题,在Hugging Face的训练过程中,如果你有GPU,他是会默认把model移动到我们的GPU中,所以在后面的代码中一定要注意tensor和model是不是在同一个device上!
# 一个tokenizer后的数据包括 label, text, input_ids
# 利用训练后的模型进行推理
import torch
text = torch.tensor(small_eval_dataset[0]['input_ids']).unsqueeze(0).to("cuda")
init_model = AutoModelForSequenceClassification.from_pretrained("google-bert/bert-base-cased", num_labels=5, cache_dir="./model").to("cuda")
with torch.no_grad():
outputs = model(text)
outputs_noft = init_model(text)
print(f"原始标签:{small_eval_dataset[0]["label"]}")
print(f"未微调:{np.argmax(outputs_noft.logits.to("cpu"), axis=-1).item()}")
print(f"微调:{np.argmax(outputs.logits.to("cpu"), axis=-1).item()}")
得到的结果如下:
这样,我们就微调好一个文本分类大模型了!! 是不是很简单!当然模型和数据很多,大家可以完成各种各样的任务,文生图,图生文都可以。这个只是最简单的例子。
完整代码
import transformers
import numpy as np
import torch
import evaluate
from transformers import AutoTokenizer
from transformers import Trainer
from transformers import AutoModelForSequenceClassification
from transformers import TrainingArguments
from datasets import load_dataset
dataset = load_dataset("yelp_review_full", cache_dir="./dataset")
# 对训练数据进行分词
tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-cased", cache_dir="./Tokenizer")
def tokenize_function(examples):
return tokenizer(examples['text'], padding="max_length", truncation=True)
# 利用map函数对tokenized进行分词化
tokenized_datasets = dataset.map(tokenize_function, batched=True)
# 选择一小批数据进行微调,不搞太多
small_train_dataset = tokenized_datasets["train"].shuffle(seed=42).select(range(50))
small_eval_dataset = tokenized_datasets["test"].shuffle(seed=42).select(range(50))
model = AutoModelForSequenceClassification.from_pretrained("google-bert/bert-base-cased", num_labels=5, cache_dir="./model")
traning_args = TrainingArguments(
output_dir="./test_trainer", # 输出结果文件夹
num_train_epochs=3,
per_device_train_batch_size=16,
per_device_eval_batch_size=16,
eval_strategy="epoch", # 在每一个epoch后进行验证
save_strategy="epoch", # 在每个epoch后保存模型
)
# 由于Hugging face中的Trainer只会计算loss
# 我们可以自定义一些指标的计算
metrics = evaluate.load("accuracy")
def compute_metrics(eval_pred):
logits, labels = eval_pred
predictions = np.argmax(logits, axis=-1)
return metrics.compute(predictions=predictions, references=labels)
# 现在就可以进行训练了
trainer = Trainer(
model=model,
args=traning_args,
train_dataset=small_train_dataset,
eval_dataset=small_eval_dataset,
compute_metrics=compute_metrics
)
trainer.train()
text = torch.tensor(small_eval_dataset[0]['input_ids']).unsqueeze(0).to("cuda")
init_model = AutoModelForSequenceClassification.from_pretrained("google-bert/bert-base-cased", num_labels=5, cache_dir="./model").to("cuda")
with torch.no_grad():
outputs = model(text)
outputs_noft = init_model(text)
print(f"原始标签:{small_eval_dataset[0]["label"]}")
print(f"未微调:{np.argmax(outputs_noft.logits.to("cpu"), axis=-1).item()}")
print(f"微调:{np.argmax(outputs.logits.to("cpu"), axis=-1).item()}")
创作不易,欢迎点赞、评论、收藏(甚至打赏~) : )