Изменения

Перейти к: навигация, поиск

Вариационный автокодировщик

9189 байт добавлено, 12:11, 8 января 2021
м
Нет описания правки
'''Вариационный автокодировщик''' (англ. ''Variational Autoencoder'', ''VAE'') {{---}} это [[автокодировщик]] (a.k.a. генеративная модель, которая учится отображать объекты в заданное скрытое пространство (и обратно)) основанный на вариационном выводе.
== Описание ==
'''Генеративное Порождающее моделирование''' (англ. ''Generative modelling'') {{---}} область машинного обучения, имеющая дело с распределением <math>P(X)</math>, определенном на датасете <math>X </math> из пространства (возможно многомерного) <math>\ChiX</math>. Так, например, популярные задачи генерации картинок имеют дело с огромным количеством измерений (пикселей).
Также как и в обыкновенных кодировщиках у нас имеется скрытое вероятностное пространство <math>Z </math> соответствующее случайной величине <math>(z, P(z))</math> (распределенной как-нибудь фиксированно, здесь ~<math>\sim N(0, 1)</math>). И мы хотим иметь декодер <math>f(z, \theta) \colon Z \times \Theta \to \Chi X </math>. При этом мы хотим найти такие <math>\theta</math>, чтобы после разыгрывания <math>z </math> по <math>P(z) </math> мы получили "что-то похожее" на элементы <math>X</math>.  Вообще, для любого <math>x \in X</math> мы хотим считать <math>P(x) = \int P(x|z; \theta)P(z)dz</math>, здесь мы заменили <math>f(z, \theta)</math> на <math>P(x|z; \theta)</math>, чтобы явно показать зависимость между <math>x</math> и <math>z</math> и после этого применить формулу полной вероятности. Обычно <math>P(x|z; \theta)</math> около нуля почти для всех пар <math>(x, z)</math>. Основная идея в том, чтомы хотим теперь генерировать <math>z</math>, который бы давали что-то около <math>x</math> и только их суммировать в <math>P(x)</math>. Для этого нам требуется ввести еще одно распределение <math>Q(z|X)</math>, которое будет получать <math>x</math> и говорить распределение на <math>z</math> которое наиболее вероятно будет генерировать нам такой <math>x</math>. Теперь нам нужно как-то сделать похожими распределения <math>E_{z\sim Q}P(X|z)</math> и <math>P(X)</math>.  Рассмотрим следующую дивергенцию Кульбака-Лейблера (''Kullback–Leibler divergence'', ''KL-div'').:<math>D[Q(z)||P(z|X)] = E_{z∼Q} [log Q(z) − log P(z|X)]</math>, Распишем <math>P(z|X)</math> как <math>P(X|z) * P(z) / P(X)</math>.:<math>D[Q(z)||P(z|X)] = E_{z∼Q} [log Q(z) − log P(X|z) - log P(z)] + log P(X)</math>, Что эквивалентно::<math>logP(x) - D[Q(z)||P(z|X)] = E_{z∼Q}[log P(X|z)] - D[Q(z)||P(z)]</math>, Рассмотрим эту штуку для <math>Q(z|X)</math>, тогда::<math>logP(x) - D[Q(z|X)||P(z|X)] = E_{z∼Q}[log P(X|z)] - D[Q(z|X)||P(z)]</math>, Посмотрим, на это равенство. Правую часть мы можем оптимизировать градиентным спуском (пусть пока и не совсем понятно как).В левой же части первое слагаемое {{---}} то, что мы хотим максимизировать. В то же время <math>D[Q(z|X)||P(z|X)]</math> мы хотим минимизировать. Если у нас <math>Q(z|X)</math> {{---}} достаточно сильная модель, то в какой-то момент она будет хорошо матчить <math>P(z|X)</math>, а значит их дивергенция Кульбака-Лейблера будет почти 0. Значит, при оптимизации можно исключить эту часть и стараться максимизировать только правую. В качестве бонуса мы еще получили более "податливую" <math>P(z|X)</math>, вместо нее можно смотреть на <math>Q(z|X)</math>. Теперь разберемся как оптимизировать правую часть. Сначала нужно определиться с моделью для <math>Q(z|X)</math>. Обычно ее берут равной <math>N(z|\mu(X, \theta), \sigma(X, \theta))</math>. Где <math>\mu</math> и <math>\sigma</math> какие-то похожеедетерминированные функции на X с обучаемыми параметрами <math>\theta</math>, которые мы впредь будем опускать (обычно используются нейронные сети).   Нетрудно проверить, что для дивергенция Кульбака-Лейблера двух нормальных распределений имеет следующий вид::<math>D_{K}[N(\mu_1, \Sigma_0)||N(\mu_1, \Sigma_0)]</math>, KLD есть <math>\frac{1}{2} (tr(\Sigma_1^{-1}\Sigma_0) + (\mu_1 - \mu_0)^T\Sigma_1^{-1}(\mu_1 - \mu_0) - k + log(\frac{det\Sigma_1}{det\Sigma_0})) </math>. Это значит, что:<math>D[Q(z|X)||P(z)] = D[N(\mu(X), \Sigma(X))||N(0, I)] = \frac12 (tr(\Sigma(X)) + \mu(X)^T\mu(X) - k - log(det\Sigma(X)))</math>. Теперь здесь можно считать градиенты, для BackPropagation. С первым слагаемым в правой части все немного сложнее. <math>E_{z∼Q}[log P(X|z)]</math> мы можем считать методом Монте-Карло(МК), но тогда такая штука (из-за того, что переменные спрятаны в распределении, из которого мы генерируем себе выборку, для МК) не является гладкой относительно них, а значит непонятно, как проталкивать через это градиент. Для того, чтобы все-таки можно было протолкнуть градиент, применяется так называемый ''трюк репараметризации'', который базируется на элементы простой формуле <math>N(\Sigma(X), \mu(X)) = \mu(X) + \Sigma^{\frac12}(X) * N(0, I) </math>.TODO:<math>E_{z∼Q}[log P(X|z)] = E_{\epsilon \sim N(0, I)}[log P(X = f(\mu(X) + \Sigma^{\frac12}(X) * \epsilon), \theta)]</math>. В такой форме мы уже можем использовать BackPropagation для переменных из функций <math>\Sigma</math> и <math>\mu</math>. Следующая картинка лучше поможет осознать структуру VAE и, в частности, зачем нужен (и как работает) трюк репараметризации. На левой части диаграмма без использования reparameterization trick. На правой части диаграмма с использованием reparameterization trick.  [[Файл:VAE.PNG]] взято из https://arxiv.org/pdf/1606.05908.pdf == Пример реализации ==Ниже приведена реализация частного случая VAE на языке Python с использованием библиотеки Pytorch.Эта реализация работает с датасетом MNIST.Размерность скрытого слоя {{---}} 2. Координаты в нем считаются независимыми (из-за этого, например, матрица <math>\Sigma</math> диагональная, и формула для расчета KLD немного другая).  class VariationalAutoencoder(nn.Module): def __init__(self): super().__init__() self.mu = nn.Linear(32, 2) self.gamma = nn.Linear(32, 2) self.encoder = nn.Sequential(nn.Linear(784, 32), nn.ReLU(True)) self.decoder = nn.Sequential(nn.Linear(2, 32), nn.ReLU(True), nn.Linear(32, 784), nn.Sigmoid()) def forward(self, x): mu, gamma = self.encode(x) encoding = self.reparameterize(mu, gamma) x = self.decoder(encoding) return x, mu, gamma def reparameterize(self, mu, gamma): if self.training: sigma = torch.exp(0.5*gamma) std_z = Variable(torch.from_numpy(np.random.normal(0, 1, size=sigma.size())).float()) encoding = std_z.mul(sigma).add(mu) return encoding else: return mu def encode(self, x): x = self.encoder(x) mu = self.mu(x) gamma = self.gamma(x) return mu, gamma def decode(self, x): return self.decoder(x) def latent(self, x): mu, gamma = self.encode(x) encoding = self.reparameterize(mu, gamma) return encoding def loss_function(input, output, mu, gamma, batch_size=batch_size): BCE = F.binary_cross_entropy(output, input) KLD = -0.5*torch.sum(1 + gamma - mu.pow(2) - gamma.exp()) KLD /= batch_size*784 return BCE + KLD
== Применение ==
Область применения вариационных автокодировщиков совпадает с областью применения обыкновенных автокодировщиков. А именно:
* Каскадное обучение глубоких сетей (хотя сейчас применяется все реже, в связи с появлением новых методов инициализации случайными весамивесов);* Уменьшение шума в данных;* Уменьшение размерности данных (иногда работает лучше, чем [[метод главных компонент]]<sup>[на 28.01.19 не создан]</sup>). Благодаря тому, что пользователь сам устанавливает нужное распределение скрытого вектора, вариационный кодировщик хорошо подходит для генерации новых объектов (например, картинок). Для этого достаточно разыграть скрытый вектор согласно его распределению и подать на вход декодера. Получится объект из того же распределения, что и датасет. == См. также ==*[[:Автокодировщик|Автокодировщик]] *[[:Generative Adversarial Nets (GAN)|Порождающие состязательные сети]] == Примечания ==*[https://habr.com/ru/post/429276/ Вариационные автокодировщики: теория и рабочий код]*[https://jaan.io/what-is-variational-autoencoder-vae-tutorial/ Tutorial - What is a variational autoencoder?]*[https://towardsdatascience.com/intuitively-understanding-variational-autoencoders-1bfe67eb5daf Intuitively Understanding Variational Autoencoders] == Источники информации ==*[https://arxiv.org/abs/1606.05908 Tutorial on Variational Autoencoders]*Datalore презентация Дениса Степанова
Благодаря тому, что пользователь сам устанавливает нужное распределение скрытого вектора, вариационный кодировщик хорошо подходит для генерации новых объектов (например, картинок). Для этого достаточно разыграть скрытый вектор согласно его распределению и скормить ее в декодер. Получится объект из того же распределения, что и датасет.[[Категория: Машинное обучение]][[Категория: Порождающие модели]]
89
правок

Навигация