Autoencoders for MNIST Reconstruction
This page explains autoencoders as encoder-decoder networks that compress images into a latent representation and reconstruct the original input, including denoising and convolutional autoencoder variants.
- Build an encoder that compresses MNIST images into a bottleneck vector.
- Build a decoder that reconstructs images from that bottleneck.
- Train an autoencoder by using the same image as both input and target.
- Add noise to inputs and train a denoising autoencoder.
- Use convolution and transposed convolution layers for image reconstruction.
- Autoencoders use X as both input and target because the model learns reconstruction.
- The bottleneck layer controls the compression strength.
- Denoising autoencoders receive noisy inputs but are trained to output clean images.
Setup and Simple Autoencoder
If the task uses CIFAR-10 or CIFAR-100 instead of MNIST, the autoencoder logic is the same, but the input size changes. MNIST images are 28 x 28, so a flattened image has 784 values. CIFAR images are 32 x 32 with 3 color channels, so a flattened image has 3072 values.
# CIFAR image shape: 32 x 32 x 3
x_train_flat = x_train.reshape((len(x_train), -1))
x_test_flat = x_test.reshape((len(x_test), -1))
input_dim = x_train_flat.shape[1] # 3072 for CIFAR images
In that case, the encoder input and decoder output must use input_dim, not 28*28.
encoder = keras.models.Sequential([
keras.layers.Input(shape=(input_dim,)),
keras.layers.Dense(512, activation="relu"),
keras.layers.Dense(256, activation="relu"),
keras.layers.Dense(128, activation="relu")
])
decoder = keras.models.Sequential([
keras.layers.Input(shape=(128,)),
keras.layers.Dense(256, activation="relu"),
keras.layers.Dense(512, activation="relu"),
keras.layers.Dense(input_dim, activation="sigmoid")
])
Add noise to the input, but train the model to reconstruct the clean original data. After adding noise to normalized images, use np.clip(..., 0., 1.) so pixel values stay in the valid image range.
noise_factor = 0.1
x_train_noisy = x_train + noise_factor * np.random.normal(0.0, 1.0, x_train.shape)
x_test_noisy = x_test + noise_factor * np.random.normal(0.0, 1.0, x_test.shape)
x_train_noisy = np.clip(x_train_noisy, 0., 1.)
x_test_noisy = np.clip(x_test_noisy, 0., 1.)
x_train_noisy_flat = x_train_noisy.reshape((len(x_train_noisy), -1))
x_test_noisy_flat = x_test_noisy.reshape((len(x_test_noisy), -1))
autoencoder.fit(
x_train_noisy_flat, x_train_flat,
epochs=10,
batch_size=256,
validation_data=(x_test_noisy_flat, x_test_flat)
)
The page mostly uses Sequential, but some solutions may use Input and Model. The idea is identical: define an input tensor, pass it through encoder and decoder layers, then build the model from input to output.
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense, Input
input_layer = Input(shape=(input_dim,))
encoded = Dense(512, activation="relu")(input_layer)
encoded = Dense(256, activation="relu")(encoded)
encoded = Dense(128, activation="relu")(encoded)
decoded = Dense(256, activation="relu")(encoded)
decoded = Dense(512, activation="relu")(decoded)
decoded = Dense(input_dim, activation="sigmoid")(decoded)
autoencoder = Model(inputs=input_layer, outputs=decoded)
Use this style when the task explicitly asks for a clearly defined input and output, or when the provided solution starts with Input(...).
For image reconstruction, both of these are common:
autoencoder.compile(optimizer="adam", loss="binary_crossentropy")
# or
autoencoder.compile(optimizer="adam", loss="mse")
Use binary_crossentropy when normalized pixel values and sigmoid output are treated like pixel-wise probabilities. Use mse when the task asks for reconstruction error or the provided solution uses mean squared error.
Listing 1. Import TensorFlow, Keras, Matplotlib, and NumPy
This visualization step helps verify what the data or model output looks like.
import tensorflow
from tensorflow import keras
import matplotlib.pyplot as plt
import numpy as npListing 2. Load MNIST without labels
This prepares the data or pretrained resource used by the following examples.
(x_train, _), (x_test, _) = keras.datasets.mnist.load_data()Expected text output or note
[download or training progress output omitted; run the cell to see live progress]Listing 3. Normalize MNIST pixels
This transforms the raw input into the numeric scale or shape expected by the model.
x_train = x_train / 255
x_test = x_test / 255Listing 4. Define the encoder and bottleneck layer
This defines or inspects part of the neural network architecture.
encoder=keras.models.Sequential([
keras.layers.Input(shape=[28,28]),
keras.layers.Flatten(),
keras.layers.Dense(100, activation="relu"),
keras.layers.Dense(30, activation="relu") # bottleneck
])Listing 5. Define the decoder that reconstructs 28 by 28 images
This step supports the workflow shown in the surrounding listings.
decoder=keras.models.Sequential([
keras.layers.Input(shape=[30]),
keras.layers.Dense(100, activation="relu"),
keras.layers.Dense(28*28, activation="sigmoid"),
keras.layers.Reshape([28,28])
])Listing 6. Connect encoder and decoder into one autoencoder
This step supports the workflow shown in the surrounding listings.
stacked_autoencoder=keras.models.Sequential([encoder,decoder])Listing 7. Compile the autoencoder
Compilation chooses the loss function, optimizer, and metrics used during training.
stacked_autoencoder.compile(loss="binary_crossentropy",
optimizer='adam',
metrics=['accuracy'])Listing 8. Train the autoencoder to reconstruct clean images
Training updates model weights and stores the learning history for later inspection.
history=stacked_autoencoder.fit(x_train,x_train, epochs=10, validation_data=[x_test,x_test])Expected text output or note
[download or training progress output omitted; run the cell to see live progress]Listing 9. Compare original images with reconstructions
This step supports the workflow shown in the surrounding listings.
plt.figure(figsize=(20,5))
for i in range(8):
plt.subplot(2,8,i+1)
plt.imshow(x_test[i], cmap="binary")
plt.subplot(2,8,8+1+i)
reconstruction = stacked_autoencoder.predict(x_test[i].reshape((1,28,28)))
plt.imshow(reconstruction.reshape(28,28),cmap="binary")Expected text output or note
[download or training progress output omitted; run the cell to see live progress]Listing 10. Show the original image, latent vector, and reconstruction
This step supports the workflow shown in the surrounding listings.
plt.figure(figsize=(10,5))
plt.subplot(1,3,1)
plt.imshow(x_test[0], cmap="binary")
plt.subplot(1,3,2)
latent_vector=encoder.predict(x_test[0].reshape(1,28,28))
plt.imshow(latent_vector, cmap="binary")
plt.subplot(1,3,3)
reconstruction = decoder.predict(latent_vector)
plt.imshow(reconstruction.reshape(28,28),cmap="binary")Expected text output or note
[download or training progress output omitted; run the cell to see live progress]Listing 11. Calculate the compression ratio from 784 pixels to 30 latent values
This step supports the workflow shown in the surrounding listings.
1-30/(28*28)Expected text output or note
0.9617346938775511Denoising Autoencoder
Listing 12. Add random noise to one test image
This step supports the workflow shown in the surrounding listings.
plt.figure(figsize=(10,5))
plt.subplot(1,2,1)
plt.imshow(x_test[0], cmap="binary")
plt.subplot(1,2,2)
noise=np.random.random((28,28))/4
plt.imshow(x_test[0]+noise, cmap="binary")Expected text output or note
<matplotlib.image.AxesImage at 0x7c2d5767d6d0>
<Figure size 1000x500 with 2 Axes>
[visual output omitted; run the code to display the image or chart]Listing 13. Define a deeper encoder for denoising
This step supports the workflow shown in the surrounding listings.
encoder=keras.models.Sequential([
keras.layers.Input(shape=[28,28]),
keras.layers.Flatten(),
keras.layers.Dense(100, activation="relu"),
keras.layers.Dense(100, activation="relu"),
keras.layers.Dense(30, activation="relu") # bottleneck
])Listing 14. Define a deeper decoder for denoising
This step supports the workflow shown in the surrounding listings.
decoder=keras.models.Sequential([
keras.layers.Input(shape=[30]),
keras.layers.Dense(100, activation="relu"),
keras.layers.Dense(100, activation="relu"),
keras.layers.Dense(28*28, activation="sigmoid"),
keras.layers.Reshape([28,28])
])Listing 15. Connect the denoising encoder and decoder
This step supports the workflow shown in the surrounding listings.
stacked_autoencoder=keras.models.Sequential([encoder,decoder])Listing 16. Compile the denoising autoencoder
Compilation chooses the loss function, optimizer, and metrics used during training.
stacked_autoencoder.compile(loss="binary_crossentropy",
optimizer='adam',
metrics=['accuracy'])Listing 17. Create noisy training and test images
Training updates model weights and stores the learning history for later inspection.
x_train_noise=x_train + ((np.random.random(x_train.shape))/4)
x_test_noise=x_test + ((np.random.random(x_test.shape))/4)Listing 18. Display one noisy training image
Training updates model weights and stores the learning history for later inspection.
plt.imshow(x_train_noise[0], cmap="binary")Listing 19. Train the denoising autoencoder
Training updates model weights and stores the learning history for later inspection.
history=stacked_autoencoder.fit(x_train_noise,x_train,epochs=8,
validation_data=[x_test_noise,x_test])Expected text output or note
[download or training progress output omitted; run the cell to see live progress]Listing 20. Compare noisy inputs with denoised reconstructions
This step supports the workflow shown in the surrounding listings.
plt.figure(figsize=(20,5))
for i in range(8):
plt.subplot(2,8,i+1)
plt.imshow(x_test_noise[i], cmap="binary")
plt.subplot(2,8,8+1+i)
reconstruction = stacked_autoencoder.predict(x_test_noise[i].reshape((1,28,28)))
plt.imshow(reconstruction.reshape(28,28),cmap="binary")Expected text output or note
[download or training progress output omitted; run the cell to see live progress]Convolutional Autoencoder
Listing 21. Define a convolutional encoder
This step supports the workflow shown in the surrounding listings.
encoder = keras.models.Sequential([
keras.layers.Reshape([28, 28, 1], input_shape=[28, 28]),
keras.layers.Conv2D(16, kernel_size=(3, 3), padding="same", activation="relu"),
keras.layers.MaxPool2D(pool_size=2),
keras.layers.Conv2D(32, kernel_size=(3, 3), padding="same", activation="relu"),
keras.layers.MaxPool2D(pool_size=2),
keras.layers.Conv2D(64, kernel_size=(3, 3), padding="same", activation="relu"),
keras.layers.MaxPool2D(pool_size=2)
])Expected text output or note
/usr/local/lib/python3.12/dist-packages/keras/src/layers/reshaping/reshape.py:38: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
super().__init__(**kwargs)Listing 22. Check the convolutional encoder output shape
This step supports the workflow shown in the surrounding listings.
encoder.predict(x_test[0].reshape((1, 28, 28))).shape # the leading 1 is the batch dimension for a single imageExpected text output or note
[download or training progress output omitted; run the cell to see live progress]Listing 23. Define a transposed-convolution decoder
This step supports the workflow shown in the surrounding listings.
decoder = keras.models.Sequential([
keras.layers.Conv2DTranspose(32, kernel_size=(3, 3), strides=2, padding="valid",
activation="relu",
input_shape=[3, 3, 64]),
keras.layers.Conv2DTranspose(16, kernel_size=(3, 3), strides=2, padding="same",
activation="relu"),
keras.layers.Conv2DTranspose(1, kernel_size=(3, 3), strides=2, padding="same",
activation="sigmoid"),
keras.layers.Reshape([28, 28])
])Expected text output or note
/usr/local/lib/python3.12/dist-packages/keras/src/layers/convolutional/base_conv_transpose.py:94: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
super().__init__(Listing 24. Connect the convolutional encoder and decoder
This step supports the workflow shown in the surrounding listings.
stacked_autoencoder = keras.models.Sequential([encoder, decoder])Listing 25. Compile the convolutional autoencoder
Compilation chooses the loss function, optimizer, and metrics used during training.
stacked_autoencoder.compile(loss="binary_crossentropy",
optimizer='adam', metrics=['accuracy'])Listing 26. Train the convolutional autoencoder
Training updates model weights and stores the learning history for later inspection.
history = stacked_autoencoder.fit(x_train, x_train, epochs=5, # use 10 for a longer training run
validation_data=[x_test, x_test])Expected text output or note
[download or training progress output omitted; run the cell to see live progress]Listing 27. Compare convolutional autoencoder reconstructions
This step supports the workflow shown in the surrounding listings.
plt.figure(figsize=(20, 5))
for i in range(8):
plt.subplot(2, 8, i+1)
pred = stacked_autoencoder.predict(x_test[i].reshape((1, 28, 28)))
plt.imshow(x_test[i], cmap="binary")
plt.subplot(2, 8, i+8+1)
plt.imshow(reconstruction.reshape((28, 28)), cmap="binary")Expected text output or note
[download or training progress output omitted; run the cell to see live progress]Listing 28. Display learned convolutional filters
This visualization step helps verify what the data or model output looks like.
plt.figure(figsize=(15,15))
for i in range(8 * 8):
plt.subplot(8, 8, i+1)
plt.imshow(encoder.layers[-2].weights[0][:, :, 0, i])Expected text output or note
<Figure size 1500x1500 with 64 Axes>
[visual output omitted; run the code to display the image or chart]