Превью статьи
Искусственный интеллект
Машинное обучение
Нейронные сети
14 июля
15 минут

Логистическая регрессия: главное – не перепутать

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

Вячеслав Гораш avatar

Вячеслав Гораш

Разработчик в области 3D графики и машинного обучения с 6-ти летним опытом

Это статья из цикла про основы машинного обучения.

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

Что не так с деревьями?

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

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

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

И, в-третьих, дерево имеет фундаментальные ограничения по типу зависимостей. Оно очень хорошо работает с отдельными «островками» данных, но крайне плохо обрабатывает непрерывные зависимости. Это связано с тем, что дерево разбивает данные вдоль осей координат.

Проблемы деревьев 1

Проблемы деревьев 2

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

Как быть?

На самом деле есть несколько способов справиться с недостатками решающих деревьев. Первая группа способов подразумевает их улучшение. Один из вариантов – использовать не одно дерево, а несколько. Причем в момент обучения каждое дерево учится на своей части данных. В итоге получается множество («лес») разных деревьев. И, когда надо дать ответ, мы показываем данные каждому дереву по отдельности, а потом устраиваем голосование. Победивший результат мы и выдаем на выход. Такой подход (он называется «случайный лес» или «random forest») существенно снижает влияние переобучения. Этот метод используется в некоторых случаях даже сейчас, в эпоху доминирования нейросетей.

Однако за все приходится платить. Здесь, по большей части, мы платим потерей интуитивности. Действительно, если решения одного дерева достаточно понятны (можно посмотреть его структуру и понять, почему оно дало тот или иной ответ), то понять логику голосования множества деревьев в разы сложнее. Плюс никуда не деваются ограничения распределений и зависимостей между данными.

Второй способ решения проблемы – уйти от решающих деревьев и подобных им алгоритмических моделей.

Что там с регрессией?

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

y= ∑i=0nkixi+by = \ \sum_{i = 0}^{n}{{k_{i}x}_{i} + b}

Давайте попробуем использовать такой же подход для классификации. Что нам мешает это сделать? В первую очередь – диапазон выходных значений. Это регрессия, следовательно, выход модели – это любое число. Мы же решаем задачу классификации и ожидаем только два значения: 0 или 1.

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

На самом деле лучший способ использовать регрессию для классификации – это каким-то образом привести значения из диапазона [−∞, +∞]\lbrack - \infty,\ + \infty\rbrack в диапазон [0, 1]\lbrack 0,\ 1\rbrack. Почему это сработает, ведь нам нужны всего два значения, а не диапазон? Дело в том, что непрерывное значение из диапазона [0, 1]\lbrack 0,\ 1\rbrack можно рассматривать как вероятность одного из ответов. Например, если мы получили значение 0.60.6, это можно интерпретировать как вероятность ответа 1 в 60%.

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

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

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

Разбираемся с сигмоидой

Сама функция выглядит очень просто:

σ(x)= 11+e−x\sigma(x) = \ \frac{1}{1 + e^{- x}}

Если значение xx стремится к минус бесконечности, знаменатель дроби, наоборот, устремляется в бесконечность, что дает значение всей функции очень близким к нулю. При очень больших значениях xx значение e−xe^{- x} устремляется к нулю, знаменатель близок к единице, как и итоговое значение функции. В итоге ее график выглядит так:

Сигмоида график

Видно, что функция мягко и без разрывов преобразует любое входное значение в диапазон от нуля до единицы.

Нам теперь осталось только применить эту функцию к результату регрессии и получить вероятность положительного ответа:

y^=σ(∑i=0nkixi+b)\widehat{y} = \sigma\left( \sum_{i = 0}^{n}{k_{i}x_{i}} + b \right)

Как и в обычной линейной регрессии нам теперь нужно вычислить все коэффициенты kik_{i} и смещение bb.

Обучаем

Любое обучение – это минимизация функции потерь между предсказанным результатом и реальным:

L(y, y^)→0L(y,\ \widehat{y}) \rightarrow 0

Точнее, мы минимизируем функцию не для одной пары значений, а сразу сумму всех пар:

∑i=1mL(y, y^)→0\sum_{i = 1}^{m}{L\left( y,\ \widehat{y} \right) \rightarrow 0}

Для линейной регрессии, где результаты были непрерывные, функция потерь была просто квадратом разности:

l=(y−y^)2{l = \left( y - \widehat{y} \right)}^{2}

Здесь же у нас результат дискретный (только 0 или 1). Следовательно, нужно использовать другую функцию.

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

Для задачи классификации используется другая функция, а именно – бинарная кросс-энтропия. Эта функция выглядит более сложно, чем простая разность квадратов:

l=−(y∙log⁡(y^)+(1−y)∙log⁡(1−y^))l = - \left( y \bullet\log\left( \widehat{y} \right) + (1 - y)\bullet\log{(1 - \widehat{y})} \right)

Несмотря на кажущуюся сложность, кросс-энтропия гораздо лучше работает для задачи классификации. В итоге нам нужно сделать сумму функций для каждой пары значений как можно меньше. Для этого нужно подобрать коэффициенты kik_{i} и bb.

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

Давайте максимально упростим модель. У нас, как и в статье про регрессию останется только одна входная переменная xx и два коэффициента: kk и bb. Ответ мы будем считать по формуле:

y^= σ(kx+b)\widehat{y} = \ \sigma(kx + b)

или

y^= 11+e−(kx+b)\widehat{y} = \ \frac{1}{1 + e^{- (kx + b)}}

Теперь распишем функцию потерь:

l=−(y∙log⁡(y^)+(1−y)∙log⁡(1−y^) )l = - \left( y\bullet\log\left( \widehat{y} \right) + (1 - y)\bullet\log{(1 - \widehat{y})\ } \right)l=−(y∙log⁡(11+e−(kx+b))+(1−y)∙log⁡(1− 11+e−(kx+b)))l = - \left( y\bullet\log\left( \frac{1}{1 + e^{- (kx + b)}} \right) + \left(1 - y\right) \bullet\log\left( 1 - \ \frac{1}{1 + e^{- (kx + b)}} \right) \right)

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

−∑i=1myi∙log⁡(11+e−(kxi+b))+(1−yi)∙log⁡(1− 11+e−(kxi+b)) →0- \sum_{i = 1}^{m}{y_{i} \bullet \log\left( \frac{1}{1 + e^{- \left( kx_{i} + b \right)}} \right) + \left( 1 - y_{i} \right) \bullet\log\left( 1 - \ \frac{1}{1 + e^{- \left( kx_{i} + b \right)}} \right)\ \rightarrow 0}

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

Путешествие по поверхностям

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

Визуализация функции потерь

Обратите внимание – минимум здесь – не точка, а линия. Это значит, что есть много пар kk и bb, которые нас устраивают. Нужно найти любую из них.

Нам нужно спуститься со стены желоба (случайное место, где мы начинаем обучение) на его дно. По сути, нам нужно следовать следующему алгоритму:

  1. Находим направление движения в сторону минимума
  2. Делаем небольшой шаг в сторону минимума (меняем kk и bb на некоторую величину)
  3. Повторяем шаги 1 и 2 пока не дойдем до минимума

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

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

k=k−ρ∂L∂k,  b=b−ρ∂L∂bk = k - \rho\frac{\partial L}{\partial k},\ \ b = b - \rho\frac{\partial L}{\partial b}

Параметр ρ\rho в этой записи – это просто небольшое число (например, 0.01), которое мы добавляем, чтобы сделать шаг небольшим. Так как это число управляет скоростью шагов, а, следовательно – и скоростью обучения, его называют learning rate. Про то, как такое число подбирать и что от него зависит, кроме скорости обучения, расскажу позже.

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

Осталось разобраться, а как понять, что мы уже пришли в минимум. Тут можно вспомнить, что в минимуме производные по всем параметрам станут равными нулю и мы остановимся. На самом деле такой подход обычно не работает. Дело в том, что чаще всего градиентный спуск перескакивает точку минимума, потом возвращается назад, снова перескакивает минимум, теперь уже в обратном направлении и так далее. Поэтому обычно на изменение производных смотрят менее строго. Чаще всего смотрят даже не на производные, а на саму функцию потерь LL. Если после очередного шага она почти не изменилась (ΔL< ε\mathrm{\Delta}L < \ \varepsilon), мы считаем, что находимся очень близко к минимуму и обучение можно заканчивать. Опять же, есть множество нюансов, но про них будет отдельная статья.

Считаем производные

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

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

Процесс умножения входных данных на веса и прибавления смещения называют сумматорной функций. Обозначим ее буквой zz:

z(x)=kx+bz(x) = kx + b

Далее к этому результату мы применяем сигмоиду. Этот процесс называют активационной функцией. Обозначим ее буквой aa:

a(z)=11+e−za(z) = \frac{1}{1 + e^{- z}}

И в конце мы вычисляем функцию потерь между нашим предсказанием и правильным ответом. Саму функцию обозначим как LL. В нашем случае:

L(a, y)=−(y∙log⁡(a)+(1−y)∙log⁡(1−a))L(a,\ y) = - \left( y\bullet\log(a) + (1 - y)\bullet\log{(1 - a)} \right)

Нам нужно посчитать две производные: ∂L∂k\frac{\partial L}{\partial k} и ∂L∂b\frac{\partial L}{\partial b}. И тут мы, как и в статье про линейную регрессию, можем воспользоваться цепным правилом:

∂L∂k= ∂L∂a∙∂a∂z∙∂z∂k\frac{\partial L}{\partial k} = \ \frac{\partial L}{\partial a} \bullet \frac{\partial a}{\partial z} \bullet \frac{\partial z}{\partial k}

Аналогично и для bb. Согласитесь, что считать производные по частям гораздо удобнее, чем целиком.

Начнем считать с последней функции:

∂L∂a= −(y∙1a+((1−y)∙11−a∙−1))=−(ya−1−y1−a)=1−y1−a−ya\frac{\partial L}{\partial a} = \ - \left( y \bullet \frac{1}{a} + \left( (1 - y) \bullet \frac{1}{1 - a} \bullet - 1 \right) \right) = - \left( \frac{y}{a} - \frac{1 - y}{1 - a} \right) = \frac{1 - y}{1 - a} - \frac{y}{a}

Дополнительно приведем к общему знаменателю и упростим:

1−y1−a−ya= a−ay−y+aya(1−a)=a−ya(1−a)\frac{1 - y}{1 - a} - \frac{y}{a} = \ \frac{a - ay - y + ay}{a(1 - a)} = \frac{a - y}{a(1 - a)}

Теперь считаем производную сигмоидальной активации:

∂a∂z= ∂∂z 11+e−z=−1(1+e−z)2 ∙−e−z=e−z(1+e−z)2\frac{\partial a}{\partial z} = \ \frac{\partial}{\partial z}\ \frac{1}{1 + e^{- z}} = - \frac{1}{\left( 1 + e^{- z} \right)^{2}}\ \bullet - e^{- z} = \frac{e^{- z}}{\left( 1 + e^{- z} \right)^{2}}

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

e−z(1+e−z)2= 11+e−z∙e−z1+e−z\frac{e^{- z}}{\left( 1 + e^{- z} \right)^{2}} = \ \frac{1}{1 + e^{- z}} \bullet \frac{e^{- z}}{1 + e^{- z}}

Что такое первая дробь? Это же наша исходная функция! Поэтому можем сделать такое преобразование:

∂a∂z=a∙e−z1+e−z\frac{\partial a}{\partial z} = a \bullet \frac{e^{- z}}{1 + e^{- z}}

Но и это еще не все. Преобразуем вторую часть:

e−z1+e−z= ez∙e−zez∙(1+e−z)= 11+ez\frac{e^{- z}}{1 + e^{- z}} = \ \frac{e^{z} \bullet e^{- z}}{e^{z} \bullet (1 + e^{- z})} = \ \frac{1}{{1 + e}^{z}}

Остался еще один трюк. К числителю прибавим и сразу же отнимем eze^{z}:

11+ez=1+ez−ez1+ez= 1+ez1+ez− ez1+ez=1−ez1+ez\frac{1}{{1 + e}^{z}} = \frac{1 + e^{z} - e^{z}}{{1 + e}^{z}} = \ \frac{1 + e^{z}}{1 + e^{z}} - \ \frac{e^{z}}{1 + e^{z}} = 1 - \frac{e^{z}}{1 + e^{z}}

Теперь последнее преобразование. Делим дробь на eze^{z}. Это равносильно умножению числителя и знаменателя на e−ze^{- z}:

1−ez1+ez=1−ez∙e−z(1+ez)∙e−z=1− 1e−z+1=1− 11+e−z1 - \frac{e^{z}}{1 + e^{z}} = 1 - \frac{e^{z} \bullet e^{- z}}{\left( 1 + e^{z} \right) \bullet e^{- z}} = 1 - \ \frac{1}{e^{- z} + 1} = 1 - \ \frac{1}{1 + e^{- z}}

Что мы видим? Снова нашу исходную функцию! В итоге производная сигмоиды полностью выражается через саму сигмоиду:

∂a∂z=a∙e−z1+e−z=a∙(1− 11+e−z)=a(1−a)\frac{\partial a}{\partial z} = a \bullet \frac{e^{- z}}{1 + e^{- z}} = a \bullet \left( 1 - \ \frac{1}{1 + e^{- z}} \right) = a \left(1 - a\right)

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

∂L∂a∙∂a∂z= a−ya(1−a)∙a(1−a)=a−y\frac{\partial L}{\partial a} \bullet \frac{\partial a}{\partial z} = \ \frac{a - y}{a(1 - a)} \bullet a(1 - a) = a - y

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

∂z∂k= ∂∂kkx+b=x,  ∂z∂b= ∂∂bkx+b=1\frac{\partial z}{\partial k} = \ \frac{\partial}{\partial k}kx + b = x,\ \ \frac{\partial z}{\partial b} = \ \frac{\partial}{\partial b}kx + b = 1

Все готово. Итоговые формулы для производных:

∂L∂k=x(a−y),  ∂L∂b=a−y\frac{\partial L}{\partial k} = x(a - y),\ \ \frac{\partial L}{\partial b} = a - y

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

∂L∂ki=xi(a−y)\frac{\partial L}{\partial k_{i}} = x_{i} \left(a - y \right)

Долгожданный спуск

Теперь у нас есть все, чтобы вывести алгоритм обучения. Изначально коэффициенты kk и bb мы инициализируем случайными числами. Обычно числа берут достаточно маленькими, в пределах от нуля до одной сотой. Дальше начинается сам процесс обучения:

  1. Для каждой пары входных данных и правильного ответа считаем производные по всем параметрам kk и bb
  2. Когда посчитано для всех примеров, усредняем каждую производную, то есть суммируем производные для каждого примера и делим на количество примеров
  3. Делаем шаг градиентного спуска: ki=k− ρ∙1n∙∑xij(aj−yj)k_{i} = k - \ \rho \bullet \frac{1}{n} \bullet \sum_{}^{}{x_{ij}\left( a_{j} - y_{j} \right)}. Аналогично для b:b=b−ρ∙1n∙∑(aj−yj)b:b = b - \rho \bullet \frac{1}{n} \bullet \sum_{}^{}\left( a_{j} - y_{j} \right)
  4. Повторяем пункты 1-3 до тех пор, пока не дойдем до минимума (точнее, не окажемся рядом с ним)

После окончания обучения наша модель готова к использованию. Для этого мы сначала вычисляем взвешенную сумму:

z=∑i=1nkixi+bz = \sum_{i = 1}^{n}{k_{i}x_{i} + b}

А затем применяем сигмоиду:

y^=11+e−z\widehat{y} = \frac{1}{1 + e^{- z}}

Итоговое значение y^\widehat{y} – это вероятность того, что класс объекта равен 1. Эту вероятность, как уже говорилось выше, мы можем интерпретировать по-разному в зависимости от задачи.

И снова проблемы

Несмотря на то, что логистическая регрессия кажется очень хорошим вариантов, она тоже не лишена проблем. С одной стороны, да, она лучше решающих деревьев в плане переобучения. Но с другой, у нее проблема ровно противоположная. Напомню, что решающие деревья очень хорошо работали с отдельными «островками» данных, но плохо обрабатывали непрерывные распределения. Здесь ситуация обратная.

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

Логистическая регрессия 1

Логистическая регрессия 2

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

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

Логистическая регрессия 3

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

Продолжение следует…

Вопросы и ответы

Обсудить проект

Опишите вашу задачу, мы проведём исследование и ответим вам как можно скорее.

С радостью проконсультируем вас любым из доступных способов.

Оставляя заявку, вы соглашаетесь с политикой обработки данных