Фон:

Обнаружение аномалий — это алгоритм, который позволяет машинам учиться на основе уже существующих данных, чтобы определить, являются ли какие-либо новые данные нормальными или близкими к тому, что они видели ранее, или если они ненормальны, то не близки к тому, что наблюдалось ранее. Идея состоит в том, чтобы взять данные, которые уже были установлены как нормальные, и отметить характеристики этих данных, чтобы всякий раз, когда в алгоритм вводятся новые данные, он сравнивал характеристики последнего введения с тем, что уже было установлено как нормальное. Если эти характеристики каким-либо образом отличаются от нормы, то эти новые данные классифицируются как аномальные или аномальные.

Вы можете задаться вопросом о необходимости обнаружения аномалий, если у нас уже есть алгоритмы классификации. В конце концов, простой алгоритм классификации может проверить, является ли что-то 1 или не 1, хорошим или плохим, левым или не левым. Вот почему нам нужно обнаружение аномалий. Обнаружение аномалий используется, когда вы не представляете, сколькими способами что-то может быть неправильным или другим.

Набор данных:

В оставшейся части этой статьи мы будем использовать оценки учащихся по английскому языку и математике для наших иллюстраций. Поэтому учтите, что учитель хочет отслеживать успеваемость своего ученика и замечать любые изменения, такие как улучшения или ухудшения, которые могут дать представление об общем поведении ученика. Чтобы создать базовый набор данных, состоящий из «нормальных» оценок для этого ученика, я создам значения, используя случайный класс Python, и введу их в массив Numpy.

# Import necessary libraries
import numpy as np
import matplotlib.pyplot as plt
import random
import pandas as pd
from scipy.stats import norm
from sklearn.preprocessing import MinMaxScaler
# Generate random dataset for the algorithm analysis
def generate_dataset():
  X_train_list = []
  X_val_list = []
  y_val_list = []

  # Generate the training set containing values considered to be normal
  for i in range(50):
    X_train_list.append([random.uniform(65,75), random.uniform(60,70)])

  X_train = np.array(X_train_list)

  # Generate the validation set of some normal and other anomolous values
  for i in range(20):
    X_val_list.append([random.uniform(65,75), random.uniform(60,70)])
    y_val_list.append(0)
  for i in range(5):
    X_val_list.append([random.uniform(40,60), random.uniform(50,80)])
    y_val_list.append(1)
  for i in range(5):
    X_val_list.append([random.uniform(75,85), random.uniform(50,80)])
    y_val_list.append(1)

  X_val = np.array(X_val_list)
  y_val = np.array(y_val_list)

  return X_train, X_val, y_val
# Initialise Training dataset for training
X_train, X_val, y_val = generate_dataset()

X_train[:3]
array([[70.6879255 , 60.77661224],
       [69.55868958, 67.7040642 ],
       [69.60583163, 64.59912975]])
       

Данные X_train — это данные, содержащие только нормальные значения, тогда как X_val — это данные, содержащие как нормальные, так и аномальные данные. Эти точки данных помечены с помощью массива y_val, который указывает, является ли наблюдение нормальным {0} или ненормальным {1}.

На точечной диаграмме значения X_train представлены синими кружками, а точки X_val — красными крестиками.

# Visualise the data
plt.scatter(X_train[:,0], X_train[:, 1], marker='o')
plt.scatter(X_val[:,0], X_val[:, 1], color='r', marker='x')
plt.ylim(40,100)
plt.xlim(30,100)

Алгоритм:

Существуют различные доступные алгоритмы обнаружения аномалий, однако мы рассмотрим алгоритм, основанный на гауссовском распределении данных для определения вероятности возникновения. Поскольку у нас есть наблюдение за двумя разными объектами, т. е. за баллом по математике и баллом по английскому языку, мы в основном определим вероятность того, что произведение баллов попадает в определенный диапазон.

Из визуализации выше мы видим, что большинство значений, которые классифицируются как нормальные (отмечены синими точками), попадают в заданный круг, а ненормальные попадают за пределы этого круга. Поэтому алгоритм возьмет любой новый набор точек данных и попытается определить, попадает ли он в область, где на графике расположены другие синие точки. Если это так, то новой точке данных или наблюдению присваивается тег «нормальный», а если нет, то он отмечается как аномалия.

Вероятность конкретного события определяется следующей формулой.

Чтобы объяснить это более технически, при рассмотрении набора реальных точек данных или наблюдений распределение данных обычно принимает форму колоколообразной кривой с пиком кривой, представляющим среднее значение данных. Следовательно, большинство значений попадают в диапазон среднего значения плюс или минус дисперсия данных. Чем дальше точка данных удаляется от этого диапазона в обе стороны, тем меньше вероятность ее появления.

features = ["Maths", "English"]
fig,ax = plt.subplots(1,2, figsize=(15,7))

for i in range(len(mu)):
  x_axis = np.arange(mu[i] - 2 * var[i], mu[i] + 2 * var[i], 0.05)
  ax[i].plot(x_axis, norm.pdf(x_axis, mu[i], np.sqrt(var[i])))
  ax[i].grid()
  ax[i].set_xlabel("Score")
  ax[i].set_ylabel("Probability")
  ax[i].set_title(f"Distribution curve for {features[i]} Scores")

plt.show()

Таким образом, любое значение, выходящее за пределы этого диапазона, считается менее вероятным.

Задача:

Наша работа состоит в том, чтобы определить среднее значение и дисперсию точек данных, а также определить, какое значение вероятности слишком мало, чтобы его можно было считать нормальным значением или наблюдением.

Основываясь на приведенном выше уравнении 1, мы можем использовать этот код, чтобы получить вероятности каждого наблюдения в нашем наборе данных.

# Get the values if Mean and Variance for the data set
def get_gaussian_distribution(X):
  """
  gets the gaussian distribution of the data

  parameters:
  X (numpy ndarray) : m * n dimensioned unlabeled data

  returns:
  mu () : 1 * n array showing the mean of the data
  var () : 1 * n array showing the variance of the data
  
  """
  m, n = X.shape
  # Compute the mean of each featrue in the data set
  mu = sum(X) / m
  # Compute the variance of each feature in the data set
  var = sum((X-mu)**2) / m
  
  return mu, var
mu, var = get_gaussian_distribution(X_train)
print(f"The mean is {mu}")
print(f"The variance is {var}")
The mean is [70.09867791 64.91773504]
The variance is [8.5011539 6.1037028]

Наш алгоритм определяет вероятность возникновения конкретного наблюдения как произведение отдельных вероятностей появления отдельных оценок наблюдения.

# Get probabilities of features when provided with the array of all observations when provided witht he values of the gaussian distribution 

def get_probabilities(X, mu, var):
  # Check that X is a 2D array
  if len(X.shape) != 2:
     X = X.reshape(1, len(X))
  # Initialise the array to hold the probabilities
  p_x_j = np.zeros(len(X))
  
  # Get probabiliities for each observation
  for j in range(len(X)):
    # Initialise the array that will hold the probabilities for each feature associated with the overall observation
    p_x_i = np.zeros(X[j].shape[0])
    for i in range(X[j].shape[0]):
      # Compute the probability of each individual feature
      denom = np.sqrt(2 * np.pi * var[i])
      exp_val = -((X[j][i]-mu[i]) ** 2 )/ (2 * var[i])
      p_x_i[i] = (1 / denom) * np.exp(exp_val)

    # Compute the overall probability of that observation as the product of all the feature probabilities
    p_x_j[j] = np.prod(p_x_i)

  return p_x_j
X_train_prob = get_probabilities(X_train, mu, var)

Теперь, когда у нас есть вероятность возникновения для каждой точки данных в нашем наборе данных, нам нужно определить, какой будет наша пороговая вероятность. Это вероятность, ниже которой наблюдение будет считаться аномальным или аномальным. Для этого нам нужно будет использовать показатель, известный как оценка F1.

Боковая панель: F1 Score

Оценка F1 на самом деле представляет собой комбинацию двух других очень распространенных показателей в машинном обучении — точности и отзыва. Цель оценки точности состоит в том, чтобы гарантировать, что только наиболее вероятные значения классифицируются как истинные, тем самым уменьшая количество ложных срабатываний. С другой стороны, оценка отзыва направлена ​​на то, чтобы как можно больше положительных результатов были классифицированы как положительные. Другими словами, цель состоит в том, чтобы уменьшить количество ложноотрицательных результатов. Если вам нужно больше информации о точности и полноте, ознакомьтесь с этой статьей здесь.

Специалисту по обработке и анализу данных иногда необходимо убедиться, что у него есть наилучшие оценки точности и отзыва, которые они могут получить, при этом один не намного больше другого. А в других случаях, чтобы получить более целостное представление о рассматриваемой модели, мы используем показатель F1.

Чтобы рассчитать балл F1, мы используем следующую формулу

Теперь, когда мы понимаем, что такое оценка F1, мы можем теперь перебирать различные пороговые значения, чтобы определить, какое из них даст нам лучший результат F1. Значение эпсилон, которое производит это, будет нашим пороговым значением.

# Determine the threshold probability below which an observation will be considered abnormal
# The threshold will be considered using the F1 score
def select_threshold(y_val, p_val):

  best_F1 = 0
  best_epsilon = 0
  step_value = (p_val.max() - p_val.min()) / 1000
  # Consider a wide range of theshold values
  for epsilon in np.arange(p_val.min(), p_val.max(), step_value):
    
    predictions = p_val < epsilon
    # Compute the True Positive (tp), False Positive(fp), False Negative(fn)
    tp = sum(predictions[y_val == 1])
    fp = sum(predictions[y_val == 0])
    fn = sum(y_val[predictions == 0])

    precision_score = tp / (tp + fp)
    recall_score = tp / (tp + fn)
    
    #Compute the f1 score for each value of epsilon
    F1 = (2 * precision_score * recall_score) / (precision_score + recall_score)

    if F1 > best_F1:
      best_F1 = F1
      best_epsilon = epsilon

  return best_F1, best_epsilon
F1_score, epsilon = select_threshold(y_val, get_probabilities(X_val, mu, var))
print("F1 Score ", F1_score)
print("Best Epsilon ", epsilon)
F1 Score  1.0
Best Epsilon  0.0004997651844581784

Большой! Мы достигли своего порога. Наконец, мы можем использовать нашу модель на наших тестовых данных. Помните, что мы будем использовать тот же порог, среднее значение и дисперсию на основе наших данных обучения и проверки.

# Predict anomalous occurences based on already trained parameters
def predict(X_test, epsilon, mu, var):

  y_prob = get_probabilities(X_test, mu, var)

  y_pred = (y_prob < epsilon) + 0

  return y_pred
y_pred = predict(X_val, epsilon, mu, var)
fig, ax = plt.subplots(1,2, figsize = (12,5))

scatter1= ax[0].scatter(X_val[:,0], X_val[:,1], c=y_val)
ax[0].set_xlabel("Math Scores")
ax[0].set_ylabel("English Scores")

scatter2= ax[1].scatter(X_val[:,0], X_val[:,1], c=y_pred)
ax[1].set_xlabel("Math Scores")
ax[1].set_ylabel("English Scores")
ax[1].set_label(y_pred)

fig.legend(*scatter1.legend_elements());
fig.suptitle("Normal VS Abnormal Data from Validation Set (Left) and Model Prediction (Right)");

Приятный сюрприз…

Если вы внимательно посмотрите на визуализацию, вы заметите, что есть одно наблюдение, которое было помечено как аномалия в нашем наборе данных проверки, которое наша модель фактически классифицировала как нормальное наблюдение. И это правильно.

В следующей статье мы увидим больше об обнаружении аномалий, поскольку мы больше исследуем выбор порога для этого конкретного алгоритма. Вопрос, на который нужно ответить, таков: что, если бы у нас еще не было помеченных аномалий? Как мы могли решить, какое значение использовать в качестве эпсилон?

Большое спасибо, что остались до конца!! Надеюсь, вам понравилось читать эту статью. Если да, подпишитесь на эту учетную запись и ждите новых статей от меня.

Весь мой код для этой статьи доступен здесь