Возвращаясь к основам классификации
В предыдущей статье я исследовал линейную регрессию — основу всех других передовых моделей, используемых в машинном обучении и науке о данных. Линейная регрессия моделирует непрерывные зависимые переменные, такие как цены акций.
Однако в науке о данных и машинном обучении нам обычно приходится иметь дело и с булевыми или категориальными зависимыми переменными. Типичные логические зависимые переменные включают в себя такие вещи, как статус дефолта по кредиту должника — полностью погашен/дефолт или классификация изображений кошек — это кошка/не кошка 🐈!
Одной из самых основных моделей, используемых для моделирования логических зависимых переменных, является модель логистической регрессии. В этой статье я подробно рассмотрю:
- Базовая математика модели логистической регрессии.
- Функции потерь для логических зависимых переменных.
- Метод стохастического градиентного спуска.
- Как создать модель логистической регрессии с помощью Python 🐍.
Если вы только начинаете заниматься наукой о данных или просто хотите пересмотреть некоторые базовые концепции, несмотря на свою продвинутую карьеру, продолжайте читать эту статью до конца!
Булевы зависимые переменные, вероятности и шансы
В этом разделе мы рассмотрим математику, лежащую в основе логистической регрессии, начиная с самой базовой модели машинного обучения — линейной регрессии.
В линейной регрессии зависимая переменная d, которая является непрерывной и неограниченной, имеет линейную связь с m независимыми переменными g₁, g ₂, … гₘ:
d = c₁g₁ + c₂g₂ + … + 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₁ + c₂g₂ + … + cₘgₘ.
Мы можем переформулировать это уравнение, чтобы выразить его через P:
P / (1 - P) = exp(c₁g₁ + c₂g₂ + … + cₘgₘ)
P = 1 / (1 + exp(- (c₁g₁ + c₂g₂ + … + cₘgₘ))),
что можно записать более компактно как:
P = 1 / (1 + exp(-mᵀg)),
где m — вектор длины m, содержащий параметры модели: m=[c₁, c₂, … cₘ]ᵀ и g также является вектором длины m, содержащим объясняющие переменные: g = [g₁, g₂, … gₘ]ᵀ. Это уравнение также известно как логистическая функция, отсюда и термин логистическая регрессия!
Модель линейной регрессии d = mᵀg была преобразована в модель логистической регрессии P = 1 / (1 + exp(-mᵀg)), что моделирует вероятность P как нелинейную функцию m и g! Обратите внимание, что исходная логическая зависимая переменная d’ не появляется в модели логистической регрессии — вместо этого мы имеем дело исключительно с вероятностями! d’ снова появится позже в функции потерь.
УравнениеP = 1 / (1 + exp(-mᵀg)) может быть трудным для понимания, поэтому давайте рассмотрим графически рассмотрим самый простой случай. Самый простой случай, 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( -mᵀg)) как:
∂Pᵤ(m)/∂m = - 1 / (1 + exp(-mᵀgᵤ))² · (-gᵤ· exp(- mᵀgᵤ)) = 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!
- Мы хотим смоделировать логические зависимые переменные d’ ∈ {0, 1}.
- Вместо прямого моделирования логических значений мы моделируем вероятности, используя: P = 1 / (1 + exp(- (c₁g₁ + c₂g₂ + … + cₘgₘ))).
- Мы измеряем ошибку предсказания модели с помощью бинарной кросс-энтропии:
Lᵤ(m) = - d'ᵤ · log(Pᵤ(m)) - (1 - d'ᵤ) · log( 1 – Pᵤ(м)). - Чтобы найти наилучшие параметры модели 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(- (c₁g₁ + c₂g ₂ + … + 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.