1. 项目概述与核心需求
图像分类,一听这四个字,很多朋友第一反应就是“用 Python 调个库,把图片扔进去,模型告诉我它是猫还是狗”,但真到了自己手头要做这个任务的时候,才发现事情远没有这么简单。
我最近正好在做一个图像分类任务,需求听起来也很朴实:给我一堆图片,大概几万张,我需要让模型告诉我是哪一类东西。真正落地的时候,涉及到的环节却比想象中多得多——数据怎么存、标签怎么标、模型选什么、训练起来卡不卡、精度怎么评估,每个环节都能让人踩出坑来。这篇文章,我就把自己从零到一做完一个图像分类任务的全过程捋一遍,把那些文档里不会写、教程里很少提的细节,一并给你说清楚。
要说这个任务到底解决了什么问题,其实很直白:替代人工做图片类别的判定,把重复性极高的“看图认物”工作自动化,并且保证判定标准和一致性——人看久了会眼花,模型不会。适合谁看呢?适合那些跟我一样,已经大概知道“卷积神经网络”这个词,手里有一批图片数据,正打算自己动手做分类任务,但还没想清楚从哪一步开始的朋友。
整个过程我大致分了四块:数据准备和处理、模型怎么选、训练过程怎么调、出问题了怎么排查。这四块对应着实际项目的四个阶段,每块都有我不填坑就过不去的真实经历。
在开始之前,先交代一下我的任务背景,方便你对照着看:我手头的数据是某行业场景下的街拍照片,目标是把图片分成五类,包括建筑物、行人、车辆、自然景观、标识牌之类的。原始数据是各个渠道收集来的,画质参差不齐,尺寸也不统一,有的带着拍摄时间水印,有的已经被人为裁剪过了。
2. 数据处理与预处理的完整流程
2.1 数据收集方式与目录结构规划
很多人以为图像分类任务的第一步是搭模型,我一开始也这么想,后来发现大错特错。拿到数据后的第一件大事,是把数据结构理顺。
原始数据到达我手上时的状态非常混乱,几百张图片散落在一个文件夹里,文件名带着不同的前缀后缀,有的叫“IMG_001.jpg”,有的叫“scene_building_2024_01.jpg”,有的干脆就是一串数字。最麻烦的是没有标签文件,我完全不知道每张图是什么类别。
这里我给新手朋友一个特别重要的建议:动手写任何代码之前,先把数据的目录结构确定下来。我最终采用的结构是这样的:
dataset/ ├── train/ │ ├── building/ │ ├── person/ │ ├── vehicle/ │ ├── nature/ │ └── sign/ ├── val/ │ ├── building/ │ ├── person/ │ ├── vehicle/ │ ├── nature/ │ └── sign/ └── test/ ├── building/ ├── person/ ├── vehicle/ ├── nature/ └── sign/每个类别一个文件夹,文件夹名就是类别名。这种结构的好处太多了,最大的好处是不需要额外维护一张“文件名→标签”的映射表,PyTorch 的ImageFolder、TensorFlow 的image_dataset_from_directory都直接支持这种结构。标签就是文件夹名,不会发生“文件名写错了但标签文件里还是对的”这种张冠李戴的问题。
当然,如果你的数据类别非常多,或者一个图片可能属于多个类别,目录结构就未必合适了——那种情况你需要把标签放在 CSV 或 JSON 文件里。但对于普通的单标签图像分类任务,目录结构就是最省心的方案。
2.2 人工清洗与标注的实操经验
数据目录建好后,接下来面临最大的体力活——整理和打标签。我的数据来源有几个渠道,筛选规则很重要,处理不好会直接影响最终效果。
我踩的第一个坑是重复图片问题。几百张原始照片里,同一场景不同时间拍的图片占了相当一部分,这些图片内容高度相似,如果全放进训练集,模型会对这个场景严重过拟合,导致在真实场景下泛化能力极差。我用了感知哈希算法做了去重,原理很简单:把图片缩小成一个很小的尺寸(比如 8x8),计算每个像素的灰度平均值,然后与均值比较得到一串二进制序列,两张图片的哈希序列相似度超过阈值就判定为重复。
去重之后是过滤低质量图片。模糊的、过暗的、严重过曝的、分辨率低于某个阈值的都要剔除。这里有一个细节:测试集和训练集的数据清洗标准要一致,不能训练集里全是高清图,测试集里全是模糊图,那样评估出来的指标没有意义,模型上线后遇到模糊输入会直接崩。
标注策略上,我采用的是“先自动后人工”的方式。先用一个现成的公开模型跑一遍,给图片打上预标签,然后我人工过一遍,只改那些明显标错的。
这种做法的效率比完全人工标注高太多了。5 个类别、几千张图片,人工逐一标注可能要一个礼拜,预标注后人工复核一天半就能搞定。当然前提是预标注模型的准确率不能太低,我的经验是准确率低于 70% 的话,人工改动量太大,不如直接从零人工标。
2.3 数据增强策略与注意事项
原始数据经过清洗和标注后,总数量并不算多,对于深度学习模型来说远远不够。这时候数据增强(Data Augmentation)就是性价比最高的手段。
我采用的增强策略分两部分。第一部分是基础几何变换,包括随机水平翻转(注意垂直翻转要慎用,除非你的任务场景确实允许倒置的图片)、随机旋转(角度限制在正负 15 度以内,旋转过大会产生黑边或严重形变)、随机裁剪和缩放(模拟拍摄距离变化)。第二部分是色彩扰动,包括亮度、对比度、饱和度的随机调整,用来模拟不同光线条件下的拍摄结果。
from torchvision import transforms train_transforms = transforms.Compose([ transforms.RandomResizedCrop(size=(224, 224), scale=(0.6, 1.0)), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomRotation(degrees=15), transforms.ColorJitter(brightness=0.4, contrast=0.4, saturation=0.4), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])这套增强策略看起来平常,但里面每个参数都是踩过坑之后调出来的。RandomResizedCrop的scale参数从一开始的(0.08, 1.0)改成了(0.6, 1.0),因为我的任务里图片主体占画面的比例通常较大,随机裁剪比例太小会导致裁出来的图像里完全没有目标物体,变成无效样本。ColorJitter的幅度不能调太大,我试过把亮度范围设为 0.6,结果模型在验证集上的表现显著下滑,原因是增强后的图片已经失真到人眼都难以辨认了,模型学到的特征自然也不可靠。
注意:数据增强只应用于训练集,验证集和测试集只做 resize 和归一化,不能用增强。否则验证集的评估结果会掺杂数据增强带来的噪声,无法真实反映模型性能。
2.4 数据划分比例与类别分布检查
数据划分上,我的比例是训练集 70%、验证集 15%、测试集 15%。这个比例不是拍脑袋决定的,而是根据我的数据总量和类别数估算的。数据总量在 4000 张左右,每类约 800 张,训练集每类约 560 张。对于迁移学习来说,这个量级是够用的,但前提是类别分布要均衡。
我检查了一下原始数据的类别分布,发现“建筑物”和“行人”两类图片特别多,而“标识牌”类别图片特别少。如果不做处理,模型会对样本多的类别产生偏好,样本少的类别精度会明显偏低。解决方法是针对少数类做额外增强——不只是简单的翻转和旋转,而是使用更激进的增强策略,比如更大的旋转角度、更强的色彩扰动、额外的随机擦除。
分批处理时还要注意一个容易忽略的问题:类别分布要在每个 batch 内也尽量均衡。如果用的数据加载器是按顺序取样的,一个 batch 内可能全是同一个类别的图片,这会让训练过程出现周期性波动。我用的方案是在每个 epoch 开始前对样本顺序做一次随机打乱,同时把采样器的shuffle参数设为True,确保每个 batch 内类别分布是随机的。
3. 模型选型与结构深度解读
3.1 选预训练模型还是从零开始训练
模型选型是整个任务的核心决策之一。这个决策直接决定了训练时间、精度上限和调参复杂度。
我在动手前就想清楚了一件事:我只有几千张图,这点数据量远远不够从零训练一个深度卷积神经网络。图像分类的公开基准数据集动辄上百万张图片,模型参数量动辄千万级,没有足够数据做支撑,从零训练出来的模型基本就是欠拟合状态,准确率可能连 60% 都到不了。
所以我的方案是迁移学习:使用在大型公开数据集上预训练好的模型作为特征提取器,把模型在目标数据上做微调(Fine-tuning)。这样做有三个直接的好处:特征提取层已经学到了通用的视觉特征(边缘、纹理、形状等),这些特征在绝大多数图像任务里是通用的;训练时间大幅缩短,因为只需要调整最后几层网络;需要的训练数据量小得多,几千张图就能取得不错的效果。
3.2 ResNet 还是 EfficientNet
在具体模型的选择上,我比较了两个主流系列:ResNet 和 EfficientNet。
ResNet(残差网络)是图像分类领域的老牌选手。它的核心创新是引入了残差连接,让网络可以堆叠到很深的层次而不发生梯度消失。我最初用的是 ResNet18,后来升级到 ResNet50。ResNet50 的参数量约 2500 万,在 ImageNet 上预训练过的版本可以直接通过权重下载获得。
EfficientNet 是后来出现的更高效结构,核心创新是通过复合缩放方法同时调整网络的深度、宽度和输入分辨率,在同等参数量下取得了更好的精度。我当时也尝试了 EfficientNet-B3,发现精度确实比 ResNet50 高了约 1 个百分点,但代价是推理速度慢了近一倍。考虑到我的任务对实时性要求不高,但对精度要求较高,理论上应该选择 EfficientNet。
但最终我选择了 ResNet50,原因是生态成熟度。ResNet 的预训练权重在各种框架里都能直接加载,社区讨论多、踩坑资料丰富,后续部署到不同平台时兼容性更好。而 EfficientNet 虽然精度略高,但某些平台上的算子支持不够完善,推理时可能需要额外的模型转换工作。
我的经验是:在项目时间有限、没有明确性能瓶颈的前提下,选择生态更成熟、资料更丰富的模型,远比追求几个百分点的精度提升更稳妥。
3.3 分类头的设计与参数初始化
模型的主体确定后,还需要设计分类头——也就是最后一层之前的全连接结构。预训练模型原始的分类头是面向 1000 类任务的,我需要把它替换成面向我的 5 类任务的新分类头。
我的做法是保留预训练模型除最后一层外的所有层,冻结其参数作为特征提取器,只训练新加的全连接层。新加的分类头结构是:
GlobalAveragePooling2D -> Dropout(0.3) -> Dense(128, ReLU) -> Dropout(0.3) -> Dense(5, Softmax)这里有个细节值得展开:Dropout 的比率我试过 0.5(常见默认值),但发现训练集准确率很高而验证集准确率偏低,出现了过拟合迹象。把 Dropout 降到 0.3 之后,过拟合问题得到缓解,验证集准确率提升了一个多点。Dropout 值并不是越大越好,它本质上是让模型“学得不那么死”,如果投太狠,模型学不进去,训练集准确率都会下降。
分类头参数初始化上,全连接层的权重我用的是torch.nn.init.kaiming_normal_,偏置初始化为 0。这一步看着不起眼,但对训练的稳定性影响很大——如果权重初始化过大,前向传播时激活值会饱和,梯度消失或爆炸的风险都很高;初始化过小则特征提取能力不足。
4. 训练配置与完整实操过程
4.1 关键训练参数的选择逻辑
训练参数的选择是整个过程中最容易让人纠结的部分。我总结了自己实际使用的配置和调整过程。
先看关键参数表:
| 参数名 | 我的取值 | 选择逻辑 |
|---|---|---|
| 优化器 | Adam,学习率 1e-4 | Adam 自适应调整学习率,前期训练稳定 |
| Batch Size | 32 | 我的 GPU 显存(8GB)允许的最大值 |
| Epochs | 30 | 验证集准确率在 20 epoch 后趋于平缓 |
| 学习率调度 | StepLR,每 10 个 epoch 衰减 0.1 | 后期用更小的学习率精调 |
| Loss 函数 | CrossEntropyLoss | 多分类任务的标准选择 |
| 输入尺寸 | 224x224 | 与预训练模型输入要求一致 |
Batch Size 这个参数,很多人不重视,直接拿默认值用。这里面的门道在于:Batch Size 太大,模型收敛快但容易收敛到尖锐的极小值,泛化能力较差;Batch Size 太小,梯度估计噪声大,训练不稳定,而且显存利用率低。8GB 显存跑 ResNet50,32 是我实测的稳定上限。你如果显存更大,可以适当调大,但要同步调大学习率——这两者是强关联的。
学习率的选择,我是在一个小数据集上做了多次实验才确定的。先是用 0.01,结果损失函数在训练初期就疯狂震荡,完全收敛不了;降到 0.001,损失能下降但速度太慢;最后试了 1e-4,训练曲线非常平滑。这个“先粗后细”的试错方法,比直接查资料抄参数要靠谱得多。
4.2 完整训练流程与核心代码
整个训练流程我分成了阶段性的微调策略,每步都做了记录。
第一阶段:只训练分类头,冻结主干网络。跑 10 个 epoch,验证集准确率能到 88% 左右。这一阶段的目标是把随机初始化的分类头训练好,让它能有效利用预训练模型提取到的特征。
第二阶段:解冻主干网络的最后两层,与分类头一起微调。跑 15 个 epoch,验证集准确率提升到 92%。解冻的目的,是让主干网络的高层特征适应我任务中数据分布的特点,但只解冻最后两层,避免了浅层通用特征被破坏。
第三阶段:解冻更多层(最后 4 层),用更小的学习率(1e-5)微调 10 个 epoch。验证集准确率最终稳定在 94.2%。
# 核心训练框架(PyTorch 简化版) import torch import torch.nn as nn import torch.optim as optim from torchvision import models, datasets, transforms # 加载预训练模型并替换分类头 model = models.resnet50(pretrained=True) num_features = model.fc.in_features model.fc = nn.Sequential( nn.Dropout(0.3), nn.Linear(num_features, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, 5) ) # 阶段一:冻结主干,只训练分类头 for param in model.parameters(): param.requires_grad = False for param in model.fc.parameters(): param.requires_grad = True optimizer = optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-4) criterion = nn.CrossEntropyLoss()这段代码里每个细节都有讲究:model.fc.in_features自动获取原模型最后一层的输入维度,避免手动写死;requires_grad控制哪些层参与梯度更新;optimizer只传入需要训练的参数,冻结层的参数不参与更新。
4.3 训练过程中的监控指标与观察记录
训练过程中,我养成了一个习惯:每个 epoch 结束后打印完整的指标矩阵,而不仅仅是一个 loss 值。完整的监控指标包括:训练集 loss、验证集 loss、训练集准确率、验证集准确率、每个类别的精确率和召回率。
打印这些指标的价值在于,你能直观看到模型到底在哪个环节出了问题:
- 训练集 loss 下降而验证集 loss 上升 → 过拟合,需要加强正则化或增加数据增强强度。
- 训练集和验证集 loss 都持续下降但准确率不涨 → 学习率偏大或模型容量不足。
- 某个类别的召回率明显低于其他类别 → 该类样本数量不足,或类别间特征重叠严重,需要针对性补充数据或调整类别权重。
我还记录了温度计式的曲线观察:第 5 个 epoch 时融合层开始有效工作,损失下降斜率最大;第 15 个 epoch 之后 loss 下降明显放缓,这是 StepLR 学习率衰减生效的节点;第 24 个 epoch 后验证集准确率基本不再变化,训练开始进入平台期。
这些观察记录,事后回头看简直是调参的路标。没有这些记录,你只能对着一个最终训练完的模型暗自猜测“它到底是怎么变成这样的”,完全无法复现调优过程。
5. 实验评估与结果分析
5.1 评估指标的选取与计算方式
很多人做图像分类任务,汇报结果就一个准确率,这远远不够。准确率只能反映整体情况,无法回答“模型在哪种场景下会犯什么错误”这类关键问题。
我的评估指标体系包含三个层次:整体准确率、每个类别的精确率与召回率和混淆矩阵。
以我的最终模型在测试集上的表现为例:
| 类别 | 精确率 | 召回率 | F1分数 |
|---|---|---|---|
| 建筑物 | 96.3% | 94.1% | 95.2% |
| 行人 | 92.8% | 95.1% | 93.9% |
| 车辆 | 94.5% | 92.3% | 93.4% |
| 自然景观 | 95.2% | 97.0% | 96.1% |
| 标识牌 | 88.9% | 91.2% | 90.0% |
从这个表格里能看到明显的问题:标识牌类别的各项指标整体偏低,而自然景观类别表现最好。结合混淆矩阵进一步分析,标识牌被误判为建筑物的比例约为 6%,而行人被误判为车辆的约为 3%——这说明模型在区分“竖条状结构”和“横条状结构”的特征上还有不足,也反映标识牌类别本身的样本多样性不够。
5.2 混淆矩阵的实际用途
混淆矩阵是评估阶段最常用的工具,我强烈建议每个人都画一张。
真实类别 建筑物 行人 车辆 自然景观 标识牌 建筑物 315 4 2 3 2 行人 8 289 5 2 4 车辆 3 4 278 3 5 自然景观 2 1 2 301 1 标识牌 7 3 5 3 220从这组数字里能提取很多信息:模型最常把标识牌误判为建筑物,可能是因为标识牌通常位于建筑外墙附近,背景干扰教大;行人被误判为车辆,可能是因为行人身穿的反光背心在某些光线条件下与车辆前挡风玻璃反光特征相似。
针对这些误判模式,我做了两件事的改进:一是给标识牌类别增加了更多不同背景下的训练样本,专门挑那些位于复杂背景中的标识牌图片;二是调整了数据增强策略,增加了对图像局部高光区域的随机遮挡,降低模型对反光特征的依赖。
改进后,标识牌类别的 F1 分数从 90.0% 提升到了 92.3%,虽然提升幅度不大,但说明分析混淆矩阵定向补数据的思路是正确的。
5.3 与基线模型的对比分析
我始终保留了一个简单的基线模型做对比——这里我选的基线是一个两层的微型卷积网络,参数量只有约 5 万个。基线的意义在于提供一个“最底线程度的参考”。
最终对比结果是:基线模型测试集准确率 71.4%,迁移学习模型测试集准确率 94.2%,差距是 22.8 个百分点。如果把计算量也考虑进去,基线模型由于参数量小,训练速度快了约 8 倍,但这种速度优势在精度差距面前完全不值一提。
这个对比让我深刻体会到了迁移学习的威力——同样是几小时的训练时间,使用预训练模型能取得的效果远好于从零开始训练的浅层网络。所以对于数据量有限的图像分类任务,预训练模型就是最优解,没有之一。
6. 常见问题与踩坑经验速查
6.1 训练不收敛的排查看板
训练不收敛是最让人头疼的问题,我结合自己的实际经历,整理了一份排查看板。遇到不收敛问题时,按顺序检查以下指标:
第一,损失函数值是否在合理范围内震荡?CrossEntropyLoss 的初始值应该是类别数的自然对数,对 5 分类任务来说就是 ln5 ≈ 1.609。如果初始 loss 远大于这个值,说明模型初始化有问题,或者数据预处理环节(如归一化)做错了。
第二,准确率曲线是否在上升?如果 loss 在下降但准确率纹丝不动,多半是类别不平衡导致的——模型把所有样本都预测成了多数类,loss 虽然低但准确率上不去。这时候要看单个类别的精确率和召回率。
第三,梯度值是否出现爆炸或消失?如果训练过程中出现了NaN损失,基本可以确定是学习率过大。我曾经在一次任务中把学习率设成 0.01,第二十个 epoch 时准确率突然变成 0,损失变成 NaN,回看学习率调度器才发现是 StepLR 的衰减周期设置不匹配,学习率衰减后又配合 Adam 的动态调整,出现了异常升高。
6.2 显存不足的常见应对
8GB 显存是很多个人开发者的标配,训练 ResNet50 这类模型时经常遇到显存溢出的问题。我的经验是一个从“减小 Batch Size”到“更换更小模型”的降级方案:先减 Batch Size 到 16,仍溢出就减到 8,再不行改用梯度累积(每 4 步再更新一次参数,等效于 Batch Size 32)、使用混合精度训练(显存占用减少近一半)、最后万不得已换 ResNet18 或 MobileNet。
梯度累积的代码实现很简单:
accumulation_steps = 4 optimizer.zero_grad() for i, (inputs, labels) in enumerate(train_loader): outputs = model(inputs) loss = criterion(outputs, labels) loss = loss / accumulation_steps loss.backward() if (i + 1) % accumulation_steps == 0: optimizer.step() optimizer.zero_grad()注意有个细节:梯度累积时每次的 loss 要除以累积步数,否则梯度会变成原来的 4 倍,相当于实际学习率被放大了 4 倍,训练容易失控。
6.3 模型精度上不去的排障清单
当验证集准确率卡在某个水平不提升时,我的排查顺序是:
一是检查数据本身的问题。有没有标签错误?有没有类别混淆的图片?这类问题最隐蔽,因为你不会意识到数据“看起来不对”。我遇到过一次标识牌类别里混入了几张“建筑物外墙涂鸦”的图片,模型训练后标识牌精确率一直上不去,人工复核才发现问题。
二是检查数据增强是否过强或过弱。增强过强导致模型学不到有效特征,增强过弱导致模型过拟合训练集。解决办法是跑一次只做基本预处理(缩放 + 归一化)的对照实验,对比增强前后的验证集准确率差异。
三是检查模型容量是否足够。如果你的任务比较复杂,而模型只用了 ResNet18,那可以考虑升级到 ResNet50 或更大的模型。模型容量不足的表现是训练集准确率和验证集准确率同时偏低,两者差距不大——这种情况下换更大模型是有效的。
四是检查分类头的设计。分类头过宽容易过拟合,过窄容易欠拟合。我之前把分类头的隐层节点数设成 512,验证集准确率反而比 128 时低了一点,说明容量超出了任务需要。
6.4 编号、水印与干扰因素的规避
日常收集的数据里往往带有各种“多余的痕迹”:时间水印、相机型号信息、地理坐标、边框装饰等。这些痕迹如果不提前处理,模型极有可能利用这些特征做分类判断,而不是学习真正的视觉内容。
最典型的例子是:如果某个类别有一半以上的图片都带同样的时间戳水印,模型就会把“有水印”作为分类依据。而真实部署场景里,输入的新图片没有水印,模型的表现就会显著下降。
处理办法分两步:能裁剪的区域直接裁剪掉;无法裁剪的,对训练集中的水印区域做随机遮挡或高斯模糊,破坏模型对这种特征的依赖。做完这个处理后,我在验证集上的准确率有 1 到 2 个百分点的提升,说明模型真正在学与任务相关的内容了。
6.5 数据规模不足时的迁移学习技巧
如果你的数据规模比我还少,比如只有几百张图,该怎么办?我的建议:
一是把预训练模型的所有参数完全冻结,只训练分类头。这时候模型等同于一个离线特征提取器,你只需要训练最后一层全连接。几百张图足够训好一个单层分类头。
二是使用预训练模型提取到的特征做传统机器学习分类(比如用 SVM 或随机森林做最终分类器)。这招我试过,在数据极少的情况下有时比端到端微调效果还稳定。
三是在训练过程中使用模型检查点保存最佳权重。不要只看最后一个 epoch 的结果——训练过程中验证集准确率最高点的模型权重往往比最后一步的权重更好。我现在始终都是保留验证集准确率最高的那个检查点作为最终模型。
四是对抗过拟合的终极招式正则化组合拳:数据增强 + Dropout + 权重衰减(L2)。权重衰减我设为 1e-4,能显著抑制过拟合。
7. 后续扩展思路
有了一段完整的图像分类任务实操基础后,你会发现很多东西都可以在这个基础上做延展。
最直接的扩展方向是把任务从单标签分类变成多标签分类——同一张图片里同时有建筑物、车辆和行人,那就不是一个类别的问题了。改造起来不算复杂:最后一个全连接层的输出节点数改为类别数,损失函数从 CrossEntropyLoss 换成 BCEWithLogitsLoss,评估指标改为每个类别的独立精确率和召回率。
另外一条路是把分类任务升级为目标检测——不仅要回答“图片里有什么”,还要回答“它在图片的哪个位置”。这个扩展的实际价值更大,但也需要标注边界框,数据标注成本显著上升。
我还尝试过用这个训练好的模型做特征提取,把图片转成向量再做检索——输入一张新图,从库里找出视觉上最相似的图片。这本质上就是许多以图搜图应用的原型,做出来之后还挺好玩的。
最后聊两句
这个图像分类项目做下来,我最大的体会其实不是“模型有多么聪明”,而是“数据的准备有多么关键”。模型选型、参数调优这些环节固然重要,但真正决定天花板的是数据质量。数据和特征是上限,模型只是逼近这个上限的手段。
再分享一个小技巧:如果你在训练过程中发现验证集准确率长时间不波动,可以试试把学习率调大一个数量级跑两三个 epoch 看反应。如果损失值大幅震荡,说明当前学习率偏大;如果纹丝不动,说明模型容量或数据可能已经到瓶颈了。这个“探测式调整”的办法,比我一开始闷着头等几十个 epoch 都等不到变化的情况要高效得多。