В ML меня всегда интересовало то, что чем больше вы узнаете о нем, тем больше понимаете, как мало вы знаете. Один такой случай, который произошел со мной несколько месяцев назад, когда человек спросил меня, могу ли я помочь ему в SVM, и я, будучи собой, был уверен, что это не имеет большого значения. Это было большое дело.

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

Давайте продолжим и попробуем немного понять, как можно создать собственное ядро ​​и обучить на нем простую модель SVC.

Получение наших данных

Каждый конвейер машинного обучения практически не существует без данных. Итак, давайте начнем с получения наших данных. Я простой человек, поэтому для простоты я просто создам набор данных, используя утилиту make_classification в sklearn.datasets.

from sklearn.datasets import make_classification

x,y = make_classification(n_samples = 1000)
print(x.shape, y.shape)

Теперь, когда у нас есть все данные, давайте продолжим и разберемся с работой и использованием ядер в SVM.

Понимание ядер

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

Чтобы решить эту проблему, мы игнорируем некоторые моменты и допускаем ошибку для достижения оптимального запаса, мы называем эти точки векторами поддержки. А теперь маржа называется soft margin. Итак, ваша цель - максимизировать мягкую маржу. Допустимая степень ошибочной классификации определяется с помощью перекрестной проверки. Это хороший подход, пока вы не столкнетесь с чем-то, что я называю не таким простым распределением. Взгляните на следующий не такой простой пример: -

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

Уф, это было много сложного. Но как работает RBF? RBF фактически отображает данные в бесконечных измерениях, что затрудняет визуализацию. Так что оставим это для другой статьи.

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

Мое ядро, мои правила

Каждый раз, когда вы создаете экземпляр SVC (), с ним связывается ядро, которое обрабатывает часть сопоставления, если вы не укажете это явно, тогда ядро ​​будет приниматься как RBF со следующим образом: -

Если вы не понимаете, что написано выше, ничего страшного, можете это забыть. Важно понять, как это работает: две точки вычисляют для них RBF и сохраняют их в их местоположении в граммовой матрице. Матрица Грама - это то, что мы будем использовать для определения отношений между парами для данного ядра. Теперь два способа обучить SVM через собственное ядро: -

  • Передача функции ядра
  • Матрица прохождения Грама

Для невинных душ, которые не знают о Матрице Грама, это в основном то, как представлены функции вашего ядра, вот и все. Если вы хотите вдаваться в математические подробности, не стесняйтесь обращаться в Google.

Передав функцию ядра в качестве аргумента

Давайте теперь реализуем простую линейную функцию ядра и обучим нашу модель на ней. Линейное ядро ​​выглядит следующим образом: -

Все просто, правда? Все, что вам нужно сделать, это просто выполнить скалярное произведение между парами. Давайте создадим функцию, которая сделает то же самое.

def linear_kernel(x_i, x_j):
    return x_i.dot(x_j.T)

Это было просто, не так ли? Давайте продолжим и создадим 2 классификатора, один из которых использует линейное ядро, определенное в sklearn, а другой, который мы создали, а затем сравним их производительность.

from sklearn.svm import SVC
from sklearn.metrics import accuracy_score
clf1 = SVC(kernel = linear_kernel)
clf1.fit(x,y)
print(f'Accuracy on Custom Kernel: {accuracy_score(y, clf1.predict(x))}')
clf2 = SVC(kernel = 'linear')
clf2.fit(x,y)
print(f'Accuracy on Inbuilt Kernel: {accuracy_score(y, clf2.predict(x))}')

Вывод:-

Accuracy on Custom Kernel: 0.961
Accuracy on Inbuilt Kernel: 0.961

Что ж, результаты такие же. Это было круто, не правда ли? Теперь попробуем сделать то же самое, используя второй метод.

Пройдя через матрицу Грама

Вы можете определить свои собственные ядра, указав ядро ​​как функцию, как мы видели в приведенном выше примере, или предварительно вычислив матрицу Грама. Сначала мы создадим функцию, которая преобразует матрицу грамма в данные и функцию, а затем создадим функцию для вычисления RBF.

import numpy as np
def get_gram(x1, x2, kernel):
    return np.array([[kernel(_x1, _x2) for _x2 in x2] for _x1 in x1])
def RBF(x1, x2, gamma  = 1):
    return np.exp(-gamma*np.linalg.norm(x1-x2))

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

  • Мы передадим kernel = ‘precomputed’
  • Мы передадим данные в виде матрицы граммов, а данные будут передаваться в функции fit () или Forecast ().

Но все обстоит немного иначе, если у вас есть набор для тестирования. Например, если у нас есть x_train и x_test, тогда матрица граммов для передачи в fit () вычисляется между x_train и x_train, но для прогнозирования на x_test она вычисляется между x_test и x_train.

import numpy as np
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x, y, stratify = y)
def get_gram(x1, x2, kernel):
    return np.array([[kernel(_x1, _x2) for _x2 in x2] for _x1 in x1])
def RBF(x1, x2, gamma  = 1):
    return np.exp(-gamma * np.linalg.norm(x1-x2))
clf1 = SVC(kernel = 'precomputed')
clf1.fit(get_gram(x_train, x_train, RBF), y_train)
print(f'Accuracy on Custom Kernel: {accuracy_score(y_test, clf1.predict(get_gram(x_test, x_train, RBF)))}')
clf2 = SVC(kernel = 'rbf')
clf2.fit(x_train,y_train)
print(f'Accuracy on Inbuilt Kernel: {accuracy_score(y_test, clf2.predict(x_test))}')

Вывод:-

Accuracy on Custom Kernel: 0.912
Accuracy on Inbuilt Kernel: 0.904

Код

Напутственные слова

Что ж, как насчет того, чтобы наше ядро ​​действительно работало хорошо. Шум! Теперь вы знаете, как обучить SVM на собственном ядре. Вы можете попробовать реализовать другие ядра, такие как Thin-Plate, Cauchy и другие ядра с устрашающими именами. Использование пользовательских ядер обычно не используется на практике, и, по моему опыту, это, вероятно, связано с тем, что пользовательские ядра требуют много времени для обучения и прогнозирования, особенно если вы используете подход с использованием матрицы граммов. Но в том, чтобы узнать об этом, нет ничего плохого, верно? На этом я поздравляю вас с тем, что вы узнали что-то новое, и теперь вы можете попробовать что-то новое. Следующая статья будет особенно интересной. Она о, вероятно, самом популярном алгоритме, но с нашей собственной изюминкой. До скорой встречи.

Прочтите мой блог HashNode здесь: https://krypticmouse.hashnode.dev/training-svm-over-custom-kernels