本文译自TensorFLow的官方教程:Classification on imbalanced data
本教程演示了如何对一个高度不平衡的数据集进行分类,其中一个类中的示例数量大大超过另一个类中的示例数量。您将使用托管在Kaggle上的信用卡欺诈检测数据集。其目的是在总计284,807笔交易中检测出492笔欺诈交易。您将使用Keras来定义模型和类的权重,以帮助模型从不平衡的数据中学习。
本教程包括如下的完整代码:
- 使用Pandas加载CSV文件
- 创建训练、验证和测试集
- 使用Keras定义和训练模型(包括设置类权重)
- 使用各种指标(包括精确度和召回率)评估模型
- 尝试处理不平衡数据的常见技术,如:
- 类权重技术
- 过采样技术
初始配置
import tensorflow as tf
from tensorflow import keras
import os
import tempfile
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
import sklearn
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
mpl.rcParams['figure.figsize'] = (12, 10)
colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
数据处理与勘探
下载Kaggle信用卡欺诈数据集
Pandas是一个Python库,有很多有用的工具用于加载和处理结构化数据。它可以用来下载CSV文件,并使用Pandas DataFrame来读取文件。
file = tf.keras.utils
raw_df = pd.read_csv('https://storage.googleapis.com/download.tensorflow.org/data/creditcard.csv')
raw_df.head()
raw_df[['Time', 'V1', 'V2', 'V3', 'V4', 'V5', 'V26', 'V27', 'V28', 'Amount', 'Class']].describe()
检查数据类别的不平衡度
数据集的不平衡度:
neg, pos = np.bincount(raw_df['Class'])
total = neg + pos
print('Examples:\n Total: {}\n Positive: {} ({:.2f}% of total)\n'.format(
total, pos, 100 * pos / total))
Examples:
Total: 284807
Positive: 492 (0.17% of total)
这表明阳性样本的比例很小。
数据清理、划分和规范化
原始数据有几个问题。首先,Time和Amount列变量太多,不能直接使用。删除Time列(因为其含义不明),并对Amount列取对数以减小其范围。
cleaned_df = raw_df.copy()
# You don't want the `Time` column.
cleaned_df.pop('Time')
# The `Amount` column covers a huge range. Convert to log-space.
eps = 0.001 # 0 => 0.1¢
cleaned_df['Log Ammount'] = np.log(cleaned_df.pop('Amount')+eps)
将数据集分割为训练集、验证集和测试集。在模型训练过程中使用验证集来评估loss和任何指标,但是模型并未在这些数据上训练。测试集在训练阶段完全没有使用,只在最后用于评估模型对新数据的泛化程度。这对于不平衡的数据集尤其重要,因为过拟合是缺乏训练数据的一个重要问题。
# Use a utility from sklearn to split and shuffle your dataset.
train_df, test_df = train_test_split(cleaned_df, test_size=0.2)
train_df, val_df = train_test_split(train_df, test_size=0.2)
# Form np arrays of labels and features.
train_labels = np.array(train_df.pop('Class'))
bool_train_labels = train_labels != 0
val_labels = np.array(val_df.pop('Class'))
test_labels = np.array(test_df.pop('Class'))
train_features = np.array(train_df)
val_features = np.array(val_df)
test_features = np.array(test_df)
使用sklearn StandardScaler规范输入特征。这将使平均值为O,标准偏差为1。
scaler = StandardScaler()
train_features = scaler.fit_transform(train_features)
val_features = scaler.transform(val_features)
test_features = scaler.transform(test_features)
train_features = np.clip(train_features, -5, 5)
val_features = np.clip(val_features, -5, 5)
test_features = np.clip(test_features, -5, 5)
print('Training labels shape:', train_labels.shape)
print('Validation labels shape:', val_labels.shape)
print('Test labels shape:', test_labels.shape)
print('Training features shape:', train_features.shape)
print('Validation features shape:', val_features.shape)
print('Test features shape:', test_features.shape)
Training labels shape: (182276,)
Validation labels shape: (45569,)
Test labels shape: (56962,)
Training features shape: (182276, 29)
Validation features shape: (45569, 29)
Test features shape: (56962, 29)
注意:如果希望部署模型,保留预处理计算是非常重要的。最简单的方法是将它们实现为一层网络,并将它附加到要部署的模型中。
观察数据分布
接下来比较正例子和负例子在一些特征上的分布。观察的主要内容为:
- 这些分布有意义吗?
- 是的。你已经对输入数据进行了标准化,使其主要集中在±2范围内。
- 你能看出两种样本分布的不同吗?
- 是的,正面的例子包含了更高的极值率。
pos_df = pd.DataFrame(train_features[ bool_train_labels], columns=train_df.columns)
neg_df = pd.DataFrame(train_features[~bool_train_labels], columns=train_df.columns)
sns.jointplot(x=pos_df['V5'], y=pos_df['V6'],
kind='hex', xlim=(-5,5), ylim=(-5,5))
plt.suptitle("Positive distribution")
sns.jointplot(x=neg_df['V5'], y=neg_df['V6'],
kind='hex', xlim=(-5,5), ylim=(-5,5))
_ = plt.suptitle("Negative distribution")
定义模型和指标
定义一个函数,该函数创建一个简单的神经网络,其中包含一个全连接的隐藏层,一个dropout层以减少过拟合,以及一个输出sigmoid层,该层返回交易被欺骗的概率:
METRICS = [
keras.metrics.TruePositives(name='tp'),
keras.metrics.FalsePositives(name='fp'),
keras.metrics.TrueNegatives(name='tn'),
keras.metrics.FalseNegatives(name='fn'),
keras.metrics.BinaryAccuracy(name='accuracy'),
keras.metrics.Precision(name='precision'),
keras.metrics.Recall(name='recall'),
keras.metrics.AUC(name='auc'),
keras.metrics.AUC(name='prc', curve='PR'), # precision-recall curve
]
def make_model(