Машинное обучение

Факультет математики НИУ ВШЭ, 2020-21 учебный год

Илья Щуров, Соня Дымченко, Руслан Хайдуров, Павел Егоров, Максим Бекетов

Страница курса

Домашнее задание 5. Линейные модели и градиентный спуск.

В данном задании необходимо реализовать обучение логистической и линейной регрессий с помощью различных вариантов градиентного спуска.

Правила оценивания найдите на странице курса.

Часть 1: Реализация градиентного спуска.

Реализуйте логистическую регрессию с лог-лоссом, обучаемую с помощью:

Задание 1 (1 балл) Градиентного спуска;

Подробнее о методах можно прочитать тут. Напомним, что лог-лосс вычисляется по формуле $$ \mathcal{L}(y, X, w) = -\cfrac{1}{n} \sum_{i=1}^{n} [y_i=1]\log \sigma(\langle w, x_i\rangle) + [y_i=0]\log (1 - \sigma(\langle w, x_i\rangle)) + \lambda_2 ||w||_2^2 + \lambda_1 ||w||_1 $$ где $\sigma(x) = 1 / (1 + \exp(-x))$, а $\lambda_1, \lambda_2 > 0$ -- параметры регуляризации. Считайте, что либо $\lambda_1 = 0$, либо $\lambda_2 = 0$.

Необходимо соблюдать следующие условия:

  • Все вычисления должны быть векторизованы;
  • Циклы средствами python допускается использовать только для итераций градиентного спуска в методе fit; также разрешается использовать только стандартные средства языка Python и библиотеку numpy.
  • Обучение необходимо приостанавливать, если выполнено хотя бы одно из двух условий:

    • проверку на евклидовую норму разности весов на двух соседних итерациях (например, меньше некоторого малого числа порядка $10^{-6}$, задаваемого параметром tolerance);
    • достижение максимального числа итераций (например, 10000, задаваемого параметром max_iter).
  • Чтобы проследить, что оптимизационный процесс действительно сходится, будем использовать атрибут loss_history — в нём после вызова метода fit должны содержаться значения функции потерь для всех итераций, начиная с первой (до совершения первого шага по антиградиенту);
  • Инициализировать веса можно случайным образом или нулевым вектором.

Ниже приведён шаблон класса, который должен содержать код реализации каждого из методов.

In [4]:
import numpy as np
from sklearn.base import BaseEstimator


class LogReg(BaseEstimator):
    def __init__(self, lambda_1=0.0, lambda_2=1.0, gd_type='full', 
                 tolerance=1e-4, max_iter=1000, w0=None, alpha=1e-3, eta=0.0):
        """
        lambda_1: L1 regularization param
        lambda_2: L2 regularization param
        gd_type: 'full', 'stochastic' or 'momentum'
        tolerance: for stopping gradient descent
        max_iter: maximum number of steps in gradient descent
        w0: np.array of shape (d) - init weights
        alpha: learning rate
        eta: ignored
        """
        self.lambda_1 = lambda_1
        self.lambda_2 = lambda_2
        self.gd_type = gd_type
        self.tolerance = tolerance
        self.max_iter = max_iter
        self.w0 = w0
        self.alpha = alpha
        self.w = None
        self.loss_history = None
        self.eta = eta
    
    def fit(self, X, y):
        """
        X: np.array of shape (l, d)
        y: np.array of shape (l)
        ---
        output: self
        """
        self.loss_history = []
        
        pass
        
        return self
    
    def predict_proba(self, X):
        """
        X: np.array of shape (l, d)
        ---
        output: np.array of shape (l, 2) where
        first column has probabilities of -1
        second column has probabilities of +1
        """
        if self.w is None:
            raise Exception('Not trained yet')
        
        pass
    
    def calc_gradient(self, X, y):
        """
        X: np.array of shape (l, d) (l can be equal to 1 if stochastic)
        y: np.array of shape (l)
        ---
        output: np.array of shape (d)
        """
        pass

    def calc_loss(self, X, y):
        """
        X: np.array of shape (l, d)
        y: np.array of shape (l)
        ---
        output: float 
        """ 
        pass

Задание 2 (0 баллов).

  • Загрузите обучающие данные с соревнования Porto Seguro;
  • Пересэмплируйте её так, чтобы объектов положительного и отрциательного классов было поровну;
  • Разбейте выборку на обучающую и тестовую в отношении 7:3.

Ладно, мы сделали это за вас. =)

In [ ]:
data = pd.read_csv('train.csv', index_col=0)
target = data.target.values
data = data.drop('target', axis=1)
In [ ]:
# some resampling
np.random.seed(910)
mask_plus = np.unique(np.random.choice(np.where(target == 1)[0], 100000, replace=True))
mask_zero = np.unique(np.random.choice(np.where(target == 0)[0], 100000, replace=True))

data = pd.concat((data.iloc[mask_plus], data.iloc[mask_zero]))
target = np.hstack((target[mask_plus], target[mask_zero]))

X_train, X_test, y_train, y_test = train_test_split(data, target, test_size=0.3)

Задание 3 (0.5 балла). Обучите и провалидируйте LogReg на данных из предыдущего пункта, посчитайте по тестовой выборке AUC-ROC и F-меру. Исследуйте влияние параметров max_iter и alpha на процесс оптимизации. Согласуется ли оно с вашими ожиданиями? (здесь подразумеваются графики, на которых отображаются значения метрик в зависимости от значения параметра).

In [ ]:
 

Часть 2: линейная регрессия и feature importance

Реализуйте логистическую регрессию с лог-лоссом, обучаемую с помощью:

Задание 4 (1.5 балла) Стохастического градиентного спуска;

Задание 5 (1.5 балла) Стохастического градиентного спуска с Momentum.

Подробнее о методах можно прочитать тут. Напомним, что лог-лосс вычисляется по формуле $$ \mathcal{L}(y, X, w) = -\cfrac{1}{n} \sum_{i=1}^{n} [y_i=1]\log \sigma(\langle w, x_i\rangle) + [y_i=0]\log (1 - \sigma(\langle w, x_i\rangle)) + \lambda_2 ||w||_2^2 + \lambda_1 ||w||_1 $$ где $\sigma(x) = 1 / (1 + \exp(-x))$, а $\lambda_1, \lambda_2 > 0$ -- параметры регуляризации. Считайте, что либо $\lambda_1 = 0$, либо $\lambda_2 = 0$.

Во всех пунктах необходимо соблюдать следующие условия:

  • Все вычисления должны быть векторизованы;
  • Циклы средствами python допускается использовать только для итераций градиентного спуска в методе fit; также разрешается использовать только стандартные средства языка Python и библиотеку numpy.
  • Обучение необходимо приостанавливать, если выполнено хотя бы одно из двух условий:

    • проверку на евклидовую норму разности весов на двух соседних итерациях (например, меньше некоторого малого числа порядка $10^{-6}$, задаваемого параметром tolerance);
    • достижение максимального числа итераций (например, 10000, задаваемого параметром max_iter).
  • Чтобы проследить, что оптимизационный процесс действительно сходится, будем использовать атрибут loss_history — в нём после вызова метода fit должны содержаться значения функции потерь для всех итераций, начиная с первой (до совершения первого шага по антиградиенту);
  • Инициализировать веса можно случайным образом или нулевым вектором.

Ниже приведён шаблон класса, который должен содержать код реализации каждого из методов.

In [6]:
import numpy as np
from sklearn.base import BaseEstimator

class LogReg(BaseEstimator):
    def __init__(self, lambda_1=0.0, lambda_2=1.0, gd_type='stochastic', 
                 tolerance=1e-4, max_iter=1000, w0=None, alpha=1e-3, eta=0.0):
        """
        lambda_1: L1 regularization param
        lambda_2: L2 regularization param
        gd_type: 'full', 'stochastic' or 'momentum'
        tolerance: for stopping gradient descent
        max_iter: maximum number of steps in gradient descent
        w0: np.array of shape (d) - init weights
        alpha: learning rate
        eta: momentum parameter (weight of momentum vector)
        """
        self.lambda_1 = lambda_1
        self.lambda_2 = lambda_2
        self.gd_type = gd_type
        self.tolerance = tolerance
        self.max_iter = max_iter
        self.w0 = w0
        self.alpha = alpha
        self.w = None
        self.loss_history = None
    
    def fit(self, X, y):
        """
        X: np.array of shape (l, d)
        y: np.array of shape (l)
        ---
        output: self
        """
        self.loss_history = []
        
        pass
        
        return self
    
    def predict_proba(self, X):
        """
        X: np.array of shape (l, d)
        ---
        output: np.array of shape (l, 2) where
        first column has probabilities of -1
        second column has probabilities of +1
        """
        if self.w is None:
            raise Exception('Not trained yet')
        
        pass
    
    def calc_gradient(self, X, y):
        """
        X: np.array of shape (l, d) (l can be equal to 1 if stochastic)
        y: np.array of shape (l)
        ---
        output: np.array of shape (d)
        """
        pass

    def calc_loss(self, X, y):
        """
        X: np.array of shape (l, d)
        y: np.array of shape (l)
        ---
        output: float 
        """ 
        pass

Задание 5.5 (1 балл). Обучите и провалидируйте оба метода на данных из пункта 2, посчитайте по тестовой выборке AUC-ROC и F-меру. Исследуйте влияние параметров max_iter, alpha и eta на процесс оптимизации. Согласуется ли оно с вашими ожиданиями? (здесь подразумеваются графики, на которых отображаются значения метрик в зависимости от значения параметра).

In [ ]:
 

Задание 6 (1.5 балла). Постройте графики (на одной и той же картинке) зависимости величины функции потерь от номера итерации для полного, стохастического градиентного спусков, а также для полного градиентного спуска с методом Momentum. Постройте аналогичные графики для зависимости от времени работы в секундах. Сделайте выводы о скорости сходимости различных модификаций градиентного спуска.

Назовём график красивым, если он соответствует требованиям, предъявленным к графикам в первом дз. В этом задании от вас требуются красивые графики.

In [ ]:
 

Далее мы проанализируем то, как работает линейная регрессия и регуляризация. Тут уже можно пользоваться sklearn'ом.

Задание 7 (0 баллов). Загрузите обучающие данные из соревнования New York City Taxi Trip Duration. Разделите выборку в отношении 7:3. Преобразуйте целевую переменную (trip_duration) как $\widetilde{y} = \log(1 + y)$. Удалите столбец id, а также столбцы, содержащие дату и время. Отнормируйте признаки при помощи MinMaxScaler'a. Как вы думаете, почему такое преобразование имеет смысл?

In [ ]:
 

Задание 8 (1 балл). Обучите три вида линейной регрессии на получившихся данных: обычную, Ridge и Lasso. Оцените качество при помощи MSE и $R^2$.

In [ ]:
 

Задание 9 (1 балл). Постройте графики зависимости значения метрик из предыдущего задания от значения коэффициента регуляризации для методов Lasso и Ridge. Какие выводы можно сделать?

In [ ]:
 

Хорошие ли получились результаты?

Задание 10 (0.5 баллов). При помощи кросс-валидации найдите оптимальные значения коэффициента регуляризации для методов Ridge и Lasso.

In [ ]:
 

Задание 11 (0.5 баллов). Постройте bar plot весов признаков для каждой из трёх моделей (на одном рисунке). Какие выводы можно сделать?

In [ ]:
 

Какие фичи оказались наиболее важными? Согласуется ли это с вашими ожиданиями?

Задание 12 (2 балла). Добавьте в датасет дополнительные признаки, основываясь на существующих, чтобы получить значение метрики MSE на тестовом куске данных не более 0.4. Что вы для этого сделали?

In [ ]:
 

Бонус

Бонусные задачи оцениваются особенно строго. Оценка может быть снижена за плохой код и даже за некрасивые названия переменных. Подсказок не даём.

Задание 13 (0.5 баллов). Правда ли, что лог-лосс является выпуклой функцией относительно $w$? Правда ли, что она является Липшицевой относительно $w$? Почему?

Задание 14 (3 балла). В этом задании на 2 балла засчитывается один из двух пунктов:

  • Реализуйте логистическую регрессию с лог-лоссом, обучаемую с помощью метода Adam
  • Реализуйте логистическую регрессию с лог-лоссом, обучаемую с помощью метода Ньютона

добавьте при необходимости параметры в класс модели, повторите пункты 2 и 3 и сравните результаты.

На 3 балла засчитываются оба пункта со сравнением методов и выводами.

Задание 15 (0.00 баллов). Вставьте ниже самый смешной или самый грустный график, который получился у вас в этом дз.

In [ ]: