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

Классификация

Классификацию лучше разибрать с вероятностной точки зрения. Мы разберем эту тему на примере бинарной классификации с классами $\{0, 1\}$.

Примеры задач?

Постановка задачи

Есть случайная величина $y$ - метка класса $\{0, 1\}$; вектор случайных величин - признаков $\textbf{x}$.

  • Как интерпретировать $y_i$?
  • Как по набору признаков $\textbf{x}_i$ восстановить $y_i$ с максимальной точностью?
  • Как измерить точность? Какую функцию ошибки выбрать?
  • Как применять регрессию к этой задаче?

Ответы на вопросы

Целевая величина и максимизация правдоподобия

$y_i = p(y_i|\textbf{x}_i)$ - вероятность того, что объект $\textbf{x}_i$ принадлежит к "положительному" классу (например, классу покупателей, купивших товар). Если человек действительно купил товар, $p(y_i|\textbf{x}_i) = 1$, иначе $0$.

Прогнозирование величины

Нужно прогнозировать вероятность (события). Значит, модель должна возвращать число в диапазоне $[0, 1]$ - речь идет о вероятности. Это первое ограничение, которое будет наложено на модель $f(\textbf{x}, \textbf{w})$

Правдободобие

Правдоподобие - функция, которая показывает вероятность какого-то набора исходов при заданных параметрах модели.

Простой пример правдоподобия: есть монета, броски монеты описываются случайной величиной $Y$. При этом неизвестно, "честная" ли монета, или она падает неравномерно. Броски независимы, исходы записываются в последовательность. Наблюдается последовательность бросков:

$HHHHHHHTHHTHH$

  • Как оценить, насколько правдоподобно, что монета обладает определенными характеристиками (т.е. каков параметр $\theta$, описывающий вероятность выпадения $H$)?
  • Каковы наиболее правдоподобные характеристики монеты?

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

$\textbf{y} = \{HHHHHHHTHHTHH\}$ - результаты эксперимента

$\mathcal{L}(\theta|\textbf{y}) = \prod_{i=1}^n p(Y=y_i|\theta)$ - вероятность данного набора исходов

Что нужно сделать? Найти максимум функции $\mathcal{L}(\theta|\textbf{y})$ ($= p(\textbf{y}|\theta)$).

Поиск максимума $\rightarrow$ поиск экстремума. У нас есть набор методов для того, чтобы решать подобные задачи - как минимум, аналитические решения и градиентный спуск.

Поиск максимума называется (принципом) максимизацией правдоподобия.

log-likelihood (логарифм правдоподобия)

$\log{\mathcal{L}(\theta|\textbf{y})} = \log \prod_{i=1}^n p(Y=y_i|\theta) = \sum\limits_{i=1}^n \log{p(Y=y_i|\theta)} $

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

Обобщение

Немного обобщим. Представим, что речь идет не о монете, а о более сложной модели, которая вкючает вектор параметров $\theta$ и вектор переменных $textbf{x}$. Допустим, есть всего два исхода: $\{0, 1\}$ Вероятность исхоов можно записать так:

$p(y_i=0| \textbf{x}_i, \theta)$

$p(y_i=1| \textbf{x}_i, \theta)$

Так как исходы взаимоисключающие, можно записать

$p(y_i=0| \textbf{x}_i, \theta) = 1 - p(y_i=1| \textbf{x}_i, \theta)$

Теперь давайте скажем, что функция $f(\textbf{x}_i, \theta) = p(y_i=1| \textbf{x}_i, \theta)$ - это модель, которая прогнозирует вероятность того, что будет "положительный" случайный исход.

Правдоподобие в таком случае может быть записано как

$\mathcal{L}(\theta | \textbf{y}) = \prod_{i=1, [y_i = 1]}^{n} f(\theta, \textbf{x}_i) \cdot \prod_{i=1, [y_i = 0]}^{n} (1 - f(\theta, \textbf{x}_i))$

здесь $[y_i = 0]$ - условный оператор: если $y_i = 0$ или $1$, то суммируем, иначе пропускаем элемент.

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

$- \log{\mathcal{L}(\theta | \textbf{y})} = -\sum_{i=1, [y_i = 1]}^{n} \log{f(\theta, \textbf{x}_i)} - \sum_{i=1, [y_i = 0]}^{n} \log{(1 - f(\theta, \textbf{x}_i))}$

Теперь мы должны не максимизировать, а минимизировать - из-за замены знака. Наличие одного минимума в общем виде не гарантируется, но может быть получено с определенными ограничениями на функцию $f(\theta, \textbf{x})$.

Немного упростим - избавимся от двух сумм с условиями, оставив одну сумму.

$ -\sum_{i=1, [y_i = 1]}^{n} \log{f(\theta, \textbf{x}_i)} - \sum_{i=1, [y_i = 0]}^{n} \log{(1 - f(\theta, \textbf{x}_i))} = -\sum_{i=1}^{n} y_i \log{f(\theta, \textbf{x}_i)} + (1 - y_i) \log{(1 - f(\theta, \textbf{x}_i))} $

Как воспринимать полученное выражение: $y_i$ - это конкретный исход, например, Орел/Решка. В таком случае, $x_i$ - это набор параметров броска монеты, а $\theta$ - набор параметров модели, которая определяет вероятность, с которой монета упадет на одну из граней.

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

Так как речь идет не о $\mathcal{L}$, а о $-log{\mathcal{L}}$, нужно минимизировать (из-за знака минус).

Фактически, максимизация правдоподобия была (уже практически) преобразована в задачу регрессии. Осталась пара деталей.

  • Во-первых, определить задачу в терминах функции ошибки.
  • Во-вторых, понять, почему это функция ошибки
  • В третьих, определить, как модель $f(\theta, \textbf{x}_i)$ будет возвращать только значения из $[0, 1]$.
  • В четвертых, определить модель или виды моделей, которые можно использовать

Функция ошибки

Если $y_i = f(\theta, \textbf{x}_i) = p(\hat{y}_i | \textbf{x}_i, \theta)$ - прогноз, $\hat{y}_i$ - "настоящее" значение (фактическое наблюдение), то следующая функция

$\mathcal{L}(\hat{y}_i, y_i) = -\sum_{i=1}^{n} \hat{y}_i \log{y_i} + (1 - \hat{y}_i) \log{(1 - y_i)}$

- функция ошибки, называемая бинарной кроссэнтропией.

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

$\mathbb{E}(Y|X) = f(X, \theta)$

- здесь $Y, X$ - заглавные, т.к. они обозначают случайные переменные, а не конкретные исходы.

Почему функция ошибки?

При внимательном рассмотрении формулы

$\mathcal{L}(\hat{y}_i, y_i) = -\sum_{i=1}^{n} \hat{y}_i \log{y_i} + (1 - \hat{y}_i) \log{(1 - y_i)}$

можно понять, что это аналог кросс-энтропии для дискретного распределения -

$H(p, q) = \sum\limits_x p(x) \log{q(x)}$, где $x$ - случайная величина, $p$ - "истинное" распределение, $q$ - заданное распределение, при помощи которого "кодируются" (или восстанавливаются) значения случайной величины из распределения $p$.

В случае модели логистической регрессии и других, более сложных моделей, $p(x)$ - это известные нам исходы, $\hat{y}_i$, $q(x)$ - прогнозы модели, $y_i = f(\textbf{x}_i, \theta)$.

Выражение $(1 - \hat{y}_i) \log{(1 - y_i)}$ - просто трюк для удобства расчета.

Таким образом, когда идет речь о минимизации бинарной кросс-энтропии, минимизируется разница между истинным распределением (зависимой переменной $\hat{y}$) и моделью. Если кросс-энтропия равна нулю - распределения идентичны

т.е. для всех исходов "Решка" модель прогнозирует решку с вероятностью 1, и то же самое для "Орлов".

Как модифицировать регрессионную модель

Функции для регрессии могут быть любой сложности, единственное ограничение - линейная зависимость от параметров $\theta$ (или $w$, как было на занятии по линейной регрессии).

При этом функция

$f(\textbf{x}, \theta) = \sum\limits_{k} \theta_k f_k(x)$

может возвращать значения из можнества $\mathbb{R}$: числа из $(-\inf, \inf)$. Что нужно сделать?

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

отображение со свойством

$\sigma: \mathbb{R} \rightarrow [0, 1]$

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

$\sigma(x) = \dfrac{L}{1 + e^{-k(x - x_0)}}$

  • $L$ - "высота" сигмоиды, $1$ в нашем случае
  • $k$ - крутизна кривой, в логистической регрессии используют $1$
  • $x_0$ - положение середины кривой, $0$ в нашем случае

Одно из интересных применений - рост биомассы (популяции, ... - ограниченный экспоненциальный рост).

Модель для логистической регрессии

Итак, линейная модель $f(\textbf{x}, \theta)$ встраивается внутрь сигмоиды:

$g(\textbf{x}, \theta) = \dfrac{1}{1 + e^{-f(\textbf{x}, \theta)}}$

Если подставить $y_i = g(\textbf{x}_i, \theta)$ в бинарную кроссэнтропию, получится

$\mathcal{L}(\hat{y}_i, y_i) = -\sum_{i=1}^{n} \hat{y}_i \log{\dfrac{1}{1 + e^{-f(\textbf{x}, \theta)}}} + (1 - \hat{y}_i) \log{\bigg(1 - \dfrac{1}{1 + e^{-f(\textbf{x}, \theta)}}\bigg)}$

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

Градиент бинарной кроссэнтропии

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

Чтобы рассчитать градиент, воспользуемся "цепным правилом" (chain rule):

$\dfrac{\delta\mathcal{L}}{\delta\theta} = \dfrac{\delta\mathcal{L}}{\delta\sigma}\dfrac{\delta\sigma}{\delta g}\dfrac{\delta g}{\delta\theta} $

Производные по очереди

$\dfrac{\delta\mathcal{L}}{\delta\sigma} = -\dfrac{y \log{\sigma} + (1 - y) \log{(1 - \sigma)}}{\delta\sigma} = \dfrac{y - \sigma}{(\sigma - 1)\sigma} $

$\dfrac{\delta\sigma}{\delta g} = \dfrac{\delta \bigg(\dfrac{1}{1 + e^{g}}\bigg)}{\delta g} = {\sigma}{(1 - \sigma)}$

Как это вычисляется

Графие производной сигмоиды (обратите внимание на левый и правый "края")

$\dfrac{\delta g}{\delta\theta} = \begin{bmatrix} \theta_{1} \\ \theta_{2} \\ \vdots \\ \theta_{m} \end{bmatrix} $

Сводя все вместе, получим после сокращений

$ \nabla_{\theta}\mathcal{L}(\textbf{y}, \sigma(\textbf{x}, \theta)) =\ ...$ упрощаем, убирая градиент и суммы

$= \dfrac{\delta\mathcal{L}}{\delta\sigma}\dfrac{\delta\sigma}{\delta g}\dfrac{\delta g}{\delta\theta}=\ ...$ возвращаем суммы и векторную форму
$= \dfrac{1}{n}\sum\limits_{i=1}^{n}(\textbf{y} - \sigma(\textbf{x}, \theta))\begin{bmatrix} \theta_{1} \\ \theta_{2} \\ \vdots \\ \theta_{m} \end{bmatrix} $

  • очень простое выражение
  • похоже на линейную регрессию
  • если модель $g(\textbf{x}, \theta)$ более сложна, чем линейная, изменится только вид вектора-столбца

logit

Иногда в литературе встречается выражение "логит". Что это такое?

$logit(p) = \log{\dfrac{p}{1-p}}$

Логит - это логарифм от шансов (формата "наши шансы выжить 1 к 129824", или odds по-английски). Можно сказать, что логистическая регрессия работает с логитами.

Дело в том, что $logit^{-1} = sigmoid$

Визуализация логистической регрессии

Переобучение, кросс-валидация, регуляризация

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

Метрики

Нейронные сети

Придумали Уоррен Мак-Каллок и Уолтер Питтс, в 40-х годах. Кажется, первая статься вышла в 1943.

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

Перцептрон

Простейшая нейросеть.

Что важного на иллюстрации?

  • Нейроны
  • Слои
  • Функция активации (не отображена)

Математика нейросети

Каждой связи в перцептроне (и в более сложных нейросетсях) присваивается вес.

Номер веса $w_{jk}^{l}$ - это вес нейрона от $l-1$-го слоя к $l$-му слою, $k$ - номер нейрона в $l-1$-м слое, $j$-номер нейрона в $l$-м слое.

Можно сказать, что между двумя слоями формируется матрица весов размера $j \times k$: $W^l \in R^{k \times j}$

Допустим, "на вход" нейронам $l-1$-го слоя подается вектор-строка размерности $j$: $\textbf{x} \in R^j$.

Как записать "выход" $l-1$-го слоя до применения функции активации?

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

$z^l = W^l \cdot \textbf{x}$

Применение функции активации

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

$x^2 = W^2 \cdot \textbf{x}^1$
$x^3 = W^3 \cdot \textbf{x}^2 = W^3 \cdot W^2 \cdot \textbf{x}^1 = W \cdot \textbf{x}^1$

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

Но если добавить после каждого слоя функцию активации, например, $\sigma$? Они привнесут нелинейность, и уже нельзя будет просто перемножить матрицы весов. В то же время, возможности нейросети расширятся очень значительно.

Итак, после каждого слоя добавляется функция активации:

$a^l = \sigma(W^l \cdot \textbf{a}^{l-1})$

Единственное отличие - для весов между перым и вторым слоями на вход подаются не активации предыдущего слоя, а сами данные.

$a^2 = \sigma(W^2 \cdot \textbf{x})$

Часто к входным данным применяют свои функции активации/нормализации, поэтому запись

$a^2 = \sigma(W^2 \cdot \textbf{a}^1)$

можно считать корректной.

Кроме сигмоиды, применяют и другие функции активации:

ReLU, Leaky ReLU, Softplus, tanh и другие.

Bias

Важная часть нейросети, которая позволяет нейросети работать со "сдвинутыми" данными, вектор размерности, равной количеству нейронов в слое $l$.

Выполняет ту же роль, что и $b$ в линейной регрессии:

$f(x, w) = b + wx$

$a^l = \sigma(W^l \cdot \textbf{a}^{l-1} + b^l)$

Backpropagation

Как применить градиентный спуск к нейросети?

Вычислить градиенты для всех весов. Но как это сделать?

Как модифицировать веса на промежуточных слоях во время градиентного спуска?

Алгоритм:

Backpropagation($X$, $y$):
-- $X$ - матрица с примерами, $n \times k$
-- $y$ - вектор целевых переменных, $n$ (или $n \times j$)
Пока не будет достигнуто условие остановки:

  • Шаг вперед: для всех слоев $l: 2, 3, ...L$ вычислить $a^l = \sigma(W^l \cdot \textbf{a}^{l-1} + b^l)$
  • Ошибка выходного слоя: вычислить $\delta^L = \nabla_a \mathcal{L} \odot \dfrac{\sigma(z^L)}{z^L}$
  • Ошибка промежуточного слоя:
    • вычислить $\delta^L = ((W^{l+1})^T \delta^{l+1} ) \odot \dfrac{\sigma(z^l)}{z^l}$
    • расчитать градиенты для весов и сдвига: $\dfrac{\delta \mathcal{L}}{\delta w_{jk}^l} = a_k^{l-1} \delta_j^l $, $\dfrac{\delta \mathcal{L}}{\delta b_{j}^l} = \delta_j^l$
    • вычесть полученные значения градиента из всех весов и сдвигов с учетом learning rate'a.

Вывод уравнений

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

Возможные проблемы и их решения

Застревание в локальном минимуме, выход на плато

  • От застреваний: стохастический градиентный спуск (SGD), мини-батч SGD, управление learning rate'ом, Snapshot Ensembling.
    • Батч - все данные
    • SGD - обучение по одному примеру
    • Мини-батч - обучение наборами примеров по $m$ штук
    • Эпоха - когда батч или мини-батч SGD обходит все значения

  • От проблемы плато и медленного обучения: подбор функции активации, например, использование ReLU, правильная инициация весов, нормализация входных данных, нормализация батча (Batch Normalization).

Регуляризация

  • $l1$, $l2$, их сочетания
  • нормализация батча
  • дропаут (dropout)

Седельные точки, зигзагообразный спуск

Адаптивные медоты градиентного спуска: Momentum, AdaM, NAdaM, AdaGrad

Momentum:

$\nu_t = \gamma \nu_{t-1} - \eta \nabla_w \mathcal{L(w)}$
$w = w - \nu_t$

Обзорная статья про многое из перечисленного выше

</font>

Демо

In [5]:
import keras 
import matplotlib.pyplot as plt
import numpy as np
In [6]:
from keras.datasets import mnist
In [29]:
(x_tr, y_tr), (x_te, y_te) = mnist.load_data()
In [9]:
y_tr
Out[9]:
array([5, 0, 4, ..., 5, 6, 8], dtype=uint8)
In [10]:
plt.matshow(x_tr[0])
Out[10]:
<matplotlib.image.AxesImage at 0x7f2b133d6128>
In [11]:
def to_bin(x):
    return np.array([1 if i == 1 else 0 for i in x])
In [30]:
y_tr, y_te = to_bin(y_tr), to_bin(y_te)
In [31]:
np.mean(y_tr)
Out[31]:
0.11236666666666667
In [32]:
x_tr = x_tr / 255.
x_te = x_te / 255.
In [33]:
x_tr = x_tr.reshape((len(x_tr), 28*28))
x_te = x_te.reshape((len(x_te), 28*28))
In [34]:
x_tr.shape
Out[34]:
(60000, 784)
In [36]:
x_te.shape
Out[36]:
(10000, 784)
In [44]:
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, f1_score
In [38]:
logreg = LogisticRegression()
In [39]:
logreg.fit(x_tr, y_tr)
/usr/local/lib/python3.6/dist-packages/sklearn/linear_model/logistic.py:433: FutureWarning: Default solver will be changed to 'lbfgs' in 0.22. Specify a solver to silence this warning.
  FutureWarning)
Out[39]:
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
          intercept_scaling=1, max_iter=100, multi_class='warn',
          n_jobs=None, penalty='l2', random_state=None, solver='warn',
          tol=0.0001, verbose=0, warm_start=False)
In [42]:
y_te_pr = logreg.predict_proba(x_te)[:,1]
In [49]:
y_te_pr = logreg.predict(x_te)
In [43]:
y_te_pr
Out[43]:
array([2.16962640e-12, 1.82696290e-07, 9.81656641e-01, ...,
       1.18875169e-09, 2.27493047e-05, 1.84733134e-14])
In [47]:
y_te
Out[47]:
array([0, 0, 1, ..., 0, 0, 0])
In [50]:
accuracy_score(y_te, y_te_pr)
Out[50]:
0.9937
In [52]:
f1_score(y_te, y_te_pr, average='macro')
Out[52]:
0.984400596253781
In [56]:
accuracy_score(y_te, [0] * len(y_te))
Out[56]:
0.8865
In [53]:
f1_score(y_te, [0] * len(y_te), average='macro')
/usr/local/lib/python3.6/dist-packages/sklearn/metrics/classification.py:1143: UndefinedMetricWarning: F-score is ill-defined and being set to 0.0 in labels with no predicted samples.
  'precision', 'predicted', average, warn_for)
Out[53]:
0.46991783726477604
In [54]:
f1_score(y_te, [1] * len(y_te), average='macro')
/usr/local/lib/python3.6/dist-packages/sklearn/metrics/classification.py:1143: UndefinedMetricWarning: F-score is ill-defined and being set to 0.0 in labels with no predicted samples.
  'precision', 'predicted', average, warn_for)
Out[54]:
0.101930848675348
In [58]:
from keras.layers import Dense, Input
from keras.models import Sequential
In [59]:
model = Sequential()
In [61]:
model.add(Dense(784, activation='sigmoid', input_shape=(784, )))
model.add(Dense(1, activation='sigmoid'))
In [62]:
model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_2 (Dense)              (None, 784)               615440    
_________________________________________________________________
dense_3 (Dense)              (None, 1)                 785       
=================================================================
Total params: 616,225
Trainable params: 616,225
Non-trainable params: 0
_________________________________________________________________
In [63]:
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
In [64]:
model.fit(x_tr, y_tr, validation_data=(x_te, y_te),
          epochs=5,
          batch_size=64
         )
Train on 60000 samples, validate on 10000 samples
Epoch 1/5
60000/60000 [==============================] - 7s 123us/step - loss: 0.0399 - acc: 0.9883 - val_loss: 0.0212 - val_acc: 0.9943
Epoch 2/5
60000/60000 [==============================] - 7s 112us/step - loss: 0.0240 - acc: 0.9927 - val_loss: 0.0146 - val_acc: 0.9960
Epoch 3/5
60000/60000 [==============================] - 11s 179us/step - loss: 0.0164 - acc: 0.9950 - val_loss: 0.0127 - val_acc: 0.9963
Epoch 4/5
60000/60000 [==============================] - 10s 175us/step - loss: 0.0126 - acc: 0.9958 - val_loss: 0.0112 - val_acc: 0.9969
Epoch 5/5
60000/60000 [==============================] - 11s 176us/step - loss: 0.0102 - acc: 0.9965 - val_loss: 0.0103 - val_acc: 0.9970
Out[64]:
<keras.callbacks.History at 0x7f2b07e7d9b0>
In [65]:
y_te_pr = model.predict(x_te)
In [67]:
y_te_pr = [1 if i > 0.5 else 0 for i in y_te_pr]
In [69]:
accuracy_score(y_te, y_te_pr)
Out[69]:
0.997
In [68]:
f1_score(y_te, y_te_pr)
Out[68]:
0.9867724867724867
In [ ]:
0.9844005962
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]: