机器学习中欠采样+ Logistic回归—不平衡的数据

机器学习中欠采样+ Logistic回归—不平衡的数据 - 欺诈检测

为了打击欺诈,我们必须首先检测它。在发现欺诈行为时,您必须考虑:

  • 如果你试图找出所有的欺诈案件,其中一些案件将被错误的贴上标签。这将导致无辜的人被指控犯有欺诈罪。
  • 如果你试图让无辜的人免受指控,你就会把一些欺诈者误认为是无辜的。在这种情况下,公司将损失更多的钱。

您的欺诈检测算法不完善是不可避免的。

我们来看看这些数据。

它是货币交易的数据集。它给出了发送者的ID,接收者的ID,转移的金额,以及交易前后的发送者和接收者的余额。它还告诉我们哪些样本是欺诈,哪些不是。它是生成的数据集。公司不希望你知道他们损失了多少钱,所以我们只能这么做。

让我们加载数据集并查看它的样子:

import pandas as pd 
cols = ['step', 'type', 'amount', 'nameOrig', 'oldbalanceOrg', 'newbalanceOrig',
'nameDest', 'oldbalanceDest', 'newbalanceDest', 'isFraud', 'isFlaggedFraud']
df = pd.read_csv('PS_20174392719_1491204439457_log.csv', header = 0, names = cols)
print('df.shape:', df.shape)

df.head()
机器学习中欠采样+ Logistic回归—不平衡的数据 - 欺诈检测

机器学习中欠采样+ Logistic回归—不平衡的数据 - 欺诈检测

我们可以将表拆分成不同的集合。每组都具有所有特征,但不是所有的观察结果。

对于训练集,我们可以使用isFraud列值来训练我们的模型。在测试集上应用模型将为我们提供每个观察的预测isFraud值。

一种简单的方法可能是尝试:

pd.value_counts(df.isFraud, normalize = True)
机器学习中欠采样+ Logistic回归—不平衡的数据 - 欺诈检测

机器学习中欠采样+ Logistic回归—不平衡的数据 - 欺诈检测

我们可以这样做的另一种方法是使用isFraud列的模式:

import numpy as np
from sklearn.metrics import accuracy_score
majority_class = df.isFraud.mode()[0]
y_pred = np.full(shape = df.isFraud.shape, fill_value = majority_class)
accuracy_score(df.isFraud, y_pred)
机器学习中欠采样+ Logistic回归—不平衡的数据 - 欺诈检测

这给我们的准确度得分值为0.998709,与value_counts()相同 - 这是预期的。isFraud=0的值要比isFraud=1的值多得多。我们可以从上面的value_counts()或下面输出的分类报告中看到这一点:

from sklearn.metrics import classification_report
print(classification_report(df.isFraud, y_pred))
机器学习中欠采样+ Logistic回归—不平衡的数据 - 欺诈检测

机器学习中欠采样+ Logistic回归—不平衡的数据 - 欺诈检测

我们可以看到我们获得了完美的召回率和精确度,但support值告诉我们另一件事情。我们有6354407个值支持isFraud = 0的情况,而8213个值支持isFraud = 1。

让我们来看看ROC曲线的AUC评分,而不是准确性。该分数衡量我们的模型区分类的能力。

from sklearn.metrics import roc_auc_score
roc_auc_score(df.isFraud, y_pred)
机器学习中欠采样+ Logistic回归—不平衡的数据 - 欺诈检测

0.5 

这给了我们0.5的值。ROC AUC评分的值为1.0,是任何人使用任何模型都能得到的最佳值。为什么我们得到0.5?这是因为我们可以完美地预测所有isFraud = 0的情况,但是没有一个isFraud = 1的情况。所以在这两个类中,我们只能预测1(这给我们的ROC AUC为0.5)。

为了为我们的模型提供公平的竞争环境,我们可以对欺诈交易进行过度抽样,也可以对干净的交易进行抽样。我们可以使用imbalance-learn库来完成这项工作。

from imblearn.under_sampling import RandomUnderSampler
X = df.drop(['isFraud', 'type', 'nameOrig', 'nameDest'], axis = 1)
y = df.isFraud
rus = RandomUnderSampler(sampling_strategy=0.8)
X_res, y_res = rus.fit_resample(X, y)
print(X_res.shape, y_res.shape)
print(pd.value_counts(y_res))
机器学习中欠采样+ Logistic回归—不平衡的数据 - 欺诈检测

我们将随机下采样的sampling_strategy设置为0.8。这只是为了说明我们这样做时会发生什么。它允许我们指定少数类样本与多数类样本的比率。它为我们提供了18479行数据,其值计数如下:

(18479, 7) (18479,)
0 10266
1 8213
dtype: int64

让我们看看我们的表在重采样和删除这些列之后是什么样子的:

cols_numeric = ['step', 'amount', 'oldbalanceOrg', 'newbalanceOrig',
'oldbalanceDest', 'newbalanceDest', 'isFlaggedFraud']
df_rus = pd.DataFrame(X_res, columns = cols_numeric)
df_rus.head()
机器学习中欠采样+ Logistic回归—不平衡的数据 - 欺诈检测

机器学习中欠采样+ Logistic回归—不平衡的数据 - 欺诈检测

现在让我们将数据集分成3个部分 - 训练,验证和测试数据集。验证数据集我们可以反复使用不同的模型。一旦我们认为我们有最好的模型,我们将使用我们的测试数据集。

我们这样做的原因是,我们的模型不仅应该用部分训练数据集为我们提供良好的结果,而且还应该用我们从未见过的数据提供良好的结果。通过将测试数据集只使用一次,我们强迫自己不要过度使用验证数据集。

trainize / valsize / testsize显示了应保留用于训练/验证/测试的总数据集的分数。

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
def train_validation_test_split(
X, y, train_size=0.8, val_size=0.1, test_size=0.1,
random_state=None, shuffle=True):
assert int(train_size + val_size + test_size + 1e-7) == 1
X_train_val, X_test, y_train_val, y_test = train_test_split(
X, y, test_size=test_size, random_state=random_state, shuffle=shuffle)
X_train, X_val, y_train, y_val = train_test_split(
X_train_val, y_train_val, test_size=val_size/(train_size+val_size),
random_state=random_state, shuffle=shuffle)
return X_train, X_val, X_test, y_train, y_val, y_test
X_train, X_val, X_test, y_train, y_val, y_test = train_validation_test_split(
X_res, y_res, train_size=0.8, val_size=0.1, test_size=0.1, random_state=1)
class_weight = {0: 4, 1: 5}
model = LogisticRegression(class_weight=class_weight)
model.fit(X_train, y_train)
y_pred = model.predict(X_val)
print(classification_report(y_val, y_pred))
print('accuracy', accuracy_score(y_val, y_pred))
roc_auc_score(y_val, y_pred)
机器学习中欠采样+ Logistic回归—不平衡的数据 - 欺诈检测

注意class_weight参数。我们把它放在那里是因为isFraud = 0的欠采样行数为10000行,isFraud = 1的欠采样行数为8000行。我们想要权衡它们以使它们平衡。这样做的比例是4:5,这是这里使用的类权重。

如果我们在没有设置sampling_strategy = 0.8的情况下进行了欠采样,那么我们就会有平衡的类,并且不需要class_weight参数。如果我们得到一个新的数据集,它的参数稍微不平衡,我们可以使用带有类权重的逻辑回归来平衡它,而不需要重新采样。

机器学习中欠采样+ Logistic回归—不平衡的数据 - 欺诈检测

现在我们得到了0.90的准确度 - 这是一个很好的分数。我们的ROC AUC评分也是0.9。

现在让我们在测试数据集上尝试我们的模型:

y_pred = model.predict(X_test)
print(classification_report(y_test, y_pred))
print('Accuracy', accuracy_score(y_test, y_pred))
print('ROC AUC score:', roc_auc_score(y_test, y_pred))
机器学习中欠采样+ Logistic回归—不平衡的数据 - 欺诈检测

机器学习中欠采样+ Logistic回归—不平衡的数据 - 欺诈检测

再次得到0.90。看起来RandomUnderSampler做得很好。

我们必须将模型应用于完整的(未采样的)数据集。我们接下来就这样做:

y_pred = model.predict(X)
print(classification_report(y, y_pred))
print('Accuracy:', accuracy_score(y, y_pred))
print('ROC AUC score:', roc_auc_score(y, y_pred))
机器学习中欠采样+ Logistic回归—不平衡的数据 - 欺诈检测

机器学习中欠采样+ Logistic回归—不平衡的数据 - 欺诈检测

准确率和ROC AUC评分都非常好,isFraud = 0的精度/召回率/f1-score也是如此。问题是isFraud = 1的精度非常低,为0.01。由于f1-score是精确度和召回率的加权平均值,因此它也低至0.02。也许我们这里没有足够的数据来处理Logistic回归。或者我们应该进行过采样而不是欠采样。


分享到:


相關文章: