1. 项目概述:为什么分类头值得你花时间研究?
如果你正在学习或者使用深度学习做图像分类,你可能花了很多时间在调整网络主干(比如ResNet、EfficientNet)的结构,或者在数据增强、损失函数上绞尽脑汁。但你是否曾经停下来,仔细审视过那个位于网络最末端、看似简单的“分类头”?这个常常被我们当作“黑盒”或“标准配置”的部分,恰恰是决定模型最终性能、泛化能力乃至部署效率的关键枢纽。我见过太多项目,主干网络选得又大又强,却因为分类头设计不当,导致模型在验证集上表现平平,或者在实际部署时效率低下。
所谓“分类头”,通常指的是卷积神经网络(CNN)或视觉Transformer(ViT)等特征提取器之后的部分,负责将提取到的高维特征图或特征向量,映射到最终的类别概率分布上。它通常由全局池化层、一个或多个全连接层以及一个Softmax(或Sigmoid)激活函数构成。听起来很简单,对吧?但魔鬼藏在细节里。全局池化用平均池化还是最大池化?全连接层要不要加Dropout?加在哪里?中间层的维度设为多少合适?这些看似微小的选择,会像蝴蝶效应一样,最终影响模型的收敛速度、过拟合程度和推理速度。
在工业界实际部署模型时,分类头往往是定制化程度最高的部分。你可能需要应对类别不均衡、需要做细粒度分类、或者需要将模型部署到计算资源受限的边缘设备上。一个精心设计的分类头,能以最小的计算开销,显著提升模型在特定任务上的“战斗力”。因此,无论你是刚读完《零基础入门深度学习》的新手,还是在为“CIFAR-10图像分类PyTorch CNN”实验调参的学生,亦或是正在搭建“Halcon深度学习缺陷检测”系统的工程师,深入理解并掌握分类头的设计艺术,都是一项高回报的投资。接下来,我将结合多年的实战经验,为你拆解分类头的每一个核心组件,分享那些在论文和教科书里不会写的调参心得和避坑指南。
2. 分类头的核心组件与设计哲学
2.1 从特征到决策:分类头的标准流水线
一个典型的图像分类模型可以看作由两部分组成:特征提取器(Backbone)和分类头(Head/Classifier)。特征提取器(如一系列卷积层或Transformer块)负责从原始像素中提取具有判别性的高级语义特征。而分类头的任务,就是对这些特征进行“总结”并做出“判决”。
它的标准流水线通常遵循以下三步:
- 特征图空间信息压缩(池化):特征提取器输出的特征图通常还保留了空间维度(Height × Width)。对于分类任务,我们关心的是“整张图片里有什么”,而不是“这个东西在图片的哪个具体位置”。因此,第一步就是消除空间维度,将每个通道的二维特征图“压缩”成一个标量。这就是全局池化层的作用。
- 特征向量非线性变换与降维(全连接层):池化后,我们得到一个长的一维特征向量(长度等于通道数C)。这个向量虽然包含了所有信息,但维度可能很高(如ResNet-50的Backbone输出是2048维),且特征分布可能不适合直接用于分类。我们需要通过一个或多个全连接层(FC Layer)对其进行非线性变换和降维,将其映射到一个与任务类别数相匹配的低维空间。
- 输出概率归一化(激活函数):最后一个全连接层的输出(通常称为Logits)是未归一化的分数。我们需要通过Softmax(多分类)或Sigmoid(多标签分类)函数,将这些分数转化为各类别的概率分布,所有概率之和为1。
这个流水线看似固定,但每一步都充满了可优化的细节。设计分类头的核心哲学在于:在表征能力和计算复杂度之间,在防止过拟合和保持模型容量之间,找到针对你特定任务的最佳平衡点。
2.2 全局池化:平均池化与最大池化的抉择
全局池化是分类头的第一个操作,它决定了我们如何汇总空间信息。最常用的两种方式是全局平均池化(Global Average Pooling, GAP)和全局最大池化(Global Max Pooling, GMP)。
- 全局平均池化(GAP):对每个通道的特征图,计算所有像素值的平均值。这是目前最主流、最通用的选择,尤其是在ResNet之后被广泛采用。
- 优点:具有平移不变性,对特征的微小空间变化不敏感,更稳定。它强制特征图与类别之间建立关联,有一定的可解释性(参见CAM类方法)。从正则化角度看,它对噪声更鲁棒。
- 缺点:可能会稀释掉微小的、但具有判别性的特征响应。如果关键特征只出现在图像的一个很小区域,平均操作可能会使其被背景信息淹没。
- 全局最大池化(GMP):对每个通道的特征图,取所有像素值的最大值。
- 优点:能够捕捉最显著的特征响应,对于纹理、边缘等局部强特征非常敏感。在某些细粒度分类或目标关键部位定位的任务中可能更有效。
- 缺点:对噪声和异常值非常敏感,容易过拟合到训练集中的某些特定模式,泛化性可能稍差。
如何选择?我的经验是:默认从GAP开始。它在绝大多数通用图像分类任务(如ImageNet、CIFAR)上表现都非常稳健。只有在一些特定的场景下,才值得尝试GMP或混合策略:
- 任务驱动:如果你的任务高度依赖局部显著特征(例如,判断图片中是否有某个特定logo,而该logo可能很小),可以尝试GMP。
- 实验对比:这是一个超参数。对于重要的项目,完全可以在相同的Backbone和训练设置下,分别用GAP和GMP训练一个epoch,观察验证集精度和损失曲线的收敛情况。有时,结合两者的混合池化(如平均和最大池化结果拼接)也能带来惊喜,但这会增加后续FC层的输入维度。
实操心得:在PyTorch中,实现GAP非常简单,通常不单独定义层,而是在前向传播时直接使用
torch.mean(feature_map, dim=[2, 3])。但更常见的做法是,在构建Backbone时,其最后一个卷积模块(如ResNet的AdaptiveAvgPool2d)已经包含了全局池化操作。你需要清楚你的Backbone输出是二维特征图还是一维向量。
2.3 全连接层:结构、维度与正则化艺术
全连接层是分类头的“大脑”,负责进行最终的逻辑判断。它的设计是防止过拟合、提升性能的主战场。
1. 结构设计:单层 vs. 多层
- 单层FC(线性分类器):这是最经典的结构,即
GAP -> FC(C, num_classes)。其中C是Backbone输出特征向量的维度。这种结构参数最少,计算最快,过拟合风险低。当你的Backbone非常强大(如在ImageNet上预训练的模型),且你的下游任务与其预训练任务相似时,单层FC往往是足够且高效的。 - 多层FC(小型MLP):结构例如
GAP -> FC(C, 512) -> ReLU -> Dropout -> FC(512, num_classes)。引入了一个或多个隐藏层。- 何时使用:当你的任务与Backbone的预训练任务差异较大(领域迁移),或者任务本身非常复杂(类别极多、细粒度分类)时,Backbone提取的通用特征可能需要进一步的、针对性的非线性变换才能很好地分离。此时,一个小的隐藏层可以提供额外的模型容量。
- 风险:立即引入过拟合风险。增加的参数需要更多的数据来训练,也需要更严格的正则化。
2. 隐藏层维度选择这是一个关键的超参数。维度太大,过拟合;维度太小,瓶颈效应,信息损失。
- 经验法则:隐藏层维度通常设置为Backbone输出维度C的
1/4到1/2之间。例如,对于ResNet-50的2048维特征,隐藏层维度设为512或1024是常见的起点。 - 更科学的方法:可以将其视为一个超参数进行小范围网格搜索。但更实用的方法是,先用一个较小的维度(如256)训练,观察模型是否欠拟合(训练损失都难以下降),再逐步增大。
3. 正则化核心:Dropout与权重衰减只要使用了多层FC,就必须配套使用正则化技术。
- Dropout:这是应对全连接层过拟合的“神器”。它在前向传播时,随机将一部分神经元的输出置零。我的建议是:
- 位置:通常加在隐藏层的激活函数(如ReLU)之后、下一个线性层之前。
- 比率(p):常用值在0.3到0.5之间。对于较小的数据集或较大的模型,可以用0.5;对于较大的数据集,可以用0.3或0.4。这需要根据验证集性能调整。
- 权重衰减(Weight Decay/L2正则化):虽然通常在优化器(如AdamW)中全局设置,但它对全连接层参数的影响尤为显著。它惩罚大的权重值,鼓励模型学习更简单、更平滑的函数。对于分类头中的FC层,一个适中的权重衰减(如1e-4)是必要的。
注意事项:很多人喜欢在最后一个FC层前加Batch Normalization(BN)。对于分类头,我通常不建议这样做。因为经过GAP后,我们处理的是一个样本的全局特征向量,BN的“批统计量”意义在这里被削弱。更重要的是,在微调(Fine-tuning)预训练模型时,向分类头添加新的BN层会引入新的运行均值/方差参数,其初始状态可能与预训练模型的分布不匹配,可能导致训练初期不稳定。使用Dropout是更安全、更通用的选择。
2.4 输出层与损失函数:Softmax与交叉熵的默契搭档
分类头的最后一层是线性层,其输出维度等于类别数K。这些输出值(Logits)需要被转化为概率。
- Softmax:用于单标签多分类(一张图只属于一个类别)。它将所有Logits通过指数函数归一化,使得所有输出概率之和为1。公式为 $P(class=i) = \frac{e^{z_i}}{\sum_{j=1}^{K} e^{z_j}}$。Softmax会放大最大Logit值与其他值的差距,使得模型预测更加“自信”。
- Sigmoid:用于多标签分类(一张图可以同时属于多个类别)。它对每个Logits独立操作,将其映射到(0,1)区间,代表该类别的独立概率。每个Sigmoid输出之间互不影响。
与输出层紧密相关的是损失函数。对于Softmax输出,标准搭配是交叉熵损失(Cross-Entropy Loss)。PyTorch中的nn.CrossEntropyLoss已经将Softmax计算和交叉熵损失合并,因此在模型定义时,最后一个FC层后面不需要再显式添加nn.Softmax,直接输出Logits即可。对于Sigmoid输出,则使用nn.BCEWithLogitsLoss(同样内置了Sigmoid)。
一个关键技巧:Logits的数值稳定性。由于Softmax涉及指数运算,如果Logits值过大(正或负),可能导致数值溢出(inf)或下溢(梯度为0)。虽然现代深度学习框架(如PyTorch、TensorFlow)的CrossEntropyLoss实现已经做了数值稳定优化,但在自定义损失或需要单独处理Logits时仍需注意。确保模型初始化合理,以及训练初期不会产生爆炸性的梯度。
3. 高级分类头结构与实战策略
3.1 应对类别不平衡:偏置初始化与重加权损失
现实中的数据常常是不平衡的。例如,在缺陷检测中,“正常”的样本远多于“缺陷”样本。标准的Softmax交叉熵损失会倾向于让模型更多地预测多数类,导致对少数类的识别率极低。
1. 偏置项(Bias)初始化技巧最后一个FC层的权重和偏置的初始化很重要。一个简单有效的技巧是,根据训练集的先验分布来初始化输出层的偏置项。
- 原理:假设我们期望模型在未看到任何特征(即FC层输入为0)时,输出的概率分布与训练集的类别先验分布一致。对于Softmax,这可以通过设置偏置项 $b_i = \log(p_i)$ 来实现,其中 $p_i$ 是第i类的频率(样本数/总样本数)。
- PyTorch实现:
这个方法不能解决根本问题,但能为模型提供一个更好的起点,加速训练初期对少数类的学习。import torch.nn as nn import numpy as np # 假设 num_classes = 10, class_counts 是每个类别的样本数列表 class_counts = [1000, 200, 150, ...] # 示例 prior = np.array(class_counts) / sum(class_counts) bias_init = -np.log(1 / prior - 1) # 对于Sigmoid/逻辑回归的推导,更常用的是 log(prior) # 更常用的Softmax偏置初始化是直接 log(prior) bias_init = torch.log(torch.tensor(prior, dtype=torch.float32) + 1e-8) # 加小量防止log(0) # 获取分类头最后一层的偏置参数 model.fc.bias.data = bias_init
2. 损失函数重加权(Re-weighting)这是更主流和有效的方法。核心思想是在损失函数中,给少数类的样本更高的权重。
- 类别权重(Class Weight):PyTorch的
CrossEntropyLoss直接支持weight参数。# 计算类别权重,常见方法是逆类别频率 class_weights = 1.0 / torch.tensor(class_counts, dtype=torch.float32) class_weights = class_weights / class_weights.sum() # 可选:归一化 criterion = nn.CrossEntropyLoss(weight=class_weights) - Focal Loss:最初为密集目标检测设计,但同样适用于极度不平衡的分类。它通过降低易分类样本(高概率样本)的损失权重,让模型更关注难分类的样本(通常是少数类)。公式为 $FL(p_t) = -\alpha_t (1 - p_t)^{\gamma} \log(p_t)$,其中 $\alpha_t$ 是类别平衡因子,$\gamma$ 是调节因子(>0),$p_t$ 是模型对真实类别的预测概率。Focal Loss能有效抑制简单负样本的梯度,是处理不平衡问题的强大工具。
实战策略:我通常会先尝试使用类别权重。如果问题非常极端(如1:1000的不平衡),则会考虑结合Focal Loss。同时,在数据层面进行适度的过采样(如SMOTE的变种用于图像)或欠采样,与损失函数重加权结合使用,效果更佳。
3.2 双线性汇合与细粒度分类
对于细粒度图像分类(例如,区分不同品种的狗、不同型号的汽车),不同类别间的差异非常细微,往往体现在物体的局部特征(如鸟的喙形、车的车灯)以及这些局部特征之间的关联上。标准的GAP+FC结构可能会丢失这些关键的局部空间关系。
双线性汇合(Bilinear Pooling)是一种专门为细粒度分类设计的高效特征交互方法。其核心思想是:
- 从Backbone中提取两个特征图(可以是同一网络不同层的输出,也可以是两个不同网络分支的输出)。
- 对每个空间位置的特征向量进行外积运算,以捕获特征通道之间的成对交互关系。
- 对所有空间位置的双线性特征进行求和池化,得到一个二阶统计量(协方差矩阵式的描述子)。
- 将这个描述子展平后送入分类头。
虽然原始的双线性汇合计算和存储开销大,但后续有很多改进工作(如Compact Bilinear Pooling)降低了复杂度。它的强大之处在于,能够显式地建模不同特征通道之间的相互关系,这对于捕捉细微的、组合式的视觉模式非常有效。
对于非研究型的实战项目,直接实现双线性汇合可能稍显复杂。一个更实用的思路是:使用更高分辨率的输入图像,并避免在Backbone中使用过于激进的下采样(如将某些步幅2的卷积改为步幅1),让特征图保留更多的空间细节。然后,在分类头之前,可以尝试使用非全局的池化方式(如自适应最大池化到7x7),再接上一个小的卷积层或空间注意力模块来融合信息,最后再展平送入FC层。这相当于让模型在“决策”前,有更多的空间上下文信息可供利用。
3.3 动态分类头与增量学习
在实际应用中,我们常常面临需要增加新类别的需求。例如,一个商品识别系统最初能识别1000种商品,现在需要增加100种新品。重新训练整个模型(从1000类到1100类)成本高昂,且可能对旧类别的性能造成灾难性遗忘。
这就引出了动态分类头(Dynamic Head)或增量学习(Incremental Learning)的概念。其核心思想是,固定住已经训练好的特征提取器(Backbone)和旧类别的分类器参数,只为新类别添加新的分类权重向量。
一种简单的实现范式:
- 特征提取器固定:冻结Backbone的所有参数,将其视为一个固定的特征提取器。
- 分类头扩展:保留旧的分类头FC层(权重矩阵形状为
[feature_dim, old_class_num]),并为其并联一个新的、可训练的FC层([feature_dim, new_class_num])。在推理时,将特征同时输入这两个FC层,将输出拼接起来。 - 平衡训练:训练时,只使用新类别的数据,但损失函数需要特殊设计(如结合蒸馏损失),以鼓励模型在保持旧类别判别能力的同时学习新类别。
更高级的方法会涉及更复杂的分类头结构,如使用原型网络(Prototypical Network)为每个类别学习一个原型向量,新类别只需增加新的原型即可。或者使用解耦的余弦分类器,将权重向量的模长固定,只学习角度,这样新旧类别的分类边界更不容易冲突。
避坑指南:增量学习是一个前沿且具有挑战性的课题。简单的“冻结Backbone+训练新头”方法往往会导致模型严重偏向新类别(新旧类别不平衡)。在实践中,如果数据允许,“部分微调”通常是更稳妥的选择:即用新旧类别混合的数据,解冻Backbone的最后1-2个阶段(block)连同新的分类头一起进行微调,同时使用较小的学习率。这能在性能、遗忘和训练成本之间取得更好的平衡。
4. 分类头的训练技巧与调参实战
4.1 学习率策略:分类头与主干网络的差异化对待
在微调(Fine-tuning)预训练模型时,一个至关重要的技巧是对分类头和主干网络(Backbone)使用不同的学习率。这是因为两者所处的学习阶段不同:
- 主干网络:已经在海量数据(如ImageNet)上预训练,学到了通用的视觉特征。我们微调时,不希望剧烈改变这些特征,只希望它们能稍微适应新任务的数据分布。因此,需要较小的学习率(例如,基础学习率的1/10)。
- 分类头:通常是随机初始化的,需要从头开始学习针对新任务的决策边界。因此,它需要较大的学习率来快速收敛。
在PyTorch中,可以很方便地通过优化器的参数分组来实现:
import torch.optim as optim # 假设模型由 backbone 和 classifier 组成 backbone_params = model.backbone.parameters() classifier_params = model.classifier.parameters() # 为不同参数组设置不同的学习率 optimizer = optim.AdamW([ {'params': backbone_params, 'lr': base_lr * 0.1}, # Backbone用小学习率 {'params': classifier_params, 'lr': base_lr} # 分类头用基础学习率 ], weight_decay=1e-4)这个简单的策略能显著提升微调的效果和稳定性,是实践中的标准操作。
4.2 权重初始化与批归一化层处理
1. 分类头权重初始化对于新添加的分类头FC层,正确的初始化很重要。通常我们使用PyTorch默认的初始化(如Kaiming均匀初始化),这对于ReLU激活函数后的层是合适的。请避免使用全零初始化,这会导致对称性破坏问题,所有神经元学到相同的东西。
2. 微调时批归一化(BN)层的处理这是另一个关键细节。预训练模型中的BN层包含了在预训练数据上计算得到的运行均值(running_mean)和方差(running_var)。在微调时,有两种策略:
- 冻结BN的统计量:将BN层设置为
eval()模式(model.eval()或model.backbone.bn_layer.eval())。这样,在训练和推理时都使用预训练时保存的running_mean/var,而不更新它们。这适用于新数据与预训练数据分布非常相似,且微调数据量较小的情况,可以防止小批量数据带来的统计量噪声。 - 更新BN的统计量:让BN层保持
train()模式。此时,它会根据当前微调批次的数据更新running_mean/var。这适用于新数据分布与预训练数据有差异,或微调数据量较大的情况。
我的通用建议是:在大多数微调场景下,让BN层保持训练模式,并与其他层一起使用较小的学习率进行更新。你可以通过前面提到的参数分组,给BN层的参数也设置一个较小的学习率。同时,确保微调时的批量大小(Batch Size)不要太小(如小于8),以保证BN统计量估计的准确性。
4.3 分类头性能的监控与诊断
如何判断你的分类头设计得好不好?除了看最终的验证集准确率,训练过程中的一些信号也非常有诊断价值:
训练损失 vs. 验证损失:
- 训练损失下降很快,验证损失几乎不变或上升:这是典型的过拟合信号。说明分类头(可能结合了Backbone)过于复杂,记住了训练集的噪声。你需要加强正则化:增大Dropout比率、增强权重衰减、添加更多的数据增强、或者简化分类头结构(如减少FC层维度或层数)。
- 训练损失和验证损失都下降得很慢:这可能是欠拟合或学习率过低的信号。首先尝试增大学习率(特别是分类头的学习率)。如果无效,则考虑增加分类头的容量(如增加FC隐藏层维度),或者解冻更多Backbone层以提供更强的特征。
混淆矩阵分析: 准确率是一个宏观指标。通过绘制混淆矩阵,你可以清晰地看到模型具体在哪些类别上容易混淆。例如,如果“哈士奇”和“阿拉斯加”总是分错,这可能不是分类头的问题,而是特征提取器未能学到区分它们的细微特征(如毛色、体型)。此时,你可能需要更强大的Backbone,或者引入针对性的数据增强、注意力机制。
特征空间可视化: 使用t-SNE或UMAP等降维技术,将分类头之前的特征(即GAP后的特征向量)可视化。一个理想的分类头应该能将不同类别的特征映射到彼此分离的簇中。如果可视化结果显示各类特征混杂在一起,即使分类头再复杂也难以分开,这说明问题出在特征提取器上,你需要重新审视Backbone或数据质量。
5. 常见问题排查与避坑实录
5.1 验证集准确率震荡或不提升
- 现象:训练损失正常下降,但验证集准确率在某个值附近波动,无法进一步提升。
- 排查与解决:
- 检查学习率:这是最常见的原因。学习率可能已经太大(在最优解附近震荡)或太小(收敛缓慢)。使用学习率预热(Warmup)和余弦退火(Cosine Annealing)调度器通常能缓解这个问题。
- 检查数据泄露:确保训练集和验证集是严格分离的,没有重复的样本。特别是在使用随机增强时,要确保同一张图片的原图不会同时出现在两个集合中。
- 检查标签噪声:验证集可能存在错误标签。人工抽查一些被模型高置信度分错的样本,看看是不是标签本身就有问题。
- 简化模型:可能是模型(特别是分类头)过于复杂,对验证集产生了过拟合。尝试减少FC层的神经元数量,或增大Dropout比率。
- 调整数据增强:过强的数据增强可能会让训练任务变得“太难”,导致模型学不到有效的特征。可以暂时减弱或关闭一些增强(如CutMix、RandAugment),观察验证集精度是否回升。
5.2 模型对某些类别始终预测错误
- 现象:无论怎么训练,模型对某几个特定类别的召回率(Recall)始终为零或极低。
- 排查与解决:
- 类别不平衡:这是首要怀疑对象。检查这些类别的训练样本数量是否远少于其他类别。如果是,立即应用3.1节提到的类别不平衡处理技术。
- 特征混淆:这些类别可能在视觉上与其他类别高度相似。除了使用混淆矩阵分析,可以计算这些困难类别的特征向量中心,并与易混淆类别的特征中心计算余弦距离,确认特征是否可分。
- 数据质量问题:检查这些类别的训练图片质量是否普遍较差(模糊、遮挡、光照异常)。可能需要清洗数据或补充高质量样本。
- 修改损失函数:尝试使用Focal Loss,它通过降低易分类样本的权重,迫使模型更关注这些难以分类的少数样本。
5.3 部署时推理速度慢
- 现象:模型在服务器上测试时速度尚可,但部署到边缘设备(如手机、嵌入式设备)后推理帧率不达标。
- 排查与解决:
- 分类头参数量分析:一个
FC(2048, 1000)的层就有约200万个参数。虽然相比动辄数千万参数的Backbone不算多,但在某些极端边缘场景下仍需优化。可以考虑:- 使用全局深度可分离卷积替代FC层:将GAP后的特征向量重塑为一个小特征图,用1x1的深度可分离卷积实现通道混合和降维,最后再GAP到类别数。这能大幅减少计算量。
- 降低Backbone输出维度:如果任务简单,可以考虑使用更轻量的Backbone(如MobileNet、ShuffleNet),其输出维度(如1024或1280)比ResNet-50的2048小。
- 量化与剪枝:对训练好的模型进行后量化(Post-Training Quantization)或量化感知训练(QAT),将FP32权重转换为INT8,可以显著减少模型大小和加速推理。也可以对分类头的FC层进行稀疏化剪枝,移除不重要的连接。
- 推理引擎优化:确保使用了针对目标硬件优化的推理引擎(如TensorRT、OpenVINO、Core ML、TFLite),它们会对全连接等算子进行高效的融合与加速。
- 分类头参数量分析:一个
5.4 从零开始训练时分类头不收敛
- 现象:当不使用预训练模型,完全从随机初始化开始训练时,损失居高不下,准确率接近随机猜测。
- 排查与解决:
- 初始化问题:确认所有层的初始化是否正确。对于使用ReLU的FC层,应使用Kaiming初始化。可以打印出分类头第一层权重的均值和方差进行检查。
- 学习率过大:从零开始训练对学习率更敏感。尝试使用一个非常小的学习率(如3e-4)配合Warmup策略开始。
- 梯度消失/爆炸:如果分类头有多层FC,且没有使用残差连接或归一化层,深层梯度可能难以传播。在层间添加残差连接(如
x = x + fc_relu(x))或尝试使用Pre-LN结构的Transformer块作为分类头(在视觉任务中较少见,但可行)。 - 数据流检查:在训练循环的第一步,暂停并检查:输入图片是否已归一化?标签格式是否正确(从0开始的整数)?分类头的输出Logits范围是否合理(不应出现极大的数值)?损失函数计算是否正确?
设计一个好的分类头,就像为一把锋利的宝剑(Backbone)配上一个合适的剑柄。它需要贴合任务的手感,平衡性能与效率,并在长期的实战中保持稳定。这个过程没有一成不变的公式,需要你根据数据的特点、任务的约束和部署的环境,不断地进行实验、观察和调整。记住,分类头虽小,却是连接抽象特征与具体决策的桥梁,值得你投入与Backbone同等的关注和思考。