Наивный Байесовский классификатор на Python

Добрый день, уважаемые читатели. Сегодня мы разберём уникальный алгоритм машинного обучения — наивный Байесовский классификатор. Без математики здесь не обойтись, поэтому сразу приступаем.

Подпишись на группу Вконтакте и Телеграм-канал. Там еще больше полезного контента для программистов.
А на YouTube-канале ты найдешь обучающие видео по программированию. Подписывайся!

Теорема Байеса и наивное предположение

Весь алгоритм построен на теореме Байеса — одной из фундаментальных теорем теории вероятностей. Звучит она следующим образом:

    \[P(A | B) = \frac{P(B | A) \cdot P(A)}{P(B)}\]

Для читателей, незнакомых с нотацией, приведенной выше, привожу список пояснений каждой части теоремы:

  • P(A | B) — вероятность гипотезы А при наступлении события В, т. е. вероятность А, когда В уже верно;
  • P(B | A) — вероятность наступления события В, если гипотеза А верна;
  • P(A) — вероятность того, что гипотеза А верна;
  • P(B) — вероятность наступления события В;

В задачах классификации данная теорема рассматривается следующим образом:
В — один набор признаков (sample). А — вероятность того, что набор В принадлежит к определенному классу.

Также, отбросим знаменатель, так как в нашем случае он является константным.

Рассматривая каждый признак, наш классификатор выглядит следующим образом:

    \[c = \text{arg max }(c \in C) P(c|x_1, x_2, \ldots, x_n) = \text{arg max }(c \in C) P(c) \cdot P(x_1, x_2, \ldots, x_n|c)\]

И теперь наступает время для «наивного» предположения — именно отсюда алгоритм наивной классификации и взял своё название. Предположим, что x_1, x_2, \ldots, x_n никак не коррелируют между собой, и тогда наша «идея» классификации выглядит следующим образом:

    \[c = \text{arg max } (c \in C) P(c) \prod_{i} P(x_i|c)\]

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

    \[c = \text{arg min } (c \in C) \text{ -ln P(c)} + \sum_{i} \text{-ln }P(x_i|c)\]

Теперь нам остается лишь вычислить P(c) и P(x_i|c), что и является обучением модели.

Реализация на Python

Теперь запрограммируем нашу математическую модель с помощью Python. Для начала импортируем необходимые нам модули:

from collections import defaultdict

import numpy as np
from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split

Примечание. Придерживаемся правил РЕР8, и импортируем функции и классы из модуля стандартной библиотеки в самом начале, а затем все остальные.

Затем, подобая программному интерфейсу sklearn, создадим класс модели:

class NaiveBayesClassifier(object):
    def init(self):  
        self.__class_freq = defaultdict(lambda:0)
        self.__feat_freq = defaultdict(lambda:0)


    def fit(self, X, y):
        # calculate classes and features frequencies
        for feature, label in zip(X, y):
            self.__class_freq[label] += 1
            for value in feature:
                self.__feat_freq[(value, label)] += 1

        # normalizate values
        num_samples = len(X)
        for k in self.__class_freq:
            self.__class_freq[k] /= num_samples

        for value, label in self.__feat_freq:
            self.__feat_freq[(value, label)] /= self.__class_freq[label]

        return self

В методе fit мы, благодаря классу defaultdict из модуля collections, так раз и вычисляем параметры модели.

Приступим к реализации предсказания модели:

def predict(self, X):
    # return argmin of classes 
    return min(self.__class_freq.keys(), 
               key=lambda c : self.__calculate_class_freq(X, c)) 
 
def __calculate_class_freq(self, X, clss):
    # calculate frequence for current class
    freq = - np.log(self.__class_freq[clss])
   
    for feat in X: 
        freq += - np.log(self.__feat_freq.get((feat, clss), 10 ** (-7)))
    return freq

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

Вот и всё. Теперь осталось протестировать нашу модель. В качестве набора данных возьмем Iris — самый первый и простой набор данных.

data = load_iris()

X, y = data.data, data.target

X_train, X_test, y_train, y_test = train_test_split(X, y,
                                                    test_size=0.2,
                                                    random_state=42)

model = NaiveBayesClassifier().fit(X_train, y_train)

predictions = [model.predict(x) for x in X_test]
print(accuracy_score(predictions, y_test))

Output: 0.9666666666666667.

Неплохой результат, как для модели, написанного своими руками, при чём так минималистично.

Заключение

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

Если помимо вероятностного прогнозирования вас интересует линейная алгебра, советую прочитать статью «Линейная алгебра — программируем с NumPy».

А также подписывайтесь на группу ВКонтакте, Telegram и YouTube-канал. Там еще больше полезного и интересного для программистов.