开头我先说个真实经历。刚接触 NLP 那会儿我以为迁移学习就是把别人训练好的模型拿过来跑一下完事。结果第一次给一个法律文本分类任务做微调我拿了一个里边的通用分类模型直接上全量微调训练集只有 800 条数据。三天后模型在验证集上表现特别理想F1 值很漂亮我一激动就部署上线了。上线第一周就出现了大量误判后来查明原因——训练分布和线上真实分布差异太大而我又把模型的底层通用特征全给覆盖掉了。说白了那不是微调那是让一个见过世面的模型去死记硬背一套小范围的题目。后来我仔细把 Transformers 库里的工具链梳理了一遍踩完一圈坑之后才算是真正理解了微调该怎么做数据要准备成什么样全量、Freeze、LoRA 三种方式各自的边界在哪里。这篇文章我想把这些经验系统地讲清楚适合那些已经跑通过 Transformers 基础流程、但还没有把微调真正落到项目里的人。1. 为什么我以为懂了迁移学习、却还是把微调做成灾难1.1 一场从零训练带来的教训当年那个法律文本分类项目我用的是某开源预训练模型任务是把一段纠纷描述归类到案由。因为业务字段和公开语料差异大我图省事没有针对领域做增量预训练直接在全部层上做了微调。这在操作上看起来是最简单、最充分的方案但问题恰恰出在这里。预训练模型的核心资产是什么不是它背下来的那些百科知识而是它底层学到的那种通用语言表达能力——句法结构、上下文关系、同义改写的能力。这些能力存在于网络浅层和中间层。当我用 800 条法律语料做全量微调时反向传播的梯度会把底层参数也一并推着走。如果学习率稍微大一点或者训练步数比较多模型就会遗忘掉原本那种通用的语言理解只记得这套小语料里的局部规律。结果就是在测试集上因为测试集和训练集来自同一个数据源分布相近表现自然非常好但线上真实输入五花八门一旦句式超出训练分布模型立刻崩。这不是偶然是迁移学习中一个很核心的风险——灾难性遗忘。1.2 迁移学习在 Transformers 下的本质三笔可继承的资产我们常说预训练模型是别人训练好的成果其实它在参数上提供了三笔可继承的资产词表与嵌入层它包含了对词汇、子词、标点、大小写等的基础编码能力。对于中文模型这个嵌入层通常能较好地覆盖常用字词但专业领域名词可能没覆盖到。中间层的通用语义表示这里面存储的是上下文理解、指代消解、短语组合等信息。这些能力对任何下游任务都有用。输出层的任务能力这一点常常被忽略。不同预训练模型在预训练阶段的任务头是不同的比如 BERT 用的是掩码语言模型头生成模型用的是语言模型头。我们在微调时通常会把输出层换掉换成自己的分类头或生成头。理解这三笔资产之后迁移学习怎么落地这个问题就细化为如何在下游任务训练时尽量保住前两笔资产同时让模型学到新任务的特征。这也是为什么 Freeze 和 LoRA 这类方案能在实践中获得不错效果——它们没有让底层通用能力被冲掉。1.3 什么场景该用微调、什么场景不该用很多新手会把微调当成默认选择但实际操作中有些任务根本不需要动模型权重。拿文本分类举例如果训练数据只有几百条或者任务类别相对固定直接用 Embedding 加特征工程或者做上下文学习往往更稳定。这里的关键判断标准是数据量要覆盖下游任务的多样性通常每条类别至少要有几百到上千条样本。如果数据量不够微调反而容易过拟合灾难性遗忘的概率会非常高。反过来如果任务涉及特定领域术语、特定写作风格、复杂推理链或者你希望模型输出有固定的格式要求那么微调几乎不可避免。例如让模型输出结构化的 JSON 数据单靠提示词能稳定一套格式但遇到复杂嵌套结构就会失控微调之后模型会更容易走指令路径输出格式的稳定性会有很大改善。2. 环境版本这一关Transformers、PyTorch、CUDA、GPU 到底怎么配2.1 版本兼容问题的现实面貌很多人刚开始做微调第一步就卡在环境上。搜索哪个版本的 PyTorch 和 CUDA 支持 Transformers 3.4.0这类问题说明大家经常在网上搜这种组合。这类问题的核心其实是误解Transformers 库本身并不直接依赖 CUDA 版本它的依赖主要在 PyTorch 侧。PyTorch 在编译时已经绑定了自己支持的 CUDA 版本Transformers 只是在调用 PyTorch 的 API。所以问题的关键不是Transformers 跟 CUDA 怎么配而是哪个 PyTorch 版本和你机器的驱动、CUDA 版本匹配。Transformers 3.4.0 是 2020 年底的版本当时对应 PyTorch 1.6 到 1.7 那一代。但说实话我不建议再用这么老的版本去搞新项目。老的 Transformers 对现在主流的模型结构支持有限比如后来的 LLaMA、Qwen 等新架构老版本没法直接加载。除非你有必须维护老代码的理由否则尽量用新版本。2.2 我最终采用的稳定环境组合以我目前常用的一套环境为例跑过多种中小规模模型的微调包括 BERT 系列、T5 系列、Qwen 系列稳定性都还可以组件版本说明Python3.10兼容性好依赖冲突少PyTorch2.1.2cu118cu118 指 CUDA 11.8绝大多数显卡驱动都支持Transformers4.38 或更新支持 LLaMA、Qwen 等一系列新结构Datasets2.16数据加载和预处理Peft0.7做 LoRA 微调Accelerate0.26多卡训练和混合精度安装方式我一般这么写pip install torch2.1.2 --index-url https://download.pytorch.org/whl/cu118 pip install transformers datasets peft accelerate安装之后先用一段简单的代码验证环境是否可用import torch import transformers print(PyTorch:, torch.__version__) print(CUDA available:, torch.cuda.is_available()) print(CUDA version:, torch.version.cuda) print(Transformers:, transformers.__version__)如果torch.cuda.is_available()返回 False大概率是 PyTorch 的 CUDA 版本和显卡驱动不匹配或者装成了 CPU 版。这个排查很简单但也最常见。2.3 没有高端 GPU 时的降级思路我见过不少同学一上来就想微调 7B、13B 的模型然后发现自己的 8G 显存根本装不下。这时候通常有三条路用更小的模型比如用 Qwen 0.5B 或 1.8B、用 BERT base约 1.1 亿参数在单卡上完全可以跑全量微调。量化技术把模型量化成 8bit 或 4bit显存占用能压缩一半以上。Transformers 原生支持 bitsandbytes 加载。LoRA 等参数高效微调只训练很小一部分参数可以把 7B 模型压在一张 12G 左右的卡上。如果你要给学生做微调演示或者算力比较有限我强烈建议先用小的模型把流程跑通再用大模型。道理很简单流程逻辑在任意规模上是相通的先用小模型发现问题再上规模成本低很多。3. 数据准备与训练闭环我把微调拆成六个可复用的阶段微调能不能成功数据准备占了至少一半的比重。我在项目里慢慢把微调流程沉淀成了六个阶段在每个项目里重复使用效率和稳定性提升都很明显。3.1 第一阶段数据清洗——决定微调上限很多开源数据集直接拿过来用质量参差不齐。常见的问题有文本里有 HTML 标签残留、乱码、全半角不统一标签分布极端不平衡某一类占了 90%训练集和验证集存在数据泄漏比如同一文本的重复片段同时出现在训练和验证中我一般会写一个清洗函数统一处理这些情况。以文本分类为例核心字段包括text和labelimport re def clean_text(text): text re.sub(r[^], , text) text re.sub(r\s, , text) text text.strip() return text def deduplicate(dataset, keytext): seen set() unique_indices [] for i, sample in enumerate(dataset): text sample[key] if text not in seen: seen.add(text) unique_indices.append(i) return dataset.select(unique_indices)清洗后检查类别分布。如果发现极端不平衡需要用欠采样、过采样或者加权损失来缓解。这方面如果处理不到位模型很容易变成预测结果几乎全部集中在高频类别。3.2 第二阶段训练集/验证集/测试集划分——防止数据泄漏划分数据的时候有一个很容易忽略的细节如果有同一来源的多条记录要按来源分组划分而不是直接随机切分。比如做新闻分类同一篇文章的前半段和后半段如果被分到训练集和验证集验证结果会虚高。真实业务场景中类似情况经常出现在同一个用户的多个行为记录里。我的习惯是先用train_test_split按一定比例划分然后计算训练集和验证集的文本重叠度。如果有重叠说明泄漏了。一套最朴素但有效的检查逻辑train_set set(train_data[text]) valid_set set(valid_data[text]) overlap train_set valid_set print(fOverlap samples: {len(overlap)})如果这个值不是 0就得重新做分组划分。3.3 第三阶段Tokenization 与处理策略这是 Transformers 使用中最常出问题的一环。文本进入模型之前要先经过 Tokenizer 变成 token id 序列。常见坑包括忘记设置truncationTrue导致超长文本直接报错max_length随意设置太短的截断会导致语义缺失太长的会占显存对生成类任务没正确处理 label导致损失计算在 padding 位置也参与了分类任务的标准做法如下from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(bert-base-chinese) def tokenize_function(examples): return tokenizer( examples[text], paddingmax_length, truncationTrue, max_length128, ) tokenized_datasets datasets.map(tokenize_function, batchedTrue)对于生成任务需要把输入和输出分别处理。一般习惯是把输出文本也做 tokenize并设置labels同时把 padding 的部分设成-100这样损失计算时会忽略掉 padding 位置def tokenize_for_generation(examples): model_inputs tokenizer(examples[input_text], max_length512, truncationTrue) labels tokenizer(examples[output_text], max_length512, truncationTrue) model_inputs[labels] [ [(l if l ! tokenizer.pad_token_id else -100) for l in label] for label in labels[input_ids] ] return model_inputs这个细节特别重要。如果不设成-100生成模型的损失函数会把[PAD]位置也算进去模型会努力去预测空白导致生成质量明显下降。3.4 第四阶段训练参数与 TrainingArgumentsTransformers 库把训练封装在Trainer里。但 Trainer 的默认参数并不能直接用几个关键参数值得认真设置from transformers import TrainingArguments training_args TrainingArguments( output_dir./results, num_train_epochs3, per_device_train_batch_size16, per_device_eval_batch_size32, evaluation_strategyepoch, save_strategyepoch, logging_dir./logs, logging_steps50, learning_rate2e-5, warmup_ratio0.1, weight_decay0.01, fp16True, load_best_model_at_endTrue, metric_for_best_modeleval_loss, report_tonone, )其中几个参数背后的考虑learning_rate2e-5微调的标准学习率范围大约是 1e-5 到 5e-5。学习率过大灾难性遗忘会很严重。warmup_ratio0.1前 10% 的步数让学习率从 0 线性上升到目标值避免刚开始训练时大步冲击预训练参数。fp16True混合精度能省一半显存训练速度也更快。但对 CPU 环境无效而且某些算子不支持需要实测。load_best_model_at_endTrue训练结束自动加载验证集上表现最好的模型而不是最后一个 checkpoint。3.5 第五阶段评估策略——不能只盯着交叉熵损失很多教程里只在训练结束后看一眼损失这是不够的。拿分类任务来说类别不平衡时损失下降得很好但准确率可能很一般。要加上真实业务关心的指标import numpy as np from datasets import load_metric accuracy_metric load_metric(accuracy) def compute_metrics(eval_pred): logits, labels eval_pred predictions np.argmax(logits, axis-1) return accuracy_metric.compute(predictionspredictions, referenceslabels)然后把这个函数传给 Trainerfrom transformers import Trainer trainer Trainer( modelmodel, argstraining_args, train_datasettokenized_datasets[train], eval_datasettokenized_datasets[validation], tokenizertokenizer, compute_metricscompute_metrics, ) trainer.train()对于多分类建议同时看每个类别的精确率、召回率、F1尤其是低频类别。只用宏观准确率很容易掩盖低频类别几乎不可用的事实。3.6 第六阶段推理验证——在真实场景中测试训练结束后很多人直接调model.predict就完事了。但我在实战中反复验证发现一个很关键的问题训练时的输入格式和推理时的输入格式如果不一致模型效果会明显下降。举个例子训练时分类模型的输入是[CLS] 文本内容 [SEP]推理时如果传入的文本没经过同一条 pipeline 清洗比如还带着 HTML 标签效果自然下降。更常见的是生成任务训练时指定了特殊前缀模板推理时也要用同样的模板否则模型就像是到了一个陌生环境行为完全不确定。所以我强烈建议单独写一个推理模块这个模块必须复用训练阶段的清洗、tokenize、模板构造逻辑而不是在 notebook 里随手调。推理代码的核心逻辑大致是这样def predict_single(text): cleaned clean_text(text) inputs tokenizer(cleaned, return_tensorspt, truncationTrue, max_length128) inputs {k: v.to(model.device) for k, v in inputs.items()} with torch.no_grad(): outputs model(**inputs) pred_id torch.argmax(outputs.logits, dim-1).item() return id_to_label[pred_id]4. 三种微调方案的实测对比全量微调、Freeze 微调、LoRA 微调这一节是很多人真正关心的部分。我分别跑过全量微调、Freeze 微调和 LoRA 微调在不同任务上的表现差异很值得展开讲。4.1 全量微调数据充足时的正统方案全量微调指的是让预训练模型的每一层参数都参与训练。它的优点是模型的适配能力最强理论上限最高缺点是训练成本高且在小数据集上容易过拟合。前文那个法律文本项目翻车本质上就是全量微调在小规模数据集、较大学习率下的灾难性遗忘。适用条件总结下来有三条数据规模较大我个人的经验惯例是每类至少 1000 条以上下游任务和预训练任务有一定差距需要大幅度调整底层特征算力充足可以负担完整的反向传播全量微调的代码其实就是把模型加载进来用 Trainer 直接训练。如果想让模型更快收敛可以把num_train_epochs调低一般 3 到 5 轮即可。我在 BERT 分类任务上的经验是超过 5 轮后验证集表现通常会开始变差早停很有必要。4.2 Freeze 微调锁住底层只训上层Freeze 微调的核心思路是冻结预训练模型的大部分层参数只训练靠近输出端的少数层和分类头。这背后的假设是底层和中层的通用语义表示已经足够好不需要针对下游任务做大幅调整。实现上可以用参数名来控制哪些层不参与训练from transformers import AutoModelForSequenceClassification model AutoModelForSequenceClassification.from_pretrained( bert-base-chinese, num_labels10, ) for name, param in model.named_parameters(): if not (classifier in name or pooler in name): param.requires_grad False只训练分类头和池化层不仅训练速度快显存占用小而且在数据量较少时过拟合风险更低。我做过一个对比实验在 500 条训练数据、5 分类任务上全量微调验证集 F1 只有 0.71而 Freeze 微调达到了 0.79。原因就在于底层参数没有因小数据而偏移模型保留了足够的泛化能力。Freeze 微调的局限也很明显如果任务需要模型学习全新的领域知识比如识别专业术语之间的复杂关系只训练上层就不太够。这时候可以在冻结层之外再插入一些可训练的变换层给模型更多适应空间。4.3 LoRA 微调参数效率背后的原理LoRA 是这几年参数高效微调里最实用的方案之一。它的思路非常巧妙不直接更新预训练权重 W而是把权重的更新量拆解成两个低秩矩阵的乘积。假设权重矩阵的维度是 d×d训练时学习两个矩阵 Ad×r和 Br×dr 比 d 小得多比如 8 或 16。这样训练时只需要更新 A、B以及可能加的一些额外层参数量瞬间下降几个数量级。在代码里LoRA 的落地一般用 PEFT 库from transformers import AutoModelForCausalLM from peft import LoraConfig, get_peft_model, TaskType model AutoModelForCausalLM.from_pretrained( Qwen/Qwen2-1.5B, torch_dtypetorch.float16, device_mapauto, ) lora_config LoraConfig( task_typeTaskType.CAUSAL_LM, r8, lora_alpha32, lora_dropout0.1, target_modules[q_proj, k_proj, v_proj, o_proj], ) model get_peft_model(model, lora_config) model.print_trainable_parameters()print_trainable_parameters()会输出可训练参数量。如果模型总共 15 亿参数LoRA 通常只训练其中的几百万到一两千万占比大约在 1% 左右非常夸张。LoRA 有两点明显的实践价值显存占用低因为大部分参数不需要保存梯度优化器状态所以能把更大模型塞进消费级显卡。多个任务切换方便每个任务只需要存一个很小的 LoRA 权重文件切换时动态加载即可不需要存多份完整模型。LoRA 的潜在风险在于秩 r 的选择。r 太小表达能力受限r 太大训练参数量上升又失去了参数效率优势。我在文本分类上常用 r8在生成式任务上常用 r16 到 32。具体还得看数据规模和任务复杂度。4.4 三种方案怎么选一张表说清关键差异方案可训练参数占比训练显存小数据表现大数据表现适用场景全量微调100%最高风险高容易过拟合理论上限最高数据充足、算力充足Freeze 微调5% 到 20%低稳定不容易忘上限偏低数据少、任务与预训练差距小LoRA 微调0.1% 到 2%低比较稳定接近全量微调大模型、多任务、显存受限你在实际选型时可以按这个思路来判断先看数据量再看显存最后看任务难度。如果数据几百条优先试 Freeze 或 LoRA如果数据几万条以上且任务比较复杂全量微调值得一试但也要搭配早停策略和合理的正则化。5. 微调过程中常见的坑完整排查链路与避雷指南这一节我把平时在微调实战中遇到的、具有代表性的问题整理成三条完整的排查链路每条都是我真实踩过的。5.1 坑一Embedding 层和新增 Token 导致的维度错误有一个很常见的操作为了让模型认识领域新词比如诉请案由货款这些词我们会往 Tokenizer 里添加新 token。这本身没错但很多人添加完 token 忘了调整模型的 embedding 层大小于是加载模型时报错RuntimeError: size mismatch for bert.embeddings.word_embeddings.weight: copying a param with shape torch.Size([21128, 768]) from checkpoint, the shape in current model is torch.Size([21130, 768]).这个报错的意思很明确新词表大小是 21130但模型的 embedding 权重还是 21128。解决方案是加载模型时设置model.resize_token_embeddings(len(tokenizer))。我遇到过更隐蔽的情况报错不发生在模型加载阶段而是发生在训练开始后因为resize_token_embeddings这个操作之后新加的 embedding 权重是随机初始化的模型还没学会它们的语义直接开始训练会导致这些 token 的梯度特别大震荡干扰其他参数。我的做法是在数据里专门构造一些包含这些新词的标准句子让模型先适应一遍。换句话说新增 token 不是加到词表就完了你还要保证它们有充足的训练语料。5.2 坑二学习率设置不当导致的灾难性遗忘这个坑的表现形式很典型训练到第二个 epoch 的时候训练损失在下降验证损失却在回升或者验证集 F1 波动非常剧烈。很多人第一反应是过拟合了于是调小 dropout或者加正则。但有一个经常被忽略的根因学习率太大模型正在快速覆盖预训练权重里的通用特征。排查链路我建议这样走先看第一个 epoch 结束时的验证集指标如果已经明显下降怀疑学习率问题。把学习率降低一个数量级比如从 2e-5 降到 2e-6重新训练。观察训练损失的曲线是否变得更平滑验证集是否更早进入稳定区间。如果降低学习率后仍然快速遗忘考虑换 Freeze 或 LoRA限制可训练参数范围。还有一个细节warmup 比例。warmup 太短模型在最初的几个 step 里用较大的学习率直接冲击底层参数也是一种隐蔽的灾难性遗忘源。我常用的 warmup 比例是 10%对于小数据集甚至会调高到 20%。5.3 坑三跑着跑着显存溢出不一定是显存不够OOM 是微调过程中最常见的报错。很多人第一反应是调小 batch size但有些 OOM 不是因为显存本身不够而是因为代码实现存在隐性显存浪费。比如在 GPU 上使用 Python 原生的list存放 tensor忘记用torch.stack合并导致显存碎片化。在no_grad()之外的地方执行推理累积了计算图。混合精度fp16的参数设置不当导致某些层仍然用了 fp32。排查步骤如下调小 batch size看能否跑通。如果调小之后仍然 OOM说明问题很可能不在 batch size。检查是否在没有torch.no_grad()的推理流程中累积了梯度图。使用torch.cuda.memory_summary()观察显存分配情况确认是否存在碎片化。检查是否有model.cuda()和inputs.cuda()在不同设备上不一致的情况。还有一个本地容易踩的坑在数据加载的map函数里用了并行加载但num_proc设置过大导致内存溢出。如果你用的是 16G 内存的机器num_proc4就足够了别盲目开很大。5.4 坑四序列长度设置对性能的隐性影响有些任务看起来很简单比如做短文本分类但如果训练数据的长度差异很大统一设置一个过大的max_length会让批量训练时的 padding 比例过高大量显存被浪费在无意义的 padding token 上。我常用的处理方式是先统计训练集长度分布。如果 95% 的样本长度不超过 100那么max_length设 128 就足够没必要设 512。如果训练文本长度跨度很大可以按长度分组做动态 padding这比统一填充到最大长度更省显存。Transformers 的DataCollatorWithPadding就是干这个的from transformers import DataCollatorWithPadding data_collator DataCollatorWithPadding( tokenizertokenizer, paddingTrue, return_tensorspt, )把它传给 Trainer 之后每个 batch 内的短样本只填充到该 batch 内的最大长度而不是全局最长效率提升立竿见影。6. 从实验室到业务侧模型导出、推理优化与后续迭代训练完成只是第一步真正落地才是考验。6.1 模型保存与导出别只存权重要存完整组件很多教程训练完直接model.save_pretrained(./model)就结束了。但这样的保存方式别人或者你的部署服务在加载时需要另外再加载 Tokenizer。如果在微调过程中新增了 token部署端没同步更新 Tokenizer就会出大问题。我的做法是模型和 Tokenizer 一起保存model.save_pretrained(./final_model) tokenizer.save_pretrained(./final_model)加载时也一起加载from transformers import AutoModelForSequenceClassification, AutoTokenizer model AutoModelForSequenceClassification.from_pretrained(./final_model) tokenizer AutoTokenizer.from_pretrained(./final_model)如果是 LoRA 微调保存的时候要额外注意。PEFT 模型保存时会生成一个 adapter 目录部署时需要先加载基座模型再加载 adapter 权重。6.2 推理优化ONNX 与量化把模型部署到服务端推理延迟是一项硬指标。Transformers 的直接推理虽然方便但对于高并发场景来说有点慢。我常用的优化路径是先把模型导出为 ONNX 格式再用 ONNX Runtime 做推理。特别是 batch size 固定时ONNX 的加速效果非常明显。python -m transformers.onnx --model./final_model onnx/导出的 ONNX 模型可以在 CPU 上获得显著加速这是因为 ONNX Runtime 针对 CPU 做了算子融合和计算图优化。如果你追求更低延迟可以进一步做 INT8 量化但量化后精度会有一定下降需要重新在业务评测集上验证。6.3 模型更新与迭代微调不是一次性工作业务上线之后模型会碰到新的问题。合理做法是建立数据回流机制定期收集线上预测置信度低、用户纠错的样本进入下一轮微调。这里有一个重要原则不要在新一轮微调时只用新增样本要把历史核心样本也带上否则模型会在新样本上过拟合又出现灾难性遗忘。我在实际运营中会把数据集分为三块历史核心样本、新增难例、验证集。每轮迭代时历史核心样本抽一部分新增难例全量加入重新做一次微调。这种持续学习的思路虽然朴素但确实有效。你在做生产环境的模型迭代时也可以尝试这个模式。6.4 给学生演示或教学场景下的特殊建议前面看到热搜里有给学生演示大模型微调除了 deepseek-r1 还有其他模型吗其实选择还挺多。如果只是演示 LoRA 微调的流程和效果我推荐用小型生成模型比如 Qwen2-0.5B 或 1.5B。显存占用小训练速度快输出效果直观。如果学生基础比较弱甚至可以先拿 GPT-2 或中文版的小模型跑通完整流程再换更大的模型看效果变化。教学演示的核心是流程完整性数据准备、tokenize、配置、训练、评估、推理。把这套链路走通比追求一个很大的模型更有教育价值。至于微调和记忆的区别这类疑问也很值得展开。微调改变的是模型的参数它让模型学会某种能力而不是简单地记住几条事实。当你问模型某个任务时它是通过参数里的统计规律来生成行为。相比之下检索式外挂数据库更像是记忆系统把答案查出来放在提示词里。两者不是互斥关系实际项目里经常搭配着用把高频静态的知识做成外挂检索把行为风格和格式要求做成微调。这样既节省训练成本又能在边际场景里保持灵活性。写在最后的个人体会做了一段时间的微调项目之后最大的感触是真正决定模型能不能用的不是模型本身而是你对任务的理解、对数据的处理、对训练过程的把控。同样的预训练模型同样的数据量不同人跑出来的效果可以差很远。差别不在代码而在细节。我建议所有刚上手微调的人先别急着追求超大模型和花哨的框架老老实实把一套小模型、少数据、全流程跑通记录下每一步的效果变化。等你亲手经历过一次为什么改个学习率效果差这么多为什么加了几个新词反而崩了之后你对迁移学习的理解会上升一个层次。我踩过的坑你大概率也会遇到希望这篇文章能帮你少走几段弯路。
