Keras Quickstart¶
Keras is the official high-level API for TensorFlow. It lets you build, train, evaluate, and deploy neural networks with minimal boilerplate, while still giving you access to lower-level control when you need it. In this file you will go from data to trained model to diagnostic plots — the complete practical workflow.
Learning Objectives¶
- Build models using both the Sequential and Functional APIs and know when to use each
- Configure the right output layer and loss function for classification vs. regression
- Train a model and interpret the output of
model.fit() - Plot training curves to diagnose model behavior
- Evaluate a model on held-out data and generate predictions
- Print and read a
model.summary()
Sequential vs. Functional API¶
Keras gives you two ways to build models.
Sequential API — for linear stacks of layers where each layer has one input and one output. This covers 80% of use cases.
import tensorflow as tf
from tensorflow import keras
model = keras.Sequential([
keras.layers.Dense(64, activation="relu", input_shape=(10,)),
keras.layers.Dense(32, activation="relu"),
keras.layers.Dense(1, activation="sigmoid"),
])
Functional API — for anything more complex: multiple inputs, multiple outputs, skip connections (like ResNet), shared layers. You define tensors explicitly and pass them through layers.
inputs = keras.Input(shape=(10,))
x = keras.layers.Dense(64, activation="relu")(inputs)
x = keras.layers.Dense(32, activation="relu")(x)
outputs = keras.layers.Dense(1, activation="sigmoid")(x)
model = keras.Model(inputs=inputs, outputs=outputs)
Both produce the same model here. The Functional API scales to architectures that the Sequential API cannot express.
Tip
Use Sequential for quick experiments and linear architectures. Switch to the Functional API the moment you need to branch the computation graph — multi-task learning, skip connections, or merging multiple inputs.
The Complete Workflow: Binary Classification¶
This is the pattern you will use most often. Walk through it once carefully — every step matters.
import numpy as np
import tensorflow as tf
from tensorflow import keras
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# ── 1. Data ──────────────────────────────────────────────────────────────────
X, y = make_classification(
n_samples=3000,
n_features=15,
n_informative=8,
n_redundant=3,
random_state=42,
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# Scale inputs — neural networks are sensitive to feature scale
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
# ── 2. Build ──────────────────────────────────────────────────────────────────
model = keras.Sequential([
keras.layers.Dense(64, activation="relu", input_shape=(X_train.shape[1],)),
keras.layers.Dense(32, activation="relu"),
keras.layers.Dense(1, activation="sigmoid"), # sigmoid for binary output
], name="binary_classifier")
# Print architecture: shows layer shapes and parameter counts
model.summary()
# Output:
# Model: "binary_classifier"
# ┌─────────────────────┬───────────────────┬──────────┐
# │ Layer (type) │ Output Shape │ Param # │
# ├─────────────────────┼───────────────────┼──────────┤
# │ dense (Dense) │ (None, 64) │ 1,024 │
# │ dense_1 (Dense) │ (None, 32) │ 2,080 │
# │ dense_2 (Dense) │ (None, 1) │ 33 │
# └─────────────────────┴───────────────────┴──────────┘
# Total params: 3,137 (12.25 KB)
# ── 3. Compile ────────────────────────────────────────────────────────────────
model.compile(
optimizer=keras.optimizers.Adam(learning_rate=0.001),
loss="binary_crossentropy",
metrics=["accuracy"],
)
# ── 4. Train ──────────────────────────────────────────────────────────────────
history = model.fit(
X_train,
y_train,
epochs=40,
batch_size=32,
validation_split=0.2, # Keras holds out 20% of X_train automatically
verbose=1,
)
# ── 5. Evaluate on test set ───────────────────────────────────────────────────
test_loss, test_accuracy = model.evaluate(X_test, y_test, verbose=0)
print(f"\nTest loss: {test_loss:.4f}")
print(f"Test accuracy: {test_accuracy:.4f}")
# Output:
# Test loss: 0.3247
# Test accuracy: 0.8617
# ── 6. Predict ────────────────────────────────────────────────────────────────
# model.predict() returns probabilities (floats between 0 and 1)
probabilities = model.predict(X_test[:5])
print("\nPredicted probabilities:", probabilities.flatten().round(3))
# Output: [0.823 0.134 0.912 0.067 0.741]
# Convert to binary labels
predicted_labels = (probabilities >= 0.5).astype(int).flatten()
print("Predicted labels:", predicted_labels)
print("True labels: ", y_test[:5])
Reading the Training History¶
model.fit() returns a History object. Plotting its contents tells you whether training went well.
def plot_training_history(history):
"""Plot loss and accuracy curves for train and validation."""
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
# Loss curves
ax1.plot(history.history["loss"], label="Train loss")
ax1.plot(history.history["val_loss"], label="Val loss", linestyle="--")
ax1.set_xlabel("Epoch")
ax1.set_ylabel("Loss")
ax1.set_title("Loss Curves")
ax1.legend()
# Accuracy curves
ax2.plot(history.history["accuracy"], label="Train accuracy")
ax2.plot(history.history["val_accuracy"], label="Val accuracy", linestyle="--")
ax2.set_xlabel("Epoch")
ax2.set_ylabel("Accuracy")
ax2.set_title("Accuracy Curves")
ax2.legend()
plt.tight_layout()
plt.savefig("training_history.png", dpi=120)
plt.show()
plot_training_history(history)
What the Curves Tell You¶
| Pattern | Diagnosis | Fix |
|---|---|---|
| Both curves still decreasing at end | Underfit — train longer | Increase epochs |
| Train loss falls, val loss rises after epoch N | Overfit — memorizing training data | Dropout, more data, early stopping |
| Val loss better than train loss | Dropout is active at training time (expected behavior) | No fix needed |
| Loss becomes NaN | Learning rate too high, or input not scaled | Reduce lr by 10x, check StandardScaler |
| Loss barely moves | Learning rate too small, or dead neurons | Increase lr, check initialization |
Multiclass Classification¶
When there are more than two classes, use softmax output and the right loss:
from sklearn.datasets import make_classification
# 4-class problem
X, y = make_classification(
n_samples=2000, n_features=12, n_classes=4,
n_informative=8, random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
model = keras.Sequential([
keras.layers.Dense(64, activation="relu", input_shape=(12,)),
keras.layers.Dense(32, activation="relu"),
keras.layers.Dense(4, activation="softmax"), # 4 outputs, sum to 1
])
model.compile(
optimizer="adam",
loss="sparse_categorical_crossentropy", # integer labels — no one-hot needed
metrics=["accuracy"],
)
history = model.fit(X_train, y_train,
validation_split=0.2, epochs=30, batch_size=32, verbose=0)
test_loss, test_acc = model.evaluate(X_test, y_test, verbose=0)
print(f"Test accuracy: {test_acc:.4f}")
# Output: Test accuracy: 0.7375
# Predict class probabilities for 3 samples
proba = model.predict(X_test[:3])
print("Class probabilities:\n", proba.round(3))
# Output:
# [[0.12 0.61 0.14 0.13]
# [0.05 0.08 0.77 0.10]
# [0.41 0.09 0.08 0.42]]
# Get the predicted class (argmax of softmax output)
predicted_classes = np.argmax(proba, axis=1)
print("Predicted classes:", predicted_classes)
Regression¶
For continuous targets, remove the output activation and use MSE:
from sklearn.datasets import make_regression
X, y = make_regression(n_samples=2000, n_features=10,
noise=0.2, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
scaler_X = StandardScaler()
scaler_y = StandardScaler()
X_train = scaler_X.fit_transform(X_train)
X_test = scaler_X.transform(X_test)
y_train_scaled = scaler_y.fit_transform(y_train.reshape(-1, 1)).flatten()
y_test_scaled = scaler_y.transform(y_test.reshape(-1, 1)).flatten()
model = keras.Sequential([
keras.layers.Dense(64, activation="relu", input_shape=(10,)),
keras.layers.Dense(32, activation="relu"),
keras.layers.Dense(1), # no activation — raw linear output for regression
])
model.compile(
optimizer="adam",
loss="mse",
metrics=["mae"],
)
history = model.fit(X_train, y_train_scaled,
validation_split=0.2, epochs=30, batch_size=32, verbose=0)
test_mse, test_mae = model.evaluate(X_test, y_test_scaled, verbose=0)
print(f"Test MSE: {test_mse:.4f} | Test MAE: {test_mae:.4f}")
Warning
For regression, always scale the target variable (y) in addition to the features. An unscaled target with large values (e.g., house prices in dollars) forces the output layer to produce huge numbers, which destabilizes training and requires very careful learning rate tuning.
Saving and Loading Models¶
# Save the entire model (architecture + weights + optimizer state)
model.save("my_model.keras")
# Load it back — ready to continue training or make predictions
loaded_model = keras.models.load_model("my_model.keras")
# Save only the weights (useful when architecture is defined in code)
model.save_weights("model_weights.weights.h5")
model.load_weights("model_weights.weights.h5")
Info
Use the .keras format (native Keras v3 format). Avoid the older .h5 whole-model format — it has compatibility issues with custom layers. The legacy SavedModel format still works but .keras is the current recommendation.
Activation Function Quick Reference¶
# Which output activation to use — the most common source of architecture mistakes
# Binary classification (one output, predict P(class=1))
output = keras.layers.Dense(1, activation="sigmoid")
# Multiclass classification (N outputs, predict P(class=k))
output = keras.layers.Dense(n_classes, activation="softmax")
# Regression (predict a continuous value)
output = keras.layers.Dense(1) # no activation
# Multi-label classification (each class is independent binary)
output = keras.layers.Dense(n_classes, activation="sigmoid")
Success
The output layer and loss function always go together. Sigmoid output → binary_crossentropy. Softmax output → categorical or sparse_categorical_crossentropy. No activation → mse or mae. Mixing these is one of the most common bugs when building networks.
Interview Questions¶
Q1: What does model.compile() do?
Show answer
model.compile() configures the training process. It attaches three things to the model: the optimizer (the algorithm that will update weights), the loss function (what the optimizer will minimize), and the metrics (what to track and report during training, but not optimize). No computation happens at compile time — it is pure configuration.
Q2: What is validation_split in model.fit() doing?
Show answer
validation_split=0.2 tells Keras to hold out the last 20% of the training data before training begins. This held-out slice is used at the end of each epoch to compute val_loss and val_accuracy — metrics on data the model has never seen during weight updates. It is how you detect overfitting during training. Note: Keras takes the last N rows, so shuffle your data before fitting if order matters.
Q3: What is the difference between model.evaluate() and model.predict()?
Show answer
model.evaluate() runs a forward pass on the provided data and computes the loss and metrics — useful for reporting a final test score. model.predict() runs a forward pass and returns the raw output array (probabilities for classification, continuous values for regression) — useful when you need the actual predictions to make decisions or compute custom metrics.
Q4: Why is StandardScaler important before training a neural network?
Show answer
Neural networks use gradient descent, and gradients are sensitive to the scale of input features. A feature with values in the range [0, 100,000] will dominate the gradient signal over a feature in [0, 1], causing slow convergence, instability, and often worse final performance. StandardScaler maps each feature to mean 0 and standard deviation 1, putting all features on an equal footing. Always fit the scaler on training data only and transform both train and test.
What's Next¶
You've covered the Keras Sequential API for binary classification, multiclass classification, and regression, the output activation and loss function pairing table, training curve plotting for learning rate diagnosis, model saving and loading, and the activation quick-reference for avoiding the most common architecture mistakes. Next up: 04-overfitting-regularization — where you'll learn to diagnose overfitting from training curves, apply dropout and L2 weight decay as regularisation techniques, and use early stopping to prevent overtraining without manual epoch tuning.
Optional Deep Dive
Read the TensorFlow documentation on the Keras training API at https://www.tensorflow.org/guide/keras/training_with_built_in_methods — it covers custom training loops, tf.data datasets for efficient input pipelines, callbacks, and distributed training, giving you the full picture of production Keras workflows beyond the model.fit() defaults used here.
Back: Training Neural Networks | Next: Overfitting and Regularization