Проблема с обучением линейного алгоритма бинарной классификации образов с помощью градиентного алгоритма
Теория и результаты работы программы представлены на скриншотах.
Вопрос: правильно ли это реализовано в коде ниже и почему не отображается разделяющая линия?
import numpy as np
import matplotlib.pyplot as plt
# Данные
data_x = [
(5.3, 2.3), (5.7, 2.5), (4.0, 1.0), (5.6, 2.4), (4.5, 1.5),
(5.4, 2.3), (4.8, 1.8), (4.5, 1.5), (5.1, 1.5), (6.1, 2.3),
(5.1, 1.9), (4.0, 1.2), (5.2, 2.0), (3.9, 1.4), (4.2, 1.2),
(4.7, 1.5), (4.8, 1.8), (3.6, 1.3), (4.6, 1.4), (4.5, 1.7),
(3.0, 1.1), (4.3, 1.3), (4.5, 1.3), (5.5, 2.1), (3.5, 1.0),
(5.6, 2.2), (4.2, 1.5), (5.8, 1.8), (5.5, 1.8), (5.7, 2.3),
(6.4, 2.0), (5.0, 1.7), (6.7, 2.0), (4.0, 1.3), (4.4, 1.4),
(4.5, 1.5), (5.6, 2.4), (5.8, 1.6), (4.6, 1.3), (4.1, 1.3),
(5.1, 2.3), (5.2, 2.3), (5.6, 1.4), (5.1, 1.8), (4.9, 1.5),
(6.7, 2.2), (4.4, 1.3), (3.9, 1.1), (6.3, 1.8), (6.0, 1.8),
(4.5, 1.6), (6.6, 2.1), (4.1, 1.3), (4.5, 1.5), (6.1, 2.5),
(4.1, 1.0), (4.4, 1.2), (5.4, 2.1), (5.0, 1.5), (5.0, 2.0),
(4.9, 1.5), (5.9, 2.1), (4.3, 1.3), (4.0, 1.3), (4.9, 2.0),
(4.9, 1.8), (4.0, 1.3), (5.5, 1.8), (3.7, 1.0), (6.9, 2.3),
(5.7, 2.1), (5.3, 1.9), (4.4, 1.4), (5.6, 1.8), (3.3, 1.0),
(4.8, 1.8), (6.0, 2.5), (5.9, 2.3), (4.9, 1.8), (3.3, 1.0),
(3.9, 1.2), (5.6, 2.1), (5.8, 2.2), (3.8, 1.1), (3.5, 1.0),
(4.5, 1.5), (5.1, 1.9), (4.7, 1.4), (5.1, 1.6), (5.1, 2.0),
(4.8, 1.4), (5.0, 1.9), (5.1, 2.4), (4.6, 1.5), (6.1, 1.9),
(4.7, 1.6), (4.7, 1.4), (4.7, 1.2), (4.2, 1.3), (4.2, 1.3)
]
data_y = [
1, 1, -1, 1, -1, 1, 1, -1, 1, 1,
1, -1, 1, -1, -1, -1, -1, -1, -1, 1,
-1, -1, -1, 1, -1, 1, -1, 1, 1, 1,
1, -1, 1, -1, -1, -1, 1, 1, -1, -1,
1, 1, 1, 1, -1, 1, -1, -1, 1, 1,
-1, 1, -1, -1, 1, -1, -1, 1, 1, 1,
-1, 1, -1, -1, 1, 1, -1, 1, -1, 1,
1, 1, -1, 1, -1, 1, 1, 1, 1, -1,
-1, 1, 1, -1, -1, -1, 1, -1, -1, 1,
-1, 1, 1, -1, 1, -1, -1, -1, -1, -1
]
# Конвертация данных в numpy массивы
X = np.array(data_x)
y = np.array(data_y)
# Параметры градиентного спуска
alpha_w0 = 0.1 # Шаг для w0
alpha_w1_w2 = 0.01 # Шаг для w1 и w2
num_iterations = 1000 # Количество итераций
# Инициализация весов
weights = np.array([0.0, 0.0, 1.0]) # w = [ω0, ω1, ω2]
# Добавление столбца единиц для w0
X_b = np.c_[np.ones(X.shape[0]), X]
def loss_function(M):
return np.exp(-M)
def gradient(weights, X, y):
M = y * (X @ weights)
loss_grad = -np.exp(-M)[:, np.newaxis] * X
return np.mean(loss_grad, axis=0)
# Градиентный спуск
for _ in range(num_iterations):
grad = gradient(weights, X_b, y)
weights[0] -= alpha_w0 * grad[0] # ω0
weights[1] -= alpha_w1_w2 * grad[1] # ω1
weights[2] -= alpha_w1_w2 * grad[2] # ω2
# Получение разделяющей линии
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
x_values = np.linspace(x_min, x_max, 100) # 100 точек для построения линии
# Проверяем weight[2] на близость к нулю перед расчетом y_line
if np.isclose(weights[2], 0): # Если вес почти 0
weights[2] += 1e-10 # Добавить маленькое значение, чтобы избежать деления на ноль
# Пересчитываем y_line корректно
y_line = -(weights[1] * x_values + weights[0]) / weights[2]
# Визуализация результатов
plt.figure(figsize=(10, 6))
plt.scatter(X[y == 1][:, 0], X[y == 1][:, 1], label='Класс 1', color='blue', marker='o')
plt.scatter(X[y == -1][:, 0], X[y == -1][:, 1], label='Класс -1', color='red', marker='x')
# Отображаем разделяющую линию
plt.plot(x_values, y_line, color='green', label='Разделяющая линия', linewidth=2)
# Настройки графика
plt.xlabel('Признак 1')
plt.ylabel('Признак 2')
plt.title('Результаты обучения линейной модели')
plt.axhline(0, color='black', linewidth=0.5, linestyle='--') # Горизонтальная линия для оси Y
plt.axvline(0, color='black', linewidth=0.5, linestyle='--') # Вертикальная линия для оси X
plt.xlim(x_min, x_max)
plt.ylim(X[:, 1].min() - 1, X[:, 1].max() + 1) # Ограничиваем по оси Y
plt.legend()
plt.grid()
plt.show()
Ответы (1 шт):
Автор решения: Сергиенко Артём
→ Ссылка
Во-первых, я немного изменил определение функции потерь:
def loss_function(M):
# Убедимся, что M не слишком велико, чтобы избежать переполнения
return np.clip(np.exp(-M), 1e-10, None)
Во-вторых, исправил расчёт производной функции потерь – там действительно не участвовал y в соответствии с формулой:
def gradient(weights, X, y):
M = y * (X @ weights)
loss_grad = -loss_function(M)[:, np.newaxis] * (y[:, np.newaxis] * X) # Используем y для градиента
return np.mean(loss_grad, axis=0)
Добавил проверку на "взрыв градиентов":
# Проверка на "взрыв" градиентов и нормализация весов
if np.any(np.isnan(weights)) or np.any(np.isinf(weights)):
print("Градиенты взорвались, завершение программы.")
break
Исправил расчёт переменной y_line:
# С безопасным делением, если weights[2] равно 0
if weights[2] != 0:
y_line = -(weights[1] * x_values + weights[0]) / weights[2]
else:
y_line = np.zeros_like(x_values) # Если w2 = 0, выдаем горизонтальную линию y=0
В итоге разделяющая линия сформировалась:





