Как интерпретировать атрибут coef_ линейного SVC из scikit-learn для задачи мультиклассовой классификации

В моем недавнем посте я показал вам, как интерпретировать атрибуты coef_ и intercept_ подобранной модели SVC для задачи бинарной классификации, а также как построить решающую плоскость. Если вы еще не читали его, я рекомендую вам ознакомиться с ним, прежде чем читать этот пост. Вот ссылка на него:



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

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

Готовый? Пойдем!

Создание некоторых фиктивных данных

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

import numpy as np
import matplotlib.pyplot as plt
X = np.array([
[1.5, 1  ], [1.5, 2  ], [ 2,  1  ], [ 2,  2  ], #upper right (Green)
[1,  -1  ], [1,  -2  ], [ 2, -1  ], [ 2, -2  ], #lower right (Blue)
[-1, -1.5], [-1, -2.5], [-2, -1.5], [-2, -2.5], #lower left (Red)
[-1,  1  ], [-1,  2  ], [-2,  1  ], [-2,  2  ], #upper left (Yellow)
])
y = np.array([
'green','green','green','green',
'blue','blue','blue','blue',
'red','red','red','red',
'yellow','yellow','yellow','yellow',
])
plt.scatter(X[:, 0], X[:, 1], c=y)
plt.show()

Это должно привести к следующему сюжету:

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

Подгонка SVC к данным

Следующим шагом будет использование библиотеки scikit-learn для подбора SVC к нашим данным. Это можно сделать просто так:

from sklearn.svm import SVC
clf = SVC(kernel='linear')
clf.fit(X, y)

С точки зрения SVC существует два метода создания гиперплоскостей. Один из них называется Один против одного (OVO), а другой называется Один против остальных (OVR). Я не буду вдаваться в дальнейшие подробности об этом сейчас, так как это тема для другого поста. Пока вам просто нужно знать, что мы будем создавать OVO SVC. Короче говоря, это означает, что мы будем сравнивать каждый класс с любым другим классом, и для каждого из этих сравнений будет соответствующая гиперплоскость.

Мы можем взглянуть на атрибуты coef_ и intercept_ подогнанного SVC, как и в предыдущей статье.

print('coef_\n', clf.coef_)
print('intercept_', clf.intercept_)
>> coef_
   [[ 0.00000000e+00 -1.00000000e+00]
    [ 9.99532404e-01  2.22044605e-16]
    [ 5.00000000e-01 -5.00000000e-01]
    [ 4.00000000e-01  4.00000000e-01]
    [ 8.00000000e-01  0.00000000e+00]
    [ 0.00000000e+00 -8.00000000e-01]]
>> intercept_
   [ 1.45716772e-16
     4.30211422e-16
     0.00000000e+00
     0.00000000e+00
    -2.00000000e-01
    -2.00000000e-01]

Итак, очевидно, мы получаем 6 векторов для атрибута coef_, а также 6 значений для атрибута intercept_. Это значения, которые мы собираемся использовать для построения гиперплоскостей, а также для классификации новых точек данных.

Почему цифра 6, спросите вы? Чтобы ответить на этот вопрос, давайте заглянем в документацию scikit-learn для SVC:

Теперь мы можем видеть, что значение 6 получается из уравнения:

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

Надеюсь, теперь это понятно. Однако это оставляет нас с дополнительным вопросом: какой вектор coef_ и значение intercept_ соответствуют какой метке? Как я кратко упомянул выше, метод OVO сравнивает каждый класс друг с другом. Это означает, что мы сравниваем

ярлык 1 против ярлыка 2
ярлык 1 против ярлыка 3
ярлык 1 против ярлыка 4
ярлык 2 против ярлыка 3

и так далее… пока все метки не будут сопоставлены друг с другом, (подробнее о том, как классифицируется новая точка данных, я расскажу позже)

Каждый из наших coef_ векторов представляет такое сравнение. Но все же, как мы узнаем, какой вектор соответствует какому сравнению.

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

line_colors = {0:'red', 1:'blue', 2:'green', 3:'yellow', 4:'black', 5:'gray'}
number_of_coefficients = len(clf.coef_)
figure, axis = plt.subplots(3, 2)
row = 0
col = 0
for j in range(number_of_coefficients):
  for i in range(j+1):
     w = clf.coef_[i]
     w = [w[0], w[1] + 0.0001] #adding 0.0001 just to make sure we       
                               #don't devide by 0
     a = -w[0] / w[1]
     xx = np.linspace(-4,4)
     yy = a * xx - clf.intercept_[i] / w[1]
     axis[row, col].plot(xx, yy, label=f'h{i}', c=line_colors[i])
  
  axis[row, col].set_xlim([-4, 4])
  axis[row, col].set_ylim([-4, 4])
  axis[row, col].scatter(X[:, 0], X[:, 1], c = y)
  row = row + 1 if col == 1 else row
  col = col + 1 if col != 1 else 0
plt.show()

(Чтобы статья не стала слишком длинной, я не включил объяснение фрагмента кода, просто обратите внимание, что он используется для создания графиков с использованием значений coef_ и intercept_)

Получается следующий сюжет:

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

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

  1. Красная линия сравнивает синий класс с зеленым классом.
  2. Синяя линия сравнивает синий класс с красным классом.
  3. Зеленая линия сравнивает синий класс с желтым классом.
  4. Желтая линия сравнивает зеленый класс с красным классом.
  5. Черная линия сравнивает зеленый класс с желтым классом.
  6. серая линия сравнивает красный класс с желтым классом.

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

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

Быстрый пример: возьмем красную линию, ее первая метка — это синий класс, а вторая — зеленый класс. >. Если заданные данные указывают на него справа от линии, то эта гиперплоскость классифицирует его как синий.

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

Пример с новой точкой

Давайте посмотрим на пример. Мы можем построить новую точку [1.5, -2.5] (коричневая точка на графике), которая, как мы ожидаем, будет классифицирована как синяя точка. Вот график, чтобы сделать его более заметным:

Проходя сравнения (гиперплоскости) одно за другим:

  1. красная линия классифицирует его как синий
  2. Синяя линия классифицирует его как синий
  3. Зеленая линия классифицирует его как синий.
  4. Желтая линия классифицирует его как красный
  5. черная линия классифицирует его как зеленый
  6. серая линия классифицирует его как красный

Новая точка классифицируется как синяя3 раза, красная 2 раза и зеленая1 раз. Поэтому мы классифицируем точку с помощью синей метки.

Если мы используем подобранную модель для прогнозирования, мы должны получить тот же результат. Это можно сделать следующим образом:

new_point = np.array([[1.5, -2.5]])
print(clf.predict(new_point))
>> ['blue']

И мы получаем синюю метку, как и ожидалось.

Как определяется порядок сравнений

Я надеюсь, что приведенный выше пример был легким для вас, и вы поняли, как мы пришли к выводу о том, какие гиперплоскости соответствуют каким сравнениям. Однако это был простой эксперимент с легко разделяемыми данными. Это позволило нам определить взаимосвязь между гиперплоскостями и атрибутами coef_ посредством визуального осмотра. Однако с реальными данными, скорее всего, не так просто работать. Итак, вы можете задаться вопросом, существует ли система для порядка сравнений?

Другими словами, можем ли мы быть уверены, что гиперплоскость, представленная coef_[0], всегда будет той, которая отделяет класс синий от класса зеленый?

Я пришел к выводу, что порядок определяется сортировкой этикеток. В этом случае, когда метки являются словами, я заметил, что первые три гиперплоскости coef_[0] , coef_[1] , coef_[2] относятся к синей метке. Это подтверждает мою теорию, так как синий лексикографически (в алфавитном порядке) будет упорядочен в качестве первой метки. Следующие две метки coef_[3] и coef_[4] относятся к зеленой метке, что имеет смысл, поскольку 'g' сортируется после 'b' перед 'r' и перед 'y'.

Это может быть немного сложно понять, но подумайте немного, и это, вероятно, будет иметь для вас смысл.

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

Спасибо, что нашли время прочитать эту статью! Я надеюсь, что это было полезно для вас. Если у вас есть какие-либо вопросы, комментарии или вы заметили ошибки в коде или тексте, я призываю вас связаться со мной.

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