СОДЕРЖАНИЕ

В этом посте мы рассмотрим:

1. Проблема, решаемая с помощью логистической регрессии

2. Функции активации

3. Функция затрат для логистической регрессии

4. Градиентный спуск для логистической регрессии.

5. Необходимость предварительной обработки данных

6. Методы предварительной обработки данных

7. Решение набора данных Titanic в Kaggle с помощью логистической регрессии

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

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

(i) Двоичная классификация

(ii) Мультиклассовая классификация

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

Бинарная классификация

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

Теперь возникает очевидный вопрос, как позволить модели выходных вероятностей. В линейной регрессии мы увидели, что наш результат описывается уравнением y = Wx + b, где y может иметь любые действительные значения. Если мы используем это уравнение в качестве базовой линии, то для преобразования вывода этого уравнения в вероятность нам понадобится функция, которая может вводить любое действительное число и выводить значение от 0 до 1. Функция, которую мы используем для этой цели, называется функцией активации. .

Функции активации

Рассмотрим конечный выход 0 или 1 как переключатель, где 0 - это «выключенное состояние», а 1 - «включенное состояние». Значения вероятности, которые мы вводим в функцию активации, можно рассматривать как значения тока (электричества). Когда ток превышает определенный порог, наш переключатель «активируется» во включенное состояние, в противном случае он остается в выключенном состоянии. Различные функции активации имитируют это поведение, и наиболее распространенной из них является функция активации сигмовидной кишки.

Для двоичной классификации сигмовидная функция вводит результат уравнения y = Wx + b, а затем выводит значение вероятности для каждого класса. В формуле, показанной выше для сигмовидной функции, мы видим, что для x = 0 выходной сигнал сигмоидной функции равен точно 0,5. Таким образом, для всех значений x ›0 включается выходной переключатель, и соответствующие входные данные классифицируются как принадлежащие к категории 1, в противном случае они относятся к категории 0.

Другие примеры функций активации:

(i) Функция гиперболического тангенса (tanh) - tanh (x) определяется как (e ^ xe ^ -x) / (e ^ x + e ^ -x) и имеет значения в диапазоне из (-1, 1).

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

(iii) Функция Softmax - функция активации Softmax хорошо работает с задачей классификации нескольких классов и вычисляет вероятность появления каждого класса. Класс, соответствующий наибольшему значению вероятности, затем считается выходным классом для входных данных.

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

Функция затрат для логистической регрессии

Результатом нашего двоичного классификатора логистической регрессии является композиция двух функций: функции линейной регрессии y = Wx + b и функции активации сигмовидной формы. Если f (x) = Wx + b и S (x) = 1 / (1 + e ^ -x), то выходом двоичного классификатора логистической регрессии будет S (f (x)).

Пока что мы знаем только о функции стоимости евклидова расстояния, которую мы видели в линейной регрессии. Он представлен следующим образом: J = sum_over_all_training_examples ((y_hat_i - y_i) ²), где y_hat_i - это прогнозируемый результат, а y_i - фактический результат для обучающего примера i. Но можем ли мы использовать ту же функцию затрат для логистической регрессии? Ответ - нет, потому что выходные значения в линейной регрессии были реальными значениями, тогда как y_hat_i и y_i в логистической регрессии являются вероятностными и конечным набором дискретных значений соответственно. Например, предположим, что y_i равно 1. Если y_hat_i равно 0,9, потеря для этого обучающего примера составит (1–0,9) ², то есть 0,01. Если y_hat_i равно 0,1, то потеря для этого обучающего примера составляет (1–0,9) ², то есть 0,81. Ясно, что разница между значениями функции потерь невелика, даже несмотря на то, что согласно первому прогнозу мы получаем выходной класс 1, а согласно второму прогнозу мы получаем выходной класс 0. Итак, нам нужно штрафовать вероятность значения, которые приводят к неправильной классификации, гораздо больше, чем значения вероятности, ведущие к правильной классификации. По этой причине мы используем другой вид функции стоимости для логистической регрессии, которая представляет собой бинарную кроссентропию, которая позволит нам решить задачу минимизации параметров W и b.

Бинарная кроссентропия

Чтобы лучше понять, как выбрать эту функцию в качестве функции стоимости для логистической регрессии, давайте разберемся в значении бинарной кроссэнтропии. Информационная энтропия, часто просто энтропия, является базовой величиной в теории информации, связанной с любой случайной величиной, которую можно интерпретировать как средний уровень «информации», «неожиданности» или «неопределенности», присущий возможным результатам переменной. Кросцентропия определяет, насколько близко прогнозируемое распределение вероятностей (энтропия) выходных классов к истинному распределению. Таким образом, двоичная кроссэнтропия как функция стоимости сравнивает вероятностный выход сигмоидной функции с правильной меткой класса (из 2 классов) и вычисляет стоимость. Чем ниже стоимость, тем точнее прогноз.

Мы выбрали бинарную кроссэнтропию в качестве функции стоимости для логистической регрессии, чтобы штрафовать значения вероятности, приводящие к неправильной классификации. Как же помогает использование функции журнала? Что ж, придумать функцию журнала для этой цели - многолетняя работа многих гениальных статистиков, но я могу помочь объяснить это. Давайте посмотрим на крайние случаи. Учтите, что для метки класса 1 (y) наша модель логистической регрессии выводит вероятность 0,9 (y_hat). Тогда потеря, соответствующая этому прогнозу, будет - {1 * log (0.9) + 0 * log (0.1)}, то есть 1.05. Если бы выход логистической регрессии для этого класса был 0,1, то потери для этого обучающего примера были бы - {1 * log (0,1) + 0 * log (0,9)}, то есть 2,30. Если мы сравним эти значения со значениями потерь в функции стоимости евклидова расстояния, мы можем ясно увидеть, что бинарная функция кроссэнтропии больше наказывает неправильные прогнозы. Итак, когда мы выполняем операцию обучения, минимизируя потери двоичной кроссэнтропии за счет градиентного спуска, значения параметров будут обновляться таким образом, чтобы получить более высокую точность, чем функция стоимости евклидова расстояния.

Градиентный спуск для логистической регрессии

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

temp_W = temp_b = random_values
flag_W = flag_b = False
do
  if ((abs(W — temp_W) > delta_W) and flag_W = False)
    temp_W = W — alpha * partial_derivate_wrt_W(J)
  if ((abs(b — temp_b) > delta_b) and flag_b = False)
    temp_b = b — alpha * partial_derivative_wrt_b(J)
  if(temp_W == W)
    flag_W = True
  if(temp_b == b)
    flag_b = True
  W = temp_W
  b = temp_b
while True

Чтобы вычислить частную производную функции стоимости J по параметрам W и b, мы сначала заменяем y_hat в функции затрат на композиционную функцию, используемую для представления выходных данных двоичного классификатора логистической регрессии, как показано выше, а затем вычисляем производные. Полный расчет можно найти в ответе на этот вопрос на сайте math.stackexchange.com. Этот ответ игнорирует параметр смещения b и вычисляет только частную производную J по W, но частную производную J по b можно вычислить аналогичным образом.

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

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

f (x) = W1x1 + W2x2 + W3x3 + ………. + Wnxn

S(x) = 1/(1 + e^-x)

Затем выходные данные для двоичной классификации с функцией нескольких входов могут быть представлены как S (f (x)).

Учтите, что всего существует C классов, помеченных от 0 до C-1. Тогда есть 2 способа решить эту проблему.

(i) Предположим, что в настоящее время мы заинтересованы в прогнозировании метки класса k, затем мы разделяем все данные на 2 класса: класс k, представленный как 1, и все другие классы, представленные как 0, а затем решаем задачу двоичной классификации. Этот процесс можно автоматизировать и для всех остальных классов. Это нормально работает, но является неэффективным методом.

(ii) Мы можем использовать нейронную сеть с C выходами (и 1-2 скрытыми слоями в зависимости от размера и сложности данных) и использовать функцию активации softmax для выходного слоя. Я расскажу об этом больше в серии статей по глубокому обучению.

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

Необходимость предварительной обработки данных

Одна вещь, которая может застать вас врасплох, - это тот факт, что для решения задачи машинного обучения почти 90% времени тратится на предварительную обработку данных. Звучит странно, правда. Я предполагаю, что вы, возможно, проводили опрос хотя бы раз в жизни. Просматривая ответы на вопросы анкеты, мы видим, что не все люди отвечают на все вопросы. Некоторые люди могут записывать числа в виде слов. В ответах на опрос можно увидеть массу вариаций. Точно так же наборы данных реального мира также подготавливаются путем геодезии. Поэтому вполне возможно, что в данном наборе данных могут отсутствовать значения. Есть много ошибок, которые люди могут автоматически разрешить благодаря своему интеллекту, а машина - нет. Кроме того, мы не хотим, чтобы наша модель машинного обучения частично обрабатывала какие-либо функции входных данных, поэтому нам нужно уменьшить все значения до определенного диапазона. Теперь, когда у нас есть интуитивное представление о необходимости предварительной обработки данных, давайте рассмотрим некоторые методы один за другим:

(i) Работа с недостающими данными

Некоторые или все функции данных могут иметь отсутствующие значения, которые при загрузке в виде фрейма данных pandas представлены NaN (не числом). Для каждой входной функции мы сначала проверяем процентное значение NaN в ней. Если процент значений NaN для функции значительно велик, мы можем просто игнорировать эту функцию во время процесса обучения, поскольку ее влияние на результат минимально. Если у нас есть обучающие данные, загруженные в фрейм данных pandas как «train_df», то мы можем проверить общее количество значений NaN для всех функций с помощью следующей строки кода:

train_df.isna().sum()

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

(ii) Заполнение пропущенных значений

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

train_df[“age”].fillna(train_df[“age”].median(skipna=True), inplace=True)

(iii) Кодирование категориальных переменных

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

train_df[‘country’].value_counts()

Это выводит количество экземпляров каждой страны в наборе данных. Предположим, имеется 8 различных стран, тогда один из способов заменить нечисловые значения в столбце страны - присвоить каждой стране номер от 1 до 8. Этот процесс известен как Кодирование метки. В Python операцию кодирования меток можно выполнить следующим образом:

from sklearn.preprocessing import LabelEncoder
labelencoder = LabelEncoder()
train_df[‘country_encoded’] = labelencoder.fit_transform(train_df[‘country’])

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

(iv) Масштабирование функций

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

Если вы зашли так далеко на своем посту, вы должны гордиться собой. Мы подробно изучили каждый из элементов логистической регрессии, узнали о различных функциях активации, а также поняли, почему важна предварительная обработка данных, и рассмотрели некоторые способы предварительной обработки данных. Теперь, чтобы объединить все наши концепции и получить уверенность в вопросах, затронутых в этом посте, давайте решим набор данных Titanic на Kaggle, используя двоичный классификатор логистической регрессии. Эта проблема была частью соревнования, поэтому, даже если мы получим средние результаты, у нас должно быть достаточно места для решения любых проблем логистической регрессии, с которыми мы столкнемся в будущем. Я говорю о средних результатах, потому что есть много методов лучше, чем логистическая регрессия, которые мы увидим в следующих статьях, и они приходят с практикой.

Постановка задачи

Щелкните здесь для набора данных. Приведенные данные имеют 12 характеристик. Набор обучающих данных содержит 891 пример, а данные, по которым мы должны представить прогнозы, содержат 418 примеров. Эти проблемы можно сформулировать как проблему логистической регрессии следующим образом:

Y = сигмовидная (W1x1 + W2x2 + W3x3 + W4x4 + W5x5 + W6x6 + W7x7 + W8x8 + W9x9 + W10x10 + W11x11 + W12x12)

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

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score, classification_report, precision_score, recall_score
from sklearn.metrics import confusion_matrix, precision_recall_curve, roc_curve, auc, log_loss
import matplotlib.pyplot as plt
import os
for dirname, _, filenames in os.walk(‘/kaggle/input’):
  for filename in filenames:
    print(os.path.join(dirname, filename))

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

train_df = pd.read_csv(“/kaggle/input/titanic/train.csv”)
test_df = pd.read_csv(“/kaggle/input/titanic/test.csv”)
train_df.head()

test_df.head()

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

Начнем с проверки недостающих значений.

train_df.isna().sum()

Мы видим, что из 891 записи 177 записей о возрасте отсутствуют, 687 записей о каюте и 2 записи о погрузке отсутствуют, что в процентах может быть представлено как:

print(“Percentage missing age values: “ + str((train_df[‘Age’].isna().sum()/train_df.shape[0])*100))
print(“Percentage missing cabin values: “ + str((train_df[‘Cabin’].isna().sum()/train_df.shape[0])*100))
print(“Percentage missing embarked values: “ + str((train_df[‘Embarked’].isna().sum()/train_df.shape[0])*100))

Мы заметили, что около 80% значений в столбце Cabin отсутствуют, поэтому мы не можем с уверенностью заполнить эти значения. Итак, мы отбрасываем всю колонку "Кабина". Теперь, чтобы увидеть, какие значения мы можем заполнить для возраста, давайте посмотрим, как значения возраста распределяются по набору данных.

age_distribution = dict(train_df[“Age”].value_counts())
lists = sorted(age_distribution.items())
x, y = zip(*lists)
plt.plot(x, y)
plt.show()

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

train_data = train_df.copy()
train_data[“Age”].fillna(train_df[“Age”].median(skipna=True), inplace=True)
train_data[“Embarked”].fillna(train_df[‘Embarked’].value_counts().idxmax(), inplace=True)
train_data.drop(‘Cabin’, axis=1, inplace=True)

Теперь мы видим, что все пропущенные значения исчезли.

train_data.isna().sum()

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

train_data.head()

Нам еще предстоит доработать несколько вещей. Столбцы Name и Ticket нам вообще не нужны, потому что они явно не играют никакой роли в предсказаниях (если только вы не можете заставить свои машины изучать астрологию). Теперь нам нужно закодировать значения категориальных столбцов, то есть Pclass, Sex, Embarked. Несмотря на то, что Pclass имеет целочисленные значения, эти целые числа представляют собой местоположение на «Титанике», на которое пассажир купил билет. Мы используем горячую кодировку для кодирования этих столбцов.

training=pd.get_dummies(train_data, columns=[“Pclass”,”Embarked”,”Sex”])
training.drop(‘Sex_female’, axis=1, inplace=True)
training.drop(‘PassengerId’, axis=1, inplace=True)
training.drop(‘Name’, axis=1, inplace=True)
training.drop(‘Ticket’, axis=1, inplace=True)
final_train = training
final_train.head()

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

test_df.isna().sum()

test_data = test_df.copy()
test_data[“Age”].fillna(train_df[“Age”].median(skipna=True), inplace=True)
test_data[“Fare”].fillna(train_df[“Fare”].median(skipna=True), inplace=True)
test_data.drop(‘Cabin’, axis=1, inplace=True)
testing = pd.get_dummies(test_data, columns=[“Pclass”,”Embarked”,”Sex”])
testing.drop(‘Sex_female’, axis=1, inplace=True)
testing.drop(‘PassengerId’, axis=1, inplace=True)
testing.drop(‘Name’, axis=1, inplace=True)
testing.drop(‘Ticket’, axis=1, inplace=True)
final_test = testing
final_test.head()

Теперь для такого небольшого набора данных у нас осталось слишком много функций. В предыдущем посте мы использовали только 8 функций, поэтому давайте попробуем выбрать 8 основных функций. В этом нам помогает ранжирование функций с помощью рекурсивного исключения признаков (RFE). Он доступен как функция в учебном пособии по обучению. Подробнее о RFE можно прочитать здесь. Но для сходимости этой рекурсивной операции нам необходимо уменьшить масштаб значений в обучающих данных, поскольку RFE не применяет саму операцию масштабирования, что может привести к неправильному выбору функций.

sc = StandardScaler()
final_train[[“Age”, “Fare”]] = sc.fit_transform(final_train[[“Age”, “Fare”]])
final_test[[“Age”, “Fare”]] = sc.fit_transform(final_test[[“Age”, “Fare”]])
final_train.head()

from sklearn.linear_model import LogisticRegression
from sklearn.feature_selection import RFE
cols = [“Age”, “SibSp”, “Parch”, “Fare”, “Pclass_1”, “Pclass_2”, “Pclass_3”, “Embarked_C”, “Embarked_Q”, “Embarked_S”, “Sex_male”]
X = final_train[cols]
y = final_train[‘Survived’]
model = LogisticRegression()
# selecting top 8 features
rfe = RFE(model, n_features_to_select = 8)
rfe = rfe.fit(X, y)
print(‘Top 8 most important features: ‘ + str(list(X.columns[rfe.support_])))

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

selected_features = [‘Age’, ‘SibSp’, ‘Pclass_1’, ‘Pclass_3’, ‘Embarked_C’, ‘Embarked_Q’, ‘Embarked_S’, ‘Sex_male’]
X = final_train[selected_features]\
y = final_train[‘Survived’]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.15, random_state=2)
logreg = LogisticRegression()
logreg.fit(X_train, y_train)
y_pred = logreg.predict(X_test)
y_pred_proba = logreg.predict_proba(X_test)[:, 1]
print(‘Train/Test split results:’)
print(“Logistic Regression accuracy is: “ + str(accuracy_score(y_test, y_pred)))
print(“Logistic Regression log_loss is: “ + str(log_loss(y_test, y_pred_proba)))

Мы получаем точность около 78%, что неплохо. Когда я отправил свой файл отправки на Kaggle, предсказав значения Survived в предоставленном ими тестовом наборе данных, я получил общедоступную оценку 0,7703, что хорошо, учитывая тот факт, что мы не применили ничего, кроме логистической регрессии. То, как мы обрабатывали данные, помогло нам получить этот балл. Без предварительной обработки данных наши результаты были бы намного хуже. Полный код этого поста можно найти здесь.

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