В машинном обучении с учителем, помимо построения регрессионных моделей для прогнозирования непрерывных переменных, также важно решать задачу классификации, начиная с ее простейшей формы, известной как бинарная классификация, которая дает два результата: 0 — отрицательный класс или 1. - Положительный класс. Обычный порог для этих проблем равен 0,5, так как модель будет предсказывать, что входные данные относятся к положительному классу, если функция гипотезы возвращает значение выше 0,5, и присоединяется к отрицательному классу, если значение результата меньше 0,5. Если мы хотим использовать функцию гипотезы Многомерная регрессия, диапазон целевых значений будет идти от отрицательной бесконечности слева до положительной бесконечности справа, что делает неразумным классифицировать результат со значением 3 265 634. Следовательно, для бинарного классификатора нам нужно вычислить алгоритм, который живет в диапазоне от 0 до 1 включительно. В этой статье будет рассказано, как логистическая регрессия использует градиентный спуск для поиска оптимизированных параметров и как реализовать алгоритм на Python.

Институт логистической регрессии

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

  • y или hθ(x) =Функция гипотезы (зависимая переменная), использующая параметры модели theta в качестве входных данных
  • θ0, θ1,…, θn =веса ​​или параметры модели
  • x1, x2,…, xn = предикторы (независимые переменные)
  • n =количество функций

Или мы можем использовать для него векторизованную форму с вектором параметров модели и вектором признаков экземпляра. Полное из этих значений можно найти здесь:

Функция g() — это сигмовидная функция, которая позволяет функции гипотезы возвращать число от 0 до 1. Сигмовидная функция определяется следующим образом:

Интересной характеристикой этой сигмовидной линии является то, что функция принимает значение 0,5 в качестве порога, когда z = 0 для вывода своего прогноза. В частности, при положительном входе функция будет генерировать вероятность больше 0,5 (т. е. предсказывать ее как положительный класс), а при отрицательном — будет проецироваться отрицательный класс.

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

Функция стоимости

На этом этапе новичкам в машинном обучении может быть трудно понять, как сгенерировать функцию стоимости для бинарной классификации, когда она содержит только два значения (0 и 1). Идея оценки логистической модели состоит в том, чтобы измерить каждую разницу, созданную фактическим результатом и прогнозируемым результатом. Важным напоминанием здесь является то, что нам нужно использовать значение, сгенерированное функцией гипотезы, прежде чем она будет делать прогнозы. Это связано с тем, что если существуют две модели, которые возвращают оценочную вероятность 0,6 и 0,9, их предсказания будут принадлежать к положительному классу. Однако мы бы предпочли использовать модель 0,9, чем модель 0,6. Вот формула для оценки одного тренировочного экземпляра.

Чтобы лучше понять, почему выбраны эти функции, давайте визуализируем функции -log(x) и -log(1-x).

(Примечание: весь код для графиков в статье можно найти здесь:)

Из визуализации мы можем ясно видеть, что выбор создания этих функций для оценки прогноза для одного экземпляра является подходящим. Если исходные входные данные принадлежат положительному классу (-log(-x)Graph), поскольку его проекция приближается к 0 (т. е. делает неверный прогноз), функция стоимости достигает положительной бесконечности. Эта предельная функция продемонстрировала свойство, заключающееся в том, что потери будут близки к 0, если прогноз приближается к 1; с другой стороны, это будет сильно наказывать модель, если оценочная вероятность приближается к 0. Эта логика также применима к случаю отрицательного класса, в котором модель будет наказана (большая стоимость), если она сделает неверный прогноз и вознаградится ( небольшая стоимость), когда прогноз близок.

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

  • J(θ) =Функция стоимости, которая использует тета в качестве входных данных
  • m =количество экземпляров
  • x(i) =ввод (функции) i-го обучающего примера
  • y(i) =результат (функции) i-го обучающего примера

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

Как работает градиентный спуск в логистической регрессии?

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

Символ 𝛼 представляет скорость обучения алгоритма, которая управляет скоростью обучения модели. Выбор подходящей скорости обучения имеет важное значение, поскольку это гарантирует, что наша функция стоимости будет сходиться в разумные сроки. Если модель не может сходиться или если для определения ее минимального значения требуется слишком много времени, данные означают, что наша скорость обучения, вероятно, является неправильным выбором. Однако в алгоритме градиентного спуска скорость обучения просто играет роль постоянной величины; следовательно, после частичного дифференцирования функции стоимости алгоритм принимает вид:

  • x^(i)_j = значение признака j в i-м обучающем примере

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

X – это матрица, содержащая все значения в наборе данных, но не включающая значения результатов.

Затем мы генерируем формулу вектора градиента для функции стоимости:

Поэтому, чтобы завершить наш обновленный алгоритм theta, у нас есть:

В ПОРЯДКЕ! В основном мы прошли через все формулы, которые нам нужны для реализации градиентного спуска для логистической регрессии в Python. Давайте перейдем непосредственно к части кодирования.

Реализация градиентного спуска для логистической регрессии в Python

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

def generateXvector(X):
    """ Taking the original independent variables matrix and add a row of 1 which corresponds to x_0
        Parameters:
          X:  independent variables matrix
        Return value: the matrix that contains all the values in the dataset, not include the outcomes variables. 
    """    vectorX = np.c_[np.ones((len(X), 1)), X]
    return vectorX

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

def theta_init(X):
    """ Generate an initial value of vector θ from the original independent variables matrix
         Parameters:
          X:  independent variables matrix
        Return value: a vector of theta filled with initial guess
    """
    theta = np.random.randn(len(X[0])+1, 1)
    return theta

Будет слишком сложно, если мы включим процесс вычисления значения сигмоиды во время функции градиентного спуска. Таким образом, было бы лучше, если бы мы разделили эти два процесса.

def sigmoid_function(X):
    """ Calculate the sigmoid value of the inputs
         Parameters:
          X:  values
        Return value: the sigmoid value
    """
    return 1/(1+math.e**(-X))

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

def Logistics_Regression(X,y,learningrate, iterations):
    """ Find the Logistics regression model for the data set
         Parameters:
          X: independent variables matrix
          y: dependent variables matrix
          learningrate: learningrate of Gradient Descent
          iterations: the number of iterations
        Return value: the final theta vector and the plot of cost function
    """
    y_new = np.reshape(y, (len(y), 1))   
    cost_lst = []
    vectorX = generateXvector(X)
    theta = theta_init(X)
    m = len(X)
    for i in range(iterations):
        gradients = 2/m * vectorX.T.dot(sigmoid_function(vectorX.dot(theta)) - y_new)
        theta = theta - learningrate * gradients
        y_pred = sigmoid_function(vectorX.dot(theta))
        cost_value = - np.sum(np.dot(y_new.T,np.log(y_pred)+ np.dot((1-y_new).T,np.log(1-y_pred)))) /(len(y_pred))
 #Calculate the loss for each training instance
        cost_lst.append(cost_value)
    plt.plot(np.arange(1,iterations),cost_lst[1:], color = 'red')
    plt.title('Cost function Graph')
    plt.xlabel('Number of iterations')
    plt.ylabel('Cost')
    return theta

Изучите наш алгоритм градиентного спуска для логистической регрессии

Удивительный! Мы только что завершили реализацию градиентного спуска для логистической регрессии. Наша следующая задача — проверить наш код, сравнив его идеальные параметры с результатом LogisticsRegression от SkLearn. В общем, для задачи классификации более правильно использовать показатель точности как метод оценки производительности модели; поэтому нам потребовалось немного больше кода, чтобы обеспечить точность нашего алгоритма. Для классификации мы будем использовать известный набор данных Iris DataSet от SkLearn. Цель этого набора данных — выделить три различных класса ирисов: ирис разноцветный, ирис сетозный и ирис виргинский.

Однако для целей этой статьи мы не собираемся классифицировать все три вида, поскольку логистическая регрессия работает намного лучше с бинарной классификацией. Поэтому наша цель — определить, является ли ввод Iris Versicolor или нет.

from sklearn import datasets
iris = datasets.load_iris()
X = iris["data"]
y = (iris["target"] == 0).astype(np.int) #return 1 if Iris Versicolor, else 0.

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

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
X_train = sc.fit_transform(X_train)
X_test = sc.transform(X_test)

Использование функции LogisticsRegression от SkLearn для обучения данных.

from sklearn.linear_model import LogisticRegression
classifier = LogisticRegression(random_state = 0, penalty = 'none')
classifier.fit(X_train, y_train)
classifier.intercept_, classifier.coef_
>>> (array([-11.07402312]),
 array([[ -1.32289075,   4.23503694, -10.11887281,  -9.22137322]]))

Вот оценка точности, полученная встроенной функцией.

y_pred = classifier.predict(X_test)
from sklearn.metrics import confusion_matrix, accuracy_score
accuracy_score(y_test, y_pred)
>>> 1.0

Вау! Модель логистической регрессии имеет высший балл при определении образа Iris Versicolor. Обратите внимание, что я установил параметр penalty равным «none», чтобы не применять регуляризацию к модели. Однако, поскольку набор данных не так сложен (небольшое количество экземпляров и признаков), модели все же легко удалось найти идеальное гиперпространство для различения этих переменных.

Тем не менее, получение идеальной классификации по-прежнему остается для нас проблемой. Мы реализуем некоторый код для возврата показателя точности нашей модели.

def column(matrix, i):
    """ Returning all the values in a specific columns
         Parameters:
          X: the input matrix
          i: the column
     Return value: an array with desired column
    """
    return [row[i] for row in matrix]
def accuracy_LR(X,y,learningrate, iteration,X_test, y_test):
    """ Returning the accuracy score for a training model
    
    """
    ideal = Logistics_Regression(X,y,learningrate, iteration)
    hypo_line = ideal[0]
    for i in range(1,len(ideal)):
        hypo_line = hypo_line + ideal[i]*column(X_test,i-1)
    logistic_function = sigmoid_function(hypo_line)
    for i in range(len(logistic_function)):
        if logistic_function[i] >= 0.5:
            logistic_function[i] = 1
        else:
            logistic_function[i] = 0
    last1 = np.concatenate((logistic_function.reshape(len(logistic_function),1), y_test.reshape(len(y_test),1)),1)
    count = 0
    for i in range(len(y_test)):
        if last1[i][0] == last1[i][1]:
            count = count+1
    acc = count/(len(y_test))
    return acc

Давайте сначала посмотрим, как работает наша оптимизация:

Logistics_Regression(X_train,y_train, 1, 1000000)
>>> array([[-10.81166363],
           [ -2.36666046],
           [  5.55693637],
           [-10.29389333],
           [ -9.64684255]])

Наши параметры действительно близки к встроенной генерации. А как насчет нашей оценки точности?

accuracy_LR(X_train,y_train, 1, 1000000,X_test, y_test)
>>> 1.0

Идеально! Мы также получаем 1,0 балла. Наш алгоритм работает без сбоев и дает такой результат!

Последние мысли

Прежде всего, спасибо всем за то, что дочитали до конца. Это так много значит для меня. Код этой статьи можно найти здесь.

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

Если вы любите машинное обучение, науку о данных или любые технические проблемы в наши дни, давайте поговорим со мной через Linkedin.