一、前言
今天的文章來自VLDB
TranAD: Deep Transformer Networks for Anomaly Detection in Multivariate Time Series Data

論文鏈接:https://arxiv.org/pdf/2201.07284v6.pdf
代碼地址:https://github.com/imperial-qore/TranAD
二、問題
在文章中提出了對于多變量異常檢測的幾個有挑戰(zhàn)性的問題
缺乏異常的label
大數(shù)據(jù)量
在現(xiàn)實(shí)應(yīng)用中需要盡可能少的推理時間(實(shí)時速度要求高)
在本文中,提出了基于transformer的模型TranAD,該模型使用基于注意力機(jī)制的序列編碼器,利用數(shù)據(jù)中更廣泛的時間趨勢快速推斷。TranAD使用基于score的自適應(yīng)來實(shí)現(xiàn)魯棒的多模態(tài)特征提取以及通過adversarial training以獲得穩(wěn)定性。此外,模型引入元學(xué)習(xí)(MAML)允許我們使用有限的數(shù)據(jù)來訓(xùn)練模型。
三、方法
3.1 問題定義
一個時間序列因?yàn)槭嵌嘧兞繒r間序列,每一個X是一個大小為m的向量,即該序列有m個特征。
該工作定義了兩種任務(wù)
Anomaly Detection(檢測):給予一個序列來預(yù)測目前時刻的異常情況(0或者1),1代表該數(shù)據(jù)點(diǎn)是異常的。
Anomaly Diagnosis(診斷): 文中這塊用denote which of the modes of the datapoint at the ??-th timestamp are anomalous.來描述,其實(shí)就是判斷是哪幾個維度的特征(mode)導(dǎo)致的實(shí)體的異常,診斷到維度模式的程度。
3.2 數(shù)據(jù)預(yù)處理
對數(shù)據(jù)做normalize,數(shù)據(jù)的保存形式,是一個實(shí)體一個npy文件,維度是(n, featureNum)

對數(shù)據(jù)進(jìn)行滑窗,這里對于windowSize之前的數(shù)據(jù)并不舍去,而是用前面的數(shù)據(jù)直接復(fù)制,代碼如下:
windows?=?[];?w_size?=?model.n_window
?for?i,?g?in?enumerate(data):?
??if?i?>=?w_size:?w?=?data[i-w_size:i]
??else:?w?=?torch.cat([data[0].repeat(w_size-i,?1),?data[0:i]])
3.3 模型
先看一下transformer的模型圖。

模型本身和TranAD除了有兩個decoder其他基本上完全一樣,這里結(jié)構(gòu)不贅述了,具體看
transformer的論文: https://arxiv.org/pdf/1706.03762.pdf
代碼:https://pytorch.org/docs/stable/generated/torch.nn.Transformer.html (推薦看官方的trasnformer代碼,直接看源碼實(shí)現(xiàn))
TranAD也省去了decoder中feed forward后的add&Norm,softmax也更改為sigmoid,文中提到sigmoid是把輸出的數(shù)據(jù)擬合到輸入數(shù)據(jù)的歸一化狀態(tài)中,即【0, 1】范圍內(nèi)。
這個圖其實(shí)十分清晰,這個方法最大的創(chuàng)新不在模型本身,甚至模型沒什么改動,主要引入了對抗訓(xùn)練的思想 解釋下其中的變量
W為輸入的窗口數(shù)據(jù)(前面數(shù)據(jù)預(yù)處理頁提到了窗口數(shù)據(jù)的生成)
Focus Score是和W一樣維度的變量,在第一階段為0矩陣,第二階段是通過W和O1的計(jì)算得出
C W的最后一個窗口數(shù)據(jù)
這個C變量,文中這樣說。

其實(shí)看給的代碼最好理解
elif?'TranAD'?in?model.name:
??l?=?nn.MSELoss(reduction?=?'none')
??data_x?=?torch.DoubleTensor(data);?dataset?=?TensorDataset(data_x,?data_x)
??bs?=?model.batch?if?training?else?len(data)
??dataloader?=?DataLoader(dataset,?batch_size?=?bs)
??n?=?epoch?+?1;?w_size?=?model.n_window
??l1s,?l2s?=?[],?[]
??if?training:
???for?d,?_?in?dataloader:
????local_bs?=?d.shape[0]
????window?=?d.permute(1,?0,?2)?//?這個就是W
????elem?=?window[-1,?:,?:].view(1,?local_bs,?feats)??//?這個就是C
????z?=?model(window,?elem)
????l1?=?l(z,?elem)?if?not?isinstance(z,?tuple)?else?(1?/?n)?*?l(z[0],?elem)?+?(1?-?1/n)?*?l(z[1],?elem)
????if?isinstance(z,?tuple):?z?=?z[1]
????l1s.append(torch.mean(l1).item())
????loss?=?torch.mean(l1)
????optimizer.zero_grad()
????loss.backward(retain_graph=True)
????optimizer.step()
???scheduler.step()
???tqdm.write(f'Epoch?{epoch},\tL1?=?{np.mean(l1s)}')
???return?np.mean(l1s),?optimizer.param_groups[0]['lr']
這里比較大的創(chuàng)新在于第一階段和第二階段的訓(xùn)練。
第一階段:為了更好的重構(gòu)序列數(shù)據(jù),和大部分encoder-decoder模型的作用沒有什么不同
第二階段:引入對抗性訓(xùn)練的思想。
解讀這個訓(xùn)練階段之前,先把模型代碼過一下。
class?TranAD(nn.Module):
?def?__init__(self,?feats):
??super(TranAD,?self).__init__()
??self.name?=?'TranAD'
??self.lr?=?lr
??self.batch?=?128
??self.n_feats?=?feats
??self.n_window?=?10
??self.n?=?self.n_feats?*?self.n_window
??self.pos_encoder?=?PositionalEncoding(2?*?feats,?0.1,?self.n_window)
??encoder_layers?=?TransformerEncoderLayer(d_model=2?*?feats,?nhead=feats,?dim_feedforward=16,?dropout=0.1)
??self.transformer_encoder?=?TransformerEncoder(encoder_layers,?1)
??decoder_layers1?=?TransformerDecoderLayer(d_model=2?*?feats,?nhead=feats,?dim_feedforward=16,?dropout=0.1)
??self.transformer_decoder1?=?TransformerDecoder(decoder_layers1,?1)
??decoder_layers2?=?TransformerDecoderLayer(d_model=2?*?feats,?nhead=feats,?dim_feedforward=16,?dropout=0.1)
??self.transformer_decoder2?=?TransformerDecoder(decoder_layers2,?1)
??self.fcn?=?nn.Sequential(nn.Linear(2?*?feats,?feats),?nn.Sigmoid())
?def?encode(self,?src,?c,?tgt):
??src?=?torch.cat((src,?c),?dim=2)
??src?=?src?*?math.sqrt(self.n_feats)
??src?=?self.pos_encoder(src)
??memory?=?self.transformer_encoder(src)
??tgt?=?tgt.repeat(1,?1,?2)
??return?tgt,?memory
?def?forward(self,?src,?tgt):
??#?Phase?1?-?Without?anomaly?scores
??c?=?torch.zeros_like(src)
??x1?=?self.fcn(self.transformer_decoder1(*self.encode(src,?c,?tgt)))
??#?Phase?2?-?With?anomaly?scores
??c?=?(x1?-?src)?**?2
??x2?=?self.fcn(self.transformer_decoder2(*self.encode(src,?c,?tgt)))
??return?x1,?x2
從代碼可以看出來,雖然圖中畫的Window Encoder對于Decoder1和2來說是shared,但其實(shí)是分開的。
分別用transformer_decoder1和transformer_decoder2實(shí)現(xiàn)的。
第一階段 ? Input Reconstruction
很多傳統(tǒng)的encoder-decoder模型經(jīng)常不能夠獲取short-term的趨勢,會錯過一些偏差很小的異常,為了解決這個問題,采用兩個階段的方式來進(jìn)行重構(gòu)。
在第一階段,模型的目標(biāo)是生成和輸入窗口數(shù)據(jù)近似的reconstruction。對于這個推斷的偏差,稱之為focus score,有助于Transformer Encoder內(nèi)部的注意力網(wǎng)絡(luò)提取時間趨勢,關(guān)注偏差高的子序列。(理解其實(shí)就是用與真實(shí)值的殘差去擬合偏差高的序列,在第二階段)
第一階段下的focus score是0矩陣,與輸入windows數(shù)據(jù)一致,輸出O,與W計(jì)算L2 loss作為第一階段的loss函數(shù)。

注意代碼中的encode的部分,是將C和focus score直接concat再乘以sqrt(featureNum),之后再經(jīng)過位置編碼,其實(shí)文中只順帶說了一下而已,沒有說為什么這么做,我個人傾向于為了將C和focus score信息放在一起,已concat常見的嘗試揉在了一起。
第二階段
將第一階段O1與W的L2 loss作為focus score,在進(jìn)行之前的步驟。這可以在第二階段調(diào)整注意力權(quán)重,并為特定輸入子序列提供更高的神經(jīng)網(wǎng)絡(luò)激活,以提取短期的時間趨勢(這句話是文中說的,和我前面的那里理解基本上差不多)。
之后算是比較重點(diǎn)的地方了,引入對抗訓(xùn)練的思想,來設(shè)計(jì)第二階段的loss這個地方十分的繞,建議大家讀讀原文的3.4 Offline Two-Phase Adversarial Training的Evolving Training Objective部分
第二個decoder盡力去區(qū)分輸入和第一階段decoder1的重建,所以是max O2這個L2 loss(這里就會有疑問,decoder1的重建不是O1嗎?為啥要max ||O2-W||2? 這里我從作者的角度去理解,其實(shí)他是一種條件近似轉(zhuǎn)移,因?yàn)镺1和O2再第一階段都是近似W,那區(qū)別于第一階段的decoder1的重建O1,其實(shí)就是區(qū)別于O2)
第一個decoder盡力去通過創(chuàng)建一個接近W的O1來迷惑decoder2(其實(shí)就是想讓這個focus score接近于0矩陣),其實(shí)就是min(W和O1),其實(shí)轉(zhuǎn)移也就是min(W和O2),所以得到了下面這個公式

可以分解為:

再加上第一階段的loss,總loss就是:

這里又對decoder1和decoder2做了明確的解釋:

看代碼理解
之后我們看下代碼里的區(qū)別,代碼里其實(shí)根本就沒有算O2,只算了O1

在做反向傳播,優(yōu)化參數(shù)的時候也只算了L1的總loss,沒有算L2
這里有個z的type判別,因?yàn)樽隽撕芏嘞趯?shí)驗(yàn),不是最終的模型,最終就是后面那個L1loss,其中前面有個參數(shù),是epoch+1,參數(shù)會隨著訓(xùn)練輪數(shù)的增加,倒數(shù)慢慢變小,即前面的第一階段的loss慢慢權(quán)重減小,而第二階段的loss慢慢權(quán)重增大。
其實(shí)這也給出了一個我個人感覺非常合理的解釋,因?yàn)榈诙A段要附屬于第一階段的訓(xùn)練,應(yīng)該先讓O1和O2接近于W,之后才能去用對抗訓(xùn)練,這樣才會讓第二階段訓(xùn)練有效,否則就混亂了。

現(xiàn)在再理解下這個,就完全明白了:
測試階段,引入了閾值自動選擇(POT,但代碼中沒有看到這的設(shè)置),以及score的計(jì)算

四、結(jié)果

對于異常的定義,是score大于閾值就是異常。
任意一個維度有異常就算作是異常,感覺這樣描述本質(zhì)上還是單序列的異常檢測,沒有從根本解決多變量的問題。
4.1 數(shù)據(jù)集
大部分常用數(shù)據(jù)集
4.2 結(jié)果
每一個維度都有reconstruction,并且每個維度都有對應(yīng)的score,不得不說這個圖示還是很清晰的,構(gòu)建很清晰易懂。
后面還有各種實(shí)驗(yàn),參數(shù)靈敏度、數(shù)據(jù)集等實(shí)驗(yàn),這篇paper實(shí)驗(yàn)部分還是很滿的,整體來說,工作量還是拉滿的。
本文也對兩種任務(wù)都做了實(shí)驗(yàn),異常檢測部分不用說了,常規(guī)操作,診斷部分采用 HitRate and NDCG兩種指標(biāo)進(jìn)行root cause的檢驗(yàn)。
五、總結(jié)和思考
對于代碼,給出了每個對比模型和數(shù)據(jù)集,可以為后續(xù)實(shí)驗(yàn)做參考,并給出了整體消融實(shí)驗(yàn)的代碼,代碼還是很全面的,雖然有一些雜亂,但對于一個要做這個方向的同學(xué)來說,還是相當(dāng)于巨人的肩膀的。
把transformer和對抗性訓(xùn)練放在一起,確實(shí)是很新穎的想法
在代碼處O2部分為何省略存疑,以及第二階段的loss,其實(shí)有點(diǎn)套的生硬,為何不都引到O1上,假設(shè)引到O1上,那loss1就只剩下L2 loss了,可能在公式上就并非這種對稱了。
存疑的點(diǎn)就是 代碼中在訓(xùn)練過程中并未對L2進(jìn)行訓(xùn)練,這樣的話O2是否像理論說的那樣輸出工作?
診斷的定義和診斷任務(wù)的探索,其實(shí)是有一些生硬的,并且也沒完全說清楚,當(dāng)然這篇文章標(biāo)題是anomaly detection,其實(shí)并未將diagnosis算重點(diǎn),所以這個也可以接受。
文章的標(biāo)題是多變量的異常檢測,其實(shí)雖然可以應(yīng)用在多變量上,但實(shí)際還是單變量的異常來判別是否是多變量的整體實(shí)體的異常,本質(zhì)還是用單變量問題解決多變量(這里可以探究一下,因?yàn)樽罱K的score是由loss決定,而loss本身的維度是和輸入的window數(shù)據(jù)一樣的維度,意思就是每一個特征維度有一個score,所以其實(shí)得到的score還是單變量的score而并非實(shí)體的score,所以這里作者也沒有探究多變量pattern的情況,可能存在多個變量異常,但只是一個跳變的pattern,不足以讓整體異常的情況。)文章來源:http://www.zghlxwxcb.cn/news/detail-405800.html
代碼里也沒給出POT的相關(guān)代碼。文章來源地址http://www.zghlxwxcb.cn/news/detail-405800.html
推薦閱讀:
我的2022屆互聯(lián)網(wǎng)校招分享
我的2021總結(jié)
淺談算法崗和開發(fā)崗的區(qū)別
互聯(lián)網(wǎng)校招研發(fā)薪資匯總
2022屆互聯(lián)網(wǎng)求職現(xiàn)狀,金9銀10快變成銅9鐵10?。?
公眾號:AI蝸牛車
保持謙遜、保持自律、保持進(jìn)步
發(fā)送【蝸?!揩@取一份《手把手AI項(xiàng)目》(AI蝸牛車著)
發(fā)送【1222】獲取一份不錯的leetcode刷題筆記
發(fā)送【AI四大名著】獲取四本經(jīng)典AI電子書
到了這里,關(guān)于融合transformer和對抗學(xué)習(xí)的多變量時間序列異常檢測算法TranAD論文和代碼解讀...的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!