使用 Cloud AI Platform 说明欺诈检测模型

1. 概览

在本实验中,您将使用 AI Platform Notebooks 构建和训练用于识别欺诈性交易的模型,并通过 Explainable AI SDK 了解模型的预测结果。欺诈检测是一种特定于金融服务的异常检测,它给机器学习模型带来了一些有趣的挑战:数据集本身不均衡,需要对模型结果进行解释。

学习内容

您将了解如何:

  • 处理不平衡的数据集
  • AI Platform Notebooks 中使用 tf.keras 构建和评估欺诈检测模型
  • 使用笔记本中的 Explainable AI SDK 了解模型为何将交易归类为欺诈交易
  • 将模型部署到 AI Platform 并提供说明,并针对已部署的模型获取预测结果和说明

在 Google Cloud 上运行本实验的总费用约为 $1

2. 为什么要进行欺诈检测?

异常值检测可以很好地应用于机器学习,因为通常很难编写一系列基于规则的语句来识别数据中的离群值。欺诈检测是一种异常检测,它给机器学习带来了两个有趣的挑战:

  • 数据集非常不均衡:因为异常就是异常,所以异常数量并不多。当数据集均衡时,机器学习的效果最佳,因此当离群值在数据中所占的比例不到 1% 时,情况就会变得很复杂。
  • 结果需要说明:如果您正在查找欺诈性活动,您可能很希望了解系统为何将某些内容标记为欺诈性内容,而不是光凭空谈。可解释性工具可以帮助您做到这一点。

3. 设置您的环境

您需要一个启用了结算功能的 Google Cloud Platform 项目才能运行此 Codelab。如需创建项目,请按照此处的说明操作。

第 1 步:启用 Cloud AI Platform Models API

转到 Cloud 控制台的 AI Platform 模型部分,然后点击“启用”(如果尚未启用)。

d0d38662851c6af3.png

第 2 步:启用 Compute Engine API

前往 Compute Engine,然后选择启用(如果尚未启用)。您需要用它来创建笔记本实例。

第 3 步:创建 AI Platform Notebooks 实例

前往 Cloud 控制台的 AI Platform Notebooks 部分,然后点击新建实例。然后选择不带 GPUTensorFlow 企业版 2.1 实例类型:

9e2b62be57fff946

使用默认选项,然后点击创建。创建实例后,选择打开 JupyterLab

fa67fe02f2a9ba73.png

打开该实例时,从启动器中选择 Python 3 笔记本:

4390b1614ae8eae4

第 4 步:导入 Python 软件包

创建一个新单元格,并导入我们将在此 Codelab 中使用的库:

import itertools
import numpy as np
import pandas as pd
import tensorflow as tf
import json
import matplotlib as mpl
import matplotlib.pyplot as plt
import explainable_ai_sdk

from sklearn.utils import shuffle
from sklearn.metrics import confusion_matrix
from sklearn.preprocessing import StandardScaler
from tensorflow import keras
from explainable_ai_sdk.metadata.tf.v2 import SavedModelMetadataBuilder

4. 下载和处理数据

我们将使用 Kaggle 这个合成生成的数据集来训练模型。原始数据集包含 630 万行,其中 8, 000 行是欺诈性交易,仅占整个数据集的 0.1%!

第 1 步:下载 Kaggle 数据集并使用 Pandas 读取

我们已在 Google Cloud Storage 中为您提供了 Kaggle 数据集。您可以通过在 Jupyter 笔记本中运行以下 gsutil 命令来下载该笔记本:

!gsutil cp gs://financial_fraud_detection/fraud_data_kaggle.csv .

接下来,我们将数据集作为 Pandas DataFrame 读取并预览:

data = pd.read_csv('fraud_data_kaggle.csv')
data = data.drop(columns=['type'])
data.head()

您应该会在预览中看到如下内容:

8d3d9e022fce1e7f

第 2 步:考虑不平衡的数据

如上所述,该数据集目前包含 99.9% 的非欺诈样本。如果我们按原样使用数据训练模型,那么模型猜测每笔交易都不是欺诈交易,仅仅因为 99.9% 的数据不是欺诈交易,模型有可能达到 99.9% 的准确率。

您可以采用几种不同的方法来处理不均衡的数据。在这里,我们将使用降采样技术。降采样是指在训练中仅使用大多数类别的一小部分。在本例中,“非欺诈”是多数类,因为它占了 99.9% 的数据。

为了对数据集进行下采样,我们将从所有大约 8, 000 个欺诈样本和约 3.1 万个非欺诈案例中随机抽取样本。这样一来,生成的数据集将包含 25% 的欺诈案例,而之前只有 0.1%。

首先,将数据拆分到两个 DataFrame 中,一个用于欺诈,一个用于非欺诈(稍后在此 Codelab 中部署模型时,我们会用到它):

fraud = data[data['isFraud'] == 1]
not_fraud = data[data['isFraud'] == 0]

然后,从非欺诈案例中随机抽取样本。我们之所以采用 0.005% 的比例,是因为它将欺诈/非欺诈交易按 25 / 75 的比例分成。这样,您可以将数据重新整合在一起并重排。为了简化操作,我们还将移除不用于训练的几个列:

# Take a random sample of non fraud rows
not_fraud_sample = not_fraud.sample(random_state=2, frac=.005)

# Put it back together and shuffle
df = pd.concat([not_fraud_sample,fraud])
df = shuffle(df, random_state=2)

# Remove a few columns (isFraud is the label column we'll use, not isFlaggedFraud)
df = df.drop(columns=['nameOrig', 'nameDest', 'isFlaggedFraud'])

# Preview the updated dataset
df.head()

现在,我们得到了一个更加均衡的数据集。不过,如果我们发现模型的准确率在 75% 左右,很可能是猜测出“非欺诈”

第 3 步:将数据拆分为训练集和测试集

构建模型之前要做的最后一件事是拆分数据。我们将使用 80/20 的训练-测试拆分比例:

train_test_split = int(len(df) * .8)

train_set = df[:train_test_split]
test_set = df[train_test_split:]

train_labels = train_set.pop('isFraud')
test_labels = test_set.pop('isFraud')

*E.A. Lopez-Rojas 、Elmir 和 S.Axelsson。“PaySim:一款用于检测欺诈的金融移动支付模拟器”。地点:塞浦路斯拉纳卡第 28 届欧洲建模和模拟研讨会 EMSS。2016

5. 构建、训练和评估 tf.keras 模型

我们将使用 TensorFlow 的 tf.keras API 进行构建。本部分中的模型代码根据 TensorFlow 文档中的本教程构建而成。首先,我们将对数据进行归一化,然后构建和训练模型,并使用 class_weight 参数解决剩余的数据不平衡问题。

第 1 步:对数据进行标准化

使用数值数据训练模型时,将数据归一化非常重要,尤其是当每一列位于不同范围内时。这有助于防止损失在训练期间爆炸。我们可以通过以下方式将数据归一化:

scaler = StandardScaler()
train_set = scaler.fit_transform(train_set) # Only normalize on the train set
test_set = scaler.transform(test_set)

# clip() ensures all values fall within the range [-5,5]
# useful if any outliers remain after normalizing
train_set = np.clip(train_set, -5, 5)
test_set = np.clip(test_set, -5, 5)

然后,预览一下标准化数据:

train_set

第 2 步:确定类别权重

在对数据进行降采样时,我们仍希望保留一部分非欺诈交易,这样我们就不会丢失这些交易的信息,这也是我们未能使数据达到完美平衡的原因。由于数据集仍然不均衡,我们最关心如何正确识别欺诈性交易,因此我们希望模型为数据集中的欺诈样本赋予更多权重

借助 Keras class_weight 参数,我们可以根据样本在数据集中出现的频率,指定希望为每个类别提供多少权重:

weight_for_non_fraud = 1.0 / df['isFraud'].value_counts()[0]
weight_for_fraud = 1.0 / df['isFraud'].value_counts()[1]

class_weight = {0: weight_for_non_fraud, 1: weight_for_fraud}

我们将在下一步训练模型时使用此变量。

第 3 步:训练和评估模型

我们将使用 Keras Sequential Model API 来构建模型,该 API 可让我们将模型定义为层的堆栈。我们会在训练过程中跟踪许多指标,这有助于我们了解模型在数据集中的每个类别上的表现。

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'),
]

def make_model(metrics = METRICS):
  model = keras.Sequential([
      keras.layers.Dense(
          16, activation='relu',
          input_shape=(train_set.shape[-1],)),
      keras.layers.Dropout(0.5),
      keras.layers.Dense(1, activation='sigmoid'),
  ])

  model.compile(
      optimizer=keras.optimizers.Adam(lr=1e-3),
      loss=keras.losses.BinaryCrossentropy(),
      metrics=metrics)

  return model

然后,我们将定义一些要在训练期间使用的全局变量以及一些早停法参数。

EPOCHS = 100
BATCH_SIZE = 512

early_stopping = tf.keras.callbacks.EarlyStopping(
    monitor='val_auc', 
    verbose=1,
    patience=10,
    mode='max',
    restore_best_weights=True)

最后,我们将调用上面定义的函数来创建模型:

model = make_model()
model.summary()

我们可以使用 fit() 方法并传入上面定义的形参来训练模型:

results = model.fit(
    train_set,
    train_labels,
    batch_size=BATCH_SIZE,
    epochs=EPOCHS,
    callbacks = [early_stopping],
    validation_data=(test_set, test_labels),
    class_weight=class_weight)

训练需要几分钟时间才能运行。

第 4 步:直观呈现模型指标

现在,我们已经有一个经过训练的模型,接下来,我们通过绘制各种指标在整个训练周期内查看模型的表现:

mpl.rcParams['figure.figsize'] = (12, 10)
colors = plt.rcParams['axes.prop_cycle'].by_key()['color']

def plot_metrics(history):
  metrics =  ['loss', 'auc', 'precision', 'recall']
  for n, metric in enumerate(metrics):
    name = metric.replace("_"," ").capitalize()
    plt.subplot(2,2,n+1)
    plt.plot(history.epoch,  history.history[metric], color=colors[0], label='Train')
    plt.plot(history.epoch, history.history['val_'+metric],
             color=colors[0], linestyle="--", label='Val')
    plt.xlabel('Epoch')
    plt.ylabel(name)
    if metric == 'loss':
      plt.ylim([0, plt.ylim()[1]])
    elif metric == 'auc':
      plt.ylim([0.8,1])
    else:
      plt.ylim([0,1])

    plt.legend()

plot_metrics(results)

您的图表应类似于下图(但并不完全相同):

f98a88e530bb341f.png

第 5 步:输出混淆矩阵

混淆矩阵可以很好地直观呈现模型在测试数据集中的表现。对于每个类别,该部分会显示模型正确预测和错误预测的测试样本的百分比。Scikit Learn 提供了一些用于创建和绘制混淆矩阵的实用程序,我们将在这里使用它们。

在笔记本的开头,我们导入了 confusion_matrix 实用程序。要使用它,我们首先要创建一个模型预测结果列表。在这里,我们将对模型返回的值进行四舍五入,使此列表与我们的标准答案标签列表匹配:

predicted = model.predict(test_set)

y_pred = []

for i in predicted.tolist():
  y_pred.append(int(round(i[0])))

现在,我们可以将其连同标准答案标签一起馈送到 confusion_matrix 方法:

cm = confusion_matrix(test_labels.values, y_pred)
print(cm)

这显示了模型对测试集的正确预测和错误预测的绝对数量。左上角的数字显示了我们的模型正确预测为非欺诈性质的测试集中有多少样本。右下角的数字表示其被正确预测为欺诈邮件的数量(我们最关注该数字)。您可以看到,它为每个类别正确预测了大多数样本。

为便于理解,我们调整了 Scikit Learn 文档中的 plot_confusion_matrix 函数。在此处定义该函数:

def plot_confusion_matrix(cm, classes,
                          normalize=False,
                          title='Confusion matrix',
                          cmap=plt.cm.Blues):
    """
    This function prints and plots the confusion matrix.
    Normalization can be applied by setting `normalize=True`.
    """
    plt.imshow(cm, interpolation='nearest', cmap=cmap)
    plt.title(title)
    plt.colorbar()
    tick_marks = np.arange(len(classes))
    plt.xticks(tick_marks, classes, rotation=45)
    plt.yticks(tick_marks, classes)

    if normalize:
        cm = np.round(cm.astype('float') / cm.sum(axis=1)[:, np.newaxis], 3)

    thresh = cm.max() / 2.
    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
        plt.text(j, i, cm[i, j],
                 horizontalalignment="center",
                 color="white" if cm[i, j] > thresh else "black")

    plt.tight_layout()
    plt.ylabel('True label')
    plt.xlabel('Predicted label')

然后向它传递模型中的数据来创建图表。我们将此处的 normalize 设置为 True,以便混淆矩阵以百分比形式显示正确和错误预测的数量:

classes = ['not fraud', 'fraud']
plot_confusion_matrix(cm, classes, normalize=True)

您应该会看到类似下图的内容(具体数字会有所不同):

b52ef4ccddce5d8c.png

在这里,我们可以看到,我们的模型在测试集中的 1,594 笔欺诈交易中正确预测了大约 85% 的交易。请注意,本实验的重点不是模型质量,如果您在生产环境中部署欺诈检测模型,您希望欺诈类别的准确率超过 85%。本实验的目标是向您介绍用于解释基于不平衡数据集训练的模型的工具。

接下来,我们将使用 Explainable AI SDK 了解我们的模型依赖哪些特征来进行这些预测。

6. 使用 Explainable AI SDK

Explainable AI SDK 提供了用于获取模型说明的实用方法。它预先安装在 Tensorflow AI Platform Notebook 实例中。请注意,在本实验开始时,我们将它导入到笔记本中。借助 SDK,我们可以在笔记本实例中从模型获取特征归因,这意味着我们无需将模型部署到云端即可使用。

在本部分中,我们将导出刚刚训练为 Tensorflow SavedModel 的模型,然后将 SDK 对准我们保存的模型资源以获取说明。

第 1 步:导出经过训练的模型

首先,我们将模型保存到笔记本实例的某个目录中:

model_dir = 'fraud_model'
tf.saved_model.save(model, model_dir)

如果您刷新笔记本左侧边栏中的文件夹视图,您应该会看到一个名为 fraud_model/ 的新目录。

第 2 步:使用 SDK 获取说明元数据

接下来,我们将 Explainable AI SDK 指向该目录。此操作会生成获取模型说明所需的元数据。get_metadata() 方法会显示 SDK 从您的模型中推断出的元数据,例如输入名称:

model_builder = SavedModelMetadataBuilder(model_dir)
metadata = model_builder.get_metadata()
print(metadata)

可解释性有助于我们回答以下问题:“为什么我们的模型认为这是欺诈?”

第 3 步:指定模型的基准

对于表格数据,Explainable AI 服务的工作原理是返回每个特征的归因值。这些值表示特定特征对预测结果的影响程度。假设某笔交易的金额导致我们的模型将其预测欺诈概率提高了 0.2%。您可能会想的是“相对于什么呢?”0.2%。这就是基准的概念。

模型的基准本质上是其所要比较的对象。我们为模型中的每个特征选择基线值,基线预测因此成为模型在特征设置为基线时预测的值。

基准的选择取决于您解决的预测任务。对于数值特征,通常使用数据集中每个特征的中间值作为基准。不过,在欺诈检测方面,这并不是我们想要的。我们最关注如何解释我们的模型将交易标记为欺诈的情况。这意味着,我们要比较的基准情况是非欺诈交易。

为解决此问题,我们将使用数据集中非欺诈交易的中位数作为基准。我们可以使用在上面提取的 not_fraud_sample DataFrame 来获得中位数,然后对其进行缩放以匹配模型的预期输入:

not_fraud_sample = not_fraud_sample.drop(columns=['nameOrig', 'nameDest', 'isFlaggedFraud', 'isFraud'])

baseline = scaler.transform(not_fraud_sample.values)
baseline = np.clip(baseline, -5, 5)
baseline_values = np.median(baseline, axis=0)

请注意,我们不需要指定基准,否则,SDK 将使用 0 作为模型所需的每个输入值。在我们的欺诈检测用例中,可以指定基准,我们将在下面执行此操作:

input_name = list(metadata['inputs'])[0]
model_builder.set_numeric_metadata(input_name, input_baselines=[baseline_values.tolist()], index_feature_mapping=df.columns.tolist()[:6])
model_builder.save_metadata(model_dir)

运行上面的 save_metadata() 方法会在模型的目录中创建一个名为 explanation_metadata.json 的文件。在您的笔记本中,转到 spot_model/ 目录,以确认已创建文件。此文件包含 SDK 将用于生成特征归因的元数据。

第 4 步:获取模型说明

现在,我们已准备好获取各个样本的特征归因。为此,我们先使用 SDK 创建对模型的本地引用:

local_model = explainable_ai_sdk.load_model_from_local_path(
    model_dir, 
    explainable_ai_sdk.SampledShapleyConfig()
)

接下来,我们通过一个应归类为欺诈的样本交易获取模型的预测结果和说明:

fraud_example = [0.722,0.139,-0.114,-0.258,-0.271,-0.305]
response = local_model.explain([{input_name: fraud_example}])
response[0].visualize_attributions()

运行此命令应创建一个如下所示的可视化结果:

67211d9396197146

在本例中,账户在交易发生前的初始余额是最大的欺诈指标,将我们模型的预测从基准值推高了超过 0.5。其次是交易金额、目标账号余额和步骤。在数据集中,“步骤”表示一个时间单位(1 步即 1 小时)。归因值也可以是负数。

“近似误差”可让您了解自己对相应说明的信任程度。通常,误差超过 5% 意味着您可能无法使用特征归因。请记住,解释的效果取决于您使用的训练数据和模型。改进训练数据、模型或尝试不同的模型基准可以减少近似误差。

您还可以通过增加说明方法中使用的步数来减少此错误。您可以使用 SDK 更改此设置,只需在说明配置中添加 path_count 参数即可(如果未指定,则默认值为 10):

local_model = explainable_ai_sdk.load_model_from_local_path(
    model_dir, 
    explainable_ai_sdk.SampledShapleyConfig(path_count=20)
)

在此模型上,您可以使用 Explainable AI 执行更多操作。以下是一些建议:

  • 向模型发送多个样本,并计算归因值的平均数,以了解某些特征在总体上是否更重要。我们可以用它来改进模型,还可能会移除不重要的特征
  • 找出我们的模型标记为欺诈但非欺诈性交易的误报,并检查其归因价值
  • 使用其他基准,看看这对归因值有何影响

🎉 恭喜!🎉

您学习了如何说明不均衡的数据,如何训练 TensorFlow 模型检测欺诈性交易,以及如何使用 Explainable AI SDK 查看您的模型在进行个人预测时最依赖哪些特征。如果您愿意,可以到此为止。在笔记本中使用 SDK 让您可以在部署模型之前访问解释,从而简化模型开发流程。您很可能在构建好满意的模型后,想要部署该模型以获取大规模预测。如果您对此有兴趣,请继续执行后续步骤(可选)。完成后,请跳到清理步骤。

7. 可选:将模型部署到 AI Platform Prediction

在此步骤中,您将学习如何将模型部署到 AI Platform Prediction

第 1 步:将已保存的模型目录复制到 Cloud Storage 存储分区。

通过我们之前运行的 SDK 步骤,您已经拥有将模型部署到 AI Platform 所需的一切。为了准备部署,您需要将 SavedModel 资产和说明元数据放入 Explainable AI 服务可以读取的 Cloud Storage 存储分区中。

为此,我们需要定义一些环境变量。在下面的值中填写您的 Google Cloud 项目名称和您要创建的存储分区的名称(必须是全局唯一的)。

# Update these to your own GCP project and model
GCP_PROJECT = 'your-gcp-project'
MODEL_BUCKET = 'gs://storage_bucket_name'

现在,我们可以创建一个存储分区来存储导出的 TensorFlow 模型资源。部署模型时,我们将 AI Platform 指向此存储分区。

在笔记本中运行以下 gsutil 命令以创建存储分区:

!gsutil mb -l 'us-central1' $MODEL_BUCKET

然后,将您的本地模型目录复制到该存储分区中:

!gsutil -m cp -r ./$model_dir/* $MODEL_BUCKET/explanations

第 2 步:部署模型

接下来,我们将定义一些将在部署命令中使用的变量:

MODEL = 'fraud_detection'
VERSION = 'v1'
model_path = MODEL_BUCKET + '/explanations'

我们可以使用以下 gcloud 命令创建模型:

!gcloud ai-platform models create $MODEL --region=us-central1

现在,我们可以使用 gcloud 部署此模型的第一个版本了。部署该版本大约需要 5-10 分钟:

!gcloud beta ai-platform versions create $VERSION \
--model $MODEL \
--origin $model_path \
--runtime-version 2.1 \
--framework TENSORFLOW \
--python-version 3.7 \
--machine-type n1-standard-4 \
--explanation-method 'sampled-shapley' \
--num-paths 10 \
--region=us-central1

origin 标志中,我们传入已保存的模型和元数据文件的 Cloud Storage 位置。Explainable AI 目前为表格模型提供了两种不同的解释方法。在这里,我们使用采样 Shapley。num-paths 参数表示针对每个输入特征进行采样的路径数。通常,模型越复杂,需要的近似步骤就越多,才能达到合理的收敛。

如需确认您的模型是否已正确部署,请运行以下 gcloud 命令:

!gcloud ai-platform versions describe $VERSION --model $MODEL --region=us-central1

状态应为 READY

第 3 步:获取对已部署模型的预测和说明

出于可解释性的目的,我们最关注的是对模型预测欺诈的情况进行解释。我们会向模型发送 5 个测试样本,这些样本均为欺诈性交易。

我们将使用 Google Cloud CLI 获取预测结果。运行以下代码,获取测试集中所有欺诈示例的索引:

fraud_indices = []

for i,val in enumerate(test_labels):
    if val == 1:
        fraud_indices.append(i)

接下来,我们将以模型所需的格式保存 5 个样本,并将它们写入一个文件:

num_test_examples = 5

instances = []
for i in range(num_test_examples):
    ex = test_set[fraud_indices[i]]
    instances.append({input_name: ex.tolist()})

with open('prediction.json', 'a') as outputfile:
    json.dump({"instances": instances}, outputfile)

我们可以使用 gcloud 将这五个示例发送到我们的模型:

!gcloud beta ai-platform explain \
--model=$MODEL \
--version=$VERSION \
--region='us-central1' \
--json-request=prediction.json

在响应 JSON 中,您将看到这些示例中每个特征的归因值。每个样本的 example_score 键都包含模型的预测结果,在本例中即是特定交易为欺诈性交易的可能性百分比。

8. 清理

如果您想继续使用该笔记本,建议您在不使用时将其关闭。在 Cloud 控制台的 Notebooks 界面中,选择相应笔记本,然后选择停止

879147427150b6c7

如果您想删除在本实验中创建的所有资源,只需删除该笔记本实例,而无需停止该实例。

使用 Cloud 控制台中的导航菜单,浏览到“存储空间”,然后删除您为存储模型资产而创建的两个存储分区。