Binary cross-entropy
Для бинарной классификации обычно используют другую функцию потерь — binary cross-entropy, или BCE:
L = −[ y · ln p + (1 − y) · ln(1 − p) ]
где:
y — правильная метка, 0 или 1;
p — вероятность класса 1, которую выдала модель.
Если правильный ответ равен единице, то y = 1:
L = −ln p
Если правильный ответ равен нулю, то y = 0:
L = −ln(1 − p)
В общей формуле одно из двух слагаемых всегда умножается на ноль. Поэтому для каждого объекта фактически работает только один из двух случаев.
Логарифм устроен так, что уверенная ошибка обходится модели очень дорого.
Например, если:
y = 1
p ≈ 0.0025
то модель почти уверена в неправильном классе, и значение BCE становится большим.
Удивительно простая производная
Главное удобство сочетания sigmoid + BCE становится видно при вычислении производной.
После всех преобразований получается:
∂L/∂z = p − y
Множитель
p · (1 − p)
из производной сигмоиды при выводе сокращается.
Поэтому вместо длинного выражения остаётся простая разность:
что предсказали − что было
p − y
Например, если:
y = 1
p = 0.2
то:
∂L/∂z = 0.2 − 1 = −0.8
Знак минус говорит, что для уменьшения потерь значение z нужно увеличить.
Если же:
y = 0
p = 0.8
то:
∂L/∂z = 0.8 − 0 = 0.8
Теперь градиент положительный, значит при шаге против градиента z будет уменьшаться.
От производной по z к производной по весу
Но модель обучает не само число z. Она меняет веса w₁, w₂, ... и свободный член b.
Вспомним линейную часть:
z = w₁ · x₁ + w₂ · x₂ + ... + b
Посмотрим на один вес w_j. Если остальные параметры не менять, то производная z по этому весу равна соответствующему признаку:
∂z/∂w_j = x_j
Теперь применяем цепное правило:
∂L/∂w_j
=
∂L/∂z
·
∂z/∂w_j
Подставляем уже известные выражения:
∂L/∂w_j = (p − y) · x_j
А для свободного члена:
∂z/∂b = 1
поэтому:
∂L/∂b = p − y
Сравним с линейной регрессией
В линейной регрессии с MSE вклад одного объекта в производную по весу был:
2 · промах · признак
Теперь, для логистической регрессии с sigmoid + BCE:
(p − y) · признак
Сама идея осталась той же:
- получить прогноз;
- сравнить его с правильным ответом;
- получить вклад этого объекта в градиент;
- обновить веса против градиента.
Один объект руками
Пусть у модели пока один признак:
x = 2
Правильная метка:
y = 1
Модель выдала:
p = 0.2
Сначала находим сигнал ошибки:
p − y = 0.2 − 1 = −0.8
Теперь вклад в производную по весу:
(p − y) · x
=
−0.8 · 2
=
−1.6
Если lr = 0.1, то обновление веса:
w_new
=
w_old − 0.1 · (−1.6)
w_new = w_old + 0.16
Вес увеличился. Это логично: правильный ответ был 1, а модель дала слишком маленькую вероятность. Нужно увеличить z, а значит в данном примере увеличить вес.
Что будет, если объектов много
Пока мы разобрали вклад одного объекта:
градиент по весу =
(p − y) · признак
Если объектов несколько, поступаем так же, как в линейной регрессии: считаем такой вклад для каждого объекта, складываем их и усредняем.
Например:
g1 = 0
для каждого объекта:
посчитать p
signal = p − y
g1 += signal · x1
g1 = g1 / число_объектов
После этого делаем обычный шаг:
w1 = w1 − lr · g1
Но здесь появляется новый вопрос.
Что делать, если обучающих объектов не восемь и не восемьдесят, а несколько миллионов? Неужели перед каждым обновлением весов обязательно проходить всю огромную базу?
Необязательно. Градиент можно оценивать по небольшой части данных.
Именно к этому мы перейдём в следующем разделе: batch gradient descent, SGD и mini-batch обучение.
Главное из этого раздела
линейная часть:
z = w₁x₁ + w₂x₂ + ... + b
вероятность:
p = sigmoid(z)
правильный ответ:
y = 0 или 1
binary cross-entropy:
L = −[y ln p + (1 − y) ln(1 − p)]
градиент по z:
∂L/∂z = p − y
градиент по весу:
∂L/∂w_j = (p − y) · x_j
градиент по свободному члену:
∂L/∂b = p − y
В выходном слое именно с величины p − y начинается обратное распространение ошибки. В более глубокой сети дальше цепное правило пересчитает этот сигнал для весов предыдущих слоёв.