В этой статье мы углубимся в реализацию модели бинарной логистической регрессии с нуля, используя набор данных электронной почты для обнаружения спама. Мы построим модель, используя Python и важные библиотеки, такие как Numpy, Pandas и Scikit-Learn.

Введение в логистическую регрессию

Логистическая регрессия — это фундаментальный метод машинного обучения, который применяется к ситуациям, когда выходная переменная является категориальной. В бинарной логистической регрессии выходная переменная имеет две категории, такие как электронные письма «спам» и «не спам».

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

Понимание набора данных

Используемый здесь набор данных включает электронные письма, каждое из которых помечено как спам (1) или не спам (0). Два основных используемых столбца: Email_text и Ярлыки. Столбец Email_text включает содержимое электронных писем, а Ярлыки — это двоичная метка, указывающая, является ли каждое электронное письмо спамом или нет.

Более подробный EDA (исследовательский анализ данных) вы можете найти здесь, в этой записной книжке.

Предварительная обработка текстовых данных

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

from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import CountVectorizer

Email_dataset = pd.read_csv("spam_ham_dataset.csv")

X = Email_dataset['Email_text']
y = Email_dataset['Labels'].values

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Fit the CountVectorizer on your training data
vectorizer = CountVectorizer()
X_train = vectorizer.fit_transform(X_train).toarray()

# Transform your test data using the same vectorizer
X_test = vectorizer.transform(X_test).toarray()

Реализация бинарной логистической регрессии

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

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

Функция стоимости, которую мы используем, — это функция логарифмических потерь. Эта функция измеряет производительность модели классификации, где входные данные прогнозирования представляют собой значение вероятности от 0 до 1. Целью наших моделей машинного обучения является минимизация этого значения.

def cost_function(h, y):
    return (-y * np.log(h + 1e-10) - (1 - y) * np.log(1 - h + 1e-10)).mean()

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

  • Он вычисляет ошибку для одного обучающего примера.
  • Стоимость рассчитывается как отрицательная сумма фактического значения, умноженного на натуральный логарифм прогнозируемого значения, и разницы фактического значения от единицы, умноженной на натуральный логарифм отличия прогнозируемого значения от единицы.
  • Формула функции стоимости для одного обучающего примера: -y log(h) — (1 — y)log(1 — h).
  • Если фактический класс равен 1 (y = 1), а модель правильно предсказывает 1 (h = 1), то стоимость равна 0.
  • Если фактический класс равен 1 (y = 1), но модель предсказывает 0 (h = 0), то стоимость бесконечна.
  • То же самое применимо, когда фактический класс равен 0 (y = 0).
  • Когда вы вычисляете функцию стоимости для всего тренировочного набора, она называется функцией средней стоимости, и вы берете среднее значение всех отдельных затрат.

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

J(w) = -1/n Σ [y log(h(x)) + (1 — y)log(1 — h(x))]

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

  • h(x): это прогноз, который ваша модель делает для ввода x. Для логистической регрессии это результат применения сигмовидной функции к взвешенной сумме входных признаков.
  • y * log(h(x)): эта часть функции вычисляет потери для положительных примеров (где фактическое значение y равно 1). Если фактическое значение равно 1, а модель предсказывает 1 (h(x) близко к 1), потери равны 0, поскольку log(1) = 0. Но если модель предсказывает 0 (h(x) близко к 0), потеря стремится к бесконечности, потому что log(0) приближается к отрицательной бесконечности, поэтому мы получаем очень большой штраф за ложное отрицательное значение.
  • (1 — y) * log(1 — h(x)): Эта часть функции вычисляет потери для отрицательных примеров (где фактическое значение y равно 0). Если фактическое значение равно 0, а модель предсказывает 0 (h(x) близко к 0), потери равны 0, поскольку log(1) = 0. Но если модель предсказывает 1 (h(x) близко к 1), log (1 — h(x)) приближается к отрицательной бесконечности, поэтому мы получаем большой штраф за ложное срабатывание.
  • -1/n Σ: Наконец, мы суммируем все эти значения потерь для каждого примера в нашем наборе данных и делим на количество примеров, чтобы вычислить среднюю потерю, которая является значением, которое возвращает функция логарифмических потерь.
  • (1e-10) — это очень маленькое число, которое должно препятствовать тому, чтобы значения внутри журнала стали равными 0.

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

Реализовать градиентный спуск

Затем мы реализуем Gradient Descent, алгоритм оптимизации, используемый для минимизации функции стоимости. Градиентный спуск используется при обучении модели машинного обучения. Это алгоритм оптимизации, используемый для минимизации некоторой функции путем итеративного движения в направлении наискорейшего спуска, определяемого отрицательным значением градиента.

def gradient_descent(X, h, y):
    return np.dot(X.T, (h - y)) / y.shape[0]

Обучение модели

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

def update_weight_loss(weight, learning_rate, gradient):
    return weight - learning_rate * gradient
num_iter = 1000
learning_rate = 0.1
weight = np.zeros(X_train.shape[1])

costs = []

for i in range(num_iter):
    z = np.dot(X_train, weight)
    h = sigmoid(z)
    gradient = gradient_descent(X_train, h, y_train)
    weight = update_weight_loss(weight, learning_rate, gradient)
    
    if(i % 100 == 0):
        z = np.dot(X_train, weight)
        h = sigmoid(z)
        cost = cost_function(h, y_train)
        costs.append(cost)
        print(f'cost: {cost} \t')

Визуализация функции стоимости

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

Прогнозирование новых данных

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

final_pred = sigmoid(np.dot(X_test, weight))
y_pred = final_pred.round()
print('Classification Report:')
print(classification_report(y_test, y_pred))

Заключение

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

Полный код вы можете найти здесь.

Это все люди. Да пребудет с тобой сила.