Deep Learning 基础 – 类别不均衡问题
本文主要包含如下内容:
类别不均衡问题
类别不均衡:分类任务中不同类别的训练样例数差别很大。为了解决类别不均衡的影响,我们比较以下解决方法。
扩大数据集
更多的数据往往战胜更好的算法,收集更多数据使得类别之间更加均衡。
阈值移动
在二分类问题中,假如预测一个样本为
的概率为
,
的概率为
。通常,我们认为
为正例,否则为反例。
表示两类可能性的比,即几率(odds),或称为优势比。
如果
,我们认为该样本是
类的几率大于
。
然而,在一个数据集中正负样本比例不相同时,假设在数据集中有
个
样本,
个
样本,存在观测几率为
(样本均衡的情况下观测几率为1)。
在算法分类过程中,如果预测几率
大于实际的观测几率
,此时我们才把样本分类为
。
上式称为“再缩放”,“阈值移动”(threshold-moving),运用该等式在训练好的分类器上进行预测。
重采样(resampling)
过采样(oversampling):加数量较少那一类样本的数量,使得正负样本比例均衡。
欠采样(undersampling):减少数量较多那一类样本的数量,使得正负样本比例均衡。
欠采样(undersampling)
随机欠采样:随机欠采样是指随机从多数类样本中抽取一部分数据进行删除,随机欠采样有一个很大的缺点是未考虑样本的分布情况,而采样过程又具有很大的随机性,可能会误删多数类样本中一些重要的信息。
EasyEnsemble:通过多次从多数类样本有放回的随机抽取一部分样本生成多个子数据集,将每个子集与少数类数据联合起来进行训练生成多个模型,然后集合多个模型的结果进行判断。这种方法看起来和随机森林的原理很相似。
BalanceCascade:通过一次随机欠采样产生训练集,训练一个分类器,对于那些分类正确的多数类样本不放回,然后对这个剩下的多数类样本再次进行欠采样产生第二个训练集,训练第二个分类器,同样把分类正确的样本不放回,以此类推,直到满足某个停止条件,最终的模型也是多个分类器的组合。
过采样(oversampling)
随机过采样:多次随机从少数类样本中有放回的抽取数据,采样数量大于原有的少数类样本数量,其中有一部分数据会出现重复,而重复数据的出现会增大方差造成模型的过拟合。
SMOTE算法(Synthetic Minority Oversampling Technique),合成少数类过采样技术:基于随机过采样算法的一种改进方案,SMOTE算法的基本思想是对少数类样本进行分析并根据少数类样本人工合成新样本添加到数据集中。例如:通过对训练集中的小类数据进行插值来产生额外的小类样本数据。
改变性能评价标准
单一的准确率 accuracy 无法客观衡量模型的性能。在类别不均衡分类任务中,需要使用更有说服力的评价指标来对分类器进行评价。
混淆矩阵(Confusion Matrix):使用一个表格对分类器所预测的类别与其真实的类别的样本统计,分别为:TP、FN、FP与TN。由此计算精确率(precision)、召回率(recall)、F1值(F1 value)。
ROC曲线(ROC Curves)与AUC
尝试不同的分类算法
因为不同的算法适用于不同的任务与数据,应该使用不同的算法进行比较。决策树往往在类别不均衡数据上表现不错。它使用基于类变量的划分规则去创建分类树,因此可以强制地将不同类别的样本分开。目前流行的决策树算法有:C4.5、C5.0、CART和Random Forest等。
对模型进行惩罚
对分类器的小类样本数据增加权值,降低大类样本的权值(这种方法其实是产生了新的数据分布,即产生了新的数据集,译者注),从而使得分类器将重点集中在小类样本身上。
Label Shuffing
运用Label Shuffing,数据均衡。该方式可以使得数据集严格的均等,有不错的效果。
def Label_Shuffing_for_Trian_Val(category_num,train_label_dir,train_output_dir):
dicts={}
f = open(train_label_dir,'r')
lines = f.readlines()
for line in lines:
img_dir = line.split()[0]
label = int(line.split()[1])
dicts[img_dir] = label
dicts = sorted(dicts.items(), key=lambda item: item[1]) # 按照label从小到大排序
f.close()
# 统计每一类label的数目
counts = {}
new_dicts = []
for i in range(category_num):
counts[i] = 0
for line in dicts:
if line[1] > category_num - 1:
continue
line = list(line)
line.append(counts[line[1]])
counts[line[1]] += 1
new_dicts.append(line)
print counts
# 把原列表按照每一类分成各个block并形成新列表
tab = []
origin_index = 0
for i in range(category_num):
block = []
for j in range(counts[i]):
block.append(new_dicts[origin_index])
origin_index += 1
# print block
tab.append(block)
# print tab
nums = [] # 找到数目最多的label类别,从大到校排序
for key in counts:
nums.append(counts[key])
nums.sort(reverse=True)
# print nums
lists = [] # 形成随机label序列
for i in range(nums[0]):
lists.append(i)
# print lists
all_index = []
for i in range(category_num):
random.shuffle(lists)
# print lists
lists_res = [j % counts[i] for j in lists]
all_index.append(lists_res)
# print lists_res
# print all_index[10]
f = open(train_output_dir, 'w')
shuffle_labels = []
index = 0
for line in all_index:
for i in line:
shuffle_labels.append(tab[index][i])
index += 1
# print shuffle_labels
random.shuffle(shuffle_labels)
id = 0
for line in shuffle_labels:
# print line
f.write(line[0]+' '+str(line[1]))
# f.write(str(id) + '\t' + str(line[1]) + '\t' + line[0])
f.write('\n')
id += 1
f.close()