Tuesday, January 19, 2021

使用 LSTM Autoencoder 進行異常檢測

簡介
上回提到,因為 LSTM 能記住事件的先後次序,所以它特別適合用來處理一些,具有時間順序的數據 (Time Series Data) 。上一期,我們實現了如何使用監督學習 (Supervised Learning) 做未來預測。而這一次,我們會用 LSTM 實現另一種常用的系統:非監督式異常檢測 (Unsupervised Anomaly Detection)。

所謂異常檢測 (Anomaly Detection),其實是指:當我們訓練系統的時候,我們可以透過提供大量正常數據,繼而讓系統自己找出這些數據的規律。然後,當系統突然收到一些與既定規律相違背的數據,它就能自動把這些數據判別為異常。

這種模型,特別適合用來做入侵偵測系統 (Intrusion Detection System):因為黑客入侵等事件並不尋常, 而且入侵模式千變萬化。所以,我們只能大約知道正常的網絡流量會是怎樣。相反,對於黑客進行攻擊的情況,我們所知的並不多。故此,系統設計者能只提供正常情況的數據,以及極少量的入侵數據(這種數據,在 ML 世界中,也稱為非平衡數據 (Unbalanced Data)),然後就讓系統自行找出正常情況的規律。

是次實作,我們會用另一個較簡單的例子:心跳規律來做示範。我們只需要提供正常的心跳數據,讓系統自行訓練,持之以恆,它就能辨認出何謂正常的心跳。訓練完成後,只要系統發現一些奇怪的心跳規律, 它就會回報異常。這次的實作,是改自 Curiousily - Time Series Anomaly Detection using LSTM Autoencoders with PyTorch in Python 的範例。我改進了以下兩點:

  • 使用 DataLoader 來進行 batch training
  • 簡化代碼,使其更易閱讀及改裝

---

實作模型:LSTM Autoencoder
Autoencoder,顧名思義, 就是一種自動編碼 (Encode) 及解碼 (Decode) 的轉碼器。

簡單來講,你可以把它想像成一個有損壓縮 (Lossy Compression) 的工具:首先,它會把收到的訊號,編譯 (Encode) 成一個非常細小的編碼 (Embedding Code,下圖紅色部分,又稱為 Bottleneck Layer)。然後, 當解碼器 (Decoder) 收到這一條小編碼,就會試圖把它解譯成原本的訊號。若果轉譯來回的數據十分相似,就代表它帶來的損失很少,也代表這對 Encoder 和 Decoder 的效能很強。相反,若果它在經過來回轉譯後,輸出與原本大相逕庭,就代表這對 Encoder 和 Decoder 失真的情況嚴重,十分垃圾,並不可靠。

Information Theory 告訴我們,由於數據儲存密度有限,若果壓縮要做到可靠,只有兩個方法:一是加大 Embedding Code,讓它存放更多資料,另一方法,則是按照數據的規律,度身訂造 Encoder 和 Decoder,以達至極致的壓縮比率。 由於我們的系統會固定 Embedding Code 的大小 (Embedding Code 的大小,通常固定在原數據十分之一左右) ,所以系統不能使用第一個方法保存資料。也就正說:系統會被迫用第二個方法達成極致壓縮。

正因如此,隨著訓練時間越長,系統將會越來越依靠訓練數據(也就是正常數據)中的一些固定規律去運作。最後,它會變成一個專為正常數據而設的壓縮器。換句話說,這個壓縮器,能在你放入正常數據的情況下,完美地進行極致壓縮。但如果你放入一些異常數據,這個壓縮器就會突然失控,結果變得強差人意。而我們這次,正正就是利用這一種特性,來判斷數據是否異常。

---

演示代碼

https://github.com/cmcvista/MLHelloWorld/blob/main/LSTMAutoencoder/HeartbeatAutoencoder.ipynb

---

幾個重要變數
是次實作,有幾個變數,需要仔細解釋:

  • 一條時間順序的資料 (data_seq_len) 的長度為 140,特徵 (data_n_features) 為 1。
    這是指一個數據長度為 140, 而維度只有 1( y 座標為 1 )。
  • Embedding code 的長度為 (data_embedding_dim) 為 64,
    也代表它的 Tensor Dimension 是 64*1。
  • 整個系統的大小為 [140, 128, 64, 64, 128, 140]
    (格式:[Input, Hidden, Embedding, Embedding, Hidden, Output] )
  • 訓練 100 次對 LSTM 來講,其實並不足夠,但由於只作示範,就懶得執行太久。

---

Losses 計算方法
損失 (Losses) 的定義,是指解碼後的結果,跟原數據的差異大小。在這次實作中,為了與原例子一樣,我使用了兩組數據之間的 L1Loss 的總和去定義。不過,其實用其他方法(例如 Mean Square Error (MSE) Loss)也能達至相同效果。

---

Threshold 的定義方法
這次因為懶惰,我只用肉眼判斷 Threshold 的值。這個 Threshold 是指:只要任何數據的 total loss 比它小,就應把它當成正常數據。事實上,隨著訓練次數越多,系統會對正常數據的規律更熟悉,輸出會更接近完美,所以它的 total loss 必定會越來越小。故此,訓練越多,Threshold 其實也應該變得越小,以達至更準確的辨識率。

 ---

小結
運用 LSTM Autoencoder 做異常偵測,能達至頗高的成功率,而且原理也不難懂。
不過,由於變數不少,LSTM 訓練時間也頗長, 所以部署時,應先考慮其他方法。

總而言之,這次的實驗也算成功,至少加上 batch support 後,仍能達至預期結果。

---

參考:

[1] Time Series Anomaly Detection using LSTM Autoencoders with PyTorch in Python - https://curiousily.com/posts/time-series-anomaly-detection-using-lstm-autoencoder-with-pytorch-in-python/

[2] 【深度學習】一個簡單又神奇的結構:自編碼機 AutoEncoder - https://jason-chen-1992.weebly.com/home/-autoencoder

 

Tuesday, December 29, 2020

實作:簡易版 BERT Transformers Multi-Label Classification

簡介
由 Huggingface 所開發的 Transformers Library,雖然可以用 BERT 做 NLP Multi-Class Classification(每一組數據擁有一個 class),但卻未有支援 Multi-Label Classification(每一組數據,可以有多於一個 class)。這次實作,我會以 Kaggle Jigsaw Toxic Comments 作為例子,透過小改 Transformers 的 Multi-Class Classification 範例,令它可以進行 Multi-Label 分類。

---

改進原理
Transformers 中的 BertForSequenceClassification,是一個基於 BERT 及 Attention Model,然後再使用 CrossEntropyLoss 微調訓練的模型。事實上,它也支援自動訓練:只要你使用期間提供了 label,它內置的 CrossEntropyLoss 就能自動計算並返回 loss 值。

這種「黑箱全包」的做法,雖然令入門開發者,可以在不了解 Loss Function 等概念的情況下上手,但同時,它也帶來了一些限制:由於它使用了 CrossEntropyLoss,它就只能支援一個類別的分類。舉例來講,它只能計算出「 預計矩陣 [0 0 0 1] 與期望值 3(也就是 [0 0 0 1] )的距離為 0 」。它並不能輸入多於一個維度的期望值。

所以,這次實作,我只是簡單地改了一下:不用內置於 BertForSequenceClassification 的 CrossEntropyLoss,改為採用 BCEWithLogitsLoss。由於 BCEWithLogitsLoss 本身就是專為 Multi-Label Classification 而設計的 Loss Function,所以我也懶得仔細查究,直接就拿來用。最後,我們就能把 BertForSequenceClassification 改成支援 Multi-Label Classification 的模型。

至於其他,例如 BertTokenizer 等等,則採用 Pretrained 結果,原封不動。

---

重點代碼
這幾行代碼,就是這次改動的大重點:

在訓練過程中:

# use AdamW
optimizer = optim.AdamW(model.parameters(), lr=lr)

# generate prediction
optimizer.zero_grad()
outputs = model(input_ids, attention_mask=attention_mask)
 
# compute gradients and update weights
loss = nn.BCEWithLogitsLoss(outputs.logits, labels)
loss.backward()
optimizer.step()

解說:

  • 使用 AdamW 做 Optimizer(其實其他都可以,只是我發現 AdamW 比較快)。
  • 使用 BertForSequenceClassification model 的過程中,故意不提供 label,
    這樣,Transformers 就不會偷偷地自己計算 Loss 和 Gradients,必須要自己動手。
  • 使用 BCEWithLogitsLoss 來計算 Loss,然後手動做 Backward Propagation 和 Stepping。

 在驗證測試過程中:

# generate prediction
outputs = model(input_ids, attention_mask=attention_mask)
prob = outputs.logits.sigmoid()

# record processed data count
total += (labels.size(0)*labels.size(1))

解說:

  • 用 model 計算結果後,必須經過一層 sigmoid 計算。
    因為 BCEWithLogitsLoss 入面都有 sigmoid,這樣才會一致。
  • 在計算總準繩率時,不能單一以 labels.size(0)(樣本數)做分母,
    相反,要以總標記數 (樣本數 * 每個樣本內的標記數) 做分母。
    因為:假設 Predict = [ 1 0 0 1 ], Expected = [ 1 0 0 0 ],
    它應該算做 3/4 正確,而非一個錯誤。

---

演示代碼
https://github.com/cmcvista/BERTClassifier/blob/main/BertMultiLabelClassifier.ipynb

---

小記
感謝 Joseph 提示方向。

---

參考

[1] - https://huggingface.co/transformers/training.html

[2] - https://towardsdatascience.com/transformers-for-multilabel-classification-71a1a0daf5e1

[3] - https://medium.com/huggingface/multi-label-text-classification-using-bert-the-mighty-transformer-69714fa3fb3d

Wednesday, December 16, 2020

Install Charm Crypto in Mac OS Catalina 10.15.7

Charm Crypto
Developed by Akinyele, Joseph A, Charm Crypto is a simplified Python wrapper of PBC Library. There are many researchers using this library to build prototypes for their cryptographic protocols. In fact, its performance is comparable to the original C version of PBC (I also did some experiments and found that it is actually faster than JPBC, the Java wrapper of PBC). 

Dependencies Installation
To use Charm Crypto in Mac OS Catalina, you need to have the following items preinstalled:

1. Homebrew
It is for running the subsequent commands below.
To install the latest version of homebrew, check the script in the official brew website.

2. GMP Library
Install command: brew install gmp

3. PBC Library
Install command: brew install pbc

4. OpenSSL
Install command: brew install openssl@1.1

5. WGET
Install command: brew install wget

Charm Crypto Installation
After installing the dependencies above, run the following steps:

1. Download the latest source code
Command: git clone https://github.com/JHUISI/charm.git ; cd charm

2. Run the configure in root privileges and with the brew-installed version of openssl
Command: sudo ./configure.sh --extra-cflags="-I/usr/local/opt/openssl/include" --extra-ldflags="-L/usr/local/opt/openssl/lib

(Extra CFLAGS and LDFLAGS are required because brew will not install the openssl into /usr/lib)

3. Run the compilation and installation
Command: sudo make install

4. Run the post-installation testing
Command: sudo make test

After installation, you should be able to import schemes.* and charm.* freely in Python3 freely.

Screenshot
Once you see most of the items are passed, you can start programming with Charm Crypto.
Some example schemes can be found in charm/schemes directory.