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

Значения поля type(Ghoul, Ghost, Goblin) просто заменим на на 0, 1 и 2.
Color — тоже необходимо предварительно обработать(нам нужны только числовые значения для построения модели). Для этого воспользуемся LabelEncoder и OneHotEncoder. Подробнее.
Ну, на этом этапе наши данные уже и готовы. Осталось обучить нашу модель.
Сначала применим Adagrad:
По своей сущности — это модификация стохастического градиентного спуска, про который я писал в прошлый раз: habr.com/ru/post/472300
Этот метод учитывает историю всех прошлых градиентов для каждого отдельно взятого параметра(идея масштабирования). Это позволяет уменьшать размер шага обучения для параметров, которые имеют большой градиент:

g — масштабирующий параметр(g0 = 0)
θ — параметр(вес)
эпсилон — небольшая константа, введённая дабы не допустить деления на ноль
Датасет разделим на 2 части:
Обучающая выборка(train) и валидационная(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 для нашего метода оптимизации:
При 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, которые сейчас показывают неплохие результаты. Ну и для того, чтобы понять эти методы, считаю, необходимо понять некие базовые идеи, использующиеся в более простых методах.
Всем спасибо за внимание!
На вкладке 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, которые сейчас показывают неплохие результаты. Ну и для того, чтобы понять эти методы, считаю, необходимо понять некие базовые идеи, использующиеся в более простых методах.
Всем спасибо за внимание!
