Понимание потери шарнира в scikit Learn
Я видел эту диаграмму потери шарнира:
https://math.stackexchange.com/questions/782586/how-do-you-minimize-hinge-loss
А также здесь:
https://programmatically.com/understanding-hinge-loss-and-the-svm-cost-function/
Однако создание «того же» графика с помощью scikit-learn очень похоже, но кажется «противоположным». Код выглядит следующим образом:
from sklearn.metrics import hinge_loss
import matplotlib.pyplot as plt
import numpy as np
predicted = np.arange(-10, 11, 1)
y_true = [1] * len(predicted)
loss = [0] * len(predicted)
for i, (p, y) in enumerate(zip(predicted, y_true)):
loss[i] = hinge_loss(np.array([y]), np.array([p]))
plt.plot(predicted, loss)
plt.axvline(x = 0, color = 'm', linestyle='dashed')
plt.axvline(x = -1, color = 'r', linestyle='dashed')
plt.axvline(x = 1, color = 'g', linestyle='dashed')
И некоторые конкретные моменты на графике выше:
hinge_loss([1], [-5]) = 0.0,
hinge_loss([1], [-1]) = 0.0,
hinge_loss([1], [0]) = 1.0,
hinge_loss([1], [1]) = 2.0,
hinge_loss([1], [5]) = 6.0
predicted = np.arange(-10, 11, 1)
y_true = [-1] * len(predicted)
loss = [0] * len(predicted)
for i, (p, y) in enumerate(zip(predicted, y_true)):
loss[i] = hinge_loss(np.array([y]), np.array([p]))
plt.plot(predicted, loss)
plt.axvline(x = 0, color = 'm', linestyle='dashed')
plt.axvline(x = -1, color = 'r', linestyle='dashed')
plt.axvline(x = 1, color = 'g', linestyle='dashed')
потеря шарнира для y_true == -1
И некоторые конкретные моменты на графике выше:
hinge_loss([-1], [-5]) = 0.0,
hinge_loss([-1], [-1]) = 0.0,
hinge_loss([-1], [0]) = 1.0,
hinge_loss([-1], [1]) = 2.0,
hinge_loss([-1], [5]) = 6.0
Может кто-нибудь объяснить мне, почему scikit-learn кажется противоположным двум другим первым графикам?
Спасибо заранее
РЕДАКТИРОВАТЬ: Основываясь на ответе, я могу воспроизвести тот же результат, не переворачивая значения. Это основано на следующем: какhinge_loss([0], [-1])==0
иhinge_loss([-2], [-1])==0
. Исходя из этого, я могу назватьhinge_loss()
с массивом из двух значений без изменения рассчитанного убытка.
Следующий код не переворачивает значения:
predicted = np.arange(-10, 11, 1)
y_true = [1] * len(predicted)
loss = [0] * len(predicted)
for i, (p, y) in enumerate(zip(predicted, y_true)):
loss[i] = hinge_loss(np.array([y, 0]), np.array([p, -1])) * 2
plt.plot(predicted, loss)
plt.axvline(x = 0, color = 'm', linestyle='dashed')
plt.axvline(x = -1, color = 'r', linestyle='dashed')
plt.axvline(x = 1, color = 'g', linestyle='dashed')
predicted = np.arange(-10, 11, 1)
y_true = [-1] * len(predicted)
loss = [0] * len(predicted)
for i, (p, y) in enumerate(zip(predicted, y_true)):
loss[i] = hinge_loss(np.array([y,-2]), np.array([p,-1])) * 2
plt.plot(predicted, loss)
plt.axvline(x = 0, color = 'm', linestyle='dashed')
plt.axvline(x = -1, color = 'r', linestyle='dashed')
plt.axvline(x = 1, color = 'g', linestyle='dashed')
Теперь возникает вопрос, почему для каждого соответствующего случая эти «комбинации» значений работают хорошо.
1 ответ
Взглянув на код, лежащий в основеhinge_loss
реализации, в бинарном случае происходит следующее:
lbin = LabelBinarizer(neg_label=-1)
y_true = lbin.fit_transform(y_true)[:, 0]
try:
margin = y_true * pred_decision
except TypeError:
raise TypeError("pred_decision should be an array of floats.")
losses = 1 - margin
np.clip(losses, 0, None, out=losses)
np.average(losses, weights=sample_weight)
Благодаря тому факту, чтоLabelBinarizer.fit_transform()
поведение в случае одной метки по умолчанию возвращает массив отрицательных меток
from sklearn.preprocessing import LabelBinarizer
lbin = LabelBinarizer(neg_label=-1)
lbin.fit_transform([1, 1, 1, 1, 1, 1, 1]) # returns array([[-1],[-1],[-1][-1],[-1],[-1],[-1]])
это означает, что (уникальный) знак метки переворачивается, что объясняет полученный вами сюжет.
Несмотря на то, что пример с одной меткой довольно странный, по этому вопросу были некоторые дебаты, см. https://github.com/scikit-learn/scikit-learn/issues/6723 , например. Копаясь в проблемах github, кажется, что они еще не приняли окончательного решения о возможных исправлениях, которые необходимо применить.
Ответ на РЕДАКТИРОВАТЬ:
Имо, как ты обогащаешься иp
в цикле работает, потому что - таким образом - вы эффективно "убегаете" от случая с одной меткой (в частности, что действительно важно, так это то, как вы имеете дело сy
). Действительно,
lbin = LabelBinarizer(neg_label=-1)
lbin.fit_transform(y_true)[:, 0]
сy_true=np.array([y,0])=np.array([1,0])
(первый случай) или сy_true=np.array([y,-2])=np.array([-1,-2])
(второй случай) соответственно return иarray([ 1, -1])
. С другой стороны, другая возможность для вашего второго случая, которую вы упоминаете в комментарии, а именноy_true=np.array([y,-1])=np.array([-1,-1])
не дает уйти от случая с одной этикеткой (lbin.fit_transform(np.array([-1, -1]))[:, 0]
возвращаетсяarray([-1, -1])
; таким образом, вы снова попадаете в «ошибку», описанную выше).