Рубрики
Без рубрики

Автоэнкодеры для восстановления изображений в Python и Keras

В мире, управляемом данными, оптимизация его размера имеет первостепенное значение. Автоэнкодеры автоматически кодируют и декодируют информацию для удобства транспортировки. В этой статье мы будем использовать Python и Keras для создания автоэнкодера с использованием глубокого обучения.

Автор оригинала: Ali Abdelaal.

Вступление

В настоящее время у нас есть огромные объемы данных почти в каждом приложении, которое мы используем – слушаем музыку на Spotify, просматриваем изображения друзей в Instagram или, возможно, смотрим новый трейлер на YouTube. С серверов вам всегда передаются данные.

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

Существует множество методов сжатия, и они различаются по своему использованию и совместимости. Например, некоторые методы сжатия работают только с аудиофайлами, например знаменитый кодек MPEG-2 Audio Layer III (MP3).

Существует два основных типа сжатия:

  • Lossless : Целостность и точность данных предпочтительнее, даже если мы не слишком “сбриваем” их
  • Lossy : Целостность и точность данных не так важны, как скорость их обслуживания – представьте себе передачу видео в реальном времени, где важнее быть “живым”, чем иметь высококачественное видео

Например, используя Autoencoders , мы можем разложить это изображение и представить его в виде 32-векторного кода ниже. Используя его, мы можем восстановить изображение. Конечно, это пример сжатия lossy , так как мы потеряли довольно много информации.

автоэнкодер низкого размера

Однако мы можем использовать точно такую же технику, чтобы сделать это гораздо точнее, выделив больше места для представления:

автоэнкодер высокого размера

Что такое автоэнкодеры?

Автоэнкодер-это, по определению, метод автоматического кодирования чего-либо. Используя нейронную сеть, автоэнкодер способен научиться декомпозировать данные (в нашем случае изображения) на довольно маленькие биты данных, а затем, используя это представление, реконструировать исходные данные настолько близко, насколько это возможно.

В этой задаче есть два ключевых компонента:

  • Encoder : Учится сжимать исходный ввод в небольшую кодировку
  • Декодер : Учится восстанавливать исходные данные из этой кодировки, сгенерированной кодировщиком

Эти двое обучаются вместе в симбиозе, чтобы получить наиболее эффективное представление данных, из которых мы можем восстановить исходные данные, не теряя их так много.

схема автоэнкодера

Кредит: Исследовательские ворота

Кодировщик

Задача Кодера состоит в том, чтобы найти наименьшее возможное представление данных, которое он может хранить, – извлечь наиболее заметные особенности исходных данных и представить их таким образом, чтобы декодер мог их понять.

Думайте об этом так, как если бы вы пытались запомнить что – то, например, запомнить большое число-вы пытаетесь найти в нем шаблон, который вы можете запомнить и восстановить всю последовательность из этого шаблона, так как будет легко запомнить более короткий шаблон, чем целое число.

Кодеры в их простейшей форме-это простые Искусственные нейронные сети (ANNs). Однако существуют определенные кодеры, которые используют Сверточные нейронные сети (CNNs), что является очень специфическим типом ANN.

Кодировщик принимает входные данные и генерирует их закодированную версию – сжатые данные. Затем мы можем использовать эти сжатые данные, чтобы отправить их пользователю, где они будут декодированы и восстановлены. Давайте посмотрим на кодировку для примера набора данных LFW :

сгенерированные кодировки

Кодирование здесь не имеет большого смысла для нас, но этого достаточно для декодера. Теперь уместно поставить этот вопрос:

“Но как кодировщик научился так сжимать изображения?

Вот тут-то и вступает в игру симбиоз во время тренировки.

Декодер

Декодер работает аналогично кодеру, но наоборот. Он учится читать, а не генерировать эти сжатые представления кода и генерировать изображения на основе этой информации. Очевидно, она направлена на минимизацию потерь при реконструкции.

Выход оценивается путем сравнения восстановленного изображения с исходным, используя среднеквадратичную ошибку (MSE) – чем больше оно похоже на оригинал, тем меньше ошибка.

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

Построение автоэнкодера

Keras – это фреймворк Python, который упрощает построение нейронных сетей. Это позволяет нам складывать слои разных типов для создания глубокой нейронной сети – что мы и сделаем, чтобы построить автоэнкодер.

Во-первых, давайте установим Keras с помощью pip:

$ pip install keras

Предварительная Обработка Данных

Опять же, мы будем использовать набор данных LFW . Как обычно, в подобных проектах мы предварительно обрабатываем данные, чтобы нашему автоэнкодеру было легче выполнять свою работу.

Для этого мы сначала определим несколько путей, которые ведут к набору данных, который мы используем:

# http://www.cs.columbia.edu/CAVE/databases/pubfig/download/lfw_attributes.txt
ATTRS_NAME = "lfw_attributes.txt"

# http://vis-www.cs.umass.edu/lfw/lfw-deepfunneled.tgz
IMAGES_NAME = "lfw-deepfunneled.tgz"

# http://vis-www.cs.umass.edu/lfw/lfw.tgz
RAW_IMAGES_NAME = "lfw.tgz"

Затем мы используем две функции – одну для преобразования необработанной матрицы в изображение и изменения цветовой системы на RGB:

def decode_image_from_raw_bytes(raw_bytes):
    img = cv2.imdecode(np.asarray(bytearray(raw_bytes), dtype=np.uint8), 1)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    return img

А другой-фактически загрузить набор данных и адаптировать его к нашим потребностям:

def load_lfw_dataset(
        use_raw=False,
        dx=80, dy=80,
        dimx=45, dimy=45):

    # Read attrs
    df_attrs = pd.read_csv(ATTRS_NAME, sep='\t', skiprows=1)
    df_attrs = pd.DataFrame(df_attrs.iloc[:, :-1].values, columns=df_attrs.columns[1:])
    imgs_with_attrs = set(map(tuple, df_attrs[["person", "imagenum"]].values))

    # Read photos
    all_photos = []
    photo_ids = []

    # tqdm in used to show progress bar while reading the data in a notebook here, you can change
    # tqdm_notebook to use it outside a notebook
    with tarfile.open(RAW_IMAGES_NAME if use_raw else IMAGES_NAME) as f:
        for m in tqdm.tqdm_notebook(f.getmembers()):
            # Only process image files from the compressed data
            if m.isfile() and m.name.endswith(".jpg"):
                # Prepare image
                img = decode_image_from_raw_bytes(f.extractfile(m).read())

                # Crop only faces and resize it
                img = img[dy:-dy, dx:-dx]
                img = cv2.resize(img, (dimx, dimy))

                # Parse person and append it to the collected data
                fname = os.path.split(m.name)[-1]
                fname_splitted = fname[:-4].replace('_', ' ').split()
                person_id = ' '.join(fname_splitted[:-1])
                photo_number = int(fname_splitted[-1])
                if (person_id, photo_number) in imgs_with_attrs:
                    all_photos.append(img)
                    photo_ids.append({'person': person_id, 'imagenum': photo_number})

    photo_ids = pd.DataFrame(photo_ids)
    all_photos = np.stack(all_photos).astype('uint8')

    # Preserve photo_ids order!
    all_attrs = photo_ids.merge(df_attrs, on=('person', 'imagenum')).drop(["person", "imagenum"], axis=1)

    return all_photos, all_attrs

Реализация автоэнкодера

import numpy as np
X, attr = load_lfw_dataset(use_raw=True, dimx=32, dimy=32)

Наши данные находятся в матрице X в виде 3D-матрицы, которая является представлением по умолчанию для изображений RGB. Предоставляя три матрицы – красную, зеленую и синюю, комбинация этих трех матриц генерирует цвет изображения.

Эти изображения будут иметь большие значения для каждого пикселя в диапазоне от 0 до 255. Обычно в машинном обучении мы склонны делать значения маленькими и центрированными вокруг 0, так как это помогает нашей модели обучаться быстрее и получать лучшие результаты, поэтому давайте нормализуем наши изображения:

X = X.astype('float32') / 255.0 - 0.5

К настоящему времени , если мы проверим массив X для min и max, он будет -.5 и .5 , что вы можете проверить:

print(X.max(), X.min())
0.5 -0.5

Чтобы увидеть изображение, давайте создадим функцию show_image . Это добавит 0.5 к изображениям так как значение пикселя не может быть отрицательным:

import matplotlib.pyplot as plt
def show_image(x):
    plt.imshow(np.clip(x + 0.5, 0, 1))

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

show_image(X[6])
лицо предварительно обработанных данных

Отлично, теперь давайте разделим наши данные на обучающий и тестовый набор:

from sklearn.model_selection import train_test_split
X_train, X_test = train_test_split(X, test_size=0.1, random_state=42)

Функция sklearn train_test_split() способна разделить данные, задав им тестовое соотношение, а остальное-это, конечно, размер обучения. Функция random_state , которую вы часто увидите в машинном обучении, используется для получения одних и тех же результатов независимо от того, сколько раз вы запускаете код.

Теперь время для модели:

from keras.layers import Dense, Flatten, Reshape, Input, InputLayer
from keras.models import Sequential, Model

def build_autoencoder(img_shape, code_size):
    # The encoder
    encoder = Sequential()
    encoder.add(InputLayer(img_shape))
    encoder.add(Flatten())
    encoder.add(Dense(code_size))

    # The decoder
    decoder = Sequential()
    decoder.add(InputLayer((code_size,)))
    decoder.add(Dense(np.prod(img_shape))) # np.prod(img_shape) is the same as 32*32*3, it's more generic than saying 3072
    decoder.add(Reshape(img_shape))

    return encoder, decoder

Эта функция принимает в качестве параметров image_shape (размеры изображения) и code_size (размер выходного представления). Форма изображения, в нашем случае, будет (32, 32, 3) где 32 представляют ширину и высоту, а также 3 представляет собой матрицы цветовых каналов. Тем не менее, наш образ имеет 3072 размеры.

Логически, чем меньше code_size , тем больше изображение будет сжиматься, но тем меньше функций будет сохранено, и воспроизводимое изображение будет намного больше отличаться от оригинала.

Последовательная модель Keras в основном используется для последовательного добавления слоев и углубления нашей сети. Каждый слой переходит в следующий, и здесь мы просто начинаем с входного слоя (заполнителя для ввода) с размером входного вектора – image_shape .

Задача слоя Flatten состоит в том, чтобы сгладить матрицу (32,32,3) в 1D массив ( 3072 ) поскольку сетевая архитектура не принимает 3D-матрицы.

Последний слой в кодере-это Плотный слой, который здесь является фактической нейронной сетью. Он пытается найти оптимальные параметры, которые обеспечивают наилучший выход – в нашем случае это кодирование, и мы установим его выходной размер (а также количество нейронов в нем) в code_size .

Декодер также является последовательной моделью. Он принимает входные данные (кодировку) и пытается восстановить их в виде строки. Затем он складывает его в матрицу 32x32x3 через Плотный слой. Последний слой Reshape преобразует его в изображение.

Теперь давайте соединим их вместе и начнем нашу модель:

# Same as (32,32,3), we neglect the number of instances from shape
IMG_SHAPE = X.shape[1:]
encoder, decoder = build_autoencoder(IMG_SHAPE, 32)

inp = Input(IMG_SHAPE)
code = encoder(inp)
reconstruction = decoder(code)

autoencoder = Model(inp,reconstruction)
autoencoder.compile(optimizer='adamax', loss='mse')

print(autoencoder.summary())

Этот код довольно прост – наша переменная code является выходом кодера, который мы помещаем в декодер и генерируем переменную reconstruction .

Затем мы связываем их оба, создавая Модель с параметрами inp и реконструкция и компилируем их с adamax оптимизатором и mse функцией потерь.

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

На этом этапе мы можем подвести итоги:

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
input_6 (InputLayer)         (None, 32, 32, 3)         0
_________________________________________________________________
sequential_3 (Sequential)    (None, 32)                98336
_________________________________________________________________
sequential_4 (Sequential)    (None, 32, 32, 3)         101376
=================================================================
Total params: 199,712
Trainable params: 199,712
Non-trainable params: 0
_________________________________________________________________

Здесь мы можем видеть, что вход 32,32,3 . Обратите внимание, что None здесь относится к индексу экземпляра, поскольку мы даем данные модели , она будет иметь форму (m, 32,32,3) , где m – количество экземпляров, поэтому мы сохраняем его как None .

Скрытый слой-это 32 , который действительно является выбранным нами размером кодирования, и, наконец, выход декодера, как вы видите, равен (32,32,3) .

А теперь давайте поменяемся моделью:

history = autoencoder.fit(x=X_train, y=X_train, epochs=20,
                validation_data=[X_test, X_test])

В нашем случае мы будем сравнивать построенные изображения с исходными, так что оба x и y равны X_train . В идеале вход равен выходу.

Переменная epochs определяет, сколько раз мы хотим, чтобы обучающие данные проходили через модель, а validation_data – это набор проверки, который мы используем для оценки модели после обучения:

Train on 11828 samples, validate on 1315 samples
Epoch 1/20
11828/11828 [==============================] - 3s 272us/step - loss: 0.0128 - val_loss: 0.0087
Epoch 2/20
11828/11828 [==============================] - 3s 227us/step - loss: 0.0078 - val_loss: 0.0071
.
.
.
Epoch 20/20
11828/11828 [==============================] - 3s 237us/step - loss: 0.0067 - val_loss: 0.0066

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

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()
потеря/эпоха

Мы видим, что после третьей эпохи не наблюдается значительного прогресса в потерях. Визуализация, подобная этой, может помочь вам лучше понять, сколько эпох действительно достаточно для обучения вашей модели. В этом случае просто нет необходимости тренировать его для 20 эпохи, и большая часть обучения является избыточной.

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

Теперь самая ожидаемая часть – давайте визуализируем результаты:

def visualize(img,encoder,decoder):
    """Draws original, encoded and decoded images"""
    # img[None] will have shape of (1, 32, 32, 3) which is the same as the model input
    code = encoder.predict(img[None])[0]
    reco = decoder.predict(code[None])[0]

    plt.subplot(1,3,1)
    plt.title("Original")
    show_image(img)

    plt.subplot(1,3,2)
    plt.title("Code")
    plt.imshow(code.reshape([code.shape[-1]//2,-1]))

    plt.subplot(1,3,3)
    plt.title("Reconstructed")
    show_image(reco)
    plt.show()

for i in range(5):
    img = X_test[i]
    visualize(img,encoder,decoder)
результаты кодирования pca
результаты кодирования pca 2
результаты кодирования pca 3
результаты кодирования pca 4
результаты кодирования pca 5

Вы можете видеть, что результаты не очень хорошие. Однако если принять во внимание, что все изображение закодировано в чрезвычайно малом векторе 32 если смотреть посередине, то это совсем неплохо. Через сжатие от 3072 размеры для того чтобы как раз 32 мы теряем много данных.

Теперь давайте увеличим code_size до 1000 :

результаты pca 1000
результаты pca 1000
результаты pca 1000
результаты pca 1000
результаты pca 1000

Видите разницу? Поскольку вы предоставляете модели больше места для работы, она сохраняет более важную информацию об изображении

Примечание: Кодировка не является двумерной, как показано выше. Это просто для иллюстрации. На самом деле это одномерный массив из 1000 измерений.

То, что мы только что сделали, называется Анализ главных компонент (PCA), который представляет собой метод уменьшения размерности . Мы можем использовать его, чтобы уменьшить размер набора функций, генерируя новые функции, которые меньше по размеру, но все же захватывают важную информацию.

Анализ главных компонентов – очень популярное использование автоэнкодеров.

Шумоподавление изображения

Еще одно популярное использование автоэнкодеров-шумоподавление. Давайте добавим немного случайного шума к нашим фотографиям:

def apply_gaussian_noise(X, sigma=0.1):
    noise = np.random.normal(loc=0.0, scale=sigma, size=X.shape)
    return X + noise

Здесь мы добавляем некоторый случайный шум из стандартного нормального распределения со шкалой sigma , которая по умолчанию равна 0.1 .

Для справки, вот как выглядит шум с различными значениями sigma :

plt.subplot(1,4,1)
show_image(X_train[0])
plt.subplot(1,4,2)
show_image(apply_gaussian_noise(X_train[:1],sigma=0.01)[0])
plt.subplot(1,4,3)
show_image(apply_gaussian_noise(X_train[:1],sigma=0.1)[0])
plt.subplot(1,4,4)
show_image(apply_gaussian_noise(X_train[:1],sigma=0.5)[0])
изображение с шумом

Как мы видим, по мере увеличения sigma 0.5 изображение почти не видно. Мы попытаемся восстановить исходное изображение из шумных с сигмой 0.1 .

Модель, которую мы создадим для этого, такая же, как и предыдущая, хотя мы будем тренировать ее по-другому. На этот раз мы будем тренировать его с оригинальными и соответствующими шумными изображениями:

code_size = 100

# We can use bigger code size for better quality
encoder, decoder = build_autoencoder(IMG_SHAPE, code_size=code_size)

inp = Input(IMG_SHAPE)
code = encoder(inp)
reconstruction = decoder(code)

autoencoder = Model(inp, reconstruction)
autoencoder.compile('adamax', 'mse')

for i in range(25):
    print("Epoch %i/25, Generating corrupted samples..."%(i+1))
    X_train_noise = apply_gaussian_noise(X_train)
    X_test_noise = apply_gaussian_noise(X_test)

    # We continue to train our model with new noise-augmented data
    autoencoder.fit(x=X_train_noise, y=X_train, epochs=1,
                    validation_data=[X_test_noise, X_test])

Теперь давайте посмотрим на результаты модели:

X_test_noise = apply_gaussian_noise(X_test)
for i in range(5):
    img = X_test_noise[i]
    visualize(img,encoder,decoder)
шумоподавление изображения с помощью автоэнкодера
шумоподавление изображения с помощью автоэнкодера
шумоподавление изображения с помощью автоэнкодера
шумоподавление изображения с помощью автоэнкодера
шумоподавление изображения с помощью автоэнкодера

Приложения Автоэнкодера

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

Автоэнкодер можно использовать в таких приложениях , как Deep fades , где у вас есть кодер и декодер из разных моделей.

Например, предположим, что у нас есть два автоэнкодера для Person X и один для Person Y . Ничто не мешает нам использовать кодер Person X и декодер Person Y , а затем генерировать изображения Person Y с выдающимися чертами Person X :

замена лица

Кредит: Алан Цуккони

Автоэнкодеры также могут использоваться для сегментации изображений – например, в автономных автомобилях, где вам нужно сегментировать различные предметы, чтобы автомобиль принял решение:

сегментация изображений

Кредит: Бумаги С Кодом

Вывод

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

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