Логистическая регрессия — это линейный классификатор. Он часто используется для бинарной классификации, когда есть два результата, например. 0/1.

Обзор логистической регрессии

Три шага логистической регрессии:

  1. Линейная регрессия между функциями и оценкой, оценка представляет собой непрерывное число от -Inf до Inf:

2. Сигмовидная функция вычисляет вероятность того, что эта выборка относится к классу 1, на основе оценки:

3. Определите метку (y) на основе вероятности и порога (в этом примере я использовал 0,5):

Сигмовидная функция

На шаге 2 сигмовидная функция используется для преобразования оценки в вероятность.

S-образная сигмовидная кривая:

score = list(range(-6, 7, 1))
sigmoid_score = [1/(1+np.exp(-1*x)) for x in score]
y = [0,0,0,0,0,1,0,0,1,1,1,1,1]
import matplotlib.pyplot as plt
plt.plot(score, sigmoid_score)
plt.scatter(score, y)
plt.vlines(x=0, ymin=0, ymax=1, color = 'red', linestyles='--')
plt.xlabel('score')
plt.ylabel('Sigmoid(score)')

Если порог равен 0,5, то y = 1, если сигмоид (оценка) > 0,5, и y = 0, если сигмовид (оценка) ≤ 0,5. Порог может быть любым числом от 0 до 1 в зависимости от набора данных и цели.

Градиентный подъем

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

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

Вероятность:

вероятность = p(y1=1|x1,b)*p(y2=0|x2,b)*p(y3=0|x3,b)*p(y4=1|x4,b)

Сценарий 1: Классификатор обучен и вернул набор весов (b0, .., bk), а также вероятности:

вероятность = 0,8*0,4*0,7*0,8 = 0,179

В этом случае я правильно понял 3 образца (образец 1, 3, 4) и 1 неправильно (образец 2).

Сценарий 2. Другой классификатор обучен и вернул другой набор весов (b0, …, bk), а также вероятности:

вероятность = 0,8*0,6*0,7*0,8 = 0,269

В этом случае я получил все 4 образца правильно, а также большую вероятность.

Следовательно, нам нужно искать набор весов (b0, .., bk), который может максимизировать вероятность.

Вероятность определяется как:

Журнал вероятности:

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

Процесс подъема в гору таков:

Производная логарифмического правдоподобия относительно признака j:

Скажем, у нас есть 4 образца и 2 признака — feature1, feature2. Для каждого признака коэффициент b1 и b2:

Градиенты b1 и b2 на образец:

Затем мы выполняем суммирование строк, чтобы получить градиент (b1 | x) и градиент (b2 | x) по всем образцам:

градиент(b1|x) = 10 * (0–0,3) + 2 * (0–0,4) + 4 * (1–0,8) + 5*(0–0,6) = -6

градиент (b2 | x) = 1 * (0–0,3) + 3 * (0–0,4) + 4 * (1–0,8) + 2 * (0–0,6) = -1,9

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

Термин 1[yi=1] — p(yi=1|xi, w) измеряет разницу между истинным и прогнозным значением. Если мы правильно предсказываем все выборки, этот термин будет равен 0. Тогда градиент будет равен 0, больше не будет обновляться для весов.

Алгоритм градиентного восхождения:

Градиентный подъем на практике

Я использовал общедоступный набор данных от Kaggle: https://www.kaggle.com/datasets/sveneschlbeck/beginners-classification-dataset

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

Сначала импортируйте все библиотеки и прочитайте набор данных, определите функции и цель:

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split

# import dataset
df = pd.read_csv('/Users/keru/Downloads/classification.csv')

# define feature and target
x = df.drop('success', axis=1)
y = df.success.values

Столбец со значением 1 необходимо вставить в матрицу признаков (x) для термина пересечения (b0):

# insert a column with value 1 for intercept 
x = np.insert(np.array(x), 0, np.ones(df.shape[0]), axis=1)

Нормируйте матрицу признаков по размеру вектора для каждого признака:

# normalize feature by its vector size
x_normalizer = np.sqrt(sum(x**2))
x_norm = x/x_normalizer

Сплит «Тренировка поезда»:

# train and test split
x_train, x_test, y_train, y_test = train_test_split(x_norm, y, test_size=0.2, random_state=0)

Определим нашу функцию градиентного восхождения:

# gradient ascent
# help function to get gradient
def gradient_logistic(x: np.array,
                      y: np.array,
                      weights: np.array) -> np.array:

    score = np.matmul(x, weights)
    proba_class1 = 1 / (1 + np.exp(-1 * score))
    gradient_matrix = np.zeros((x.shape[0], x.shape[1]))

    for i in range(len(weights)):  # loop over feature - column
        for j in range(x.shape[0]):  # loop over sample - row
            if y[j] == 1:
                gradient_matrix[j][i] = x[j, i] * (1 - proba_class1[j])
            if y[j] == 0:
                gradient_matrix[j][i] = x[j, i] * (0 - proba_class1[j])

    # sum over row
    gradient = gradient_matrix.sum(axis=0)
    return (gradient)

Определите порог для остановки (порог = 0,01) и размер шага.

# initial weights, gradient, threshold and stepsize
weights_all = dict()
gradient_vector_size = dict()
threshold = 0.01
stepsize_list = [0.01, 0.1, 1, 5]

Начать итерацию для каждого шага:

for stepsize in stepsize_list:

    # initial weights and gradient
    weights = np.zeros(x.shape[1])
    gradient = np.ones(x.shape[1])
    iter = 0

    # iteration starts for each stepsize
    gradient_vector_size_iteration = []
    while np.sqrt(np.dot(gradient, gradient)) > threshold:
        gradient_vector_size_iteration.append(np.sqrt(np.dot(gradient, gradient)))
        gradient = gradient_logistic(x_train, y_train, weights)
        weights = weights + stepsize * gradient
        weights_all[str(iter)] = weights
        iter += 1

    gradient_vector_size[str(stepsize)] = gradient_vector_size_iteration

Постройте размер вектора градиента для каждого размера шага:

import matplotlib.pyplot as plt
figure, ax = plt.subplots(1,4, figsize = (10,5))
i = 0
for key in gradient_vector_size.keys():
    ax[i].plot(gradient_vector_size[key])
    ax[i].set_title('stepsize ' + key)
    i +=1

plt.show()

Все 4 размера шага сошлись, размер шага = 5 сходился намного быстрее (при итерации 400+), чем размер шага = 0,01 (при итерации 200k+).

Окончательные коэффициенты регрессии для 4 различных размеров шага:

Коэффициенты регрессии сошлись для всех 4 размеров шага.

Теперь давайте предскажем на тестовом наборе:

# predicting on test data
# 1. get score
test_score = np.matmul(x_test, weights)
# 2. get proba
proba_class1 = 1/(1+np.exp(-1*test_score))
# 3. get pred label
pred_label = [1 if x>=0.5 else 0 for x in proba_class1]

Оцените производительность на тестовом наборе:

# evaluation
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, pred_label)
print(cm)

from sklearn.metrics import classification_report
cr = classification_report(y_test, pred_label)
print(cr)

Матрица путаницы:

            Model predict 
True label   0   1
         0   23  7
         1   1   29

Есть 7 ложноположительных случаев и 1 ложноотрицательный случай

Отчет о классификации:

                 precision    recall  f1-score   support
         0.0       0.96      0.77      0.85        30
         1.0       0.81      0.97      0.88        30
    accuracy                           0.87        60
   macro avg       0.88      0.87      0.87        60
weighted avg       0.88      0.87      0.87        60

На этом все, спасибо за прочтение :)