Introduction to Artificial Neural Networks

CSI 4106 - Fall 2026

Marcel Turcotte

Version: Oct 18, 2026 16:55

Preamble

Message of the Day

Message of the Day

Message of the Day (continued)

Message of the Day (2024)

Learning objectives

  • Explain how weights, biases, and activations determine a network’s forward computation.
  • Explain why hidden layers with nonlinear activations can represent XOR.
  • Compare the shapes and output ranges of sigmoid, tanh, and ReLU.
  • State the assumptions and limits of universal approximation.
  • Build and evaluate a Keras classifier on Fashion-MNIST using consistent preprocessing and separate training, validation, and test sets.

Introduction

TensorFlow Playground

Neural Networks (NN)

Artificial neural networks are machine learning models built from interconnected computational units, inspired by biological neurons.

  • Computation: with fixed parameters, a network maps inputs to outputs.
  • Learning: training uses examples to adjust the parameters.

Machine Learning Problems

  • Supervised Learning: Classification, Regression

  • Unsupervised Learning: Autoencoders, Self-Supervised

  • Reinforcement Learning: Now an Integral Component

A neuron

Interconnected neurons

Neural Mind

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.

Connectionist

Hierarchy of concepts

Basics

Computation with threshold units

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}

Computation with threshold units

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 compute functions

  • 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?

Perceptron

Perceptron

Threshold logic unit

Simple Step Functions

\text{Heaviside}(t) =

  • 1, if t \geq 0

  • 0, if t < 0

\text{sign}(t) =

  • 1, if t > 0

  • 0, if t = 0

  • -1, if t < 0

Notation

Notation

Perceptron

Perceptron

Notation

Notation

  • 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.

From computation to learning

  • The forward computation uses the current weights and biases to produce a prediction.
  • Training adjusts these parameters using examples and their target outputs.
  • Rosenblatt’s perceptron established this shift toward learning from data.

A limitation: XOR

XOR as a composition of gates

At least one input is true, and they are not both true:

A\oplus B=(A\lor B)\land\neg(A\land B)

  • OR detects whether at least one input is true.
  • AND detects whether both inputs are true.
  • Combining their outputs gives XOR.

Multilayer Perceptron

XOR: intermediate computations

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

Feedforward Neural Network (FNN)

Weighted sum and activation

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

  • z_j: weighted sum plus bias, before activation.
  • o_j: the unit’s output, after applying the activation \varphi.

Forward pass (computation)

o_3 = \sigma(w_{1,3}x^{(1)}+w_{2,3}x^{(2)}+b_3)

o_4 = \sigma(w_{1,4}x^{(1)}+w_{2,4}x^{(2)}+b_4)

o_5 = \sigma(w_{1,5}x^{(1)}+w_{2,5}x^{(2)}+b_5)

o_6 = \sigma(w_{3,6}o_3+w_{4,6}o_4+w_{5,6}o_5+b_6)

o_7 = \sigma(w_{3,7}o_3+w_{4,7}o_4+w_{5,7}o_5+b_7)

Numerical forward pass

Code
import numpy as np
from scipy.special import expit as sigmoid

rng = np.random.default_rng(42)
x1, x2 = 0.5, 0.9

w13, w14, w15, w23, w24, w25 = rng.uniform(-1, 1, size=6)
w36, w46, w56, w37, w47, w57 = rng.uniform(-1, 1, size=6)
b3, b4, b5, b6, b7 = np.zeros(5)
z3 = w13 * x1 + w23 * x2 + b3
o3 = sigmoid(z3)
z4 = w14 * x1 + w24 * x2 + b4
o4 = sigmoid(z4)
z5 = w15 * x1 + w25 * x2 + b5
o5 = sigmoid(z5)
z6 = w36 * o3 + w46 * o4 + w56 * o5 + b6
o6 = sigmoid(z6)
z7 = w37 * o3 + w47 * o4 + w57 * o5 + b7
o7 = sigmoid(z7)

(o6, o7)
(np.float64(0.48638976530233274), np.float64(0.6254814695767328))

Forward pass in a deep network

Forward pass with biases

Why nonlinear activations?

  • Hidden layers transform their inputs into intermediate representations.
  • Without nonlinear activations between layers, their composition is still one affine transformation.
  • Nonlinear activations allow the network to represent more complex input-output relationships.

Activations and training

  • Backpropagation computes gradients; an optimizer uses them to update parameters.
  • The derivatives of a hard step function do not provide a useful signal for ordinary gradient-based training.
  • Modern networks use activations such as sigmoid, tanh, and ReLU.

Sigmoid

\sigma(z)=\frac{1}{1+e^{-z}}

Output range: (0,1).

Code
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))

Hyperbolic tangent (tanh)

\tanh(z)=2\sigma(2z)-1

Output range: (-1,1).

Code
plot_activation(np.tanh, "Tanh", (-1.1, 1.1))

Rectified linear unit (ReLU)

\operatorname{ReLU}(z)=\max(0,z)

Output range: [0,\infty).

Code
plot_activation(relu, "ReLU", (-0.5, 6.5))

Comparing activation functions

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

Universal Approximation

Definition

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.

Effect of varying w: sigmoid

Code
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")

Effect of varying b: sigmoid

Code
plot_parameter_effect(sigmoid, "Sigmoid", weights=[1], biases=[-5, -2, 0, 2, 5],
                      title="Varying b, with w = 1")

Effect of varying w: ReLU

Code
plot_parameter_effect(relu, "ReLU", weights=[0.5, 1, 2, 5], biases=[0],
                      title="Varying w, with b = 0")

Effect of varying b: ReLU

Code
plot_parameter_effect(relu, "ReLU", weights=[1], biases=[-5, -2, 0, 2, 5],
                      title="Varying b, with w = 1")

Visual demonstration with code

Approximate the same target curve using networks with different numbers of hidden units.

# Defining the function to be approximated

def f(x):
    return 2 * x**3 + 4 * x**2 - 5 * x + 1

# Generating a dataset, x in [-4,2), f(x) as above

approximation_rng = np.random.default_rng(42)
X = approximation_rng.uniform(-4, 2, size=(1000, 1))

y = f(X.flatten())

Increasing the number of neurons

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) 

Comparing fitted curves

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

Comparing training losses

Code
fig, ax = plt.subplots(figsize=(7, 4))
for i, n in enumerate(sizes):
    ax.plot(models[i].loss_curve_, color=colors(i), label=f"Hidden units = {n}")
ax.set(title="Training loss", xlabel="Iteration", ylabel="Loss")
ax.legend()
plt.show()

Universal Approximation

Let’s code

Frameworks

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.

Keras

The main steps are:

  1. Define the network’s layers.
  2. Compile with a loss, an optimizer, and evaluation metrics.
  3. Fit the model to training data and monitor validation performance.
  4. Evaluate on the test set and make predictions.

Fashion-MNIST dataset

Fashion-MNIST contains images of clothing and accessories:

  • 60,000 training images and 10,000 test images.
  • Each image has 28 × 28 grayscale pixels.
  • Each image has one integer label, from 10 classes.

Loading and splitting

import tensorflow as tf

(X_train_full, y_train_full), (X_test, y_test) = (
    tf.keras.datasets.fashion_mnist.load_data()
)

X_train, y_train = X_train_full[:-5000], y_train_full[:-5000]
X_valid, y_valid = X_train_full[-5000:], y_train_full[-5000:]

Inspecting and scaling the inputs

X_train.shape, X_train.dtype
((55000, 28, 28), dtype('uint8'))

Convert pixel intensities from 0–255 integers to 0–1 floating-point values in every split.

X_train = X_train.astype("float32") / 255.0
X_valid = X_valid.astype("float32") / 255.0
X_test = X_test.astype("float32") / 255.0

Images and labels

Code
class_names = ["T-shirt/top", "Trouser", "Pullover", "Dress", "Coat",
               "Sandal", "Shirt", "Sneaker", "Bag", "Ankle boot"]

plt.figure(figsize=(2, 2))
plt.imshow(X_train[0], cmap="binary")
plt.title(class_names[y_train[0]])
plt.axis("off")
plt.show()

First 40 images

Code
fig, axes = plt.subplots(4, 10, figsize=(12, 4.8))
for index, ax in enumerate(axes.flat):
    ax.imshow(X_train[index], cmap="binary", interpolation="nearest")
    ax.set_title(class_names[y_train[index]], fontsize=10)
    ax.axis("off")
plt.tight_layout()
plt.show()

Creating a model

tf.keras.utils.set_random_seed(42)

model = tf.keras.Sequential([
    tf.keras.Input(shape=(28, 28)),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(300, activation="relu"),
    tf.keras.layers.Dense(100, activation="relu"),
    tf.keras.layers.Dense(10, activation="softmax")
])

Model structure and parameters

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

Compiling the model

model.compile(
    loss="sparse_categorical_crossentropy",
    optimizer=tf.keras.optimizers.SGD(learning_rate=0.01),
    metrics=["accuracy"]
)

Training the model

history = model.fit(
    X_train, y_train, epochs=30, batch_size=32,
    validation_data=(X_valid, y_valid), verbose=2
)
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

Training and validation curves

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

Final test evaluation

test_loss, test_accuracy = model.evaluate(X_test, y_test, verbose=0)
print(f"Test loss: {test_loss:.3f}")
print(f"Test accuracy: {test_accuracy:.3f}")
Test loss: 0.362
Test accuracy: 0.874

Making predictions

X_new = X_test[:3]
y_proba = model.predict(X_new, verbose=0)
y_proba.round(2)
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)
y_pred = y_proba.argmax(axis=1)
y_pred
array([9, 2, 1])
y_observed = y_test[:3]
y_observed
array([9, 2, 1], dtype=uint8)

Predicted and observed labels

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

Test performance by class

from sklearn.metrics import classification_report

y_test_proba = model.predict(X_test, verbose=0)
y_test_pred = y_test_proba.argmax(axis=1)
print(classification_report(y_test, y_test_pred,
                            target_names=class_names, digits=3))
              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

Conclusion

Summary

  • Computation: a forward pass maps inputs to outputs using weights, biases, and activations.
  • Representation: nonlinear hidden layers compute intermediate features that can make XOR separable.
  • Learning: training adjusts parameters; backpropagation computes gradients for an optimizer.
  • Universal approximation: suitable parameters can exist without a guarantee that training will find them.
  • Implementation: Keras connects these ideas in a complete Fashion-MNIST classification workflow.
  • Evaluation: apply consistent preprocessing and distinguish training, validation, and test performance.

3Blue1Brown on Deep Learning

Next lecture

  • We will explore the implementation of artificial neural networks.

References

Cybenko, George V. 1989. “Approximation by Superpositions of a Sigmoidal Function.” Mathematics of Control, Signals and Systems 2: 303–14. https://api.semanticscholar.org/CorpusID:3958369.
Géron, Aurélien. 2022. Hands-on Machine Learning with Scikit-Learn, Keras, and TensorFlow. 3rd ed. O’Reilly Media, Inc.
Goodfellow, Ian, Yoshua Bengio, and Aaron Courville. 2016. Deep Learning. Adaptive Computation and Machine Learning. MIT Press. https://dblp.org/rec/books/daglib/0040158.
Hornik, Kurt, Maxwell Stinchcombe, and Halbert White. 1989. “Multilayer Feedforward Networks Are Universal Approximators.” Neural Networks 2 (5): 359–66. https://doi.org/https://doi.org/10.1016/0893-6080(89)90020-8.
Lakoff, George, and Srini Narayanan. 2025. The Neural Mind: How Brains Think. 1st ed. University of Chicago Press. https://press.uchicago.edu/ucp/books/book/chicago/N/bo243406239.html.
LeCun, Yann, Yoshua Bengio, and Geoffrey Hinton. 2015. “Deep Learning.” Nature 521 (7553): 436–44. https://doi.org/10.1038/nature14539.
LeNail, Alexander. 2019. “NN-SVG: Publication-Ready Neural Network Architecture Schematics.” Journal of Open Source Software 4 (33): 747. https://doi.org/10.21105/joss.00747.
McCulloch, Warren S, and Walter Pitts. 1943. “A logical calculus of the ideas immanent in nervous activity.” The Bulletin of Mathematical Biophysics 5 (4): 115–33. https://doi.org/10.1007/bf02478259.
Minsky, Marvin, and Seymour Papert. 1969. Perceptrons: An Introduction to Computational Geometry. MIT Press.
Rosenblatt, F. 1958. “The perceptron: A probabilistic model for information storage and organization in the brain.” Psychological Review 65 (6): 386–408. https://doi.org/10.1037/h0042519.
Rumelhart, David E., Geoffrey E. Hinton, and Ronald J. Williams. 1986. “Learning representations by back-propagating errors.” Nature 323 (6088): 533–36. https://doi.org/10.1038/323533a0.
Russell, Stuart, and Peter Norvig. 2020. Artificial Intelligence: A Modern Approach. 4th ed. Pearson. http://aima.cs.berkeley.edu/.

Marcel Turcotte

[email protected]

School of Electrical Engineering and Computer Science (EECS)

University of Ottawa