Вопрос или проблема
Я пытаюсь самостоятельно реализовать алгоритм логистической регрессии для самообучения, но у меня возникают некоторые проблемы с достижением аналогичной точности, как у логистической регрессии из sklearn.
Вот код, который я использую (набор данных, который я использую, это набор данных ‘training.csv’ по Титаник из Kaggle, который вы можете скачать здесь, если хотите протестировать это самостоятельно).
import numpy as np
import random
import matplotlib.pyplot as plt
#%matplotlib inline
def cost(X, Y, W):
"""
x = матрица признаков
y = вектор истинных меток
w = вектор весов
"""
m = len(Y)
if isinstance(Y, list):
Y = np.array(Y)
return -(1/m) * np.sum([Y*np.log(sigmoid(W, X)), (1-Y)*np.log(1-sigmoid(W, X))])
def sigmoid(w, x):
"""Гипотетическая функция логистической регрессии.
w = вектор весов
x = вектор признаков
"""
z = np.dot(w.T, x)
return 1 / (1 + np.exp(-z))
def grad_descent(A, w, y,
lr = 0.01,
stochastic = False,
max_iter = 1000,
mute = True,
plot = True):
"""
A = дизайн матрица
w = вектор весов
y = истинная метка
lr = скорость обучения
stochastic = использовать ли стохастический градиентный спуск
max_iter = максимальное количество эпох для обучения
mute = выводить ли текущую эпоху на экран
plot = строить ли график функции потерь после завершения обучения
"""
if not isinstance(A, np.ndarray):
m = "A должно быть numpy массивом, получено %s"
raise TypeError(m % type(A).__name__)
if isinstance(y, list):
y = np.array(y)
y = y.T
y = np.expand_dims(y, axis = 1)
if isinstance(w, list):
w = np.array(w)
# Сделать w столбцовым вектором
w.shape = (A.shape[1], 1)
losses = []
i = 0
while i < max_iter:
old_weights = w
# создать/обновить вектор alpha
alpha = [sigmoid(w, A[i, :]) for i in range(A.shape[0])]
if not mute:
print("Эпоха %d" % (i+1))
if stochastic:
# стохастический градиентный спуск выбирает точку обучения случайным образом
# здесь мы выбираем случайную строку из матрицы A
rand = random.randint(0, A.shape[0]-1)
# выбираем случайные значения
temp_A = A[rand].T
temp_A = temp_A.reshape(A.shape[1], 1)
temp_b = alpha[rand] - y[rand]
# Расчет градиента
grad = np.dot(temp_A, (temp_b))
# Обновление весов
w = (w.T - (lr * grad)).T
# выполнить пакетный градиентный спуск
else:
# количество образцов
m = len(y)
# Расчет градиента
grad = (1/m) * np.dot(A.T, (alpha - y))
# Обновление весов
w = w - (lr * grad)
if i != 0:
# если потеря начинает увеличиваться, тогда остановись
if cost(A.T, y, w) > losses[-1]:
print("Остановка на эпохе %d" % i)
if plot:
print('Потери')
plt.plot(losses)
return old_weights
break
# Отслеживание значения функции потерь
losses.append(cost(A.T, y, w))
# увеличиваем счетчик эпох
i += 1
print("Остановка на эпохе %d" % i)
if plot:
print('Потери')
plt.plot(losses)
return w
#############################################################################
#############################################################################
#############################################################################
if __name__ == "__main__":
import pandas as pd
train = pd.read_csv(r'C:\Users\LENOVO\Documents\Self_Study\titanic\train.csv')
# преобразуем столбец Sex в нули и единицы
train['Sex'] = train['Sex'].map({'female': 1, 'male': 0})
# В столбце fare есть нулевые значения, заменим их на более вероятное значение
rows = np.where(train.Fare == np.min(train.Fare))
# присваиваем среднее значение стоимости для данного класса
class_ = train.iloc[rows[0], 2].values
for clas, row in zip(class_, rows[0]):
# находим среднее
Pclass = train.loc[(train['Pclass'] == clas)]
c_mean = np.mean(Pclass['Fare'])
# присваиваем значение правильной строке
train.iloc[row, 9] = c_mean
train.head()
# задаем скорость обучения
lr = 0.01
sexes = train.Sex
fares = train.Fare
# масштабируем значение fare, деля на максимальное значение, чтобы получить диапазон от 0 до 1
fares = fares/np.max(fares)
# помещаем в матричный формат
A = np.array([[1, s, f] for s, f in zip(sexes, fares)])
# создаем начальные веса
w = [0, 0, 0]
# получаем истинные метки
y = list(train.Survived)
# обучаем модель
weights = grad_descent(A, w, y, lr = 0.01,
stochastic = False)
# Давайте используем эти веса, чтобы сделать предсказания на обучающих данных и увидеть, как это выглядит
def classification(weights, features):
prob = sigmoid(weights, features)
if prob > .5:
return 1
else:
return 0
correct = 0
for i, row in train.iterrows():
fare = row['Fare']
sex = row['Sex']
A = np.array([[1, sex, fare]])
A.shape = (1, 3)
pred = classification(weights, A[0,:])
if row['Survived'] == pred:
correct += 1
print(correct/len(train.index))
В итоге я получаю около 65% точности, в то время как используя sklearn, я могу добиться 78% точности. Я понимаю, что алгоритм sklearn, вероятно, гораздо более сложен, чем мой, но я надеялся, что смогу хотя бы приблизиться к нему (может быть, в районе 70%). Есть какие-то советы?
Вы используете (стохастический) градиентный спуск. Чтобы он работал правильно, скорость обучения (размер шага) должна быть установлена правильно. Я предполагаю, что ошибка заключается именно в этом.
Вместо этого вы можете попробовать логистическую регрессию через IRLS (см. ее определение), сравните также IRLS и GD
Или для введенных вами данных вы только что нашли плохой локальный оптимум.
Ответ или решение
Ваша реализация логистической регрессии вполне неплохая, однако, как вы заметили, существует несколько аспектов, которые могут существенно повлиять на точность модели. Рассмотрим некоторые из этих аспектов, а затем предложим пути для улучшения.
1. Проблемы с настройкой гиперпараметров
Параметры, такие как скорость обучения (learning rate), играют ключевую роль в обучении модели. Неправильная настройка скорости обучения может привести к плохой сходимости или застреванию в локальных минимумах. Попробуйте изменить величину скорости обучения:
- Маленькая скорость обучения: приводит к медленному обучению и потенциальной потере важной информации из-за недостаточного обновления весов.
- Слишком большая скорость обучения: может привести к дивергенции, когда ваши веса начинают «перепрыгивать» через минимумы функции потерь.
2. Инициализация весов
Начальное значение весов может существенно влиять на результат. Если вы инициализируете веса нулями, алгоритм может перестать обучаться, так как градиенты будут одинаковыми для всех признаков. Вместо этого, попробуйте инициализировать веса случайными небольшими значениями.
w = np.random.randn(A.shape[1], 1) * 0.01
3. Функция потерь
Убедитесь, что ваша функция потерь корректно вычисляет потери. В вашем коде есть потенциальные проблемы с обращением к логарифму. Чтобы избежать ошибок переполнения, вы можете обернуть логарифмы в np.clip()
:
epsilon = 1e-15
preds = sigmoid(W, X)
preds = np.clip(preds, epsilon, 1 - epsilon) # Защита от переполнения
return -(1/m) * np.sum(Y*np.log(preds) + (1-Y)*np.log(1-preds))
4. Стандартизация/нормализация данных
Нормализация или стандартизация признаков может помочь ускорить процесс сходимости. Обычно, когда данные не имеют одинакового масштаба, это может негативно сказаться на обучении.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
A[:, 1:] = scaler.fit_transform(A[:, 1:])
5. Использование IRLS
Как было упомянуто в ответе, алгоритм IRLS (Iteratively Reweighted Least Squares) может предоставить более надежную сходимость и быть менее чувствительным к выбору начальных значений по сравнению с градиентным спуском. Он часто применяется для логистической регрессии и может быть более эффективным в вашей ситуации.
6. Увеличение количества признаков
Вы можете улучшить вашу модель, добавив дополнительные признаки. Например, использование информации о классе/возрасте пассажиров и других доступных признаков в наборе данных. Обратите внимание, что этот этап требует предварительной обработки данных, такой как обработка пропусков или категориальных переменных.
7. Использование регуляризации
Регуляризация может помочь избежать переобучения и сделать модель более обобщающей. Попробуйте добавить L1 или L2 регуляризацию к вашей функции потерь.
Заключение
Работа с машинным обучением может быть сложной, и чтобы достичь точности, сопоставимой с sklearn
, потребуется время и эксперименты. Применение предложенных рекомендаций поможет вам улучшить вашу реализацию логистической регрессии и, вероятно, повысит точность модели. Не забывайте также прослеживать промахи и указывать их в коде, чтобы идентифицировать места для улучшения. Удачи в вашем самообучении!