案例系列:银行信用卡欺诈_不均衡数据二分类
介绍
我们将使用各种预测模型来查看它们在检测交易是正常支付还是欺诈方面的准确性。正如数据集中所描述的那样,由于隐私原因,特征已经进行了缩放,并且特征的名称没有显示。尽管如此,我们仍然可以分析数据集的一些重要方面。让我们开始吧!我们的目标:
- 了解我们所提供的“小”数据的分布情况。
- 创建一个“欺诈”和“非欺诈”交易的50/50子数据框比例。(NearMiss算法)
- 确定我们将使用的分类器,并决定哪个分类器的准确性更高。
- 创建一个神经网络,并将其准确性与我们最好的分类器进行比较。
- 了解在不平衡数据集中常见的错误。
大纲:
I. 了解我们的数据a) 收集数据的感知
III. 随机欠采样和过采样
a) 分布和相关性
b) 异常检测
c) 降维和聚类(t-SNE)
d) 分类器
e) 深入研究逻辑回归
f) 使用SMOTE进行过采样
IV. 测试
a) 使用逻辑回归进行测试
b) 神经网络测试(欠采样vs过采样)
纠正不平衡数据集的先前错误:
- 不要在过采样或欠采样的数据集上进行测试。
- 如果我们想要实施交叉验证,请记住在交叉验证期间对训练数据进行过采样或欠采样,而不是之前!
- 不要使用准确率得分作为不平衡数据集的度量标准(通常会很高并且具有误导性),而是使用f1得分,精确度/召回率得分或混淆矩阵
收集数据的感知:
我们首先必须对数据进行基本感知。请记住,除了交易和金额之外,我们不知道其他列是什么(出于隐私原因)。我们唯一知道的是,那些未知的列已经被缩放过了。
概要:
- 交易金额相对较小。所有交易的平均金额约为88美元。
- 没有“Null”值,所以我们不需要处理替换值的方法。
- 大多数交易(99.83%)是非欺诈交易,而欺诈交易发生在数据框中的0.17%的时间内。
特征技术细节:
- PCA转换:数据的描述说所有特征都经过了PCA转换(降维技术)(除了时间和金额)。
- 缩放:请记住,为了实施PCA转换,特征需要事先进行缩放。(在这种情况下,我们假设开发数据集的人员对所有V特征进行了缩放。)
# 导入所需的库
import numpy as np # 线性代数运算
import pandas as pd # 数据处理,CSV文件的输入输出
import tensorflow as tf
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.manifold import TSNE
from sklearn.decomposition import PCA, TruncatedSVD
import matplotlib.patches as mpatches
import time
# 分类器库
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
import collections
# 其他库
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from imblearn.pipeline import make_pipeline as imbalanced_make_pipeline
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import NearMiss
from imblearn.metrics import classification_report_imbalanced
from sklearn.metrics import precision_score, recall_score, f1_score, roc_auc_score, accuracy_score, classification_report
from collections import Counter
from sklearn.model_selection import KFold, StratifiedKFold
import warnings
warnings.filterwarnings("ignore")
# 读取数据
df = pd.read_csv('../input/creditcard.csv')
df.head()
/opt/conda/lib/python3.6/site-packages/sklearn/externals/six.py:31: DeprecationWarning: The module is deprecated in version 0.21 and will be removed in version 0.23 since we've dropped support for Python 2.7. Please rely on the official version of six (https://pypi.org/project/six/).
"(https://pypi.org/project/six/).", DeprecationWarning)
# 对DataFrame进行描述性统计分析
df.describe()
# 检查数据框df中是否存在缺失值
# 使用isnull()函数返回一个布尔值的数据框,其中缺失值为True,非缺失值为False
# 然后使用sum()函数计算每列中缺失值的数量
# 最后使用max()函数找出所有列中缺失值数量的最大值
# 如果最大值为0,则表示数据框中没有缺失值
df.isnull().sum().max()
0
df.columns
Index(['Time', 'V1', 'V2', 'V3', 'V4', 'V5', 'V6', 'V7', 'V8', 'V9', 'V10',
'V11', 'V12', 'V13', 'V14', 'V15', 'V16', 'V17', 'V18', 'V19', 'V20',
'V21', 'V22', 'V23', 'V24', 'V25', 'V26', 'V27', 'V28', 'Amount',
'Class'],
dtype='object')
# 打印出数据集中非欺诈交易的比例
print('非欺诈交易占数据集的比例为', round(df['Class'].value_counts()[0]/len(df) * 100,2), '%')
# 打印出数据集中欺诈交易的比例
print('欺诈交易占数据集的比例为', round(df['Class'].value_counts()[1]/len(df) * 100,2), '%')
No Frauds 99.83 % of the dataset
Frauds 0.17 % of the dataset
注意: 请注意我们原始数据集的不平衡!大部分交易都是非欺诈交易。如果我们将这个数据框作为我们预测模型和分析的基础,我们可能会得到很多错误,并且我们的算法很可能会过拟合,因为它会“假设”大部分交易都不是欺诈交易。但是我们不希望我们的模型做出假设,我们希望我们的模型能够检测到表明欺诈的模式!
# 导入必要的库
import seaborn as sns
import matplotlib.pyplot as plt
# 定义颜色列表,用于绘制图表时不同类别的颜色
colors = ["#0101DF", "#DF0101"]
# 使用sns库的countplot函数绘制柱状图,统计不同类别的数量
# 'Class'是要统计的列名,data=df表示数据来源是df数据框,palette=colors表示使用定义的颜色列表
sns.countplot('Class', data=df, palette=colors)
# 设置图表标题
plt.title('Class Distributions \n (0: No Fraud || 1: Fraud)', fontsize=14)
Text(0.5, 1.0, 'Class Distributions \n (0: No Fraud || 1: Fraud)')
分布: 通过观察分布,我们可以了解这些特征的偏斜程度,还可以进一步观察其他特征的分布情况。有一些技术可以帮助减少分布的偏斜,在将来的笔记本中将会实施这些技术。
# 创建一个包含两个子图的图像,并设置图像大小为(18, 4)
fig, ax = plt.subplots(1, 2, figsize=(18,4))
# 获取数据集中"Amount"列的值
amount_val = df['Amount'].values
# 获取数据集中"Time"列的值
time_val = df['Time'].values
# 在第一个子图上绘制"Amount"列的分布图,并设置颜色为红色
sns.distplot(amount_val, ax=ax[0], color='r')
# 设置第一个子图的标题为"交易金额分布",字体大小为14
ax[0].set_title('Distribution of Transaction Amount', fontsize=14)
# 设置第一个子图的x轴范围为数据集中"Amount"列的最小值和最大值
ax[0].set_xlim([min(amount_val), max(amount_val)])
# 在第二个子图上绘制"Time"列的分布图,并设置颜色为蓝色
sns.distplot(time_val, ax=ax[1], color='b')
# 设置第二个子图的标题为"交易时间分布",字体大小为14
ax[1].set_title('Distribution of Transaction Time', fontsize=14)
# 设置第二个子图的x轴范围为数据集中"Time"列的最小值和最大值
ax[1].set_xlim([min(time_val), max(time_val)])
# 显示图像
plt.show()
缩放和分布
在我们的内核的这个阶段,我们首先要缩放由时间和金额组成的列。时间和金额应该与其他列一样进行缩放。另一方面,我们还需要创建一个子样本的数据框,以便具有相等数量的欺诈和非欺诈案例,帮助我们的算法更好地理解确定交易是否为欺诈的模式。
什么是子样本?
在这种情况下,我们的子样本将是一个具有50/50欺诈和非欺诈交易比例的数据框。这意味着我们的子样本将具有相同数量的欺诈和非欺诈交易。
为什么要创建子样本?
在这个笔记本的开头,我们看到原始数据框的不平衡非常严重!使用原始数据框将导致以下问题:
- 过拟合:我们的分类模型将假设在大多数情况下没有欺诈!我们希望我们的模型在发生欺诈时能够确定。
- 错误的相关性:虽然我们不知道“V”特征代表什么,但了解每个特征如何影响结果(欺诈或非欺诈)将是有用的。通过使用不平衡的数据框,我们无法看到类别和特征之间的真实相关性。
总结:
- 缩放后的金额和时间是具有缩放值的列。
- 在我们的数据集中有492个欺诈案例,所以我们可以随机选择492个非欺诈案例来创建我们的新子数据框。
- 我们将492个欺诈案例和非欺诈案例连接起来,创建一个新的子样本。
# 导入所需的库
from sklearn.preprocessing import StandardScaler, RobustScaler
# 创建StandardScaler和RobustScaler对象
std_scaler = StandardScaler() # 标准缩放器,用于对数据进行标准化处理
rob_scaler = RobustScaler() # 鲁棒缩放器,对数据进行鲁棒性缩放处理,对异常值不敏感
# 使用RobustScaler对'Amount'列进行缩放处理,并将结果保存到新的'scaled_amount'列中
df['scaled_amount'] = rob_scaler.fit_transform(df['Amount'].values.reshape(-1,1))
# 使用RobustScaler对'Time'列进行缩放处理,并将结果保存到新的'scaled_time'列中
df['scaled_time'] = rob_scaler.fit_transform(df['Time'].values.reshape(-1,1))
# 删除原始的'Time'和'Amount'列
df.drop(['Time','Amount'], axis=1, inplace=True)
# 从数据框中提取'scaled_amount'和'scaled_time'列,并赋值给变量scaled_amount和scaled_time
scaled_amount = df['scaled_amount']
scaled_time = df['scaled_time']
# 从数据框中删除'scaled_amount'和'scaled_time'列,并在原地修改数据框
df.drop(['scaled_amount', 'scaled_time'], axis=1, inplace=True)
# 在数据框的第一列插入'scaled_amount'列,并将之前保存的scaled_amount赋值给该列
df.insert(0, 'scaled_amount', scaled_amount)
# 在数据框的第二列插入'scaled_time'列,并将之前保存的scaled_time赋值给该列
df.insert(1, 'scaled_time', scaled_time)
# 'Amount'和'Time'已经被缩放!
# 显示数据框的前几行
df.head()
分割数据(原始数据框)
在进行随机欠采样技术之前,我们需要将原始数据框分开。为什么呢?出于测试目的,尽管在实施随机欠采样或过采样技术时我们会分割数据,但我们希望在原始测试集上测试我们的模型,而不是在这些技术创建的测试集上进行测试。主要目标是使用欠采样和过采样的数据框来拟合模型(以便我们的模型能够检测到模式),并在原始测试集上进行测试。
# 导入必要的库
from sklearn.model_selection import train_test_split
from sklearn.model_selection import StratifiedShuffleSplit
# 输出正常交易和欺诈交易在数据集中的比例
print('No Frauds', round(df['Class'].value_counts()[0]/len(df) * 100,2), '% of the dataset')
print('Frauds', round(df['Class'].value_counts()[1]/len(df) * 100,2), '% of the dataset')
# 将特征和标签分离
X = df.drop('Class', axis=1)
y = df['Class']
# 使用分层K折交叉验证将数据集分为训练集和测试集
sss = StratifiedKFold(n_splits=5, random_state=None, shuffle=False)
# 遍历每个分层K折交叉验证的训练集和测试集
for train_index, test_index in sss.split(X, y):
print("Train:", train_index, "Test:", test_index)
original_Xtrain, original_Xtest = X.iloc[train_index], X.iloc[test_index]
original_ytrain, original_ytest = y.iloc[train_index], y.iloc[test_index]
# 我们已经有了下采样数据的X_train和y_train,所以我使用original来区分并避免覆盖这些变量。
# original_Xtrain, original_Xtest, original_ytrain, original_ytest = train_test_split(X, y, test_size=0.2, random_state=42)
# 检查标签的分布情况
# 转换为数组
original_Xtrain = original_Xtrain.values
original_Xtest = original_Xtest.values
original_ytrain = original_ytrain.values
original_ytest = original_ytest.values
# 检查训练集和测试集标签分布是否相似
train_unique_label, train_counts_label = np.unique(original_ytrain, return_counts=True)
test_unique_label, test_counts_label = np.unique(original_ytest, return_counts=True)
print('-' * 100)
print('Label Distributions: \n')
print(train_counts_label/ len(original_ytrain))
print(test_counts_label/ len(original_ytest))
No Frauds 99.83 % of the dataset
Frauds 0.17 % of the dataset
Train: [ 30473 30496 31002 ... 284804 284805 284806] Test: [ 0 1 2 ... 57017 57018 57019]
Train: [ 0 1 2 ... 284804 284805 284806] Test: [ 30473 30496 31002 ... 113964 113965 113966]
Train: [ 0 1 2 ... 284804 284805 284806] Test: [ 81609 82400 83053 ... 170946 170947 170948]
Train: [ 0 1 2 ... 284804 284805 284806] Test: [150654 150660 150661 ... 227866 227867 227868]
Train: [ 0 1 2 ... 227866 227867 227868] Test: [212516 212644 213092 ... 284804 284805 284806]
----------------------------------------------------------------------------------------------------
Label Distributions:
[0.99827076 0.00172924]
[0.99827952 0.00172048]
随机欠采样:
在项目的这个阶段,我们将实施“随机欠采样”,它基本上是通过删除数据来使数据集更加平衡,从而避免我们的模型过拟合。
步骤:
- 首先,我们需要确定我们的类别是多么不平衡(在类别列上使用“value_counts()”来确定每个标签的数量)
- 一旦确定了有多少个“欺诈交易”实例(欺诈 = "1"),我们应该将“非欺诈交易”调整为与欺诈交易数量相同(假设我们想要50/50的比例),这将相当于492个欺诈案例和492个非欺诈交易案例。
- 实施这种技术后,我们的数据框将有一个与我们的类别相比为50/50的子样本。然后,我们将实施的下一步是对数据进行洗牌,以查看我们的模型是否能够在每次运行此脚本时保持一定的准确性。
注意:“随机欠采样”的主要问题是我们面临的风险是我们的分类模型的准确性可能不如我们希望的那样高,因为存在大量的信息损失(从284,315个非欺诈交易中带来492个非欺诈交易)。
# 由于我们的类别分布不平衡,我们需要使它们等价,以获得类别的正常分布。
# 在创建子样本之前,让我们对数据进行洗牌
df = df.sample(frac=1)
# 欺诈类别的数量为492行。
fraud_df = df.loc[df['Class'] == 1]
non_fraud_df = df.loc[df['Class'] == 0][:492]
# 将欺诈类别和非欺诈类别合并为一个新的数据集
normal_distributed_df = pd.concat([fraud_df, non_fraud_df])
# 对数据集的行进行洗牌
new_df = normal_distributed_df.sample(frac=1, random_state=42)
# 打印新数据集的前几行
new_df.head()
平均分配和相关性:
现在我们的数据框已经正确平衡,我们可以进一步进行分析和数据预处理。
# 打印子样本数据集中各类别的分布情况
print('Distribution of the Classes in the subsample dataset')
# 计算各类别在子样本数据集中的比例并打印
print(new_df['Class'].value_counts()/len(new_df))
# 使用sns库的countplot函数绘制各类别的计数柱状图
# 数据来源为new_df,颜色使用预设的调色板colors
sns.countplot('Class', data=new_df, palette=colors)
# 设置图表标题为"Equally Distributed Classes",字体大小为14
plt.title('Equally Distributed Classes', fontsize=14)
# 显示图表
plt.show()
Distribution of the Classes in the subsample dataset
1 0.5
0 0.5
Name: Class, dtype: float64
相关矩阵
相关矩阵是理解我们的数据的精髓。我们想知道是否有一些特征对于特定交易是否是欺诈有很大影响。然而,重要的是我们使用正确的数据框(子样本),以便我们能够看到哪些特征与欺诈交易相关性较高或较低。摘要和解释:
- 负相关: V17、V14、V12和V10呈负相关。请注意,这些值越低,最终结果越可能是欺诈交易。
- 正相关: V2、V4、V11和V19呈正相关。请注意,这些值越高,最终结果越可能是欺诈交易。
- 箱线图:我们将使用箱线图来更好地了解这些特征在欺诈和非欺诈交易中的分布。
**注意:**我们必须确保在我们的相关矩阵中使用子样本,否则我们的相关矩阵将受到类别之间严重不平衡的影响。这是由于原始数据框中存在严重的类别不平衡。
# 创建一个2行1列的图表,并设置图表的大小为24x20
f, (ax1, ax2) = plt.subplots(2, 1, figsize=(24,20))
# 计算整个DataFrame的相关性
corr = df.corr()
# 绘制热力图,使用'coolwarm_r'颜色映射,设置注释的字体大小为20,将图表放在ax1上
sns.heatmap(corr, cmap='coolwarm_r', annot_kws={'size':20}, ax=ax1)
# 设置ax1的标题为"Imbalanced Correlation Matrix \n (don't use for reference)",字体大小为14
ax1.set_title("Imbalanced Correlation Matrix \n (don't use for reference)", fontsize=14)
# 计算子样本DataFrame的相关性
sub_sample_corr = new_df.corr()
# 绘制热力图,使用'coolwarm_r'颜色映射,设置注释的字体大小为20,将图表放在ax2上
sns.heatmap(sub_sample_corr, cmap='coolwarm_r', annot_kws={'size':20}, ax=ax2)
# 设置ax2的标题为"SubSample Correlation Matrix \n (use for reference)",字体大小为14
ax2.set_title('SubSample Correlation Matrix \n (use for reference)', fontsize=14)
# 显示图表
plt.show()
# 创建一个包含4个子图的画布,每个子图的大小为(20,4)
f, axes = plt.subplots(ncols=4, figsize=(20,4))
# 在第一个子图中绘制箱线图,x轴为"Class",y轴为"V17",数据来源为new_df,颜色使用自定义的调色板colors,绘图对象为axes[0]
sns.boxplot(x="Class", y="V17", data=new_df, palette=colors, ax=axes[0])
# 设置第一个子图的标题为'V17 vs Class Negative Correlation'
axes[0].set_title('V17 vs Class Negative Correlation')
# 在第二个子图中绘制箱线图,x轴为"Class",y轴为"V14",数据来源为new_df,颜色使用自定义的调色板colors,绘图对象为axes[1]
sns.boxplot(x="Class", y="V14", data=new_df, palette=colors, ax=axes[1])
# 设置第二个子图的标题为'V14 vs Class Negative Correlation'
axes[1].set_title('V14 vs Class Negative Correlation')
# 在第三个子图中绘制箱线图,x轴为"Class",y轴为"V12",数据来源为new_df,颜色使用自定义的调色板colors,绘图对象为axes[2]
sns.boxplot(x="Class", y="V12", data=new_df, palette=colors, ax=axes[2])
# 设置第三个子图的标题为'V12 vs Class Negative Correlation'
axes[2].set_title('V12 vs Class Negative Correlation')
# 在第四个子图中绘制箱线图,x轴为"Class",y轴为"V10",数据来源为new_df,颜色使用自定义的调色板colors,绘图对象为axes[3]
sns.boxplot(x="Class", y="V10", data=new_df, palette=colors, ax=axes[3])
# 设置第四个子图的标题为'V10 vs Class Negative Correlation'
axes[3].set_title('V10 vs Class Negative Correlation')
# 显示画布
plt.show()
# 创建一个包含4个子图的画布,每个子图的大小为(20,4)
f, axes = plt.subplots(ncols=4, figsize=(20,4))
# 第一个子图:V11与Class之间的正相关关系
sns.boxplot(x="Class", y="V11", data=new_df, palette=colors, ax=axes[0])
axes[0].set_title('V11 vs Class Positive Correlation')
# 第二个子图:V4与Class之间的正相关关系
sns.boxplot(x="Class", y="V4", data=new_df, palette=colors, ax=axes[1])
axes[1].set_title('V4 vs Class Positive Correlation')
# 第三个子图:V2与Class之间的正相关关系
sns.boxplot(x="Class", y="V2", data=new_df, palette=colors, ax=axes[2])
axes[2].set_title('V2 vs Class Positive Correlation')
# 第四个子图:V19与Class之间的正相关关系
sns.boxplot(x="Class", y="V19", data=new_df, palette=colors, ax=axes[3])
axes[3].set_title('V19 vs Class Positive Correlation')
# 显示图形
plt.show()
异常检测:
本节的主要目标是从与我们的类别高度相关的特征中删除“极端异常值”。这将对我们模型的准确性产生积极影响。
四分位数范围法:
- 四分位数范围(IQR): 我们通过计算第75个百分位数和第25个百分位数之间的差值来计算。我们的目标是创建一个超出第75个和第25个百分位数的阈值,以防某些实例通过此阈值,实例将被删除。
- 箱线图: 除了可以轻松看到第25个和第75个百分位数(方框的两端),还可以轻松看到极端异常值(超出下限和上限的点)。
异常值删除的权衡:
我们必须小心,我们希望删除异常值的阈值有多远。我们通过将一个数字(例如:1.5)乘以(四分位数范围)来确定阈值。阈值越高,检测到的异常值越少(乘以较高的数字,例如:3),阈值越低,检测到的异常值越多。
权衡:
阈值越低,删除的异常值越多,但我们更希望关注“极端异常值”而不仅仅是异常值。为什么?因为我们可能会面临信息丢失的风险,这会导致我们的模型准确性降低。您可以调整此阈值,看看它如何影响我们分类模型的准确性。
总结:
- 可视化分布: 我们首先通过可视化要用来消除一些异常值的特征的分布来开始。与特征V12和V10相比,V14是唯一具有高斯分布的特征。
- 确定阈值: 在我们决定要使用哪个数字与iqr相乘(较低的值会删除更多的异常值)之后,我们将继续确定下限和上限阈值,即减去q25 - 阈值(下限阈值)并添加q75 + 阈值(上限阈值)。
- 条件删除: 最后,我们创建一个条件删除,即如果在两个极端情况下超过了“阈值”,则将删除实例。
- 箱线图表示: 通过箱线图可视化,“极端异常值”的数量已大大减少。
**注意:**在实施异常值减少后,我们的准确性提高了3%以上!一些异常值可能会扭曲我们模型的准确性,但请记住,我们必须避免过多的信息丢失,否则我们的模型有欠拟合的风险。
参考资料:有关四分位数范围法的更多信息:如何使用统计数据识别数据中的异常值 by Jason Brownless(Machine Learning Mastery博客)
# 导入需要的库
from scipy.stats import norm
# 创建一个包含3个子图的画布,大小为(20, 6)
f, (ax1, ax2, ax3) = plt.subplots(1,3, figsize=(20, 6))
# 获取V14特征在欺诈交易中的值
v14_fraud_dist = new_df['V14'].loc[new_df['Class'] == 1].values
# 绘制V14特征在欺诈交易中的分布图,拟合正态分布曲线,颜色为'#FB8861'
sns.distplot(v14_fraud_dist,ax=ax1, fit=norm, color='#FB8861')
# 设置子图1的标题为'V14 Distribution \n (Fraud Transactions)',字体大小为14
ax1.set_title('V14 Distribution \n (Fraud Transactions)', fontsize=14)
# 获取V12特征在欺诈交易中的值
v12_fraud_dist = new_df['V12'].loc[new_df['Class'] == 1].values
# 绘制V12特征在欺诈交易中的分布图,拟合正态分布曲线,颜色为'#56F9BB'
sns.distplot(v12_fraud_dist,ax=ax2, fit=norm, color='#56F9BB')
# 设置子图2的标题为'V12 Distribution \n (Fraud Transactions)',字体大小为14
ax2.set_title('V12 Distribution \n (Fraud Transactions)', fontsize=14)
# 获取V10特征在欺诈交易中的值
v10_fraud_dist = new_df['V10'].loc[new_df['Class'] == 1].values
# 绘制V10特征在欺诈交易中的分布图,拟合正态分布曲线,颜色为'#C5B3F9'
sns.distplot(v10_fraud_dist,ax=ax3, fit=norm, color='#C5B3F9')
# 设置子图3的标题为'V10 Distribution \n (Fraud Transactions)',字体大小为14
ax3.set_title('V10 Distribution \n (Fraud Transactions)', fontsize=14)
# 显示画布
plt.show()
# -----> V14 Removing Outliers (Highest Negative Correlated with Labels)
# 选择标签为1的数据中的V14特征列
v14_fraud = new_df['V14'].loc[new_df['Class'] == 1].values
# 计算V14特征列的第25和第75百分位数
q25, q75 = np.percentile(v14_fraud, 25), np.percentile(v14_fraud, 75)
print('Quartile 25: {} | Quartile 75: {}'.format(q25, q75))
# 计算IQR(四分位距)
v14_iqr = q75 - q25
print('iqr: {}'.format(v14_iqr))
# 计算异常值的上下限
v14_cut_off = v14_iqr * 1.5
v14_lower, v14_upper = q25 - v14_cut_off, q75 + v14_cut_off
print('Cut Off: {}'.format(v14_cut_off))
print('V14 Lower: {}'.format(v14_lower))
print('V14 Upper: {}'.format(v14_upper))
# 找出V14特征列中的异常值
outliers = [x for x in v14_fraud if x < v14_lower or x > v14_upper]
print('Feature V14 Outliers for Fraud Cases: {}'.format(len(outliers)))
print('V10 outliers:{}'.format(outliers))
# 删除V14特征列中的异常值所在的行
new_df = new_df.drop(new_df[(new_df['V14'] > v14_upper) | (new_df['V14'] < v14_lower)].index)
print('----' * 44)
# -----> V12 removing outliers from fraud transactions
# 选择标签为1的数据中的V12特征列
v12_fraud = new_df['V12'].loc[new_df['Class'] == 1].values
# 计算V12特征列的第25和第75百分位数
q25, q75 = np.percentile(v12_fraud, 25), np.percentile(v12_fraud, 75)
# 计算IQR(四分位距)
v12_iqr = q75 - q25
# 计算异常值的上下限
v12_cut_off = v12_iqr * 1.5
v12_lower, v12_upper = q25 - v12_cut_off, q75 + v12_cut_off
print('V12 Lower: {}'.format(v12_lower))
print('V12 Upper: {}'.format(v12_upper))
# 找出V12特征列中的异常值
outliers = [x for x in v12_fraud if x < v12_lower or x > v12_upper]
print('V12 outliers: {}'.format(outliers))
print('Feature V12 Outliers for Fraud Cases: {}'.format(len(outliers)))
# 删除V12特征列中的异常值所在的行
new_df = new_df.drop(new_df[(new_df['V12'] > v12_upper) | (new_df['V12'] < v12_lower)].index)
print('Number of Instances after outliers removal: {}'.format(len(new_df)))
print('----' * 44)
# Removing outliers V10 Feature
# 选择标签为1的数据中的V10特征列
v10_fraud = new_df['V10'].loc[new_df['Class'] == 1].values
# 计算V10特征列的第25和第75百分位数
q25, q75 = np.percentile(v10_fraud, 25), np.percentile(v10_fraud, 75)
# 计算IQR(四分位距)
v10_iqr = q75 - q25
# 计算异常值的上下限
v10_cut_off = v10_iqr * 1.5
v10_lower, v10_upper = q25 - v10_cut_off, q75 + v10_cut_off
print('V10 Lower: {}'.format(v10_lower))
print('V10 Upper: {}'.format(v10_upper))
# 找出V10特征列中的异常值
outliers = [x for x in v10_fraud if x < v10_lower or x > v10_upper]
print('V10 outliers: {}'.format(outliers))
print('Feature V10 Outliers for Fraud Cases: {}'.format(len(outliers)))
# 删除V10特征列中的异常值所在的行
new_df = new_df.drop(new_df[(new_df['V10'] > v10_upper) | (new_df['V10'] < v10_lower)].index)
print('Number of Instances after outliers removal: {}'.format(len(new_df)))
Quartile 25: -9.692722964972385 | Quartile 75: -4.282820849486866
iqr: 5.409902115485519
Cut Off: 8.114853173228278
V14 Lower: -17.807576138200663
V14 Upper: 3.8320323237414122
Feature V14 Outliers for Fraud Cases: 4
V10 outliers:[-19.2143254902614, -18.8220867423816, -18.4937733551053, -18.049997689859396]
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
V12 Lower: -17.3430371579634
V12 Upper: 5.776973384895937
V12 outliers: [-18.683714633344298, -18.047596570821604, -18.4311310279993, -18.553697009645802]
Feature V12 Outliers for Fraud Cases: 4
Number of Instances after outliers removal: 976
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
V10 Lower: -14.89885463232024
V10 Upper: 4.920334958342141
V10 outliers: [-24.403184969972802, -18.9132433348732, -15.124162814494698, -16.3035376590131, -15.2399619587112, -15.1237521803455, -14.9246547735487, -16.6496281595399, -18.2711681738888, -24.5882624372475, -15.346098846877501, -20.949191554361104, -15.2399619587112, -23.2282548357516, -15.2318333653018, -22.1870885620007, -17.141513641289198, -19.836148851696, -22.1870885620007, -16.6011969664137, -16.7460441053944, -15.563791338730098, -14.9246547735487, -16.2556117491401, -22.1870885620007, -15.563791338730098, -22.1870885620007]
Feature V10 Outliers for Fraud Cases: 27
Number of Instances after outliers removal: 947
# 创建一个包含3个子图的画布
f, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(20,6))
# 定义颜色列表
colors = ['#B3F9C5', '#f9c5b3']
# 在第一个子图上绘制箱线图,x轴为"Class",y轴为"V14",数据来源为new_df,使用colors列表中的颜色,将结果绘制在ax1上
sns.boxplot(x="Class", y="V14", data=new_df, ax=ax1, palette=colors)
ax1.set_title("V14特征\n去除异常值", fontsize=14) # 设置子图标题
ax1.annotate('较少的极端异常值', xy=(0.98, -17.5), xytext=(0, -12), # 在图中添加注释,注释的位置为(0.98, -17.5),注释文本的位置为(0, -12)
arrowprops=dict(facecolor='black'), # 设置箭头的样式
fontsize=14) # 设置注释文本的字体大小
# 在第二个子图上绘制箱线图,x轴为"Class",y轴为"V12",数据来源为new_df,使用colors列表中的颜色,将结果绘制在ax2上
sns.boxplot(x="Class", y="V12", data=new_df, ax=ax2, palette=colors)
ax2.set_title("V12特征\n去除异常值", fontsize=14) # 设置子图标题
ax2.annotate('较少的极端异常值', xy=(0.98, -17.3), xytext=(0, -12), # 在图中添加注释,注释的位置为(0.98, -17.3),注释文本的位置为(0, -12)
arrowprops=dict(facecolor='black'), # 设置箭头的样式
fontsize=14) # 设置注释文本的字体大小
# 在第三个子图上绘制箱线图,x轴为"Class",y轴为"V10",数据来源为new_df,使用colors列表中的颜色,将结果绘制在ax3上
sns.boxplot(x="Class", y="V10", data=new_df, ax=ax3, palette=colors)
ax3.set_title("V10特征\n去除异常值", fontsize=14) # 设置子图标题
ax3.annotate('较少的极端异常值', xy=(0.95, -16.5), xytext=(0, -12), # 在图中添加注释,注释的位置为(0.95, -16.5),注释文本的位置为(0, -12)
arrowprops=dict(facecolor='black'), # 设置箭头的样式
fontsize=14) # 设置注释文本的字体大小
# 显示图形
plt.show()
降维和聚类:
理解t-SNE算法:
为了理解这个算法,你需要理解以下术语:
- 欧几里得距离
- 条件概率
- 正态分布和T分布图
注意: 如果你想要一个简单易懂的视频,请看Joshua Starmer的StatQuest: t-SNE, Clearly Explained。
总结:
- t-SNE算法能够相当准确地对我们数据集中的欺诈和非欺诈案例进行聚类。
- 尽管子样本非常小,但t-SNE算法能够在每种情况下相当准确地检测到聚类(在运行t-SNE之前,我对数据集进行了洗牌)。
- 这表明进一步的预测模型在区分欺诈案例和非欺诈案例方面将表现得相当好。
# New_df是从随机欠采样数据中得到的(较少的实例)
X = new_df.drop('Class', axis=1) # 将'Class'列从new_df中删除,得到特征矩阵X
y = new_df['Class'] # 将'Class'列作为目标变量y
# T-SNE实现
t0 = time.time() # 记录开始时间
X_reduced_tsne = TSNE(n_components=2, random_state=42).fit_transform(X.values) # 使用T-SNE算法对特征矩阵X进行降维,降至2维
t1 = time.time() # 记录结束时间
print("T-SNE took {:.2} s".format(t1 - t0)) # 输出T-SNE算法的运行时间
# PCA实现
t0 = time.time() # 记录开始时间
X_reduced_pca = PCA(n_components=2, random_state=42).fit_transform(X.values) # 使用PCA算法对特征矩阵X进行降维,降至2维
t1 = time.time() # 记录结束时间
print("PCA took {:.2} s".format(t1 - t0)) # 输出PCA算法的运行时间
# TruncatedSVD实现
t0 = time.time() # 记录开始时间
X_reduced_svd = TruncatedSVD(n_components=2, algorithm='randomized', random_state=42).fit_transform(X.values) # 使用TruncatedSVD算法对特征矩阵X进行降维,降至2维
t1 = time.time() # 记录结束时间
print("Truncated SVD took {:.2} s".format(t1 - t0)) # 输出TruncatedSVD算法的运行时间
T-SNE took 7.0 s
PCA took 0.032 s
Truncated SVD took 0.0051 s
# 创建一个1行3列的图像,并返回一个包含3个子图的元组
f, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(24,6))
# 设置总标题
f.suptitle('Clusters using Dimensionality Reduction', fontsize=14)
# 创建两个颜色块,用于图例
blue_patch = mpatches.Patch(color='#0A0AFF', label='No Fraud')
red_patch = mpatches.Patch(color='#AF0000', label='Fraud')
# t-SNE散点图
ax1.scatter(X_reduced_tsne[:,0], X_reduced_tsne[:,1], c=(y == 0), cmap='coolwarm', label='No Fraud', linewidths=2)
ax1.scatter(X_reduced_tsne[:,0], X_reduced_tsne[:,1], c=(y == 1), cmap='coolwarm', label='Fraud', linewidths=2)
ax1.set_title('t-SNE', fontsize=14) # 设置子图标题
ax1.grid(True) # 显示网格线
ax1.legend(handles=[blue_patch, red_patch]) # 添加图例
# PCA散点图
ax2.scatter(X_reduced_pca[:,0], X_reduced_pca[:,1], c=(y == 0), cmap='coolwarm', label='No Fraud', linewidths=2)
ax2.scatter(X_reduced_pca[:,0], X_reduced_pca[:,1], c=(y == 1), cmap='coolwarm', label='Fraud', linewidths=2)
ax2.set_title('PCA', fontsize=14) # 设置子图标题
ax2.grid(True) # 显示网格线
ax2.legend(handles=[blue_patch, red_patch]) # 添加图例
# TruncatedSVD散点图
ax3.scatter(X_reduced_svd[:,0], X_reduced_svd[:,1], c=(y == 0), cmap='coolwarm', label='No Fraud', linewidths=2)
ax3.scatter(X_reduced_svd[:,0], X_reduced_svd[:,1], c=(y == 1), cmap='coolwarm', label='Fraud', linewidths=2)
ax3.set_title('Truncated SVD', fontsize=14) # 设置子图标题
ax3.grid(True) # 显示网格线
ax3.legend(handles=[blue_patch, red_patch]) # 添加图例
plt.show() # 显示图像
分类器(欠采样):
在本节中,我们将训练四种类型的分类器,并决定哪种分类器在检测欺诈交易方面更有效。在此之前,我们需要将数据分为训练集和测试集,并将特征与标签分开。
总结:
- 在大多数情况下,逻辑回归分类器比其他三种分类器更准确。(我们将进一步分析逻辑回归)
- GridSearchCV用于确定为分类器提供最佳预测分数的参数。
- 逻辑回归具有最佳的接收操作特征曲线分数(ROC),这意味着逻辑回归相当准确地区分了欺诈和非欺诈交易。
学习曲线:
- 训练分数和交叉验证分数之间的差距越大,模型过拟合(高方差)的可能性就越大。
- 如果训练和交叉验证集中的分数都很低,这表明我们的模型欠拟合(高偏差)。
- 逻辑回归分类器在训练和交叉验证集中都显示出最佳分数。
# 将新的数据集new_df分为特征集X和目标变量y
# 特征集X为去除了'Class'列的new_df
X = new_df.drop('Class', axis=1)
# 目标变量y为new_df的'Class'列
y = new_df['Class']
# 导入train_test_split函数用于将数据集划分为训练集和测试集
from sklearn.model_selection import train_test_split
# 使用train_test_split函数将数据集划分为训练集和测试集
# X_train为训练集特征数据,X_test为测试集特征数据
# y_train为训练集标签数据,y_test为测试集标签数据
# test_size=0.2表示将数据集划分为80%的训练集和20%的测试集
# random_state=42表示随机种子,保证每次划分的结果一致性
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 将值转换为数组,以便用于分类算法的输入。
# 将训练集的特征值转换为数组
X_train = X_train.values
# 将测试集的特征值转换为数组
X_test = X_test.values
# 将训练集的目标值转换为数组
y_train = y_train.values
# 将测试集的目标值转换为数组
y_test = y_test.values
# 定义了一个字典,其中包含了四个分类器的名称和对应的分类器对象
classifiers = {
"LogisiticRegression": LogisticRegression(), # 逻辑回归分类器
"KNearest": KNeighborsClassifier(), # K最近邻分类器
"Support Vector Classifier": SVC(), # 支持向量机分类器
"DecisionTreeClassifier": DecisionTreeClassifier() # 决策树分类器
}
# 导入cross_val_score函数用于交叉验证
from sklearn.model_selection import cross_val_score
# 遍历classifiers字典中的每个分类器
for key, classifier in classifiers.items():
# 使用训练集对分类器进行训练
classifier.fit(X_train, y_train)
# 使用交叉验证计算训练得分
training_score = cross_val_score(classifier, X_train, y_train, cv=5)
# 打印分类器的名称和训练得分
print("Classifiers: ", classifier.__class__.__name__, "Has a training score of", round(training_score.mean(), 2) * 100, "% accuracy score")
Classifiers: LogisticRegression Has a training score of 95.0 % accuracy score
Classifiers: KNeighborsClassifier Has a training score of 93.0 % accuracy score
Classifiers: SVC Has a training score of 92.0 % accuracy score
Classifiers: DecisionTreeClassifier Has a training score of 88.0 % accuracy score
# 使用GridSearchCV寻找最佳参数。
# 逻辑回归
log_reg_params = {"penalty": ['l1', 'l2'], 'C': [0.001, 0.01, 0.1, 1, 10, 100, 1000]}
# 定义逻辑回归的参数空间
grid_log_reg = GridSearchCV(LogisticRegression(), log_reg_params)
# 创建逻辑回归的GridSearchCV对象,传入逻辑回归模型和参数空间
grid_log_reg.fit(X_train, y_train)
# 在训练集上拟合GridSearchCV对象
# 我们自动获得了具有最佳参数的逻辑回归模型
log_reg = grid_log_reg.best_estimator_
knears_params = {"n_neighbors": list(range(2,5,1)), 'algorithm': ['auto', 'ball_tree', 'kd_tree', 'brute']}
# 定义K近邻分类器的参数空间
grid_knears = GridSearchCV(KNeighborsClassifier(), knears_params)
# 创建K近邻分类器的GridSearchCV对象,传入K近邻分类器模型和参数空间
grid_knears.fit(X_train, y_train)
# 在训练集上拟合GridSearchCV对象
# 获得最佳参数的K近邻分类器
knears_neighbors = grid_knears.best_estimator_
# 支持向量分类器
svc_params = {'C': [0.5, 0.7, 0.9, 1], 'kernel': ['rbf', 'poly', 'sigmoid', 'linear']}
# 定义支持向量分类器的参数空间
grid_svc = GridSearchCV(SVC(), svc_params)
# 创建支持向量分类器的GridSearchCV对象,传入支持向量分类器模型和参数空间
grid_svc.fit(X_train, y_train)
# 在训练集上拟合GridSearchCV对象
# 获得最佳参数的支持向量分类器
svc = grid_svc.best_estimator_
# 决策树分类器
tree_params = {"criterion": ["gini", "entropy"], "max_depth": list(range(2,4,1)),
"min_samples_leaf": list(range(5,7,1))}
# 定义决策树分类器的参数空间
grid_tree = GridSearchCV(DecisionTreeClassifier(), tree_params)
# 创建决策树分类器的GridSearchCV对象,传入决策树分类器模型和参数空间
grid_tree.fit(X_train, y_train)
# 在训练集上拟合GridSearchCV对象
# 获得最佳参数的决策树分类器
tree_clf = grid_tree.best_estimator_
# 过拟合案例
# 使用逻辑回归模型进行交叉验证,并计算交叉验证得分
log_reg_score = cross_val_score(log_reg, X_train, y_train, cv=5)
print('逻辑回归交叉验证得分:', round(log_reg_score.mean() * 100, 2).astype(str) + '%')
# 使用K近邻模型进行交叉验证,并计算交叉验证得分
knears_score = cross_val_score(knears_neighbors, X_train, y_train, cv=5)
print('K近邻交叉验证得分:', round(knears_score.mean() * 100, 2).astype(str) + '%')
# 使用支持向量机模型进行交叉验证,并计算交叉验证得分
svc_score = cross_val_score(svc, X_train, y_train, cv=5)
print('支持向量机交叉验证得分:', round(svc_score.mean() * 100, 2).astype(str) + '%')
# 使用决策树模型进行交叉验证,并计算交叉验证得分
tree_score = cross_val_score(tree_clf, X_train, y_train, cv=5)
print('决策树交叉验证得分:', round(tree_score.mean() * 100, 2).astype(str) + '%')
Logistic Regression Cross Validation Score: 94.05%
Knears Neighbors Cross Validation Score 92.73%
Support Vector Classifier Cross Validation Score 93.79%
DecisionTree Classifier Cross Validation Score 91.41%
# 将'Class'列从数据框df中删除,得到下采样的特征数据
undersample_X = df.drop('Class', axis=1)
# 将'Class'列作为下采样的目标变量
undersample_y = df['Class']
# 使用StratifiedShuffleSplit方法进行交叉验证
# 将数据集划分为训练集和测试集,并打印出每次划分的训练集和测试集的索引
for train_index, test_index in sss.split(undersample_X, undersample_y):
print("Train:", train_index, "Test:", test_index)
# 根据索引提取训练集和测试集的特征数据
undersample_Xtrain, undersample_Xtest = undersample_X.iloc[train_index], undersample_X.iloc[test_index]
# 根据索引提取训练集和测试集的目标变量
undersample_ytrain, undersample_ytest = undersample_y.iloc[train_index], undersample_y.iloc[test_index]
# 将训练集和测试集的特征数据转换为数组形式
undersample_Xtrain = undersample_Xtrain.values
undersample_Xtest = undersample_Xtest.values
# 将训练集和测试集的目标变量转换为数组形式
undersample_ytrain = undersample_ytrain.values
undersample_ytest = undersample_ytest.values
# 初始化用于存储模型评估指标的列表
undersample_accuracy = []
undersample_precision = []
undersample_recall = []
undersample_f1 = []
undersample_auc = []
# 使用NearMiss技术进行下采样
# 使用NearMiss方法对下采样的特征数据和目标变量进行采样,以观察标签的分布情况
X_nearmiss, y_nearmiss = NearMiss().fit_sample(undersample_X.values, undersample_y.values)
print('NearMiss Label Distribution: {}'.format(Counter(y_nearmiss)))
# 使用正确的方式进行交叉验证
# 将下采样的训练集和测试集进行交叉验证,并计算模型评估指标
for train, test in sss.split(undersample_Xtrain, undersample_ytrain):
# 构建包含NearMiss采样和逻辑回归模型的管道
undersample_pipeline = imbalanced_make_pipeline(NearMiss(sampling_strategy='majority'), log_reg)
# 在训练集上训练模型
undersample_model = undersample_pipeline.fit(undersample_Xtrain[train], undersample_ytrain[train])
# 在测试集上进行预测
undersample_prediction = undersample_model.predict(undersample_Xtrain[test])
# 计算模型评估指标并将其添加到相应的列表中
undersample_accuracy.append(undersample_pipeline.score(original_Xtrain[test], original_ytrain[test]))
undersample_precision.append(precision_score(original_ytrain[test], undersample_prediction))
undersample_recall.append(recall_score(original_ytrain[test], undersample_prediction))
undersample_f1.append(f1_score(original_ytrain[test], undersample_prediction))
undersample_auc.append(roc_auc_score(original_ytrain[test], undersample_prediction))
Train: [ 56959 56960 56961 ... 284804 284805 284806] Test: [ 0 1 2 ... 57174 58268 58463]
Train: [ 0 1 2 ... 284804 284805 284806] Test: [ 56959 56960 56961 ... 115109 116514 116648]
Train: [ 0 1 2 ... 284804 284805 284806] Test: [113919 113920 113921 ... 170890 170891 170892]
Train: [ 0 1 2 ... 284804 284805 284806] Test: [168136 168614 168817 ... 228955 229310 229751]
Train: [ 0 1 2 ... 228955 229310 229751] Test: [227842 227843 227844 ... 284804 284805 284806]
NearMiss Label Distribution: Counter({0: 492, 1: 492})
# 导入所需的库
from sklearn.model_selection import ShuffleSplit
from sklearn.model_selection import learning_curve
# 定义绘制学习曲线的函数
def plot_learning_curve(estimator1, estimator2, estimator3, estimator4, X, y, ylim=None, cv=None,
n_jobs=1, train_sizes=np.linspace(.1, 1.0, 5)):
# 创建一个2x2的子图,设置图像大小为20x14,共享y轴
f, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2,2, figsize=(20,14), sharey=True)
if ylim is not None:
plt.ylim(*ylim)
# 绘制第一个估计器的学习曲线
train_sizes, train_scores, test_scores = learning_curve(
estimator1, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes)
train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
test_scores_std = np.std(test_scores, axis=1)
ax1.fill_between(train_sizes, train_scores_mean - train_scores_std,
train_scores_mean + train_scores_std, alpha=0.1,
color="#ff9124")
ax1.fill_between(train_sizes, test_scores_mean - test_scores_std,
test_scores_mean + test_scores_std, alpha=0.1, color="#2492ff")
ax1.plot(train_sizes, train_scores_mean, 'o-', color="#ff9124",
label="Training score")
ax1.plot(train_sizes, test_scores_mean, 'o-', color="#2492ff",
label="Cross-validation score")
ax1.set_title("Logistic Regression Learning Curve", fontsize=14)
ax1.set_xlabel('Training size (m)')
ax1.set_ylabel('Score')
ax1.grid(True)
ax1.legend(loc="best")
# 绘制第二个估计器的学习曲线
train_sizes, train_scores, test_scores = learning_curve(
estimator2, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes)
train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
test_scores_std = np.std(test_scores, axis=1)
ax2.fill_between(train_sizes, train_scores_mean - train_scores_std,
train_scores_mean + train_scores_std, alpha=0.1,
color="#ff9124")
ax2.fill_between(train_sizes, test_scores_mean - test_scores_std,
test_scores_mean + test_scores_std, alpha=0.1, color="#2492ff")
ax2.plot(train_sizes, train_scores_mean, 'o-', color="#ff9124",
label="Training score")
ax2.plot(train_sizes, test_scores_mean, 'o-', color="#2492ff",
label="Cross-validation score")
ax2.set_title("Knears Neighbors Learning Curve", fontsize=14)
ax2.set_xlabel('Training size (m)')
ax2.set_ylabel('Score')
ax2.grid(True)
ax2.legend(loc="best")
# 绘制第三个估计器的学习曲线
train_sizes, train_scores, test_scores = learning_curve(
estimator3, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes)
train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
test_scores_std = np.std(test_scores, axis=1)
ax3.fill_between(train_sizes, train_scores_mean - train_scores_std,
train_scores_mean + train_scores_std, alpha=0.1,
color="#ff9124")
ax3.fill_between(train_sizes, test_scores_mean - test_scores_std,
test_scores_mean + test_scores_std, alpha=0.1, color="#2492ff")
ax3.plot(train_sizes, train_scores_mean, 'o-', color="#ff9124",
label="Training score")
ax3.plot(train_sizes, test_scores_mean, 'o-', color="#2492ff",
label="Cross-validation score")
ax3.set_title("Support Vector Classifier \n Learning Curve", fontsize=14)
ax3.set_xlabel('Training size (m)')
ax3.set_ylabel('Score')
ax3.grid(True)
ax3.legend(loc="best")
# 绘制第四个估计器的学习曲线
train_sizes, train_scores, test_scores = learning_curve(
estimator4, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes)
train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
test_scores_std = np.std(test_scores, axis=1)
ax4.fill_between(train_sizes, train_scores_mean - train_scores_std,
train_scores_mean + train_scores_std, alpha=0.1,
color="#ff9124")
ax4.fill_between(train_sizes, test_scores_mean - test_scores_std,
test_scores_mean + test_scores_std, alpha=0.1, color="#2492ff")
ax4.plot(train_sizes, train_scores_mean, 'o-', color="#ff9124",
label="Training score")
ax4.plot(train_sizes, test_scores_mean, 'o-', color="#2492ff",
label="Cross-validation score")
ax4.set_title("Decision Tree Classifier \n Learning Curve", fontsize=14)
ax4.set_xlabel('Training size (m)')
ax4.set_ylabel('Score')
ax4.grid(True)
ax4.legend(loc="best")
# 返回绘制的图像
return plt
# 创建一个ShuffleSplit对象,用于生成交叉验证的数据集
cv = ShuffleSplit(n_splits=100, test_size=0.2, random_state=42)
# 调用plot_learning_curve函数,绘制学习曲线图
plot_learning_curve(log_reg, knears_neighbors, svc, tree_clf, X_train, y_train, (0.87, 1.01), cv=cv, n_jobs=4)
<module 'matplotlib.pyplot' from '/opt/conda/lib/python3.6/site-packages/matplotlib/pyplot.py'>
# 导入所需的库
from sklearn.metrics import roc_curve
from sklearn.model_selection import cross_val_predict
# 使用交叉验证预测逻辑回归模型的预测结果,并使用决策函数作为预测值
log_reg_pred = cross_val_predict(log_reg, X_train, y_train, cv=5, method="decision_function")
# 使用交叉验证预测k近邻模型的预测结果
knears_pred = cross_val_predict(knears_neighbors, X_train, y_train, cv=5)
# 使用交叉验证预测支持向量机模型的预测结果,并使用决策函数作为预测值
svc_pred = cross_val_predict(svc, X_train, y_train, cv=5, method="decision_function")
# 使用交叉验证预测决策树模型的预测结果
tree_pred = cross_val_predict(tree_clf, X_train, y_train, cv=5)
# 导入 roc_auc_score 函数
from sklearn.metrics import roc_auc_score
# 输出 Logistic Regression 模型的 ROC AUC 分数
print('Logistic Regression: ', roc_auc_score(y_train, log_reg_pred))
# 输出 KNears Neighbors 模型的 ROC AUC 分数
print('KNears Neighbors: ', roc_auc_score(y_train, knears_pred))
# 输出 Support Vector Classifier 模型的 ROC AUC 分数
print('Support Vector Classifier: ', roc_auc_score(y_train, svc_pred))
# 输出 Decision Tree Classifier 模型的 ROC AUC 分数
print('Decision Tree Classifier: ', roc_auc_score(y_train, tree_pred))
Logistic Regression: 0.9798658657817729
KNears Neighbors: 0.9246195096680248
Support Vector Classifier: 0.9746783159014857
Decision Tree Classifier: 0.9173877431007686
# 计算各个分类器的ROC曲线的假正率(FPR)、真正率(TPR)和阈值(threshold)
log_fpr, log_tpr, log_threshold = roc_curve(y_train, log_reg_pred)
knear_fpr, knear_tpr, knear_threshold = roc_curve(y_train, knears_pred)
svc_fpr, svc_tpr, svc_threshold = roc_curve(y_train, svc_pred)
tree_fpr, tree_tpr, tree_threshold = roc_curve(y_train, tree_pred)
# 定义绘制多个分类器ROC曲线的函数
def graph_roc_curve_multiple(log_fpr, log_tpr, knear_fpr, knear_tpr, svc_fpr, svc_tpr, tree_fpr, tree_tpr):
# 创建一个画布
plt.figure(figsize=(16,8))
# 设置标题
plt.title('ROC Curve \n Top 4 Classifiers', fontsize=18)
# 绘制Logistic Regression分类器的ROC曲线
plt.plot(log_fpr, log_tpr, label='Logistic Regression Classifier Score: {:.4f}'.format(roc_auc_score(y_train, log_reg_pred)))
# 绘制KNears Neighbors分类器的ROC曲线
plt.plot(knear_fpr, knear_tpr, label='KNears Neighbors Classifier Score: {:.4f}'.format(roc_auc_score(y_train, knears_pred)))
# 绘制Support Vector分类器的ROC曲线
plt.plot(svc_fpr, svc_tpr, label='Support Vector Classifier Score: {:.4f}'.format(roc_auc_score(y_train, svc_pred)))
# 绘制Decision Tree分类器的ROC曲线
plt.plot(tree_fpr, tree_tpr, label='Decision Tree Classifier Score: {:.4f}'.format(roc_auc_score(y_train, tree_pred)))
# 绘制对角线
plt.plot([0, 1], [0, 1], 'k--')
# 设置坐标轴范围
plt.axis([-0.01, 1, 0, 1])
# 设置x轴标签
plt.xlabel('False Positive Rate', fontsize=16)
# 设置y轴标签
plt.ylabel('True Positive Rate', fontsize=16)
# 添加注释
plt.annotate('Minimum ROC Score of 50% \n (This is the minimum score to get)', xy=(0.5, 0.5), xytext=(0.6, 0.3),
arrowprops=dict(facecolor='#6E726D', shrink=0.05),
)
# 添加图例
plt.legend()
# 调用函数绘制多个分类器ROC曲线
graph_roc_curve_multiple(log_fpr, log_tpr, knear_fpr, knear_tpr, svc_fpr, svc_tpr, tree_fpr, tree_tpr)
# 显示图形
plt.show()
对LogisticRegression的深入了解:
术语:
- 真正例(True Positives):正确分类的欺诈交易
- 假正例(False Positives):错误分类的欺诈交易
- 真负例(True Negative):正确分类的非欺诈交易
- 假负例(False Negative):错误分类的非欺诈交易
- 精确率(Precision):真正例 / (真正例 + 假正例)
- 召回率(Recall):真正例 / (真正例 + 假负例)
- 精确率(Precision)如其名所示,表示我们的模型在检测欺诈交易方面的准确性(确信度),而召回率(Recall)表示我们的模型能够检测到的欺诈案例的数量。
- 精确率/召回率权衡(Precision/Recall Tradeoff):我们的模型越精确(选择性), 它能够检测到的案例就越少。例如:假设我们的模型精确率为95%,假设只有5个欺诈案例,模型在95%或更高的精确率下认为这些案例是欺诈案例。然后假设还有5个案例,我们的模型认为90%是欺诈案例,如果我们降低精确率,我们的模型将能够检测到更多的案例。
总结:
- 精确率开始下降在0.90和0.92之间,尽管如此,我们的精确率仍然相当高,而且我们的召回率也相当不错。
# 定义函数logistic_roc_curve,用于绘制逻辑回归的ROC曲线
# 参数log_fpr表示逻辑回归的假阳性率,log_tpr表示逻辑回归的真阳性率
def logistic_roc_curve(log_fpr, log_tpr):
# 创建一个图形对象,设置图形大小为12x8
plt.figure(figsize=(12,8))
# 设置图形标题为"Logistic Regression ROC Curve",字体大小为16
plt.title('Logistic Regression ROC Curve', fontsize=16)
# 绘制逻辑回归的ROC曲线,线条颜色为蓝色,线宽为2
plt.plot(log_fpr, log_tpr, 'b-', linewidth=2)
# 绘制一条红色虚线,表示随机猜测的ROC曲线
plt.plot([0, 1], [0, 1], 'r--')
# 设置x轴标签为"False Positive Rate",字体大小为16
plt.xlabel('False Positive Rate', fontsize=16)
# 设置y轴标签为"True Positive Rate",字体大小为16
plt.ylabel('True Positive Rate', fontsize=16)
# 设置坐标轴范围,x轴范围为-0.01到1,y轴范围为0到1
plt.axis([-0.01,1,0,1])
# 调用logistic_roc_curve函数,传入逻辑回归的假阳性率和真阳性率作为参数
logistic_roc_curve(log_fpr, log_tpr)
# 显示图形
plt.show()
# 导入precision_recall_curve函数用于计算精确率、召回率和阈值
from sklearn.metrics import precision_recall_curve
# 使用precision_recall_curve函数计算精确率、召回率和阈值
# 参数y_train表示训练集的真实标签
# 参数log_reg_pred表示逻辑回归模型在训练集上的预测结果
precision, recall, threshold = precision_recall_curve(y_train, log_reg_pred)
# 导入需要的库和函数
from sklearn.metrics import recall_score, precision_score, f1_score, accuracy_score
# 使用log_reg模型对X_train进行预测,得到预测结果y_pred
y_pred = log_reg.predict(X_train)
# 过拟合情况
print('---' * 45)
print('过拟合情况:\n')
# 输出召回率(Recall Score)
print('Recall Score: {:.2f}'.format(recall_score(y_train, y_pred)))
# 输出精确率(Precision Score)
print('Precision Score: {:.2f}'.format(precision_score(y_train, y_pred)))
# 输出F1得分(F1 Score)
print('F1 Score: {:.2f}'.format(f1_score(y_train, y_pred)))
# 输出准确率(Accuracy Score)
print('Accuracy Score: {:.2f}'.format(accuracy_score(y_train, y_pred)))
print('---' * 45)
# 正确的输出格式
print('---' * 45)
print('正确的输出格式:\n')
# 输出准确率(Accuracy Score)
print("Accuracy Score: {:.2f}".format(np.mean(undersample_accuracy)))
# 输出精确率(Precision Score)
print("Precision Score: {:.2f}".format(np.mean(undersample_precision)))
# 输出召回率(Recall Score)
print("Recall Score: {:.2f}".format(np.mean(undersample_recall)))
# 输出F1得分(F1 Score)
print("F1 Score: {:.2f}".format(np.mean(undersample_f1)))
print('---' * 45)
---------------------------------------------------------------------------------------------------------------------------------------
Overfitting:
Recall Score: 0.90
Precision Score: 0.76
F1 Score: 0.82
Accuracy Score: 0.81
---------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------
How it should be:
Accuracy Score: 0.65
Precision Score: 0.00
Recall Score: 0.29
F1 Score: 0.00
---------------------------------------------------------------------------------------------------------------------------------------
# 使用逻辑回归模型log_reg对original_Xtest进行决策函数预测,得到预测得分undersample_y_score
undersample_y_score = log_reg.decision_function(original_Xtest)
# 导入average_precision_score函数
from sklearn.metrics import average_precision_score
# 使用average_precision_score函数计算undersample_y_score的平均精确度-召回率得分
undersample_average_precision = average_precision_score(original_ytest, undersample_y_score)
# 打印undersample_average_precision的值,保留两位小数
print('Average precision-recall score: {0:0.2f}'.format(
undersample_average_precision))
Average precision-recall score: 0.03
# 导入需要的库
from sklearn.metrics import precision_recall_curve
import matplotlib.pyplot as plt
# 创建一个图形对象,设置图形大小为12*6
fig = plt.figure(figsize=(12,6))
# 计算精确率、召回率和阈值
precision, recall, _ = precision_recall_curve(original_ytest, undersample_y_score)
# 绘制阶梯状的召回率-精确率曲线,设置颜色为蓝色,透明度为0.2,从左到右绘制
plt.step(recall, precision, color='#004a93', alpha=0.2, where='post')
# 填充召回率-精确率曲线下的区域,设置颜色为蓝色,透明度为0.2,从左到右填充
plt.fill_between(recall, precision, step='post', alpha=0.2, color='#48a6ff')
# 设置x轴标签为"召回率",y轴标签为"精确率"
plt.xlabel('Recall')
plt.ylabel('Precision')
# 设置y轴范围为0到1.05,x轴范围为0到1
plt.ylim([0.0, 1.05])
plt.xlim([0.0, 1.0])
# 设置标题为"UnderSampling Precision-Recall curve: \n Average Precision-Recall Score = 平均精确率-召回率得分",字体大小为16
plt.title('UnderSampling Precision-Recall curve: \n Average Precision-Recall Score ={0:0.2f}'.format(undersample_average_precision), fontsize=16)
Text(0.5, 1.0, 'UnderSampling Precision-Recall curve: \n Average Precision-Recall Score =0.03')
SMOTE技术(过采样):
SMOTE代表合成少数类过采样技术。与随机欠采样不同,SMOTE创建新的合成点以实现类的平衡。这是解决“类不平衡问题”的另一种选择。
理解SMOTE:
- 解决类不平衡: SMOTE从少数类中创建合成点,以实现少数类和多数类之间的平衡。
- 合成点的位置: SMOTE选择少数类最近邻之间的距离,在这些距离之间创建合成点。
- 最终效果: 由于我们不需要删除任何行,因此保留了更多的信息,与随机欠采样不同。
- 准确性||时间权衡: 虽然SMOTE可能比随机欠采样更准确,但由于之前所述没有删除任何行,训练时间会更长。
交叉验证过拟合错误:
交叉验证期间的过拟合:
在我们的欠采样分析中,我想向大家展示一个我犯过的常见错误。很简单,如果你想对数据进行欠采样或过采样,你不应该在交叉验证之前这样做。为什么呢?因为你将直接影响交叉验证之前的验证集,导致“数据泄漏”问题。在下一节中,你将看到令人惊讶的精确度和召回率得分,但实际上我们的数据是过拟合的!
错误的方法:
如前所述,如果我们在交叉验证之前获取少数类(“欺诈”),并在交叉验证过程中创建合成点,我们对交叉验证过程的“验证集”有一定的影响。记住交叉验证的工作原理,假设我们将数据分成5个批次,其中4/5的数据集将是训练集,而1/5将是验证集。测试集不应该被触碰!因此,我们必须在交叉验证期间而不是之前进行合成数据点的创建,就像下面这样:
正确的方法:
如上所示,SMOTE发生在交叉验证期间而不是交叉验证过程之前。合成数据仅针对训练集创建,不影响验证集。
参考资料:
- 为新手解释SMOTE
- 机器学习-过采样和欠采样-Python/Scikit/Scikit-Imblearn
# 导入所需的库 from imblearn.over_sampling import SMOTE from sklearn.model_selection import train_test_split, RandomizedSearchCV # 打印训练集和测试集的长度 print('Length of X (train): {} | Length of y (train): {}'.format(len(original_Xtrain), len(original_ytrain))) print('Length of X (test): {} | Length of y (test): {}'.format(len(original_Xtest), len(original_ytest))) # 创建用于存储评分的列表,并计算平均值 accuracy_lst = [] precision_lst = [] recall_lst = [] f1_lst = [] auc_lst = [] # 使用最佳参数的分类器 log_reg_sm = LogisticRegression() # 使用随机搜索来寻找最佳参数的逻辑回归分类器 rand_log_reg = RandomizedSearchCV(LogisticRegression(), log_reg_params, n_iter=4) # 实施SMOTE技术,正确地进行交叉验证 # 参数 log_reg_params = {"penalty": ['l1', 'l2'], 'C': [0.001, 0.01, 0.1, 1, 10, 100, 1000]} for train, test in sss.split(original_Xtrain, original_ytrain): # 创建pipeline,包括SMOTE和随机搜索的逻辑回归分类器 pipeline = imbalanced_make_pipeline(SMOTE(sampling_strategy='minority'), rand_log_reg) # SMOTE发生在交叉验证之中而不是之前 # 使用训练集拟合模型 model = pipeline.fit(original_Xtrain[train], original_ytrain[train]) # 获取最佳参数的逻辑回归分类器 best_est = rand_log_reg.best_estimator_ # 预测测试集的结果 prediction = best_est.predict(original_Xtrain[test]) # 计算评分并将其添加到相应的列表中 accuracy_lst.append(pipeline.score(original_Xtrain[test], original_ytrain[test])) precision_lst.append(precision_score(original_ytrain[test], prediction)) recall_lst.append(recall_score(original_ytrain[test], prediction)) f1_lst.append(f1_score(original_ytrain[test], prediction)) auc_lst.append(roc_auc_score(original_ytrain[test], prediction)) # 打印评分的平均值 print('---' * 45) print('') print("accuracy: {}".format(np.mean(accuracy_lst))) print("precision: {}".format(np.mean(precision_lst))) print("recall: {}".format(np.mean(recall_lst))) print("f1: {}".format(np.mean(f1_lst))) print('---' * 45)
Length of X (train): 227846 | Length of y (train): 227846 Length of X (test): 56961 | Length of y (test): 56961 --------------------------------------------------------------------------------------------------------------------------------------- accuracy: 0.9694005888966659 precision: 0.06547023328181797 recall: 0.9111002921129504 f1: 0.1209666729570652 ---------------------------------------------------------------------------------------------------------------------------------------
# 定义标签列表,包括'No Fraud'和'Fraud' labels = ['No Fraud', 'Fraud'] # 使用最佳模型对原始测试数据进行预测 smote_prediction = best_est.predict(original_Xtest) # 打印分类报告,包括原始测试数据的真实标签和使用SMOTE过采样后的预测结果,目标标签为labels print(classification_report(original_ytest, smote_prediction, target_names=labels))
precision recall f1-score support No Fraud 1.00 0.99 0.99 56863 Fraud 0.10 0.86 0.19 98 accuracy 0.99 56961 macro avg 0.55 0.92 0.59 56961 weighted avg 1.00 0.99 0.99 56961
# 使用训练好的模型best_est对原始测试数据original_Xtest进行预测,并返回预测结果的得分值 y_score = best_est.decision_function(original_Xtest)
# 计算平均精确率-召回率得分 average_precision = average_precision_score(original_ytest, y_score) # 打印平均精确率-召回率得分 print('Average precision-recall score: {0:0.2f}'.format( average_precision))
Average precision-recall score: 0.75
# 绘制图形 fig = plt.figure(figsize=(12,6)) # 计算精确率和召回率 precision, recall, _ = precision_recall_curve(original_ytest, y_score) # 绘制阶梯图 plt.step(recall, precision, color='r', alpha=0.2, where='post') # 填充阶梯图下方的区域 plt.fill_between(recall, precision, step='post', alpha=0.2, color='#F59B00') # 设置x轴和y轴的标签 plt.xlabel('Recall') plt.ylabel('Precision') # 设置y轴的范围 plt.ylim([0.0, 1.05]) # 设置x轴的范围 plt.xlim([0.0, 1.0]) # 设置标题 plt.title('OverSampling Precision-Recall curve: \n Average Precision-Recall Score ={0:0.2f}'.format(average_precision), fontsize=16)
Text(0.5, 1.0, 'OverSampling Precision-Recall curve: \n Average Precision-Recall Score =0.75')
# 导入SMOTE技术的库 # SMOTE是一种过采样技术,用于处理不平衡的数据集 # 在拆分和交叉验证之后使用SMOTE技术 sm = SMOTE(ratio='minority', random_state=42) # 使用SMOTE技术对训练集进行过采样 # X_train和y_train是原始的训练集数据 # Xsm_train和ysm_train是经过过采样后的训练集数据 Xsm_train, ysm_train = sm.fit_sample(X_train, y_train) # 这将是我们要处理的数据 # original_Xtrain和original_ytrain是原始的训练集数据 # Xsm_train和ysm_train是经过过采样后的训练集数据 Xsm_train, ysm_train = sm.fit_sample(original_Xtrain, original_ytrain)
# 导入所需的模块和函数 import time # 开始计时 t0 = time.time() # 使用GridSearchCV得到的最佳模型进行逻辑回归训练 log_reg_sm = grid_log_reg.best_estimator_ log_reg_sm.fit(Xsm_train, ysm_train) # 结束计时 t1 = time.time() # 打印训练所花费的时间 print("拟合过采样数据所花费的时间为:{}秒".format(t1 - t0))
Fitting oversample data took :14.371394634246826 sec
逻辑回归的测试数据:
混淆矩阵:
正/负: 类别(标签)的类型 [“否”, “是”]
真/假: 模型分类的正确或错误。真负例(左上方的方块): 这是“否”(未检测到欺诈)类别的正确分类数量。
假负例(右上方的方块): 这是“否”(未检测到欺诈)类别的错误分类数量。
假正例(左下方的方块): 这是“是”(检测到欺诈)类别的错误分类数量。
真正例(右下方的方块): 这是“是”(检测到欺诈)类别的正确分类数量。
总结:
- 随机欠采样: 我们将在随机欠采样子集中评估分类模型的最终性能。 请记住,这不是原始数据框中的数据。
- 分类模型: 表现最好的模型是逻辑回归和支持向量分类器(SVM)。
# 导入混淆矩阵函数 from sklearn.metrics import confusion_matrix # 使用SMOTE技术拟合的逻辑回归模型 y_pred_log_reg = log_reg_sm.predict(X_test) # 使用欠采样技术拟合的其他模型 y_pred_knear = knears_neighbors.predict(X_test) y_pred_svc = svc.predict(X_test) y_pred_tree = tree_clf.predict(X_test) # 计算逻辑回归模型的混淆矩阵 log_reg_cf = confusion_matrix(y_test, y_pred_log_reg) # 计算KNearsNeighbors模型的混淆矩阵 kneighbors_cf = confusion_matrix(y_test, y_pred_knear) # 计算支持向量机模型的混淆矩阵 svc_cf = confusion_matrix(y_test, y_pred_svc) # 计算决策树模型的混淆矩阵 tree_cf = confusion_matrix(y_test, y_pred_tree) # 创建一个2x2的图表 fig, ax = plt.subplots(2, 2,figsize=(22,12)) # 在第一个子图中绘制逻辑回归模型的混淆矩阵热力图 sns.heatmap(log_reg_cf, ax=ax[0][0], annot=True, cmap=plt.cm.copper) ax[0, 0].set_title("逻辑回归模型混淆矩阵", fontsize=14) ax[0, 0].set_xticklabels(['', ''], fontsize=14, rotation=90) ax[0, 0].set_yticklabels(['', ''], fontsize=14, rotation=360) # 在第二个子图中绘制KNearsNeighbors模型的混淆矩阵热力图 sns.heatmap(kneighbors_cf, ax=ax[0][1], annot=True, cmap=plt.cm.copper) ax[0][1].set_title("KNearsNeighbors模型混淆矩阵", fontsize=14) ax[0][1].set_xticklabels(['', ''], fontsize=14, rotation=90) ax[0][1].set_yticklabels(['', ''], fontsize=14, rotation=360) # 在第三个子图中绘制支持向量机模型的混淆矩阵热力图 sns.heatmap(svc_cf, ax=ax[1][0], annot=True, cmap=plt.cm.copper) ax[1][0].set_title("支持向量机模型混淆矩阵", fontsize=14) ax[1][0].set_xticklabels(['', ''], fontsize=14, rotation=90) ax[1][0].set_yticklabels(['', ''], fontsize=14, rotation=360) # 在第四个子图中绘制决策树模型的混淆矩阵热力图 sns.heatmap(tree_cf, ax=ax[1][1], annot=True, cmap=plt.cm.copper) ax[1][1].set_title("决策树模型混淆矩阵", fontsize=14) ax[1][1].set_xticklabels(['', ''], fontsize=14, rotation=90) ax[1][1].set_yticklabels(['', ''], fontsize=14, rotation=360) # 显示图表 plt.show()
# 导入classification_report函数 from sklearn.metrics import classification_report # 输出Logistic Regression的分类报告 print('Logistic Regression:') print(classification_report(y_test, y_pred_log_reg)) # 输出KNears Neighbors的分类报告 print('KNears Neighbors:') print(classification_report(y_test, y_pred_knear)) # 输出Support Vector Classifier的分类报告 print('Support Vector Classifier:') print(classification_report(y_test, y_pred_svc)) # 输出Support Vector Classifier的分类报告 print('Support Vector Classifier:') print(classification_report(y_test, y_pred_tree))
Logistic Regression: precision recall f1-score support 0 0.90 0.99 0.94 91 1 0.99 0.90 0.94 99 accuracy 0.94 190 macro avg 0.94 0.94 0.94 190 weighted avg 0.95 0.94 0.94 190 KNears Neighbors: precision recall f1-score support 0 0.87 1.00 0.93 91 1 1.00 0.86 0.92 99 accuracy 0.93 190 macro avg 0.93 0.93 0.93 190 weighted avg 0.94 0.93 0.93 190 Support Vector Classifier: precision recall f1-score support 0 0.88 0.99 0.93 91 1 0.99 0.88 0.93 99 accuracy 0.93 190 macro avg 0.94 0.93 0.93 190 weighted avg 0.94 0.93 0.93 190 Support Vector Classifier: precision recall f1-score support 0 0.87 0.99 0.93 91 1 0.99 0.87 0.92 99 accuracy 0.93 190 macro avg 0.93 0.93 0.93 190 weighted avg 0.93 0.93 0.93 190
# 导入所需的库 from sklearn.metrics import accuracy_score # 使用逻辑回归模型在测试集上进行预测 y_pred = log_reg.predict(X_test) # 计算逻辑回归模型在测试集上的准确率 undersample_score = accuracy_score(y_test, y_pred) # 使用SMOTE技术的逻辑回归模型进行预测 y_pred_sm = best_est.predict(original_Xtest) # 计算使用SMOTE技术的逻辑回归模型在原始测试集上的准确率 oversample_score = accuracy_score(original_ytest, y_pred_sm) # 创建一个字典,包含两种技术的名称和对应的准确率 d = {'Technique': ['Random UnderSampling', 'Oversampling (SMOTE)'], 'Score': [undersample_score, oversample_score]} # 将字典转换为DataFrame final_df = pd.DataFrame(data=d) # 移动列 score = final_df['Score'] final_df.drop('Score', axis=1, inplace=True) final_df.insert(1, 'Score', score) # 注意准确率得分可能会误导 final_df
神经网络测试随机欠采样数据与过采样数据(SMOTE):
在本节中,我们将实现一个简单的神经网络(具有一个隐藏层),以便查看我们在(欠采样或过采样(SMOTE))中实施的两个逻辑回归模型中哪个对于检测欺诈和非欺诈交易的准确性更好。
我们的主要目标:
我们的主要目标是探索我们的简单神经网络在随机欠采样和过采样数据集中的行为,并查看它们是否能够准确预测非欺诈和欺诈案例。为什么不仅关注欺诈?想象一下,你是一位持卡人,在购买商品后,你的卡被银行的算法认为是欺诈而被封锁。这就是为什么我们不仅应该强调检测欺诈案例,还应该正确分类非欺诈交易的原因。
混淆矩阵:
以下是混淆矩阵的工作原理:
- 左上方方块:模型正确分类的非欺诈交易的数量。
- 右上方方块:将交易错误分类为欺诈案例的数量,但实际标签为非欺诈。
- 左下方方块:将交易错误分类为非欺诈案例的数量,但实际标签为欺诈。
- 右下方方块:模型正确分类的欺诈交易的数量。
总结(Keras || 随机欠采样):
- 数据集:在这个最后的测试阶段,我们将在随机欠采样的子集和过采样的数据集(SMOTE)中拟合这个模型,以便使用原始数据集的测试数据来预测最终结果。
- 神经网络结构:如前所述,这将是一个简单的模型,由一个输入层(节点数等于特征数加上偏置节点),一个包含32个节点的隐藏层和一个由两个可能结果0或1(非欺诈或欺诈)组成的输出节点。
- 其他特征:学习率为0.001,我们将使用AdamOptimizer优化器,在这种情况下使用的激活函数是"Relu",对于最终输出,我们将使用稀疏分类交叉熵,它给出一个实例案例是非欺诈还是欺诈的概率(预测将选择两者之间的最高概率)。
# 导入所需的库 import keras from keras import backend as K from keras.models import Sequential from keras.layers import Activation from keras.layers.core import Dense from keras.optimizers import Adam from keras.metrics import categorical_crossentropy # 获取输入数据的特征数量 n_inputs = X_train.shape[1] # 创建一个Sequential模型 undersample_model = Sequential([ Dense(n_inputs, input_shape=(n_inputs, ), activation='relu'), # 添加一个全连接层,输入特征数量为n_inputs,激活函数为ReLU Dense(32, activation='relu'), # 添加一个全连接层,神经元数量为32,激活函数为ReLU Dense(2, activation='softmax') # 添加一个全连接层,神经元数量为2,激活函数为Softmax ])
Using TensorFlow backend.
# 打印undersample_model的模型概述信息 undersample_model.summary()
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_1 (Dense) (None, 30) 930 _________________________________________________________________ dense_2 (Dense) (None, 32) 992 _________________________________________________________________ dense_3 (Dense) (None, 2) 66 ================================================================= Total params: 1,988 Trainable params: 1,988 Non-trainable params: 0 _________________________________________________________________
# 使用Adam优化器,学习率为0.001,编译模型 undersample_model.compile(Adam(lr=0.001), loss='sparse_categorical_crossentropy', metrics=['accuracy'])
# 使用undersample_model对训练数据进行训练 # 参数X_train为训练数据的特征向量 # 参数y_train为训练数据的标签 # 参数validation_split为验证集的比例,本例中为0.2,即将训练数据的20%作为验证集 # 参数batch_size为每次训练的样本数,本例中为25 # 参数epochs为训练的轮数,本例中为20 # 参数shuffle为是否在每轮训练前打乱数据顺序,本例中为True # 参数verbose为训练过程中是否输出详细信息,本例中为2,表示每个epoch输出一次信息 undersample_model.fit(X_train, y_train, validation_split=0.2, batch_size=25, epochs=20, shuffle=True, verbose=2)
Train on 605 samples, validate on 152 samples Epoch 1/20 - 0s - loss: 0.4640 - acc: 0.7455 - val_loss: 0.3672 - val_acc: 0.8684 Epoch 2/20 - 0s - loss: 0.3475 - acc: 0.8579 - val_loss: 0.2970 - val_acc: 0.9342 Epoch 3/20 - 0s - loss: 0.2830 - acc: 0.9107 - val_loss: 0.2592 - val_acc: 0.9342 Epoch 4/20 - 0s - loss: 0.2364 - acc: 0.9388 - val_loss: 0.2336 - val_acc: 0.9211 Epoch 5/20 - 0s - loss: 0.2038 - acc: 0.9421 - val_loss: 0.2161 - val_acc: 0.9211 Epoch 6/20 - 0s - loss: 0.1798 - acc: 0.9488 - val_loss: 0.1980 - val_acc: 0.9211 Epoch 7/20 - 0s - loss: 0.1621 - acc: 0.9504 - val_loss: 0.1890 - val_acc: 0.9276 Epoch 8/20 - 0s - loss: 0.1470 - acc: 0.9521 - val_loss: 0.1864 - val_acc: 0.9276 Epoch 9/20 - 0s - loss: 0.1367 - acc: 0.9554 - val_loss: 0.1838 - val_acc: 0.9276 Epoch 10/20 - 0s - loss: 0.1281 - acc: 0.9603 - val_loss: 0.1826 - val_acc: 0.9211 Epoch 11/20 - 0s - loss: 0.1218 - acc: 0.9537 - val_loss: 0.1795 - val_acc: 0.9211 Epoch 12/20 - 0s - loss: 0.1134 - acc: 0.9570 - val_loss: 0.1856 - val_acc: 0.9211 Epoch 13/20 - 0s - loss: 0.1071 - acc: 0.9587 - val_loss: 0.1852 - val_acc: 0.9276 Epoch 14/20 - 0s - loss: 0.1015 - acc: 0.9620 - val_loss: 0.1790 - val_acc: 0.9211 Epoch 15/20 - 0s - loss: 0.0966 - acc: 0.9587 - val_loss: 0.1842 - val_acc: 0.9276 Epoch 16/20 - 0s - loss: 0.0910 - acc: 0.9636 - val_loss: 0.1813 - val_acc: 0.9276 Epoch 17/20 - 0s - loss: 0.0871 - acc: 0.9620 - val_loss: 0.1831 - val_acc: 0.9276 Epoch 18/20 - 0s - loss: 0.0835 - acc: 0.9636 - val_loss: 0.1822 - val_acc: 0.9276 Epoch 19/20 - 0s - loss: 0.0791 - acc: 0.9702 - val_loss: 0.1822 - val_acc: 0.9276 Epoch 20/20 - 0s - loss: 0.0751 - acc: 0.9752 - val_loss: 0.1877 - val_acc: 0.9211 <keras.callbacks.History at 0x7f056fd8e278>
# 使用下采样模型对原始测试数据进行预测 undersample_predictions = undersample_model.predict(original_Xtest, batch_size=200, verbose=0) # undersample_predictions:下采样模型对原始测试数据的预测结果 # undersample_model.predict:使用下采样模型对数据进行预测 # original_Xtest:原始测试数据 # batch_size=200:每次预测的批次大小为200 # verbose=0:不显示预测过程的详细信息
# 使用下采样模型对原始测试数据进行预测 undersample_fraud_predictions = undersample_model.predict_classes(original_Xtest, batch_size=200, verbose=0)
# 导入必要的库 import itertools # 创建一个混淆矩阵 def plot_confusion_matrix(cm, classes, normalize=False, title='Confusion matrix', cmap=plt.cm.Blues): """ 这个函数打印并绘制混淆矩阵。 可以通过设置`normalize=True`来进行归一化。 """ if normalize: # 如果需要归一化,则将混淆矩阵的每个元素除以该行的总和 cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis] print("Normalized confusion matrix") else: print('Confusion matrix, without normalization') # 打印混淆矩阵 print(cm) # 绘制混淆矩阵图像 plt.imshow(cm, interpolation='nearest', cmap=cmap) plt.title(title, fontsize=14) plt.colorbar() tick_marks = np.arange(len(classes)) plt.xticks(tick_marks, classes, rotation=45) plt.yticks(tick_marks, classes) fmt = '.2f' if normalize else 'd' thresh = cm.max() / 2. # 在每个单元格中添加文本,显示混淆矩阵的值 for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])): plt.text(j, i, format(cm[i, j], fmt), horizontalalignment="center", color="white" if cm[i, j] > thresh else "black") plt.tight_layout() plt.ylabel('True label') plt.xlabel('Predicted label')
# 计算Random UnderSample的混淆矩阵 undersample_cm = confusion_matrix(original_ytest, undersample_fraud_predictions) # 计算100%准确率的混淆矩阵 actual_cm = confusion_matrix(original_ytest, original_ytest) # 定义标签 labels = ['No Fraud', 'Fraud'] # 创建一个16x8的图像对象 fig = plt.figure(figsize=(16,8)) # 在图像对象中添加第一个子图,用于显示Random UnderSample的混淆矩阵 fig.add_subplot(221) plot_confusion_matrix(undersample_cm, labels, title="Random UnderSample \n Confusion Matrix", cmap=plt.cm.Reds) # 在图像对象中添加第二个子图,用于显示100%准确率的混淆矩阵 fig.add_subplot(222) plot_confusion_matrix(actual_cm, labels, title="Confusion Matrix \n (with 100% accuracy)", cmap=plt.cm.Greens)
Confusion matrix, without normalization [[55148 1715] [ 8 90]] Confusion matrix, without normalization [[56863 0] [ 0 98]]
Keras || 过采样 (SMOTE):
# 定义输入特征的数量 n_inputs = Xsm_train.shape[1] # 创建一个序列模型 oversample_model = Sequential([ # 添加一个全连接层,输入特征的数量为n_inputs,激活函数为ReLU Dense(n_inputs, input_shape=(n_inputs, ), activation='relu'), # 添加一个全连接层,神经元数量为32,激活函数为ReLU Dense(32, activation='relu'), # 添加一个全连接层,神经元数量为2,激活函数为Softmax Dense(2, activation='softmax') ])
# 编译模型 # 使用Adam优化器,学习率为0.001 # 使用稀疏分类交叉熵作为损失函数 # 使用准确率作为评估指标 oversample_model.compile(Adam(lr=0.001), loss='sparse_categorical_crossentropy', metrics=['accuracy'])
# 使用oversample_model对数据进行过采样训练 # Xsm_train: 过采样后的训练数据特征 # ysm_train: 过采样后的训练数据标签 # validation_split=0.2: 将训练数据的20%作为验证集 # batch_size=300: 每次训练的批次大小为300 # epochs=20: 进行20轮训练 # shuffle=True: 在每轮训练之前对数据进行洗牌 # verbose=2: 显示训练过程中的详细信息,包括每个epoch的进度和损失值 oversample_model.fit(Xsm_train, ysm_train, validation_split=0.2, batch_size=300, epochs=20, shuffle=True, verbose=2)
Train on 363923 samples, validate on 90981 samples Epoch 1/20 - 3s - loss: 0.0641 - acc: 0.9771 - val_loss: 0.0152 - val_acc: 0.9977 Epoch 2/20 - 3s - loss: 0.0130 - acc: 0.9972 - val_loss: 0.0070 - val_acc: 0.9995 Epoch 3/20 - 2s - loss: 0.0078 - acc: 0.9987 - val_loss: 0.0044 - val_acc: 1.0000 Epoch 4/20 - 2s - loss: 0.0060 - acc: 0.9990 - val_loss: 0.0030 - val_acc: 1.0000 Epoch 5/20 - 3s - loss: 0.0046 - acc: 0.9992 - val_loss: 0.0036 - val_acc: 0.9999 Epoch 6/20 - 2s - loss: 0.0035 - acc: 0.9993 - val_loss: 0.0012 - val_acc: 1.0000 Epoch 7/20 - 2s - loss: 0.0038 - acc: 0.9994 - val_loss: 0.0017 - val_acc: 1.0000 Epoch 8/20 - 2s - loss: 0.0028 - acc: 0.9995 - val_loss: 0.0027 - val_acc: 0.9999 Epoch 9/20 - 2s - loss: 0.0022 - acc: 0.9996 - val_loss: 0.0022 - val_acc: 1.0000 Epoch 10/20 - 2s - loss: 0.0021 - acc: 0.9996 - val_loss: 0.0017 - val_acc: 1.0000 Epoch 11/20 - 2s - loss: 0.0020 - acc: 0.9996 - val_loss: 0.0013 - val_acc: 1.0000 Epoch 12/20 - 2s - loss: 0.0018 - acc: 0.9997 - val_loss: 2.8322e-04 - val_acc: 1.0000 Epoch 13/20 - 3s - loss: 0.0018 - acc: 0.9996 - val_loss: 0.0035 - val_acc: 0.9994 Epoch 14/20 - 3s - loss: 0.0018 - acc: 0.9997 - val_loss: 9.4907e-04 - val_acc: 1.0000 Epoch 15/20 - 3s - loss: 0.0014 - acc: 0.9998 - val_loss: 1.5897e-04 - val_acc: 1.0000 Epoch 16/20 - 2s - loss: 0.0015 - acc: 0.9997 - val_loss: 5.9093e-04 - val_acc: 1.0000 Epoch 17/20 - 3s - loss: 0.0016 - acc: 0.9997 - val_loss: 3.7523e-04 - val_acc: 1.0000 Epoch 18/20 - 2s - loss: 0.0014 - acc: 0.9998 - val_loss: 2.7042e-04 - val_acc: 1.0000 Epoch 19/20 - 3s - loss: 0.0020 - acc: 0.9997 - val_loss: 2.2361e-04 - val_acc: 1.0000 Epoch 20/20 - 2s - loss: 0.0012 - acc: 0.9998 - val_loss: 1.8081e-04 - val_acc: 1.0000 <keras.callbacks.History at 0x7f056234b470>
# 使用oversample_model对original_Xtest进行预测 # 参数batch_size设置为200,表示每次预测的样本数量为200 # 参数verbose设置为0,表示不显示预测过程中的详细信息 oversample_predictions = oversample_model.predict(original_Xtest, batch_size=200, verbose=0)
# 使用oversample_model对original_Xtest进行预测,返回预测的类别 oversample_fraud_predictions = oversample_model.predict_classes(original_Xtest, batch_size=200, verbose=0)
# 计算过采样后的混淆矩阵 oversample_smote = confusion_matrix(original_ytest, oversample_fraud_predictions) # 计算实际混淆矩阵 actual_cm = confusion_matrix(original_ytest, original_ytest) # 定义标签 labels = ['No Fraud', 'Fraud'] # 创建一个图形对象 fig = plt.figure(figsize=(16,8)) # 添加第一个子图,显示过采样后的混淆矩阵 fig.add_subplot(221) plot_confusion_matrix(oversample_smote, labels, title="OverSample (SMOTE) \n Confusion Matrix", cmap=plt.cm.Oranges) # 添加第二个子图,显示实际混淆矩阵 fig.add_subplot(222) plot_confusion_matrix(actual_cm, labels, title="Confusion Matrix \n (with 100% accuracy)", cmap=plt.cm.Greens)
Confusion matrix, without normalization [[56851 12] [ 33 65]] Confusion matrix, without normalization [[56863 0] [ 0 98]]
结论:
在我们的不平衡数据集上实施SMOTE有助于解决标签不平衡的问题(非欺诈交易比欺诈交易多)。然而,我仍然必须指出,有时候在过采样的数据集上,神经网络对欺诈交易的预测比使用欠采样数据集的模型少。然而,请记住,异常值的去除仅在随机欠采样数据集上实施,而不是在过采样数据集上实施。此外,在我们的欠采样数据中,我们的模型无法正确检测大量的非欺诈交易,并将这些非欺诈交易错误地分类为欺诈案例。想象一下,由于我们的模型将该交易分类为欺诈交易,导致那些进行常规购买的人的卡被封锁,这对金融机构来说将是一个巨大的不利因素。客户投诉和客户不满的数量将增加。这个分析的下一步将是在我们的过采样数据集上进行异常值去除,并查看我们在测试集中的准确性是否提高。
**注意:**最后一件事,预测和准确性可能会发生变化,因为我在两种类型的数据框上实施了数据洗牌。主要的问题是看看我们的模型是否能够正确分类非欺诈和欺诈交易。我将带来更多更新,请继续关注!
代码链接
https://download.csdn.net/download/wjjc1017/88646449