样本不平衡解决技巧

样本不平衡解决技巧_第1张图片
大家好!今天,我们将看看一些技巧使用深度学习模型来训练不平衡的数据集。在这种情况下,我们将处理一个不平衡的CIFAR-10图像分类数据集。

长尾数据集(不平衡数据集)

CIFAR-10数据集由10类60000张32x32彩色图像组成,每类6000张图像。训练图像50000张,测试图像10000张。
样本不平衡解决技巧_第2张图片

该数据集是均匀分布类型的数据集。我们可以手工制作自己的长尾CIFAR-10数据集,得到一个不平衡的数据集。如下所示。
样本不平衡解决技巧_第3张图片
现在我们有一个长尾的CIFAR-10数据集,其中1、2、4、5、8类数据量很大,0、7类数据量中等,3、6类数据量小,9类数据量非常小。为了测试数据,我们继续使用原来的数据集。

我们将尝试使用任何方法来改善由于训练数据不平衡而导致的较差性能,我们的假设是少数类别有更差的训练和测试错误。

基准模型

我们可以考虑使用在ImageNet数据集上预训练的EfficientNetB3分类模型作为基准模型,并且添加BatchNormalization,Dropout和1个全连接层,网络体系结构的细节及其超参数如下所示。

  • 模型:EfficientNet B3 (Pre-trained ImageNet)
  • 优化器:LazyAdam (learning rate = 0.001)
  • 训练与验证比例:0.2
  • 重新洗牌:True
  • 最大轮数:30 (Early Stopping patience = 5)
  • 损失:Cross-Entropy
    样本不平衡解决技巧_第4张图片

训练性能(在使用技巧之前)

样本不平衡解决技巧_第5张图片
尽管验证的准确性相当高,但训练性能受到过拟合的影响。

混淆矩阵和测试性能

样本不平衡解决技巧_第6张图片
样本不平衡解决技巧_第7张图片
测试结果对类数较多的类进行过拟合,模型泛化效果不佳。正如你所看到的,第3、5和9类的准确性较低。

从这个基线模型,我们将应用一些技巧来获得更高的测试精度和泛化能力更强的模型。

技巧

1. 调整权重

在这里,我们的损失函数是通过给少数类别分配相对较高的权重。我们可以使用scikit-learn库中的重新加权方法来估计不平衡数据集的类权重,以’ balanced '作为参数,类权重将由n_samples / (n_classes * np.bincount(y))给出。

from sklearn.utils import class_weight
class_weights = dict(zip(np.unique(y_train), class_weight.compute_class_weight('balanced',np.unique(y_train),y_train))) 
print(class_weights)

正如你所看到的,在某些类中,数据越低,它得到的权重就越大,反之亦然。

2.学习率策略

在任何优化器中,固定的学习率都是默认的学习率计划。为了在训练阶段获得最佳的优化,选择合适的学习率是很困难的。通过在本例中对不同的学习速率进行实验,lr=0.001显示了相对较好的性能。这可以作为我们实验不同学习速率策略的基准。学习率策略是为了在特定阶段的训练阶段使用合适的学习率。学习率策略通过根据损失函数值的变化来决定学习率是否衰减。

常用的学习率策略如下:

  • 热身学习率(warmup):在这种方法中,我们从开始训练阶段逐渐提高学习率,例如,我们设置初始学习速率尽可能小,然后乘以一个值将增加学习率,直到经过一定数量的轮数后得到基本学习率。这种思想有时适用于大规模数据集的训练,以避免“早期过拟合”。
    样本不平衡解决技巧_第8张图片
  • 步进衰减学习率(step decay):在特定数量的轮数设定了一个不同的学习率。学习率的值是在训练开始时使用回调函数定义的。
    样本不平衡解决技巧_第9张图片
  • 余弦衰减学习率(Cosine Decay):在这种方法中,我们采用在训练开始时逐渐降低学习率的方法,例如,我们设定初始学习率,然后乘以一个余弦衰减函数来计算衰减学习率。因此,学习率将持续下降,直到整个训练阶段结束。
    样本不平衡解决技巧_第10张图片
    有时我们也可以做实验把多种学习率策略组合在一起比如预热余弦衰减学习率(warm-up cosine decay),
    样本不平衡解决技巧_第11张图片
    或者退火余弦衰减学习率(annealing cosine decay)。
    样本不平衡解决技巧_第12张图片
  • 自适应衰减学习率(Adaptive Decay):该技术根据每个epoch误差的变化自适应地降低学习率,如果在某些epoch错误率没有降低(我们自己定义了patience阈值),则学习率将乘以衰减值以降低学习率,防止过拟合。在本例中,我们将使用自适应学习率策略来训练长尾数据集。(我们也可以使用另一种类型的学习率策略,并在做实验时比较结果)
class LossLearningRateScheduler(tf.keras.callbacks.History):
    """
    base_lr: the starting learning rate
    lookback_epochs: the number of epochs in the past to compare with the loss function at the current epoch to determine if progress is being made.
    decay_threshold / decay_multiple: if loss function has not improved by a factor of decay_threshold * lookback_epochs, then decay_multiple will be applied to the learning rate.
    spike_epochs: list of the epoch numbers where you want to spike the learning rate.
    spike_multiple: the multiple applied to the current learning rate for a spike.
    """

    def __init__(self, base_lr, lookback_epochs, spike_epochs = None, spike_multiple = 10, decay_threshold = 0.002, decay_multiple = 0.7, loss_type = 'val_loss'):
        super(LossLearningRateScheduler, self).__init__()
        self.base_lr = base_lr
        self.lookback_epochs = lookback_epochs
        self.spike_epochs = spike_epochs
        self.spike_multiple = spike_multiple
        self.decay_threshold = decay_threshold
        self.decay_multiple = decay_multiple
        self.loss_type = loss_type
    def on_epoch_begin(self, epoch, logs=None):
        if len(self.epoch) > self.lookback_epochs:
            current_lr = tf.keras.backend.get_value(self.model.optimizer.lr)
            target_loss = self.history[self.loss_type] 
            loss_diff =  target_loss[-int(self.lookback_epochs)] - target_loss[-1]
            if loss_diff <= np.abs(target_loss[-1]) * (self.decay_threshold * self.lookback_epochs):
                print(' '.join(('Changing learning rate from', str(current_lr), 'to', str(current_lr * self.decay_multiple))))
                tf.keras.backend.set_value(self.model.optimizer.lr, current_lr * self.decay_multiple)
                current_lr = current_lr * self.decay_multiple
            else:
                print(' '.join(('Learning rate:', str(current_lr))))
            if self.spike_epochs is not None and len(self.epoch) in self.spike_epochs:
                print(' '.join(('Spiking learning rate from', str(current_lr), 'to', str(current_lr * self.spike_multiple))))
                tf.keras.backend.set_value(self.model.optimizer.lr, current_lr * self.spike_multiple)
        else:
            print(' '.join(('Setting learning rate to', str(self.base_lr))))
            tf.keras.backend.set_value(self.model.optimizer.lr, self.base_lr)
        return tf.keras.backend.get_value(self.model.optimizer.lr)

callback_lr = LossLearningRateScheduler(base_lr=0.001, lookback_epochs=3)

3. 数据增强和重采样

处理不平衡数据集的基本方法之一是进行数据扩充和重采样。有两种重采样方法,如从多数类中删除数据的欠采样和向少数类中添加重复数据的过采样。
样本不平衡解决技巧_第13张图片
在这种方法中,我们结合了数据增强和重采样技术,通过应用数据增强来平衡数据集,通过重采样频率较低的样本来调整其数量。还通过旋转图像10度,并改变图像的亮度范围为0.2到1.0来达到数据增强的目的(我们也可以使用不同的数据增强)。

from keras.utils.data_utils import Sequence
from imblearn.over_sampling import RandomOverSampler
from imblearn.tensorflow import balanced_batch_generator

class BalancedDataGenerator(Sequence):
    """ImageDataGenerator + RandomOversampling"""
    def __init__(self, x, y, datagen, batch_size=32):
        self.datagen = datagen
        self.batch_size = min(batch_size, x.shape[0])
        datagen.fit(x)
        self.gen, self.steps_per_epoch = balanced_batch_generator(x.reshape(x.shape[0], -1), y, sampler=RandomOverSampler(), batch_size=self.batch_size, keep_sparse=True)
        self._shape = (self.steps_per_epoch * batch_size, *x.shape[1:])
        
    def __len__(self):
        return self.steps_per_epoch

    def __getitem__(self, idx):
        x_batch, y_batch = self.gen.__next__()
        x_batch = x_batch.reshape(-1, *self._shape[1:])
        return self.datagen.flow(x_batch, y_batch, batch_size=self.batch_size).next()

balanced_gen = BalancedDataGenerator(X_train, y_train, datagen, batch_size=64)
balanced_gen_val = BalancedDataGenerator(X_val, y_val, datagen, batch_size=64)
steps_per_epoch = balanced_gen.steps_per_epoch

4.损失函数

解决类不平衡问题的常见损失函数之一是使用Focal loss。Focal Loss不是试图减少离群值或模型的预测与事实相去甚远的预测,而是减少它正确预测的值的权重(或影响)。损失函数是一种数学方法用来表示猜测值与数据点实际值之间的距离。我们也可以做一些研究和实验来使用或创建我们自己的损失函数,这项研究被称为度量学习。

5.标签平滑

深度学习模型可能会变得过于自信,因此,模型不能很好地泛化,特别是在训练阶段使用不平衡的数据集时。标签平滑是一种用于分类问题的正则化技术,以防止模型过于自信地预测标签。这个想法很简单,例如,我们有10个类,其中一个y的one-hot是这样的[0, 0, 0, 0, 0, 0, 0, 0, 1, 0]

根据上面的标签,我们知道标签是类别9。在我们用一个特定的平滑因子应用标签平滑之后,第9类的标签变成了这样。[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.9 0.1]

遇到包含大量类(超过100个类)的大规模数据集,通常使用这个技巧来在模型中获得更高的准确性。但是,因为我们的数据集只有10个类,所以没有必要应用这个技巧。

结果分析

1.训练

样本不平衡解决技巧_第14张图片
样本不平衡解决技巧_第15张图片
样本不平衡解决技巧_第16张图片
样本不平衡解决技巧_第17张图片
样本不平衡解决技巧_第18张图片

Baseline

loss: 0.0668
accuracy: 0.9790
val_loss: 0.7388
val_accuracy: 0.8322

-------------------------------------------------------------------------------

Re-Weighted

loss: 0.1454
accuracy: 0.9465
val_loss: 0.8069
val_accuracy: 0.8220
-------------------------------------------------------------------------------

Learning rate scheduler and Re-weighted

loss: 0.0360
accuracy: 0.9839
val_loss: 0.8414

val_accuracy: 0.8180

-------------------------------------------------------------------------------

Focal Loss - Learning rate scheduler and Re-weighted

loss: 0.2346
accuracy: 0.7935
val_loss: 0.4262
val_accuracy: 0.7358

-------------------------------------------------------------------------------

Learning rate scheduler ,Data augmentation and Random oversampling

loss: 0.0255
accuracy: 0.9919
val_loss: 1.2233
val_accuracy: 0.7730f

-------------------------------------------------------------------------------

Learning rate scheduler ,Data augmentation, Random oversampling and Re-weighted

loss: 0.0652 
accuracy: 0.9699
val_loss: 1.1787
val_accuracy: 0.7686

分析

  • 重新加权(re-weighted):训练效果优于基线模型(没有任何处理),重新加权技术可以稍微减少过拟合问题,但训练阶段并不是真正稳定的。
  • 学习率策略和重新加权(Learning rate scheduler and Re-weighted):从这个实验中,你可以从上图中看到,在训练的稳定阶段,使用学习率策略的训练性能比没有使用学习率策略的训练性能更好。
  • Focal Loss-学习率策略和重新加权(Focal loss- Learning rate scheduler and Re-weighted):focal loss确实克服了过拟合问题,但不幸的是,精度是所有实验中最低的,但幸运的是,另一个实验中,损失也是最低的。从这个实验中,我们可以得出结论,不平衡的数据集处理有一些权衡,如准确性高但容易过拟合或准确性低,损失也低。但我们的目标是需要更高的精度和更低的损失来得到一个泛化很好的模型。
  • 学习率策略,数据增强,随机过采样(Learning rate scheduler, Data augmentation, and Random oversampling):在这个实验中,我们切换回使用常见的交叉熵损失,但加入了一些数据增广和随机过采样与学习率策略。我们删除了重新加权部分,以查看使用这些技术的效果。我们可以看到过拟合问题仍然会发生,但是混淆矩阵的结果和测试结果告诉我们不同的见解(详见测试部分)。
  • 学习率调度,数据增强,随机过采样,重新加权(Learning rate scheduler, Data augmentation, Random oversampling, and Re-weighted):在本实验中,我们在之前的实验中加入了重新加权来训练模型,我们可以看到,上面的结果是我们考虑训练行为(准确性、稳定性和拟合性)和测试结果所能得到的最平衡的结果。虽然模型仍然是过拟合的,但与其他模型相比,该模型是泛化能力最好的模型(详见测试部分)。

2.测试

样本不平衡解决技巧_第19张图片
样本不平衡解决技巧_第20张图片

分析

  • 重新加权(Re-Weighted):测试结果有了一定的提高,这主要表现在第3类和第9类的准确度提高了。
  • 学习率策略和重新加权(Learning rate scheduler and Re-weighted):添加学习率策略后,测试结果略有改善。在小类和极小类数据集中,正确预测数量增加,而在类1和类5上错误预测的数量减少。所以总体精度提高了0.01。
  • Focal Loss-学习率策略和重新加权(Focal loss- Learning rate scheduler and Re-weighted):Focal Loss使模型泛化能力更强但牺牲另一个类的准确性,这是一个权衡,避免不平衡数据集过度拟合,但在我们的例子中,不平衡数据集比较严重(因为某些类只有很少量的数据),所以很难得到精度高而不过度拟合模型。
  • 学习率策略,数据增强,随机过采样,重新加权(Learning rate scheduler, Data augmentation, Random oversampling, and Re-weighted):从以上结果可以看出,如果不重新加权数据集,模型容易出现过拟合。然而,在所有的实验中,总体性能是最高的,但更好的泛化能力并不发生在这个实验中(因为所有类得到一点点更好的结果)。我们仍然不推荐这个模型,因为在这个结果中仍然存在很高的精度差距。

最终模型

样本不平衡解决技巧_第21张图片
样本不平衡解决技巧_第22张图片
学习率策略,权重加权,数据增强,随机过采样(Learning rate scheduler, Re-weighted, Data augmentation, and random oversampling):这是我们尝试的最后一个实验结果。幸运的是,考虑到泛化和准确性,结果是最理想的。我们可以看到,第3类的小数据和第9类的小数据得到了最好的改进,然后错误预测减少了一点。与之前的实验相比,总体准确率较高,学习率策略的训练性能稳定,权重加权、数据增广和随机过采样的结果泛化能力更好。

结论

在本文的最后,从实验中,我们可以得出结论,处理不平衡的数据集有一些权衡,比如准确性高但容易过拟合,或者模型没有过拟合但性能较低。然而,我们的目标是,我们需要相当的精度和更低的损失,以得到一个泛化能力较强的模型。训练阶段的一些技巧可能有助于克服这个问题。

真实世界的数据通常具有长尾和开放式分布。一个实用的识别系统必须区分多数和少数类别,并从一些已知的实例中进行归纳。

你可能感兴趣的:(keras,数据增强,深度学习,keras)