Введение
В предыдущих статьях об автоэнкодерах (Часть 1 и Часть 2) мы исследовали интуицию, теорию и реализацию недостаточного и избыточного автоэнкодеров. Автоэнкоды состоят из двух частей: кодировщика и декодера. Кодировщик перемещает ввод в скрытое пространство, в то время как декодер пытается получить представление ввода обратно из представления скрытого пространства.
Проблема с классическими автоэнкодерами
Однако кодировщик в этих случаях является детерминированным по своей природе, то есть отображает значение на входе в точку в скрытом пространстве, которая впоследствии отображается обратно во входное пространство декодером. Однако хороший автоэнкодер должен изучать не точечное представление входных данных, а скорее распределение скрытых пространственных характеристик по двум причинам:
- Характеристики данных не имеют точечного представления. У них есть раздача.
- Многообразная структура входных данных должна быть гладкой и не разрозненной.
Вариативные автоэнкодеры (VAE) пытаются решить эту проблему, используя вероятностную модель скрытых представлений, которая лучше понимает лежащие в основе причинные связи, помогая в более эффективном обобщении.
Структура
Рассмотрим z, скрытое пространство или скрытое представление, и введите x, и пусть z имеет распределение вероятностей p (z) .
Для обобщения мы хотели бы иметь p (x). У нас есть доступ только к x, мы хотим захватить p (z | x).
Итак, для захвата p (x) нам понадобится p (z). Однако, поскольку z недоступен, мы не можем знать распределение z, а затем p (z), что делает эту проблему неразрешимой.
Однако есть другой способ решить эту проблему. Скрытую переменную z можно заставить следовать известному распределению. Это то, что делает VAE. В VAE мы применяем гауссово распределение для скрытой переменной z. Гауссово распределение можно охарактеризовать средним значением и дисперсией, которые оцениваются входными значениями.
VAE состоит из 3 компонентов:
- Кодировщик: он кодирует входной вектор в n измерениях в 2h измерения. h - размеры скрытого пространства. Здесь размеры 2h представляют объединенные h средние и h отклонения.
- Сэмплер: он берет длинный вектор размером 2h и создает гауссовское распределение на основе средних значений h и дисперсий h, из которого отбирается значение z, используемое для вывода.
- Декодер: принимает гауссово распределение и выводит восстановленный вектор, который сравнивается с входным вектором для вычисления потерь.
Как обеспечить структуру в распределении скрытых переменных
Давайте посмотрим, что здесь делает классическая функция потерь автокодировщика, то есть потеря реконструкции. Распределение z можно представить как пузыри в скрытом пространстве. Потери при реконструкции сводятся к минимуму, когда пузырьки вообще не перекрываются, поскольку перекрытие вносит неоднозначность. Следовательно, потеря реконструкции отодвигает пузырьки z-распределения как можно дальше друг от друга. Если нет регулирования того, как далеко могут лежать эти пузыри, потеря реконструкции будет толкать их слишком далеко друг от друга, делая распределение незначительным по сравнению с пространством, в котором расположены пузыри, что сводит на нет всю цель получения распределения в первом случае. место.
Следовательно, нам нужен член в потерях, который может связать пузыри распределения и обеспечить гауссово распределение для отдельных пузырей. Эта роль выполняется KL-дивергенцией гауссова распределения относительно нормального распределения с нулевым средним и единичной дисперсией.
Дивергенция KL (или относительная энтропия) гауссиана относительно стандартного нормального распределения равна:
Последний член дивергенции KL накладывает L2 штраф на средние латентных переменных, притягивая их к началу координат, не давая им уйти. Остальные члены, т.е. V(z)-log(V(z))-1
, имеют минимум в V (z) в 1, что очевидно, когда мы находим относительную энтропию по отношению к стандартному нормальному распределению. Следовательно, составной член потерь обеспечивает баланс между уменьшением ошибки восстановления и отличием от стандартного нормального распределения.
Трюк с изменением параметров
Мы делаем выборку из гауссова распределения для скрытой переменной (z), которая подается в декодер. Однако это создает проблему при обратном распространении и, как следствие, оптимизации, потому что, когда мы выполняем градиентный спуск для обучения модели VAE, мы не знаем, как выполнить обратное распространение через модуль выборки.
Вместо этого мы используем трюк с повторной параметризацией для выборки z.
Теперь возможно обратное распространение, поскольку градиенты по z должны проходить только через функции суммы и произведения.
Реализация с использованием PyTorch
Мы будем использовать набор данных MNIST для объяснения.
Этапы загрузки и преобразования данных аналогичны классическим кодировщикам. Здесь мы сосредоточимся на архитектуре VAE.
Мы определяем простой VAE с точки зрения трех модулей: кодировщика, декодера и сэмплера. В сэмплере реализован трюк повторной параметризации, описанный выше.
Базовая архитектура
d = 20 | |
class VAE(nn.Module): | |
def __init__(self): | |
super().__init__() | |
self.encoder = nn.Sequential( | |
nn.Linear(784, d ** 2), | |
nn.ReLU(), | |
nn.Linear(d ** 2, d * 2) | |
) | |
self.decoder = nn.Sequential( | |
nn.Linear(d, d ** 2), | |
nn.ReLU(), | |
nn.Linear(d ** 2, 784), | |
nn.Sigmoid(), | |
) | |
def sampler(self, mu, logvar): | |
if self.training: | |
std = logvar.mul(0.5).exp_() | |
eps = std.data.new(std.size()).normal_() | |
return eps.mul(std).add_(mu) | |
else: | |
return mu | |
def forward(self, x): | |
mu_logvar = self.encoder(x.view(-1, 784)).view(-1, 2, d) | |
mu = mu_logvar[:, 0, :] | |
logvar = mu_logvar[:, 1, :] | |
z = self.sampler(mu, logvar) | |
return self.decoder(z), mu, logvar | |
model = VAE().to(device) |
Входные размеры (784) соответствуют размеру изображения MNIST 28x28. Входные данные перемещаются из 784 размеров в 400 (dxd) dim, проходят через нелинейный слой (ReLU), а затем перемещаются в гиперпространство 2d, где d = 20. Скрытое пространство имеет размерность d (20). Нам нужны размеры 2d, поскольку они представляют собой конкатенацию d средних и d отклонений.
Модуль декодера также очень похож на классический автоэнкодер. Он принимает d-мерный вектор скрытого пространства, пропускает его через набор линейных и нелинейных слоев и выводит конечный вектор размера 784, как и входной.
Волшебство происходит в слое сэмплера.
def sampler(self, mu, logvar): | |
if self.training: | |
std = logvar.mul(0.5).exp_() | |
eps = std.data.new(std.size()).normal_() | |
return eps.mul(std).add_(mu) | |
else: | |
return mu |
Модуль сэмплера принимает 2d-вектор и возвращает z на основе трюка с повторной параметризацией. Здесь следует отметить, что мы вводим не дисперсию в слой сэмплера, а логарифм дисперсии. Это так, поскольку дисперсия должна быть положительной, в то время как логарифм дисперсии также может быть отрицательным. Это гарантирует, что дисперсия всегда положительна, и мы можем использовать полный диапазон значений в качестве входных данных. Это также делает процесс более стабильным.
Затем мы определяем потери как сумму потерь при реконструкции и расхождения KL. Обучение аналогично классическому автоэнкодеру и было рассмотрено в более ранней статье.
Перед обучением мы получаем эту реконструкцию, где верхний ряд - оригинал, а нижний - реконструкция:
Спустя всего 20 эпох результаты реконструкции следующие:
Чтобы посмотреть на векторы скрытого пространства, мы генерируем несколько случайных выборок и используем декодер для генерации выборки:
z_sample = torch.randn((8, d)).to(device)
sample_out = model.decoder(z_sample)
Мы видим, что скрытое пространство изучило представление цифр, хотя и не полностью. С большей эпохой обучения скрытый слой может еще лучше улавливать входную функцию.
Интерполяция между двумя входами
Мы можем использовать представления скрытого пространства для интерполяции между двумя входами (здесь изображения), взяв средневзвешенное значение представления скрытого пространства. Рассмотрим цифры 0 и 6. Мы можем посмотреть на постепенные шаги превращения 0 в 6.
Каждый шаг преобразования определяет скрытую переменную интерполированного изображения как:
z_interpolation = ((i/N)*z_6 + (1-i/N)*z_0) where i ranges from 0 to 8
output_interpolation = model.decoder(z_interpolation)
Вывод
Как мы видели, вариационные автоэнкодеры представляют скрытые переменные не в виде точек, а в виде вероятностного облака или распределения. Это не только помогает сделать представление скрытого пространства более общим, но также делает многообразие более связным и гладким. Базовые концепции VAE моделируют процесс генерации данных, которые в дальнейшем могут быть использованы в GAN.