Ансамбли нейронных сетей с PyTorch и Sklearn

МЕНЮ


Искусственный интеллект
Поиск
Регистрация на сайте
Помощь проекту

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


2020-02-19 15:57

Нейронные сети довольно популярны. Их главное преимущество в том, что они способны обобщать довольно сложные данные, на которых другие алгоритмы показывают низкое качество. Но что делать, если качество нейронной сети все еще неудовлетворительное?

И тут на помощь приходят ансамбли...

Что такое ансамбли

Ансамбль алгоритмов машинного обучения — это использование нескольких (не обязательно разных) моделей вместо одной. То есть сначала мы обучаем каждую модель, а затем объединяем их предсказания. Получается, что наши модели вместе образуют одну более сложную (в плане обобщающей способности — способности "понимать" данные) модель, которую часто называют метамоделью. Чаще всего метамодель обучается уже не на нашей первоначальной выборке данных, а на предсказаниях других моделей. Она как бы учитывает опыт всех моделей, и это позволяет уменьшить ошибки.

План

  1. Сначала мы рассмотрим одну простую PyTorch-модель и получим ее качество
  2. Затем соберем несколько моделей с помощью Scikit-learn и узнаем, как добиться более высокого уровня

Одна единственная модель

Итак, будем работать с датасетом Digits из Sklearn, так как его можно довольно просто получить в две строчки кода:

# импортируем библиотеки from sklearn.datasets import load_digits import numpy as np import matplotlib.pyplot as plt  # собственно, загрузка датасета x, y = load_digits(n_class=10, return_X_y=True)

Посмотрим, как выглядят наши данные:

print(x.shape) >>> (1797, 64) print(np.unique(y)) >>> array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

index = 0  print(y[index]) plt.imshow(x[index].reshape(8, 8), cmap="Greys")

Прэкрасный нолик

x[index] >>> array([ 0.,  0.,  5., 13.,  9.,  1.,  0.,  0.,  0.,  0., 13., 15., 10.,        15.,  5.,  0.,  0.,  3., 15.,  2.,  0., 11.,  8.,  0.,  0.,  4.,        12.,  0.,  0.,  8.,  8.,  0.,  0.,  5.,  8.,  0.,  0.,  9.,  8.,         0.,  0.,  4., 11.,  0.,  1., 12.,  7.,  0.,  0.,  2., 14.,  5.,        10., 12.,  0.,  0.,  0.,  0.,  6., 13., 10.,  0.,  0.,  0.])

Ага, чиселки принимают значения от 0 до 15. Значит, нужна нормализация:

from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler  # разделим выборку на тренировочную и тестовую x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2,                                                     shuffle=True, random_state=42) print(x_train.shape) >>> (1437, 64) print(x_test.shape) >>> (360, 64)

Используем StandardScaler: он переводит все данные в границы от -1 до 1. Обучаем его только на тренировочной выборке, чтобы он не подстраивался под тест — так score модели после теста будет ближе к score'у на реальных данных:

scaler = StandardScaler() scaler.fit(x_train)

И нормализуем наши данные:

x_train_scaled = scaler.transform(x_train) x_test_scaled = scaler.transform(x_test)

Время Torch'а!

import torch from torch.utils.data import TensorDataset, DataLoader import torch.nn as nn import torch.nn.functional as F from torch.optim import Adam

Простой сверточной сети вполне достаточно для демонстрации:

class SimpleCNN(nn.Module):   def __init__(self):     super(SimpleCNN, self).__init__()      # слои свертки     self.conv1 = nn.Conv2d(in_channels=1, out_channels=3, kernel_size=5, stride=1, padding=2)     self.conv1_s = nn.Conv2d(in_channels=3, out_channels=3, kernel_size=5, stride=2, padding=2)     self.conv2 = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=3, stride=1, padding=1)     self.conv2_s = nn.Conv2d(in_channels=6, out_channels=6, kernel_size=3, stride=2, padding=1)     self.conv3 = nn.Conv2d(in_channels=6, out_channels=10, kernel_size=3, stride=1, padding=1)     self.conv3_s = nn.Conv2d(in_channels=10, out_channels=10, kernel_size=3, stride=2, padding=1)      self.flatten = nn.Flatten()     # гордый полносвязный слой     self.fc1 = nn.Linear(10, 10)    # функция для "пропускания" данных через модельку   def forward(self, x):     x = F.relu(self.conv1(x))     x = F.relu(self.conv1_s(x))     x = F.relu(self.conv2(x))     x = F.relu(self.conv2_s(x))     x = F.relu(self.conv3(x))     x = F.relu(self.conv3_s(x))      x = self.flatten(x)     x = self.fc1(x)     x = F.softmax(x)      return x

Определим некоторые гиперпараметры :

batch_size = 64 # размер батча learning_rate = 1e-3 # шаг оптимизатора epochs = 200 # сколько эпох обучаемся

Сперва преобразуем данные в тензоры, с которыми работает PyTorch:

x_train_tensor = torch.tensor(x_train_scaled.reshape(-1, 1, 8, 8).astype(np.float32)) x_test_tensor = torch.tensor(x_test_scaled.reshape(-1, 1, 8, 8).astype(np.float32))  y_train_tensor = torch.tensor(y_train.astype(np.long)) y_test_tensor = torch.tensor(y_test.astype(np.long))

В PyTorch есть очень удобные обертки на случай, если ваши данные — это простые тензоры. Здесь датасет — штука, которая может вернуть пару X и y по индексу (прокачанный массив), а DataLoader — итератор, который будет последовательно возвращать наши пары <X, y> батчами по 64 картинки:

train_dataset = TensorDataset(x_train_tensor, y_train_tensor) train_loader = DataLoader(train_dataset, batch_size=batch_size)  test_dataset = TensorDataset(x_test_tensor, y_test_tensor) test_loader = DataLoader(test_dataset, batch_size=batch_size)

Штуки, которые нужны для обучения модельки:

simple_cnn = SimpleCNN().cuda() # перемещаем на gpu, чтобы училось быстрее, жилось веселее criterion = nn.CrossEntropyLoss() optimizer = Adam(simple_cnn.parameters(), lr=learning_rate)

И само обучение:

for epoch in range(epochs): # итерируемся 200 эпох   simple_cnn.train()   train_samples_count = 0 # общее количество картинок, которые мы прогнали через модельку   true_train_samples_count = 0 # количество верно предсказанных картинок   running_loss = 0    for batch in train_loader:     x_data = batch[0].cuda() # данные тоже необходимо перемещать на gpu     y_data = batch[1].cuda()      y_pred = simple_cnn(x_data)     loss = criterion(y_pred, y_data)      optimizer.zero_grad()     loss.backward()     optimizer.step() # обратное распространение ошибки      running_loss += loss.item()      y_pred = y_pred.argmax(dim=1, keepdim=False)     true_classified = (y_pred == y_data).sum().item() # количество верно предсказанных картинок в текущем батче     true_train_samples_count += true_classified     train_samples_count += len(x_data) # размер текущего батча    train_accuracy = true_train_samples_count / train_samples_count   print(f"[{epoch}] train loss: {running_loss}, accuracy: {round(train_accuracy, 4)}") # выводим логи    # прогоняем тестовую выборку   simple_cnn.eval()   test_samples_count = 0   true_test_samples_count = 0   running_loss = 0    for batch in test_loader:     x_data = batch[0].cuda()     y_data = batch[1].cuda()      y_pred = simple_cnn(x_data)     loss = criterion(y_pred, y_data)      loss.backward()      running_loss += loss.item()      y_pred = y_pred.argmax(dim=1, keepdim=False)     true_classified = (y_pred == y_data).sum().item()     true_test_samples_count += true_classified     test_samples_count += len(x_data)    test_accuracy = true_test_samples_count / test_samples_count   print(f"[{epoch}] test loss: {running_loss}, accuracy: {round(test_accuracy, 4)}")

Смотрим, что получилось

Из-за некоторых особенностей Torch'а и алгоритмов, результат на Вашей машине может не совпадать с полученным здесь. Это вполне нормально. Рассмотрим только последние 20 эпох.

[180] train loss: 40.52328181266785, accuracy: 0.6966 [180] test loss: 10.813781499862671, accuracy: 0.6583 [181] train loss: 40.517325043678284, accuracy: 0.6966 [181] test loss: 10.811877608299255, accuracy: 0.6611 [182] train loss: 40.517088294029236, accuracy: 0.6966 [182] test loss: 10.814386487007141, accuracy: 0.6611 [183] train loss: 40.515315651893616, accuracy: 0.6966 [183] test loss: 10.812204122543335, accuracy: 0.6611 [184] train loss: 40.5108939409256, accuracy: 0.6966 [184] test loss: 10.808713555335999, accuracy: 0.6639 [185] train loss: 40.50885498523712, accuracy: 0.6966 [185] test loss: 10.80833113193512, accuracy: 0.6639 [186] train loss: 40.50892996788025, accuracy: 0.6966 [186] test loss: 10.809209108352661, accuracy: 0.6639 [187] train loss: 40.508036971092224, accuracy: 0.6966 [187] test loss: 10.806900978088379, accuracy: 0.6667 [188] train loss: 40.507275462150574, accuracy: 0.6966 [188] test loss: 10.79791784286499, accuracy: 0.6611 [189] train loss: 40.50368785858154, accuracy: 0.6966 [189] test loss: 10.799399137496948, accuracy: 0.6667 [190] train loss: 40.499858379364014, accuracy: 0.6966 [190] test loss: 10.795265793800354, accuracy: 0.6611 [191] train loss: 40.498780846595764, accuracy: 0.6966 [191] test loss: 10.796114206314087, accuracy: 0.6639 [192] train loss: 40.497228503227234, accuracy: 0.6966 [192] test loss: 10.790620803833008, accuracy: 0.6639 [193] train loss: 40.44325613975525, accuracy: 0.6973 [193] test loss: 10.657087206840515, accuracy: 0.7 [194] train loss: 39.62049174308777, accuracy: 0.7495 [194] test loss: 10.483307123184204, accuracy: 0.7222 [195] train loss: 39.24516046047211, accuracy: 0.7613 [195] test loss: 10.462445378303528, accuracy: 0.7278 [196] train loss: 39.16947162151337, accuracy: 0.762 [196] test loss: 10.488057255744934, accuracy: 0.7222 [197] train loss: 39.196797251701355, accuracy: 0.7634 [197] test loss: 10.502906918525696, accuracy: 0.7222 [198] train loss: 39.395434617996216, accuracy: 0.7537 [198] test loss: 10.354896545410156, accuracy: 0.7472 [199] train loss: 39.331292152404785, accuracy: 0.7509 [199] test loss: 10.367400050163269, accuracy: 0.7389

Можно заметить, что моделька переобучилась: качество на тренировочной выборке выше качества на тестовой.

Так, и что же имеем: только 74% предсказаний оказываются верными. Not great, not terrible.

Время улучшений!

Будем использовать Bagging. Очень кратко этот подход можно описать так: берутся несколько моделей, обучаются независимо, а затем все модели голосуют за результат. И тут могут быть различные варианты реализации: либо все модели имеют одинаковую "цену" голоса, либо у их голосов есть какие-то веса. Предсказанием метамодели считается усредненный результат.

В sklearn есть модуль ensembles, в котором представлено несколько алгоритмов для реализации ансамблей. Наш случай — классификация, используем BaggingClassifier:

import sklearn from sklearn.ensemble import BaggingClassifier

Ансамбли оперируют базовыми моделями. В случае sklearn, базовая модель — сущность, реализующая методы sklearn.base.BaseEstimator. В зависимости от вида ансамбля, будут нужны различные реализации функций этой сущности. Для нашего случая необходимо описать метод fit (для обучения модели), функцию predict_proba, выдающую вероятность каждого класса (для того, чтобы метамодель смогла по вероятностям оценивать уверенность базовых классификаторов и присваивать их голосам большие или меньшие веса), и функцию predict (которая выдает номер класса), чтобы было удобно оценивать качество модели.

Ради понятности буду описывать каждую часть кода отдельно, потом их просто нужно "склеить" в один класс.

Конструктор класса базовой модели. Из тех граблей, на которые я успел наткнуться, стоит отметить именование параметров и их типы.

Во-первых, если вы не хотите переписывать функции клонирования объекта базового классификатора (а придется переписывать аж 2 функции — get_params и set_params), то нужно давать аргументам в конструкторе те же имена, что и присваиваемым переменным класса. То есть, если вы подаете в конструктор параметр net_type, то в методе __init__ вы должны объявить переменную net_type.

Во-вторых, лучше передавать аргументами в конструктор не объекты, а функции для их создания, потому что при копировании базовых моделей (а sklearn именно копирует переданную вами модель несколько раз, пока не получит нужное число базовых моделей) возникают различные сложности с функциями copy, deepcopy и ссылками на объекты. Например, может оказаться, что переданный и скопированный объект оптимизатора ссылается не на новый (скопированный) объект модели, а на самый первый, использованный при инициализации ансамбля.

class PytorchModel(sklearn.base.BaseEstimator):   def __init__(self, net_type, net_params, optim_type, optim_params, loss_fn,                input_shape, batch_size=32, accuracy_tol=0.02, tol_epochs=10,                cuda=True):     self.net_type = net_type # конструктор для создания нейронки     self.net_params = net_params # параметры нейронки     self.optim_type = optim_type # конструктор для создания оптимизатора     self.optim_params = optim_params # параметры оптимизатора     self.loss_fn = loss_fn # функция подсчета loss'а      self.input_shape = input_shape # размерность входных данных     self.batch_size = batch_size # размер батча     self.accuracy_tol = accuracy_tol # порог точности, об его использовании -- позже     self.tol_epochs = tol_epochs # количество эпох при данном пороге точности     self.cuda = cuda # обучаем ли на gpu

Самое интересное: метод fit. По сути, он работает как и обучение нашей первой нейросети — пропускаем данные, считаем Loss, делаем обратное распространение ошибки. Вот только вопрос: как нам понять, какое количество эпох надо обучать модель? Первым решением может являться задание определенного количество эпох до обучения. Например, 200 (как в первой модели). Но это может привести к переобучению, а может и к недообучению. Второе решение — смотреть, как изменяется accuracy нашей модели. У первой нейросети можно заметить, что в конце accuracy держится примерно на одном уровне несколько эпох подряд. Тогда мы можем остановить обучение, если модель уже на протяжении нескольких (tol_epochs) эпох не улучшает свое качество (то есть accuracy изменяется не более, чем на accuracy_tol).

  def fit(self, X, y):     self.net = self.net_type(**self.net_params) # создаем нашу сеть из ее конструктора и параметров     # `**self.net_params` означает, что пары ключ-значения из словаря будут передаваться в функцию как названия аргументов и их значения     if self.cuda:       self.net = self.net.cuda() # перемещаем сеть на gpu, если обучаемся на нем     self.optim = self.optim_type(self.net.parameters(), **self.optim_params) # как и сеть, создаем оптимизатор     # в него первым параметром нужно передать обучаемые параметры нейросети      uniq_classes = np.sort(np.unique(y)) # ищем уникальные классы     self.classes_ = uniq_classes # одно из требований ансамбля в sklearn -- наличие в базовой модели переменной `classes_`      X = X.reshape(-1, *self.input_shape) # изменяем размер входных данных под размерность входного слоя нейронки     # аналогично первой нейросети создаем датасет и DataLoader     x_tensor = torch.tensor(X.astype(np.float32))     y_tensor = torch.tensor(y.astype(np.long))     train_dataset = TensorDataset(x_tensor, y_tensor)     train_loader = DataLoader(train_dataset, batch_size=self.batch_size,                               shuffle=True, drop_last=False)     last_accuracies = [] # тут будут храниться accuracy модели за последние `tol_epochs` эпох     epoch = 0     keep_training = True     while keep_training:       self.net.train() # для некоторых нейросетей важно вызвать этот метод (например, если вы используете BatchNormalization)       train_samples_count = 0 # общее количество объектов выборки       true_train_samples_count = 0 # количество верно предсказанных объектов выборки        for batch in train_loader:         x_data, y_data = batch[0], batch[1]         if self.cuda:           x_data = x_data.cuda()           y_data = y_data.cuda()          # прогоняем данные через модель         y_pred = self.net(x_data)         loss = self.loss_fn(y_pred, y_data)          # обратное распространение ошибки         self.optim.zero_grad()         loss.backward()         self.optim.step()          y_pred = y_pred.argmax(dim=1, keepdim=False) # ищем индекс максимальной вероятности, это и есть предсказанный класс         true_classified = (y_pred == y_data).sum().item() # ищем количество верно предсказанных объектов         true_train_samples_count += true_classified         train_samples_count += len(x_data)        train_accuracy = true_train_samples_count / train_samples_count # считаем accuracy       last_accuracies.append(train_accuracy)        if len(last_accuracies) > self.tol_epochs:         last_accuracies.pop(0)        if len(last_accuracies) == self.tol_epochs:         accuracy_difference = max(last_accuracies) - min(last_accuracies) # смотрим, какова максимальная разница в accuracy за последние `tol_epochs` эпох         if accuracy_difference <= self.accuracy_tol:           keep_training = False # если разница мала, прекращаем обучение

Теперь будем делать функцию для предсказания вероятностей классов. По сути — так же пропускаем данные, только не считаем Loss, а записываем все в какой-нибудь массив:

  def predict_proba(self, X, y=None):     # точно так же, как и в fit, делаем DataLoader, из которого будем брать батчи     X = X.reshape(-1, *self.input_shape)     x_tensor = torch.tensor(X.astype(np.float32))     if y:       y_tensor = torch.tensor(y.astype(np.long))     else:       y_tensor = torch.zeros(len(X), dtype=torch.long)     test_dataset = TensorDataset(x_tensor, y_tensor)     test_loader = DataLoader(test_dataset, batch_size=self.batch_size,                               shuffle=False, drop_last=False)      self.net.eval() # еще одна важная функция, как и `train`, нужна для методов вроде BatchNormalization, где слои работают по-разному при обучении и валидации     predictions = [] # массив, куда сохраняем предсказания     for batch in test_loader:       x_data, y_data = batch[0], batch[1]       if self.cuda:         x_data = x_data.cuda()         y_data = y_data.cuda()        y_pred = self.net(x_data)        predictions.append(y_pred.detach().cpu().numpy()) # получаем предсказания модели, переводим их в numpy-массив и записываем в список с предсказаниями      predictions = np.concatenate(predictions) # конкатенируем наши предсказания для батчей в предсказание для всей выборки     return predictions

Простая и относительно красивая функция:

  def predict(self, X, y=None):     predictions = self.predict_proba(X, y) # используем написанное ранее     predictions = predictions.argmax(axis=1) # берем индекс максимальной вероятности, он является предсказанным классом     return predictions

Полный код базового классификатора
class PytorchModel(sklearn.base.BaseEstimator):   def __init__(self, net_type, net_params, optim_type, optim_params, loss_fn,                input_shape, batch_size=32, accuracy_tol=0.02, tol_epochs=10,                cuda=True):     self.net_type = net_type     self.net_params = net_params     self.optim_type = optim_type     self.optim_params = optim_params     self.loss_fn = loss_fn      self.input_shape = input_shape     self.batch_size = batch_size     self.accuracy_tol = accuracy_tol     self.tol_epochs = tol_epochs     self.cuda = cuda    def fit(self, X, y):     self.net = self.net_type(**self.net_params)     if self.cuda:       self.net = self.net.cuda()     self.optim = self.optim_type(self.net.parameters(), **self.optim_params)      uniq_classes = np.sort(np.unique(y))     self.classes_ = uniq_classes      X = X.reshape(-1, *self.input_shape)     x_tensor = torch.tensor(X.astype(np.float32))     y_tensor = torch.tensor(y.astype(np.long))     train_dataset = TensorDataset(x_tensor, y_tensor)     train_loader = DataLoader(train_dataset, batch_size=self.batch_size,                               shuffle=True, drop_last=False)     last_accuracies = []     epoch = 0     keep_training = True     while keep_training:       self.net.train()       train_samples_count = 0       true_train_samples_count = 0        for batch in train_loader:         x_data, y_data = batch[0], batch[1]         if self.cuda:           x_data = x_data.cuda()           y_data = y_data.cuda()          y_pred = self.net(x_data)         loss = self.loss_fn(y_pred, y_data)          self.optim.zero_grad()         loss.backward()         self.optim.step()          y_pred = y_pred.argmax(dim=1, keepdim=False)         true_classified = (y_pred == y_data).sum().item()         true_train_samples_count += true_classified         train_samples_count += len(x_data)        train_accuracy = true_train_samples_count / train_samples_count       last_accuracies.append(train_accuracy)        if len(last_accuracies) > self.tol_epochs:         last_accuracies.pop(0)        if len(last_accuracies) == self.tol_epochs:         accuracy_difference = max(last_accuracies) - min(last_accuracies)         if accuracy_difference <= self.accuracy_tol:           keep_training = False    def predict_proba(self, X, y=None):     X = X.reshape(-1, *self.input_shape)     x_tensor = torch.tensor(X.astype(np.float32))     if y:       y_tensor = torch.tensor(y.astype(np.long))     else:       y_tensor = torch.zeros(len(X), dtype=torch.long)     test_dataset = TensorDataset(x_tensor, y_tensor)     test_loader = DataLoader(test_dataset, batch_size=self.batch_size,                               shuffle=False, drop_last=False)      self.net.eval()     predictions = []     for batch in test_loader:       x_data, y_data = batch[0], batch[1]       if self.cuda:         x_data = x_data.cuda()         y_data = y_data.cuda()        y_pred = self.net(x_data)        predictions.append(y_pred.detach().cpu().numpy())      predictions = np.concatenate(predictions)     return predictions    def predict(self, X, y=None):     predictions = self.predict_proba(X, y)     predictions = predictions.argmax(axis=1)     return predictions

Итак, протестируем наш новоиспеченный классификатор:

base_model = PytorchModel(net_type=SimpleCNN, net_params=dict(), optim_type=Adam,                           optim_params={"lr": 1e-3}, loss_fn=nn.CrossEntropyLoss(),                           input_shape=(1, 8, 8), batch_size=32, accuracy_tol=0.02,                           tol_epochs=10, cuda=True)  base_model.fit(x_train_scaled, y_train) # обучение модели  preds = base_model.predict(x_test_scaled) # предсказываем на тестовом наборе true_classified = (preds == y_test).sum() # количество верно предсказанных объектов test_accuracy = true_classified / len(y_test) # accuracy  print(f"Test accuracy: {test_accuracy}") >>> Test accuracy: 0.7361111111111112

Все готово для создания ансамбля.

meta_classifier = BaggingClassifier(base_estimator=base_model, n_estimators=10) # заметьте, что в конструктор передаем именно объект базового классификатора  meta_classifier.fit(x_train_scaled.reshape(-1, 64), y_train) # обучаем на тестовой выборке, на вход требуется двумерный массив, поэтому reshape'им >>> BaggingClassifier(            base_estimator=PytorchModel(accuracy_tol=0.02, batch_size=32,               cuda=True, input_shape=(1, 8, 8),               loss_fn=CrossEntropyLoss(),               net_params={},               net_type=<class '__main__.SimpleCNN'>,               optim_params={'lr': 0.001},               optim_type=<class 'torch.optim.adam.Adam'>,               tol_epochs=10),           bootstrap=True, bootstrap_features=False, max_features=1.0,           max_samples=1.0, n_estimators=10, n_jobs=None,           oob_score=False, random_state=None, verbose=0,           warm_start=False)

Теперь можно с помощью уже реализованной функции score оценить accuracy нашего ансамбля:

print(meta_classifier.score(x_test_scaled.reshape(-1, 64), y_test)) >>> 0.95

Некоторые выводы и куда двигаться дальше

Ансамбль действительно лучше справляется с задачей классификации, чем одна модель. Это происходит благодаря большей обобщающей способности. У ансамблей есть ряд преимуществ по сравнению с обычными моделями. Например, товарищи обнаружили, что ансамбли являются более устойчивыми на новых данных (то есть их предсказания не начинают "скакать" из-за неуверенности модели).

Что можно улучшить?

Во-первых, обучение. Возможно, нужно смотреть на Loss'ы или как-нибудь подбирать параметры для остановки обучения на ходу.

Во-вторых, можно реализовать Boosting. Его отличие от Bagging'а в том, что модели строятся не независимо, а каждая следующая учитывает ошибки предыдущей. То есть при обучении неудачным (неправильно классифицированным) примерам придается больший вес. Тут лучше покопаться в исходном коде sklearn, а потом можно поэлементно умножать Loss нейронки на какие-то веса.

В третьих, можно формировать ансамбль из разных видов нейронок. Тогда увеличивается разница в том, как модели "видят" данные. Как будто у метамодели появляется новая точка зрения, и она может глубже понять ситуацию.

Терпеливому читателю

Спасибо, что дочитали этот пост до конца. Мне было бы очень интересно услышать Ваше мнение по поводу статьи — какие-то замечания, предложения, идеи, в общем, все, что Вас воодушевляет и чем Вы готовы поделиться с миром.

Пару слов об мне. Меня зовут Евгений, Data science'ом я занимаюсь уже полтора года. Сейчас в большей степени погружаюсь в Computer vision, но также интересуюсь нейротехнологиями. (Соединить искусственные и натуральные нейронные сети — моя мечта!) Этот пост создан благодаря нашей команде — FARADAY Lab. Мы — начинающие российские стартаперы и готовы делиться с Вами тем, что узнаем сами.

Удачи c:

Полезные ссылки


Источник: habr.com

Комментарии: