變分自編碼器(Variational Autoencoder,VAE)是生成式模型(Generative Model)的一種,另外一種常見的生成式模型是生成式對抗網絡(Generative Adversarial Network,GAN)html
這裏咱們介紹下VAE的原理,並用Keras實現git
咱們常常會有這樣的需求:根據不少個樣本,學會生成新的樣本網絡
以MNIST爲例,在看過幾千張手寫數字圖片以後,咱們能進行模仿,並生成一些相似的圖片,這些圖片在原始數據中並不存在,有一些變化可是看起來類似dom
換言之,須要學會數據x的分佈,這樣,根據數據的分佈就能輕鬆地產生新樣本函數
$$ P(X) $$學習
但數據分佈的估計不是件容易的事情,尤爲是當數據量不足的時候ui
可使用一個隱變量z,由z通過一個複雜的映射獲得x,而且假設z服從高斯分佈編碼
$$ x=f(z;\theta) $$spa
所以只須要學習隱變量所服從高斯分佈的參數,以及映射函數,便可獲得原始數據的分佈code
爲了學習隱變量所服從高斯分佈的參數,須要獲得z足夠多的樣本
然而z的樣本並不能直接得到,所以還須要一個映射函數(條件機率分佈),從已有的x樣本中獲得對應的z樣本
$$ z=Q(z|x) $$
這看起來和自編碼器很類似,從數據自己,經編碼獲得隱層表示,經解碼還原
但VAE和AE的區別以下:
除了重構偏差,因爲在VAE中咱們假設隱變量z服從高斯分佈,所以encoder對應的條件機率分佈,應當和高斯分佈儘量類似
能夠用相對熵,又稱做KL散度(Kullback–Leibler Divergence),來衡量兩個分佈的差別,或者說距離,但相對熵是非對稱的
$$ D(f\parallel g)=\int f(x)\log\frac{f(x)}{g(x)}dx $$
這裏以MNIST爲例,學習隱變量z所服從高斯分佈的均值和方差兩個參數,從而能夠重新的z生成原始數據中沒有的x
encoder和decoder各用兩層全鏈接層,簡單一些,主要爲了說明VAE的實現
加載庫
# -*- coding: utf-8 -*- import numpy as np import matplotlib.pyplot as plt from keras.layers import Input, Dense, Lambda from keras.models import Model from keras import backend as K from keras import objectives from keras.datasets import mnist
定義一些常數
batch_size = 100 original_dim = 784 intermediate_dim = 256 latent_dim = 2 epochs = 50
encoder部分,兩層全鏈接層,隱層表示包括均值和方差
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)
Lambda
層不參與訓練,只參與計算,用於後面產生新的z
def sampling(args): z_mean, z_log_var = args epsilon = K.random_normal(shape=(batch_size, latent_dim), mean=0.) return z_mean + K.exp(z_log_var / 2) * epsilon z = Lambda(sampling, output_shape=(latent_dim,))([z_mean, z_log_var])
decoder部分,兩層全鏈接層,x_decoded_mean
爲重構的輸出
decoder_h = Dense(intermediate_dim, activation='relu') decoder_mean = Dense(original_dim, activation='sigmoid') h_decoded = decoder_h(z) x_decoded_mean = decoder_mean(h_decoded)
自定義總的損失函數並編譯模型
def vae_loss(x, x_decoded_mean): xent_loss = original_dim * objectives.binary_crossentropy(x, x_decoded_mean) kl_loss = -0.5 * K.sum(1 + z_log_var - K.square(z_mean) - K.exp(z_log_var), axis=-1) return xent_loss + kl_loss vae = Model(x, x_decoded_mean) vae.compile(optimizer='rmsprop', loss=vae_loss)
加載數據並訓練,CPU訓練的速度還算能忍
(x_train, y_train), (x_test, y_test) = mnist.load_data() x_train = x_train.astype('float32') / 255. x_test = x_test.astype('float32') / 255. x_train = x_train.reshape((len(x_train), np.prod(x_train.shape[1:]))) x_test = x_test.reshape((len(x_test), np.prod(x_test.shape[1:]))) vae.fit(x_train, x_train, shuffle=True, epochs=epochs, batch_size=batch_size, validation_data=(x_test, x_test))
定義一個encoder,看看MNIST中的數據在隱層中變成了什麼樣子
encoder = Model(x, z_mean) x_test_encoded = encoder.predict(x_test, batch_size=batch_size) plt.figure(figsize=(6, 6)) plt.scatter(x_test_encoded[:, 0], x_test_encoded[:, 1], c=y_test) plt.colorbar() plt.show()
結果以下,說明在二維的隱層中,不一樣的數字被很好地分開了
再定義一個生成器,從隱層到輸出,用於產生新的樣本
decoder_input = Input(shape=(latent_dim,)) _h_decoded = decoder_h(decoder_input) _x_decoded_mean = decoder_mean(_h_decoded) generator = Model(decoder_input, _x_decoded_mean)
用網格化的方法產生一些二維數據,做爲新的z輸入到生成器,並將生成的x展現出來
n = 20 digit_size = 28 figure = np.zeros((digit_size * n, digit_size * n)) grid_x = np.linspace(-4, 4, n) grid_y = np.linspace(-4, 4, n) for i, xi in enumerate(grid_x): for j, yi in enumerate(grid_y): z_sample = np.array([[yi, xi]]) x_decoded = generator.predict(z_sample) digit = x_decoded[0].reshape(digit_size, digit_size) figure[(n - i - 1) * digit_size: (n - i) * digit_size, j * digit_size: (j + 1) * digit_size] = digit plt.figure(figsize=(10, 10)) plt.imshow(figure) plt.show()
結果以下,和以前看到的隱層圖是一致的,甚至能看到一些數字之間的過渡態
因爲包含一些隨機因素,因此每次生成的結果會存在一些差別
若是將全鏈接層換成CNN,應該能夠獲得更好的表示結果
掌握以上內容後,用相同的方法,能夠在FashionMNIST這個數據集上再跑一遍,數據集規模和MNIST徹底相同
只需改動四行便可
from keras.datasets import fashion_mnist (x_train, y_train), (x_test, y_test) = fashion_mnist.load_data() grid_x = np.linspace(-3, 3, n) grid_y = np.linspace(-3, 3, n)
完整代碼以下
# -*- coding: utf-8 -*- import numpy as np import matplotlib.pyplot as plt from keras.layers import Input, Dense, Lambda from keras.models import Model from keras import backend as K from keras import objectives from keras.datasets import fashion_mnist batch_size = 100 original_dim = 784 intermediate_dim = 256 latent_dim = 2 epochs = 50 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) def sampling(args): z_mean, z_log_var = args epsilon = K.random_normal(shape=(batch_size, latent_dim), mean=0.) return z_mean + K.exp(z_log_var / 2) * epsilon z = Lambda(sampling, output_shape=(latent_dim,))([z_mean, z_log_var]) decoder_h = Dense(intermediate_dim, activation='relu') decoder_mean = Dense(original_dim, activation='sigmoid') h_decoded = decoder_h(z) x_decoded_mean = decoder_mean(h_decoded) def vae_loss(x, x_decoded_mean): xent_loss = original_dim * objectives.binary_crossentropy(x, x_decoded_mean) kl_loss = -0.5 * K.sum(1 + z_log_var - K.square(z_mean) - K.exp(z_log_var), axis=-1) return xent_loss + kl_loss vae = Model(x, x_decoded_mean) vae.compile(optimizer='rmsprop', loss=vae_loss) (x_train, y_train), (x_test, y_test) = fashion_mnist.load_data() x_train = x_train.astype('float32') / 255. x_test = x_test.astype('float32') / 255. x_train = x_train.reshape((len(x_train), np.prod(x_train.shape[1:]))) x_test = x_test.reshape((len(x_test), np.prod(x_test.shape[1:]))) vae.fit(x_train, x_train, shuffle=True, epochs=epochs, batch_size=batch_size, validation_data=(x_test, x_test)) encoder = Model(x, z_mean) x_test_encoded = encoder.predict(x_test, batch_size=batch_size) plt.figure(figsize=(6, 6)) plt.scatter(x_test_encoded[:, 0], x_test_encoded[:, 1], c=y_test) plt.colorbar() plt.show() decoder_input = Input(shape=(latent_dim,)) _h_decoded = decoder_h(decoder_input) _x_decoded_mean = decoder_mean(_h_decoded) generator = Model(decoder_input, _x_decoded_mean) n = 20 digit_size = 28 figure = np.zeros((digit_size * n, digit_size * n)) grid_x = np.linspace(-3, 3, n) grid_y = np.linspace(-3, 3, n) for i, xi in enumerate(grid_x): for j, yi in enumerate(grid_y): z_sample = np.array([[yi, xi]]) x_decoded = generator.predict(z_sample) digit = x_decoded[0].reshape(digit_size, digit_size) figure[(n - i - 1) * digit_size: (n - i) * digit_size, j * digit_size: (j + 1) * digit_size] = digit plt.figure(figsize=(10, 10)) plt.imshow(figure) plt.show()
咱們來看一下隱層的表示,一樣起到了很好的分類效果
而後再來生成一些圖形,能夠看到不一樣種類衣服之間的過渡