Проблема с обучением линейного алгоритма бинарной классификации образов с помощью градиентного алгоритма

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

Вопрос: правильно ли это реализовано в коде ниже и почему не отображается разделяющая линия?

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

В итоге разделяющая линия сформировалась:

введите сюда описание изображения

→ Ссылка