Добрый день, уважаемые читатели. Темой нашей сегодняшней статьи станет объединение библиотек Keras и Sklearn.

Подпишись на группу Вконтакте и Телеграм-канал. Там еще больше полезного контента для программистов.А на  YouTube-канале ты найдешь обучающие видео по программированию. Подписывайся!

Действительно, в поисках лучшей архитектуры сети мы много раз обучаем несколько вариантов модели на определённом наборе данных, а затем сравниваем их обобщающую способность. Сегодня я предлагаю автоматизировать эту процедуру, использовав пользовательский класс и GridSearchCV.

Для тех, кто не знает, как работает сеточный поиск и конвейеры в sklearn, советую ознакомиться с моей прошлой статьей.

Импорт модулей и загрузка данных

Начнём наш сегодняшний эксперимент с импортирования всех необходимых функций и классов.

In[1]:
# Импортирование необходимых функций и классов 
import numpy as np

from sklearn.datasets import load_breast_cancer

from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

from tensorflow.keras.wrappers.scikit_learn import KerasClassifier
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout

from tensorflow.keras.utils import plot_model
from PIL import Image

Не буду здесь объяснять назначение каждого импорта, я буду использовать все функции постепенно.

В качестве набора данных я предпочёл Breast Cancer. Этот датасет можно считать классическим в сфере классификации. Набор содержит 569 наблюдений, каждое из которых содержит 30 признаков.

In[2]:
# Загрузка данных
dataset = load_breast_cancer()
X = np.array(dataset.data)
y = np.array(dataset.target)
X.shape, y.shape

((569, 30), (569,))

Разделим набор на обучающие и тестовые данные. Для этого используем такой инструмент, как функцию train_test_split из модуля model_selection библиотеки sklearn. В моём случае тестовый набор будет составлять 20% от общего.

In[3]:
# Разделение данных на тренировочные/тестовые
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

Пользовательский класс для построения модели

Немного забегая вперёд, скажу, что GridSearchCV использует подстановку гиперпараметров в классы моделей для дальнейшего их обучения. Однако использование класса Sequential() не очень подходит под эту задачу. Поэтому мы реализуем свой класс, в котором построим модель и приведём её к программному интерфейсу sklearn.

# Класс кастомного классификатора
class ModelBuilder(object):
  # Конструктор класса
  def __init__(self):
    pass

Я объявляю класс и также описываю, что его конструктор не принимает никаких аргументов, а также ничего не выполняет.

  # Метод, используемый GridSearchCV 
  # для установки параметров
  def set_params(self, 
                 layers=[],
                 optimizer='rmsprop',
                 loss='binary_crossentropy',
                 metric=['accuracy'],
                 epochs=200,
                 batch_size=5):
     
    # Установка параметров в качестве полей класса
    self.__layers = layers
    self.__optimizer = optimizer
    self.__loss = loss
    self.__metric = metric
    self.__epochs = epochs
    self.__batch_size = batch_size

     
    # Преобразования модели в классификатор sklearn
    self.model = KerasClassifier(build_fn=self.__build, 
                                 epochs=epochs,
                                 batch_size=batch_size)

Класс GridSearchCV использует метод set_params(**params) для подстановки параметров.

Сюда я буду передавать список слоёв сети, тип оптимизатора, меру измерения ошибки, метрику оценивая, кол-во эпох и размер мини-пакета для обучения.

С помощью Keras класса KerasClassifier() я преобразовываю нашу сеть, реализованную в классе Sequential(), так, чтобы я мог использовать модель в различных операциях sklearn. Конструктор класса принимает функцию, которая должна возвращать саму нейронную сеть, кол-во эпох и размер батча.

  # Функция для постройки модели   
  def __build(self):
     
    model = Sequential(self.__layers)
       
    model.compile(optimizer=self.__optimizer, 
                  loss=self.__loss, 
                  metrics=self.__metric)
     
    return model

Также мы переопределяем методы fit(), predict() и score(), т.к. их использует алгоритм сеточного поиска.

  # Адаптирование модели   
  def fit(self, X, y):
     
    return self.model.fit(x=X, y=y)
   
   
  # Предсказание модели
  def predict(self, X):
     
    return self.model.predict(X)
   
   
  # Вычисление точности предсказания
  def score(self, X, y):
     
    return self.model.score(X, y)

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

Сеточный поиск

Так выглядит словарь со всеми значениями параметров нашего класса:

In[5]:
# Различные наборы слоёв
params = {
  'clf__layers': [[Dense(256, activation='relu', input_shape=(30, )),                            Dense(256, activation='relu'), Dropout(0.4), Dense(1, activation='sigmoid')],
          [Dense(64, activation='relu', input_shape=(30, )), Dropout(0.4), Dense(64, activation='relu'), Dense(1, activation='sigmoid')],
          [Dense(16, activation='relu', input_shape=(30, )), Dropout(0.4), Dense(16, activation='relu'), Dense(16, activation='relu'),
          Dropout(0.4), Dense(1, activation='sigmoid')]
          ],
  'clf__optimizer': ['rmsprop'],
  'clf__loss': ['binary_crossentropy'],
  'clf__metric': [['accuracy']],
  'clf__epochs': [25, 50]
  }     

Позже я рассмотрю инструмент, с помощью которого вы сможете визуализировать все три предложенные мною архитектуры.

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

In[6]:
# Инициализация трансформера
scaler = StandardScaler()

# Инициализация классификатора
clf = ModelBuilder()

# Создание конвеера
pipeline = Pipeline([('scaler', scaler), ('clf', clf)])

Далее я создал экземпляр класса, отвечающего за сеточный поиск.

In[7]:
# Построение класса, выполняющего сеточный поиск
grid_search = GridSearchCV(pipeline, params)

И сразу запустил поиск на тренировочных данных:

In[8]:
# Алгоритм обучает множество моделей на тренировочных данных
grid_search.fit(X_train, y_train)

Результат

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

In[9]:
# Лучшие "гиперпараметры", найдены поиском
params = grid_search.best_params_

layers    = params['clf__layers']
metric    = params['clf__metric']
optimizer = params['clf__optimizer']
epochs    = params['clf__epochs']
loss      = params['clf__loss']

Теперь, основываясь на этих параметрах, я создад экземпляр класса Sequential() с такой архитектурой и параметра компилирования.

In[10]:
# Построение модели, основываясь на лучшие "гиперпараметры"
model = Sequential(layers)

model.compile(optimizer=optimizer,
              loss=loss,
              metrics=metric)

# Визуализация архитектуры
plot_model(model, to_file='C:\model.png')

С помощью функции plot_model() (прежде чем использовать её, установите PyDot — pip/conda install pydot) я сохраню изображение, которое будет содержать отображение архитектуры нашей сети.

С помощью Pillow.Image выводится изображение на экран:

In[11]:
# Вывод изображения
img = Image.open("model.png")

img

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

In[12]:
model = grid_search.best_estimator_

train_score = model.score(X_train, y_train)
test_score = model.score(X_test, y_test)

print('Score on train data is {0}'.format(train_score))
print('Score on test data is {0}'.format(test_score))

Score on train data is 0.995604395866394
Score on test data is 0.9824561476707458

Заключение

Сегодня я продемонстрировал способ автоматизировать поиск нужного кол-ва слоёв в нейронной сети, связав sklearn и Keras. Надеюсь, эта статья была для вас полезна. Желаю вам низких ошибок и надёжных опорных векторов.

Документ с расширением .ipynb, который вы сможете просмотреть и изменить на своё усмотрение, находится по ссылке.