目录

一、训练数据准备

二、模型训练

1、设备检查

2、加载模型和 tokenizer

3、配置 LoRA

4、加载数据集

5、数据预处理(加入数据批处理)

6、配置训练参数(加入Trainer)

7、开始训练

三、矩池云

1、上传整个项目文件夹

2、开始训练

三、模型测试


由于苯人已经开始实习,所以专门开个专栏来记录一下实习工作,今天上午进行了如题的训练,下面开始(顺便说一下,这是苯人第一次进行脚本式的模型训练,过程可能会很简陋,且苯人是在矩池云上进行训练的):

一、训练数据准备

负责人给到我的原始数据集是Excel形式,且没有表头,需要转换为大模型训练所需的json格式,处理代码如下:

import pandas as pd
import json

df = pd.read_excel("QW.xlsx", header=None) #没有表头
df.columns = ["question", "answer"] #所以手动设置列名

# 去掉空值
df = df.dropna(subset=["question", "answer"])

with open("QW.json", "w", encoding="utf-8") as f:
    for _, row in df.iterrows():
        record = {
            #去掉首尾空格
            "instruction": "你是一个生产数据分析助手,请根据生产数据回答以下问题:", #模型角色
            "input": str(row["question"]).strip(), #用户输入
            "output": str(row["answer"]).strip() #期望输出
        }
        f.write(json.dumps(record, ensure_ascii=False) + "\n")
        # ensure_ascii :允许保存中文

print("已生成QW.json,样例:")
print(open("QW.json", "r", encoding="utf-8").readline())

运行后会生成 QW.json ,每行数据长这样:

{"instruction": "你是一个生产数据分析助手,请根据生产数据回答以下问题:", "input": "最近90天重庆生产汇报/扭力梁/C65的平均出勤人数是多少?", "output": "最近90天,重庆生产汇报中关于扭力梁/C65项目的平均出勤人数为4.03人。"}

二、模型训练

由于我是CPU,所以我写的脚本最终要在矩池云上运行,训练的流程我大概总结了:

设备检查 -> 加载模型和 tokenizer -> 配置 lora 参数 -> 加载数据集 -> 数据预处理(包括批处理) ->配置训练参数(顺便加上 trainer) -> 开始训练 -> 保存模型(在trainer里实现了)

1、设备检查

def train():
    try:
        # 检查设备
        device = "cuda" if torch.cuda.is_available() else "cpu"

2、加载模型和 tokenizer

# 加载模型和tokenizer
        # model_path = "./deepseek-1.5B" #上传到矩池云用这个
        model_path = "../deepseek-1.5B"

        tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
        if tokenizer.pad_token is None:
            tokenizer.pad_token = tokenizer.eos_token #添加结束符

        # 配置模型
        model = AutoModelForCausalLM.from_pretrained(
            model_path,
            torch_dtype=torch.float16 if device == "cuda" else torch.float32,
            device_map="auto",
            trust_remote_code=True
        )

注意 model_path 这里,我本来的基础模型在项目文件夹的根目录,但是上传到矩池云的话就一个 project 目录,所以要注意目录层级

3、配置 LoRA

# 配置LoRA
        lora_config = LoraConfig(
            r=8,  # 低秩维度
            lora_alpha=16,
            target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],  #针对transformer block
            lora_dropout=0.05,
            bias="none",
            task_type="CAUSAL_LM"
        )
        model = get_peft_model(model, lora_config)
        model.print_trainable_parameters()

4、加载数据集

# 加载数据集
        dataset = load_dataset('json', data_files='QW.json') # hf的dataset库
        train_dataset = dataset['train'] #只提供一个数据集时默认全部作为训练集
        # 或者加载时分割
        # dataset = load_dataset('json', data_files='QW.json', split='train[:80%]')
        print(f"训练数据集数: {len(train_dataset)}")

这里我们只有一个数据集

5、数据预处理(加入数据批处理)

# 数据预处理
        def preprocess_function(examples):
            texts = []
            for i in range(len(examples['instruction'])):
                #构建训练文本格式
                text = (
                    f"### Instruction:\n{examples['instruction'][i]}\n\n"
                    f"### Input:\n{examples['input'][i]}\n\n"
                    f"### Response:\n{examples['output'][i]}{tokenizer.eos_token}" #tokenizer.eos_token是结束标记
                )
                texts.append(text)
            return tokenizer( #把文本变为 token 向量
                texts,
                truncation=True, #超过长度就截断
                max_length=768, #最大输出
                padding="max_length", #统一填充
                return_tensors="pt" #返回张量
            )

        print("开始预处理数据...")
        tokenized_dataset = train_dataset.map( #将整个预处理应用到完整数据集
            preprocess_function,
            batched=True, #批量处理
            remove_columns=train_dataset.column_names #移除原始列,只保留tokenized后的数据
        )

这里我们进行数据处理是为了将原始的 json 数据转换成模型能直接学习的训练文本,并用 tokenizer 转为 token 向量,最终喂给模型训练,用一句话总结这个流程的话就是:

原始数据 → 拼成模型格式的训练句子 → tokenizer 转 token → batch 整理 → 送入 Trainer

还有一段数据批处理:

# 数据批处理
        data_collator = DataCollatorForLanguageModeling(
            tokenizer=tokenizer,
            mlm=False,
        )

就是负责的把单条 token 化后的数据打包成统一长度的 batch、自动生成 labels、自动处理 padding和 mask、为 GPT类模型提供“下一个token”,就像一个小管家一样负责整理数据、打包数据、确保格式正确

6、配置训练参数(加入Trainer)

# 训练参数
        training_args = TrainingArguments(
            output_dir="../deepseek-1.5b-lora",  # 模型保存目录
            # output_dir="./deepseek-1.5b-lora",  # 模型保存目录
            num_train_epochs=3,  # 训练3轮
            per_device_train_batch_size=2,  # 批大小
            gradient_accumulation_steps=4,  # 累积4步,有效批大小=4
            learning_rate=1e-4,
            fp16=True,  # GPU上使用半精度训练
            #以下在上传矩池云时添加
            logging_steps=10,
            save_strategy="epoch",
            warmup_steps=100,  # 添加热身步骤
            save_total_limit=2,  # 只保存最后2个检查点
            dataloader_pin_memory=False,  # 避免内存问题
        )

这里就是配置训练的具体参数,每个参数怎么选可以参考:

  • output_dir:模型/检查点保存位置。路径别错就好。

  • num_train_epochs:一般 2–5 轮。看数据量、loss 曲线和过拟合风险。

  • per_device_train_batch_size:受显存限制,能大就大。

  • gradient_accumulation_steps:模拟大 batch。有效 batch= per_device * 累积 * GPU数

  • learning_rate:LoRA 常见 1e-4 ~ 3e-4;若发现不稳定/发散,先降到 5e-5 ~ 1e-4

  • fp16/bf16:如果 GPU 支持 bfloat16(如 A100、H100),可用 bf16=True 更稳;否则 fp16=True

  • logging_steps:日志频率。太小会频繁打印,太大看不到趋势。

  • save_strategy"epoch" 简单、占空间少;要更细粒度可用 "steps" 搭配 save_steps

  • warmup_steps:建议按数据规模改成“比例”更方便,比如 warmup_ratio=0.03

  • save_total_limit:控制磁盘占用。

  • dataloader_pin_memory:有些云平台开了反而不稳,这里关闭是求稳做法。

接下来是 Trainer:

# Trainer
        trainer = Trainer(
            model=model,
            args=training_args,
            train_dataset=tokenized_dataset,
            data_collator=data_collator,
        )

Trainer 其实是 HuggingFace Transformers 提供的 训练管理器(Training Manager),背后的逻辑其实就相当于:

for epoch in range(num_epochs):
    for step, batch in enumerate(dataloader):
        outputs = model(**batch)
        loss = outputs.loss

        loss.backward()

        if step % gradient_accumulation_steps == 0:
            optimizer.step()
            optimizer.zero_grad()

    save_checkpoint()   # save_strategy="epoch"

可以理解为:

并且还做了很多“增强功能”,比如梯度裁剪、日志记录、checkpoint清理等等,它就相当于大模型训练的全自动驾驶系统,只要给它方向(参数)、燃料(数据)、车(模型),它就负责跑完全程。

7、开始训练

# 开始训练
        print("开始训练...")
        trainer.train()

        print("训练完成!模型保存成功")

直接调用就可以了,上面也说过 trainer 里已经自动保存了模型,只需要加入最后一行输出就可以确定是否是训练完了,当然可以再加一个错误显示:

#添加错误显示和进度处理
    except Exception as e:
        print(f"训练出错: {e}")
        import traceback
        traceback.print_exc()

这样训练脚本基本就写完了,接下来就可以去矩池云租算力,也就是开始烧钱了(〒︿〒)

三、矩池云

矩池云这个平台我就不介绍了,就是一个租 GPU 的平台,这里我记录一下整个流程(因为苯人当时训练时没有截图对应的终端,只能文字描述了,图片也只能放本地的):

1、上传整个项目文件夹

上传到矩池云网盘的代码需要包含以下几个文件:

原始模型:

数据集:

训练脚本:(注意要改成英文名,因为矩池云终端识别不了中文,我这里是本地文件,可以在矩池云网盘上传后再重命名,比如 model_train.py)

将这整个文件夹命名为 deepseek_train 然后上传到矩池云网盘

2、开始训练

首先租用一张显卡,注意一定要是上传文件夹的那个区:

我一般是配置的这个环境加上开启 VNC(首先要下载 VNC软件),然后通过给出的 VNC 的账号密码打开VNC后进行连接,这时候点开桌面的终端Terminal,因为矩池云的网盘默认根目录是 /mnt,所以首先要 cd /mnt 到根目录下,然后可以 ls,查看是否显示刚刚上传的 deepseek_train 文件夹,有的话就 cd 进入,没有那就要排查一下原因了

进入项目文件夹后把依赖包安装好,好像有 transformers、datasets、accelerate、peft、pandas这些,torch 不需要因为我们选择的环境里已经有了,参考版本:

然后直接运行 python model_train.py,就开始训练了,还好训练的图我截了:

训练完后看到保存了检查点就说明模型保存成功了:

                                        

这里的333其实表示的是训练步数,我这里的333是因为首先 epoch=3,batch_size=2,梯度累计步数=4,首先每轮的步数是 886/(2*4)=111,然后有3轮所以 111*3=333步,我又设置的只保存最后一轮检查点的模型参数,所以就是这个文件夹了,然后不带一秒犹豫地停止租用并退出(´▽`)ノ 

顺便说一下,在终端的时候如果文件有任何问题都是可以修改的,可以直接在外部把有问题的文件删除然后再重新上传(建议下载一个矩池云网盘);也可以直接打开 jupyternotebook,就在复制VNC账号密码的地方,与VNC一排的有 jupyternotebook,下面有一个“点击打开”,然后就会弹出网页了,同样在这上面也可以增删改查文件。

三、模型测试

将矩池云上保存的模型下载到本地后就可以在本地进行模型测试了,写一个测试文件:

from transformers import AutoTokenizer, AutoModelForCausalLM
from peft import PeftModel
import torch

# 加载微调模型和基础模型
model_path = "../ds1.5b_lora-checkpoint-333"  # 微调权重路径
base_model = "../deepseek-1.5B"  # 基础模型路径

# 模型配置(加载基础模型的分词器-> 加载原始模型-> 将lora适配器加载到基础模型上)
tokenizer = AutoTokenizer.from_pretrained(base_model)
model = AutoModelForCausalLM.from_pretrained(base_model)
model = PeftModel.from_pretrained(model, model_path) #PeftModel:加载适配器
device = "cuda" if torch.cuda.is_available() else "cpu"
model.to(device)

# 测试输入文本 按照训练的格式
test_text = "### Instruction:\n最近90天完成率的趋势如何?\n\n### Response:\n"

# 转换输入
inputs = tokenizer(test_text, return_tensors="pt").to(device)

# 生成输出
outputs = model.generate(
    **inputs, #输入token
    max_length=512,
    temperature=0.7, #控制随机性
    pad_token_id=tokenizer.eos_token_id  # 设置结束符
)

# 解码并输出生成结果
print(tokenizer.decode(outputs[0], skip_special_tokens=True))

测试流程大概是:

加载原始模型及微调模型 -> 模型配置 (加载基础模型的分词器 -> 加载原始模型 -> 将lora适配器加载到基础模型上 -> )-> 输入测试文本转换输入,生成输出并解码 

运行结果我就不贴了(因为苯人没截图,且已经把原始模型删了),反正肯定能回答出来,只是精准度的问题

  以上就是这篇,其实下一个任务是训练 qwen1.5b,负责人告诉我他们通过官方的方法微调了一个,我就想在 LlaMA-Factory 上微调一个试试,结果这一试试出了好多问题。。本来下一篇我想写这个的,根本写不出来。。最终还是脚本训练的,虽然训练的结果还可以因为我是全参数微调,但是基本逻辑跟这篇差不多了,我始终认为应该是数据集的配置 dataset_info.json 有问题,跟数据集格式就是一直没对齐 (ー̀дー́),等我下次准备好了再试一次

以上有问题可以指出 (๑•̀ㅂ•́)و✧

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐