ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PEFT LoRA微调Qwen1.5:HC3-Chinese人机文本分类实战

PEFT LoRA微调Qwen1.5:HC3-Chinese人机文本分类实战 简介这份资源围绕Qwen1.5大模型微调展开基于PEFT框架实现LoRA轻量微调并在HC3-Chinese数据集上完成文本分类任务适合想快速上手大模型微调、尤其关注中文文本分类落地场景的开发者与研究人员。资源包共8个文件整体大小1.57MBPython脚本和Jupyter Notebook提供了可运行的完整微调流程与调用示例PDF文档包含实验报告及LoRA原理解读PNG图片展示了全参数与LoRA微调的训练效果对比MD说明文档则对项目结构和复现步骤进行了梳理整体结构清晰方便按需阅读或直接复用。已有1018人学习或下载过这份资料。通过这套代码、文档与截图的组合读者可以完整了解Qwen1.5与LoRA结合完成中文文本分类的思路包括数据准备、关键参数配置、训练效果评估等环节也可以在此框架上继续扩展自己的微调实验。1. 别被.zip吓到Qwen1.5 微调 LoRA 做文本分类的真实形态一个常见误区给 Qwen1.5 做中文文本分类就要全参微调 7B准备几张卡做分布式训练。实际上Qwen1.5 发布后就带着完整 transformer 结构配合 PEFT 的 LoRA 适配器一张 8GB 显存卡就能在 HC3-Chinese 上跑通人机文本二分类。HC3-Chinese 不是普通主题分类数据集它把同一问题下的人类回答和 ChatGPT 回答放在一起这里要做的分类是判断一段中文回答到底由人写的还是模型生成的。这个任务和幻觉检测、内容标记同源工程上比做情感分类更贴近真实内容安全场景。适合那类想快速验证 Qwen 系列在中文任务上效果的算法工程师也适合刚接触 PEFT 方向的研究生。整个流程可以概括为选一个小模型、挂一组 LoRA、把问答对拉平成二分类样本、跑三个 epoch。2. LoRA 凭什么改动 Qwen1.5低秩矩阵、可挂载层与 HC3-Chinese 的样本结构LoRA 的原理在英文资料里被写了很多遍但中文博客常把r和alpha讲成本地魔法数字。这里按 0.5B 模型实际用法拆一遍再看 Qwen1.5 的参数结构里哪些层值得被改动。2.1 冻结主干、只学低秩增量全参微调会把 Qwen1.5 的全部参数矩阵 W 都更新。LoRA 的做法是保留原始 W 不变在旁边训练两个小矩阵 B 和 A前向传播时把BA乘积作为增量加到 W 上数学上写成h Wx (alpha/r)BAx。因为 A 是 r 行、B 是 r 列r 往往取 8 或 16所以真正参与更新的参数量能降到原来百分之一左右冻结的部分不再产生梯度。这里有一个在文本分类任务上容易忽略的点alpha/r不是学习率它只控制推理时增量的缩放。r8, alpha8和r8, alpha16相比训练速度和显存差别很小但后者让每个权重变化被放大两倍同样学习率下更容易过拟合。所以不要孤立调 alpha要先定 r再把 alpha 设成 r 的一到两倍。HC3-Chinese 这种几万条规模的中文数据集初始值适合放在r8, alpha16。2.2 Qwen1.5 的可挂载层attention 和 MLP 都要看Qwen1.5 的 decoder layer 由自注意力、MLP、两个 norm 组成LoRA 一般挂在线性层上。PEFT 的target_modules支持用正则表达式指定模块名也可以显式写出每个 projection。常见做法是先把所有带proj的层都挂上再根据显存删减。用一段代码把模块名打出来是最直接的确认方式model AutoModelForSequenceClassification.from_pretrained( Qwen/Qwen1.5-0.5B, num_labels2 ) # 只看可能与 LoRA 发生关系的线性层 for name, _ in model.named_modules(): if proj in name: print(name)AutoModelForSequenceClassification会为 Qwen1.5 生成隐层和一个score线性头输出两个 logits。上面的循环能看到model.layers.0.self_attn.q_proj、model.layers.0.mlp.gate_proj这类名字它们就是 LoRA 可以插入的位置。下面这张表是目前社区在 Qwen 系列分类模型上最常用的挂载策略target_modules 配置说明适用场景[q_proj, v_proj]显存最省改动很小分类效果一般显存小于 8G 的快速测试全部 attention MLP proj16 组 LoRA充分改变行为有 12G 以上显存求质量只选 [q_proj]r 取 16秩集中在注意力适合只调风格的生成任务文本分类不推荐实际做 HC3-Chinese 分类时我一般把gate_proj、up_proj也挂上。只动 attention 不足以区分人类和 ChatGPT 回答因为这两种文本差异更多体现在措辞频率、逻辑连接和句式结构上这些特征需要经过 MLP 才能被充分变换。当然target_modules 越多参数量越大0.5B 模型挂全部 proj 也只有约两千万可训练参数完全能接受。2.3 HC3-Chinese 不是主题分类是人机二分类HC3 数据集最初用于评估大模型幻觉它收集了真实问题、人类专家回答和 ChatGPT 生成回答。中文子集里每条记录是{q: 介绍一下量子计算, human_answers: [...], chatgpt_answers: [...]}。看起来是问答数据集但把每个 answer 单拆并打上 0/1 标签后它变成标准二分类数据集label0表示人工回答label1表示模型生成。这种构造决定了模型学到的不是“这句话对不对”而是“这句话更像谁写的”。同样一条高质量回答人和 ChatGPT 的措辞层级、连接词分布、信息密度都不同LoRA 在低秩约束下学到的正是这种分布差异。HC3-Chinese 在幻觉检测场景中经常被拿来当基线所以标题里这个数据集值得先说明白否则很容易误当成主题分类任务。3. 本地复现的准备PEFT 环境、HC3-Chinese 拉平成 train/test 与 LoraConfig3.1 依赖与显存预算LoRA 微调 Qwen1.5 的依赖比想象中少。基础是 transformers、peft、datasets、scikit-learn需要进一步压显存时才加 bitsandbytes。0.5B 和 1.8B 模型在 8G 显存卡上都能直接训练推荐版本线如下pip install transformers4.37 peft0.7 datasets2.14 \ accelerate0.26 evaluate0.4 scikit-learn1.2transformers4.37很关键。Qwen1.5 的分类模型变体在这个版本后稳定支持AutoModelForSequenceClassification加载。peft 用 0.7 及以上LoraConfig的task_typeSEQ_CLS才能把分类头自动加入可训练参数。scikit-learn给后面 F1 和 AUC 计算用避免手写混淆矩阵统计。3.2 HC3-Chinese 字段解析从 q/human_answers/chatgpt_answers 到 CSV数据集在 Hugging Face 上的常见标识是Hello-SimpleAI/HC3-Chinese。直接用load_dataset加载后把它变成一个没有嵌套结构的 DataFrame。下面这段把每个 answer 变成一条样本并保留问题上下文from datasets import load_dataset import pandas as pd raw load_dataset(Hello-SimpleAI/HC3-Chinese) records [] for split in raw.values(): for item in split: q item.get(q, ).strip() # 人工回答标 0ChatGPT 回答标 1 for ans in item[human_answers]: records.append({text: f问题{q}\n回答{ans.strip()}, label: 0}) for ans in item[chatgpt_answers]: records.append({text: f问题{q}\n回答{ans.strip()}, label: 1}) df pd.DataFrame(records) df df.drop_duplicates(subset[text]).reset_index(dropTrue) df.sample(frac1, random_state42).to_csv(hc3_chinese.csv, indexFalse)这段代码把human_answers里的每个字符串标 0chatgpt_answers里的每个字符串标 1再拼上问题本身。保留问题上下文是必要的因为很多回答本身很短单独一个“好的”或“当然”无法判断出处有了问题措辞搭配才有区分度。drop_duplicates防止同一条答案重复出现在训练和评估中造成泄漏。如果字段里 q 为空item.get(q, )会兜底返回空串不抛异常。如果下载不稳定可以用huggingface-cli先把数据集缓存到本地目录再把load_dataset的第一个参数换成本地路径。字段结构不变后续处理完全一样。3.3 LoraConfig 的参数怎么设r、lora_alpha、target_modules 与 task_typePEFT 的LoraConfig是这套流程里最值得解释的类。下面是一份能直接跑 0.5B 的配置from peft import LoraConfig config LoraConfig( task_typeSEQ_CLS, r8, lora_alpha16, target_modules[ q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj ], lora_dropout0.1, biasnone, )task_type必须写SEQ_CLS。这样 PEFT 会创建PeftModelForSequenceClassification自动把基座分类头score加入可训练参数并放进modules_to_save。分类头是新的随机线性层不走低秩分解modules_to_save是 LoRA 之外需要完整微调的那些层。其他参数含义如下参数建议值说明r8 / 16低秩矩阵的秩。r 越大增量越强参数量近似线性增长lora_alphar 的 1.5~2 倍增量缩放系数影响梯度更新实际幅度lora_dropout0.05~0.1LoRA 分支 dropout数据量小时建议 0.1 起步biasnone是否训练 bias。对文本分类收益很小但显存开销大target_modules见 2.2 表格挂载 LoRA 的层名列表或正则有经验的工程师会先设r8训一版看验证集指标如果数据量超过几万条再试r16。把r拉到 32 不会显著提升分类准确率反而让 adapter 文件更大推理时需要处理更多增量。3.4 大模型微调实战加载 Qwen1.5 的 Tokenizer 与分类模型模型加载注意点集中在 tokenizer 的 pad token 和模型是否带分类头。Qwen1.5 的 tokenizer 默认没有 pad token不设置的话动态 padding 会产生警告甚至报错。常见做法是用 eos token 顶替from transformers import AutoModelForSequenceClassification, AutoTokenizer model_id Qwen/Qwen1.5-0.5B tokenizer AutoTokenizer.from_pretrained(model_id) if tokenizer.pad_token is None: tokenizer.pad_token tokenizer.eos_token # 加载带 2 分类头的 Qwen 模型 model AutoModelForSequenceClassification.from_pretrained( model_id, num_labels2, torch_dtypeauto, )AutoModelForSequenceClassification会读取 Qwen1.5 的 model_type加载分类模型变体输出层替换成num_labels2的线性层。如果使用国内模型平台下载的权重路径换成对应本地目录即可Qwen1.5 一般不需要开trust_remote_code。拿到 model 后执行model get_peft_model(model, config)再用model.print_trainable_parameters()确认可训练参数比例。0.5B 模型挂上 LoRA 后这个比例通常在 3% 左右说明真正参与反向传播的参数很少训练阶段小 batch 也能跑稳定。4. 训练 Qwen1.5-LoRA 分类器Trainer、梯度累计、F1 评估与权重合并4.1 tokenize 与 DataCollatorWithPaddingTrainer 不关心数据集原始字段只关心 tokenizer 输出和标签。先把上一章生成的 CSV 读回变成 Dataset再批量 tokenizefrom datasets import Dataset from transformers import DataCollatorWithPadding df pd.read_csv(hc3_chinese.csv) ds Dataset.from_pandas(df[[text, label]]) def tokenize_fn(batch): return tokenizer( batch[text], paddingFalse, truncationTrue, max_length256, ) ds ds.map(tokenize_fn, batchedTrue, remove_columns[text]) train_test ds.train_test_split(test_size0.1, seed42) train_ds, eval_ds train_test[train], train_test[test] collator DataCollatorWithPadding(tokenizer)paddingFalse是有意的map 时先不 padding训练时DataCollatorWithPadding会把一个 batch 内最长样本作为长度上限把其他样本补到相同长度。这比提前统一截断到 256 更省显存也减少无效 token 参与注意力计算。max_length256对 HC3-Chinese 的回答足够中文一问一答通常在几十到一百 token 内。如果句子平均更长可以调成 512但显存会对应上涨。4.2 TrainingArguments 里最重要的 6 个参数合上 LoRA 后学习率不能再沿用全参微调的 2e-5因为被更新的参数只有几个低秩矩阵梯度量级不同常见范围在 1e-4 到 3e-4。下面参数表是个人比较稳的起点参数建议值说明learning_rate2e-4LoRA 参数少学习率比全参高一个量级per_device_train_batch_size20.5B 模型 5G 显存可稳定跑gradient_accumulation_steps8有效 batch 2 × 8 16评估曲线更平滑num_train_epochs3HC3-Chinese 打平后约几万条样本3 轮足够fp16True20 系以后显卡都支持30 系以上可换 bf16save_strategyepoch每轮保存 checkpoint方便回退eval_strategy在 transformers 4.37 中取代旧版evaluation_strategy用旧参数会报警告但不影响运行。logging_steps设成 20 或 50能在前几百步看到 loss 明显下降。如果显存不够先调低 batch size再增大 gradient accumulation不要一上来就删 target_modules。4.3 compute_metricsaccuracy 与 F1 一起看HC3-Chinese 正负样本大致均衡只看 accuracy 看不出模型在“人类回答”上的漏判率。F1 和 AUC 才是判断人机分类质量的标准。用 Trainer 的compute_metrics每轮评估自动计算from sklearn.metrics import accuracy_score, f1_score, roc_auc_score def compute_metrics(eval_pred): logits, labels eval_pred preds logits.argmax(axis-1) return { accuracy: accuracy_score(labels, preds), f1: f1_score(labels, preds, averagebinary), auc: roc_auc_score(labels, logits[:, 1]), }logits[:, 1]是“AI 生成”类别的原始分数不需要先过 softmaxAUC 对单调变换不敏感。AUC 在阈值切换时尤其重要当任务希望尽量不放过大语言模型文本时可以把预测阈值从 0.5 调高到 0.6 或 0.7准确率和召回率会换位置而 AUC 能告诉你模型排序能力本身有没有变化。把模型、参数、数据、collator 一起交给 Trainer 后调用trainer.train()。训练过程中记录每个 epoch 的 eval AUC对比基座模型随机权重的表现来判断是否收敛。如果第二个 epoch 的 F1 比第一个明显下降说明过拟合优先把lora_dropout调到 0.15或减少num_train_epochs。4.4 合并 LoRA 权重为一个完整模型训练结束后output_dir 下的 checkpoint 里只有 LoRA adapter 权重和adapter_config.json没有基座模型权重。这种方式方便发布但部署时若想让模型在普通推理服务里直接加载需要先合并import torch from peft import PeftModel base_model AutoModelForSequenceClassification.from_pretrained( model_id, num_labels2, torch_dtypetorch.float16, ) peft_model PeftModel.from_pretrained( base_model, ./qwen15-lora-hc3/checkpoint-9000, ) merged_model peft_model.merge_and_unload() merged_model.save_pretrained(./qwen15-hc3-merged) tokenizer.save_pretrained(./qwen15-hc3-merged)merge_and_unload()会把BA乘积真正加到原权重矩阵里然后释放 adapter 结构。合并后模型可以单独用from_pretrained加载效果和 PEFT 加载完全一致但失去了运行时替换单张 adapter 的能力。如果要在生产环境同时服务多个 LoRA保持 adapter 分离反而更合理固定一个版本走评估或私有化部署合并是更干净的做法。5. 验证技巧用 100 条新回答测漏判并把 LoRA 适配器跑成独立模型5.1 加载合并后的模型做推理合并后的目录就是标准 huggingface 模型目录写一个几十行的 predict 脚本作为验收端。常见做法是构造与训练一致的问题加回答格式from transformers import AutoModelForSequenceClassification, AutoTokenizer import torch tokenizer AutoTokenizer.from_pretrained(./qwen15-hc3-merged) model AutoModelForSequenceClassification.from_pretrained(./qwen15-hc3-merged) model.eval() test_text 问题人工智能会不会取代医生\n回答会但不会完全取代。 inputs tokenizer(test_text, return_tensorspt, truncationTrue, max_length256) with torch.no_grad(): logits model(**inputs).logits prob torch.softmax(logits, dim-1)[0, 1].item() print(fAI生成概率: {prob:.3f})输入格式必须和训练时保持一致。如果训练样本是“问题XXX\n回答YYY”推理时却只传“回答YYY”模型会把它当成另一类分布容易出现全偏向 label 0 的假象。这是人机文本分类最容易踩的坑比 LoRA 参数的影响更大。5.2 阈值选择与对抗样本验证验证时不要只看自己写的句子要拿几条真实 ChatGPT 回答和几条编辑改写后的回答按 0.5、0.6、0.7 三档阈值分别打印混淆矩阵。如果发现大量真实人工回答被判成 AI 生成把阈值提高如果发现模型生成的短句漏判优先去改训练数据里的问答格式而不是继续调 lora_alpha。一个有用的技巧从 HC3-Chinese 里抽出 100 条没参与训练的样本用上面的脚本跑完统计prob 0.5的条数再对比真实标签。这个数字比 TensorBoard 上的 accuracy 更接近上线后体验。对抗样本至少覆盖三种人工改写的中文长回答、夹杂英文术语的回答、以及故意用短句拼凑的模型文本。这三类最容易让二分类器失效。5.3 把 LoRA 参数抄进 LLaMA-Factory 做快速对照进阶场景里很多工程师会把同样参数拿进 LLaMA-Factory 这类工具复现。它的 LoRA 参数叫lora_rank、lora_alpha、lora_target与本机的r、lora_alpha、target_modules一一对应。如果两个框架显存和指标差别很大先检查三件事pad token 是否一致、tokenize 时是否保留同样问题前缀、分类头是否被正确排除在低秩外。想快速确认 adapter 是否能离线分发直接执行下面这条命令python -c from peft import PeftModel; PeftModel.from_pretrained(\Qwen/Qwen1.5-0.5B\, \./qwen15-lora-hc3/checkpoint-9000\)这条命令检查的是 checkpoint 目录下有没有同时存在adapter_model.safetensors和adapter_config.json两者缺一个都会在from_pretrained时报错。见到 adapter 加载成功后再去调分类阈值后续工作就只剩工程部署了。本文还有配套的精品资源点击获取
返回列表