Aprile 2019

Volume 34 Numero 4

[Test Run]

Rilevamento di anomalie neurali con PyTorch

Dal James McCaffrey

James McCaffreyRilevamento di anomalie, rilevamento degli outlier, è l'acronimo è il processo di individuazione di elementi rari in un set di dati. Ad esempio che identifica gli eventi dannosi in un file di log del server e ricerca fraudolente pubblicitarie in linea.

Un buon metodo per vedere quale ha inizio in questo articolo è esaminiamo il programma demo nella figura 1. La demo analizza un subset di 1.000-elemento del noto set di dati di modifica National Institute of Standards e Technology (MNIST). Ogni elemento di dati è un'immagine in scala di grigi 28 x 28 (784 pixel) di una cifra da zero a nove scritto a mano. Set di dati MNIST completo contiene 60.000 immagini di training e test di 10.000.

Rilevamento di anomalie immagine MNSIT usando Keras
Figura 1 MNSIT immagine le anomalie usando Keras

Il programma demo crea ed esegue il training di un 784-100, 50: 100-784 autoencoder neurali profonde tramite la libreria di codice PyTorch. Un autoencoder è una rete neurale che Impara a prevedere il relativo input. Dopo il training, la demo analizza i 1.000 immagini e consente di trovare un'immagine che è in cui più anomala, errore di ricostruzione più alto indica più anomale. La cifra più anomala è una tre che permette di ottenere la causa potrebbe essere invece otto.

Questo articolo si presuppone che si dispone delle competenze intermedi o programmazione meglio con un linguaggio C-family e una certa conoscenza di base con machine learning, ma non presume che si conosce autoencoders. Tutto il codice demo è presentato in questo articolo. Il codice e i dati sono disponibili anche nel download associato. Controllo degli errori è stata rimossa per mantenere le idee principali più chiare possibili.

Installare PyTorch

PyTorch è una libreria di codice relativamente basso livello per la creazione di reti neurali. È più o meno simile in termini di funzionalità per CNTK e TensorFlow. PyTorch viene scritto in C++, ma è un linguaggio di Python API di programmazione più semplice.

Installare PyTorch include due passaggi principali. In primo luogo, installare Python e numerosi pacchetti ausiliari necessari, ad esempio NumPy e SciPy. È quindi possibile installare PyTorch come un pacchetto di componenti aggiuntivi di Python. Anche se è possibile installare Python e i pacchetti necessari per eseguire PyTorch separatamente, è molto meglio installare una distribuzione di Python, che è una raccolta contenente l'interprete di Python di base e altri pacchetti che sono compatibili con ogni Altro. Per mia dimostrazione, è stato installato il Anaconda3 5.2.0 distribuzione, che contiene Python 3.6.5.

Dopo aver installato Anaconda, è verificato un errore per il pytorch.org sito Web e selezionato le opzioni per il sistema operativo Windows, programma di installazione di Pip, Python 3.6 e non sono presenti versioni GPU CUDA. Ciò mi ha un URL che punta al corrispondente file con estensione WhL (si pronuncia "wheel"), scaricata nel computer locale. Se si ha familiarità con l'ecosistema di Python, è possibile considerare un file con estensione WhL Python come simili a un file con estensione msi di Windows. Nel mio caso, ho scaricato PyTorch versione 1.0.0. Ho aperto una shell dei comandi, spostarsi nella directory che contiene il file con estensione WhL e immesso il comando:

pip install torch-1.0.0-cp36-cp36m-win_amd64.whl

Il programma Demo

Il programma di dimostrazione completo, con alcune modifiche di lieve entità per risparmiare spazio, viene presentato nel figura 2. Impostare un rientro con due spazi anziché di solito quattro funzioni per risparmiare spazio. Si noti che Python Usa il carattere "\" per la continuazione di riga. Ho utilizzato il blocco note per modificare un programma. La maggior parte dei miei colleghi preferisce un editor più sofisticato, ma mi piace la semplicità brutale del blocco note.

Figura 2, il programma Demo di rilevamento anomalie

# auto_anom_mnist.py
# PyTorch 1.0.0 Anaconda3 5.2.0 (Python 3.6.5)
# autoencoder anomaly detection on MNIST
import numpy as np
import torch as T
import matplotlib.pyplot as plt
# -----------------------------------------------------------
def display(raw_data_x, raw_data_y, idx):
  label = raw_data_y[idx]  # like '5'
  print("digit/label = ", str(label), "\n")
  pixels = np.array(raw_data_x[idx])  # target row of pixels
  pixels = pixels.reshape((28,28))
  plt.rcParams['toolbar'] = 'None'
  plt.imshow(pixels, cmap=plt.get_cmap('gray_r'))
  plt.show() 
# -----------------------------------------------------------
class Batcher:
  def __init__(self, num_items, batch_size, seed=0):
    self.indices = np.arange(num_items)
    self.num_items = num_items
    self.batch_size = batch_size
    self.rnd = np.random.RandomState(seed)
    self.rnd.shuffle(self.indices)
    self.ptr = 0
  def __iter__(self):
    return self
  def __next__(self):
    if self.ptr + self.batch_size > self.num_items:
      self.rnd.shuffle(self.indices)
      self.ptr = 0
      raise StopIteration  # ugh.
    else:
      result = self.indices[self.ptr:self.ptr+self.batch_size]
      self.ptr += self.batch_size
      return result
# -----------------------------------------------------------
class Net(T.nn.Module):
  def __init__(self):
    super(Net, self).__init__()
    self.layer1 = T.nn.Linear(784, 100)  # hidden 1
    self.layer2 = T.nn.Linear(100, 50)
    self.layer3 = T.nn.Linear(50,100)
    self.layer4 = T.nn.Linear(100, 784)
    T.nn.init.xavier_uniform_(self.layer1.weight)  # glorot
    T.nn.init.zeros_(self.layer1.bias)
    T.nn.init.xavier_uniform_(self.layer2.weight) 
    T.nn.init.zeros_(self.layer2.bias)
    T.nn.init.xavier_uniform_(self.layer3.weight) 
    T.nn.init.zeros_(self.layer3.bias)
    T.nn.init.xavier_uniform_(self.layer4.weight) 
    T.nn.init.zeros_(self.layer4.bias)
  def forward(self, x):
    z = T.tanh(self.layer1(x))
    z = T.tanh(self.layer2(z))
    z = T.tanh(self.layer3(z))
    z = T.tanh(self.layer4(z))  # consider none or sigmoid
    return z
# -----------------------------------------------------------
def main():
  # 0. get started
  print("Begin autoencoder for MNIST anomaly detection")
  T.manual_seed(1)
  np.random.seed(1)
  # 1. load data
  print("Loading MNIST subset data into memory ")
  data_file = ".\\Data\\mnist_pytorch_1000.txt"
  data_x = np.loadtxt(data_file, delimiter=" ",
    usecols=range(2,786), dtype=np.float32)
  labels = np.loadtxt(data_file, delimiter=" ",
    usecols=[0], dtype=np.float32)
  norm_x = data_x / 255
  # 2. create autoencoder model
  net = Net()
  # 3. train autoencoder model
  net = net.train()  # explicitly set
  bat_size = 40
  loss_func = T.nn.MSELoss()
  optimizer = T.optim.Adam(net.parameters(), lr=0.01)
  batcher = Batcher(num_items=len(norm_x),
    batch_size=bat_size, seed=1)
  max_epochs = 100
  print("Starting training")
  for epoch in range(0, max_epochs):
    if epoch > 0 and epoch % (max_epochs/10) == 0:
      print("epoch = %6d" % epoch, end="")
      print("  prev batch loss = %7.4f" % loss_obj.item())
    for curr_bat in batcher:
      X = T.Tensor(norm_x[curr_bat])
      optimizer.zero_grad()
      oupt = net(X)
      loss_obj = loss_func(oupt, X)  # note X not Y
      loss_obj.backward()
      optimizer.step()
  print("Training complete")
  # 4. analyze - find item(s) with large(st) error
  net = net.eval()  # not needed - no dropout
  X = T.Tensor(norm_x)  # all input item as Tensors
  Y = net(X)            # all outputs as Tensors
  N = len(data_x)
  max_se = 0.0; max_ix = 0
  for i in range(N):
    curr_se = T.sum((X[i]-Y[i])*(X[i]-Y[i]))
    if curr_se.item() > max_se:
      max_se = curr_se.item()
      max_ix = i
  raw_data_x = data_x.astype(np.int)
  raw_data_y = labels.astype(np.int)
  print("Highest reconstruction error is index ", max_ix)
  display(raw_data_x, raw_data_y, max_ix)
  print("End autoencoder anomaly detection demo ")
# -----------------------------------------------------------
if __name__ == "__main__":
  main()

Il programma demo avvia importando i pacchetti NumPy, PyTorch e Matplotlib. Consente di mostrare visivamente la cifra più anomala che è stata trovata dal modello di pacchetto Matplotlib. Alternativa all'importazione l'intero pacchetto di PyTorch, è possibile importare solo i moduli necessari, ad esempio, importazione torch.optim come consenso esplicito.

Il caricamento dei dati in memoria

Utilizzo di MNIST non elaborati dei dati sono piuttosto difficili perché vengono salvato in un formato binario e proprietario. Ho scritto un programma di utilità per estrarre le prime 1.000 elementi dagli elementi di training di 60.000. Salvato i dati come mnist_pytorch_1000.txt in una sottodirectory di dati.

I dati risultanti è simile alla seguente:

7 = 0 255 67 . . 123
2 = 113 28 0 . . 206
...
9 = 0 21 110 . . 254

Ogni riga rappresenta una cifra. Il primo valore per ogni riga è la cifra. Il secondo valore è un carattere di segno di uguale arbitrario semplicemente per migliorare la leggibilità. Il 28 x 28 = 784 successivo i valori sono i valori di pixel in scala di grigi compreso tra 0 e 255. Tutti i valori sono separati da un singolo carattere di spazio vuoto. Figura 3 Mostra l'elemento di dati in corrispondenza dell'indice [30] nel file di dati, che è una cifra tipico "3".

Una cifra MNIST tipico
Figura 3 MNIST tipico cifra

Il set di dati viene caricata in memoria con queste istruzioni:

data_file = ".\\Data\\mnist_pytorch_1000.txt"
data_x = np.loadtxt(data_file, delimiter=" ",
  usecols=range(2,786), dtype=np.float32)
labels = np.loadtxt(data_file, delimiter=" ",
  usecols=[0], dtype=np.float32)
norm_x = data_x / 255

Si noti che la cifra/etichetta si trova nella colonna zero e i valori di 784 pixel sono nelle colonne da due a 785. Dopo che tutti i 1.000 immagini vengono caricate in memoria, viene creata una versione normalizzata dei dati dividendo ogni valore in pixel per 255 in modo che i valori di pixel in scala sono tutte comprese tra 0,0 e 1,0.

La definizione del modello Autoencoder

Il programma demo definisce un 784-100, 50: 100-784 autoencoder. Il numero di nodi nei livelli di input e outpui (784) viene determinato in base ai dati, ma il numero di livelli nascosti e il numero di nodi in ogni livello sono iperparametri che devono essere determinati da tentativi ed errori.

Il programma demo utilizza un programma definito classe Net, per definire l'architettura di livello e il meccanismo di input / output del autoencoder. Un'alternativa consiste nel creare il autoencoder direttamente usando la funzione di sequenza, ad esempio:

net = T.nn.Sequential(
  T.nn.Linear(784,100), T.nn.Tanh(),
  T.nn.Linear(100,50), T.nn.Tanh(),
  T.nn.Linear(50,100), T.nn.Tanh(),
  T.nn.Linear(100,784), T.nn.Tanh())

L'algoritmo di inizializzazione di peso (Glorot uniforme), la funzione di attivazione di livello nascosto (tanh) e la funzione di attivazione di livello di output (tanh) sono gli iperparametri. Poiché tutti i valori di input e outpui sono compresi tra 0,0 e 1,0 per questo problema, logistica sigmoidale è una buona alternativa all'esplorazione per l'attivazione di output.

Set di training e valutare il modello Autoencoder

Il programma demo Prepara training con queste istruzioni:

net = net.train()  # explicitly set
bat_size = 40
loss_func = T.nn.MSELoss()
optimizer = T.optim.Adam(net.parameters(), lr=0.01)
batcher = Batcher(num_items=len(norm_x),
  batch_size=bat_size, seed=1)
max_epochs = 100

Poiché il autoencoder demo non usa la normalizzazione di dropout o un batch, non è necessario impostare in modo esplicito la rete in modalità di training, ma nella mio parere è buona stile a questo scopo. Le dimensioni del batch (40), l'algoritmo di ottimizzazione (Adam), velocità di apprendimento iniziale (0,01) e numero massimo di periodi (100) di training sono tutti gli iperparametri. Se si ha familiarità con neurale apprendimento automatico, si potrebbe pensare, "reti neurali che hanno una grande quantità di iperparametri" e sarebbe corretta.

Oggetto definito dal programma Batcher fornisca gli indici degli elementi di dati casuali 40 alla volta fino a quando tutte le 1.000 voci sono state elaborate (uno come valore epoch). Un approccio alternativo consiste nell'utilizzare gli oggetti set di dati e DataLoader incorporati nel modulo torch.utils.data.

La struttura del processo di training è:

for epoch in range(0, max_epochs):
  # print loss every 10 epochs
  for curr_bat in batcher:
    X = T.Tensor(norm_x[curr_bat])
    optimizer.zero_grad()
    oupt = net(X)
    loss_obj = loss_func(oupt, X)
    loss_obj.backward()
    optimizer.step()

Ogni batch di elementi viene creato utilizzando il costruttore tensore, che usa torch.float32 come tipo di dati predefinito. Si noti che la funzione loss_func confronta gli output calcolati per gli input, che ha l'effetto di training della rete per stimare i valori di input.

Dopo il training, in genere si desidera salvare il modello, ma che è di tipo bit all'esterno dell'ambito di questo articolo. La documentazione di PyTorch presenta buoni esempi che illustrano come salvare un modello con Training in vari modi.

Quando si lavora con autoencoders, nella maggior parte delle situazioni (incluso in questo esempio) è presente alcuna inerente definizione di accuratezza del modello. È necessario determinare la distanza i valori di output calcolato devono essere associati valori di input per essere considerata una stima corretta e quindi scrivere una funzione definita dall'applicazione per calcolare la metrica di accuratezza.

Uso del modello Autoencoder per trovare i dati anomali

Dopo il autoencoder training del modello, l'idea consiste nel trovare elementi di dati che sono difficili da prevedere correttamente o, allo stesso modo, gli elementi che sono difficili da ricostruire. Il codice demo esamina tutti gli elementi di 1.000 dati e calcola il quadrato della differenza tra i valori di input normalizzati e i valori calcolati output simile al seguente:

net = net.eval()  # not needed - no dropout
X = T.Tensor(norm_x)  # all input item as Tensors
Y = net(X)            # all outputs as Tensors
N = len(data_x)
max_se = 0.0; max_ix = 0
for i in range(N):
  curr_se = T.sum((X[i]-Y[i])*(X[i]-Y[i]))
  if curr_se.item() > max_se:
    max_se = curr_se.item()
    max_ix = i

Viene calcolato il numero massimo di errori al quadrato (max_se) e viene salvato l'indice dell'immagine associata (max_ix). Un'alternativa all'individuazione è il singolo elemento con l'errore di ricostruzione più grande per salvare tutti gli errori al quadrato, ordinarli e restituire i primi n elementi in cui il valore di n varia in base il particolare problema si sta esaminando.

Dopo l'elemento di dati anomali a singolo-la maggior parte è stata trovata, viene visualizzata utilizzando la funzione di visualizzazione definito dal programma:

raw_data_x = data_x.astype(np.int)
raw_data_y = labels.astype(np.int)
print("Highest reconstruction error is index ", max_ix)
display(raw_data_x, raw_data_y, max_ix)

I valori di pixel e l'etichetta vengono convertiti dal tipo float32 a int principalmente come una questione di principio perché la funzione di imshow Matplotlib all'interno della funzione di visualizzazione definito dal programma può accettare uno dei due tipi di dati.

Conclusioni

Rilevamento di anomalie usando una autoencoder neurali profonde, come presentata in questo articolo, non è una tecnica ben esaminata. Un grande vantaggio dell'uso di un autoencoder neurale rispetto alle tecniche di clustering più standard è che tecniche neurale consente di gestire i dati non numerici codificando i dati. Tecniche di clustering più dipendono da una misura numerica, ad esempio la distanza euclidea, ovvero che i dati di origine devono essere rigorosamente numerici.

Una tecnica correlata, ma anche poco esplorato per il rilevamento anomalie consiste nel creare un autoencoder per il set di dati in corso il controllo. Quindi, invece di usare l'errore di ricostruzione di trovare i dati anomali, è possibile cluster i dati usando un algoritmo standard, ad esempio k-means perché i nodi del livello nascosto più interna contengono una rappresentazione di ogni elemento di dati rigorosamente numerica. Dopo il clustering, è possibile cercare i cluster che include un numero molto ridotto di elementi di dati oppure cercare gli elementi di dati all'interno dei cluster che sono più distante dal loro centroide cluster. Questo approccio presenta caratteristiche simili a word neurale incorporamento, in cui le parole vengono convertite in vettori numerici che possono quindi essere usati per calcolare una misura della distanza tra le parole.


Dr. James McCaffreylavora per Microsoft Research Redmond, WA Ha lavorato su diverse importanti prodotti Microsoft, tra cui Azure e Bing. Dr. È possibile contattarlo McCaffrey jamccaff@microsoft.com.

Grazie per i seguenti esperti tecnici Microsoft che ha esaminato in questo articolo: Chris Lee, Ricky Loynd