Привет всем, кто выбрал путь ML-самурая! Введение: В данной статье рассмотрим метод опорных векторов (англ. SVM, Support Vector Machine) для задачи классификации. Будет представлена основная идея алгоритма, вывод настройки его весов и разобрана простая реализация своими руками. На примере датасета  будет продемонстрирована работа написанного алгоритма с линейно разделимыми/неразделимыми данными в пространстве  и визуализация обучения/прогноза. Дополнительно будут озвучены плюсы и минусы алгоритма, его модификации. 
Рисунок 1. Фото цветка ириса из открытых источников Решаемая задача: Будем решать задачу бинарной (когда класса всего два) классификации. Сначала алгоритм тренируется на объектах из обучающей выборки, для которых заранее известны метки классов. Далее уже обученный алгоритм предсказывает метку класса для каждого объекта из отложенной/тестовой выборки. Метки классов могут принимать значения . Объект — вектор c N признаками  в пространстве . При обучении алгоритм должен построить функцию , которая принимает в себя аргумент  — объект из пространства  и выдает метку класса . Общие слова об алгоритме: Задача классификации относится к обучению с учителем. SVM — алгоритм обучения с учителем. Наглядно многие алгоритмы машинного обучения можно посмотреть в этой топовой статье (см. раздел «Карта мира машинного обучения»). Нужно добавить, что SVM может применяться и для задач регрессии, но в данной статье будет разобран SVM-классификатор. Главная цель SVM как классификатора — найти уравнение разделяющей гиперплоскости
 в пространстве , которая бы разделила два класса неким оптимальным образом. Общий вид преобразования  объекта  в метку класса : . Будем помнить, что мы обозначили . После настройки весов алгоритма  и  (обучения), все объекты, попадающие по одну сторону от построенной гиперплоскости, будут предсказываться как первый класс, а объекты, попадающие по другую сторону — второй класс. Внутри функции  стоит линейная комбинация признаков объекта с весами алгоритма, именно поэтому SVM относится к линейным алгоритмам. Разделяющую гиперплоскость можно построить разными способами, но в SVM веса  и  настраиваются таким образом, чтобы объекты классов лежали как можно дальше от разделяющей гиперплоскости. Другими словами, алгоритм максимизирует зазор (англ. margin) между гиперплоскостью и объектами классов, которые расположены ближе всего к ней. Такие объекты и называют опорными векторами (см. рис.2). Отсюда и название алгоритма.

Рисунок 2. SVM (основа рисунка отсюда) Подробный вывод правил настройки весов SVM: Чтобы разделяющая гиперплоскость как можно дальше отстояла от точек выборки, ширина полосы должна быть максимальной. Вектор  — вектор нормали к разделяющей гиперплоскости. Здесь и далее будем обозначать скалярное произведение двух векторов как  или . Давайте найдем проекцию вектора, концами которого являются опорные вектора разных классов, на вектор . Эта проекция и будет показывать ширину разделяющий полосы (см. рис.3): 
Рисунок 3. Вывод правил настройки весов (основа рисунка отсюда)         Отступом (англ. margin) объекта x от границы классов называется величина . Алгоритм допускает ошибку на объекте тогда и только тогда, когда отступ  отрицателен (когда  и  разных знаков). Если , то объект попадает внутрь разделяющей полосы. Если , то объект x классифицируется правильно, и находится на некотором удалении от разделяющей полосы. Т.е. алгоритм будет правильно классифицировать объекты, если выполняется условие:   Если объединить два выведенных выражения, то получим дефолтную настройку SVM с жестким зазором (hard-margin SVM), когда никакому объекту не разрешается попадать на полосу разделения. Решается аналитически через теорему Куна-Таккера. Получаемая задача эквивалентна двойственной задаче поиска седловой точки функции Лагранжа.   Всё это хорошо до тех пор, пока у нас классы линейно разделимы. Чтобы алгоритм смог работать и с линейно неразделимых данными, давайте немного преобразуем нашу систему. Позволим алгоритму допускать ошибки на обучающих объектах, но при этом постараемся, чтобы ошибок было поменьше. Введём набор дополнительных переменных , характеризующих величину ошибки на каждом объекте . Введём в минимизируемый функционал штраф за суммарную ошибку:   Будем считать количество ошибок алгоритма (когда M<0). Назовем это штрафом (Penalty). Тогда штраф для всех объектов будет равен сумме штрафов для каждого объекта , где  — пороговая функция (см. рис.4):     Далее сделаем штраф чувствительным к величине ошибки (чем сильнее  "уходит в минус" — тем больше штраф) и заодно введем штраф за приближение объекта к границе классов. Для этого возьмем функцию, которая ограничивает пороговую функцию ошибки (см. рис.4):   При добавлении к выражению штрафа слагаемое  получаем классическую фукцию потерь SVM с мягким зазором (soft-margin SVM) для одного объекта:      — функция потерь, она же loss function. Именно ее мы и будем минимизировать с помощью градиентного спуска в реализации руками. Выведем правила изменения весов, где  – шаг спуска:     Возможные вопросы на собеседованиях (основано на реальных событиях): После общих вопросов про SVM: Почему именно Hinge_loss максимизирует зазор? – для начала вспомним, что гиперплоскость меняет свое положение тогда, когда изменяются веса  и . Веса алгоритма начинают меняться, когда градиенты лосс-функции не равны нулю (обычно говорят: “градиенты текут”). Поэтому мы специально подобрали такую лосс-функцию, у которой начинают течь градиенты в нужное время.  выглядит следующим образом: . Помним, что зазор . Когда зазор  достаточно большой ( или более), выражение  становится меньше нуля и  (поэтому градиенты не текут и веса алгоритма никак не изменяются). Если же зазор m достаточно мал (например, когда объект попадает на полосу разделения и/или отрицательный (при неверном прогнозе классификации), то Hinge_loss становится положительной (), начинают течь градиенты и веса алгоритма изменяются. Резюмируя: градиенты текут в двух случаях: когда объект выборки попал внутрь полосы разделения и при неправильной классификации объекта. Для проверки уровня иностранного языка возможны подобные вопросы: What are the similarities and differences between LogisticRegression and SVM? – firstly, we will talk about similarities: both of algorithms are linear classification algorithms in supervised learning. Some similarities are in their arguments of loss functions:  for LogReg and  for SVM (look at picture 4). Both of algorithms we can configure using gradient descent. Next let’s talk about differences: SVM return class label of object unlike LogReg, which return probability of class membership. SVM can’t work with class labels  (without renaming classes) unlike LogReg (LogReg loss finction for : , where  – real class label,  – algorithm’s return, probability of belonging object  to class). More than that, we can solve hard-margin SVM problem without gradient descent. The task of searching support vectors is reduced to search saddle point in the Lagrange function – this task refers to quadratic programming only. 
Риcунок 4. Функции потерь Простая имплементация классического soft-margin SVM: Внимание! Ссылку на полный код вы найдете в конце статьи. Ниже будут представлены блоки кода, вырванные из контекста. Некоторые блоки можно запускать только после отработки предыдущих блоков. Под многими блоками будут размещены картинки, которые показывают, как отработал код, размещенный над ней. Подробно рассмотрим работу каждого блока строчек: 1) создаем функцию add_bias_feature(a), которая автоматически расширяет вектор объектов, добавляя в конец каждого вектора число 1. Это нужно для того, чтобы «забыть» про свободный член b. Выражение  эквивалентно выражению . Мы условно считаем, что единица — это последняя компонента вектора для всех векторов x, а . Теперь настройку весов  и  будем производить одновременно. 2) далее опишем сам классификатор. Он имеет внутри себя функции инициализации init(), обучения fit(), предсказания predict(), нахождения лосс функции hinge_loss() и нахождения общей лосс функции классического алгоритма с мягким зазором soft_margin_loss(). 3) при инициализации вводятся 3 гиперпараметра: _etha – шаг градиентного спуска (), _alpha – коэффициент быстроты пропорционального уменьшения весов (перед квадратичным слагаемым в функции потерь ), _epochs – количество эпох обучения. 4) при обучении для каждой эпохи обучающей выборки (X_train, Y_train) мы будем брать по одному элементу из выборки, вычислять зазор между этим элементом и положением гиперплоскости в данный момент времени. Далее в зависимости от величины этого зазора мы будем изменять веса алгоритма с помощью градиента функции потерь . Заодно будем вычислять значение этой функции на каждой эпохе и сколько раз мы изменяем веса за эпоху. Перед началом обучения убедимся, что в функцию обучения пришло действительно не больше двух разных меток класса. Перед настройкой весов происходит их инициализация с помощью нормального распределения. Проверка работы написанного алгоритма: Проверим, что наш написанный алгоритм работает на каком-нибудь игрушечном наборе данных. Возьмем датасет Iris. Подготовим данные. Обозначим классы 1 и 2 как , а класс 0 как . С помощью алгоритма PCA (объяснение и применение тут) оптимальным образом сократим пространство 4-х признаков до 2-х с минимальными потерями данных (нам будет проще наблюдать за обучением и разультатом). Далее разделим на обучающую (трейн) выборку и отложенную (валидационную). Обучим на трейн выборке, прогнозируем и проверяем на отложенной. Подберем коэффициенты обучения таким образом, чтобы лосс функция падала. Во время обучения будем смотреть на лосс функцию обучающей и отложенной выборки.    Отлично! Наш алгоритм справился с линейно разделимыми данными. Теперь заставим его отделить классы 0 и 1 от класса 2:   Посмотрим на гифку, которая покажет, как разделяющая прямая изменяла свое положение во время обучения (всего 500 кадров изменения весов. Первые 300 подряд. Далее 200 штук на каждый 130ый кадр):   Спрямляющие пространства Важно понимать, что в реальных задачах не будет простого случая с линейно разделимыми данными. Для работы с подобными данными была предложена идея перехода в другое пространство, где данные будут линейно разделимы. Такое пространство и называется спрямляющим. В данной статье не будут затронуты спрямляющие пространства и ядра. Самую полную математическую теорию Вы сможете найти в 14,15,16 конспектах Е. Соколова и в лекциях К.В.Воронцова. Применение SVM из sklearn: В действительности, почти все классические алгоритмы машинного обучения написаны за Вас. Приведем пример кода, алгоритм возьмем из библиотеки sklearn. Плюсы и минусы классического SVM: Плюсы: Минусы: Применение SVM: Выбор того или иного алгоритма машинного обучения напрямую зависит от информации, полученной во время исследования данных. Но в общих словах, можно выделить следующие задачи: Модификации алгоритма: Существуют различные дополнения и модификации метода опорных векторов, направленные на устранение определенных недостатков: Дополнительные источники на тему SVM: Заключение: Большое спасибо за внимание! Буду благодарен за любые комментарии, отзывы и советы.
Полный код из данной статьи найдете на моем гитхабе. p.s. Благодарю yorko за советы по сглаживанию "углов". Спасибо Алексею Сизых asizykh — физтеху, который частично вложился в код. ML/DL