3、数据标注:半自动标注工具(LabelImg、CVAT),标注质量检查,处理不平衡数据集

数据标注这件事,说实话,比调模型参数更磨人。

我刚开始做微小机器学习的时候,总觉得标注就是「体力活」,随便标标就行。结果呢?模型训练出来,准确率死活上不去。后来一查,标注框歪了十几个像素,标签还打错了几个。嗯,从那以后我再也不敢小看标注了。

今天咱们聊聊怎么用半自动工具提效,怎么检查标注质量,以及怎么处理那个让人头疼的不平衡数据集。

3.1 半自动标注工具:LabelImg 和 CVAT

纯手工标注,一张图花5分钟,1000张图就是5000分钟。你想想看,这谁受得了?

半自动标注的思路很简单:先用一个预训练模型跑一遍,生成初步标注框,然后人工微调。说白了,就是让机器先干80%的活,人只负责检查和修正。

3.1.1 LabelImg:轻量级首选

LabelImg 是我个人最常用的工具,尤其适合小数据集。它基于 Python + Qt,安装简单,开箱即用。

安装方式:
# 直接 pip 安装
pip install labelImg

# 或者从源码运行
git clone https://github.com/tzutalin/labelImg
cd labelImg
python labelImg.py

使用技巧:

  • 快捷键要背熟:W 画框,A/D 切换图片,Ctrl+S 保存。我习惯左手按键盘,右手握鼠标,效率能翻倍。
  • 自动保存模式:在 View 菜单里勾选 Auto Save mode,省得忘记保存。
  • 预标注插件:LabelImg 支持加载 YOLO 或 TensorFlow 的推理结果作为初始框。我在项目中用过一次,把标注时间从3天压缩到了半天。
我的经验:LabelImg 适合单机标注,数据量在几千张以内。如果团队协作,还是用 CVAT 更靠谱。

3.1.2 CVAT:团队协作利器

CVAT(Computer Vision Annotation Tool)是 Intel 开源的在线标注平台。它支持多人同时标注,还能自动跟踪标注进度。

部署方式(Docker 一键启动):

git clone https://github.com/opencv/cvat
cd cvat
docker-compose up -d

CVAT 的几个亮点:

  • 半自动标注:内置了 Deep Learning 模型,比如 Mask R-CNN、YOLO。你点一下「Auto Annotation」,它就能生成一批候选框。
  • 标注质量看板:能看到每个人标注了多少张、平均耗时、标注框的 IoU 分布。这个功能我特别喜欢,谁在摸鱼一目了然。
  • 导出格式丰富:支持 COCO、Pascal VOC、YOLO、TFRecord 等。不用自己写格式转换脚本。
注意:CVAT 对服务器资源要求不低。我建议至少 8GB 内存 + 独立显卡,否则多人同时标注时会卡顿。

3.2 标注质量检查:别让垃圾数据毁了模型

「垃圾进,垃圾出」——这句话在机器学习里是铁律。我见过太多人花大量时间调模型,结果发现是标注数据有问题。

标注质量检查,我一般分三步走:

3.2.1 人工抽检

随机抽取 5%-10% 的标注数据,让另一个人重新标注一遍。然后计算两个标注结果的 IoU(交并比)和标签一致性。

我常用的检查指标:

指标 计算公式 合格标准
IoU 交集面积 / 并集面积 ≥ 0.7
标签准确率 一致标签数 / 总标签数 ≥ 95%
漏标率 漏标物体数 / 总物体数 ≤ 5%

3.2.2 自动化检查脚本

写个脚本跑一遍,能发现很多肉眼看不出的问题。比如:

import json

def check_annotation(annotation_file):
    with open(annotation_file, 'r') as f:
        data = json.load(f)
    
    issues = []
    for img in data['images']:
        # 检查标注框是否超出图像边界
        for ann in img['annotations']:
            x, y, w, h = ann['bbox']
            if x < 0 or y < 0 or x+w > img['width'] or y+h > img['height']:
                issues.append(f"框越界: {img['file_name']}")
            
            # 检查框面积是否过小(可能是误标)
            if w * h < 100:
                issues.append(f"框太小: {img['file_name']}")
    
    return issues
避坑指南:我曾经遇到过一个项目,标注员把「猫」和「狗」的标签搞反了。模型训练了三天,准确率一直卡在50%。后来用自动化脚本一查,发现标签分布完全反了。从那以后,我每次都会先跑一遍标签分布统计。

3.2.3 一致性校验

多人标注时,不同人的标准可能不一样。比如「这个算不算遮挡?」「边界模糊的物体要不要标?」

我的做法是:

  • 制定详细的标注规范文档,图文并茂
  • 每周开一次标注对齐会,讨论争议案例
  • 用 Fleiss' Kappa 系数衡量标注一致性,低于 0.6 就要重新培训

3.3 处理不平衡数据集

不平衡数据集,说白了就是某些类别的样本特别多,某些类别特别少。比如工业缺陷检测中,合格品占了99%,缺陷品只有1%。

这种情况下,模型会「偷懒」——它只要把所有样本都预测为合格品,准确率就有99%。但这不是我们想要的。

3.3.1 数据层面:重采样

最直接的方法就是让各类别样本数量差不多。

过采样(Oversampling):

  • 随机复制少数类样本。简单,但容易过拟合。
  • SMOTE(合成少数类过采样):在少数类样本之间插值生成新样本。我试过,效果比随机复制好不少。

欠采样(Undersampling):

  • 随机删除多数类样本。适合数据量大的情况。
  • NearMiss:只保留多数类中离少数类最近的样本。能保留更多决策边界信息。
我的建议:先试试过采样 + 数据增强的组合。比如对少数类做随机旋转、裁剪、亮度调整。这样既增加了样本量,又引入了多样性。

3.3.2 算法层面:调整损失函数

如果不想动数据,可以改损失函数。

加权交叉熵损失:

import torch.nn as nn

# 计算每个类别的权重(样本数越少,权重越大)
class_weights = [1.0 / count for count in class_counts]
weights = torch.tensor(class_weights)

criterion = nn.CrossEntropyLoss(weight=weights)

Focal Loss:这是我在目标检测中常用的。它让模型更关注难分类的样本,而不是那些已经分对的样本。

class FocalLoss(nn.Module):
    def __init__(self, alpha=0.25, gamma=2.0):
        super().__init__()
        self.alpha = alpha
        self.gamma = gamma
    
    def forward(self, pred, target):
        ce_loss = F.cross_entropy(pred, target, reduction='none')
        pt = torch.exp(-ce_loss)
        focal_loss = self.alpha * (1 - pt) ** self.gamma * ce_loss
        return focal_loss.mean()
注意:Focal Loss 的 gamma 参数一般设为 2.0。设太大,模型会忽略简单样本,导致训练不稳定。我踩过这个坑,调了一天才发现是 gamma 设成了 5.0。

3.3.3 评估指标:别只看准确率

不平衡数据集上,准确率是骗人的。你应该关注:

  • 精确率(Precision):预测为正例的样本中,有多少是真的正例。
  • 召回率(Recall):真正的正例中,有多少被找出来了。
  • F1-Score:精确率和召回率的调和平均。
  • 混淆矩阵:一目了然看到哪些类别被混淆了。

举个例子:

指标 合格品 缺陷品
精确率 0.99 0.45
召回率 0.98 0.60
F1-Score 0.98 0.51

你看,缺陷品的 F1 只有 0.51,这才是真正需要优化的地方。

3.4 小结

数据标注这件事,说白了就是「磨刀不误砍柴工」。

用半自动工具提效,用质量检查保底,用重采样或损失函数处理不平衡。这三步走下来,你的数据集质量至少能提升一个档次。

我个人习惯是:标注阶段花 60% 的时间,检查阶段花 30%,处理不平衡花 10%。别嫌检查浪费时间,它省下的调参时间,远比你想象的多。

最后一句:如果你现在正被不平衡数据集折磨,先别急着调模型。回去看看你的标注数据,说不定问题就出在那里。