全文共7940字,預計學習時長30分鐘或更長
本文將以一家造紙廠的生產為例,介紹如何使用自動編碼器構建罕見事件分類器。
現實生活中罕見事件的數據集
背景
1. 什麼是極端罕見事件?
在罕見事件問題中,數據集是不平衡的。也就是說,正樣本比負樣本數量少。典型罕見事件問題的正樣本數約佔總數的5-10%。而在極端罕見的事件問題中,正樣本數據只有不到1%。例如,本文使用的數據集裡,這一比例只有約0.6%。
這種極端罕見的事件問題在現實世界中非常常見,例如,工廠中的機器故障或在網上點擊購買時頁面失蹤。
對這些罕見事件進行分類非常有挑戰性。近來,深度學習被廣泛應用於分類中。然而正樣本數太少不利於深度學習的應用。不論數據總量多大,深度學習的使用都會受制於陽性數據的數量。
2. 為什麼要使用深度學習?
這個問題很合理。為什麼不考慮使用其他機器學習方法呢?
答案很主觀。我們總是可以採用某種機器學習方法來達到目的。為了使其成功,可以對負樣本數據進行欠採樣,以獲得接近更平衡的數據集。由於只有0.6%的正樣本數據,欠採樣將會導致數據集大小約為原始數據集的1%。機器學習方法如SVM或Random Forest仍然適用於這種大小的數據集。然而,其準確性將受到限制。剩下約99%的數據中的信息將無法使用。
如果數據足夠的話,深度學習或許更有效。它還能通過使用不同的體系結構實現模型改進的靈活性。因此,我們選擇嘗試使用深度學習的方法。
在本文中,我們將學習如何使用一個簡單的全連接層自動編碼器來構建罕見事件分類器。本文是為了演示如何使用自動編碼器來實現極端罕見事件分類器的構建。用戶可以自行探索自動編碼器的不同架構和配置。
用自動編碼器進行分類
用自動編碼器分類類似於異常檢測。在異常檢測中,先學習正常過程的模式。任何不遵循此模式的都被歸類為異常。對於罕見事件的二進位分類,可以採用類似的方法使用自動編碼器。
1. 什麼是自動編碼器?
· 自動編碼器由編碼器和解碼器兩個模塊組成。
· 編碼器學習某一進程的隱含特性。這些特性通常在一個降低的維度中。
· 解碼器可以根據這些隱含特性重新創建原始數據。
圖1解釋自動編碼器[資料來源: iSystems Design實驗室SeungchulLee教授]
2. 如何使用自動編碼器構建罕見事件分類?
· 將數據分為正標記和負標記兩部分。
· 負標記的數據視為正常狀態——無事件。
· 忽略正標記的數據,用負標記數據訓練自動編碼器。
· 所以重構誤差的概率就很小。
· 然而,如果試圖從稀有事件中重構數據,自動編碼器就很難工作。
· 這就會造成在罕見事件中發生重構誤差的概率比較高。
· 如此高的重構誤差,並將其標記為罕見事件預測。
· 此過程與異常檢測方法類似。
實際應用
1. 數據和問題
這是來自一家造紙廠關於紙張破損的二進位標記數據。紙張破損在造紙業是個很嚴重的問題。一起紙張破損就會造成數千美元損失,而造紙廠每天都會有數幾起破損。這導致每年數百萬美元的損失和工作風險。
因為生產過程本身的性質,很難檢測到紙張破損。破損概率降低5%都能給廠家帶來巨大的利益。
我們的數據包含15天內收集的約18,000行數據。y列包含兩類標籤,1表示紙張破損。其餘列是預測,正標記樣本約124例(約0.6%)。
2. 編碼
導入所需的庫。
%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
from pylab import rcParams
import tensorflow as tf
from keras.models import Model, load_model
from keras.layers import Input, Dense
from keras.callbacks import ModelCheckpoint, TensorBoard
from keras import regularizers
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, precision_recall_curve
from sklearn.metrics import recall_score, classification_report, auc, roc_curve
from sklearn.metrics import precision_recall_fscore_support, f1_score
from numpy.random import seed
seed(1)
from tensorflow import set_random_seed
set_random_seed(2)
SEED = 123 #used to help randomly select the data points
DATA_SPLIT_PCT = 0.2
rcParams['figure.figsize'] = 8, 6
LABELS = ["Normal","Break"]
注意,我們正在為可重現結果設置隨機種子。
3. 數據處理
現在,讀取並準備數據。
df = pd.read_csv("data/processminer-rare-event-mts - data.csv")這個罕見事件問題的目標就是在紙張破損發生之前就及時做出預測。我們試著在破損發生前四分鐘就要預測到。為了建立這個模型,把標籤向上移動兩行(相當於4分鐘)。只要df.y=df.y.shift(-2)就行了。然而針對這個問題,需要做出如下改變:如果第n行是陽性的,
· 令行(n-2)和(n-1)等於1。這將幫助分類器學會提前最多4分鐘預測。
· 刪除第n行。因為分類器不需要學會在事件發生時做出預測。
為了這個複雜的變化需要用下面的UDF。
sign = lambda x: (1, -1)[x < 0]
def curve_shift(df, shift_by):
'''
This function will shift the binary labels in a dataframe.
The curve shift will be with respect to the 1s.
For example, if shift is -2, the following process
will happen: if row n is labeled as 1, then
- Make row (n+shift_by):(n+shift_by-1) = 1.
- Remove row n.
i.e. the labels will be shifted up to 2 rows up.
Inputs:
df A pandas dataframe with a binary labeled column.
This labeled column should be named as 'y'.
shift_by An integer denoting the number of rows to shift.
Output
df A dataframe with the binary labels shifted by shift.
'''
vector = df['y'].copy()
for s in range(abs(shift_by)):
tmp = vector.shift(sign(shift_by))
tmp = tmp.fillna(0)
vector += tmp
labelcol = 'y'
# Add vector to the df
df.insert(loc=0, column=labelcol+'tmp', value=vector)
# Remove the rows with labelcol == 1.
df = df.drop(df[df[labelcol] == 1].index)
# Drop labelcol and rename the tmp col as labelcol
df = df.drop(labelcol, axis=1)
df = df.rename(columns={labelcol+'tmp': labelcol})
# Make the labelcol binary
df.loc[df[labelcol] > 0, labelcol] = 1
return df
現在,將數據分為訓練集、有效集和測試集。然後迅速使用只有0的數據子集來訓練自動編碼器。
df_train, df_test = train_test_split(df, test_size=DATA_SPLIT_PCT, random_state=SEED)
df_train, df_valid = train_test_split(df_train,
test_size=DATA_SPLIT_PCT, random_state=SEED)
df_train_0 = df_train.loc[df['y'] == 0]
df_train_1 = df_train.loc[df['y'] == 1]
df_train_0_x = df_train_0.drop(['y'], axis=1)
df_train_1_x = df_train_1.drop(['y'], axis=1)
4. 標準化
自動編碼器最好使用標準化數據(轉換為Gaussian、均值0、方差1)。
scaler = StandardScaler().fit(df_train_0_x)
df_train_0_x_rescaled = scaler.transform(df_train_0_x)
df_valid_0_x_rescaled = scaler.transform(df_valid_0_x)
df_valid_x_rescaled = scaler.transform(df_valid.drop(['y'], axis = 1))
df_test_0_x_rescaled = scaler.transform(df_test_0_x)
df_test_x_rescaled = scaler.transform(df_test.drop(['y'], axis = 1))
由自動編碼器構造的分類器
1. 初始化
首先,初始化自動編碼器架構。先構建一個簡單的自動編碼器,稍後再探索更複雜的架構和配置。
nb_epoch = 100
batch_size = 128
input_dim = df_train_0_x_rescaled.shape[1] #num of predictor variables,
encoding_dim = 32
hidden_dim = int(encoding_dim / 2)
learning_rate = 1e-3
input_layer = Input(shape=(input_dim, ))
encoder = Dense(encoding_dim, activation="tanh",
activity_regularizer=regularizers.l1(learning_rate))(input_layer)
encoder = Dense(hidden_dim, activation="relu")(encoder)
decoder = Dense(hidden_dim, activation='tanh')(encoder)
decoder = Dense(input_dim, activation='relu')(decoder)
autoencoder = Model(inputs=input_layer, outputs=decoder)
2. 訓練
訓練模型並將其保存在文件中。保存訓練過的模型會為將來的分析省很多時間。
autoencoder.compile(metrics=['accuracy'],
loss='mean_squared_error',
optimizer='adam')
cp = ModelCheckpoint(filepath="autoencoder_classifier.h5",
save_best_only=True,
verbose=0)
tb = TensorBoard(log_dir='./logs',
histogram_freq=0,
write_graph=True,
write_images=True)
history=autoencoder.fit(df_train_0_x_rescaled,df_train_0_x_rescaled,
epochs=nb_epoch,
batch_size=batch_size,
shuffle=True,
validation_data=(df_valid_0_x_rescaled,
df_valid_0_x_rescaled),
verbose=1,
callbacks=[cp, tb]).history
圖2:自動編碼器的損失曲線
3. 分類
接下來將展示如何利用自動編碼器重構誤差來構造罕見事件分類器。
如前所述,如果重構誤差較大,將其歸類為紙張破損。需要確定這個閾值。
使用驗證集來確定閾值。
valid_x_predictions = autoencoder.predict(df_valid_x_rescaled)
mse = np.mean(np.power(df_valid_x_rescaled - valid_x_predictions, 2), axis=1)
error_df = pd.DataFrame({'Reconstruction_error': mse, 'True_class': df_valid['y']})
precision_rt, recall_rt, threshold_rt = precision_recall_curve(error_df.True_class,
error_df.Reconstruction_error)
plt.plot(threshold_rt, precision_rt[1:], label="Precision",linewidth=5)
plt.plot(threshold_rt, recall_rt[1:], label="Recall",linewidth=5)
plt.title('Precision and recall for different threshold values')
plt.xlabel('Threshold')
plt.ylabel('Precision/Recall')
plt.legend()
plt.show()
圖3:閾值為0.85可以在精確度和召回率之間有合理權衡。
現在,對測試數據進行分類。
不要根據測試數據來估計分類閾值,這會導致過度擬合。
test_x_predictions = autoencoder.predict(df_test_x_rescaled)
mse = np.mean(np.power(df_test_x_rescaled - test_x_predictions, 2), axis=1)
error_df_test = pd.DataFrame({'Reconstruction_error': mse, 'True_class': df_test['y']})error_df_test = error_df_test.reset_index()
threshold_fixed = 0.85
groups = error_df_test.groupby('True_class')
fig, ax = plt.subplots()
for name, group in groups:
ax.plot(group.index, group.Reconstruction_error, marker='o', ms=3.5, linestyle='',
label= "Break" if name == 1 else "Normal")
ax.hlines(threshold_fixed, ax.get_xlim()[0], ax.get_xlim()[1], colors="r", zorder=100, label='Threshold')
ax.legend()
plt.title("Reconstruction error for different classes")
plt.ylabel("Reconstruction error")
plt.xlabel("Data point index")
plt.show();
圖4:使用閾值= 0.85進行分類。閾值線上方的橙色和藍色圓點分別表示真陽性和假陽性。
圖4中,閾值線上方的橙色和藍色圓點分別表示真陽性和假陽性。可以看到上面有很多假陽性的點。為了看得更清楚,可以看一個混淆矩陣。
pred_y = [1 if e > threshold_fixed else 0 for e in error_df.Reconstruction_error.values]
conf_matrix = confusion_matrix(error_df.True_class, pred_y)
plt.figure(figsize=(12, 12))
sns.heatmap(conf_matrix, xticklabels=LABELS, yticklabels=LABELS, annot=True, fmt="d");
plt.title("Confusion matrix")
plt.ylabel('True class')
plt.xlabel('Predicted class')
plt.show()
圖5:測試預測的混淆矩陣
在32次的破損中,我們預測到了9次。注意,其中包括提前兩或四分鐘的預測。這一概率約為28%,對造紙業來說是一個不錯的召回率。假陽性率約為6.3%。這對造紙廠來說不是最好的結果,但也不壞。
該模型還可以進一步改進,降低假陽性率以提高召回率。觀察如下AUC值後探討改進方法。
ROC曲線和AUC(Area Under Curve)
false_pos_rate, true_pos_rate, thresholds = roc_curve(error_df.True_class, error_df.Reconstruction_error)roc_auc = auc(false_pos_rate, true_pos_rate,)
plt.plot(false_pos_rate, true_pos_rate, linewidth=5, label='AUC = %0.3f'% roc_auc)plt.plot([0,1],[0,1], linewidth=5)
plt.xlim([-0.01, 1])
plt.ylim([0, 1.01])
plt.legend(loc='lower right')
plt.title('Receiver operating characteristic curve (ROC)')
plt.ylabel('True Positive Rate')
plt.xlabel('False Positive Rate')
plt.show()
AUC值為0.624。
值得注意的是,這是一個(多變量的)時間序列數據。我們並未考慮數據中的時間信息/模式。
留言 點讚 關注
我們一起分享AI學習與發展的乾貨
歡迎關注全平臺AI垂類自媒體 「讀芯術」