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

Я разделю этот блог на серию вопросов и ответов. И постараюсь ответить на этот вопрос, пока мы узнаем, что такое логистическая регрессия.

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

Это:

1. У вас есть базовое представление о том, что такое двоичная классификация.

2. Вы знаете базовую геометрию линии, плоскости и гиперплоскости в 2D, 3D и n-D пространстве соответственно.

3. Что такое Максима и Минима?

4. Вы знаете основы функции потерь.

Вот и все. Если вы знаете эти моменты, мы готовы к работе.

В дальнейшем я буду называть логистическую регрессию LR.

Давайте начнем путь вопросов и ответов…

Кто-то прекрасно сказал: «Мы узнаем великие вещи, только задавая им вопросы».

Давайте вопрос:

В. Что такое логистическая регрессия?

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

Давайте посмотрим на интуицию LR:

Я объясню это на примере:

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

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

+1 = Подлинный

-1 = Мошенничество

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

Наше основное внимание уделяется тому, как моя модель LR предскажет, является ли транзакция подлинной или мошеннической.

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

Итак, давайте попробуем:

Теперь мы построили наши точки данных в двухмерном пространстве и представили, что это похоже на наш график графика. Возникает вопрос, а это ..

В. Как нам различать эти два разных момента?

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

Эта линия называется границей принятия решения, как показано ниже:

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

Точно так же наша модель также пытается узнать эту границу принятия решения. Возникает другой вопрос ...

В. Как наша Модель узнает эту границу?

Теперь, если мы посмотрим на это с геометрической точки зрения, то граница решения - не что иное, как простая линия в 2D-пространстве. И мы знаем, что эта линия также имеет уравнение, которое можно использовать для ее представления.

Теперь линию можно представить с помощью двух уникальных переменных: «m» и «b».

В двух измерениях уравнение для невертикальных линий часто задается в форме пересечения наклона:

y = mx + b

куда:

· M - наклон или уклон линии.

· B - точка пересечения линии по оси y.

· X - независимая переменная функции y = f (x).

Если наша прямая проходит через начало координат, то b = 0, тогда y = mx

В трехмерном пространстве у нас есть уравнение плоскости как:

Ax + By + Cz = D

Если прямая проходит через начало координат D = 0, то z = - (a / c) x + (-b / c) y

Теперь речь идет о 2D и 3D пространстве, в n-D пространстве у нас есть гиперплоскость вместо линии или плоскости. Возникает другой вопрос ...

В. Как представить гиперплоскость в уравнении?

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

w1, w2, w3, ….. , wn

и пусть размеры, которые мы представили как

x1, x2, x3, …. , xn

и позволяет использовать термин перехват как «b».

Итак, уравнение гиперплоскости выполняется как:

w1x1 + w2x2 + w3x3 +…. + wnxn + b = 0

Мы можем представить это как:

b + ∑ (wᵢxᵢ) = 0 с.т. (‘I’ колеблется от 1 до n)

Теперь, если мы рассмотрим wᵢ как вектор, а xᵢ как другой вектор, тогда мы можем представить ∑ (wᵢxᵢ) как векторное умножение двух разных векторов, как W ᵀXᵢ, где W ᵢᵀ - это транспонированный вектор значений, представленных с использованием всех wᵢ, а Xᵢ - вектор, представленный с использованием всех значений xᵢ. Кроме того, здесь b - скалярное значение.

Учитывая все это, W ᵢ на самом деле является нормальным для нашей гиперплоскости, тогда только умножение будет иметь смысл. Это означает, что W является нормалью к нашей гиперплоскости, и, как мы все знаем, нормали всегда перпендикулярны нашей поверхности.

Итак, теперь, возвращаясь к нашей модели, после всей этой математики, мы заключаем, что наша модель должна изучить Границу принятия решения, используя 2 важные вещи:

1. Все значения wᵢ.

2. Перехват b.

Для простоты давайте рассмотрим, что наш самолет проходит через исходную точку. Тогда b = 0.

Теперь нашей модели нужно только вычислить значения wᵢ. Это нормальное явление для нашей гиперплоскости. Как было сказано, наша нормаль также является вектором, что означает, что это набор значений, которые необходимо найти, которые лучше всего подходят для наших данных.

Таким образом, основная задача в LR сводится к простой задаче поиска границы решения, которая представляет собой гиперплоскость, которая представлена ​​с помощью (W ᵢ, b) с заданным набором данных (+ ve, - ve) точки, которые лучше всего разделяют точки данных.

Давайте представим, что out W ᵢ являются производными, поэтому даже после этого как мы собираемся узнать, относятся ли они к классу +1 или -1.

Для этого рассмотрим приведенную ниже диаграмму:

Итак, если вы знакомы с основными проблемами машинного обучения, это можно объяснить тем, что по заданному набору Xᵢ мы должны предсказать Yᵢ.

Итак, Yᵢ здесь принадлежит множеству {+1, -1}

Теперь, если мы хотим вычислить значение dᵢ или dⱼ, мы можем сделать это с помощью следующей формулы:

dᵢ = (WᵀXᵢ) / ||W||

где W - вектор нормали к внешней гиперплоскости, для простоты предположим, что это единичный вектор.

Следовательно. || W || = 1

Следовательно, dᵢ = W ᵀXᵢ

Аналогично dⱼ = W ᵀXⱼ

Поскольку W и X находятся на одной стороне гиперплоскости, т.е. на положительной стороне, следовательно (W ᵀXᵢ) ›0 и (W ᵀXⱼ) ‹0

По сути, это означает, что dᵢ принадлежит классу +1, а dⱼ принадлежит классу -1.

И вот как мы можем классифицировать наши точки данных с помощью гиперплоскости.

После этого возникает главный вопрос, что…

В. Как рассчитать нашу гиперплоскость?

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

Давайте рассмотрим несколько случаев yᵢ:

1. Случай 1. Если точка положительная, а мы прогнозируем положительно, то

yᵢ = +1 и W ᵀXᵢ = +1, тогда

yᵢ * (WᵀXᵢ) > 0

2. Случай 2: Если точка отрицательная, а мы прогнозируем как отрицательную, то

yᵢ = -1 и WᵀXᵢ = -1, то

yᵢ * (WᵀXᵢ) > 0

3. Случай 3. Если точка положительная, а мы прогнозируем отрицательную, тогда

yᵢ = +1 и WᵀXᵢ = -1, то

yᵢ * (WᵀXᵢ) < 0

4. Случай 4. Если точка отрицательная, а мы прогнозируем положительную, тогда

yᵢ = -1 и WᵀXᵢ = +1, то

yᵢ * (WᵀXᵢ) < 0

Теперь, если мы внимательно посмотрим, когда мы сделали правильный прогноз, наше уравнение [yᵢ * (WᵀXᵢ)] всегда будет положительным, независимо от нашей мощности точки данных.

Следовательно, наше уравнение оптимизации справедливо как таковое.

(Макс Вт) ∑ [yᵢ * (W ᵀXᵢ)] ›0 с.т. (я = {1, n})

Давайте попробуем понять, что предлагает уравнение, уравнение говорит, что найдите мне W (вектор, нормальный к нашей гиперплоскости), который имеет максимум [yᵢ * (W ᵀXᵢ)] ›0 такой, что значение« i »находится в диапазоне от 1 до n, здесь« n »- это общее количество измерений, которые у нас есть.

Это означает, что для любого Гиперплана, у нас есть максимально правильно спрогнозированные точки, мы выберем именно их.

Возникает еще один вопрос ...

В. Как решить проблему оптимизации, чтобы найти оптимальное значение W, которое имеет максимальную правильно классифицированную точку?

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

Давайте посмотрим, как оптимальное значение W рассчитывается интуитивно:

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

На графике логистическая функция более или менее похожа на S-образную кривую. Учитывая середину = 0

Логистическая функция - это сигмовидная функция.

Поэтому мы используем уравнение оптимизации вместо «t».

t = yᵢ * (WᵀXᵢ) s.t. (i = {1,n} )

И когда мы решаем эту сигмовидную функцию, мы получаем другую задачу оптимизации, которую легко решить с помощью вычислений,

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

W * = (Argmin W) ∑ logn (1 + e⁻ᵗ)

Итак, наше уравнение меняет форму поиска от максимума до минимума, теперь мы можем решить это, поскольку это наше уравнение оптимизации.

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

В нашем случае он пытается найти минимум нашей сигмовидной функции. Возникает вопрос ...

В. Как он минимизирует функцию или как находит минимумы?

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

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

- [y * журнал (yₚ) + (1-y) * журнал (1-yₚ)]

y = фактическое значение класса точки данных

yₚ = прогнозируемое значение класса точки данных

Вот что такое логистическая регрессия, и именно так мы получаем лучшую границу принятия решений для классификации.

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

Код с нуля:

Перед этим давайте повторим несколько ключевых моментов, чтобы код имел для нас больше смысла:

X - это набор точек данных с m строками и n измерениями.

y - это набор классов, которые определяют класс для каждой точки данных от X как +1 или -1

z = WᵀXᵢ

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

b = Набор скаляров члена перехвата, не требуется, если наша гиперплоскость проходит через начало координат

yₚ = прогнозируемое значение Xᵢ из сигмовидной функции.

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

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

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

Теперь перейдем к коду…

Импорт базовых библиотек:

Мы будем использовать так называемый набор данных Iris, который представляет собой набор данных из 3 цветов, но для нашей задачи мы возьмем только 2 класса из них.

X = iris.data [:,: 2]

Эта строка гарантирует, что мы выберем только 2 класса из набора данных, на самом деле набор данных имеет 3 класса. Допустим, у нас есть класс 0 и класс 1.

y = (iris.target! = 0) * 1

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

если class равен 0, тогда false, поэтому False * 1 = 0

если класс не равен нулю, то true, поэтому True * 1 = 1

Если вы все еще не понимаете, вы можете выполнить код без «* 1», тогда вы обязательно поймете.

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

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

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

Внутри нашего класса будет 7 методов, это:

  1. __init__: Это необходимо для инициализации нашего класса и определения необходимых параметров.
  2. __b_intercept: это добавление точки пересечения "b".
  3. __sigmoid_function: это фактическая логистическая функция.
  4. __loss: Это необходимо для расчета наших потерь.
  5. fit: Это для обучения нашей модели.
  6. pred_prob: это предсказывает значение вероятности каждой точки данных, чтобы быть в любом из классов.
  7. предсказать: это фактически предсказать метку класса.

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

Так выглядит весь класс:

class LogisticRegression:
    
    # defining parameters such as learning rate, number ot iterations, whether to include intercept, 
    # and verbose which says whether to print anything or not like, loss etc.
    def __init__(self, learning_rate=0.01, num_iterations=50000, fit_intercept=True, verbose=False):
        self.learning_rate = learning_rate
        self.num_iterations = num_iterations
        self.fit_intercept = fit_intercept
        self.verbose = verbose
    
    # function to define the Incercept value.
    def __b_intercept(self, X):
        # initially we set it as all 1's
        intercept = np.ones((X.shape[0], 1))
        # then we concatinate them to the value of X, we don't add we just append them at the end.
        return np.concatenate((intercept, X), axis=1)
    
    def __sigmoid_function(self, z):
        # this is our actual sigmoid function which predicts our yp
        return 1 / (1 + np.exp(-z))
    
    def __loss(self, yp, y):
        # this is the loss function which we use to minimize the error of our model
        return (-y * np.log(yp) - (1 - y) * np.log(1 - yp)).mean()
    
    # this is the function which trains our model.
    def fit(self, X, y):
        
        # as said if we want our intercept term to be added we use fit_intercept=True
        if self.fit_intercept:
            X = self.__b_intercept(X)
        
        # weights initialization of our Normal Vector, initially we set it to 0, then we learn it eventually
        self.W = np.zeros(X.shape[1])
        
        # this for loop runs for the number of iterations provided
        for i in range(self.num_iterations):
            
            # this is our W * Xi
            z = np.dot(X, self.W)
            
            # this is where we predict the values of Y based on W and Xi
            yp = self.__sigmoid_function(z)
            
            # this is where the gradient is calculated form the error generated by our model
            gradient = np.dot(X.T, (yp - y)) / y.size
            
            # this is where we update our values of W, so that we can use the new values for the next iteration
            self.W -= self.learning_rate * gradient
            
            # this is our new W * Xi
            z = np.dot(X, self.W)
            yp = self.__sigmoid_function(z)
            
            # this is where the loss is calculated
            loss = self.__loss(yp, y)
            
            # as mentioned above if we want to print somehting we use verbose, so if verbose=True then our loss get printed
            if(self.verbose ==True and i % 10000 == 0):
                print(f'loss: {loss} \t')
    
    # this is where we predict the probability values based on out generated W values out of all those iterations.
    def predict_prob(self, X):
        # as said if we want our intercept term to be added we use fit_intercept=True
        if self.fit_intercept:
            X = self.__b_intercept(X)
        
        # this is the final prediction that is generated based on the values learned.
        return self.__sigmoid_function(np.dot(X, self.W))
    
    # this is where we predict the actual values 0 or 1 using round. anything less than 0.5 = 0 or more than 0.5 is 1
    def predict(self, X):
        return self.predict_prob(X).round()

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

model = LogisticRegression(learning_rate=0.1, num_iterations=300000)

Теперь попробуем обучить нашу модель, используя метод fit.

model.fit(X, y)

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

Он дает высшую оценку 1.

Теперь это один из способов реализации LR с нуля, но если вы хотите избавить себя от всей суеты при написании такого кода, вы можете использовать предварительно реализованный класс из библиотеки Scikit Learn.

Реализация логистической регрессии из Scikit-Learn:

Либо вы можете сами выполнить все это кодирование, либо просто импортировать класс из феноменальной библиотеки Python под названием Scikit-Learn и использовать его предварительно реализованную логистическую регрессию.

sklearn_model = linear_model.LogisticRegression()

Он также имеет тот же метод, что и для обучения модели.

sklearn_model.fit(X,y)

Давайте попробуем предугадать и проверим ценность своего предсказания.

Мы получили точность 0,99%.

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

И вот как мы можем реализовать логистическую регрессию с нуля или импортировать Scikit-Learn для использования логистической регрессии.

Спасибо за чтение….