Обучаюсь на курсе «Нейронные сети на Python» в «Университете искуственного интеллекта«. Читает Дмитрий Романов. Недавно было занятие по автокодировщикам. Почитал доп. литературу и ниже написал подробно как работает простой автокодировщик с примером кода на Keras.
Import буду писать под каждый ключевой блок кода, чтобы понятно было какая библиотека нужна для поключения того или иного функционала.
Для начала импортируем тестовую базу MNIST с изображениями рукописных букв. Размер каждой картинки 28 * 28 пикселей.
from keras.datasets import mnist import numpy as np (x_train_src, _), (x_test_src, _) = mnist.load_data() #загружаем базу картинок MNIST
Если вывести размер загруженного массива
print("x_train: ", x_train_src.shape, "x_test: ", x_test_src.shape) print("Размер изображения:", x_train_src.shape[1:]) print("Кол-во изображений:", len(x_train_src)) print("Размерность вектора:", np.prod(x_train.shape[1:])) x_train: (60000, 28, 28) x_test: (10000, 28, 28) Размер изображения: (28, 28) Кол-во изображений: 60000 Размерность вектора: 784
то увидим, что в базе 60 000 картинок и каждая размером 28 х 28. Поскольку для того, чтобы подать картинку на Dense слой в Keras (многослойный перцептрон) нужен вектор, растянем двумерную матрицу в вектор размерностью (длиной) 28 x 28 = 784 с помощью reshape.
vector_d = np.prod(x_train_src.shape[1:]) # 28 * 28 x_train = x_train_src.reshape(x_train_src.shape[0], vector_d) x_test = x_test_src.reshape(x_test_src.shape[0], vector_d) x_train = x_train.astype('float32') / 255. x_test = x_test.astype('float32') / 255. print (x_train.shape) print (x_test.shape) (60000, 784) (10000, 784)
Метод numpy.prod (анг. product — произведение) в numpy array производит перемножение элементов вектора. Можно было бы получить длину, просто перемножив количество колонок на количество строк: 28 * 28, но в случае загрузки картинок другого размера пришлось бы править код.
Приведем величину значений в векторе в диапазон от 0 до 1 ([0, 1])(нормализуем):
x_train = x_train.astype('float32') / 255. x_test = x_test.astype('float32') / 255.
Encoder (кодировщик)
Первая часть автокодировщика — encoder. Размер внутреннего (скрытого) слоя H автокодировщика сделаем равным encoding_dim = 32. Т.е в случае входного вектора 784 / 32 = 24.5 — степень компрессии, т.е. во сколько раз исходное изображение будет сжато во внутреннем слое.
from keras.layers import Input, Dense from keras.models import Model # this is the size of our encoded representations encoding_dim = 32 # 32 floats -> compression of factor 24.5, assuming the input is 784 floats # this is our input placeholder input_img = Input(shape=(vector_d,), name='Input') # "encoded" is the encoded representation of the input encoded = Dense(encoding_dim, activation='relu', name='Encoded')(input_img) # "decoded" is the lossy reconstruction of the input decoded = Dense(vector_d, activation='sigmoid', name='Decoded')(encoded) # this model maps an input to its reconstruction autoencoder = Model(inputs = input_img, outputs = decoded, name='Autoencoder') autoencoder.summary()
Model: "Autoencoder" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= Input (InputLayer) (None, 784) 0 _________________________________________________________________ Encoded (Dense) (None, 32) 25120 _________________________________________________________________ Decoded (Dense) (None, 784) 25872 ================================================================= Total params: 50,992 Trainable params: 50,992 Non-trainable params: 0 _________________________________________________________________
Если модель разобрать на encoder и decoder, то для кодировщика на вход модели придет вектор размерностью 784 и сожмется до 32.
Поскольку длина вектора изображения 784, то использовать на внутреннем слое H для тренировки модели encoder-а не получится, поскольку для него нужны вектора размерностью 32.
# this model maps an input to its encoded representation encoder = Model(inputs = input_img, outputs = encoded, name='Encoder') encoder.summary()
Model: "Encoder" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= Input (InputLayer) (None, 784) 0 _________________________________________________________________ Encoded (Dense) (None, 32) 25120 ================================================================= Total params: 25,120 Trainable params: 25,120 Non-trainable params: 0 _________________________________________________________________
Соответственно откомпилировать эту модель можно, но вот тренировку (fit) делать не на чем. Нет данных зажатых до размерности 32.
Декодер разожмет сжатую до вектора размерностью 32 исходную последовательность снова в 784.
# create a placeholder for an encoded (32-dimensional) input encoded_input = Input(shape=(encoding_dim,), name='DecoderInput') # retrieve the last layer of the autoencoder model #decoder_layer = autoencoder.layers['Encoded'-1] decoder_layer = autoencoder.get_layer('Decoded') decoder = Model(encoded_input, decoder_layer(encoded_input), name='Decoder') decoder.summary()
Model: "Decoder" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= DecoderInput (InputLayer) (None, 32) 0 _________________________________________________________________ Decoded (Dense) (None, 784) 25872 ================================================================= Total params: 25,872 Trainable params: 25,872 Non-trainable params: 0 _________________________________________________________________
autoencoder.compile(optimizer='adadelta', loss='binary_crossentropy') autoencoder.fit(x_train, x_train, epochs=50, batch_size=256, shuffle=True, validation_data=(x_test, x_test))
После обучения получаем val_loss: ~0.1017 Восстановим изображение после обработки автокодировщиком.
Можно использовать прогноз раздельными моделями, отдельно для кодировщика и декодировщика.
# encode and decode some digits # note that we take them from the *test* set encoded_imgs = encoder.predict(x_test) decoded_imgs = decoder.predict(encoded_imgs)
или объединенную модель
decoded_imgs = autoencoder.predict(x_test)
Преобразовываем вектора размерностью 784 в картинки 28 х 28 используя shape исходной тестовой выборки.
decoded_imgs = decoded_imgs.reshape(x_test_src.shape)
Визуализируем оригинальные и восстановленные изображения:
import matplotlib.pyplot as plt def plotImage(data, plt, n, i, row = 0): ax = plt.subplot(2, n, i + 1 + row * n) plt.imshow(data[i]) plt.gray() ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) def plotImages(data1, data2, n): plt.figure(figsize=(20, 4)) for i in range(n): plotImage(data1, plt, n, i, 0) plotImage(data2, plt, n, i, 1) plt.show() plotImages(x_test_src, decoded_imgs, 10)
Полезные ссылки
- Эксперименты с многослойным перцептроном в Keras.
- https://blog.keras.io/building-autoencoders-in-keras.html
- https://trainmydata.com/article/kak-ispolzovat-keras-api-dlia-ghlubokogho-obuchieniia
- Автоэнкодеры в Keras, Часть 1: Введение
- Сверточная нейронная сеть, часть 1: структура, топология, функции активации и обучающее множество