1. 四种常见的微调策略
1.1 冻结(Freeze)大部分预训练的权重,仅微调解码器
思路概述
- 把预训练的骨干网络(backbone)或大型视觉 Transformer(ViT)层全部冻结,
requires_grad = False
,只对解码器(或最后若干层)进行训练。 - 对于分割类模型,解码器通常是输出掩码的那部分,比如 SAM2 中的 mask decoder;这部分能相对快速地适配新数据,而大的骨干网络则保持原有的特征提取能力。
优点
- 显存/计算开销最小,通常只需要几 GB 就够用,不用担心 OOM(Out Of Memory)。
- 训练速度快,超参数调节相对简单。
- 不容易过拟合:因为骨干网络是固定的,保留了强大的预训练特征。
缺点
- 对于与原始预训练数据差异较大的新领域,可能适配度不足,无法充分学习到新领域的细微特征;性能提升有限。
适用场景
- 数据量较小,且与原预训练数据分布差异不算特别巨大(例如:都是自然图像,但需要做更细分的目标分割)。
- 资源有限,需要尽量减少训练时的显存占用。
- 想要快速得到一个初步可用的结果,“先做个Baseline”。
1.2 部分解冻(Partial Fine-tune) ViT 的后几层 + 解码器
思路概述
- 将骨干网络(ViT)前大部分层冻结,只解冻后几层 + 解码器。也就是说,从后面几层开始,我们让
requires_grad = True
;前面的层依然False
。 - 这样既保持了预训练特征的大部分,也为模型提供了更多参数来适应你自己的数据分布。
优点
- 比“只微调解码器”更灵活,后几层会学习你的新领域特征;
- 训练开销相对全量微调更小,适合中等规模的数据集、显存资源。
- 对大多数常见场景而言,这是一个平衡计算成本和微调效果的好策略。
缺点
- 相比完全冻结,多训练了一部分参数,资源占用与过拟合风险也有所增加;
- 需要尝试“解冻多少层”、“从第几层解冻”等超参数设置。
适用场景
- 数据分布与预训练分布有一定差异,需要在特征层面做一些适配;
- 你的数据量比极小规模更大一点,或者想在“保留原模型特征”与“适度适配新领域”之间找到平衡;
- GPU 资源足以支撑训练骨干网络的一部分层。
1.3 全量微调(Full Fine-tune)
思路概述
- 让骨干网络 + 解码器所有参数都参与训练,
requires_grad = True
。 - 对预训练模型进行二次训练,几乎没有任何冻结。
优点
- 适应性最强,有机会从底层到高层特征都对你的数据进行优化,理论上能达到最好效果;
- 对于非常特殊或专业领域的数据(比如医疗影像、大幅度与自然图像不同的遥感数据),全量微调能够更全面地学习底层差异。
缺点
- 显存/计算开销很大,如果你微调的是大型 ViT(例如 SAM 的 ViT-H ~ 632M 参数),训练过程需要更多GPU/TPU资源;
- 更容易过拟合,需要足够多的训练数据做支撑;
- 调参成本高,容易在训练过程中出现不稳定,需要更仔细地选择学习率、正则化等。
适用场景
- 有足够规模的训练数据 + 强大的算力支持(大显存 GPU、集群或 TPU 等);
- 数据分布与原预训练数据差异极大,比如完全不同领域;
- 对最终性能有非常高的要求,且能够投入大量训练资源。
1.4 现代化的“参数高效微调(PEFT)”策略(LoRA、Adapters 等)
思路概述
- 近年来在大语言模型(LLM)和多模态模型中广泛使用的一类方法,用很少的额外训练参数来“微调”大模型,而无需更新原模型的全部参数。
- 典型例子有:LoRA(Low-Rank Adaption)、视觉领域的 Visual Prompt Tuning、Adapter-based methods 等。
- 核心理念是:在网络中的某些关键层或注意力模块插入少量可训练参数(如低秩矩阵、适配器),主干网络原始权重可保持冻结或仅做少量更新。
优点
- 显存占用和训练开销大幅降低,只训练新增的小规模参数;
- 对预训练权重侵入性低,方便后续共享或切换应用;
- 效果在小数据场景通常优于简单的“只微调解码器”,并且能逼近全量微调的精度。
缺点
- 在视觉Transformer上的成熟度还不及NLP,需要在模型结构中进行更多工程改动;
- LoRA / Adapter 的超参数(如秩 r、瓶颈维度等)也需要一定的调参过程;
- 若新数据与原数据分布差异极大,可能仍需更大范围的微调才够。
适用场景
- 不想大改网络,又想利用预训练权重;
- 需要灵活地在多任务、多域之间切换;
- 有一定工程能力或找到合适的PEFT库,能对SAM2的源码或中间层进行封装/插入。
2. 对比与评估
策略 | 训练成本 | 数据需求 | 过拟合风险 | 效果提升空间 | 适用场景 |
---|---|---|---|---|---|
1. 仅微调解码器 | 最低 | 小数据可行 | 最低 | 中等 | 资源少、数据量小 |
2. 部分解冻后几层 + 解码器 | 中等 | 适中 | 中等 | 较高 | 大多数常见场景,平衡计算和效果 |
3. 全量微调 | 高 | 大数据支持 | 高 | 最高 | 数据充足、算力充足、专业场景需求高 |
4. 参数高效微调 (LoRA / Adapters 等) | 低/中等 | 小到中规模 | 低/中 | 逼近全量微调 | 工程能力较强,想用更灵活的方式 |
- 如果你是刚入门,GPU 资源不算太多,优先尝试策略 1 或策略 2。
- 如果你有大规模数据,并且想要极致性能,可以尝试策略 2 或策略 3;其中策略 3(全量微调)需要你有相当大的算力与时间投入。
- 如果你对新兴技术感兴趣,并能修改模型实现,可尝试“参数高效微调(PEFT)”方案,可以在保留原模型权重的同时,用非常少的额外参数来适配新任务。
3. 示例性代码讲解
以下是一些简化的 PyTorch 伪代码,帮助你理解如何在“冻结/解冻不同部分”或“加入 LoRA”时进行操作。示例中我们用 sam_model
表示一个类似 SAM2 的对象,它可能包含 image_encoder
(ViT骨干)和 mask_decoder
(解码器)两个主要组件。请根据实际代码结构稍作调整。
注意:SAM2 源码中,可能把 ViT 进一步拆分为
trunk
,neck
或更多子模块,你可以按需定制哪些子模块需要训练。
3.1 仅微调解码器
import torch
import torch.nn as nn
from torch.optim import AdamW
# 1. 初始化模型并加载预训练权重
sam_model = SAM2Model(...) # 你的SAM2模型类
sam_model.load_state_dict(torch.load("sam2_pretrained.pth"))
# 2. 冻结骨干网络(例如 image_encoder)
for name, param in sam_model.image_encoder.named_parameters():
param.requires_grad = False
# 3. 只训练解码器
decoder_params = [p for p in sam_model.mask_decoder.parameters() if p.requires_grad]
optimizer = AdamW(decoder_params, lr=1e-4, weight_decay=1e-2)
# 4. 常规训练循环
for epoch in range(num_epochs):
for images, masks in dataloader:
preds = sam_model(images) # forward
loss = my_loss_fn(preds, masks)
optimizer.zero_grad()
loss.backward()
optimizer.step()
这样就实现了“只微调解码器”的思路。
3.2 部分解冻后几层 + 解码器
# 假设 SAM2 的骨干网络是个ViT,包含多个块blocks:
sam_model = SAM2Model(...)
sam_model.load_state_dict(torch.load("sam2_pretrained.pth"))
# 冻结所有参数
for param in sam_model.parameters():
param.requires_grad = False
# 假设我们只解冻ViT的最后2个Transformer Block + 解码器
num_blocks = len(sam_model.image_encoder.trunk.blocks)
for i in range(num_blocks - 2, num_blocks):
for param in sam_model.image_encoder.trunk.blocks[i].parameters():
param.requires_grad = True
# 还要解冻解码器
for param in sam_model.mask_decoder.parameters():
param.requires_grad = True
# 收集需要训练的参数
trainable_params = [p for p in sam_model.parameters() if p.requires_grad]
optimizer = AdamW(trainable_params, lr=3e-5, weight_decay=1e-2)
# 正常训练
for epoch in range(num_epochs):
for images, masks in dataloader:
preds = sam_model(images)
loss = my_loss_fn(preds, masks)
optimizer.zero_grad()
loss.backward()
optimizer.step()
此时,ViT 的前面大部分块都冻结,只有后 2 层 + 解码器在学习。
3.3 全量微调
sam_model = SAM2Model(...)
sam_model.load_state_dict(torch.load("sam2_pretrained.pth"))
# 全部参数都训练
for param in sam_model.parameters():
param.requires_grad = True
optimizer = AdamW(sam_model.parameters(), lr=1e-5, weight_decay=1e-2)
for epoch in range(num_epochs):
for images, masks in dataloader:
preds = sam_model(images)
loss = my_loss_fn(preds, masks)
optimizer.zero_grad()
loss.backward()
optimizer.step()
相对简单,但需要大量资源。
3.4 现代“PEFT”示例(LoRA 方式)
PEFT 在视觉 Transformer 上的支持目前还不如 NLP 那么成熟,但也已有一些研究/开源项目在做类似尝试。大体思路是:只在注意力投影层或 MLP 层中插入一个低秩映射,主干大部分权重冻结,只更新这些低秩矩阵。以下是非常简化的示例,真实项目中会用到专门的 LoRA 库或自定义模块。
# 伪代码示例:假设我们在 self_attention.W_q, W_k, W_v 等处插入LoRA
class LoRALayer(nn.Module):
def __init__(self, orig_module, r=4, alpha=1.0):
super().__init__()
self.orig_module = orig_module # 原来的线性层
self.lora_down = nn.Linear(orig_module.in_features, r, bias=False)
self.lora_up = nn.Linear(r, orig_module.out_features, bias=False)
self.scaling = alpha / r
# 冻结原有权重
for p in self.orig_module.parameters():
p.requires_grad = False
def forward(self, x):
# 原模块的输出 + LoRA的增量
return self.orig_module(x) + self.lora_up(self.lora_down(x)) * self.scaling
def apply_lora_to_vit(sam_model, r=4, alpha=1.0):
# 遍历ViT中所有注意力投影层,然后替换为LoRALayer
for name, module in sam_model.image_encoder.named_modules():
if isinstance(module, SelfAttentionProj): # 例如某个线性投影层
new_module = LoRALayer(module, r=r, alpha=alpha)
set_module(sam_model.image_encoder, name, new_module) # 伪函数,将module替换成new_module
sam_model = SAM2Model(...)
sam_model.load_state_dict(torch.load("sam2_pretrained.pth"))
apply_lora_to_vit(sam_model, r=4, alpha=1.0)
# 除LoRA层外,原ViT参数都可设置 requires_grad=False
for name, param in sam_model.named_parameters():
if "lora" not in name:
param.requires_grad = False
# 只训练LoRA层
trainable_params = [p for p in sam_model.parameters() if p.requires_grad]
optimizer = AdamW(trainable_params, lr=1e-4)
for epoch in range(num_epochs):
for images, masks in dataloader:
preds = sam_model(images)
loss = my_loss_fn(preds, masks)
optimizer.zero_grad()
loss.backward()
optimizer.step()
如上示例里,只增加很少量的可训练参数(通过LoRALayer)来让模型学到新领域特征,既保持原模型权重不变又能适度适配新任务。
实际实现:
- 需要了解 SAM2 中的注意力代码结构;
- 需要自己写一个注入LoRA的函数,把SelfAttention里的q/k/v投影层替换;
- 也可以用已有的开源PEFT库(部分对视觉兼容不完美),需要视具体情况做一些改动。
4. 结论与建议
-
入门推荐
- 如果你是刚上手 SAM2,GPU 资源有限:先尝试 冻结大部分参数,仅微调解码器。这是最快捷的入门办法。
- 如果发现效果不理想、或你的数据和官方预训练差异较大,可以尝试 部分解冻后几层 来获取更多提升。
-
更高追求
- 若你有充足的数据和较大算力,可尝试 全量微调,能够获得更好表现。
- 如果想跟进新潮的研究,并且对工程有一定掌控力,可以研究 LoRA/Adapter 等“参数高效微调(PEFT)”。它在小数据场景下能够带来非常好的性能/开销平衡。
-
实践提示
- 数据增广:在训练分割模型时,常用的增广方法包括随机裁剪、随机翻转、随机缩放、颜色抖动等;Albumentations 或 TorchVision transforms 都是不错的选择。
- 评估指标:在验证集上监控
mIoU
、Dice
、Precision/Recall
等,观察曲线变化,防止过拟合。 - 超参数:不同微调策略下,学习率可从
1e-5 ~ 3e-5
开始尝试(仅微调解码器时可更大一点);batch size 视显存大小而定。 - 早停(Early Stopping):若验证指标长时间不提升或开始下降,可以停止训练并回滚到最优检查点。
- 多卡并行:如果有多张GPU,可以使用分布式训练(DDP)或混合精度(AMP),加快训练并节省显存。
总结
总结来说,在微调 SAM2 时,你可以根据自身数据量、硬件资源和目标效果,选择最合适的策略:
- 冻结大部分参数,只微调解码器 → 超快上手,适合小规模数据;
- 部分解冻后几层 → 兼顾性能与效率,是常用平衡点;
- 全量微调 → 效果最佳,但对数据和算力要求最高;
- PEFT (LoRA/Adapters) → 现代化低开销微调,与SAM2结合仍在快速发展中。
希望这些思路和示例代码能帮你快速搭建起 SAM2 的微调实验 pipeline。不要忘记在训练过程中注重数据增广、验证集监控、超参数调优等常规环节,才能稳步获得理想的分割效果。祝你实验顺利!
附注:不同研究社区可能对 SAM2 源码有二次封装,需根据实际项目结构稍作调整。以上示例主要为思路参考,细节实现请结合你使用的版本或框架。