Введение в анализ данных, ИАД-1

НИУ ВШЭ, 2018-19 учебный год

Домашнее задание №2

Задание выполнил(а): Подчезерцев Алексей Евгеньевич

Общая информация

Дата выдачи: 25.02.2019

Дедлайн: 23:59 5.03.2019

О задании

В данном домашнем задании вы реализуете линейную регрессию своими руками и сравните её с версией в scikit-learn.

Оценивание и штрафы

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

Внимание! Домашнее задание выполняется самостоятельно. «Похожие» решения считаются плагиатом и все задействованные студенты (в том числе те, у кого списали) не могут получить за него больше 0 баллов.

Формат сдачи

Стирать условия нельзя!

Загрузка файлов с решениями происходит в системе Anytask.

Формат названия файла: homework_02_Фамилия_Имя.ipynb

Часть 1. Обыкновенная линейная регрессия

In [1]:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

%matplotlib inline

1 (4 балла).

На семинаре мы начали реализовывать класс линейной регрессии; теперь необходимо его закончить.

In [2]:
class LinReg():
    def __init__(self, step_l=0.017, step_p=1.3, step_s=29, num_steps=100, eps=1e-6):
        self.w = None # веса
        self.w_curr = None
        self.shape = None
        self.num_steps = num_steps
        self.eps = eps
        self.step_l = step_l
        self.step_p = step_p
        self.step_s = step_s
    
    def _calc_grad(self, X_train, y_train):
        return 2 * np.dot(X_train.T, np.dot(X_train, self.w) - y_train) / self.shape[1]
    
    def _calc_step(self, step):
        return self.step_l*(self.step_s/(self.step_s+step))**self.step_p
    
    def _calc_weight(self, X_train, y_train, step):
        return self.w - self._calc_step(step) * self._calc_grad(X_train, y_train)
    
    def fit(self, X_train, y_train):
        # self.w = np.random.random(self.count)
        self.shape = X_train.shape
        self.w = np.zeros(self.shape[1])
        for step in range(self.num_steps):
            self.w_curr = self._calc_weight(X_train, y_train, step)
            if np.linalg.norm(self.w_curr - self.w) < self.eps:
                break
            self.w = self.w_curr
            
        return self
        
    def predict(self, X_test):
        return np.dot(X_test, self.w)

2 (3 балла).

Проверим корректность работы класса на датасете Boston Housing.

  • Загрузите его из sklearn
  • Проведите предобработку данных(нормализация вещественных признаков, one-hot encoding категориальных)
  • Разделите на обучение и контроль в соотношении 80:20 с random_seed 42 (самый популярный в прошлом задании)
  • Обучите собственную реализацию регрессии и сравните по метрикам MAE и RMSE с LinearRegression из sklearn.
In [3]:
from sklearn.datasets import load_boston
dataset = load_boston()
X = pd.DataFrame(dataset['data'], columns=dataset['feature_names'])
y = dataset['target']
X["MEDV"] = y
X["ONES"] = 1.0
def norm(data, col):
    data[col] = (data[col] - data[col].mean())/data[col].std()
In [4]:
X.head()
Out[4]:
CRIM ZN INDUS CHAS NOX RM AGE DIS RAD TAX PTRATIO B LSTAT MEDV ONES
0 0.00632 18.0 2.31 0.0 0.538 6.575 65.2 4.0900 1.0 296.0 15.3 396.90 4.98 24.0 1.0
1 0.02731 0.0 7.07 0.0 0.469 6.421 78.9 4.9671 2.0 242.0 17.8 396.90 9.14 21.6 1.0
2 0.02729 0.0 7.07 0.0 0.469 7.185 61.1 4.9671 2.0 242.0 17.8 392.83 4.03 34.7 1.0
3 0.03237 0.0 2.18 0.0 0.458 6.998 45.8 6.0622 3.0 222.0 18.7 394.63 2.94 33.4 1.0
4 0.06905 0.0 2.18 0.0 0.458 7.147 54.2 6.0622 3.0 222.0 18.7 396.90 5.33 36.2 1.0
In [5]:
norm(X, "CRIM")
norm(X, "ZN")
norm(X, "INDUS")
norm(X, "NOX")
norm(X, "RM")
norm(X, "AGE")
norm(X, "DIS")
norm(X, "RAD")
norm(X, "TAX")
norm(X, "PTRATIO")
norm(X, "B")
norm(X, "LSTAT")
norm(X, "MEDV")
y = X["MEDV"]
del X["MEDV"]
X.head()
Out[5]:
CRIM ZN INDUS CHAS NOX RM AGE DIS RAD TAX PTRATIO B LSTAT ONES
0 -0.419367 0.284548 -1.286636 0.0 -0.144075 0.413263 -0.119895 0.140075 -0.981871 -0.665949 -1.457558 0.440616 -1.074499 1.0
1 -0.416927 -0.487240 -0.592794 0.0 -0.739530 0.194082 0.366803 0.556609 -0.867024 -0.986353 -0.302794 0.440616 -0.491953 1.0
2 -0.416929 -0.487240 -0.592794 0.0 -0.739530 1.281446 -0.265549 0.556609 -0.867024 -0.986353 -0.302794 0.396035 -1.207532 1.0
3 -0.416338 -0.487240 -1.305586 0.0 -0.834458 1.015298 -0.809088 1.076671 -0.752178 -1.105022 0.112920 0.415751 -1.360171 1.0
4 -0.412074 -0.487240 -1.305586 0.0 -0.834458 1.227362 -0.510674 1.076671 -0.752178 -1.105022 0.112920 0.440616 -1.025487 1.0
In [6]:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
In [7]:
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, mean_absolute_error
def RMSE(test, predict):
    return np.sqrt(mean_squared_error(test, predict))
In [8]:
lr = LinearRegression()
lr.fit(X_train,y_train)
predict = lr.predict(X_test)
print('RMSE: %f' % RMSE(y_test, predict))
print('MAE: %f' % mean_absolute_error(y_test, predict))
sk_w = lr.coef_
sk_w
RMSE: 0.535886
MAE: 0.346750
Out[8]:
array([-0.10573498,  0.07635554,  0.030121  ,  0.30275162, -0.21674227,
        0.33910693, -0.01927079, -0.33149399,  0.24845253, -0.19510257,
       -0.21549315,  0.12260527, -0.39487863,  0.        ])
In [9]:
lr = LinReg(0.014,1,29)
lr.fit(X_train,y_train)
predict = lr.predict(X_test)
print('RMSE: %f' % RMSE(y_test, predict))
print('MAE: %f' % mean_absolute_error(y_test, predict))
my_w = lr.w
my_w
RMSE: 0.537063
MAE: 0.347506
Out[9]:
array([-0.10477388,  0.07326455,  0.02501959,  0.27951034, -0.21414884,
        0.34114636, -0.01952812, -0.33046442,  0.23011609, -0.17480135,
       -0.21540379,  0.1230579 , -0.39532153, -0.02445493])
In [10]:
print("Compate weights")
print('RMSE: %f' % RMSE(sk_w, my_w))
print('MAE: %f' % mean_absolute_error(sk_w, my_w))
Compate weights
RMSE: 0.011758
MAE: 0.007314

Величины RMSE и MAE практически совпали для выборок.

Метрики для весов отличаются на сотые доли

3 (3 балла).

Линейная регрессия зачастую легко переобучается - модель необходимо штрафовать за величину весов; для этого применяют L1 и L2 регуляризацию: добавление нормы весов к функции потерь. В случае L2-регулязации функционал будет выглядеть как

$$ L = (Xw - y)^T(Xw - y) + \lambda||w||_2 $$

.

Параметр $\lambda$ подбирается на отложенной выборке или по кросс-валидации.

  • Реализуйте обучение линейной регрессии с L2-регуляризацией
  • Найдите оптимальный с точки зрения метрики MAE коэффициент $\lambda$ (по кросс-валидации)
  • Постройте график зависимости метрики на тестовой выборке от $\lambda$ (подпишите оси)
  • Сравните результаты с Ridge регрессией из sklearn (аналогично пункту 2).
In [11]:
class RegL2(LinReg):
    def __init__(self, lamb, step_l=0.017, step_p=1.3, step_s=29, num_steps=100, eps=1e-6):
        LinReg.__init__(self, step_l, step_p, step_s, num_steps, eps)
        self.lamb = lamb

    def _calc_grad(self, X_train, y_train):
        return 2 * np.dot(X_train.T, np.dot(X_train, self.w) - y_train) / self.shape[1] + 2 * self.lamb * self.w
In [12]:
from sklearn.linear_model import Ridge
from sklearn.model_selection import KFold
In [13]:
def iterator(start, end, step):
    i = start
    while i <= end:
        yield i
        i += step
In [14]:
for n in range(2,11):
    plt_dataX = []
    plt_dataY = []
    kf = KFold(n_splits=n,shuffle=True, random_state=42)
    for i in iterator(0, 6, 0.1):
        plt_dataX.append(i)
        err = 0
        for train_index, test_index in kf.split(X):
            X_train, X_test = X.values[train_index], X.values[test_index]
            y_train, y_test = y[train_index], y[test_index]
            lr_l2 = RegL2(i)
            lr_l2.fit(X_train,y_train)
            err += mean_absolute_error(y_test, lr_l2.predict(X_test))
        plt_dataY.append(err / kf.get_n_splits())
    plt.figure(figsize=(20,10))
    plt.grid(True)
    plt.title('Качество модели от коэффициента гамма, N=' + str(n))
    plt.xlabel('Гамма')
    plt.ylabel('MAE')
    plt.scatter(plt_dataX, plt_dataY)
    plt.show()
    print("Best lambda value:", plt_dataX[plt_dataY.index(min(plt_dataY))], "MAE:", min(plt_dataY))
Best lambda value: 1.9000000000000006 MAE: 0.3616999360296699
Best lambda value: 2.2000000000000006 MAE: 0.3615512016290568
Best lambda value: 2.3000000000000007 MAE: 0.36227171079610115
Best lambda value: 2.0000000000000004 MAE: 0.36243345542996297
Best lambda value: 1.8000000000000005 MAE: 0.36287018511356134
Best lambda value: 0.8999999999999999 MAE: 0.36322313490166275
Best lambda value: 4.6 MAE: 0.3686324092905358
Best lambda value: 1.7000000000000004 MAE: 0.4117466732032367
Best lambda value: 3.2000000000000015 MAE: 0.5831974133904017

Начиная с некоторого значения разбиения N стабильность зависимости MAE от гаммы падает

Разобьем выборку на $\sqrt{size(X)}$ частей и возьмем данные в качестве основной

In [15]:
kf = KFold(n_splits=round(X.shape[1]**0.5),shuffle=True, random_state=42)
plt_dataX = []
plt_dataY = []
for i in iterator(0, 10, 0.1):
    plt_dataX.append(i)
    err = 0
    for train_index, test_index in kf.split(X):
        X_train, X_test = X.values[train_index], X.values[test_index]
        y_train, y_test = y[train_index], y[test_index]
        lr_l2 = RegL2(i)
        lr_l2.fit(X_train,y_train)
        err += mean_absolute_error(y_test, lr_l2.predict(X_test))
    plt_dataY.append(err / kf.get_n_splits())
plt.figure(figsize=(20,10))
plt.grid(True)
plt.title('Качество модели от коэффициента гамма, N=' + str(kf.get_n_splits()))
plt.xlabel('Гамма')
plt.ylabel('MAE')
plt.scatter(plt_dataX, plt_dataY)
plt.show()
print("Best lambda value:", plt_dataX[plt_dataY.index(min(plt_dataY))], "MAE:", min(plt_dataY))
Best lambda value: 2.3000000000000007 MAE: 0.36227171079610115

Результаты на стандартном разбиении 2 задания

In [16]:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
plt_dataX = []
plt_dataY = []
for i in iterator(0, 10, 0.1):
    plt_dataX.append(i)
    lr_l2 = RegL2(i)
    lr_l2.fit(X_train,y_train)
    plt_dataY.append(mean_absolute_error(y_test, lr_l2.predict(X_test)))
plt.figure(figsize=(20,10))
plt.grid(True)
plt.title('Качество модели от коэффициента гамма')
plt.xlabel('Гамма')
plt.ylabel('MAE')
plt.scatter(plt_dataX, plt_dataY)
plt.show()
print("Best lambda value:", plt_dataX[plt_dataY.index(min(plt_dataY))], "MAE:", min(plt_dataY))
Best lambda value: 2.800000000000001 MAE: 0.34493953032509433
In [17]:
lr_l2 = RegL2(2.3)
lr_l2.fit(X_train,y_train)
predict = lr_l2.predict(X_test)
print('RMSE: %f' % RMSE(y_test, predict))
print('MAE: %f' % mean_absolute_error(y_test, predict))
my_w = lr_l2.w
my_w
RMSE: 0.542146
MAE: 0.345122
Out[17]:
array([-0.08973678,  0.04347867, -0.00793039,  0.15331442, -0.14143446,
        0.34793497, -0.01867152, -0.24594953,  0.11809791, -0.08978445,
       -0.19706436,  0.11790084, -0.36414653, -0.01507952])
In [18]:
lr_l2 = Ridge()
lr_l2.fit(X_train,y_train)
predict = lr_l2.predict(X_test)
print('RMSE: %f' % RMSE(y_test, predict))
print('MAE: %f' % mean_absolute_error(y_test, predict))
sk_w = lr_l2.coef_
sk_w
RMSE: 0.536102
MAE: 0.346420
Out[18]:
array([-0.10491227,  0.074451  ,  0.02782472,  0.29324705, -0.21302364,
        0.33999793, -0.01929087, -0.3277258 ,  0.2404143 , -0.18778334,
       -0.21475015,  0.12255739, -0.39388018,  0.        ])
In [19]:
print("Compate weights")
print('RMSE: %f' % RMSE(sk_w, my_w))
print('MAE: %f' % mean_absolute_error(sk_w, my_w))
Compate weights
RMSE: 0.065430
MAE: 0.047945

Ошибки почти совпали, веса отличаются на сотые доли

4 (Бонус, 4 балла).

  • Реализуйте обучение линейной регрессии с L1-регуляризацией (класс RegL1)
  • Проведите сравнения аналогично пункту 3 (но с Lasso вместо Ridge).
In [20]:
from sklearn.linear_model import Lasso
In [62]:
class RegL1(LinReg):
    def __init__(self, lamb, step_l=0.017, step_p=1.3, step_s=29, num_steps=100, eps=1e-6):
        LinReg.__init__(self, step_l, step_p, step_s, num_steps, eps)
        self.lamb = lamb

    def _calc_grad(self, X_train, y_train):
        return 2 * np.dot(X_train.T, np.dot(X_train, self.w) - y_train) / self.shape[1] + self.lamb * np.sign(self.w)
In [68]:
kf = KFold(n_splits=round(X.shape[1]**0.5),shuffle=True, random_state=42)
plt_dataX = []
plt_dataY = []
for i in iterator(0, 2, 0.01):
    plt_dataX.append(i)
    err = 0
    for train_index, test_index in kf.split(X):
        X_train, X_test = X.values[train_index], X.values[test_index]
        y_train, y_test = y[train_index], y[test_index]
        lr_l1 = RegL1(i)
        lr_l1.fit(X_train,y_train)
        err += mean_absolute_error(y_test, lr_l1.predict(X_test))
    plt_dataY.append(err / kf.get_n_splits())
plt.figure(figsize=(20,10))
plt.grid(True)
plt.title('Качество модели от коэффициента лямбда, N=' + str(kf.get_n_splits()))
plt.xlabel('лямбда')
plt.ylabel('MAE')
plt.scatter(plt_dataX, plt_dataY)
plt.show()
print("Best lambda value:", plt_dataX[plt_dataY.index(min(plt_dataY))], "MAE:", min(plt_dataY))
Best lambda value: 0.8000000000000005 MAE: 0.36631946364427687
In [72]:
kf = KFold(n_splits=round(X.shape[1]**0.5),shuffle=True, random_state=42)
plt_dataX = []
plt_dataY = []
for i in iterator(0, 0.02, 0.001):
    plt_dataX.append(i)
    err = 0
    for train_index, test_index in kf.split(X):
        X_train, X_test = X.values[train_index], X.values[test_index]
        y_train, y_test = y[train_index], y[test_index]
        lr_l1 = Lasso(i)
        lr_l1.fit(X_train,y_train)
        err += mean_absolute_error(y_test, lr_l1.predict(X_test))
    plt_dataY.append(err / kf.get_n_splits())
plt.figure(figsize=(20,10))
plt.grid(True)
plt.title('Качество модели от коэффициента лямбда, N=' + str(kf.get_n_splits()))
plt.xlabel('лямбда')
plt.ylabel('MAE')
plt.scatter(plt_dataX, plt_dataY)
plt.show()
print("Best lambda value:", plt_dataX[plt_dataY.index(min(plt_dataY))], "MAE:", min(plt_dataY))
C:\Users\alex1\Anaconda3\lib\site-packages\ipykernel_launcher.py:11: UserWarning: With alpha=0, this algorithm does not converge well. You are advised to use the LinearRegression estimator
  # This is added back by InteractiveShellApp.init_path()
C:\Users\alex1\Anaconda3\lib\site-packages\sklearn\linear_model\coordinate_descent.py:478: UserWarning: Coordinate descent with no regularization may lead to unexpected results and is discouraged.
  positive)
C:\Users\alex1\Anaconda3\lib\site-packages\sklearn\linear_model\coordinate_descent.py:492: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations. Fitting data with very small alpha may cause precision problems.
  ConvergenceWarning)
C:\Users\alex1\Anaconda3\lib\site-packages\ipykernel_launcher.py:11: UserWarning: With alpha=0, this algorithm does not converge well. You are advised to use the LinearRegression estimator
  # This is added back by InteractiveShellApp.init_path()
C:\Users\alex1\Anaconda3\lib\site-packages\sklearn\linear_model\coordinate_descent.py:478: UserWarning: Coordinate descent with no regularization may lead to unexpected results and is discouraged.
  positive)
C:\Users\alex1\Anaconda3\lib\site-packages\sklearn\linear_model\coordinate_descent.py:492: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations. Fitting data with very small alpha may cause precision problems.
  ConvergenceWarning)
C:\Users\alex1\Anaconda3\lib\site-packages\ipykernel_launcher.py:11: UserWarning: With alpha=0, this algorithm does not converge well. You are advised to use the LinearRegression estimator
  # This is added back by InteractiveShellApp.init_path()
C:\Users\alex1\Anaconda3\lib\site-packages\sklearn\linear_model\coordinate_descent.py:478: UserWarning: Coordinate descent with no regularization may lead to unexpected results and is discouraged.
  positive)
C:\Users\alex1\Anaconda3\lib\site-packages\sklearn\linear_model\coordinate_descent.py:492: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations. Fitting data with very small alpha may cause precision problems.
  ConvergenceWarning)
C:\Users\alex1\Anaconda3\lib\site-packages\ipykernel_launcher.py:11: UserWarning: With alpha=0, this algorithm does not converge well. You are advised to use the LinearRegression estimator
  # This is added back by InteractiveShellApp.init_path()
C:\Users\alex1\Anaconda3\lib\site-packages\sklearn\linear_model\coordinate_descent.py:478: UserWarning: Coordinate descent with no regularization may lead to unexpected results and is discouraged.
  positive)
C:\Users\alex1\Anaconda3\lib\site-packages\sklearn\linear_model\coordinate_descent.py:492: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations. Fitting data with very small alpha may cause precision problems.
  ConvergenceWarning)
Best lambda value: 0.008 MAE: 0.36512837159984046
In [64]:
lr_l1 = Lasso(0.008)
lr_l1.fit(X_train,y_train)
predict = lr_l1.predict(X_test)
print('RMSE: %f' % RMSE(y_test, predict))
print('MAE: %f' % mean_absolute_error(y_test, predict))
sk_w = lr_l1.coef_
sk_w
RMSE: 0.495147
MAE: 0.332534
Out[64]:
array([-0.07823835,  0.11128474, -0.02066988,  0.17214534, -0.17024246,
        0.29529768,  0.        , -0.32606646,  0.19358231, -0.1721684 ,
       -0.19891033,  0.07818417, -0.42314283,  0.        ])
In [73]:
lr_l1 = RegL1(0.8)
lr_l1.fit(X_train,y_train)
predict = lr_l1.predict(X_test)
print('RMSE: %f' % RMSE(y_test, predict))
print('MAE: %f' % mean_absolute_error(y_test, predict))
my_w = lr_l1.w
my_w
RMSE: 0.494684
MAE: 0.334208
Out[73]:
array([-0.07662108,  0.11128795, -0.00856227,  0.19605064, -0.17737064,
        0.28557038,  0.0023824 , -0.32248859,  0.21388522, -0.20031402,
       -0.19659249,  0.07125133, -0.42247388, -0.00206165])
In [74]:
print("Compate weights")
print('RMSE: %f' % RMSE(sk_w, my_w))
print('MAE: %f' % mean_absolute_error(sk_w, my_w))
Compate weights
RMSE: 0.012384
MAE: 0.008634

Модели почти похожи, коэффициенты и ошибки совпадают

5 (Бонус, 2 балла).

Исследуйте для реализации регрессии с L2-регуляризацией зависимость качества на тестовой выборке (с графиками) от:

  • Длины шага
  • Количества шагов спуска
  • Константы epsilon.
In [27]:
class RegL2Spec(LinReg):
    def __init__(self, lamb=2.3, step=0.001, num_steps=100, eps=1e-6):
        LinReg.__init__(self, None, None, None, num_steps, eps)
        self.lamb = lamb
        self.step = step
        
    def _calc_step(self, step):
        return self.step
    
    def _calc_grad(self, X_train, y_train):
        return 2 * np.dot(X_train.T, np.dot(X_train, self.w) - y_train) / self.shape[1] + 2 * self.lamb * self.w
In [28]:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
In [29]:
plt_dataX = []
plt_dataY = []
for i in iterator(10, 100, 1):
    plt_dataX.append(i)
    lr_l2 = RegL2Spec(num_steps=i)
    lr_l2.fit(X_train,y_train)
    plt_dataY.append(mean_absolute_error(y_test, lr_l2.predict(X_test)))
for i in iterator(100, 1000, 10):
    plt_dataX.append(i)
    lr_l2 = RegL2Spec(num_steps=i)
    lr_l2.fit(X_train,y_train)
    plt_dataY.append(mean_absolute_error(y_test, lr_l2.predict(X_test)))
plt.figure(figsize=(20,10))
plt.grid(True)
plt.title('Качество модели от количества шагов')
plt.xlabel('Количества шагов')
plt.ylabel('MAE')
plt.scatter(plt_dataX, plt_dataY)
plt.show()
print("Best value:", plt_dataX[plt_dataY.index(min(plt_dataY))], "MAE:", min(plt_dataY))
Best value: 870 MAE: 0.34506055876677294

Улучшение модели снижается около 100 итерации, практически перестает изменятся после 300-400

In [30]:
plt_dataX = []
plt_dataY = []
for i in iterator(0.0001, 0.006, 0.0001):
    lr_l2 = RegL2Spec(num_steps=100, step = i)
    lr_l2.fit(X_train,y_train)
    if mean_absolute_error(y_test, lr_l2.predict(X_test)) < 1:
        plt_dataX.append(i)
        plt_dataY.append(mean_absolute_error(y_test, lr_l2.predict(X_test)))
plt.figure(figsize=(20,10))
plt.grid(True)
plt.title('Качество модели от размера шага')
plt.xlabel('Шаг')
plt.ylabel('MAE')
plt.scatter(plt_dataX, plt_dataY)
plt.show()
print("Best value:", plt_dataX[plt_dataY.index(min(plt_dataY))], "MAE:", min(plt_dataY))
Best value: 0.005400000000000001 MAE: 0.34515400391567286

Валидный размер шага практически не влияет на качество модели.

На границах интервала наблюдается резкое ухудшенеи качество модели.

Для малого шага обосновывается недостаточным временем для обучения, для больших - выход из области минимума.

Наиболее оптимальный один из самых большых шагов

In [31]:
plt_dataX = []
plt_dataY = []
def log_iterator(start, end, step):
    i = start
    while i <= end:
        yield i
        i *= step
for i in log_iterator(10**-7, 1, 1.4):
    lr_l2 = RegL2Spec(num_steps=100, step = 0.0054, eps=i)
    lr_l2.fit(X_train,y_train)
    plt_dataX.append(i)
    plt_dataY.append(mean_absolute_error(y_test, lr_l2.predict(X_test)))
plt.figure(figsize=(20,10))
plt.grid(True)
plt.title('Качество модели от эпсилон')
plt.xlabel('Eps')
plt.ylabel('MAE')
plt.scatter(plt_dataX, plt_dataY)
plt.xscale("log", nonposx='clip')
plt.xlim(10**-7)
plt.show()
print("Best value:", plt_dataX[plt_dataY.index(min(plt_dataY))], "MAE:", min(plt_dataY))
Best value: 1e-07 MAE: 0.34515400391567286

Чем меньше $Eps$, тем лучше модель, однако осбого улучшения ниже $10^{-2} - 10^{-3}$ нет