【数据不平衡处理方法】

处理数据不平衡问题的方法有多种,以下是一些常用的方法:

  1. 过采样(Oversampling):增加少数类样本的数量,使得正样本和负样本的数量更加平衡。过采样的方法包括随机复制样本、SMOTE(Synthetic
    Minority Over-sampling Technique)等。

  2. 欠采样(Undersampling):减少多数类样本的数量,使得正样本和负样本的数量更加平衡。欠采样的方法包括随机删除样本、Cluster
    Centroids等。

  3. 生成合成样本(Synthetic Sample Generation):通过生成合成样本的方式增加少数类样本的数量,而不是简单地复制已有的样本。SMOTE是最常用的合成样本生成方法之一。

  4. 集成学习(Ensemble Learning):使用集成学习方法,如Bagging、Boosting等,结合多个模型来处理数据不平衡问题。在不同的子模型中,可以使用不同的采样策略来处理不平衡问题。

  5. 代价敏感学习(Cost-sensitive Learning):在训练过程中,给少数类样本赋予更高的权重,以降低模型对多数类样本的偏好,使得模型更加关注少数类样本。

  6. 阈值调整(Threshold Adjustment):调整模型的预测阈值,使得模型更容易检测到少数类样本。通常情况下,可以将阈值设置得更低,以增加对少数类样本的检测灵敏度。

  7. 基于特征选择的方法(Feature-based Methods):选择更具代表性的特征,或者通过特征工程的方式增加有助于区分少数类样本的特征,从而提高模型对少数类样本的识别能力。

选择哪种方法取决于数据集的特点、任务需求以及计算资源等因素。通常情况下,需要尝试多种方法,并根据模型的性能进行评估和选择。

1.生成合成样本

以下的处理是为了增加训练样本的多样性。提高畸形听小骨的学习效果,使模型更好地适应这类样本,从而提升整体的检测性能。

以一个demo为例。 由于数据集存在数据不平衡的问题,主要体现在学习占比较多的是正常听小骨,而畸形听小骨的样本相对较少,这使得模型在学习畸形听小骨方面的效果不佳。在这里插入图片描述

1.1. 脚本筛选出数据少的数据

为了解决这一问题。我通过脚本筛选出数据集中所有包含畸形听小骨的图像。
以下为筛选并复制畸形听小骨脚本的全部代码:

import os
import shutil

def filter_and_copy_labels(input_folder, output_folder):
    # 确保输出文件夹存在
    if not os.path.exists(output_folder):
        os.makedirs(output_folder)

    # 遍历输入文件夹中的标签文件
    for filename in os.listdir(input_folder):
        if filename.endswith(".txt"):
            input_path = os.path.join(input_folder, filename)
            output_path = os.path.join(output_folder, filename)

            # 打开输入文件并检查是否有标签值为1的行,如果有则复制整个文件到输出文件夹
            with open(input_path, 'r') as input_file:
                for line in input_file:
                    label = int(line.split()[0])
                    if label == 1:
                        shutil.copyfile(input_path, output_path)
                        break  # 一旦找到一个标签值为1的行,就复制整个文件并停止循环

if __name__ == "__main__":
    input_folder = r"E:\CT\dataset\labels"
    output_folder = r"E:\CT\dataset\labels_1"

    filter_and_copy_labels(input_folder, output_folder)

1.2. 进行数据增强

并对这些图像进行数据增强,包括缩放,旋转,错切,翻转,增加高斯噪声和 限制对比度自适应直方图均衡等方法进行数据增强等操作。
全部脚本:

import xml.etree.ElementTree as ET
import os
import numpy as np
from PIL import Image
import imgaug as ia
from imgaug import augmenters as iaa

ia.seed(1)


# 读取出图像中的目标框
def read_xml_annotation(root, image_id):
    in_file = open(os.path.join(root, image_id))
    tree = ET.parse(in_file)
    root = tree.getroot()
    bndboxlist = []

    for object in root.findall('object'):  # 找到root节点下的所有country节点
        bndbox = object.find('bndbox')  # 子节点下节点rank的值

        xmin = int(bndbox.find('xmin').text)
        xmax = int(bndbox.find('xmax').text)
        ymin = int(bndbox.find('ymin').text)
        ymax = int(bndbox.find('ymax').text)

        bndboxlist.append([xmin, ymin, xmax, ymax])

    return bndboxlist  # 以多维数组的形式保存


def change_xml_annotation(root, image_id, new_target):
    new_xmin = new_target[0]
    new_ymin = new_target[1]
    new_xmax = new_target[2]
    new_ymax = new_target[3]

    in_file = open(os.path.join(root, str(image_id) + '.xml'))
    tree = ET.parse(in_file)
    xmlroot = tree.getroot()
    object = xmlroot.find('object')
    bndbox = object.find('bndbox')
    xmin = bndbox.find('xmin')
    xmin.text = str(new_xmin)
    ymin = bndbox.find('ymin')
    ymin.text = str(new_ymin)
    xmax = bndbox.find('xmax')
    xmax.text = str(new_xmax)
    ymax = bndbox.find('ymax')
    ymax.text = str(new_ymax)
    tree.write(os.path.join(root, str(image_id) + "_aug" + '.xml'))


def change_xml_list_annotation(root, image_id, new_target, saveroot, id,image_aug):
    in_file = open(os.path.join(root, str(image_id) + '.xml'))  # 读取原来的xml文件
    tree = ET.parse(in_file)  # 读取xml文件
    xmlroot = tree.getroot()
    # 获取图像的宽度和高度信息
    img_height, img_width, _ = image_aug.shape
    index = 0
    # 将bbox中原来的坐标值换成新生成的坐标值
    for object in xmlroot.findall('object'):  # 找到root节点下的所有country节点
        bndbox = object.find('bndbox')  # 子节点下节点rank的值

        # 注意new_target原本保存为高维数组
        new_xmin = new_target[index][0]
        new_ymin = new_target[index][1]
        new_xmax = new_target[index][2]
        new_ymax = new_target[index][3]

        xmin = bndbox.find('xmin')
        xmin.text = str(new_xmin)
        ymin = bndbox.find('ymin')
        ymin.text = str(new_ymin)
        xmax = bndbox.find('xmax')
        xmax.text = str(new_xmax)
        ymax = bndbox.find('ymax')
        ymax.text = str(new_ymax)

        index = index + 1
    # 添加图像宽度和高度信息
    size = xmlroot.find('size')
    width_elem = size.find('width')
    height_elem = size.find('height')

    width_elem.text = str(img_width)
    height_elem.text = str(img_height)

    tree.write(os.path.join(saveroot, str(image_id) + "_aug_" + str(id) + '.xml'))


# 处理文件
def mkdir(path):
    # 去除首位空格
    path = path.strip()
    # 去除尾部 \ 符号
    path = path.rstrip("\\")
    # 判断路径是否存在
    # 存在     True
    # 不存在   False
    isExists = os.path.exists(path)
    # 判断结果
    if not isExists:
        # 如果不存在则创建目录
        # 创建目录操作函数
        os.makedirs(path)
        print(path + ' 创建成功')
        return True
    else:
        # 如果目录存在则不创建,并提示目录已存在
        print(path + ' 目录已存在')
        return False


if __name__ == "__main__":
    # 存储增强前的图片文件夹路径
    IMG_DIR = r"E:\CT\Aug_1\images_1"
    # 存储增强前的XML文件夹路径
    XML_DIR = r"E:\CT\Aug_1\xml_1"

    # 存储增强后的影像文件夹路径
    AUG_IMG_DIR = r"E:\CT\Aug_1\Aug_1_sample1\images"
    mkdir(AUG_IMG_DIR)
    # 存储增强后的XML文件夹路径
    AUG_XML_DIR = r"E:\CT\Aug_1\Aug_1_sample1\xml"
    mkdir(AUG_XML_DIR)

    AUGLOOP = 5  # 每张影像增强的数量

    boxes_img_aug_list = []
    new_bndbox = []
    new_bndbox_list = []

    # 图片数据增强

    seq = iaa.Sequential([
        # 选择0到5种方法做变换
        iaa.SomeOf((2, 8),
                   [
                   # 改变标签文件的数据增强方式,有时需要重新标注    # 注意只要是带有旋转和平移性质的方式都要检查标签是否合适,做人工微调
                       # 仿射变换
                       # 包含:平移(Translation)、旋转(Rotation)、放缩(zoom)、错切(shear)。
                       # 仿设变换通常会产生一些新的像素点,我们需要指定这些新的像素点的生成方法,这种指定通过设置cval和mode两个参数来实现。参数order用来设置插值方法。
                       # 只有在mode设置为“constant”时,cval的值才有效。
                       iaa.Affine(
                           translate_percent={"x": (-0.1, 0.1), "y": (-0.1, 0.1)},  # 平移
                           scale=(0.8, 0.95),  # 图像缩放为80%到95%之间
                           rotate=(-15, 15),  # 旋转±15度之间
                           shear=(-10, 10),  # 错切±15度之间
                           mode="edge"
                       ),

                       # 改变图片的尺寸只使用一个
                       iaa.OneOf([
                           # 长和宽变成256*256
                           iaa.Resize({"height": 1500, "width": 1500}),
                           # 长变成原来的0.5到0.75的随机倍数,宽同理
                           iaa.Resize({"height": (0.5, 0.75), "width": (0.8, 1)}),
                           # 长变成原来的0.8到1的随机倍数,且长宽比不变
                           iaa.Resize({"height": (0.8, 1), "width": "keep-aspect-ratio"})
                       ]),

                       # 翻转只使用一个
                       iaa.OneOf([
                           # 镜像翻转
                           iaa.Fliplr(1),  # 对50%的图片进行水平镜像翻转
                           iaa.Flipud(1),  # 对50%的图片进行垂直镜像翻转
                           # 中心对称
                           iaa.Sequential([
                               iaa.Fliplr(1),  # 对50%的图片进行水平镜像翻转
                               iaa.Flipud(1),  # 对50%的图片进行垂直镜像翻转
                           ]),
                       ]),
                       
                   # 不改变标签文件的数据增强方式
                       # 添加噪声只使用一个
                       iaa.OneOf([
                           # 增加高斯噪声
                           iaa.AdditiveGaussianNoise(
                                 loc=0, scale=(0.0, 0.05 * 255)
                            ),
                           # 为每个像素乘以一个值
                           iaa.MultiplyElementwise((0.8, 1.2)),
                           # 为每个像素加上一个值
                           iaa.AddElementwise((-40, 40)),
                       ]),
                       # 限制对比度自适应直方图均衡(CLAHE算法),本算法与普通的自适应直方图均衡不同地方在于对比度限幅,图像对比度会更自然。
                       iaa.CLAHE(clip_limit=(1, 15)),

                       # 不作任何变换
                       iaa.Noop(),
                   ],
                   random_order=True  # 以随机的方式执行上述扩充
                   )
    ], random_order=True)
    # 得到当前运行的目录和目录当中的文件,其中sub_folders可以为空

    for root, sub_folders, files in os.walk(XML_DIR):
        # 遍历每一张图片
        for name in files:
            bndbox = read_xml_annotation(XML_DIR, name)
            for epoch in range(AUGLOOP):
                seq_det = seq.to_deterministic()  # 固定变换序列,之后就可以先变换图像然后变换关键点,这样可以保证两次的变换完全相同

                # 读取图片
                img_path = os.path.join(IMG_DIR, name[:-4] + '.jpg')
                img = np.array(Image.open(img_path).convert('RGB'), dtype=np.uint8)

                # bndbox 坐标增强,依次处理所有的bbox
                for i in range(len(bndbox)):
                    bbs = ia.BoundingBoxesOnImage([
                        ia.BoundingBox(x1=bndbox[i][0], y1=bndbox[i][1], x2=bndbox[i][2], y2=bndbox[i][3]),
                    ], shape=img.shape)

                    bbs_aug = seq_det.augment_bounding_boxes([bbs])[0]
                    boxes_img_aug_list.append(bbs_aug)

                    # new_bndbox_list:[[x1,y1,x2,y2],...[],[]]
                    new_bndbox_list.append([int(bbs_aug.bounding_boxes[0].x1),
                                            int(bbs_aug.bounding_boxes[0].y1),
                                            int(bbs_aug.bounding_boxes[0].x2),
                                            int(bbs_aug.bounding_boxes[0].y2)])
                # 存储变化后的图片
                image_aug = seq_det.augment_images([img])[0]
                path = os.path.join(AUG_IMG_DIR, str(name[:-4]) + "_aug_" + str(epoch) + '.jpg')
                Image.fromarray(image_aug).save(path)

                # 存储变化后的XML
                change_xml_list_annotation(XML_DIR, name[:-4], new_bndbox_list, AUG_XML_DIR, epoch,image_aug)
                new_bndbox_list = []
                print(name[:-4] + ' ' + 'finish')
    print('Complete!!!!!')

增强后正常和畸形的标签框数量相仿。这样的处理能够提高畸形听小骨的学习效果,使模型更好地适应这类样本,从而提升整体的检测性能。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值