Building Your First Neural Network
Building Your First Neural Network
It is time to build a complete, working neural network. You will train a model to classify handwritten digits using the MNIST dataset, arguably the most famous benchmark in deep learning. By the end of this section, you will have a model that achieves over 97% accuracy on data it has never seen.
Preparing the Dataset (MNIST)
MNIST contains 70,000 grayscale images of handwritten digits (0–9), each 28 x 28 pixels: 60,000 for training and 10,000 for testing. TensorFlow includes it as a built-in dataset, so loading it requires just one line. The official TensorFlow MNIST Keras example demonstrates the following preparation steps:
import tensorflow as tf
import numpy as np
# Load the dataset
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
print(x_train.shape) # (60000, 28, 28) 60k training images
print(x_test.shape) # (10000, 28, 28) 10k test images
print(y_train[:5]) # [5 0 4 1 9] digit labels
print(x_train.dtype) # uint8 pixel values 0 to 255
# Normalise: scale pixel values from 0–255 to 0.0–1.0
# Neural networks train faster and more stably when inputs are
# on a consistent, small scale close to the initial weight magnitudes
x_train = x_train.astype('float32') / 255.0
x_test = x_test.astype('float32') / 255.0
# Flatten: reshape 28×28 images into 784-element vectors
# Dense (fully-connected) layers expect 1-D inputs
x_train = x_train.reshape(-1, 784) # (60000, 784)
x_test = x_test.reshape(-1, 784) # (10000, 784)
print(x_train.shape) # (60000, 784)
print(x_train.min(), x_train.max()) # 0.0 1.0
Did You Know?
MNIST was created by Yann LeCun, Corinna Cortes, and Christopher Burges in 1998. It was originally used to train and evaluate convolutional networks for optical character recognition on postal envelopes. Today, it serves as a quick sanity check if your model cannot learn MNIST, something is wrong with your setup or code.
Building the Model with tf.keras
With the data ready, define the neural network using Keras's Sequential API. Each call to model.add() (or each layer in the list) stacks one layer on top of the previous one, forming a feed-forward network:
import tensorflow as tf
model = tf.keras.Sequential([
# Input shape: each sample is a 784-element vector
tf.keras.layers.InputLayer(input_shape=(784,)),
# Hidden layer 1: 128 neurons
# ReLU (Rectified Linear Unit) = max(0, x)
# Introduces non-linearity, lets the network learn complex patterns
tf.keras.layers.Dense(128, activation='relu'),
# Dropout: randomly zeros 20% of neurons during training
# Forces the network to learn redundant representations
# This is one of the most effective regularisation techniques
tf.keras.layers.Dropout(0.2),
# Hidden layer 2: 64 neurons
tf.keras.layers.Dense(64, activation='relu'),
# Output layer: 10 neurons (one per digit class)
# Softmax converts raw scores (logits) into probabilities
# Output values are non-negative and sum to 1.0
tf.keras.layers.Dense(10, activation='softmax'),
])
# Print architecture summary: layers, shapes, parameter counts
model.summary()
# Total params: ~109,386 (all trainable)
Training and Evaluating the Model
Compiling sets the learning algorithm and loss function. Fitting runs the training loop. Both are single-function calls with Keras:
# Compile
model. compile(
# Adam: adaptive learning rate — currently the most popular
# optimiser for classification tasks
optimizer='adam',
# sparse_categorical_crossentropy: the correct choice when
# labels are integers (0, 1, 2 …), not one-hot vectors
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
# Train
history = model.fit(
x_train, y_train,
epochs=10, # 10 full passes through the training set
batch_size=64, # process 64 samples per gradient update
validation_split=0.1, # hold out 10% of train data for validation
verbose=1
)
# Evaluate on unseen test data
test_loss, test_acc = model.evaluate(x_test, y_test, verbose=0)
print(f'Test loss: {test_loss:.4f}')
print(f'Test accuracy: {test_acc:.4f}') # Typically ~0.9760+
# Make predictions
predictions = model.predict(x_test[:5])
predicted_classes = predictions.argmax(axis=1)
print('Predicted:', predicted_classes)
print('Actual: ', y_test[:5])
Expected Result
After 10 epochs, you should see a test accuracy above 97%. If it is significantly lower, verify two things: (1) pixel values are divided by 255.0, and (2) you are using sparse_categorical_crossentropy rather than categorical_crossentropy; the latter expects one-hot encoded labels, which y_train is not.










