Tutorial 2 — Classification¶
Learn how to build a classifier for multi-class problems. We will use synthetic data, train/test splits, batch normalisation, dropout, and callbacks.
Binary classification¶
For problems with two classes (spam/not-spam, pass/fail, …) the output is a single probability and the loss is binary crossentropy.
import numpy as np
from snn.model import Sequential
from snn.layers import Dense, Dropout, BatchNormalization
from snn.utils import train_test_split, standardize
rng = np.random.default_rng(42)
# Two blobs of points
X0 = rng.normal(loc=[-2, -2], scale=1.0, size=(300, 2))
X1 = rng.normal(loc=[ 2, 2], scale=1.0, size=(300, 2))
X = np.vstack([X0, X1])
y = np.array([[0]] * 300 + [[1]] * 300, dtype=np.float64)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
X_train = standardize(X_train) # zero-mean, unit variance
X_test = standardize(X_test)
model = Sequential([
Dense(32, activation="relu"),
Dropout(0.2),
Dense(16, activation="relu"),
Dense(1, activation="sigmoid"),
])
model.compile("adam", "binary_crossentropy", metrics=["binary_accuracy"])
model.fit(X_train, y_train, epochs=30, batch_size=32,
validation_data=(X_test, y_test))
model.evaluate(X_test, y_test)
Multi-class classification¶
For problems with 3+ classes encode targets as one-hot vectors and use categorical crossentropy.
One-hot encoding¶
from snn.utils import to_categorical
y_int = np.array([0, 1, 2, 0, 2, 1]) # integer class labels
y_hot = to_categorical(y_int, num_classes=3)
# [[1,0,0], [0,1,0], [0,0,1], [1,0,0], [0,0,1], [0,1,0]]
Full example¶
import numpy as np
from snn.model import Sequential
from snn.layers import Dense, Dropout, BatchNormalization
from snn.utils import to_categorical, train_test_split, standardize
rng = np.random.default_rng(0)
# 4-class synthetic data
X = rng.normal(size=(800, 6))
y_int = (
(X[:, 0] + X[:, 1] > 0).astype(int) * 2
+ (X[:, 2] > 0).astype(int)
) # 4 classes: 0-3
y = to_categorical(y_int, num_classes=4)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
X_train, X_test = standardize(X_train), standardize(X_test)
model = Sequential([
Dense(64, activation="relu"),
BatchNormalization(),
Dropout(0.3),
Dense(32, activation="relu"),
Dense(4, activation="softmax"),
])
model.compile(
optimizer="adam",
loss="categorical_crossentropy",
metrics=["categorical_accuracy"],
learning_rate=3e-4,
verbose=True, # print compile summary
)
model.fit(X_train, y_train, epochs=40, batch_size=32,
validation_data=(X_test, y_test))
results = model.evaluate(X_test, y_test)
print(f"\nTest accuracy: {results['categorical_accuracy']:.4f}")
Integer targets (no one-hot needed)¶
If your targets are plain integers (0, 1, 2, …) you can use sparse categorical crossentropy — no one-hot encoding required.
model.compile(
"adam",
"sparse_categorical_crossentropy",
metrics=["categorical_accuracy"],
)
# y_train stays as shape (N,) integers — no to_categorical
model.fit(X_train, y_train_int, epochs=40, batch_size=32)
Early stopping¶
Stop training automatically when validation loss stops improving:
from snn.utils import EarlyStopping
early_stop = EarlyStopping(
monitor="val_loss",
patience=10, # stop if no improvement for 10 epochs
min_delta=1e-4,
restore_best_weights=False,
verbose=1,
)
model.fit(
X_train, y_train,
epochs=200,
validation_data=(X_test, y_test),
callbacks=[early_stop],
)
From-logits mode¶
If your last Dense layer has no activation (outputs raw scores), pass
from_logits=True so the loss applies softmax internally for numerical
stability:
model = Sequential([
Dense(64, activation="relu"),
Dense(4), # no activation — raw logits
])
model.compile(
"adam",
"categorical_crossentropy",
from_logits=True, # ← loss handles the softmax
)
Precision, recall, and F1¶
from snn.metrics import precision, recall, f1_score, confusion_matrix
y_pred = model.predict(X_test).argmax(axis=1)
y_true = y_test.argmax(axis=1)
print("Precision:", precision(model.predict(X_test), y_test))
print("Recall: ", recall (model.predict(X_test), y_test))
print("F1: ", f1_score (model.predict(X_test), y_test))
print(confusion_matrix(y_pred, y_true))
What next?¶
Tutorial 3 — regression and non-linear function fitting.
Tutorial 4 — deep dive into choosing the right activation.