Эксперименты с входными данными в глубоком обучении

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

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

Идея на сегодня

Моя идея на сегодня: вместо последовательной модели реализовать модель с несколькими входами, каждый из которых принимает подмножество начальных признаков (эти подмножество пересекаются). Как считаете, приведёт ли это к улучшению обобщающей способности? Давайте проверим!

Загрузка данных

Для проверки этой идеи нам не стоит перезагружать своё железо большими вычислениями. Мы будем использовать BreastCancer — этот датасет очень часто использовался в моих статьях, а также он входит в набор датасетов sklearn.

Импортируем необходимые классы и функции:

In[1]:
from tensorflow.python.keras.layers import Dense, Dropout, Input, Concatenate
from tensorflow.python.keras.models import Model, Sequential

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import normalize

Также загрузим данные и проведём их нормализацию:

In[2]:
data = load_breast_cancer()

X = normalize(data.data * 10)
y = data.target

Создание и сравнение моделей

Теперь создадим последовательную модель Keras. Этот процесс уже описывался в этой статье (если вы не знаете, как это делать, то эта статья обязательна к прочтению).

In[3]:
model = Sequential()

model.add(Dense(64, activation='relu'))
model.add(Dense(32, activation='relu'))
model.add(Dropout(0.4))
model.add(Dense(32, activation='relu'))
model.add(Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['accuracy'])

Также разделим данные на обучающую и тестовую выборку:

In[4]:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

X_train.shape, y_train.shape

Out[4]:
((455, 30), (455,))

Сохраним историю обучения модели на обучающих данных. Её я использовать не буду, однако она полностью в вашем распоряжении :) ).

In[5]:
history = model.fit(X_train, y_train, batch_size=128, epochs=100)

С помощью функционального API создадим модель с тремя входами (всё это описывалось также в этой статье).

In[6]:
input_layer_1 = Input(shape=(15, ))

input_layer_2 = Input(shape=(15, ))

input_layer_3 = Input(shape=(15, ))

layer_1 = Dense(32, activation='relu')(input_layer_1)
layer_2 = Dense(32, activation='relu')(input_layer_2)
layer_3 = Dense(32, activation='relu')(input_layer_3)

concatenate = Concatenate(axis=1)([layer_1, layer_2, layer_3])

dropout = Dropout(0.4)(concatenate)

layer_4 = Dense(32, activation='relu')(dropout)
layer_5 = Dense(1, activation='sigmoid')(layer_4)

model_ = Model(inputs=[input_layer_1, input_layer_2, input_layer_3], outputs=layer_5)

model_.compile(optimizer='rmsprop',
               loss='binary_crossentropy',
               metrics=['accuracy'])

Итак, теперь самое интересное: разделим обучающую и тестовую выборки на три набора путём индексации (индексация массивов NumPy происходит также, как и в Python).

In[7]:
input_1 = X_train[:, :15]
input_2 = X_train[:, 5:20]
input_3 = X_train[:, 14:-1]

test_1 = X_test[:, :15]
test_2 = X_test[:, 5:20]
test_3 = X_test[:, 14:-1]

Почему именно такие индексы? Обратите внимание, мы указали shape=(15, ) в классе Input(). А значит именно такой формы должен быть отдельный образец из выборок.

Сохраним историю обучения новой модели:

In[8]:
history_ = model_.fit([input_1, input_2, input_3], 
                      y_train, 
                      batch_size=128, epochs=100)

А теперь сравним обобщающую способность обоих моделей на тестовых данных:

In[9]:
model.evaluate(X_test, y_test)[1]

114/114 [==============================] - 0s 1ms/sample - loss: 0.3172 - acc: 0.8596

Out[9]:
0.8596491
In [10]:
model_.evaluate([test_1, test_2, test_3], y_test)[1]

114/114 [==============================] - 0s 1ms/sample - loss: 0.4026 - acc: 0.8333

Out[10]:
0.8333333

Таким образом, обучив модели много раз, я пришёл к выводу, что сегодняшняя идея вряд ли приведёт к улучшению модели. Я предполагаю, что это связанно с потерей выделения связи между некоторыми признаками (например, между первым и последним).

Заключение

К сожалению, мы не добились успеха в этот раз. Однако, скорее всего, нам повезёт, и мы найдём «новый рецепт» глубокого обучения. Желаю вам всегда избегать локального минимума!

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

Также рекомендую прочитать статью Функциональный API библиотеки Keras. А также подписывайтесь на группу ВКонтакте, Telegram и YouTube-канал. Там еще больше полезного и интересного для программистов.