
deit微调时模型持续输出同一类预测(如全为0),主因常是学习率过高导致优化器跳过全局最优、陷入退化解;需结合数据预处理校验、参数解冻策略与学习率衰减等多维度协同调整。
deit微调时模型持续输出同一类预测(如全为0),主因常是学习率过高导致优化器跳过全局最优、陷入退化解;需结合数据预处理校验、参数解冻策略与学习率衰减等多维度协同调整。
在使用 DeiTForImageClassificationWithTeacher 进行医学影像(如膝骨关节炎X光片)微调时,出现“所有预测均为类别 0”且测试准确率恰好等于数据集中类别 0 的占比(如40.4% ≈ 正样本比例),这是典型的训练未收敛或优化失败信号,而非偶然过拟合。从您提供的 logits 输出可见:所有样本的第 0 类得分(≈33.97)显著高于第 1 类(≈33.28),且二者差值稳定(约0.69),说明模型并未真正学习判别性特征,而是在输出层权重上形成了强偏向。
? 根本原因分析(不止于学习率!)
虽然答案中指出“学习率过高”是关键诱因——这完全正确(DeiT 等 ViT 架构对学习率极其敏感,官方推荐微调 lr 为 5e-5 ~ 1e-4,而非您当前的 0.01),但仅调低学习率往往不够。结合您的代码与日志,还需排查以下系统性问题:
1. 输入预处理严重失配(高危!)
您使用了 transforms.Grayscale(num_output_channels=3) + ToTensor(),但 DeiT 预训练时严格依赖 ImageNet 归一化统计量(均值 [0.485, 0.456, 0.406],标准差 [0.229, 0.224, 0.225])。而 ToTensor() 仅做 0–255 → 0–1 缩放,缺失标准化步骤,导致输入分布剧烈偏移,使 ViT 的 LayerNorm 和注意力机制失效。
✅ 修复代码:
transform = transforms.Compose([
transforms.Grayscale(num_output_channels=3),
transforms.Resize((384, 384)), # DeiT-384 要求输入尺寸
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), # 关键!
])2. 模型头未适配任务 + 参数冻结策略错误
DeiTForImageClassificationWithTeacher 默认保留原始 ImageNet 的 1000 分类头,而您的任务是二分类。若直接训练全部参数(model.parameters()),底层 ViT 特征提取器会因梯度冲击而崩溃;若完全冻结又无法迁移知识。
✅ 推荐分阶段微调策略:
# Step 1: 冻结全部主干,仅训练分类头(快速验证数据流)
for param in model.deit.parameters():
param.requires_grad = False
# 替换分类头为2维输出(DeiT默认head为1000维)
model.classifier = nn.Linear(model.classifier.in_features, 2) # 注意:DeiT-384用'classifier'而非'fc'
model.classifier.to(device)
# Step 2: 使用极小学习率(1e-4)训练头层数轮
optimizer = optim.Adam(model.classifier.parameters(), lr=1e-4)
# Step 3(可选):解冻最后几层Transformer block(如最后2个encoder layer)进行精细调优
for layer in model.deit.encoder.layer[-2:]:
for param in layer.parameters():
param.requires_grad = True
optimizer = optim.Adam([
{'params': model.classifier.parameters(), 'lr': 1e-4},
{'params': model.deit.encoder.layer[-2:].parameters(), 'lr': 5e-5}
])3. 损失函数与标签对齐问题
确保 y 是 torch.long 类型(CrossEntropyLoss 要求),且取值为 {0, 1}。您的数据加载逻辑中 int(label) 正确,但建议显式转换:
# 在 __getitem__ 中添加: return image, torch.tensor(label, dtype=torch.long)
4. 学习率调度不可或缺
ViT 架构需 Warmup + Decay。直接使用固定 lr=1e-4 仍可能震荡。推荐 get_linear_schedule_with_warmup:
from transformers import get_linear_schedule_with_warmup
total_steps = len(train_loader) * num_epochs
warmup_steps = int(0.1 * total_steps) # 10% warmup
scheduler = get_linear_schedule_with_warmup(
optimizer,
num_warmup_steps=warmup_steps,
num_training_steps=total_steps
)
# 训练循环中添加:
optimizer.step()
scheduler.step() # 每步更新学习率✅ 完整修复后训练流程要点总结
| 维度 | 错误实践 | 推荐方案 |
|---|---|---|
| 学习率 |
0.01(过大100倍) |
头部:1e-4;主干解冻:5e-5
|
| 归一化 | 缺失 Normalize
|
强制使用 ImageNet 均值/方差 |
| 输入尺寸 | 未 Resize 到 384×384 |
transforms.Resize((384,384)) 必须添加 |
| 分类头 | 复用1000维原始头 | 替换为 nn.Linear(in_features, 2)
|
| 优化策略 | 固定学习率 | Warmup + Linear Decay |
| 数据验证 | 未检查 batch 中 label 分布 | 打印 torch.bincount(targets) 确认平衡 |
⚠️ 重要提醒:DeiT 的 distilled 版本包含教师蒸馏损失,在微调时应优先使用
DeiTForImageClassification(非WithTeacher),后者在 fine-tuning 场景下可能引入不必要约束。若坚持用WithTeacher,需禁用蒸馏损失或自定义 forward。
通过以上四重校准,模型将摆脱“伪收敛”,开始学习区分健康与骨关节炎X光片的实质性纹理与结构差异。记住:ViT 微调不是黑箱,而是对预训练知识的精准外科手术——尺度、归一化、学习率缺一不可。


















