Возвращаясь к основам классификации

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

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

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

  • Базовая математика модели логистической регрессии.
  • Функции потерь для логических зависимых переменных.
  • Метод стохастического градиентного спуска.
  • Как создать модель логистической регрессии с помощью Python 🐍.

Если вы только начинаете заниматься наукой о данных или просто хотите пересмотреть некоторые базовые концепции, несмотря на свою продвинутую карьеру, продолжайте читать эту статью до конца!

Булевы зависимые переменные, вероятности и шансы

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

В линейной регрессии зависимая переменная d, которая является непрерывной и неограниченной, имеет линейную связь с m независимыми переменными g₁, g ₂, … гₘ:

d = cg₁ + cg₂ + … + cₘgₘ,

где c₁, c₂, … cₘ — параметры модели m, связанные с независимыми переменными.

Однако в задачах бинарной классификации зависимая переменная d' является логической: d' имеет значения только 0 или 1. Поскольку приведенное выше уравнение линейной регрессии возвращает непрерывные и неограниченные значения для d его нельзя использовать напрямую для моделирования логических зависимых переменных.

Мы могли бы попробовать рассматривать непрерывный вывод для d как вероятность. Однако у нас не может быть отрицательных вероятностей или вероятностей больше 1! Следовательно, d нельзя рассматривать как вероятность.

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

O = P / (1 - P),

где O — шансы, а P — вероятность.

В то время как вероятность P имеет диапазон [0, 1], коэффициент O имеет диапазон [0, ∞), что делает его неограниченным для положительных значений. Нам все еще нужно учитывать отрицательные значения. Для этого мы возьмем логарифм шансов O, чтобы получить логарифм шансовl:

l = лог(O),

где l непрерывен и имеет диапазон (-∞, ∞), а без потери общности log — это логарифм по основанию e.

Это именно то решение, которое нам нужно, чтобы использовать приведенное выше уравнение линейной регрессии для моделирования логических зависимых переменных с использованием шансов и вероятностей! Все, что нам нужно сделать, это рассматривать d как логарифмические шансы l:

l = log(O) = log(P / (1 - P)) = c g₁ + cg₂ + … + cₘgₘ.

Мы можем переформулировать это уравнение, чтобы выразить его через P:

P / (1 - P) = exp(cg₁ + cg₂ + … + cₘgₘ)
P = 1 / (1 + exp(- (cg₁ + cg₂ + … + cₘgₘ))),

что можно записать более компактно как:

P = 1 / (1 + exp(-mg)),

где m — вектор длины m, содержащий параметры модели: m=[c₁, c₂, … cₘ]ᵀ и g также является вектором длины m, содержащим объясняющие переменные: g = [g, g₂, … gₘ]ᵀ. Это уравнение также известно как логистическая функция, отсюда и термин логистическая регрессия!

Модель линейной регрессии d = mg была преобразована в модель логистической регрессии P = 1 / (1 + exp(-mg)), что моделирует вероятность P как нелинейную функцию m и g! Обратите внимание, что исходная логическая зависимая переменная d’ не появляется в модели логистической регрессии — вместо этого мы имеем дело исключительно с вероятностями! d’ снова появится позже в функции потерь.

УравнениеP = 1 / (1 + exp(-mg)) может быть трудным для понимания, поэтому давайте рассмотрим графически рассмотрим самый простой случай. Самый простой случай, P = 1 / (1 + exp(-g)) только с 1 независимой переменной g, показан на графике ниже. . Когда g увеличивается, P стремится к значению 1, а когда g уменьшается, P стремится к значению из 0. Также на график включены некоторые синтетические логические точки данных для d' = 0 и d' = 1.

Когда d' = 1, P в идеале должно иметь соответствующее значение, близкое к 1, а когда d' = 0, P должно иметь соответствующее значение, близкое к 0. Задача классификации, определяемая моделью логистической регрессии, сводится к поиску набора параметров модели m=[c₁, c₂, … cₘ]ᵀчто приводит к описанному здесь поведению.

Векторы булевых зависимых переменных

В предыдущем разделе мы разработали уравнение логистической регрессии для одной логической зависимой переменной d’. В реальных жизненных ситуациях нам, скорее всего, придется иметь дело с вектором логических зависимых переменных, соответствующих набору n различных измерений: d' = [d '₁, d'₂, … d'ₙ]ᵀ.

В этом случае вероятность, соответствующая u-му элементу в d', моделируется моделью логистической регрессии с соответствующим набором m пояснительных переменные Gᵤ, Gᵤ₂, … Gᵤₘ:

Pᵤ = 1 / (1 + exp(- (Gᵤc+ Gᵤc ₂ + … + Gᵤₘcₘ))).

Обратите внимание, что Pᵤ — это просто u-й элемент в векторе: P = [P₁, P₂, … Pₙ]ᵀ. Для P это уравнение также можно записать в векторной форме следующим образом:

P = 1 / (1 + exp(-Gm)),

где G — матрица размера n×m со структурой:

G = [[G₁₁, G₁₂, … G],
.…….[G₂₁, G₂₂, … G],
.……. …
.…….[Gₙ₁, Gₙ₂, … Gₙₘ]],

с u-й строкой G, содержащей вектор m объясняющих переменных [Gᵤ, Gᵤ ₂, … Gᵤₘ],иm=[c ₁, c₂, … cₘ]ᵀ, как описано ранее.

Минимизация бинарной кросс-функции потери энтропии

Чтобы количественно оценить, насколько хорошо работает наша модель логистической регрессии, нам необходимо минимизировать ошибку между вероятностными предсказаниями модели P и булевыми значениями истинностиd' по отношению к параметры модели m.

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

Lᵤ(m) = - d'· log(Pᵤ(m)) - (1 - d') · log(1 - Pᵤ(m)).

Первый член в Lᵤ(m) активен, только когда d'ᵤ = 1, а второй член, только когда d' = 0. Чтобы получить потери для всех n точек данных, мы просто берем сумму:

L(m) = Σᵤ Lᵤ(m).

Мы хотим минимизировать функцию потерь L(m) по отношению к параметрам модели m. Для этого мы дифференцируем Lᵤ(m) относительно m:

Lᵤ(m)/∂m = ∂/∂m(- d'· log(Pᵤ(m)) - (1 - d') · log(1 - Pᵤ(m))),

Эта производная выглядит устрашающе, но мы можем использовать правило произведения, чтобы разбить ∂Lᵤ(m)/∂m на более простые компоненты:

Lᵤ(m)/∂m = ∂Lᵤ(m)/∂Pᵤ(m) · ∂Pᵤ(m)/∂m.

Термин ∂Lᵤ(m)/∂Pᵤ(m) можно оценить как:

Lᵤ(m)/∂Pᵤ(m) = - d / Pᵤ + (1 - d) / (1 - Pᵤ) = (Pᵤ - d) / (Pᵤ · (1 - Pᵤ)),

в то время как ∂Pᵤ(m)/∂m можно оценить по формуле P = 1 / (1 + exp( -mg)) как:

Pᵤ(m)/∂m = - 1 / (1 + exp(-mg))² · (-g· exp(- mg)) = g· Pᵤ · (1 - Pᵤ),

где gᵤ =[Gₙ₁, Gₙ₂, … Gₙₘ]ᵀ — вектор, содержащий u-ю строку матрицы G.

Используя эти результаты, производная ∂Lᵤ(m)/∂m теперь может быть оценена как:

Lᵤ(m)/∂m = (Pᵤ - d) / (Pᵤ · (1 - Pᵤ)) · g· Pᵤ · (1 - Pᵤ) = g· (Pᵤ - d).

В идеале мы должны установить ∂Lᵤ(m)/∂m = 0 и найти наилучшие параметры модели m. К сожалению, мы не можем вычислить m, так как они не входят в выражение для ∂Lᵤ(m)/∂m ! Поэтому кажется, что нам может понадобиться обратиться к численным методам, чтобы минимизировать Lᵤ(m) по отношению к m.

Стохастический градиентный спуск

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

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

Помните, что мы хотим минимизировать L(m) относительно m. Используя алгоритм стохастического градиентного спуска, мы делаем несколько небольших шагов в m в направлении самого крутого отрицательного градиента: - ∂Lᵤ(m) /∂mк точке минимума L(m).

Поэтому мы итеративно обновляем параметры модели m, используя ∂Lᵤ(m)/∂m для всех nэлементов в d' = [d'₁, d'₂, … d'ₙ]ᵀ:

m := m - R · ∂Lᵤ(m)/∂m,

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

Размер скорости обучения очень важен: если R слишком мало, шаги почти не изменят значение m, с другой стороны, если R слишком велико, то мы можем выйти за пределы минимальной точки! Выбор подходящего значения для R зависит от задействованных данных.

Алгоритм стохастического градиентного спуска можно записать в виде псевдокода:

# Initialize the model parameters with some initial value.
m = initialize_m()
# Iterate the algorithm until some termination condition is reached.
while termination_condition == False:
    # Shuffle the rows the training data.
    shuffle_training_data()
    # Iterate through all the n elements in the data.
    for i in range(n):
        # Update the model parameters m using the derivative of the 
        # loss function for that element.
        m = m - R * dLdm[i]
    # Check if the termination condition is reached.
    check_termination_condition()

Резюме

Мы рассмотрели множество деталей, поэтому давайте подытожим то, через что мы прошли, прежде чем переходить к кодированию на Python!

  1. Мы хотим смоделировать логические зависимые переменные d’ ∈ {0, 1}.
  2. Вместо прямого моделирования логических значений мы моделируем вероятности, используя: P = 1 / (1 + exp(- (cg₁ + cg₂ + … + cₘgₘ))).
  3. Мы измеряем ошибку предсказания модели с помощью бинарной кросс-энтропии:
    Lᵤ(m) = - d'· log(Pᵤ(m)) - (1 - d') · log( 1 – Pᵤ(м)).
  4. Чтобы найти наилучшие параметры модели m, мы минимизируем Lᵤ(m) относительно m. К сожалению, эту проблему минимизации приходится решать с помощью численных методов, таких как стохастический градиентный спуск.

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

Мы сделали достаточно математики на данный момент! В этом разделе мы создадим решатель модели логистической регрессии, используя Python!

Во-первых, мы определяем logistic_regression, которые реализуют модель логистической регрессии:

import numpy as np
def logistic_regression(G, m):
    """
    Logistic regression model.
    Inputs
    G: np.array of shape nxm containing the explanatory variables.
    m: np.array of length m containing the model parameters.
    Returns
    P: np.array of length n containing the modeled probabilities.
    """
    return 1 / (1 + np.exp(-np.dot(G, m)))

Затем мы определяем binary_cross_entropy и binary_cross_entropy_grad, которые вычисляют бинарную перекрестную энтропию и ее градиент.

def binary_cross_entropy(d, P):
    """
    Calculates the mean binary cross entropy for all n data points.
    Inputs
    d: np.array of length n containing the boolean dependent 
       variables.
    P: np.array of length n containing the model's probability 
       predictions.
    Returns
    bce: float containing the mean binary cross entropy.
    """
    
    # For d = 1:
    d_1 = d[d == 1] * np.log(P[d == 1])
    # For d = 0:
    d_0 = (1 - d[d == 0]) * np.log(1 - P[d == 0])
    return -np.mean(d_1) - np.mean(d_0)
def binary_cross_entropy_grad(g, d, p):
    """
    Calculates the gradient of the binary cross entropy loss for a 
    single data point.
    Inputs:
    g: np.array of length m containing the vector of explanatory 
       variables for 1 data point.
    d: integer containing the boolean dependent variable for 1 data 
       point.
    p: float containing the model's probability predictions for 1 
       data point.
    Returns:
    bce_grad: np.array of length m containing the gradients of the 
              binary cross entropy.
    """
    return g * (p - d)

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

def SGD(d, G, m, R = 0.01):
    """
    Stochastic gradient descent. Updates the model parameters m 
    iteratively using all individual data points in d.
    
    Inputs
    d: np.array of length n containing the boolean dependent 
       variables.
    G: np.array of shape nxm containing the explanatory variables.
    m: np.array of length m containing the initial model parameters.
    R: float containing the SGD learning rate.
    
    Returns
    m: np.array of length m containing the updated model parameters.
    """
    # Shuffle the data points.
    indices = np.arange(len(d))
    np.random.shuffle(indices)
    d = d[indices]
    G = G[indices]
    # Iteratively update the model parameters using every single
    # data point one by one.
    for i in range(len(d)):
        P = logistic_regression(G[i], m)
        m = m - R * binary_cross_entropy_grad(G[i], d[i], P)
    return m

Теперь все, что нам нужно, это некоторые данные для тестирования нашего решателя логистической регрессии! Мы используем набор данных классификации цветов ириса, доступный на сайте sklearn. Исходный набор данных предоставляет 3 разных класса: 0, 1 и 2. Мы ограничиваем набор данных только классами 0 и 1.

from sklearn.datasets import load_iris
# Load the iris flower dataset from sklearn.
data = load_iris()
G = data["data"]   # Explanatory variable matrix.
d = data["target"] # Dependent variable array.
# The dataset has 3 classes. For the time being, restrict the data
# to only classes 0 and 1 in order to create boolean dependent
# variables.                  
want = (d == 0) | (d == 1)
G = G[want]
d = d[want]

Поскольку наша модель логистической регрессии

P = 1 / (1 + exp(- (cg₁ + cg ₂ + … + cₘgₘ)))

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

# Because we do not explicitly account for a bias term in our
# logistic regression model, we need to add a column of 1s to the
# matrix G. This will play the term of the bias term.
G = np.hstack([np.ones([len(G), 1]), G])
# Take a look at the first 3 data points in the dataset.
for i in range(3):
    print(G[i], d[i])

[1.  5.1 3.5 1.4 0.2] 0
[1.  4.9 3.  1.4 0.2] 0
[1.  4.7 3.2 1.3 0.2] 0

Теперь, когда у нас есть готовые структуры данных G и d, пришло время сделать первоначальное предположение о параметрах модели m! Поскольку для каждой точки данных есть 5 независимых переменных, в m будет 5 параметров модели. Делаем предположение, что параметры модели имеют значение 1.

m = np.ones(5)

Затем мы запускаем решатель стохастического градиентного спуска для 20 итераций, используя скорость обучения R = 0,01, чтобы предотвратить выход алгоритма за пределы минимальной точки.

# Run the SGD algorithm 20 times.
for i in range(20):
    # Use a learning rate of 0.01.
    m = SGD(d, G, m, 0.01)
    # Calculate the mean loss for all data in the dataset.
    P = logistic_regression(G, m)
    bce = binary_cross_entropy(d, P)
    print("Step {:3d} : m = [ ".format(i+1), end = "")
    for j in range(len(m)):
        print("{:6.3f}".format(m[j]), end = " ")
    print("], loss = {:.3f}.".format(bce))

Step   1 : m = [  0.692 -0.506 -0.080  0.640  0.961 ], loss = 0.548. Step   2 : m = [  0.674 -0.517 -0.207  0.854  1.050 ], loss = 0.417. Step   3 : m = [  0.643 -0.609 -0.355  0.985  1.110 ], loss = 0.322. Step   4 : m = [  0.629 -0.620 -0.440  1.124  1.168 ], loss = 0.258. Step   5 : m = [  0.616 -0.638 -0.514  1.234  1.214 ], loss = 0.218. Step   6 : m = [  0.608 -0.635 -0.568  1.337  1.257 ], loss = 0.187. Step   7 : m = [  0.598 -0.652 -0.626  1.417  1.291 ], loss = 0.165. Step   8 : m = [  0.592 -0.652 -0.668  1.495  1.324 ], loss = 0.148. Step   9 : m = [  0.579 -0.691 -0.726  1.545  1.348 ], loss = 0.135. Step  10 : m = [  0.573 -0.699 -0.765  1.605  1.373 ], loss = 0.123. Step  11 : m = [  0.568 -0.697 -0.796  1.665  1.398 ], loss = 0.113. Step  12 : m = [  0.563 -0.702 -0.828  1.716  1.420 ], loss = 0.104. Step  13 : m = [  0.556 -0.716 -0.862  1.759  1.439 ], loss = 0.097. Step  14 : m = [  0.551 -0.723 -0.891  1.802  1.457 ], loss = 0.091. Step  15 : m = [  0.547 -0.729 -0.918  1.843  1.475 ], loss = 0.086. Step  16 : m = [  0.542 -0.736 -0.944  1.880  1.491 ], loss = 0.081. Step  17 : m = [  0.538 -0.741 -0.968  1.916  1.506 ], loss = 0.077. Step  18 : m = [  0.535 -0.741 -0.987  1.953  1.522 ], loss = 0.073. Step  19 : m = [  0.531 -0.748 -1.009  1.985  1.536 ], loss = 0.069. Step  20 : m = [  0.528 -0.748 -1.027  2.018  1.549 ], loss = 0.066.

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

# Print the ground truth.
print(d)
# Print the modeled probabilities of the logistic regression model, 
# rounded to the nearest integer value.
print(np.round(logistic_regression(G, m)).astype(int))

[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]

Похоже, что наша модель идеально классифицировала каждую точку данных в наборе данных! Далее давайте более подробно рассмотрим фактические неокругленные выходные данные модели как для случая, когда d’ = 1, так и для случая d’ = 0.

# Get the probability predictions from the model.
P = logistic_regression(G, m)
# d' = 0
print("d' = {}, P = {:.3f}.".format(d[0], P[0]))
# d' = 1
print("d' = {}, P = {:.3f}.".format(d[-1], P[-1]))

d' = 0, P = 0.023. 
d' = 1, P = 0.975.

Мы видим, что когда d' = 1, P очень близко к 1, а когда d' = 0, P очень близко к 0 — именно такое поведение мы и ожидали!

Слово предостережения

В приведенном выше примере используемые данные были очень хорошо разделены. То есть была явная разница в объясняющих переменных между d' = 1 и d' = 0. Вот как нам удалось получить идеальный результат на нашем предсказания.

В реальных проектах по науке о данных данные обычно не такие чистые — нам нужно спроектировать функции в данных, рассмотреть соотношение между количеством 0 и 1 в d' и тонко настроить процесс обучения модели. В зависимости от данных другие функции потерь, такие как фокальная потеря или регуляризованные функции потерь, могут работать лучше, чем бинарная перекрестная энтропия.

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

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

Кроме того, мы протестировали нашу модель на данных, используемых для ее обучения — на практике весь набор данных должен быть разделен на наборы данных для обучения и проверки! Модель должна быть обучена на обучающем наборе данных, а затем протестирована на проверочном наборе данных — это необходимо для того, чтобы модель не просто запомнила обучающий набор данных и могла обобщать свои прогнозы на данные, которые она ранее не видела!

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

Краткое содержание

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

Рекомендации

[1] В. М. Менке (2012 г.), Анализ геофизических данных: дискретная инверсная теория MATLAB Edition, Elsevier.
[2] Аллен Б. Дауни (2014 г.), Исследовательские данные Think Stats Анализ на Python, Green Tea Press.