運用 Cloud AI 平台說明詐欺偵測模型

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 專案,才能執行這項程式碼研究室。如要建立專案,請按照這裡的說明操作。

步驟 1:啟用 Cloud AI Platform Models API

前往 Cloud Console 的 AI Platform Models 區段,如果尚未啟用,請點選「啟用」。

d0d38662851c6af3.png

步驟 2:啟用 Compute Engine API

前往「Compute Engine」,然後選取「啟用」 (如果尚未啟用)。您需要這項資訊才能建立筆記本執行個體。

步驟 3:建立 AI Platform Notebooks 執行個體

前往 Cloud Console 的 AI Platform Notebooks 專區,然後點選「建立執行個體」。然後選取「TensorFlow Enterprise 2.1」執行個體類型,且「不加入任何 GPU」

9e2b62be57fff946.png

使用預設選項,然後按一下「建立」。建立執行個體後,請選取「Open JupyterLab」

fa67fe02f2a9ba73.png

開啟執行個體後,從啟動器選取「Python 3」筆記本:

4390b1614ae8eae4.png

步驟 4:匯入 Python 套件

建立新的儲存格,並匯入本程式碼研究室會用到的程式庫:

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.png

步驟 2:考量資料不平衡問題

如上所述,目前資料集包含 99.9% 的非詐欺範例。如果我們直接使用資料訓練模型,模型很可能猜測每筆交易都不是詐欺交易,因此達到 99.9% 的準確率,因為資料中 99.9% 的案例都不是詐欺交易。

處理不平衡資料的方法有幾種,這裡會使用稱為「下採樣」的技術。降採樣是指在訓練時,只使用多數類別的一小部分。在本例中,「非詐欺」是多數類別,因為這類資料占了 99.9%。

為對資料集進行下採樣,我們會採用所有約 8,000 個詐欺範例,以及約 31,000 個非詐欺案件的隨機樣本。這樣一來,產生的資料集就會有 25% 的詐欺案件,相較於先前的 0.1% 來說,比例大幅提升。

首先,將資料分割成兩個 DataFrame,一個用於詐欺,另一個用於非詐欺 (我們會在程式碼研究室稍後部署模型時使用):

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 , A. Elmir 和 S. Axelsson。「PaySim:A financial mobile money simulator for fraud detection」。In: The 28th European Modeling and Simulation Symposium-EMSS, Larnaca, Cyprus. 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 的檔案。在筆記本中,前往 fraud_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.png

在本例中,帳戶在交易前的初始餘額是最大的詐欺指標,將模型的預測結果從基準線向上推升超過 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)
)

您還可以在這個模型上使用「可解釋 AI」執行許多其他操作。例如:

  • 將多個範例傳送至模型,並計算歸因值的平均值,判斷特定特徵是否整體而言更重要。我們可以運用這項資訊改善模型,並移除不重要的特徵
  • 找出模型標示為詐欺但並非詐欺的交易,並檢查其歸因值
  • 使用不同的基準,看看這對歸因值有何影響

🎉 恭喜!🎉

您已瞭解如何處理不平衡的資料、訓練 TensorFlow 模型來偵測詐欺交易,以及使用 Explainable AI SDK 查看模型最依賴哪些特徵進行個別預測。你可以視需要停止操作。在筆記本中使用 SDK,可讓您在部署模型前取得說明,簡化模型開發程序。建構出滿意的模型後,您可能會想部署模型,大規模取得預測結果。如果符合上述情況,請繼續進行下一個選用步驟。完成後,請跳到「清除」步驟。

7. 選用:將模型部署至 AI Platform Prediction

在本步驟中,您將瞭解如何將模型部署至 AI Platform Prediction

步驟 1:將儲存的模型目錄複製到 Cloud Storage 值區。

透過先前執行的 SDK 步驟,您已具備將模型部署至 AI Platform 的所有必要條件。如要準備部署作業,您必須將 SavedModel 資產和說明中繼資料放入 Explainable AI 服務可讀取的 Cloud Storage bucket。

為此,我們將定義一些環境變數。在下方填入 Google Cloud 專案名稱和要建立的 bucket 名稱 (必須是全域不重複的名稱)。

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

現在,我們準備建立儲存空間 bucket,用來儲存匯出的 TensorFlow 模型資產。部署模型時,我們會將 AI Platform 指向這個值區。

在筆記本中執行下列 gsutil 指令,建立 bucket:

!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 位置。可解釋 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.png

如要刪除在本實驗室中建立的所有資源,請直接刪除筆記本執行個體,而不是停止執行個體。

使用 Cloud 控制台的導覽選單瀏覽至「儲存空間」,然後刪除您建立的兩個 bucket,以儲存模型資產。