Python Data Foundations Documentation

A plain documentation-style guide for Python, data handling, visualization, and machine learning basics.

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.

What you should be able to do
  • 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.
Reusable patterns
  • 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

CIFAR autoencoder instead of MNIST.

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")
])
In case the task asks for denoising.

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)
)
In case the solution uses Keras Functional API.

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(...).

Loss function choice.

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 np

Listing 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 / 255

Listing 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.9617346938775511

Denoising 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 image
Expected 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]

Back to overview