修复不平衡数据集:ADASYN简介(附完整代码)

修复不平衡数据集:ADASYN简介(附完整代码)

介绍

用于分类的任何实际数据集很可能是不平衡的,您感兴趣的事件是极少数(少数例子) ,而非有趣的事件主导机器学习数据集(大多数示例)。因此,我们构建的用于识别罕见情况的机器学习模型将表现得非常糟糕。

一个直观的例子:想象一下对信用卡欺诈进行分类。如果每1,000,000笔交易只有5笔欺诈交易,那么我们的机器学习模型所要做的就是预测所有数据的负类,并且要求模型的准确率为99.9995% !其实无论输入数据是什么,对模型学会“预测负面”基本没用!为了解决这个问题,数据集必须与相似数量的正面和负面实例进行平衡。

解决这个问题的一些传统方法是欠采样和过采样。欠采样是将多数类下采样到与少数类相同数量的数据。但是,这是非常低效的数据!被丢弃的数据有关于负面实例的重要信息。想象一下,建造一个猫分类器,拥有1,000,000张图像,但只有50张猫图像。在对大约50张负片图像进行下采样以获得平衡数据集后,我们删除了所有老虎和狮子的图片。由于老虎和狮子看起来像猫一样,分类器会将它们误认为猫!在过采样中,少数类被复制x次,直到其大小类似于多数类。这里最大的缺陷是我们的机器学习会过度拟合于少数数据,因为同样的实例出现很多次。

修复不平衡数据集:ADASYN简介(附完整代码)

为避免上述所有问题,可以使用ADASYN!ADASYN(Adaptive Synthetic)是一种生成合成数据的算法,并且非常流行,因为该算法为“难以学习”的示例生成了更多数据。它是如何做到的呢?,它是如何工作的呢?在本文中,我还将提供ADASYN算法的每个部分的代码。

可以在此处找到原始论文的链接 https://sci2s.ugr.es/keel/pdf/algorithm/congreso/2008-He-ieee.pdf

ADASYN算法

步骤1

使用比例计算少数与多数实例的比率:

修复不平衡数据集:ADASYN简介(附完整代码)

其中mₛ和mₗ分别是少数和多数类的实例。如果d低于某个阈值,则初始化算法。

修复不平衡数据集:ADASYN简介(附完整代码)

步骤2

计算要生成的合成少数数据的总数。

修复不平衡数据集:ADASYN简介(附完整代码)

这里,G是要生成的少数数据的总数。ß是ADASYN之后所需的少(数数据:多数数据)的比率。ß= 1表示ADASYN之后的完美平衡数据集。

修复不平衡数据集:ADASYN简介(附完整代码)

第3步

找到每个少数实例的k-最近邻,并计算rᵢ值。在此步骤之后,每个少数实例应与不同的邻域相关联。

修复不平衡数据集:ADASYN简介(附完整代码)

rᵢ值表示每个特定邻域中多数类的主导地位。较高的rᵢ邻域包含更多的多数类示例,并且更难学习。有关此步骤的可视化,请参见下文。在该示例中,K = 5(寻找5个最近邻居)。

修复不平衡数据集:ADASYN简介(附完整代码)

修复不平衡数据集:ADASYN简介(附完整代码)

第4步

归一化rᵢ值,使所有rᵢ值之和等于1。

修复不平衡数据集:ADASYN简介(附完整代码)

这一步主要是简化步骤5的一个前奏。

修复不平衡数据集:ADASYN简介(附完整代码)

第5步

计算每个邻域生成的合成实例的数量。

修复不平衡数据集:ADASYN简介(附完整代码)

因为对于由多数类实例主导的邻域,rᵢ更高,所以将为这些邻域生成更多的合成少数类实例。

修复不平衡数据集:ADASYN简介(附完整代码)

第6步

为每个邻域生成Gᵢ数据。首先,少数数据x的邻域为例。然后,随机选择该邻域中的另一个少数实例,xzᵢ。可以使用以下公式计算新的合成实例:

修复不平衡数据集:ADASYN简介(附完整代码)

在上面的等式中,λ是0-1之间的随机数,sᵢ是新的合成实例,xᵢ和xzᵢ是同一邻域内的两个少数实例。下面提供该步骤的可视化。直观地,基于xᵢ和xzᵢ的线性组合创建合成实例。

修复不平衡数据集:ADASYN简介(附完整代码)

可以在合成实例中添加白噪声,以使新数据更加真实。而且,代替线性插值,可以在3个少数实例之间绘制平面,并且可以在平面上生成点。

修复不平衡数据集:ADASYN简介(附完整代码)

Python完整示例代码

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import neighbors
seed = 10
np.random.seed(seed)
class MinMaxNormalization:
 """
 Min-Max Normalization. Was using in conjunction of ADASYN to test results
 data: Data to be normalized
 axis: 0 is by columns, 1 is by rows
 returns: Normalized data
 """
 def __init__(self, data, axis=0):
 self.row_min = np.min(data, axis=axis)
 self.row_max = np.max(data, axis=axis)
 self.denominator = abs(self.row_max - self.row_min)
 # Fix divide by zero, replace value with 1 because these usually happen for boolean columns
 for index, value in enumerate(self.denominator):
 if value == 0:
 self.denominator[index] = 1
 def __call__(self, data):
 return np.divide((data - self.row_min), self.denominator)
def adasyn(X, y, beta, K, threshold=1):
 """
 Adaptively generating minority data samples according to their distributions.
 More synthetic data is generated for minority class samples that are harder to learn.
 Harder to learn data is defined as positive examples with not many examples for in their respective neighbourhood.
 Inputs
 -----
 X: Input features, X, sorted by the minority examples on top. Minority example should also be labeled as 1
 y: Labels, with minority example labeled as 1
 beta: Degree of imbalance desired. A 1 means the positive and negative examples are perfectly balanced.
 K: Amount of neighbours to look at
 threshold: Amount of imbalance rebalance required for algorithm
 Variables
 -----
 xi: Minority example
 xzi: A minority example inside the neighbourhood of xi
 ms: Amount of data in minority class
 ml: Amount of data in majority class
 clf: k-NN classifier model
 d: Ratio of minority : majority
 beta: Degree of imbalance desired
 G: Amount of data to generate
 Ri: Ratio of majority data / neighbourhood size. Larger ratio means the neighbourhood is harder to learn,
 thus generating more data.
 Minority_per_xi: All the minority data's index by neighbourhood
 Rhat_i: Normalized Ri, where sum = 1
 Gi: Amount of data to generate per neighbourhood (indexed by neighbourhoods corresponding to xi)
 Returns
 -----
 syn_data: New synthetic minority data created
 """
 ms = int(sum(y))
 ml = len(y) - ms
 clf = neighbors.KNeighborsClassifier()
 clf.fit(X, y)
 # Step 1, calculate the degree of class imbalance. If degree of class imbalance is violated, continue.
 d = np.divide(ms, ml)
 if d > threshold:
 return print("The data set is not imbalanced enough.")
 # Step 2a, if the minority data set is below the maximum tolerated threshold, generate data.
 # Beta is the desired balance level parameter. Beta > 1 means u want more of the imbalanced type, vice versa.
 G = (ml - ms) * beta
 # Step 2b, find the K nearest neighbours of each minority class example in euclidean distance.
 # Find the ratio ri = majority_class in neighbourhood / K
 Ri = []
 Minority_per_xi = []
 for i in range(ms):
 xi = X[i, :].reshape(1, -1)
 # Returns indices of the closest neighbours, and return it as a list
 neighbours = clf.kneighbors(xi, n_neighbors=K, return_distance=False)[0]
 # Skip classifying itself as one of its own neighbours
 # neighbours = neighbours[1:]
 # Count how many belongs to the majority class
 count = 0
 for value in neighbours:
 if value > ms:
 count += 1
 Ri.append(count / K)
 # Find all the minority examples
 minority = []
 for value in neighbours:
 if value <= ms:
 minority.append(value)
 Minority_per_xi.append(minority)
 # Step 2c, normalize ri's so their sum equals to 1
 Rhat_i = []
 for ri in Ri:
 rhat_i = ri / sum(Ri)
 Rhat_i.append(rhat_i)
 assert (sum(Rhat_i) > 0.99)
 # Step 2d, calculate the number of synthetic data examples that will be generated for each minority example
 Gi = []
 for rhat_i in Rhat_i:
 gi = round(rhat_i * G)
 Gi.append(int(gi))
 # # Step 2e, generate synthetic examples
 syn_data = []
 for i in range(ms):
 xi = X[i, :].reshape(1, -1)
 for j in range(Gi[i]):
 # If the minority list is not empty
 if Minority_per_xi[i]:
 index = np.random.choice(Minority_per_xi[i])
 xzi = X[index, :].reshape(1, -1)
 si = xi + (xzi - xi) * np.random.uniform(0, 1)
 syn_data.append(si)
 # Test the new generated data
 test = []
 for values in syn_data:
 a = clf.predict(values)
 test.append(a)
 print("Using the old classifier, {} out of {} would be classified as minority.".format(np.sum(test), len(syn_data)))
 # Build the data matrix
 data = []
 for values in syn_data:
 data.append(values[0])
 print("{} amount of minority class samples generated".format(len(data)))
 # Concatenate the positive labels with the newly made data
 labels = np.ones([len(data), 1])
 data = np.concatenate([labels, data], axis=1)
 # Concatenate with old data
 org_data = np.concatenate([y.reshape(-1, 1), X], axis=1)
 data = np.concatenate([data, org_data])
 return data, Minority_per_xi, Ri
if __name__ == "__main__":
 path = '/home/rui/Documents/logistic_regression_tf/'
 df = pd.read_csv(path + 'data/10_data_plc_1500.csv')
 df.reset_index(drop=True, inplace=True)
 X = df.drop(df.columns[0], axis=1).values
 X = X.astype('float32')
 y = df.iloc[:, 0].values
 Syn_data, neighbourhoods, Ri = adasyn(X, y, beta=0.08, K=800, threshold=1)
 np.savetxt(path + 'data/syn_10_data_plc_1500.csv', Syn_data, delimiter=',')

修复不平衡数据集:ADASYN简介(附完整代码)

修复不平衡数据集:ADASYN简介(附完整代码)

修复不平衡数据集:ADASYN简介(附完整代码)

ADASYN的弱点

ADASYN有两个主要缺点:

  • 对于稀疏分布的少数实例,每个邻域可能只包含1个少数实例。
  • ADASYN的精度可能因适应性而受损。

为了解决第一个问题,少数实例的邻域可以将其值重复Gi次,或者我们可以简单地忽略为这样的邻域生成合成数据。也可以增加邻域大小。

出现第二个问题是因为在具有大量多数类实例的邻域中生成了更多数据。因此,合成数据将类似于多数类数据,可能产生许多误报。一种解决方案是将Gi限制为最大量,因此不会为邻域做出太多实例。

结论

这样就包含了ADASYN算法。ADASYN的最大优势在于它为“难以学习”的实例创建更多数据的自适应性,并允许您对机器学习模型进行更多负样本采样,因为您最终可以综合平衡数据。

相关推荐