Логистическая регрессия для машинного обучения

машинное обучение

Логистическая регрессия — это метод машинного обучения для задач бинарной классификации (0 или 1), который можно использовать для оценки вероятности чего-либо. Например, возможность того, что пользователь купит определенный продукт, возможность пациента, страдающего определенным заболеванием, и возможность того, что пользователь нажмет на рекламу.

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

  1. Выберите функцию предсказания.
  2. Вычислите функцию потерь.
  3. Вычислите минимум функции потерь, используя градиентный спуск.
  4. Векторизировать.
  5. Реализуйте алгоритм логистической регрессии на основе градиентного спуска.
  6. Полиномиальные признаки.
  7. Мультикатегория.
  8. Регуляризация.

функция предсказания

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

g(\mathrm{z})=\frac{1}{1+e^{-z}}

Вы можете использовать код Python, чтобы нарисовать его:

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(t):
    return 1. / (1. + np.exp(-t))
  
x = np.linspace(-10, 10, 500)

plt.plot(x, sigmoid(x))
plt.show()

Для случая линейной границы его выражение:

\theta_{0}+\theta_{1} x_{1}+\ldots+\theta_{n} x_{n}=\sum_{i=0}^{n} \theta_{i} x_{i}=\theta^{T} x

Определите функцию предсказания как:

h_{\theta}(x)=g\left(\theta^{T} x\right)=\frac{1}{1+e^{-\theta^{T} x}}

h_{\theta}(x)Выражается как вероятность исхода, принимающего 1, поэтому:

\begin{array}{l}{P(y=1 | x ; \theta)=h_{\theta}(x)} \\ {P(y=0 | x ; \theta)=1-h_{\theta}(x)}\end{array}

Построить функцию потерь

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

\operatorname{cost}\left(h_{\theta}(x), y\right)=\left\{\begin{array}{cc}{-\log \left(h_{\theta}(x)\right)} & {\text { if } y=1} \\ {-\log \left(1-h_{\theta}(x)\right)} & {\text { if } y=0}\end{array}\right.

Выполните преобразование:

\begin{aligned} J(\theta) &=\frac{1}{m} \sum_{i=1}^{m} \operatorname{cost}\left(h_{\theta}\left(x^{(i)}\right), y^{(i)}\right) \\ &=-\frac{1}{m}\left[\sum_{i=1}^{m} y^{(i)} \log h_{\theta}\left(x^{(i)}\right)+\left(1-y^{(i)}\right) \log \left(1-h_{\theta}\left(x^{(i)}\right)\right)\right] \end{aligned}

Градиентный спуск для решения функции потерь

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

\theta_{j} :=\theta_{j}-\alpha \frac{\partial}{\partial \theta_{j}} J(\theta), \quad(j=0 \ldots n)

Ниже приводится результат после частной производной:

\frac{\partial}{\partial \theta_{j}} J(\theta)=\frac{1}{m} \sum_{i=1}^{m}\left(h_{\theta}\left(\mathrm{x}^{(\mathrm{i})}\right)-y^{(i)}\right) x_{j}^{(\mathrm{i})}

Конкретный процесс вывода выглядит следующим образом:

так:

\theta_{j} :=\theta_{j}-\alpha \frac{1}{m} \sum_{i=1}^{m}\left(h_{\theta}\left(\mathrm{x}^{(\mathrm{i})}\right)-y^{(i)}\right) x_{j}^{(\mathrm{i})}, \quad(j=0 \ldots n)

Поскольку 1/m — константа,\alphaтакже является константой, поэтому окончательное выражение:

\theta_{j}=\theta_{j}-\alpha \sum_{i=1}^{m}\left(h_{\theta}\left(\mathrm{x}^{(6)}\right)-y^{(i)}\right) x_{j}^{(6)}, \quad(j=0 \ldots n)

векторизация

Окончательный результат после векторизации:

\theta :=\theta-\alpha \cdot\left(\frac{1}{m}\right) \cdot x^{T} \cdot(g(x \cdot \theta)-y)

Конкретное происхождение -todo

Python реализует логистическую регрессию

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

def __init__(self):
    self.coef_ = None
    self.intercept_ = None
    self._theta = None

def _sigmoid(self, t):
    return 1. / (1. + np.exp(-t))

def fit(self, X_train, y_train, eta=0.01, n_iters=1e4):
    X_b = np.hstack([np.ones((len(X_train), 1)), X_train])
    initial_theta = np.zeros(X_b.shape[1])
    self._theta = gradient_descent(X_b, y_train, initial_theta, eta, n_iters)

    self.intercept_ = self._theta[0]
    self.coef_ = self._theta[1:]
    return self

Код реализации градиентного спуска выглядит следующим образом:

def J(theta, X_b, y):
    y_hat = self._sigmoid(X_b.dot(theta))
    try:
        return - np.sum(y*np.log(y_hat) + (1-y)*np.log(1-y_hat)) / len(y)
    except:
        return float('inf')

def dJ(theta, X_b, y):
    return X_b.T.dot(self._sigmoid(X_b.dot(theta)) - y) / len(y)

def gradient_descent(X_b, y, initial_theta, eta, n_iters=1e4, epsilon=1e-8):
    theta = initial_theta
    cur_iter = 0

    while cur_iter < n_iters:
        gradient = dJ(theta, X_b, y)
        last_theta = theta
        theta = theta - eta * gradient
        if (abs(J(theta, X_b, y) - J(last_theta, X_b, y)) < epsilon):
            break

        cur_iter += 1

    return theta

Метод прогнозирования и метод подсчета очков:

def predict(self, X_predict):
    assert self.intercept_ is not None and self.coef_ is not None, \
        "must fit before predict!"
    assert X_predict.shape[1] == len(self.coef_), \
        "the feature number of X_predict must be equal to X_train"

    X_b = np.hstack([np.ones((len(X_predict), 1)), X_predict])
    proba = self._sigmoid(X_b.dot(self._theta))
    return np.array(proba >= 0.5, dtype='int')

def score(self, X_test, y_test):
    y_predict = self.predict(X_test)
    assert len(y_true) == len(y_predict), \
               "the size of y_true must be equal to the size of y_predict"

    return np.sum(y_true == y_predict) / len(y_true)

Полиномиальные функции

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

Сначала мы моделируем набор данных с нелинейным распределением:

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(666)
X = np.random.normal(0, 1, size=(200, 2))
y = np.array(X[:,0]**2 + X[:,1]**2 < 1.5, dtype='int')

plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
plt.show()

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

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler

def PolynomialLogisticRegression(degree):
    return Pipeline([
        # 给样本特征添加多形式项;
        ('poly', PolynomialFeatures(degree=degree)),
        # 数据归一化处理;
        ('std_scaler', StandardScaler()),
        ('log_reg', LogisticRegression())
    ])

poly_log_reg = PolynomialLogisticRegression(degree=2)
poly_log_reg.fit(X, y)

plot_decision_boundary(poly_log_reg, axis=[-4, 4, -4, 4])
plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
plt.show()

Граница решения для окончательных данных выглядит следующим образом:

def plot_decision_boundary(model, axis):
    x0, x1 = np.meshgrid(
        np.linspace(axis[0], axis[1], int((axis[1]-axis[0])*100)).reshape(-1,1),
        np.linspace(axis[2], axis[3], int((axis[3]-axis[2])*100)).reshape(-1,1)
    )
    X_new = np.c_[x0.ravel(), x1.ravel()]
    
    y_predict = model.predict(X_new)
    zz = y_predict.reshape(x0.shape)
    
    from matplotlib.colors import ListedColormap
    custom_cmap = ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])
    
    plt.contourf(x0, x1, zz, linewidth=5, cmap=custom_cmap)

Проблема мультиклассификации

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

  1. OVR (One vs Rest), пара оставшихся значений.
  2. OVO (One vs One) означает «один на один».

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

OVR

ovr

Как показано на рисунке выше, при классификации n типов выбороксоответственноВозьмите определенный класс образцов как один класс и обработайте оставшиеся n-1 образцов класса как другой класс, чтобы его можно было преобразовать в n задач бинарной классификации. Наконец, можно получить n моделей алгоритмов (как показано на рисунке выше, в итоге будет 4 модели алгоритмов), и выборки, которые необходимо предсказать, передаются в n моделей соответственно, а тип выборки соответствует модели с наибольшая вероятность является результатом предсказания.

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

from sklearn.multiclass import OneVsRestClassifier
from sklearn.linear_model import LogisticRegression

log_reg = LogisticRegression()
ovr = OneVsRestClassifier(log_reg)
ovr.fit(X_train, y_train)
ovr.score(X_test, y_test)

OVO

ovo

Среди n типов образцов каждый раз выбираются два типа образцов, и, наконец, формируются\mathrm{C}_{\mathrm{n}}^{2}Дихотомический случай, т.\mathrm{C}_{\mathrm{n}}^{2}алгоритмическая модель, есть\mathrm{C}_{\mathrm{n}}^{2}Результат прогнозирования, тип выборки с наибольшим разнообразием среди этих результатов является окончательным результатом прогнозирования.

В реализации логистической регрессии sklearn ovr используется по умолчанию для мультиклассификации.Если вы используете ovo, вам нужно указатьmulti_class,в то же времяsolverПараметры также необходимо изменить (p.s: sklearn не использует градиентный спуск, как описано выше, для оптимизации функции потерь):

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression

iris = datasets.load_iris()

X = iris.data
y = iris.target

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

log_reg2 = LogisticRegression(multi_class="multinomial", solver="newton-cg")
log_reg2.fit(X_train, y_train)
log_reg2.score(X_test, y_test)
from sklearn.multiclass import OneVsRestClassifier

ovr = OneVsRestClassifier(log_reg)
ovr.fit(X_train, y_train)
ovr.score(X_test, y_test)

from sklearn.multiclass import OneVsOneClassifier

ovo = OneVsOneClassifier(log_reg)
ovo.fit(X_train, y_train)
ovo.score(X_test, y_test)

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

Обычно выражение регуляризации выглядит следующим образом:

J(\theta)+\alpha L_{1}

Вы можете преобразовать его и изменить местоположение гиперпараметра. Если гиперпараметр C больше, исходная функция потерьJ(\theta)положение относительно важно. Если гиперпараметры очень малы, положение регуляризатора относительно важно. Если вы хотите сделать обычный член неважным, вам нужно увеличить параметр C. Это выражение обычно используется в sklearn.

C \cdot J(\theta)+L_{1}

Алгоритм логистической регрессии в sklearn автоматически инкапсулирует функцию регуляризации модели, и ему нужно только настроить C и штраф (выбор регулярного термина).L_{1}илиL_{2}.