خروجی Denoising Autoencoder و ورود به Sparse Autoencoder
در Denoising Autoencoder بخشی از Pixelها یا Featureها خراب میشوند، اما Target همان Data تمیز است. نتیجه نشان میدهد Model میتواند ساختار گمشده را از Patternهای Dataset حدس بزند.
شکل 17-9. خروجی Denoising Autoencoder و ورود به Sparse Autoencoder
Sparse Autoencoder
Constraint دیگری که Feature Extraction خوبی ایجاد میکند Sparsity است. Coding Layer میتواند حتی بزرگتر از Input باشد، اما Loss Model را مجبور میکند در هر Instance فقط تعداد کمی Neuron فعال باشند. در نتیجه هر Neuron Coding تمایل پیدا میکند Feature معنیداری را نمایندگی کند.
یک روش ساده استفاده از Activation نوع Sigmoid در Coding Layer و L1 Regularization روی Activationهاست:
sparse_l1_encoder = tf.keras.Sequential([
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(100, activation="relu"),
tf.keras.layers.Dense(300, activation="sigmoid"),
tf.keras.layers.ActivityRegularization(l1=1e-4)
])
sparse_l1_decoder = tf.keras.Sequential([
tf.keras.layers.Dense(100, activation="relu"),
tf.keras.layers.Dense(28 * 28),
tf.keras.layers.Reshape([28, 28])
])
sparse_l1_ae = tf.keras.Sequential([sparse_l1_encoder, sparse_l1_decoder])
L1 بهجای اینکه همهٔ Activationها را کمی کوچک کند، بسیاری از آنها را به صفر نزدیک میکند و تعداد کمی Feature مهم را نگه میدارد.
Sparsity Loss با KL Divergence
روش دقیقتر این است که Mean Activation هر Coding Neuron روی Batch اندازهگیری شود و با Target Sparsity، مثلاً 0.1، مقایسه شود. برای Penalize کردن اختلاف، KL Divergence Gradient قویتری از MSE دارد.
شکل 17-10. Sparsity Loss با KL Divergence
D_KL(P || Q) = Σᵢ P(i) log(P(i) / Q(i))
D_KL(p || q) = p log(p/q) + (1-p) log((1-p)/(1-q))
Sparsity Loss همهٔ Neuronها جمع میشود و با یک Hyperparameter وزندار به Reconstruction Loss اضافه میشود. Weight خیلی بزرگ Reconstruction را خراب میکند و Weight خیلی کوچک Constraint را بیاثر میسازد.
kl_divergence = tf.keras.losses.kullback_leibler_divergence
class KLDivergenceRegularizer(tf.keras.regularizers.Regularizer):
def __init__(self, weight, target):
self.weight = weight
self.target = target
def __call__(self, inputs):
mean_activities = tf.reduce_mean(inputs, axis=0)
return self.weight * (
kl_divergence(self.target, mean_activities) +
kl_divergence(1. - self.target, 1. - mean_activities))
Variational Autoencoder یا VAE
VAE یک Autoencoder Probabilistic و Generative است. Encoder بهجای تولید یک Coding قطعی، Parameterهای Distribution را تولید میکند. برای هر Input، دو Vector ساخته میشود: Mean با نماد μ و Standard Deviation یا شکل پایدارتر آن γ = log(σ²). سپس Coding بهصورت Random از این Distribution Sample میشود.
شکل 17-11. Variational Autoencoder یا VAE
این Randomness دو اثر مهم دارد: Latent Space Smooth میشود و Pointهای نزدیک معمولاً Sampleهای مشابه میسازند؛ همچنین میتوان پس از Training یک Vector Random از Gaussian استاندارد Sample کرد و با Decoder نمونهٔ جدید تولید کرد.
Loss در VAE
Cost از دو بخش تشکیل میشود: Reconstruction Loss و Latent Loss. Latent Loss Distribution Coding را به Gaussian استاندارد نزدیک میکند. برای هر Instance:
L_latent = -1/2 Σᵢ (1 + log(σᵢ²) - σᵢ² - μᵢ²)
# با γ = log(σ²)
L_latent = -1/2 Σᵢ (1 + γᵢ - exp(γᵢ) - μᵢ²)
استفاده از Log Variance از نظر عددی پایدارتر است.
Reparameterization Trick
Sampling معمولی مسیر Gradient را قطع میکند. برای اینکه Backpropagation ممکن باشد، Noise مستقل ε ~ N(0,1) Sample میشود و Coding به صورت زیر ساخته میشود:
z = μ + exp(γ / 2) * ε
به این ترتیب Randomness در ε قرار میگیرد، ولی مسیر از μ و γ تا Loss Differentiable باقی میماند.
پیادهسازی Encoder و Decoder VAE
class Sampling(tf.keras.layers.Layer):
def call(self, inputs):
mean, log_var = inputs
return tf.random.normal(tf.shape(log_var)) * tf.exp(log_var / 2) + mean
codings_size = 10
inputs = tf.keras.layers.Input(shape=[28, 28])
Z = tf.keras.layers.Flatten()(inputs)
Z = tf.keras.layers.Dense(150, activation="relu")(Z)
Z = tf.keras.layers.Dense(100, activation="relu")(Z)
codings_mean = tf.keras.layers.Dense(codings_size)(Z)
codings_log_var = tf.keras.layers.Dense(codings_size)(Z)
codings = Sampling()([codings_mean, codings_log_var])
variational_encoder = tf.keras.Model(
inputs=[inputs], outputs=[codings_mean, codings_log_var, codings])
decoder_inputs = tf.keras.layers.Input(shape=[codings_size])
x = tf.keras.layers.Dense(100, activation="relu")(decoder_inputs)
x = tf.keras.layers.Dense(150, activation="relu")(x)
x = tf.keras.layers.Dense(28 * 28)(x)
outputs = tf.keras.layers.Reshape([28, 28])(x)
variational_decoder = tf.keras.Model(decoder_inputs, outputs)
_, _, codings = variational_encoder(inputs)
reconstructions = variational_decoder(codings)
variational_ae = tf.keras.Model(inputs=[inputs], outputs=[reconstructions])
تولید Image و Semantic Interpolation با VAE
پس از Training کافی است Codingهای Random از Gaussian استاندارد Sample شوند و به Decoder داده شوند. خروجیها معمولاً Smooth و کمی Blurry هستند، اما بسیاری از آنها شبیه نمونههای واقعی Fashion MNIST خواهند بود.
شکل 17-12. تولید Image و Semantic Interpolation با VAE
Latent Space پیوسته است؛ بنابراین میتوان بین دو Coding Interpolate کرد و Sequence از Imageهایی ساخت که بهتدریج از یک مفهوم به مفهوم دیگر تبدیل میشوند.
شکل 17-13. تولید Image و Semantic Interpolation با VAE
Generative Adversarial Network
GAN در سال ۲۰۱۴ معرفی شد و از دو شبکه تشکیل میشود:
- Generator: یک Coding تصادفی را به Sample Fake تبدیل میکند.
- Discriminator: یک Sample را میگیرد و Probability واقعی بودن آن را پیشبینی میکند.
شکل 17-14. Generative Adversarial Network
Training دو Phase دارد. در Phase اول Discriminator با Batch ترکیبی Real/Fake Train میشود. در Phase دوم Generator از طریق GAN Train میشود و Target همهٔ Fakeها عمداً «Real» قرار میگیرد تا Generator یاد بگیرد Discriminator را فریب دهد.
GAN ساده برای Fashion MNIST
codings_size = 30
Dense = tf.keras.layers.Dense
generator = tf.keras.Sequential([
Dense(100, activation="selu"),
Dense(150, activation="selu"),
Dense(28 * 28, activation="sigmoid"),
tf.keras.layers.Reshape([28, 28])
])
discriminator = tf.keras.Sequential([
tf.keras.layers.Flatten(),
Dense(150, activation="selu"),
Dense(100, activation="selu"),
Dense(1, activation="sigmoid")
])
gan = tf.keras.Sequential([generator, discriminator])
discriminator.compile(loss="binary_crossentropy", optimizer="rmsprop")
discriminator.trainable = False
gan.compile(loss="binary_crossentropy", optimizer="rmsprop")
ویژگی مهم این است که trainable هنگام Compile در Graph Training لحاظ میشود: Discriminator در Training مستقیم خودش Trainable است، ولی هنگام Train کردن GAN Freeze میماند.
Custom Training Loop برای GAN
batch_size = 32
dataset = tf.data.Dataset.from_tensor_slices(X_train).shuffle(1000)
dataset = dataset.batch(batch_size, drop_remainder=True).prefetch(1)
def train_gan(gan, dataset, batch_size, codings_size, n_epochs):
generator, discriminator = gan.layers
for epoch in range(n_epochs):
for X_batch in dataset:
# phase 1: discriminator
noise = tf.random.normal([batch_size, codings_size])
generated_images = generator(noise)
X_fake_and_real = tf.concat([generated_images, X_batch], axis=0)
y1 = tf.constant([[0.]] * batch_size + [[1.]] * batch_size)
discriminator.train_on_batch(X_fake_and_real, y1)
# phase 2: generator
noise = tf.random.normal([batch_size, codings_size])
y2 = tf.constant([[1.]] * batch_size)
gan.train_on_batch(noise, y2)
شکل 17-15. Custom Training Loop برای GAN
مشکلات Training GAN
GAN یک Game پویا بین دو Model است و Loss معمول Supervised رفتار سادهای ندارد. از نظر نظری Equilibrium مطلوب زمانی است که Distribution Generator با Data واقعی یکسان شود و Discriminator فقط بتواند با Probability 50% حدس بزند. اما Gradient Descent تضمین نمیکند این Nash Equilibrium را پیدا کند.
Mode Collapse
Generator ممکن است فقط چند Mode محدود را تولید کند؛ مثلاً مدتی فقط Shoe بسازد چون در فریب Discriminator برای آن Class موفقتر است. Discriminator نیز روی همین Fakeهای جدید Overfit میکند و Dynamics میتواند بین Modeهای مختلف Oscillate کند.
راهکارهای پیشنهادی شامل Experience Replay، Mini-batch Discrimination، Lossهای جایگزین و Architectureهای پایدارتر هستند. Hyperparameterها، Random Seed و Learning Rate نسبی دو Network اثر زیادی دارند.
Deep Convolutional GAN یا DCGAN
DCGAN مجموعهای از Guidelineهای تجربی برای GANهای تصویری پایدارتر معرفی کرد:
- Pooling را با Strided Convolution در Discriminator و Transposed Convolution در Generator جایگزین کنید.
- Batch Normalization را در بیشتر Layerهای هر دو Network استفاده کنید، بهجز Input Discriminator و Output Generator.
- در Architecture عمیق Fully Connected Hidden Layerها را حذف کنید.
- در Generator از ReLU استفاده کنید و Output را با
tanh بسازید.
- در Discriminator از Leaky ReLU استفاده کنید.
codings_size = 100
generator = tf.keras.Sequential([
tf.keras.layers.Dense(7 * 7 * 128),
tf.keras.layers.Reshape([7, 7, 128]),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.Conv2DTranspose(64, 5, strides=2,
padding="same", activation="relu"),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.Conv2DTranspose(1, 5, strides=2,
padding="same", activation="tanh")
])
discriminator = tf.keras.Sequential([
tf.keras.layers.Conv2D(64, 5, strides=2, padding="same",
activation=tf.keras.layers.LeakyReLU(0.2)),
tf.keras.layers.Dropout(0.4),
tf.keras.layers.Conv2D(128, 5, strides=2, padding="same",
activation=tf.keras.layers.LeakyReLU(0.2)),
tf.keras.layers.Dropout(0.4),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(1, activation="sigmoid")
])
چون Generator با tanh Output میدهد، Data Training باید به بازهٔ [-1,1] Rescale شود. در این مثال Dropout در Discriminator از BatchNorm پایدارتر عمل کرده است؛ GANها به جزئیات Architecture حساساند.