Автоэнкодеры и вариационные автоэнкодеры
- О чём эта тема
- Первый конспект про обучение без учителя: сеть учится сжимать объекты в компактное скрытое представление и восстанавливать их обратно. Разбираются автоэнкодер и его применения (очистка от шума, поиск аномалий), пределы автоэнкодера как генератора и вариационный автоэнкодер (VAE) — первая генеративная модель траектории. По материалам лекции курса об автоэнкодерах.
- Аннотация
- Конспект открывается постановкой задачи обучения без учителя и идеей обучения представлений. Затем вводится снижение размерности: предположение о многообразии и метод главных компонент с интерактивным тренажёром проекции. Центральная часть посвящена автоэнкодеру — архитектуре из кодировщика и декодировщика, обучаемой восстанавливать собственный вход, — и его применениям: очистке изображений от шума, обнаружению аномалий по ошибке реконструкции, предобучению. Далее показываются пределы автоэнкодера как генератора: дыры в латентном пространстве и артефакты интерполяции. Из этих проблем выводится вариационный автоэнкодер: кодировщик предсказывает параметры распределения, репараметризация пропускает градиент через выборку, KL-дивергенция стягивает латентное пространство к стандартному нормальному распределению. Приводится реализация VAE на Keras и кратко обсуждается условный вариант (CVAE).
- Пререквизиты
- Конспект 2 (функция ошибки, градиент), конспект 3 (Dense, Keras, MNIST-подобные данные, функциональный API — конспект 7, раздел 4), конспект 5 (переобучение). Нормальное распределение — из курса теории вероятностей.
- Мотивация
- Во всех предыдущих конспектах обучение требовало меток, а разметка — самая дорогая часть подготовки данных (конспект 5, раздел 5.1). При этом неразмеченных данных обычно на порядки больше, чем размеченных. Автоэнкодер учится на данных без всяких меток — «меткой» служит сам объект — и извлекает из них представление, которое затем можно использовать для сжатия, очистки от шума, поиска аномалий и генерации новых объектов.
1. Обучение без учителя
«Если интеллект — это торт, то основная часть торта — обучение без учителя, глазурь на нём — обучение с учителем, а вишенка сверху — обучение с подкреплением» (Ян Лекун)
До сих пор каждая задача была задачей обучения с учителем: каждому объекту сопоставлена метка, и сеть учится её предсказывать. В обучении без учителя (англ. unsupervised learning) объекты известны, но меток нет — модель должна сама найти в данных структуру, неявно разделяющую объекты по похожести.
Зачем это нужно? Во-первых, размеченных объектов часто слишком мало, а неразмеченных — много, и хочется, чтобы данные «сами разделились» согласованно с будущими метками. Во-вторых, обучение без учителя часто даёт представления, позволяющие быстро адаптироваться к новым задачам — иногда эффективнее, чем перенос обучения (конспект 7). Область, изучающая такие представления, называется обучением представлений (англ. representation learning): вместо ручного конструирования признаков сеть выучивает их сама.
2. Снижение размерности и метод главных компонент
Почему объект вообще можно описать меньшим числом признаков? В глубоком обучении используют предположение о многообразии (англ. manifold assumption): реальные данные не распределены равномерно по всему пространству признаков, а занимают лишь его малую часть — многообразие. Лица людей на фотографиях 300×300 лежат в пространстве заметно меньшей размерности, чем 90 000: далеко не каждая матрица чисел даёт изображение человека.
Классический линейный способ снижения размерности — метод главных компонент (англ. Principal Component Analysis, PCA): объекты проецируются на подпространство, выбранное так, чтобы восстановление обратно давало наименьшую ошибку. Каждая следующая компонента перпендикулярна предыдущим и объясняет наибольшую часть оставшейся дисперсии.
Точки — двумерные объекты (данные иллюстративные), прямая — одномерное подпространство, в которое мы их сжимаем. Поворачивайте ось и следите за ошибкой восстановления: минимум достигается вдоль направления наибольшего разброса данных — это и есть первая главная компонента.
PCA ограничен линейностью: он ищет наилучшую плоскость, а многообразие реальных данных обычно изогнуто. Нелинейное обобщение этой идеи и есть автоэнкодер.
3. Автоэнкодер
Автоэнкодер — архитектура из двух сетей. Кодировщик \(E\) сжимает признаковое описание объекта в вектор небольшой размерности \(z = E(x)\) — скрытое (латентное) представление. Декодировщик \(D\) восстанавливает из этого вектора объект в исходном пространстве:
784
ReLU
24
ReLU
784, sigmoid
Автоэнкодер для MNIST (изображения 28×28 = 784): узкое место в середине — латентное представление. Левая половина — кодировщик, правая — декодировщик.
Обучается автоэнкодер минимизацией ошибки реконструкции — расхождения между входом и восстановлением; меткой объекта служит сам объект:
(вместо квадрата отклонения, как в конспекте 2, формула (2.2), для изображений с яркостями в \([0,1]\) часто берут бинарную перекрёстную энтропию). На Keras:
from tensorflow.keras.layers import Input, Dense from tensorflow.keras.models import Model latent_dim = 24 x = Input(shape=(784,)) h = Dense(256, activation='relu')(x) z = Dense(latent_dim, activation='relu')(h) # скрытое представление h_dec = Dense(256, activation='relu')(z) x_hat = Dense(784, activation='sigmoid')(h_dec) # восстановление autoencoder = Model(x, x_hat) autoencoder.compile(optimizer='adam', loss='binary_crossentropy') # вход и «метка» — одно и то же изображение autoencoder.fit(x_train, x_train, epochs=20, batch_size=100, validation_data=(x_test, x_test))
4. Применения автоэнкодера
4.1. Очистка от шума
Шумоподавляющий автоэнкодер (англ. denoising autoencoder) обучается восстанавливать
чистое изображение по зашумлённому входу: в fit подаётся пара
«зашумлённый вход → чистая цель». Латентное пространство хранит существенную структуру объекта,
а случайный шум в него просто не помещается:
4.2. Обнаружение аномалий
Автоэнкодер обучают только на «нормальных» данных. Нормальные примеры он восстанавливает с малой ошибкой реконструкции; аномальные, которых он не видел при обучении, — с большой. По распределению ошибок на обучающих данных выбирают порог: всё, что восстановилось хуже порога, объявляется аномалией. Выбор порога — компромисс между пропуском аномалий и ложными срабатываниями (те же ошибки первого и второго рода, что в конспекте 4).
4.3. Предобучение
Кодировщик, обученный без меток на большом корпусе, можно использовать как экстрактор признаков для задачи с малым числом размеченных примеров — та же логика, что у переноса обучения (конспект 7, раздел 8), но без необходимости в чужой размеченной выборке вроде ImageNet.
5. Автоэнкодер как генератор и его пределы
Декодировщик превращает вектор латентного пространства в изображение — значит, подавая ему новые векторы, можно генерировать новые объекты. Но какие векторы подавать? Латентным пространством автоэнкодера никто не управлял, и оно получается неудобным:
Точка из пустой зоны декодируется в объект, не похожий ни на что из обучающей выборки. Это видно на интерполяции: если плавно двигаться по латентному пространству от одной цифры к другой, промежуточные точки проходят через «дыры» и дают нечитаемые изображения:
Чтобы декодировщик стал полноценным генератором, латентное пространство должно быть связным (без дыр) и известным (чтобы знать, откуда брать случайные векторы). Простая регуляризация представлений (штраф за удаление от нуля) лишь масштабирует картину — нужен другой механизм.
6. Вариационный автоэнкодер
6.1. Кодировать распределение, а не точку
Вариационный автоэнкодер (англ. Variational Autoencoder, VAE; Kingma, Welling, 2013) меняет кодировщик: вместо одной точки латентного пространства он предсказывает для объекта распределение — вектор средних \(\mu\) и вектор логарифмов дисперсий \(\log\sigma^2\) многомерного нормального распределения с независимыми компонентами. Латентное представление получается выборкой из этого распределения, а декодировщик обязан восстанавливать объект из любой такой выборки — окрестность каждого объекта в латентном пространстве становится «осмысленной».
6.2. Репараметризация
Через генератор случайных чисел нельзя пропустить градиент. Помогает свойство нормального распределения \(N(\mu, \sigma^2) = \mu + \sigma \cdot N(0, 1)\): выборку берут из стандартного нормального распределения и детерминированно преобразуют:
Случайность вынесена в независимый вход \(\varepsilon\), а путь градиента к \(\mu\) и \(\sigma\) полностью детерминирован — это репараметризация (англ. reparametrization trick).
6.3. KL-дивергенция: стянуть пространство к N(0, 1)
Осталась проблема: сети выгодно занулить \(\sigma\) и вернуться к обычному автоэнкодеру с его дырами. Поэтому вводится регуляризация: распределение каждого объекта должно быть близко к стандартному нормальному. Мерой близости распределений служит дивергенция Кульбака–Лейблера; для пары нормальных распределений она вычисляется аналитически:
Пунктир — целевое распределение N(0, 1), сплошная кривая — распределение кодировщика N(μ, σ²), точки на оси — выборка z = μ + σε по формуле (9.3) при фиксированных ε. KL-дивергенция (9.4) равна нулю только при μ = 0, σ = 1 и растёт при любом отклонении — в том числе при σ → 0, что и не даёт распределению схлопнуться в точку.
Если обучать сеть только на KL-дивергенцию, декодировщик игнорируется, и кодировщик просто отображает все объекты в стандартное нормальное распределение — классы полностью перемешиваются:
Поэтому полная функция ошибки VAE складывается из ошибки реконструкции (как у обычного автоэнкодера) и KL-дивергенции:
Первое слагаемое заставляет декодировщик корректно восстанавливать объекты, второе — держит латентное пространство связным и известным. Результат — компактное пространство без дыр, где близкие по смыслу объекты лежат рядом:
Из лекции курса об автоэнкодерах
6.4. Реализация на Keras
Код по мотивам ноутбука курса (VAE для MNIST, латентная размерность 2):
import tensorflow as tf from tensorflow.keras.layers import Input, Dense, Lambda from tensorflow.keras.models import Model original_dim, intermediate_dim, latent_dim = 784, 256, 2 # кодировщик: из изображения — параметры распределения x = Input(shape=(original_dim,)) h = Dense(intermediate_dim, activation='relu')(x) z_mean = Dense(latent_dim)(h) z_log_var = Dense(latent_dim)(h) # репараметризация: z = mu + sigma * eps — формула (9.3) def sampling(args): z_mean, z_log_var = args eps = tf.random.normal(shape=tf.shape(z_mean)) return z_mean + tf.exp(z_log_var / 2) * eps z = Lambda(sampling)([z_mean, z_log_var]) # декодировщик (слои создаются отдельно, чтобы переиспользовать их в генераторе) decoder_h = Dense(intermediate_dim, activation='relu') decoder_out = Dense(original_dim, activation='sigmoid') x_hat = decoder_out(decoder_h(z)) vae = Model(x, x_hat) # ошибка (9.5): реконструкция + KL-дивергенция (9.4) recon = original_dim * tf.keras.losses.binary_crossentropy(x, x_hat) kl = -0.5 * tf.reduce_sum(1 + z_log_var - tf.square(z_mean) - tf.exp(z_log_var), axis=-1) vae.add_loss(tf.reduce_mean(recon + kl)) vae.compile(optimizer='rmsprop') vae.fit(x_train, epochs=50, batch_size=100, validation_data=(x_test, None)) # генератор новых цифр: отдельная модель из обученных слоёв декодировщика z_in = Input(shape=(latent_dim,)) generator = Model(z_in, decoder_out(decoder_h(z_in)))
tf.exp(z_log_var / 2), а в KL-дивергенции — tf.exp(z_log_var).
Подстановка выхода сети как «сигмы» напрямую ломает обе формулы.
7. Условный вариационный автоэнкодер
У обученного VAE нельзя попросить «сгенерируй именно пятёрку»: класс и стиль перемешаны в одном латентном векторе. Условный VAE (CVAE) решает это, подавая метку класса дополнительным входом и кодировщику, и декодировщику. Тогда латентному пространству остаётся кодировать только стиль написания цифры (наклон, толщину, форму штрихов), а класс задаётся явно: можно генерировать заданную цифру со случайным стилем или переносить стиль одной цифры на другую — разделение (англ. disentangling) стиля и метки.
У «ванильного» VAE есть и ограничения: изображения получаются размытыми — перекрёстная энтропия на пикселях поощряет усреднённые, «безопасные» реконструкции. Следующие два конспекта — про генеративные модели, дающие резкие изображения: диффузионные модели и GAN.
Контрольные вопросы
-
Меткой служит сам входной объект: сеть обучается восстанавливать вход (fit(x, x)), поэтому ручная разметка не нужна вовсе — годятся любые неразмеченные данные.
-
Реальные данные занимают лишь малую искривлённую часть пространства признаков — многообразие меньшей размерности. Именно поэтому объект можно сжать в короткий латентный вектор и восстановить: автоэнкодер выучивает параметризацию этого многообразия.
-
Латентное пространство обычного автоэнкодера никак не контролируется: в нём есть пустые зоны, которым не соответствует ничего из обучающих данных, и неизвестно, из какого распределения брать векторы. Случайная точка с большой вероятностью попадёт в «дыру» и декодируется в нечитаемый объект.
-
Через операцию случайной выборки нельзя пропустить градиент. Репараметризация z = μ + σε выносит случайность в независимый вход ε ~ N(0,1), а путь от ошибки к параметрам μ и σ делает детерминированным — обратное распространение работает.
-
Только реконструкция — сеть занулит σ и выродится в обычный автоэнкодер с дырами в латентном пространстве. Только KL — кодировщик отобразит все объекты в стандартное нормальное распределение, перемешав классы, а декодировщик не научится ничему. Работает только сумма обоих слагаемых (формула (9.5)).
-
Автоэнкодер обучают только на нормальных данных и вычисляют распределение ошибок реконструкции. Нормальные объекты восстанавливаются с малой ошибкой, аномальные — с большой; порог ошибки, разделяющий их, выбирают из требований задачи к пропускам и ложным срабатываниям.