CSI 4106 - Fall 2026
Version: Oct 18, 2026 16:55
Artificial neural networks are machine learning models built from interconnected computational units, inspired by biological neurons.
Lakoff and Narayanan (2025)
Neurons are just cells. Alone, they are incapable of thought. But combined into circuits hooked up to our bodies in just the right ways, they have allowed human beings to survive, think, communicate, and create all of culture.
where x_1, x_2 \in \{0,1\} and f(z) is a step function:
f(z)= \begin{cases}0, & z<\theta \\ 1, & z \geq \theta\end{cases}
f(x_1 + x_2)= \begin{cases}0, & x_1 + x_2 <\theta \\ 1, & x_1 + x_2 \geq \theta\end{cases}
With \theta = 2, the unit implements an AND logic gate.
With \theta = 1, the unit implements an OR logic gate.
Networks of threshold units can implement Boolean functions by composing logical operations.
With the connections and thresholds fixed, the network performs a specified computation.
Learning introduces a different question: how can data determine the parameters?
X is the input data matrix where each row corresponds to an example and each column represents one of the D features.
W is the weight matrix, structured with one row per input (feature) and one column per neuron.
Bias terms can be represented separately; both approaches appear in the literature. Here, b is a vector with a length equal to the number of neurons.
At least one input is true, and they are not both true:
A\oplus B=(A\lor B)\land\neg(A\land B)
| A | B | Target y | o_1: AND | o_2: OR | o_3: XOR |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 0 | 1 | 1 |
| 1 | 0 | 1 | 0 | 1 | 1 |
| 1 | 1 | 0 | 1 | 1 | 0 |
Each computational unit performs two operations:
z_j=\sum_{i\to j} w_{i,j}o_i+b_j, \qquad o_j=\varphi(z_j).
(np.float64(0.48638976530233274), np.float64(0.6254814695767328))
\sigma(z)=\frac{1}{1+e^{-z}}
Output range: (0,1).
import matplotlib.pyplot as plt
def relu(z):
return np.maximum(0, z)
def plot_activation(activation, name, output_limits):
z = np.linspace(-6, 6, 400)
fig, ax = plt.subplots(figsize=(5, 3.5))
ax.plot(z, activation(z), color="black", linewidth=2)
ax.set(xlabel="$z$", ylabel="Output", title=name, ylim=output_limits)
ax.grid(True)
fig.patch.set_alpha(0)
plt.show()
plot_activation(sigmoid, "Sigmoid", (-0.1, 1.1))\tanh(z)=2\sigma(2z)-1
Output range: (-1,1).
\operatorname{ReLU}(z)=\max(0,z)
Output range: [0,\infty).
z = np.linspace(-4, 4, 401)
fig, ax = plt.subplots(figsize=(7, 4))
ax.step(z, (z >= 0).astype(int), where="post", label="Step")
ax.plot(z, sigmoid(z), label="Sigmoid")
ax.plot(z, np.tanh(z), label="Tanh")
ax.plot(z, relu(z), label="ReLU")
ax.set(xlabel="$z$", ylabel="Output", ylim=(-1.2, 4.2))
ax.grid(True)
ax.legend()
plt.show()The universal approximation theorem (UAT) establishes that, on a compact input domain, a network with a sufficiently wide single hidden layer, sigmoid activations, and a linear output can approximate any specified continuous target function to any desired accuracy.
def plot_parameter_effect(activation, name, weights, biases, title):
x = np.linspace(-10, 10, 400)
fig, ax = plt.subplots(figsize=(6, 4))
for w in weights:
for b in biases:
ax.plot(x, activation(w * x + b), label=f"w = {w}, b = {b}")
ax.set(xlabel="$x$", ylabel=f"{name}(wx + b)", title=title)
ax.grid(True)
ax.legend()
plt.show()
plot_parameter_effect(sigmoid, "Sigmoid", weights=[0.5, 1, 2, 5], biases=[0],
title="Varying w, with b = 0")Approximate the same target curve using networks with different numbers of hidden units.
from sklearn.neural_network import MLPRegressor
from sklearn.model_selection import train_test_split
X_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size=0.1, random_state=42)
models = []
sizes = [1, 2, 5, 10, 100]
for n in sizes:
regressor = MLPRegressor(hidden_layer_sizes=(n,), max_iter=5000, random_state=42)
regressor.fit(X_train, y_train)
models.append(regressor) colors = plt.colormaps["cool"].resampled(len(sizes))
plot_inputs = np.sort(X_valid, axis=0)
fig, ax = plt.subplots(figsize=(7, 4))
for i, n in enumerate(sizes):
predictions = models[i].predict(plot_inputs)
ax.plot(plot_inputs, predictions, color=colors(i), label=f"Hidden units = {n}")
ax.plot(plot_inputs, f(plot_inputs), "r.", label="Target")
ax.set(xlabel="$x$", ylabel="Output")
ax.legend()
plt.show()PyTorch, TensorFlow, and JAX provide tools for numerical computation and automatic differentiation.
Keras provides a high-level API for building and training models with these backends.
The main steps are:
Fashion-MNIST contains images of clothing and accessories:
Model: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ ┃ Layer (type) ┃ Output Shape ┃ Param # ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩ │ flatten (Flatten) │ (None, 784) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dense (Dense) │ (None, 300) │ 235,500 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dense_1 (Dense) │ (None, 100) │ 30,100 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dense_2 (Dense) │ (None, 10) │ 1,010 │ └─────────────────────────────────┴────────────────────────┴───────────────┘
Total params: 266,610 (1.02 MB)
Trainable params: 266,610 (1.02 MB)
Non-trainable params: 0 (0.00 B)
Epoch 1/30
1719/1719 - 1s - 839us/step - accuracy: 0.7616 - loss: 0.7239 - val_accuracy: 0.8298 - val_loss: 0.5026
Epoch 2/30
1719/1719 - 1s - 705us/step - accuracy: 0.8299 - loss: 0.4894 - val_accuracy: 0.8418 - val_loss: 0.4472
Epoch 3/30
1719/1719 - 1s - 710us/step - accuracy: 0.8448 - loss: 0.4434 - val_accuracy: 0.8480 - val_loss: 0.4233
Epoch 4/30
1719/1719 - 1s - 714us/step - accuracy: 0.8548 - loss: 0.4154 - val_accuracy: 0.8550 - val_loss: 0.4088
Epoch 5/30
1719/1719 - 1s - 703us/step - accuracy: 0.8607 - loss: 0.3951 - val_accuracy: 0.8584 - val_loss: 0.3970
Epoch 6/30
1719/1719 - 1s - 714us/step - accuracy: 0.8667 - loss: 0.3790 - val_accuracy: 0.8594 - val_loss: 0.3889
Epoch 7/30
1719/1719 - 1s - 701us/step - accuracy: 0.8713 - loss: 0.3656 - val_accuracy: 0.8616 - val_loss: 0.3824
Epoch 8/30
1719/1719 - 1s - 708us/step - accuracy: 0.8748 - loss: 0.3539 - val_accuracy: 0.8642 - val_loss: 0.3780
Epoch 9/30
1719/1719 - 1s - 701us/step - accuracy: 0.8783 - loss: 0.3432 - val_accuracy: 0.8666 - val_loss: 0.3740
Epoch 10/30
1719/1719 - 1s - 695us/step - accuracy: 0.8817 - loss: 0.3336 - val_accuracy: 0.8678 - val_loss: 0.3684
Epoch 11/30
1719/1719 - 1s - 695us/step - accuracy: 0.8851 - loss: 0.3247 - val_accuracy: 0.8702 - val_loss: 0.3641
Epoch 12/30
1719/1719 - 1s - 708us/step - accuracy: 0.8875 - loss: 0.3165 - val_accuracy: 0.8726 - val_loss: 0.3601
Epoch 13/30
1719/1719 - 1s - 695us/step - accuracy: 0.8898 - loss: 0.3089 - val_accuracy: 0.8736 - val_loss: 0.3578
Epoch 14/30
1719/1719 - 1s - 703us/step - accuracy: 0.8922 - loss: 0.3017 - val_accuracy: 0.8724 - val_loss: 0.3554
Epoch 15/30
1719/1719 - 1s - 710us/step - accuracy: 0.8942 - loss: 0.2951 - val_accuracy: 0.8736 - val_loss: 0.3543
Epoch 16/30
1719/1719 - 1s - 702us/step - accuracy: 0.8962 - loss: 0.2887 - val_accuracy: 0.8734 - val_loss: 0.3520
Epoch 17/30
1719/1719 - 1s - 697us/step - accuracy: 0.8983 - loss: 0.2829 - val_accuracy: 0.8738 - val_loss: 0.3522
Epoch 18/30
1719/1719 - 1s - 686us/step - accuracy: 0.9002 - loss: 0.2771 - val_accuracy: 0.8746 - val_loss: 0.3495
Epoch 19/30
1719/1719 - 1s - 689us/step - accuracy: 0.9021 - loss: 0.2717 - val_accuracy: 0.8758 - val_loss: 0.3478
Epoch 20/30
1719/1719 - 1s - 710us/step - accuracy: 0.9043 - loss: 0.2664 - val_accuracy: 0.8760 - val_loss: 0.3465
Epoch 21/30
1719/1719 - 1s - 706us/step - accuracy: 0.9060 - loss: 0.2615 - val_accuracy: 0.8754 - val_loss: 0.3465
Epoch 22/30
1719/1719 - 1s - 703us/step - accuracy: 0.9081 - loss: 0.2565 - val_accuracy: 0.8772 - val_loss: 0.3449
Epoch 23/30
1719/1719 - 1s - 707us/step - accuracy: 0.9102 - loss: 0.2519 - val_accuracy: 0.8782 - val_loss: 0.3454
Epoch 24/30
1719/1719 - 1s - 701us/step - accuracy: 0.9121 - loss: 0.2471 - val_accuracy: 0.8780 - val_loss: 0.3445
Epoch 25/30
1719/1719 - 1s - 698us/step - accuracy: 0.9145 - loss: 0.2427 - val_accuracy: 0.8790 - val_loss: 0.3437
Epoch 26/30
1719/1719 - 1s - 703us/step - accuracy: 0.9158 - loss: 0.2384 - val_accuracy: 0.8792 - val_loss: 0.3446
Epoch 27/30
1719/1719 - 1s - 696us/step - accuracy: 0.9172 - loss: 0.2341 - val_accuracy: 0.8810 - val_loss: 0.3445
Epoch 28/30
1719/1719 - 1s - 706us/step - accuracy: 0.9193 - loss: 0.2299 - val_accuracy: 0.8804 - val_loss: 0.3441
Epoch 29/30
1719/1719 - 1s - 701us/step - accuracy: 0.9210 - loss: 0.2260 - val_accuracy: 0.8808 - val_loss: 0.3445
Epoch 30/30
1719/1719 - 1s - 703us/step - accuracy: 0.9223 - loss: 0.2220 - val_accuracy: 0.8800 - val_loss: 0.3440
epochs = np.arange(1, len(history.history["loss"]) + 1)
fig, axes = plt.subplots(1, 2, figsize=(9, 3.5))
for ax, metric, title in zip(axes, ["loss", "accuracy"], ["Loss", "Accuracy"]):
ax.plot(epochs, history.history[metric], label="Training")
ax.plot(epochs, history.history[f"val_{metric}"], label="Validation")
ax.set(xlabel="Epoch", ylabel=title)
ax.grid(True)
ax.legend()
axes[1].set_ylim(0, 1)
plt.tight_layout()
plt.show()array([[0. , 0. , 0. , 0. , 0. , 0.33, 0. , 0.01, 0. , 0.65],
[0. , 0. , 1. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ],
[0. , 1. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ]],
dtype=float32)
fig, axes = plt.subplots(1, 3, figsize=(8, 3))
for ax, image, prediction, observed in zip(axes, X_new, y_pred, y_observed):
ax.imshow(image, cmap="binary", interpolation="nearest")
ax.set_title(f"Predicted: {class_names[prediction]}\n"
f"Observed: {class_names[observed]}", fontsize=10)
ax.axis("off")
plt.tight_layout()
plt.show() precision recall f1-score support
T-shirt/top 0.842 0.810 0.826 1000
Trouser 0.992 0.964 0.978 1000
Pullover 0.744 0.850 0.793 1000
Dress 0.824 0.929 0.873 1000
Coat 0.806 0.794 0.800 1000
Sandal 0.885 0.986 0.933 1000
Shirt 0.777 0.618 0.689 1000
Sneaker 0.944 0.911 0.927 1000
Bag 0.961 0.963 0.962 1000
Ankle boot 0.983 0.918 0.949 1000
accuracy 0.874 10000
macro avg 0.876 0.874 0.873 10000
weighted avg 0.876 0.874 0.873 10000
Marcel Turcotte
School of Electrical Engineering and Computer Science (EECS)
University of Ottawa