Dieses Mal implementieren wir Variational Autoencoder mit Keras.
Betrachten Sie einen primitiven Autoencoder. Angenommen, das Gewicht W1 und die Vorspannung b1 werden an den Eingang x angelegt und über die Aktivierungsfunktion f1 auf die Zwischenschicht abgebildet, und dann werden das Gewicht W2 und die Vorspannung b2 angelegt und über die Aktivierungsfunktion f2 ausgegeben.
Zu diesem Zeitpunkt wird, wenn f2 eine ** konstante Funktion ** ist und die Verlustfunktion die Summe des ** quadratischen Fehlers ** ist, das Lernen fortgesetzt, so dass die Ausgabe y die Eingabe x reproduziert. W1 und b1 werden Merkmalsgrößen genannt, die Daten darstellen.
Lassen Sie es uns zunächst mit einem einfachen Auto-Encoder implementieren. Der Datensatz verwendet MNIST.
Da das Eingabebild MNIST ist, 28 * 28 = 784 Dimensionen, beschränken Sie es auf 256 Dimensionen, 64 Dimensionen, 32 Dimensionen und stellen Sie es dann auf 64 Dimensionen, 256 Dimensionen, 784 Dimensionen wieder her. Die Verlustfunktion ist die Kreuzentropie der Differenz zwischen dem Ausgabebild und dem Eingabebild.
Da die Anzahl der Dimensionen auf dem Weg eingegrenzt wird, lernt das neuronale Netzwerk die Gewichte, um nur die wichtigen Merkmale zu belassen. Wenn Sie nach dem Lernen ein Bild in die Eingabe einfügen, wird fast dasselbe Bild von der Ausgabe ausgegeben.
Verschieben wir nun den folgenden Code.
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
#Datensatz lesen
(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:])))
#Modellbau
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()
#Lernen
autoencoder.fit(x_train, x_train,
nb_epoch=50,
batch_size=256,
shuffle=True,
validation_data=(x_test, x_test))
#Konvertieren Sie das Testbild mit dem Trainingsmodell
decoded_imgs = autoencoder.predict(x_test)
n = 10
plt.figure(figsize=(10, 2))
for i in range(n):
#Testbild anzeigen
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)
#Konvertiertes Bild anzeigen
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()
Die obere Reihe ist das ursprüngliche Testbild, und die untere Reihe ist das Bild, das vom automatischen Encoder aus dem Testbild konvertiert wurde. Ich denke, dass das Testbild fast reproduziert werden kann.
Die Ausgabe wird übrigens durch das am stärksten eingegrenzte 32-dimensionale Z-Schichtsignal bestimmt. Mit anderen Worten kann gesagt werden, dass verschiedene Formen der Zahlen 0 bis 9 im 32-dimensionalen latenten Raum Z verteilt sind.
Wir können eigentlich keine 32 Dimensionen sehen, aber was ist, wenn wir die Anzahl der Dimensionen verringern und den latenten Raum z 2 Dimensionen machen? In zwei Dimensionen können Sie zeigen, wie die Zahlen 0-9 in einer Ebene verteilt sind.
Nun wollen wir sehen, was passiert, wenn der am meisten eingegrenzte Teil zweidimensional gemacht wird. Ändern Sie "encoding_dim = 32" im vorherigen Code in "encoding_dim = 2" und führen Sie ihn aus.
Wie erwartet ist es schwierig zu reproduzieren, wenn der latente Raum z zweidimensional ist. "0", "1", "7" werden reproduziert, aber der Rest wird mit anderen Zahlen gemischt und kann nicht gut reproduziert werden.
Mit anderen Worten, in einem engen latenten Raum von zwei Dimensionen können die Zahlen 0 bis 9 nicht sauber geteilt und verteilt werden, und es scheint, dass viele Zahlen gemischt und verteilt sind.
Wie können die Zahlen 0-9 im engen latenten Raum Z gut verteilt werden, ohne sich zu überlappen? Die typische Verteilung in der natürlichen Welt ist die Normalverteilung (Gaußsche Verteilung). Daher nehmen wir hier an, dass die Verteilung der Zahlen 0 bis 9 der Normalverteilung folgt, und betrachten dieses Modell.
Nachdem eine Zahl in die Eingabe eingegeben und auf 64 Dimensionen eingegrenzt wurde, überprüfen Sie die mittlere $ \ mu $ Varianz $ \ sigma $, um herauszufinden, zu welcher Normalverteilung die Zahl gehört. Zufällig abgetastete Werte aus dieser Verteilung werden in Z eingegeben, und die Decodergewichte werden gelernt, so dass es keinen Unterschied zwischen Eingabe und Ausgabe gibt. Auf diese Weise können die Zahlen von 0 bis 9 ohne Überlappung gut verteilt werden.
Es gibt jedoch ein Problem mit dieser Idee, und wenn ein zufälliges Stichprobenelement enthalten ist, ist eine Fehlerrückübertragung nicht möglich. Daher ist es ein solches Modell, das die Weitergabe von Fehlern ermöglicht, während diese Idee genutzt wird.
Wobei $ \ epsilon $ eine sehr kleine Zufallszahl ist. Multiplizieren Sie die Verteilung $ \ sigma $ mit diesem $ \ epsilon $. Diese Technik heißt ** Reparametrization Trick **. Wenn Sie diesen Teil als $ \ mu $ = z_mean, $ \ sigma $ = z_logvar, $ \ epsilon $ = epsilon, codieren
# 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
Die Verlustfunktion $ L $ von VAE wird wie folgt ausgedrückt. $L = E_{z \sim q(z|x)} log_{Pmodel}(x|z) - D_{KL}(q(z|x)||Pmodel(z)) $
Der erste Term ist der erwartete Wert der logarithmischen Wahrscheinlichkeit der Daten X für q (z | X), der angibt, ob die Ausgabe nahe an den Originaldaten liegt. Ersetzen Sie sie daher durch einen quadratischen Fehler. Der zweite Term wird als Calback Libra-Abstand bezeichnet ($ D_ {KL} $ steht für KL-Divergenz), und p (z) ist eine Normalverteilung
$=\beta||y-x||^2 - D_{KL} (N(\mu, \sigma)|N(0,1))\ $
Der zweite Term wird ausgedrückt als kl_loss, $ \ sigma ^ 2 $ = z_logvar, $ \ mu $ = z_mean, und wenn er durch einen ungefähren Ausdruck ersetzt wird, wird die Verlustfunktion $ L $ (vae_loss) wie folgt.
#Verlustfunktion
# 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)
Lassen Sie uns den gesamten VAE-Code einschließlich des vorherigen Codes schreiben und ausführen.
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
#Datensatz lesen
(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
#VAE Modellbau
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')
#Verlustfunktion
# 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))
#Testbild konvertieren
decoded_imgs = vae.predict(x_test)
#Anzeige des Testbildes und des konvertierten Bildes
n = 10
plt.figure(figsize=(10, 2))
for i in range(n):
#Testbild anzeigen
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)
#Konvertiertes Bild anzeigen
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()
Der latente Raum z kann nun auch in nur zwei Dimensionen recht gut reproduziert werden. Viele Zahlen wie "0", "1", "2", "7", "9" können reproduziert werden. "4" und "5" sind immer noch nutzlos.
Da der latente Raum Z zweidimensional ist, kann er in einer Ebene dargestellt werden. Mal sehen, wie die Zahlen 0 bis 9 dort verteilt sind und wie das Bild dort verteilt ist. Fügen Sie dem Code für die gesamte VAE Folgendes hinzu und führen Sie ihn aus.
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)Von(4, 4)Ist in 30x30 unterteilt und geplottet
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")
Es scheint, dass "0", "1", "2", "6" und "7" verteilt sind, ohne sich mit anderen Zahlen zu überlappen.
Da es entlang der Normalverteilung verteilt ist, können Sie sehen, dass es sich kontinuierlich von einer Zahl zur anderen ändert.
Recommended Posts