Загрузка данных


import numpy as np
import matplotlib.pyplot as plt


# Исходные данные из урока
X = np.array([
    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
    [10, 5, 3, 2, 0, 9, 1, 0, 8, 2]
]).T

y = np.array([45, 55, 50, 55, 65, 35, 75, 80, 50, 60])


# ==================================================
# ЗАДАНИЕ 1
# ==================================================

def gradient_descent(X, y, eta=1e-2, iterations=10000):
    n = X.shape[0]
    m = X.shape[1]

    w = np.zeros(m)

    for i in range(iterations):
        y_pred = np.dot(X, w)

        grad = 2 / n * np.dot(X.T, (y_pred - y))

        w = w - eta * grad

    return w


w_gd = gradient_descent(
    X,
    y,
    eta=1e-2,
    iterations=10000
)

# Веса, полученные методом наименьших квадратов
w_ols = np.linalg.inv(X.T @ X) @ X.T @ y

print("ЗАДАНИЕ 1")
print("Веса градиентного спуска:", w_gd)
print("Веса МНК:", w_ols)


# ==================================================
# ЗАДАНИЕ 2
# ==================================================

def gradient_descent_tol(X, y, eta=1e-2, tol=1e-4):
    n = X.shape[0]
    m = X.shape[1]

    w = np.zeros(m)

    error_prev = np.mean((np.dot(X, w) - y) ** 2)

    iterations = 0

    while True:
        y_pred = np.dot(X, w)

        grad = 2 / n * np.dot(X.T, (y_pred - y))

        w = w - eta * grad

        error = np.mean((np.dot(X, w) - y) ** 2)

        iterations += 1

        if abs(error_prev - error) < tol:
            break

        error_prev = error

    return w, iterations, error


w_tol, iterations_tol, error_tol = gradient_descent_tol(
    X,
    y,
    eta=1e-2,
    tol=1e-4
)

print("\nЗАДАНИЕ 2")
print("Веса:", w_tol)
print("Количество итераций:", iterations_tol)
print("MSE:", error_tol)


# ==================================================
# ЗАДАНИЕ 3
# ==================================================

def gradient_descent_history(X, y, eta, iterations):
    n = X.shape[0]
    m = X.shape[1]

    w = np.zeros(m)

    errors = []

    for i in range(iterations):
        y_pred = np.dot(X, w)

        grad = 2 / n * np.dot(X.T, (y_pred - y))

        w = w - eta * grad

        error = np.mean((np.dot(X, w) - y) ** 2)

        errors.append(error)

    return w, errors


etas = [0.001, 0.005, 0.01, 0.02]

iterations = 1000

plt.figure(figsize=(10, 6))

for eta in etas:
    w, errors = gradient_descent_history(
        X,
        y,
        eta,
        iterations
    )

    plt.plot(
        range(1, iterations + 1),
        errors,
        label=f'eta = {eta}'
    )

    print(
        f"eta = {eta}, "
        f"веса = {w}, "
        f"MSE = {errors[-1]}"
    )

plt.xlabel("Количество итераций")
plt.ylabel("MSE")
plt.title("Зависимость ошибки от количества итераций")
plt.legend()
plt.grid()
plt.show()


# ==================================================
# ЗАДАНИЕ 4
# ==================================================

print("\nЗАДАНИЕ 4")
print("""
Вывод:

1. При увеличении количества итераций ошибка уменьшается,
пока алгоритм не приблизится к минимуму.

2. Чем выше скорость обучения eta, тем быстрее алгоритм
может приблизиться к минимуму.

3. Слишком маленькая eta приводит к медленному обучению.

4. Слишком большая eta может привести к тому, что алгоритм
будет перескакивать через минимум, а ошибка может начать
колебаться или увеличиваться.

5. Скорость обучения и количество итераций связаны:
при маленькой eta требуется больше итераций.

6. Ускорить вычисление весов можно правильным подбором eta:
выбрать достаточно большую скорость обучения, при которой
алгоритм остается стабильным, а также использовать условие
остановки tol вместо лишних итераций.
""")