Cette fois, implémentons l'autoencodeur variationnel avec keras.
Considérez un Autoencoder primitif. Supposons que le poids W1 et la polarisation b1 soient appliqués à l'entrée x et mappés à la couche intermédiaire via la fonction d'activation f1, puis le poids W2 et la polarisation b2 sont appliqués et sortis via la fonction d'activation f2.
A ce moment, si f2 est ** fonction constante ** et que la fonction de perte est la somme de ** erreur carrée **, l'apprentissage se poursuit de sorte que la sortie y reproduise l'entrée x. W1 et b1 sont appelés quantités de caractéristiques qui représentent des données.
Tout d'abord, implémentons-le à partir d'un simple encodeur automatique. L'ensemble de données utilise MNIST.
Comme l'image d'entrée est MNIST, 28 * 28 = 784 dimensions, réduisez-la à 256 dimensions, 64 dimensions, 32 dimensions, puis restaurez-la à 64 dimensions, 256 dimensions, 784 dimensions. La fonction de perte est l'entropie croisée de la différence entre l'image de sortie et l'image d'entrée.
Parce que le nombre de dimensions est réduit en cours de route, le réseau neuronal apprend les poids pour tenter de ne laisser que les caractéristiques importantes. Après avoir appris, si vous mettez une image en entrée, presque la même image sera sortie de la sortie.
Maintenant, déplaçons le code ci-dessous.
from keras.layers import Input, Dense
from keras.models import Model
from keras.datasets import mnist
import numpy as np
import matplotlib.pyplot as plt
#Lecture de l'ensemble de données
(x_train, _), (x_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:])))
#Construction de modèles
encoding_dim = 32
input_img = Input(shape=(784,))
x1 = Dense(256, activation='relu')(input_img)
x2 = Dense(64, activation='relu')(x1)
encoded = Dense(encoding_dim, activation='relu')(x2)
x3 = Dense(64, activation='relu')(encoded)
x4 = Dense(256, activation='relu')(x3)
decoded = Dense(784, activation='sigmoid')(x4)
autoencoder = Model(input=input_img, output=decoded)
autoencoder.compile(optimizer='adadelta', loss='binary_crossentropy')
autoencoder.summary()
#Apprentissage
autoencoder.fit(x_train, x_train,
nb_epoch=50,
batch_size=256,
shuffle=True,
validation_data=(x_test, x_test))
#Convertir l'image de test avec le modèle de formation
decoded_imgs = autoencoder.predict(x_test)
n = 10
plt.figure(figsize=(10, 2))
for i in range(n):
#Afficher l'image de test
ax = plt.subplot(2, n, i+1)
plt.imshow(x_test[i].reshape(28, 28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
#Afficher l'image convertie
ax = plt.subplot(2, n, i+1+n)
plt.imshow(decoded_imgs[i].reshape(28, 28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
plt.show()
La ligne supérieure est l'image de test d'origine et la ligne inférieure est l'image convertie à partir de l'image de test par l'encodeur automatique. Je pense que l'image de test peut être presque reproduite.
À propos, la sortie est déterminée par le signal de couche Z à 32 dimensions le plus réduit. En d'autres termes, on peut dire que diverses formes de nombres 0 à 9 sont réparties dans l'espace latent à 32 dimensions Z.
Nous ne pouvons pas réellement voir 32 dimensions, mais que se passe-t-il si nous abandonnons le nombre de dimensions et rendons l'espace latent z 2 dimensions? En deux dimensions, vous pouvez montrer comment les nombres 0-9 sont répartis sur un plan.
Voyons ce qui se passe lorsque la partie la plus réduite est en deux dimensions. Remplacez ʻencoding_dim = 32 par ʻencoding_dim = 2
dans le code précédent et exécutez-le.
Comme prévu, il est difficile de se reproduire si l'espace latent z est bidimensionnel. "0", "1", "7" sont reproduits, mais le reste est mélangé avec d'autres nombres et ne peut pas être reproduit correctement.
En d'autres termes, dans un espace latent étroit de deux dimensions, les nombres de 0 à 9 ne peuvent pas être soigneusement divisés et distribués, et il semble que de nombreux nombres soient mélangés et distribués.
Comment bien répartir les nombres 0-9 dans l'espace latent étroit Z sans se chevaucher? La distribution typique dans le monde naturel est la distribution normale (distribution gaussienne), donc ici nous supposons que la distribution des nombres 0 à 9 suit la distribution normale, et considérons ce modèle.
Une fois qu'un nombre entre en entrée et est réduit à 64 dimensions, regardez la variance moyenne $ \ mu $ $ \ sigma $ pour découvrir à quelle distribution normale le nombre appartient. Les valeurs échantillonnées aléatoirement de cette distribution sont placées dans Z, et les poids du décodeur sont appris de sorte qu'il n'y ait pas de différence entre l'entrée et la sortie. En faisant cela, il semble que les nombres de 0 à 9 peuvent être bien distribués sans se chevaucher.
Cependant, il y a un problème avec cette idée, et si un élément d'échantillonnage aléatoire est inclus, la propagation en retour d'erreur ne sera pas possible. Par conséquent, c'est un tel modèle qui permet la propagation de retour d'erreur tout en utilisant cette idée.
Où $ \ epsilon $ est un très petit nombre aléatoire. Multipliez la distribution $ \ sigma $ par ce $ \ epsilon $. Cette technique s'appelle ** Reparametrization Trick **. Si vous codez cette partie comme $ \ mu $ = z_mean, $ \ sigma $ = z_logvar, $ \ epsilon $ = epsilon,
# Reparametrization Trick
def sampling(args):
z_mean, z_logvar = args
batch = K.shape(z_mean)[0]
dim = K.int_shape(z_mean)[1]
epsilon = K.random_normal(shape=(batch, dim)) # ε
return z_mean + K.exp(0.5 * z_logvar) * epsilon
La fonction de perte $ L $ de VAE est exprimée comme suit. $L = E_{z \sim q(z|x)} log_{Pmodel}(x|z) - D_{KL}(q(z|x)||Pmodel(z)) $
Le premier terme est la valeur attendue de la vraisemblance logarithmique des données X pour q (z | X), qui indique si la sortie est proche des données d'origine, remplacez-la par une erreur carrée. Le deuxième terme est appelé distance Calback Libra ($ D_ {KL} $ représente la divergence KL), et p (z) est une distribution normale.
$=\beta||y-x||^2 - D_{KL} (N(\mu, \sigma)|N(0,1))\ $
Le deuxième terme est exprimé par kl_loss, $ \ sigma ^ 2 $ = z_logvar, $ \ mu $ = z_mean, et lorsqu'il est remplacé par une expression approximative, la fonction de perte $ L $ (vae_loss) devient comme suit.
#Fonction de perte
# Kullback-Leibler Loss
kl_loss = 1 + z_logvar - K.square(z_mean) - K.exp(z_logvar)
kl_loss = K.sum(kl_loss, axis=-1)
kl_loss *= -0.5
# Reconstruction Loss
reconstruction_loss = mse(inputs, outputs)
reconstruction_loss *= original_dim
vae_loss = K.mean(reconstruction_loss + kl_loss)
Écrivons et exécutons l'intégralité du code VAE, y compris le code précédent.
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from keras.layers import Lambda, Input, Dense
from keras.models import Model
from keras.datasets import mnist
from keras.losses import mse
from keras import backend as K
import numpy as np
import matplotlib.pyplot as plt
#Lecture de l'ensemble de données
(x_train, y_train), (x_test, y_test) = mnist.load_data()
image_size = x_train.shape[1] # = 784
original_dim = image_size * image_size
x_train = np.reshape(x_train, [-1, original_dim])
x_test = np.reshape(x_test, [-1, original_dim])
x_train = x_train.astype('float32') / 255
x_test = x_test.astype('float32') / 255
input_shape = (original_dim, )
latent_dim = 2
# Reparametrization Trick
def sampling(args):
z_mean, z_logvar = args
batch = K.shape(z_mean)[0]
dim = K.int_shape(z_mean)[1]
epsilon = K.random_normal(shape=(batch, dim), seed = 5) # ε
return z_mean + K.exp(0.5 * z_logvar) * epsilon
#Construction du modèle VAE
inputs = Input(shape=input_shape)
x1 = Dense(256, activation='relu')(inputs)
x2 = Dense(64, activation='relu')(x1)
z_mean = Dense(latent_dim)(x2)
z_logvar = Dense(latent_dim)(x2)
z = Lambda(sampling, output_shape=(latent_dim,))([z_mean, z_logvar])
encoder = Model(inputs, [z_mean, z_logvar, z], name='encoder')
encoder.summary()
latent_inputs = Input(shape=(latent_dim,))
x3 = Dense(64, activation='relu')(latent_inputs)
x4 = Dense(256, activation='relu')(x3)
outputs = Dense(original_dim, activation='sigmoid')(x4)
decoder = Model(latent_inputs, outputs, name='decoder')
decoder.summary()
z_output = encoder(inputs)[2]
outputs = decoder(z_output)
vae = Model(inputs, outputs, name='variational_autoencoder')
#Fonction de perte
# Kullback-Leibler Loss
kl_loss = 1 + z_logvar - K.square(z_mean) - K.exp(z_logvar)
kl_loss = K.sum(kl_loss, axis=-1)
kl_loss *= -0.5
# Reconstruction Loss
reconstruction_loss = mse(inputs, outputs)
reconstruction_loss *= original_dim
vae_loss = K.mean(reconstruction_loss + kl_loss)
vae.add_loss(vae_loss)
vae.compile(optimizer='adam')
vae.fit(x_train,
epochs=50,
batch_size=256,
validation_data=(x_test, None))
#Convertir l'image de test
decoded_imgs = vae.predict(x_test)
#Affichage de l'image de test et de l'image convertie
n = 10
plt.figure(figsize=(10, 2))
for i in range(n):
#Afficher l'image de test
ax = plt.subplot(2, n, i+1)
plt.imshow(x_test[i].reshape(28, 28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
#Afficher l'image convertie
ax = plt.subplot(2, n, i+1+n)
plt.imshow(decoded_imgs[i].reshape(28, 28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
plt.show()
L'espace latent z peut maintenant être assez bien reproduit même en deux dimensions seulement. De nombreux nombres tels que "0", "1", "2", "7" et "9" peuvent être reproduits. "4" et "5" sont toujours inutiles.
L'espace latent Z étant bidimensionnel, il peut être représenté sur un plan. Voyons comment les nombres 0 à 9 y sont répartis et quelle image y est distribuée. Ajoutez ce qui suit au code de l'ensemble de VAE et exécutez-le.
import matplotlib.cm as cm
def plot_results(encoder,
decoder,
x_test,
y_test,
batch_size=128,
model_name="vae_mnist"):
z_mean, _, _ = encoder.predict(x_test,
batch_size=128)
plt.figure(figsize=(12, 10))
cmap=cm.tab10
plt.scatter(z_mean[:, 0], z_mean[:, 1], c=cmap(y_test))
m = cm.ScalarMappable(cmap=cmap)
m.set_array(y_test)
plt.colorbar(m)
plt.xlabel("z[0]")
plt.ylabel("z[1]")
plt.show()
# (-4, -4)De(4, 4)Est divisé en 30x30 et tracé
n = 30 # 50>30
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)[::-1]
for i, yi in enumerate(grid_y):
for j, xi in enumerate(grid_x):
z_sample = np.array([[xi, yi]])
x_decoded = decoder.predict(z_sample)
digit = x_decoded[0].reshape(digit_size, digit_size)
figure[i * digit_size: (i + 1) * digit_size,
j * digit_size: (j + 1) * digit_size] = digit
plt.figure(figsize=(10, 10))
start_range = digit_size // 2
end_range = n * digit_size + start_range + 1
pixel_range = np.arange(start_range, end_range, digit_size)
sample_range_x = np.round(grid_x, 1)
sample_range_y = np.round(grid_y, 1)
plt.xticks(pixel_range, sample_range_x)
plt.yticks(pixel_range, sample_range_y)
plt.xlabel("z[0]")
plt.ylabel("z[1]")
plt.axis('off')
plt.imshow(figure, cmap='Greys_r')
#plt.savefig(filename)
plt.show()
plot_results(encoder,
decoder,
x_test,
y_test,
batch_size=128,
model_name="vae_mlp")
Il semble que "0", "1", "2", "6" et "7" sont distribués sans chevauchement avec d'autres nombres.
Puisqu'il est distribué le long de la distribution normale, vous pouvez voir qu'il change continuellement d'un nombre à un autre.
Recommended Posts