简介这份资源面向机器学习入门者与图像分类方向的开发者围绕SVM与贝叶斯分类器展开帮助读者理解如何从图像中提取色彩直方图、纹理、边缘等特征并完成分类任务。压缩包共216个文件约12.87MB以102个bmp图像样本、16个cpp源码与18个h头文件为核心配合obj、sbr等编译中间文件及exe可执行程序构成一套可运行的图像分类器工程另含少量txt说明与db数据文件。资源附带图形界面用户可直观操作并对比不同算法的分类效果省去手动特征工程的繁琐。已有2483人学习下载适合希望快速验证SVM、贝叶斯等经典方法在图像分类中表现、并借助现成工程结构进行二次开发或课程实验的读者参考。1. 机器学习做图像分类从 1-shot 到 ResNet50 的落地路线图图像分类是机器学习里最容易被低估的任务。很多人以为把图片塞进模型、跑几个 epoch 就完事结果一上手就翻车小样本场景下 1-shot 准确率还不如随机猜用 ViT 评估时分类头到底调不调心里没底ResNet50 迁移学习的学习率设错直接让 loss 炸成 NaN。这些不是玄学是每个环节的参数和流程没对齐。这篇笔记面向两类人一是刚入门机器学习、想用 Python 跑通图像分类全流程的工程师二是已经会调库、但在小样本和迁移学习边界上反复踩坑的熟手。我会从数据组织、模型选型、训练配置讲到评估验证重点拆解 1-shot/5-shot 小样本分类和 ResNet50 迁移学习的完整步骤把每个参数为什么这么设、改哪里会出什么问题讲清楚。读完你能自己搭一条可复现的图像分类流水线知道什么场景该用什么模型、哪些参数是必调的、失败时先看什么。2. 图像分类的数据管线与模型选型为什么你的 baseline 跑不过别人2.1 数据组织方式决定上限ImageFolder 与自定义 Dataset 的取舍图像分类的第一道坎不是模型是数据怎么喂进去。常见做法是用torchvision.datasets.ImageFolder它要求目录结构按类别分文件夹每个文件夹下放对应图片。这种方式零代码成本适合类别均衡、样本量在几百到几万张的场景。但真实项目里经常遇到类别不均衡、单类样本极少比如 1-shot、或者图片和标签存在 CSV 里的情况这时候必须写自定义 Dataset。下面是一个能同时处理 ImageFolder 和 CSV 标签的自定义 Dataset 骨架关键点在于把路径解析和图像变换解耦方便后续替换增强策略import os import pandas as pd from PIL import Image from torch.utils.data import Dataset class ImageClsDataset(Dataset): def __init__(self, root, label_fileNone, transformNone): root: 图片根目录 label_file: 可选CSV 文件包含 filename,label 两列不传则按文件夹名解析 transform: torchvision 变换组合 self.root root self.transform transform if label_file: df pd.read_csv(label_file) self.samples [(os.path.join(root, r.filename), int(r.label)) for r in df.itertuples()] self.classes sorted(df[label].unique().tolist()) else: self.samples [] self.classes sorted(os.listdir(root)) for idx, cls in enumerate(self.classes): cls_dir os.path.join(root, cls) for fname in os.listdir(cls_dir): self.samples.append((os.path.join(cls_dir, fname), idx)) def __len__(self): return len(self.samples) def __getitem__(self, idx): path, label self.samples[idx] img Image.open(path).convert(RGB) # 统一转 RGB避免灰度图通道数不一致 if self.transform: img self.transform(img) return img, label逻辑说明label_file存在时走 CSV 解析适合标签和图片分离的场景否则按文件夹名自动生成类别索引。convert(RGB)是血泪经验PNG 透明通道或灰度图直接进模型会导致通道数对不上报错信息还不直观。参数上root和label_file的路径建议用绝对路径相对路径在 DataLoader 多进程下容易因为工作目录变化而找不到文件。2.2 模型选型ResNet50、ViT 与小样本场景的匹配逻辑选模型不是越新越好。ResNet50 在中等规模数据集ImageNet 级别预训练上依然是性价比最高的 backbone参数量约 25M推理速度在单卡上能到几百 FPS适合工业质检、森林图像分类这类对延迟敏感的场景。ViT 的优势在于全局注意力数据量足够大百万级以上时上限更高但小数据集上容易过拟合而且评估时分类头的处理方式和 CNN 不同。关于「用 ViT 评估时分类头用调整吗」这个问题答案取决于你的评估目的。如果只是线性探测linear probe冻结 backbone 只训练分类头那分类头必须重新初始化并训练到收敛如果是全量微调分类头跟着一起更新不需要单独调整。常见错误是加载了预训练权重却忘了替换分类头导致输出维度对不上数据集类别数。小样本场景1-shot、5-shot下直接微调 ResNet50 或 ViT 都会严重过拟合。更可靠的做法是采用度量学习思路比如 Prototypical Networks 或 Matching Networks用预训练 backbone 提取特征再在特征空间做最近邻分类。下面是一个 1-shot 分类的评估逻辑示例import torch import torch.nn.functional as F def evaluate_1shot(model, support_x, support_y, query_x, query_y): support_x: [N_way, 1, C, H, W] 每个类 1 个样本 query_x: [N_query, C, H, W] 返回 1-shot 准确率 model.eval() with torch.no_grad(): # 提取 support 特征并计算每个类的原型 support_feat model(support_x.view(-1, *support_x.shape[2:])) # [N_way, D] prototypes support_feat.view(support_x.size(0), -1, support_feat.size(-1)).mean(dim1) # [N_way, D] query_feat model(query_x) # [N_query, D] # 余弦相似度分类 query_feat F.normalize(query_feat, dim-1) prototypes F.normalize(prototypes, dim-1) logits query_feat prototypes.t() # [N_query, N_way] pred logits.argmax(dim-1) acc (pred query_y).float().mean().item() return acc逻辑说明1-shot 下每个类只有一个支持样本原型就是该样本的特征向量。用余弦相似度替代欧氏距离在特征归一化后更稳定。参数上support_x的维度组织很关键N_way是类别数1-shot 时第二维为 1如果做 5-shot把第二维改成 5 并对特征取平均即可。注意 backbone 必须用预训练权重且冻结否则 1-shot 下几个梯度步就能把特征空间破坏掉。2.3 训练配置学习率、batch size 与冻结策略的联动迁移学习里最容易被忽视的是学习率分层。ResNet50 的 backbone 用预训练权重时学习率通常设 1e-4 到 1e-3如果随机初始化需要 1e-2 级别。分类头因为是新初始化的学习率可以设大 10 倍。常见做法是给 backbone 和分类头分别设置参数组import torch.optim as optim backbone_params [p for n, p in model.named_parameters() if fc not in n] head_params [p for n, p in model.named_parameters() if fc in n] optimizer optim.SGD([ {params: backbone_params, lr: 1e-4}, {params: head_params, lr: 1e-3} ], momentum0.9, weight_decay1e-4)逻辑说明weight_decay设 1e-4 是 ResNet 系列的经验值太大导致欠拟合太小正则不够。batch size 在单卡 8G 显存下 ResNet50 输入 224x224 最多跑到 32再大就 OOM。如果必须用大 batch记得同步放大学习率否则收敛变慢。冻结策略上数据量少于 1000 张时建议先冻结 backbone 只训分类头 5 到 10 个 epoch再解冻全量微调这样比一上来就全量微调稳定得多。3. 从零跑通 ResNet50 小样本图像分类完整步骤与参数表3.1 环境准备与数据划分的四个边界坑动手之前先把环境固定住。PyTorch 版本建议 1.12 以上torchvision 对应版本即可CUDA 版本按显卡驱动选。不要用最新版追新小样本场景下很多增强库和预训练权重对版本敏感。数据划分上小样本分类的验证集构造和常规分类不同1-shot 意味着每个类只有 1 个训练样本验证集和测试集需要单独划分且要保证类别覆盖一致。四个常见边界坑第一训练集里某些类只有 1 张图做分层采样时直接报错需要手动处理第二验证集类别数和训练集不一致导致分类头输出维度对不上第三图片尺寸不统一Resize 时把长宽比破坏得太厉害小样本下特征质量下降明显第四随机种子没固定每次跑出来的 1-shot 准确率波动超过 10 个百分点根本无法对比方法。import random import numpy as np import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark False set_seed(42)逻辑说明cudnn.deterministicTrue会牺牲一点速度换取可复现性小样本实验里这个代价值得付。benchmarkFalse避免算法自动选择带来的随机性。参数上seed 选 42 只是习惯关键是整个实验固定同一个值对比方法时不要换。3.2 训练循环与关键参数epoch、学习率调度、早停小样本分类的训练循环和常规分类有本质区别。1-shot 下每个 epoch 的样本数极少通常采用 episode 训练策略每个 episode 随机采样 N 个类、每类 K 个支持样本和 Q 个查询样本用查询样本的损失更新模型。这种训练方式比直接按 batch 训更稳定。def train_episode(model, optimizer, support_x, support_y, query_x, query_y, criterion): model.train() optimizer.zero_grad() logits model(support_x, support_y, query_x) # 自定义 forward输出 query 的分类 logits loss criterion(logits, query_y) loss.backward() optimizer.step() return loss.item()逻辑说明model需要自定义 forward 接收支持集和查询集内部完成特征提取和度量。参数上episode 数量建议 2000 到 5000每个 episode 的 N_way 设 5 或 10K 设 1 或 5Q 设 5 到 15。学习率用 1e-3 配 Adam或者 1e-2 配 SGD每 500 个 episode 衰减 0.5。早停看验证集 episode 的平均准确率连续 300 个 episode 不提升就停。参数1-shot 推荐值5-shot 推荐值说明N_way5 或 105 或 10类别数太大增加难度K (shot)15每类支持样本数Q (query)5~155~15每类查询样本数episode 数3000~50002000~40005-shot 收敛更快学习率1e-3 (Adam)1e-3 (Adam)配合 cosine 衰减验证频率每 100 episode每 100 episode用固定验证集3.3 评估指标为什么 accuracy 不够还要看置信区间小样本分类的准确率波动大单次评估的 accuracy 没有统计意义。正确做法是跑多个 episode 取平均并报告 95% 置信区间。常见做法是跑 600 个测试 episode每个 episode 随机采样然后计算均值和标准差。import numpy as np from scipy import stats def compute_ci(acc_list, confidence0.95): arr np.array(acc_list) mean arr.mean() sem stats.sem(arr) # 标准误 ci stats.t.interval(confidence, len(arr)-1, locmean, scalesem) return mean, ci逻辑说明sem是标准误t.interval给出置信区间。参数上acc_list是每个测试 episode 的准确率列表长度建议不少于 600。如果置信区间宽度超过 5 个百分点说明 episode 采样不够或者模型不稳定需要增加测试 episode 数或检查数据划分。4. 图像分类落地避坑五条血泪排查记录4.1 现象1-shot 准确率只有 20%换模型也没用原因backbone 没有用预训练权重或者用了但被随机初始化覆盖了。小样本下从零训练特征提取器根本学不到可迁移特征。解决加载 ImageNet 预训练权重冻结 backbone只训练度量模块或分类头。验证方法是打印 backbone 第一层卷积权重看是否和预训练值一致。4.2 现象训练 loss 正常下降验证准确率始终在随机水平原因数据标签和图片路径错位或者 CSV 里 label 列被读成了字符串导致类别索引混乱。解决随机抽 10 张图可视化并打印对应标签确认图片内容和标签一致。CSV 读取时显式指定dtype{label: int}避免字符串比较。4.3 现象用 ViT 评估时分类头输出维度报错原因ViT 预训练模型的分类头输出是 1000 类ImageNet直接加载后没有替换成自己的类别数。解决加载模型后重新初始化model.heads.head为nn.Linear(in_features, num_classes)或者用model.reset_classifier(num_classes)。如果做线性探测冻结 backbone 后只训练新分类头。4.4 现象ResNet50 微调时 loss 突然变成 NaN原因学习率太大或者输入数据没有归一化。ResNet 预训练权重期望输入是 ImageNet 均值和方差归一化后的张量如果直接送 0-255 的像素值梯度会爆炸。解决在 transform 里加Normalize(mean[0.485,0.456,0.406], std[0.229,0.224,0.225])学习率从 1e-4 开始试不要一上来就 1e-2。4.5 现象森林图像分类任务里模型把树和草地混淆原因森林场景类间差异小、类内差异大常规数据增强随机裁剪、翻转不够。解决加入颜色抖动ColorJitter和随机灰度模拟不同光照条件如果类别不平衡用 WeightedRandomSampler 重采样。另外输入分辨率从 224 提到 320 或 384 对小类间差异任务有明显提升但显存占用会增加需要相应调小 batch size。5. 进阶技巧用特征缓存把 1-shot 评估速度提 10 倍小样本评估里最耗时的不是训练是反复提取支持集和查询集的特征。如果 backbone 冻结特征只取决于输入图像和模型参数完全可以在评估前把所有图片的特征算好缓存起来后续 episode 采样直接在特征空间做速度能提升一个数量级。具体做法先用冻结的 backbone 对训练集和测试集所有图片提取特征存成[N, D]的 tensor 和对应标签。然后每个 episode 从缓存里按类别采样支持集和查询集特征直接做余弦相似度分类。这样单次评估从分钟级降到秒级方便跑大量 episode 统计置信区间。torch.no_grad() def extract_features(model, dataloader, device): model.eval() feats, labels [], [] for imgs, lbls in dataloader: imgs imgs.to(device) f model(imgs) # backbone 输出不含分类头 feats.append(f.cpu()) labels.append(lbls) return torch.cat(feats), torch.cat(labels) # 缓存后episode 采样直接在特征上做 def episode_from_cache(feats, labels, n_way, k_shot, q_query): classes torch.unique(labels) chosen classes[torch.randperm(len(classes))[:n_way]] support, query [], [] for c in chosen: idx (labels c).nonzero(as_tupleTrue)[0] perm idx[torch.randperm(len(idx))] support.append(feats[perm[:k_shot]]) query.append(feats[perm[k_shot:k_shotq_query]]) return torch.stack(support), torch.stack(query)逻辑说明extract_features只跑一次前向输出 backbone 特征。episode_from_cache从缓存里按类采样支持集和查询集都来自同一类别的不同样本。参数上k_shot和q_query之和不能超过该类样本总数否则需要处理越界。这个技巧的前提是 backbone 完全冻结如果做全量微调则缓存失效。另一个实用技巧是特征归一化方式的选择。余弦相似度配合 L2 归一化在 1-shot 下通常比欧氏距离高 2 到 5 个百分点但 5-shot 下差距缩小。我一般会两种都跑一遍看验证集置信区间有没有重叠重叠就选计算量小的那个。最后说一个习惯每次实验固定随机种子、记录完整参数、保存验证集 episode 的准确率列表而不是只存均值。这样后面复现或者对比方法时不用靠回忆去猜当时设了什么。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?