如何识别罕见情况的机器学习数据集(大多数示例)

介绍

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

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

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

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

可以在此处找到原始论文的链接

ADASYN 算法

步骤1

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

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

第2步

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

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

第 3 步

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

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

步骤4

图片[1]-如何识别罕见情况的机器学习数据集(大多数示例)-老王博客

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

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

第 5 步

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

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

第 6 步

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

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

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

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 有两个主要缺点:

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

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

综上所述

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

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论