完整博客教程:使用Lamini和Hugging Face进行大语言模型微调
介绍
在本教程中,我们将深入探讨如何使用Lamini库和Hugging Face transformers来微调大型语言模型。通过实际的代码示例,我们将一步步了解如何加载预训练模型、准备数据、进行训练和评估模型性能。
环境设置和导入
import os
import lamini# 设置Lamini API配置
lamini.api_url = os.getenv("POWERML__PRODUCTION__URL")
lamini.api_key = os.getenv("POWERML__PRODUCTION__KEY")
代码解释:这里设置了Lamini的API端点URL和认证密钥,用于访问Lamini的服务。
import datasets
import tempfile
import logging
import random
import config
import os
import yaml
import time
import torch
import transformers
import pandas as pd
import jsonlinesfrom utilities import *
from transformers import AutoTokenizer
from transformers import AutoModelForCausalLM
from transformers import TrainingArguments
from llama import BasicModelRunner
代码解释:导入所有必要的库,包括:
datasets
:用于处理数据集torch
:PyTorch深度学习框架transformers
:Hugging Face的transformers库pandas
:数据处理库
配置和初始化
logger = logging.getLogger(__name__)
global_config = None# 数据集配置
dataset_name = "lamini_docs.jsonl"
dataset_path = f"/content/{dataset_name}"
use_hf = False
dataset_path = "lamini/lamini_docs"
use_hf = True# 模型配置
model_name = "EleutherAI/pythia-70m"
代码解释:设置日志记录器并配置数据集和模型路径。这里使用了Hugging Face数据集(use_hf = True
)和Pythia-70M模型。
training_config = {"model": {"pretrained_name": model_name,"max_length" : 2048},"datasets": {"use_hf": use_hf,"path": dataset_path},"verbose": True
}
代码解释:创建训练配置字典,指定:
预训练模型名称
最大序列长度(2048个token)
数据集来源和路径
详细输出模式
数据预处理
# 加载分词器
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token# 分词和分割数据
train_dataset, test_dataset = tokenize_and_split_data(training_config, tokenizer)print(train_dataset)
print(test_dataset)
代码解释:
加载与预训练模型对应的分词器
将填充token设置为结束token(EOS)
使用辅助函数对数据进行分词并分割为训练集和测试集
加载基础模型
# 加载基础模型
base_model = AutoModelForCausalLM.from_pretrained(model_name)# 设备选择(GPU优先)
device_count = torch.cuda.device_count()
if device_count > 0:logger.debug("Select GPU device")device = torch.device("cuda")
else:logger.debug("Select CPU device")device = torch.device("cpu")base_model.to(device)
代码解释:
从Hugging Face加载预训练的因果语言模型
自动检测并选择可用的计算设备(优先使用GPU)
将模型移动到选定的设备上
推理函数
def inference(text, model, tokenizer, max_input_tokens=1000, max_output_tokens=100):# 分词input_ids = tokenizer.encode(text,return_tensors="pt",truncation=True,max_length=max_input_tokens)# 生成文本device = model.devicegenerated_tokens_with_prompt = model.generate(input_ids=input_ids.to(device),max_length=max_output_tokens)# 解码生成的文本generated_text_with_prompt = tokenizer.batch_decode(generated_tokens_with_prompt, skip_special_tokens=True)# 去除提示文本,只保留生成的答案generated_text_answer = generated_text_with_prompt[0][len(text):]return generated_text_answer
代码解释:这个函数实现了完整的文本生成流程:
分词:将输入文本转换为模型可理解的token ID
生成:使用模型生成新的文本
解码:将生成的token ID转换回人类可读的文本
后处理:去除原始提示,只返回模型生成的答案部分
测试基础模型
test_text = test_dataset[0]['question']
print("Question input (test):", test_text)
print(f"Correct answer from Lamini docs: {test_dataset[0]['answer']}")
print("Model's answer: ")
print(inference(test_text, base_model, tokenizer))
代码解释:在微调之前测试基础模型的性能,建立性能基准。
训练配置
max_steps = 3
trained_model_name = f"lamini_docs_{max_steps}_steps"
output_dir = trained_model_nametraining_args = TrainingArguments(# 学习率learning_rate=1.0e-5,# 训练轮数num_train_epochs=1,# 最大训练步数max_steps=max_steps,# 训练批次大小per_device_train_batch_size=1,# 输出目录output_dir=output_dir,# 其他参数overwrite_output_dir=False,disable_tqdm=False,eval_steps=120,save_steps=120,warmup_steps=1,per_device_eval_batch_size=1,evaluation_strategy="steps",logging_strategy="steps",logging_steps=1,optim="adafactor",gradient_accumulation_steps=4,gradient_checkpointing=False,# 早停参数load_best_model_at_end=True,save_total_limit=1,metric_for_best_model="eval_loss",greater_is_better=False
)
代码解释:配置训练参数,包括:
学习率:1e-5,适合微调的小学习率
批次大小:每个设备的训练批次大小
评估策略:每隔一定步数进行评估
优化器:使用AdaFactor优化器,节省内存
梯度累积:通过累积梯度来模拟更大的批次大小
模型分析
model_flops = (base_model.floating_point_ops({"input_ids": torch.zeros((1, training_config["model"]["max_length"]))})* training_args.gradient_accumulation_steps
)print(base_model)
print("Memory footprint", base_model.get_memory_footprint() / 1e9, "GB")
print("Flops", model_flops / 1e9, "GFLOPs")
代码解释:分析模型的:
内存占用:模型参数占用的内存大小
计算量:浮点运算次数,衡量模型复杂度
训练过程
trainer = Trainer(model=base_model,model_flops=model_flops,total_steps=max_steps,args=training_args,train_dataset=train_dataset,eval_dataset=test_dataset,
)trainer.do_grad_scaling = False
training_output = trainer.train()
代码解释:创建训练器并开始训练过程,使用配置好的参数和数据集。
保存模型
save_dir = f'{output_dir}/final'
trainer.save_model(save_dir)
print("Saved model to:", save_dir)
代码解释:将训练好的模型保存到指定目录,便于后续使用。
加载和测试微调后的模型
# 加载微调后的模型
finetuned_slightly_model = AutoModelForCausalLM.from_pretrained(save_dir, local_files_only=True)
finetuned_slightly_model.to(device)# 测试微调后的模型
test_question = test_dataset[0]['question']
print("Question input (test):", test_question)
print("Finetuned slightly model's answer: ")
print(inference(test_question, finetuned_slightly_model, tokenizer))test_answer = test_dataset[0]['answer']
print("Target answer output (test):", test_answer)
代码解释:加载刚刚微调的模型并测试其性能,与基础模型进行对比。
使用预训练的微调模型
# 加载在Lamini上预训练的模型
finetuned_longer_model = AutoModelForCausalLM.from_pretrained("lamini/lamini_docs_finetuned")
tokenizer = AutoTokenizer.from_pretrained("lamini/lamini_docs_finetuned")
finetuned_longer_model.to(device)print("Finetuned longer model's answer: ")
print(inference(test_question, finetuned_longer_model, tokenizer))
代码解释:加载在更多数据上训练更长时间的模型,观察性能提升。
使用更大的模型
# 使用更大的模型(2.8B参数)
bigger_finetuned_model = BasicModelRunner(model_name_to_id["bigger_model_name"])
bigger_finetuned_output = bigger_finetuned_model(test_question)
print("Bigger (2.8B) finetuned model (test): ", bigger_finetuned_output)
代码解释:使用更大的2.8B参数模型,通常更大的模型会有更好的性能。
数据探索
# 探索训练数据中的特定模式
count = 0
for i in range(len(train_dataset)):if "keep the discussion relevant to Lamini" in train_dataset[i]["answer"]:print(i, train_dataset[i]["question"], train_dataset[i]["answer"])count += 1
print(count)
代码解释:分析训练数据,查找包含特定内容的样本,了解数据分布。
模型对比
# 对比基础模型和微调模型
base_tokenizer = AutoTokenizer.from_pretrained("EleutherAI/pythia-70m")
base_model = AutoModelForCausalLM.from_pretrained("EleutherAI/pythia-70m")print("Base model response:")
print(inference("What do you think of Mars?", base_model, base_tokenizer))print("Finetuned model response:")
print(inference("What do you think of Mars?", finetuned_longer_model, tokenizer))
代码解释:直接对比基础模型和微调模型在相同问题上的表现。
使用Lamini的高级接口
# 使用Lamini的高级简化接口
model = BasicModelRunner("EleutherAI/pythia-410m")
model.load_data_from_jsonlines("lamini_docs.jsonl", input_key="question", output_key="answer")
model.train(is_public=True)
代码解释:展示Lamini库提供的简化接口,只需几行代码即可完成:
模型初始化
数据加载
训练过程
模型评估
# 评估模型性能
out = model.evaluate()# 整理评估结果
lofd = []
for e in out['eval_results']:q = f"{e['input']}"at = f"{e['outputs'][0]['output']}" # 微调模型输出ab = f"{e['outputs'][1]['output']}" # 基础模型输出di = {'question': q, 'trained model': at, 'Base Model': ab}lofd.append(di)# 创建对比表格
df = pd.DataFrame.from_dict(lofd)
style_df = df.style.set_properties(**{'text-align': 'left'})
style_df = style_df.set_properties(**{"vertical-align": "text-top"})
style_df
代码解释:
对模型进行全面评估
收集微调模型和基础模型的输出结果
使用pandas创建对比表格,直观展示性能差异
总结
通过本教程,我们学习了:
环境配置:如何设置Lamini和Hugging Face环境
数据准备:加载和预处理训练数据
模型加载:使用预训练的语言模型
训练配置:设置合适的训练参数
微调过程:在特定数据上微调模型
性能评估:对比不同模型的输出效果
微调大型语言模型是一个强大的技术,可以让通用模型适应特定领域的需求。Lamini库大大简化了这个过程,使得即使是没有深厚机器学习背景的开发者也能够有效地微调和使用大型语言模型。
关键要点:
微调可以显著提升模型在特定任务上的表现
合适的学习率和训练步数很重要
更大的模型通常表现更好,但需要更多计算资源
使用像Lamini这样的工具可以大幅降低入门门槛