Метод опорных векторов (SVM) - Лена Капаца
Метод опорных векторов (SVM) by Лена Капаца May 7, 2021 Основы

Метод опорных векторов (Support Vector Machine) – это алгоритм Машинного обучения (ML), который проецирует Наблюдения (Observation) в n-мерном пространстве Признаков (Feature) с целью нахождения гиперплоскости, разделяющей наблюдения на классы:

Подход можно использовать как для Классификации (Classification), так и для задач Регрессии (Regression). Чаще всего он используется в задачах классификации. Мы изображаем каждый элемент данных как точку в n-мерном пространстве, где n – количество признаков, причем значение каждой ячейки наблюдения является конкретной координатой в соответствующей плоскости. Затем мы выполняем классификацию, находя разделяющую гиперплоскость, которая разграничивает два класса.

Опорные векторы (обозначены розовым на изображении выше) формируются за счет пограничных наблюдений. Классификатор SVM – это граница, которая лучше всего разделяет два класса.

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

Плоскость с наибольшим расстоянием от крайних наблюдений обоих классов является наилучшей. Это расстояние называется Полем (Margin).

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

Алгоритм SVM обладает Устойчивостью (Robustness) к выбросам и позволяет игнорировать выбросы и находить гиперплоскость с максимальным полем:

SVM может решить и эту проблему без труда с помощью дополнительной функции z = x2 + y2. Благодаря введению оси z кластеры выглядят по-другому в паре осей x и z:

На приведенном выше графике следует учитывать следующее:

В классификаторе SVM легко создать линейную гиперплоскость между этими двумя классами. Но возникает еще один животрепещущий вопрос: нужно ли добавлять эту функцию вручную, чтобы получить гиперплоскость? Нет, в алгоритме SVM есть метод, называемый Ядерным методом (Kernel Trick). Это функция, которая берет низкоразмерное входное пространство и преобразует его в более высокоразмерное, то есть преобразует неразрешимую проблему в разрешимую. Проще говоря, он выполняет несколько чрезвычайно сложных преобразований данных, а затем обнаруживает простой способ классификации.

Возвращаясь к исходным условиям сценария 6, мы получаем окружность как нелинейную гиперплоскость:

SVM и Scikit-learn

SVM прекрасно реализован в SkLearn. Для начала импортируем необходимые библиотеки:

import numpy as np

import matplotlib
import matplotlib.pyplot as plt

import scipy
from scipy import stats

import seaborn as sns; sns.set()

import sklearn as sk
# сгенерируем игрушечный датасет
from sklearn.datasets.samples_generator import make_blobs
# используем функцию-классификатор SVC
from sklearn.svm import SVC 

%matplotlib inline

Сгенерируем датасет, где наблюдения сгруппированы в два кластера-класса по 25 наблюдений каждое. Стандартное отклонение внутри каждой группы равно 0.6. Построим Точечную диаграмму (Scatterplot) для нашего датасета:

# сгенерируем классифицированный набор данных
X, y = make_blobs(n_samples = 50, centers = 2,
                  random_state = 0, cluster_std = 0.60)
plt.scatter(X[:, 0], X[:, 1], c = y, s = 50, cmap = 'autumn')

Цветовая гамма "осень" окрасила кластеры в два цвета:

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

# создадим сетку. np.linspace() вернет равномерно расположенные числа в указанном интервале
xfit = np.linspace(-1, 3.5)
plt.scatter(X[:, 0], X[:, 1], c = y, s = 50, cmap = 'autumn')

# выделим неверно классифицированные наблюдения крестиком
plt.plot([0.6], [2.1], 'x', color = 'red', markeredgewidth = 2, markersize = 10)

# построим сетку
for m, b in [(1, 0.65), (0.5, 1.6), (-0.2, 2.9)]:
    plt.plot(xfit, m * xfit + b, '-k')

# зададим предельные значения оси x
plt.xlim(-1, 3.5);

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

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

xfit = np.linspace(-1, 3.5)
plt.scatter(X[:, 0], X[:, 1], c = y, s = 50, cmap = 'autumn')

for m, b, d in [(1, 0.65, 0.33), (0.5, 1.6, 0.55), (-0.2, 2.9, 0.2)]:
    yfit = m * xfit + b
    plt.plot(xfit, yfit, '-k')
    # отрисуем полупрозрачные поля разнойширины
    plt.fill_between(xfit, yfit - d, yfit + d, edgecolor = 'none',
                     color = '#AAAAAA', alpha = 0.4)

plt.xlim(-1, 3.5);

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

На помощь здесь придет SVC (Support Vector Classification), который рассчитает оптимальную ширину поля и направление разделяющей гиперплоскости:

model = SVC(kernel = 'linear', C = 1E10) # выберем линейное решение
model.fit(X, y) # обучим классификатор

Настройки алгоритма по умолчанию выглядят следующим образом:

SVC(C=10000000000.0, break_ties=False, cache_size=200, class_weight=None,
    coef0=0.0, decision_function_shape='ovr', degree=3, gamma='scale',
    kernel='linear', max_iter=-1, probability=False, random_state=None,
    shrinking=True, tol=0.001, verbose=False)

Нам предстоит инициализировать кастомную функцию plot_svc_decision_function():

def plot_svc_decision_function(model, ax = None, plot_support = True):
    # Отобразим разрешающую гиперплоскость в двумерном пространстве
    if ax is None:
        ax = plt.gca()
    xlim = ax.get_xlim() # определим максимальное значение по оси x
    ylim = ax.get_ylim()
    
    # подготовим разметку для сетки
    x = np.linspace(xlim[0], xlim[1], 30)
    y = np.linspace(ylim[0], ylim[1], 30)
    
    Y, X = np.meshgrid(y, x) #  np.meshgrid() вернет матрицы координат
    xy = np.vstack([X.ravel(), Y.ravel()]).T
    P = model.decision_function(xy).reshape(X.shape)
    
    ax.contour(X, Y, P, colors = 'k', # нарисуем пунктирные границы полей  
               levels = [-1, 0, 1], alpha = 0.5,
               linestyles = ['--', '-', '--'])
    
    if plot_support:
        ax.scatter(model.support_vectors_[:, 0],
                   model.support_vectors_[:, 1],
                   s = 300, linewidth = 1, facecolors = 'none');
    ax.set_xlim(xlim)
    ax.set_ylim(ylim)

Несмотря на подробное описание функции, некоторые элементы (например, support_vectors_) работают неявно, что позволяет отнести SVM к Черным ящикам (Black Box).

plt.scatter(X[:, 0], X[:, 1], c = y, s = 50, cmap = 'autumn')
plot_svc_decision_function(model);

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

Ноутбук, не требующий дополнительной настройки на момент написания статьи, можно скачать здесь.

Автор оригинальной статьи: Sunil Ray, Jake VanderPlas

Фото: @utsmanmedia

© Лена Капаца. Все права защищены.