模型微调哪些事
微调一个 Embedding 模型后,我又问了 7 个问题
上一篇博客里,我用 50 条数据、8 分钟时间,在自己的 MacBook 上微调了一个 m3e-base 模型。微调完之后,我盯着屏幕上的文件,冒出了一连串问题。这篇文章就是这些问题和答案的记录。
Q1:模型下载到哪里去了?
跑 step1_test_model.py 的时候,脚本里写的是:
model = SentenceTransformer("moka-ai/m3e-base")
没指定任何路径,400MB 的模型凭空就加载了。它藏在哪?
答案是 HuggingFace 的默认缓存目录:
~/.cache/huggingface/hub/models--moka-ai--m3e-base/
首次运行时从 HuggingFace Hub 下载,之后直接读本地缓存。你可以用 du -sh 看一眼:
$ du -sh ~/.cache/huggingface/hub/models--moka-ai--m3e-base/
391M
391MB,和博客里说的 400MB 基本吻合。所以如果你哪天想清理磁盘空间,知道去哪里找它了。
Q2:脚本的正确运行姿势是什么?
四个脚本要按顺序跑,但在此之前,有一个容易忽略的前置步骤——激活虚拟环境。
cd embedding-finetune
source venv/bin/activate # 激活虚拟环境
pip install -r requirements.txt # 安装依赖(装进 venv 里)
python step1_test_model.py # 下载模型,测试推理
python step2_prepare_data.py # 查看和验证训练数据
python step3_finetune.py # 微调(CPU 约 5~10 分钟)
python step4_compare.py # 对比微调前后效果
每个脚本跑完都会告诉你下一步该干什么,像游戏里的任务指引一样。
Q3:pip install 会污染我的全局环境吗?
不会。关键在于你先执行了 source venv/bin/activate。
激活虚拟环境之后,pip 指向的是 venv 里的那个 pip,所有依赖都装进了 embedding-finetune/venv/lib/python3.13/site-packages/ 里,跟系统环境完全隔离。
你可以验证一下:
$ which pip
/Users/你的用户名/.../embedding-finetune/venv/bin/pip # ← 在 venv 里
看到路径里有 venv 就对了。反过来说,如果你忘了激活虚拟环境就直接 pip install,那就真的装到全局去了。
一句话:先 activate,再 install。顺序不能反。
Q4:原始模型是"一个文件",微调后变成"一个文件夹"?
其实两者都是一组文件,只是存储格式不同。
原始模型在 HuggingFace 缓存里长这样:
models--moka-ai--m3e-base/
├── blobs/
│ ├── d2c4a5f5d0a9... ← 其实就是 model.safetensors(390MB)
│ ├── 15df79188... ← 其实就是 tokenizer.json
│ └── ... ← 文件名全是哈希值,人看不懂
└── snapshots/
└── 764b537a.../
├── model.safetensors → ../../blobs/d2c4a5f5... (符号链接)
└── ...
HuggingFace 借鉴了 Git 的设计:真正的文件放在 blobs/ 里,用内容哈希做文件名,方便去重和版本管理。对人类不友好,但对程序很高效。
微调后的模型就直观多了:
output/finetuned-m3e/final/
├── model.safetensors ← 390MB,一亿多个微调后的参数
├── config.json ← 模型结构
├── tokenizer.json ← 分词器
├── tokenizer_config.json
├── sentence_bert_config.json
├── modules.json
└── 1_Pooling/config.json
文件名都是人话,一目了然。
但本质上,两者装的东西完全一样:模型权重 + 分词器 + 配置文件。模型结构、参数数量都没变,唯一的区别是那 1 亿多个参数的数值被你的训练数据调过了。
打个比方:同一个 Excel 模板,填了不同的数据。模板没变,数据变了。
Q5:重复跑 step3,权重会不断变化吗?
不会。因为脚本里写死了从原始模型开始:
model = SentenceTransformer("moka-ai/m3e-base") # 每次都从这里出发
所以不管你跑多少次,流程都是:
第 1 次:原始模型 + train_data.csv → 微调 → 保存
第 2 次:原始模型 + train_data.csv → 微调 → 覆盖保存
第 3 次:原始模型 + train_data.csv → 微调 → 覆盖保存
起点相同,数据相同,结果基本一致(有微小的随机性差异,但可以忽略)。
如果你想让权重"不断变化",需要改成加载上一次的微调结果:
model = SentenceTransformer("./output/finetuned-m3e/final") # 从上次的结果继续
但这通常不是个好主意——下一个问题会解释为什么。
Q6:那正确的微调姿势是什么?
往 train_data.csv 里追加新数据,然后从原始模型全量重训。
train_data.csv 的增长过程:
第 1 周:50 条 → python step3_finetune.py
第 2 周:80 条 → python step3_finetune.py
第 3 周:120 条 → python step3_finetune.py
...
每次都是:原始 m3e-base + 全部数据 → 微调
你可能会问:为什么不在上一次微调的基础上继续训练?那样不是更快吗?
问题在于灾难性遗忘——模型学新东西的时候,会把旧东西忘掉。就像一个学生复习第三章的时候把第一章忘了。
而全量重训等于每次考试前把所有章节从头复习一遍,最笨但最稳。
以你目前的数据量(几十到几百条),全量重训也就 5~10 分钟,完全没必要搞增量训练。
Q7:数据量大到没法全量重训了怎么办?
如果有一天你的训练数据涨到了几十万条,全量重训要好几天,那就需要"对抗遗忘"的策略了。从简单到复杂,有这么几种:
方法 1:旧数据回放(最实用)
训练新数据时,随机抽 20%~30% 的旧数据混进去一起训练。
本轮训练数据 = 全部新数据 + 随机抽样 30% 旧数据
就像每次考试前,新章节全看,旧章节挑着复习。效果好,实现简单。
方法 2:降低学习率
learning_rate = 2e-5 # 初次微调:正常步幅
learning_rate = 2e-6 # 增量训练:小步慢走,少改旧参数
步子小了,新东西学得慢一点,但旧东西也不会被踩掉太多。通常配合其他方法一起用。
方法 3:冻结底层
模型有很多层,底层是通用能力(中文语法、词语关系),顶层是任务能力(酒店相似度)。
┌─────────┐
│ 顶层 │ ← 只调这里
├─────────┤
│ 底层 │ ← 冻结,不许动
└─────────┘
冻结底层 = 保护通用知识不被破坏。
方法 4:LoRA / 适配器(大模型主流方案)
根本不改原始参数,而是在模型旁边挂一个小插件,只训练这个插件。
原始 m3e-base(冻结,永远不动)
│
├── 适配器 A(酒店行业数据训练) → 酒店场景挂载
├── 适配器 B(餐饮行业数据训练) → 餐饮场景挂载
└── 适配器 C(交通行业数据训练) → 交通场景挂载
原始模型纹丝不动,不同场景换不同插件。压根就没有遗忘问题,因为没改过原始参数。每个适配器只有几 MB,训练也快。
这也是现在大模型微调(比如 ChatGPT 的行业定制)最主流的方案。
选哪个?
| 方法 | 实现难度 | 适用场景 |
|---|---|---|
| 全量重训 | 最简单 | 数据 < 几万条(你现在的场景) |
| 旧数据回放 | 简单 | 数据太多,全量训练太慢 |
| 降低学习率 | 简单 | 配合其他方法使用 |
| 冻结底层 | 简单 | 数据少、怕过拟合 |
| LoRA / 适配器 | 中等 | 多场景切换、大模型微调 |
但说实话,对于 Embedding 模型微调这个场景,99% 的情况下"全量重训"就够了。 等你真正遇到需要 LoRA 的那一天,你的认知和工程能力也足以应对了。
写在最后
这些问题看起来琐碎——模型存在哪?怎么装依赖?重复跑会怎样?但正是这些"小问题"构成了真正的理解。
会跑脚本不等于懂模型,就像会开车不等于懂发动机。但当你知道油箱在哪、变速箱怎么工作、为什么不能一直踩油门不换挡——你就从"会开车"变成了"懂车的人"。
微调模型也是一样。知道权重存在哪、每次训练从哪个起点出发、为什么不能无脑叠加训练——这些才是让你从"跑通了脚本"变成"真正掌握了微调"的关键。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)