28.10.2019       Выпуск 306 (28.10.2019 - 03.11.2019)       Статьи

Идея инерции(SGDm), идея масштабирования (Adagrad) и регуляризация в машинном обучении на примере задачи классификации

Датасет, используемый далее, взят с уже прошедшего соревнования на kaggle отсюда.
На вкладке Data можно прочитать описание всех полей.

Весь исходный код здесь в формате ноутбука.

Читать>>




Экспериментальная функция:

Ниже вы видите текст статьи по ссылке. По нему можно быстро понять ссылка достойна прочтения или нет

Просим обратить внимание, что текст по ссылке и здесь может не совпадать.

Датасет, используемый далее, взят с уже прошедшего соревнования на kaggle

отсюда

.

На вкладке Data можно прочитать описание всех полей.

Весь исходный код

здесь

в формате ноутбука.

Загружаем данные, проверяем, что вообще имеем:

import numpy as np
import pandas as pd

dataset = pd.read_csv('../input/ghouls-goblins-and-ghosts-boo/train.csv') # обучающая выборка
X_test = pd.read_csv('../input/ghouls-goblins-and-ghosts-boo/test.csv') # тестовая выборка
print(dataset.shape)
print(dataset[:10])

Значения поля type(Ghoul, Ghost, Goblin) просто заменим на на 0, 1 и 2.

Color — тоже необходимо предварительно обработать(нам нужны только числовые значения для построения модели). Для этого воспользуемся LabelEncoder и OneHotEncoder.

Подробнее

.

from sklearn.preprocessing import LabelEncoder, OneHotEncoder

labelencoder_X_1 = LabelEncoder()
X_train[:, 4] = labelencoder_X_1.fit_transform(X_train[:, 4])

labelencoder_X_2 = LabelEncoder()
X_test[:, 4] = labelencoder_X_2.fit_transform(X_test[:, 4])

labelencoder_Y_2 = LabelEncoder()

Y_train = labelencoder_Y_2.fit_transform(Y_train)

one_hot_encoder = OneHotEncoder(categorical_features = [4])
X_train = one_hot_encoder.fit_transform(X_train).toarray()
X_test = one_hot_encoder.fit_transform(X_test).toarray()

Ну, на этом этапе наши данные уже и готовы. Осталось обучить нашу модель.

Сначала применим

Adagrad

:

По своей сущности — это модификация стохастического градиентного спуска, про который я писал в прошлый раз:

habr.com/ru/post/472300

Этот метод учитывает историю всех прошлых градиентов для каждого отдельно взятого параметра(идея масштабирования). Это позволяет уменьшать размер шага обучения для параметров, которые имеют большой градиент:

g — масштабирующий параметр(g0 = 0)

θ — параметр(вес)

эпсилон — небольшая константа, введённая дабы не допустить деления на ноль

Датасет разделим на 2 части:

Обучающая выборка(train) и валидационная(val):

from sklearn.model_selection import train_test_split

x_train, x_val, y_train, y_val = train_test_split(X_train, Y_train, test_size = 0.2)

Небольшая подготовка к обучению модели:

import torch
import numpy as np 

device = 'cuda' if torch.cuda.is_available() else 'cpu'

def make_train_step(model, loss_fn, optimizer):
    def train_step(x, y):
        model.train()
        yhat = model(x)
        loss = loss_fn(yhat, y)
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()
        return loss.item()
    return train_step

Само обучение модели:

from torch import optim, nn

model = torch.nn.Sequential(
    nn.Linear(10, 270),
    nn.ReLU(),
    nn.Linear(270, 3))
lr = 0.01
n_epochs = 500
loss_fn = torch.nn.CrossEntropyLoss()

optimizer = optim.Adagrad(model.parameters(), lr=lr)

train_step = make_train_step(model, loss_fn, optimizer)

from sklearn.utils import shuffle

for epoch in range(n_epochs):
    x_train, y_train = shuffle(x_train, y_train) # случайно перетасуем данные
    X = torch.FloatTensor(x_train)
    y = torch.LongTensor(y_train)
    loss = train_step(X, y)
print(loss)

Оценка модели:

# оцениваем модель:
test_var = torch.FloatTensor(x_val)
with torch.no_grad():
    result = model(test_var)
values, labels = torch.max(result, 1)
num_right = np.sum(labels.data.numpy() == y_val)
print('Точность {:.2f}'.format(num_right / len(y_val)))

Здесь у нас, кроме слоёв, только 2 настраиваемых параметра(пока что):

learning rate и n_epochs(кол-во эпох).

В зависимости от того, как мы скомбинируем эти два параметра, могут возникнуть 3 ситуации:

1 — все хорошо, т.е. модель показывает низкий loss на обучающей выборке и высокую точность на валидационной.

2 — underfitting — большой loss на обучающей выборке и низкая точность на валидационной.

3 — overfitting — низкий loss на обучающей выборке, но низкая точность на валидационной.

С первым всё понятно :)

Со вторым, вроде, тоже — поэкспериментировать с learning rate'ом и n_epochs.

А что делать с третьим? Ответ прост — регуляризация!

Раньше мы имели целевую функцию(loss function) вида:

L = MSE(Y, y) без дополнительных слагаемых

Суть регуляризации заключается как раз в том, чтобы, добавив в целевую функцию некоторое слагаемое, «штрафовать» градиент, если он слишком большой. Иными словами — мы накладываем ограничение на нашу целевую функцию.

Существует множество методов регуляризации. Подробнее про L1 и L2 — регуляризацию:

craftappmobile.com/l1-vs-l2-regularization/#_L1_L2

В методе Adagrad реализована L2 регуляризация, давайте ее и применим!

Сначала для наглядности посмотрим на показатели модели без регуляризации:

lr = 0.01, n_epochs = 500:

loss = 0.44…

Точность: 0.71

lr = 0.01, n_epochs = 1000:

loss = 0.41…

Точность: 0.75

lr = 0.01, n_epochs = 2000:

loss = 0.39…

Точность: 0.75

lr = 0.01, n_epochs = 3000:

loss = 0.367…

Точность: 0.76

lr = 0.01, n_epochs = 4000:

loss = 0.355…

Точность: 0.72

lr = 0.01, n_epochs = 10000:

loss = 0.285…

Точность: 0.69

Здесь видно, что при 4к+ эпох — модель уже оверфитит. Сейчас попробуем избежать этого:

Для этого добавим параметр weight_decay для нашего метода оптимизации:

optimizer = optim.Adagrad(model.parameters(), lr=lr, weight_decay = 0.001)

При lr = 0.01, m_epochs = 10000:

loss = 0.367…

Точность: 0.73

При 4000 эпох:

loss = 0.389…

Точность: 0.75

Вышло куда лучше, а добавили мы всего лишь 1 параметр в оптимизаторе :)

Теперь рассмотрим SGDm(это стохастический градиентный спуск с небольшим расширением — эвристикой, если угодно).

Суть заключается в том, что

SGD

обновляет параметры весьма сильно после каждой итерации. Логично было бы «сглаживать» градиент, используя при этом градиенты с прошлых итераций(идея инерции):

θ — параметр(вес)

µ — гиперпараметр инерции

SGD без параметра momentum:

SGD с параметром momentum:

Получилось не сильно лучше, но суть здесь в том, что есть методы, использующие сразу идеи масштабирования и инерции. Например, Adam или Adadelta, которые сейчас показывают неплохие результаты. Ну и для того, чтобы понять эти методы, считаю, необходимо понять некие базовые идеи, использующиеся в более простых методах.

Всем спасибо за внимание!






Разместим вашу рекламу

Пиши: mail@pythondigest.ru

Нашли опечатку?

Выделите фрагмент и отправьте нажатием Ctrl+Enter.

Система Orphus