如何用Stratified sampling优化不平衡图像数据集的训练效果?
针对类别不平衡图像数据集的分层抽样优化方案
针对你遇到的7万张标注图像、20类分布不均导致模型准确率仅68%的问题,分层抽样确实是解决类别不平衡的核心方向之一。结合图像任务的特性,我整理了几个实操性强的抽样策略,帮你优化训练过程:
1. 基础分层抽样:先锁定类别比例一致性
这是最直接的入门方案,核心是保证训练集中各类别的样本占比与原始数据集完全一致,避免模型被样本量多的大类“带偏”。
用Python可以借助sklearn的StratifiedShuffleSplit快速实现,比如按7:3划分训练/测试集:
from sklearn.model_selection import StratifiedShuffleSplit import pandas as pd # 假设你有存储图像路径和标签的DataFrame df = pd.read_csv("image_labels.csv") X = df["image_path"].values y = df["label"].values # 初始化分层抽样器,固定随机种子保证可复现 sss = StratifiedShuffleSplit(n_splits=1, test_size=0.3, random_state=42) for train_idx, test_idx in sss.split(X, y): train_df = df.iloc[train_idx] test_df = df.iloc[test_idx] # 验证抽样后类别分布是否匹配原数据集 print("原数据集类别占比:\n", df["label"].value_counts(normalize=True)) print("训练集类别占比:\n", train_df["label"].value_counts(normalize=True))
如果小类的样本量本身还能接受(比如超过100张),这个方法就能快速改善模型的偏向性,但如果部分小类样本极少,就得结合下面的进阶策略。
2. 小类过抽样+大类欠抽样的组合策略
如果某些小类样本量不足(比如少于500张),单纯分层抽可能还是让模型学不到足够特征;而大类样本过多又会带来冗余、拖慢训练。这时候可以针对性处理:
- 小类过抽样:不要简单重复样本(容易过拟合),建议用图像增强生成新样本——比如对小类图像做随机翻转、裁剪、亮度调整、高斯模糊等,人工扩充小类的有效样本量
- 大类欠抽样:不是随机删除,而是选最具代表性的样本——比如用聚类算法把大类图像分组,每个簇只保留1-2张核心样本,减少冗余
举个代码示例(假设样本数<500为小类,>5000为大类):
import numpy as np from sklearn.cluster import KMeans from skimage.io import imread from skimage.transform import resize # 统计各类别样本数 class_counts = df["label"].value_counts() small_classes = class_counts[class_counts < 500].index.tolist() large_classes = class_counts[class_counts > 5000].index.tolist() # 处理小类:用图像增强生成新样本(这里简化为重复+随机采样,实际替换为增强逻辑) small_class_dfs = [] for cls in small_classes: cls_df = df[df["label"] == cls] # 扩充到500张样本 expanded_df = pd.concat([cls_df]*(500 // len(cls_df) + 1)).sample(n=500, random_state=42) small_class_dfs.append(expanded_df) # 处理大类:聚类选代表性样本 large_class_dfs = [] for cls in large_classes: cls_df = df[df["label"] == cls] # 提取图像特征(实际可用预训练CNN提取更精准的特征) features = [] for path in cls_df["image_path"]: img = imread(path) img_resized = resize(img, (64, 64)) features.append(img_resized.flatten()) features = np.array(features) # 聚类后每个簇选1个样本,最终保留2000张 kmeans = KMeans(n_clusters=2000, random_state=42) kmeans.fit(features) cluster_indices = [np.where(kmeans.labels_ == c)[0][0] for c in range(2000)] sampled_large_df = cls_df.iloc[cluster_indices] large_class_dfs.append(sampled_large_df) # 中等类别直接分层抽样 medium_classes = [c for c in class_counts.index if c not in small_classes + large_classes] medium_class_dfs = [df[df["label"] == c].sample(frac=0.7, random_state=42) for c in medium_classes] # 合并最终训练集并打乱 final_train_df = pd.concat(small_class_dfs + large_class_dfs + medium_class_dfs) final_train_df = final_train_df.sample(frac=1, random_state=42)
3. 基于模型反馈的主动抽样:优先学难分类样本
如果基础抽样后准确率提升有限,可以试试主动学习的思路:让模型自己“选”需要学的样本。
- 先用基础分层抽样的数据集训练一个初始模型(比如ResNet50)
- 用模型预测整个数据集,找出那些模型预测置信度低的样本(比如top1和top2类别概率差小于0.2)
- 把这些难分类样本加入训练集(注意保持类别比例),重新训练模型
- 重复上述过程,直到准确率达到预期
这种方法能让模型优先攻克容易混淆的样本,尤其是小类中容易被误判为大类的图像,针对性提升整体准确率。
关键注意事项
- 验证/测试集也要分层:测试集必须严格匹配原始数据的类别分布,否则评估的准确率毫无参考价值
- 避免小类简单重复采样:重复样本会导致模型过拟合,一定要结合图像增强生成新的有效样本
- 全程监控类别分布:每次抽样后都要检查训练集的类别占比,确保没有偏离预期
建议你先从基础分层抽样开始,观察小类的召回率变化,如果小类表现依然很差,再逐步加入过抽样+欠抽样的组合策略,应该能有效提升模型的准确率。
内容的提问来源于stack exchange,提问作者Santosh Kashyap




