近年來,『異常檢測』這一術(shù)語(有時也稱作離群點檢測)越來越多地出現(xiàn)在互聯(lián)網(wǎng)和會議演示中,盡管這不是一個新的話題了。 有些領(lǐng)域已經(jīng)使用了很長一段時間了。 目前,由于銀行業(yè)務(wù),審計,物聯(lián)網(wǎng)(IoT)等方面的進(jìn)步,異常檢測已成為很多領(lǐng)域中相當(dāng)普遍的任務(wù)。 與其他廣泛應(yīng)用的任務(wù)一樣,異常檢測可以使用多種技術(shù)和工具來解決。 因此,在考察『異常檢測是什么』和『異常檢測的工作機(jī)制是什么』的時候,會引起不少困擾。
本文將探討如何使用Apache MXNet ,利用不同類型的神經(jīng)網(wǎng)絡(luò)來檢測時間序列數(shù)據(jù)中的異常情況。MXNet是一種快速、可擴(kuò)展的訓(xùn)練和推理框架,它提供了一個易于使用、簡潔的機(jī)器學(xué)習(xí)API。本教程使用Python Jupyter Notebook。 到本教程結(jié)束時,您應(yīng)該:
?知道什么是異常檢測,以及解決這個問題的常用技術(shù)
?自己能夠搭建MXNet環(huán)境
?判斷不同類型的網(wǎng)絡(luò)之間的差異,以及他們的優(yōu)勢和劣勢
?為這樣的任務(wù)加載和預(yù)處理數(shù)據(jù)
?在MXNet中構(gòu)建網(wǎng)絡(luò)體系結(jié)構(gòu)
?使用MXNet訓(xùn)練模型并將其用于預(yù)測
本教程中使用的所有代碼和數(shù)據(jù)都可以在GitHub上找到。
異常檢測
在提及任何機(jī)器學(xué)習(xí)任務(wù)的概念時,我傾向于在一開始就指出,許多情況下,這一任務(wù)實際上是關(guān)于『發(fā)現(xiàn)模式』的。因此在這個問題里也不例外。異常檢測的模型訓(xùn)練需要我們在訓(xùn)練數(shù)據(jù)中找到一種模式,然后我們可以根據(jù)這種模式來判斷哪些觀測點不符合這種模式。這些觀測點被稱為是『異常點』或者『離群點』。換句話說,我們將要尋找那些偏離正常模式的情況,這些情況是罕見的、意料之外的。
圖1顯示了一個人心跳異常,暗示了某種醫(yī)學(xué)上的癥狀。

圖1. Wolff-Parkinson-White綜合征是一種心跳異常,您可以清楚地看到δ波如何擴(kuò)大心室復(fù)雜性并縮短PR間期。 圖由Mateusz Dymczyk提供
我們必須在異常檢測和『新穎性檢測』之間做出重要的區(qū)分。后者會把新的,以前未被發(fā)現(xiàn)的事件暴露出來,這些事件仍然是可以接受的和預(yù)期之內(nèi)的。 例如,在某個時間點,您的信用卡賬單可能會開始顯示您從未購買過的嬰兒用品。 這些是訓(xùn)練數(shù)據(jù)中未發(fā)現(xiàn)的新觀察結(jié)果,但考慮到消費者生活的正常變化,可能是可接受的購買行為,不應(yīng)將其標(biāo)記為異常。
異常也可以用于多種用例:
? 預(yù)測性維護(hù)。 在工廠或任何類型的物聯(lián)網(wǎng)環(huán)境中,您可以使用在正常執(zhí)行模式下收集的數(shù)據(jù)構(gòu)建模型,并使用它來預(yù)測即將發(fā)生的故障。 這意味著不會讓意外的停產(chǎn)事故發(fā)生。
? 欺詐識別。 金融機(jī)構(gòu)經(jīng)常使用這種技術(shù)來捕捉到意外的消費:例如,如果您的信用卡被盜的情況下。
? 衛(wèi)生保健。 比如用于醫(yī)療診斷。
? 網(wǎng)絡(luò)安全。 你曾想過要抓住所有試圖入侵你的系統(tǒng)的入侵者嗎? 異常檢測可以幫到你。
同樣,如前所述,可以使用廣泛的方法來解決這個問題。 一些最受歡迎的方案包括:
?卡爾曼濾波器,它使用簡單的統(tǒng)計方法
?基于深度學(xué)習(xí)的自動編碼器
當(dāng)您嘗試使用大多數(shù)這些算法時會出現(xiàn)幾個問題。 例如,他們傾向于對數(shù)據(jù)做出特定的假設(shè),有些不適用于多元數(shù)據(jù)集。
這就是為什么今天我們將使用剛才最后提到的方法展開研究,這種方法使用多層感知器和長期短期記憶(LSTM)網(wǎng)絡(luò)兩個模型。
為了簡單起見,本教程只使用其中一個,其他方法可能也不錯,但是神經(jīng)網(wǎng)絡(luò)的好處是他們在模擬多元問題方面很在行,適合部署到生產(chǎn)環(huán)境中(特別是類似于本例這種情況在使用IoT時間序列數(shù)據(jù)時更加適合)。
自編碼機(jī)
我們在這里討論的網(wǎng)絡(luò)類型有很多名字:autoencoder; autoassociator; 還是我個人最喜歡的Diabolo。 該技術(shù)是一種用于無監(jiān)督學(xué)習(xí)高效編碼的人工神經(jīng)網(wǎng)絡(luò)。 簡單來說,這意味著它被用來找到一種不同的方式來表示(編碼)我們的輸入數(shù)據(jù)。 自編碼機(jī)有時也用于減少數(shù)據(jù)的尺寸。
自動編碼器通過兩個步驟找到Identity函數(shù)(Id:X→X)的近似值:
1.編碼器步驟,將輸入數(shù)據(jù)轉(zhuǎn)換為中間狀態(tài)
2.解碼器步驟,將其轉(zhuǎn)換為能夠匹配輸入特征大小的數(shù)據(jù)

圖2.自編碼機(jī)流程圖,我們輸入一個數(shù)字(4)的圖像,將其編碼為壓縮格式,然后將其解碼為圖像格式。 圖由Mateusz Dymczyk提供
用數(shù)學(xué)愛好者看得懂的方式來說,這個流程包含兩個轉(zhuǎn)換函數(shù):
?:X→F
ψ:F→X
通常,自動編碼器通過優(yōu)化輸出層和輸入層之間的均方誤差來進(jìn)行訓(xùn)練,其中X是輸入向量, Y是輸出向量, n是元素(此處為圖像像素)的數(shù)量:
Y=(ψ°?)X
MSE=1n∑ni=1(Y?X)2
在我們完成了自編碼機(jī)的訓(xùn)練之后,我們需要設(shè)置一個閾值,來判定我們是否偵測到了異常。 根據(jù)具體情況和數(shù)據(jù),有不同的方法設(shè)置這個閾值 – 例如,根據(jù)受試者操作特性曲線(ROC)或F1分?jǐn)?shù)都可以。 您設(shè)置的閾值越高,系統(tǒng)檢測異常的時間就越長(在某些情況下會檢測到更少的異常)。 在本教程中,我們將在完成訓(xùn)練模型之后對訓(xùn)練數(shù)據(jù)集進(jìn)行預(yù)測,計算每個預(yù)測的誤差,并找出這些誤差的均值和標(biāo)準(zhǔn)差。 所有高于第三個標(biāo)準(zhǔn)偏差的樣本將被標(biāo)記為異常。
系統(tǒng)設(shè)置
在我們進(jìn)入數(shù)據(jù)分析和建模之前,我們需要先安裝一些工具。 我強烈建議使用某種Python環(huán)境管理系統(tǒng),如Anaconda或Virtualenv。 本教程將使用后者。
1.安裝Virtualenv。?在大部分系統(tǒng)上簡單執(zhí)行pip install virtualenv即可安裝。
2.創(chuàng)建一個新的virtualenv環(huán)境,執(zhí)行virtualenv oreilly-anomaly即可.? 在當(dāng)前目錄下這會創(chuàng)建一個名為『oreilly-anomaly』的新文件夾。
3.通過. oreilly-anomaly/bin/activate命令激活環(huán)境。
4.通過運行pip install numpy pandas ipython jupyter ipykernel matplotlib來安裝Numpy, Pandas, Jupyter Notebook, and Matplotlib。
5.安裝 MXNet。
6.執(zhí)行如下命令把該虛擬環(huán)境加入Jupyter Kernel中:python -m ipykernel install –user –name=oreilly-anomaly
7.運行notebook:jupyter notebook .
8.在Jupyter中,選擇oreilly這一kernel: Menu → Kernel → Change kernel → oreilly-anomaly
數(shù)據(jù)集
正如介紹中所提到的,無論數(shù)據(jù)究竟帶不帶標(biāo)簽,異常檢測都可以用于多種行業(yè)的數(shù)據(jù)。今天,我們將使用基于物聯(lián)網(wǎng)的數(shù)據(jù),這些數(shù)據(jù)可以用來進(jìn)行預(yù)測性維護(hù)。 通過預(yù)測性維護(hù),您可以使用機(jī)器數(shù)據(jù)提前預(yù)測可能發(fā)生問題的時間。 相比定期維護(hù),它有許多優(yōu)點。 在傳統(tǒng)系統(tǒng)中,您必須非常了解自己的機(jī)器,才能知道需要多長時間維護(hù)一次,否則你就需要頻繁地進(jìn)行檢查。 如果不然,整個系統(tǒng)就有宕機(jī)的可能性。
本數(shù)據(jù)是由東京一家創(chuàng)業(yè)公司LP研究所制造的硬件傳感器收集的。 這次使用的傳感器可以讀取多達(dá)21個不同的值,包括X,Y和Z維度上的線性加速度(速度在單一方向上的變化率)。 現(xiàn)在為了簡單起見(也為了方便可視化),我們將只使用一個特征:X方向上的線性加速度。在現(xiàn)實情況中,您可能會想要使用更多方向上的數(shù)據(jù),特別是在使用神經(jīng)網(wǎng)絡(luò)時,因為它們會自動進(jìn)行特征工程。
圖3顯示了該特征的樣本數(shù)據(jù)。

圖3.關(guān)于設(shè)備X方向上線性加速度的IoT數(shù)據(jù)。圖片由Mateusz Dymczyk提供
可以看到,這些數(shù)據(jù)具有周期性,特征尺度也比較好 ——? 然而并不總是如此。 要注意到,尖峰偶爾出現(xiàn),它們屬于正常情況,而不會被歸類為異常。我們需要確保我們的模型足夠聰明來正確地處理這些樣本點。
前饋網(wǎng)絡(luò)
在處理機(jī)器學(xué)習(xí)問題時,從一個簡單的解決方案開始迭代總是一個好主意。 否則,你可能從一開始就迷失在復(fù)雜性中。
出于這個原因,我們首先將使用最簡單的神經(jīng)網(wǎng)絡(luò)之一 – 多層感知器(MLP)來實現(xiàn)我們的自編碼機(jī)。 一個MLP是一種前饋神經(jīng)網(wǎng)絡(luò),意思是一個沒有循環(huán)迭代,所有的連接都向前(與我們將在下一節(jié)中使用的遞歸神經(jīng)網(wǎng)絡(luò)是相反的)。 MLP是一個簡單的網(wǎng)絡(luò),至少有三層:輸入,輸出和至少一個隱藏層。 前饋自編碼器是一種特殊類型的MLP,其中輸入層中的神經(jīng)元數(shù)量與輸出層中的神經(jīng)元數(shù)量相同。 圖4是一個簡單的例子。

圖4.簡單的前饋自編碼機(jī),通過MLP實現(xiàn)。圖片由Mateusz Dymczyk提供
MLP的主要優(yōu)點是它們很容易建模,訓(xùn)練速度快。 而且,研究人員基于MLP展開了大量研究,可以認(rèn)為它們的工作機(jī)制已經(jīng)被較好的理解了。
在進(jìn)行MLP建模時,作為模型的創(chuàng)建者,您需要弄清楚幾件事情,其中包括:
?隱藏層的數(shù)量和每層神經(jīng)元的數(shù)量
?每個神經(jīng)元中使用的激活函數(shù)的類型
?用于訓(xùn)練的優(yōu)化器
所有這些選擇都會影響你模型的結(jié)果。 如果您選擇了錯誤的參數(shù),那么您的網(wǎng)絡(luò)可能根本不會收斂,需要很長時間才能收斂(例如,如果您選擇的是不好的優(yōu)化器或錯誤的學(xué)習(xí)率),在真實數(shù)據(jù)上過擬合或者欠擬合。
我們來看看代碼中最重要的部分。
數(shù)據(jù)準(zhǔn)備
我們首先使用Pandas DataFrame從我們的CSV文件中讀取數(shù)據(jù)。 這將返回一個Pandas的DataFrame:
train_data_raw = pd.read_csv(‘resources/normal.csv’)
validate_data_raw = pd.read_csv(‘resources/verify.csv’)
現(xiàn)在我們要提取列,我們將實際用于訓(xùn)練和預(yù)測:
feature_list = [” LinAccX (g)”]
features = len(feature_list)
train_data_selected = train_data_raw[feature_list].as_matrix()
validate_data_selected = validate_data_raw[feature_list].as_matrix()
在我們開始建模網(wǎng)絡(luò)之前,我們需要做更多的預(yù)處理。 MLP網(wǎng)絡(luò)的主要缺點是缺乏“記憶”。在訓(xùn)練和預(yù)測期間,每個記錄被視為一個單獨的個體樣本。 然而,在處理時間序列時,觀測樣本之間的依賴性是非常重要的。 我們的數(shù)據(jù)中的一個尖峰并不一定意味著一個異常:這取決于它的環(huán)境。
為了解決這個問題,我們將使用一個簡單的方法創(chuàng)建窗口記錄 ,這個方法將通過記錄進(jìn)行記錄,并追加window – 1記錄(在我們的例子中, 窗口大小將被設(shè)置為25,但是這個值應(yīng)該基于您的使用案例,您讀入數(shù)據(jù)的頻率,以及您希望模型預(yù)測異常的速度,可能會犧牲準(zhǔn)確性)。 這種新型的記錄將具有window * features大小,并將在時間步長之間模擬時間依賴性。 如果我們想利用第一個window – 1讀進(jìn)來的樣本,我們需要將它們填充到合適的長度,因為MLP網(wǎng)絡(luò)需要一個定長的輸入。 在這個例子中,我們將用零填充它們:
def prepare_dataset(dataset, window):
windowed_data = []
for i in range(len(dataset)):
start = i + 1 – window if i + 1 – window >= 0 else 0
observation = dataset[start : i + 1,]
to_pad = (window – i – 1 if i + 1 – window < 0 else 0) * features
observation = observation.flatten()
observation = np.lib.pad(observation, (to_pad, 0), ‘constant’, constant_values=(0, 0))
windowed_data.append(observation)
return np.array(windowed_data)
在構(gòu)建機(jī)器學(xué)習(xí)模型時,您不希望將所有數(shù)據(jù)用于訓(xùn)練 – 這可能會使您的模型過擬合。 出于這個原因,將數(shù)據(jù)分解為訓(xùn)練集和驗證集是正常的,并且在訓(xùn)練期間使用兩者來進(jìn)行評估。 通常情況下,分割數(shù)據(jù)很容易,但是對于時間序列數(shù)據(jù)來說,它會變得更加復(fù)雜一些。 這是因為記錄之間的時間依賴性:每個數(shù)據(jù)點出現(xiàn)的上下文是非常重要的。 這就是為什么在本教程中,我們不是隨機(jī)抽樣數(shù)據(jù),而是簡單地找到一個分割點,并將其用于將數(shù)據(jù)分成兩個子集(80%的數(shù)據(jù)用于訓(xùn)練,20%用于測試):
rows = len(data_train)
split_factor = 0.8
train = data_train[0:int(rows*split_factor)]
test = data_train[int(rows*split_factor):]
現(xiàn)在我們需要準(zhǔn)備一個DataLoader對象,它將以批處理的方式將數(shù)據(jù)提供給MXNet:
batch_size = 256
train_data = mx.gluon.data.DataLoader(train, batch_size, shuffle=False)
test_data = mx.gluon.data.DataLoader(test, batch_size, shuffle=False)
這個迭代器將以256個樣本大小的Batch傳遞訓(xùn)練數(shù)據(jù)。 如果您在GPU上運行,那么這一點尤其重要,因為Batch過大可能會導(dǎo)致內(nèi)存不足錯誤。 另一方面,太小的Batch會延長訓(xùn)練時間。
建模
由于使用Apache Gluon(MXNet的高級接口),建模代碼非常簡短。
我們的模型將是一系列用來代表隱藏層的塊。 為了使建模變得容易,我們將使用gluon.nn.Sequential:
model = gluon.nn.Sequential()
with model.name_scope():
現(xiàn)在添加隱藏層,激活函數(shù)和壓縮層是一個簡單的MXNet方法調(diào)用:
model.add(gluon.nn.Dense(16, activation=’tanh’)) # Adds a fully connected layer with 16 neurons and a tanh activation
model.add(gluon.nn.Dropout(0.25)) # Adds a dropout layer
這會將輸入傳遞給包含16個神經(jīng)元的第一個隱層,然后將其傳遞給激活層(在本例中為“tanh”),這不僅比其他許多激活方法在計算上更節(jié)約,而且看上去能夠快速收斂,讓訓(xùn)練出的MLP網(wǎng)絡(luò)有更高精度。因為剔除了部分?jǐn)?shù)據(jù),因此不會過度擬合。 在我們的網(wǎng)絡(luò)中,我們將Dropout層的輸出傳遞給另一個隱層,并且再次重復(fù)這個循環(huán)(隱藏層有8個和16個神經(jīng)元 – 隱藏層應(yīng)該有比輸入層更少的層來尋找結(jié)構(gòu))。 我們的最后一層將不會有任何激活或dropout,它將被視為輸出層。
在建模之前,我們需要為網(wǎng)絡(luò)參數(shù)(在這種情況下,我們使用所謂的Xavier初始化,分配初始值,并準(zhǔn)備一個訓(xùn)練器對象(在這里我們使用的是Adam優(yōu)化器?):
model.collect_params().initialize(mx.init.Xavier(), ctx=ctx)
trainer = gluon.Trainer(model.collect_params(), ‘adam’, {‘learning_rate’: 0.001})
對于這個問題(我們有興趣計算輸出層和輸入層之間形成的損失函數(shù)),我們可以使用gluon.loss.L2Loss來計算均方誤差:
L = gluon.loss.L2Loss()
最后,我們準(zhǔn)備一個評估方法,它將檢查我們的模型在每個Epoch之后的效果如何:
def evaluate_accuracy(data_iterator, model, L):
loss_avg = 0.
for i, data in enumerate(data_iterator):
data = data.as_in_context(ctx) # Pass data to the CPU or GPU
label = data
output = model(data) # Run batch through our network
loss = L(output, label) # Calculate the loss
loss_avg = loss_avg*i/(i+1) + nd.mean(loss).asscalar()/(i+1)
return loss_avg
并循環(huán)訓(xùn)練多個Epoch:
epochs = 50
all_train_mse = []
all_test_mse = []
# Gluon training loop
for e in range(epochs):
for i, data in enumerate(train_data):
data = data.as_in_context(ctx)
label = data
with autograd.record():
output = model(data) #Feed the data into our model
loss = L(output, label) #Compute the loss
loss.backward() #Adjust parameters
trainer.step(batch_size)
train_mse = evaluate_accuracy(train_data, model, L)
test_mse = evaluate_accuracy(test_data, model, L)
all_train_mse.append(train_mse)
all_test_mse.append(test_mse)
圖5顯示了模型對于我們的訓(xùn)練數(shù)據(jù)和驗證數(shù)據(jù)有多接近。

圖5.訓(xùn)練和驗證數(shù)據(jù)的MSE結(jié)果 圖片由Mateusz Dymczyk提供
擬合模型后,我們可以提供新的數(shù)據(jù)做出預(yù)測:
def predict(to_predict, L):
predictions = []
for i, data in enumerate(to_predict):
input = data.as_in_context(ctx)
out = model(input)
prediction = L(out, input).asnumpy().flatten()
predictions = np.append(predictions, prediction)
return predictions
在計算所有訓(xùn)練數(shù)據(jù)的MSE后,我們可以設(shè)定異常的閾值:
threshold =? np.mean(errors) + 3*np.std(errors)
最后,我們可以對一個測試數(shù)據(jù)集進(jìn)行預(yù)測,在這種情況下,通過對機(jī)器人引擎進(jìn)行編程來模擬系統(tǒng)宕機(jī)(另一種選擇是使用統(tǒng)計方法來生成錯誤的數(shù)據(jù))。 圖6顯示了最終的紅色異常。

圖6.測試數(shù)據(jù)集中的異常值。圖片由Mateusz Dymczyk提供
由于程序的設(shè)定,機(jī)器人會在2000個周期左右以及4000個周期前面一點都會停止,MLP在這里的診斷是正確的。
而且我們能看到,盡管訓(xùn)練數(shù)據(jù)集包含了0.1到0.2之間的散點,但是神經(jīng)網(wǎng)絡(luò)足夠聰明,可以發(fā)現(xiàn)如果有多個這樣的讀數(shù)連在一起,那么很可能是出錯了。 我們也注意到,它正確地預(yù)見到了在1000個周期左右,具有這樣的值的讀數(shù)并非是異常的,當(dāng)然模型錯誤地匯報了一些異常。我們可能需要調(diào)整一些參數(shù)(使用Dropout,正則化或數(shù)據(jù)分割方法)以獲得更好的模型。
對數(shù)據(jù)集進(jìn)行窗口化的必要性,可以通過使用窗口大小為1的參數(shù)運行腳本來進(jìn)行比較(見圖7):

圖7.不恰當(dāng)?shù)拇翱诖笮∠?,?xùn)練的MLP結(jié)果。圖片由Mateusz Dymczyk提供
我們清楚地看到,網(wǎng)絡(luò)不考慮任何時間結(jié)構(gòu),只是簡單地過擬合了大部分訓(xùn)練數(shù)據(jù)集,大約在[-1,1]之間。 該范圍之外的所有散點都被錯誤地標(biāo)記為異常。
LSTM
現(xiàn)在我們已經(jīng)有了一個可行的基本解決方案,讓我們再考慮一下我們的問題。 在MLP例子的“數(shù)據(jù)準(zhǔn)備”一節(jié)中,我們提到,使用MLP網(wǎng)絡(luò)訓(xùn)練,樣本不會保留以前的任何信息,這可能存在問題。 這是因為,在時間序列分析中,時間依賴往往是非常重要的。 MLP例子中,我們的開窗策略是克服這個缺點的一種很粗糙的方法。
為了克服這個缺點,我們現(xiàn)在來看看循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)。 在FF網(wǎng)絡(luò)(前饋神經(jīng)網(wǎng)絡(luò))中,它們將使用具有激活函數(shù)的神經(jīng)元來構(gòu)建,但主要的區(qū)別是它們也將包含循環(huán)。 我們不僅要把樣本點饋送到網(wǎng)絡(luò),而且還要把網(wǎng)絡(luò)的之前的隱含狀態(tài)也要饋送進(jìn)來。圖8顯示了RNN的架構(gòu)。

圖8.遞歸神經(jīng)網(wǎng)絡(luò)。 圖片由Mateusz Dymczyk提供
傳統(tǒng)RNNs的主要問題是,當(dāng)使用例如tanh或類似的輸出總是在[-1,1]范圍內(nèi)的激活層時,它們將大量的信息編碼成小的輸出范圍。 這使得學(xué)習(xí)長期依賴非常具有挑戰(zhàn)性。 例如,當(dāng)建立一個預(yù)測句子中下一個單詞的模型時,如果下一個單詞能使用前幾個單詞推理出來,那么我們可能會做的很好。 但是另一方面,如果要求聯(lián)系更遠(yuǎn)的上下文(幾個句子以外),我們可能無法保留足夠的“長期”信息來做出適當(dāng)?shù)念A(yù)測。
這個數(shù)學(xué)問題,是由于一個叫做梯度消失和梯度爆炸現(xiàn)象。 在后一種情況下,我們網(wǎng)絡(luò)中的權(quán)重可能開始呈指數(shù)級增長,可能變得比他們應(yīng)該取的值更大。 這個問題可以通過簡單的截斷或擠壓太高的值來解決。 前者(梯度消失)問題更大,其中一些(或全部)權(quán)重按指數(shù)規(guī)模變小,有時會變得很小,以至于計算機(jī)上的舍入錯誤可能使整個模型完全失效。
這些問題導(dǎo)致了所謂的長期短期記憶網(wǎng)絡(luò) (LSTM)的建立。 LSTM背后的基本思想是,它們像傳統(tǒng)的RNN一樣,具有鏈?zhǔn)浇Y(jié)構(gòu),保留以前的信息,但速度更為穩(wěn)定。 而普通 RNNs在每個單元格內(nèi)只有一個激活層,LSTM使用輸入門(決定什么新的信息應(yīng)該傳遞到網(wǎng)絡(luò)),遺忘門(決定以怎樣的速率遺忘什么信息),和輸出門計算新狀態(tài),如圖9所示。

圖9. LSTM網(wǎng)絡(luò)中的Cell。圖片由Mateusz Dymczyk提供
通過將輸出的數(shù)量設(shè)置為與輸入數(shù)量相同的值,我們可以再次獲得一個自編碼機(jī) – 這是一個自己記住以前狀態(tài)的自編碼機(jī)。
與我們的MLP例子相比,我們不會做任何預(yù)先的數(shù)據(jù)預(yù)處理。 不過,我們將把數(shù)據(jù)再次分割成訓(xùn)練集和驗證集。 Gluon為我們提供了一些不太一樣的數(shù)據(jù)加載器抽象類:
split_factor = 0.8
train = train_data_selected.astype(np.float32)[0:int(rows*split_factor)]
validation = train_data_selected.astype(np.float32)[int(rows*split_factor):]
train_data = mx.gluon.data.DataLoader(train, batch_size, shuffle=False)
validation_data = mx.gluon.data.DataLoader(validation, batch_size, shuffle=False)
封裝類可以很容易地對LSTM甚至更復(fù)雜的深層LSTM序列進(jìn)行建模。下面的代碼創(chuàng)建一系列堆疊的神經(jīng)網(wǎng)絡(luò)塊,使用Xavier初始化初始化所有參數(shù),生成一個優(yōu)化器,并準(zhǔn)備一個損失函數(shù):
model = mx.gluon.nn.Sequential()
with model.name_scope():
model.add(mx.gluon.rnn.LSTM(window, dropout=0.35))
model.add(mx.gluon.rnn.LSTM(features))
# Use the non default Xavier parameter initializer
model.collect_params().initialize(mx.init.Xavier(), ctx=ctx)
# Use Adam optimizer for training
trainer = gluon.Trainer(model.collect_params(), ‘adam’, {‘learning_rate’: 0.01})
# Similarly to previous example we will use L2 loss for evaluation
L = gluon.loss.L2Loss()
在Gluon中的訓(xùn)練是在一個簡單的for循環(huán)中進(jìn)行的,循環(huán)了變量epochs指定的次數(shù):
e range ( epochs ):
for i , enumerate data ( train_data ):
data = data .? as_in_context ( ctx ) .? reshape (( – 1 , features , 1 ))
label = data
with autograd .? record ():
output = model ( data )
loss = L ( output , label )
loss .? backward ()
trainer .? step ( batch_size )
對于每個Epoch,這個代碼將使用我們的訓(xùn)練數(shù)據(jù)逐批地使用model(data)調(diào)用計算輸出,然后計算損失并使用訓(xùn)練器對象更新所有參數(shù)。 MSE結(jié)果如圖10所示。

圖10.第一個LSTM模型如何擬合訓(xùn)練和驗證數(shù)據(jù)。 圖片由Mateusz Dymczyk提供
在這種情況下,兩個MSE值同時快速收斂到0.這可能意味著我們的模型正在過擬合,并應(yīng)該調(diào)整網(wǎng)絡(luò)設(shè)計的某些部分。
使用與我們之前的例子相同的技術(shù),我們可以獲得一個閾值,并在我們的測試集上運行預(yù)測,產(chǎn)生如圖11所示的結(jié)果。

圖11. LSTM的結(jié)果。圖片由Mateusz Dymczyk提供
我們這次可以看到,網(wǎng)絡(luò)并不總是把錯誤讀數(shù)匯報為異常,但是足以讓我們檢測到機(jī)器的問題了。
進(jìn)一步改進(jìn)
盡管我們能夠在本教程中創(chuàng)建看似實用的模型,但仍然有幾個重要方面我們沒有時間來討論:
1.數(shù)據(jù)準(zhǔn)備。針對您的數(shù)據(jù)集,您可能需要了解其他數(shù)據(jù)準(zhǔn)備步驟。例如,將神經(jīng)網(wǎng)絡(luò)的時間序列數(shù)據(jù)標(biāo)準(zhǔn)化通常是一個好主意。在其他情況下,您將需要對您的特征進(jìn)行編碼,比如那些分類變量。
2.網(wǎng)絡(luò)架構(gòu)優(yōu)化。不同的用例需要不同數(shù)量的隱藏層,激活函數(shù),正則化函數(shù),優(yōu)化器,DropOut層等。
3.如果再次閱讀LSTM Gluon 文檔和LSTM計算圖,您會注意到LSTM問題中,每個樣本都可以定義為一系列觀測。同樣,如在MLP的例子中,我們可以使用我們的窗口方法,使每個觀察包含幾個觀測樣本,并將其輸入我們的LSTM網(wǎng)絡(luò)。在這種情況下,網(wǎng)絡(luò)可能會更多地利用讀取之間的時間依賴性,而網(wǎng)絡(luò)的輸入仍然只有特征的數(shù)量大小發(fā)生變化。
4.不同的訓(xùn)練/驗證分離策略和自動模型評估。我們只通過繪制和檢查樣本測試集來評估我們的模型。在現(xiàn)實世界中,您需要準(zhǔn)備打過標(biāo)簽的測試數(shù)據(jù)集(例如使用統(tǒng)計量數(shù)據(jù)),并使用某種度量標(biāo)準(zhǔn)(例如預(yù)測和回測)以自動化的方式查看模型的執(zhí)行情況。
結(jié)論
在本教程中,我們解決了時間序列物聯(lián)網(wǎng)數(shù)據(jù)中的異常檢測問題。 正如我們現(xiàn)在所看到的,異常檢測是一個非常廣泛的問題,不同的用例需要不同的技術(shù)來進(jìn)行數(shù)據(jù)準(zhǔn)備和建模。 我們探索了兩種穩(wěn)健的方法:前饋神經(jīng)網(wǎng)絡(luò)和長期的短期記憶網(wǎng)絡(luò),各有優(yōu)缺點。 FFN的速度更快(在NVidia 1080 GPU上,執(zhí)行50個Epoch為5.5秒,25個Epoch為33秒),但是需要更多規(guī)劃和準(zhǔn)備,而LSTM則稍微慢一些,但也更加智能。 深層神經(jīng)網(wǎng)絡(luò)被證明是非常善于發(fā)現(xiàn)結(jié)構(gòu)和依賴的,但是相應(yīng)的代價是,我們至少需要了解如何構(gòu)建它們的基本知識。 最后,我們看到諸如Apache MXNet之類的框架,使得這個高難度任務(wù)變得平易近人起來。
這篇文章由O’Reilly和亞馬遜合作完成。 在此處查看我們的編輯獨立性聲明。
Mateusz Dymczyk
Mateusz是一名專注于分布式計算和JVM的軟件工程師。 他目前正在H2O.ai公司工作,這家公司提供的H2O是開源可擴(kuò)展機(jī)器學(xué)習(xí)平臺。Mateusz在波蘭AGH UST獲得了計算機(jī)科學(xué)碩士學(xué)位。之后他到日本富士通實驗室學(xué)習(xí)了機(jī)器學(xué)習(xí)方法和NLP問題。 在業(yè)余時間,他用組織會議、參加會議和在會議/技術(shù)見面會上演講的方式參與IT社區(qū)的活動。

