0% found this document useful (0 votes)
14 views10 pages

Neural Networks: Mathematical Foundations

The document provides an introduction to the mathematical foundations and practical implementation of neural networks using Keras and TensorFlow. It covers data manipulation, tensor operations, model training, and evaluation, specifically using the MNIST dataset. Additionally, it includes examples of building a neural network from scratch and performing gradient-based optimization.

Uploaded by

slakshmi.epcet
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
14 views10 pages

Neural Networks: Mathematical Foundations

The document provides an introduction to the mathematical foundations and practical implementation of neural networks using Keras and TensorFlow. It covers data manipulation, tensor operations, model training, and evaluation, specifically using the MNIST dataset. Additionally, it includes examples of building a neural network from scratch and performing gradient-based optimization.

Uploaded by

slakshmi.epcet
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

The mathematical building blocks of neural networks

A first look at a neural network

August 6, 2025

[ ]: import os
[Link]["KERAS_BACKEND"] = "jax"

import os
from [Link] import register_cell_magic

@register_cell_magic
def backend(line, cell):
current, required = [Link]("KERAS_BACKEND", ""), [Link]()[-1]
if current == required:
get_ipython().run_cell(cell)
else:
print(
f"This cell requires the {required} backend. To run it, change␣
,→KERAS_BACKEND to "

f"\"{required}\" at the top of the notebook, restart the runtime,␣


,→and rerun the notebook."

# The mathematical building blocks of neural networks


# A first look at a neural network

from [Link] import mnist

(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

train_images.shape

len(train_labels)

train_labels

test_images.shape

len(test_labels)

1
test_labels

import keras
from keras import layers

model = [Link](
[
[Link](512, activation="relu"),
[Link](10, activation="softmax"),
]
)

[Link](
optimizer="adam",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)

train_images = train_images.reshape((60000, 28 * 28))


train_images = train_images.astype("float32") / 255
test_images = test_images.reshape((10000, 28 * 28))
test_images = test_images.astype("float32") / 255

[Link](train_images, train_labels, epochs=5, batch_size=128)

test_digits = test_images[0:10]
predictions = [Link](test_digits)
predictions[0]

predictions[0].argmax()

predictions[0][7]

test_labels[0]

test_loss, test_acc = [Link](test_images, test_labels)


print(f"test_acc: {test_acc}")

# Data representations for neural networks


# Scalars (rank-0 tensors)
import numpy as np
x = [Link](12)
x

[Link]

2
# Matrices (rank-2 tensors)
x = [Link]([[5, 78, 2, 34, 0],
[6, 79, 3, 35, 1],
[7, 80, 4, 36, 2]])
[Link]

# Rank-3 tensors and higher-rank tensors

x = [Link]([[[5, 78, 2, 34, 0],


[6, 79, 3, 35, 1],
[7, 80, 4, 36, 2]],
[[5, 78, 2, 34, 0],
[6, 79, 3, 35, 1],
[7, 80, 4, 36, 2]],
[[5, 78, 2, 34, 0],
[6, 79, 3, 35, 1],
[7, 80, 4, 36, 2]]])
[Link]

# Key attributes
from [Link] import mnist

(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

train_images.ndim

train_images.shape

train_images.dtype

import [Link] as plt

digit = train_images[4]
[Link](digit, cmap=[Link])
[Link]()

train_labels[4]

# Manipulating tensors in NumPy


my_slice = train_images[10:100]
my_slice.shape

my_slice = train_images[10:100, :, :]
my_slice.shape

3
my_slice = train_images[10:100, 0:28, 0:28]
my_slice.shape

my_slice = train_images[:, 14:, 14:]

my_slice = train_images[:, 7:-7, 7:-7]

# The notion of data batches

batch = train_images[:128]

batch = train_images[128:256]

n = 3
batch = train_images[128 * n : 128 * (n + 1)]

#Real-world examples of data tensors


#Vector data
#Timeseries data or sequence data
#Image data
#Video data
#The gears of neural networks: tensor operations
#Element-wise operations

def naive_relu(x):
assert len([Link]) == 2
x = [Link]()
for i in range([Link][0]):
for j in range([Link][1]):
x[i, j] = max(x[i, j], 0)
return x

def naive_add(x, y):


assert len([Link]) == 2
assert [Link] == [Link]
x = [Link]()
for i in range([Link][0]):
for j in range([Link][1]):
x[i, j] += y[i, j]
return x

import time

x = [Link]((20, 100))
y = [Link]((20, 100))

t0 = [Link]()

4
for _ in range(1000):
z = x + y
z = [Link](z, 0.0)
print("Took: {0:.2f} s".format([Link]() - t0))

t0 = [Link]()
for _ in range(1000):
z = naive_add(x, y)
z = naive_relu(z)
print("Took: {0:.2f} s".format([Link]() - t0))

# Broadcasting
import numpy as np

X = [Link]((32, 10))
y = [Link]((10,))

y = np.expand_dims(y, axis=0)

Y = [Link](y, (32, 1))

def naive_add_matrix_and_vector(x, y):


assert len([Link]) == 2
assert len([Link]) == 1
assert [Link][1] == [Link][0]
x = [Link]()
for i in range([Link][0]):
for j in range([Link][1]):
x[i, j] += y[j]
return x

import numpy as np

x = [Link]((64, 3, 32, 10))


y = [Link]((32, 10))
z = [Link](x, y)

# Tensor product
x = [Link]((32,))
y = [Link]((32,))

z = [Link](x, y)
z = x @ y

def naive_vector_product(x, y):


assert len([Link]) == 1
assert len([Link]) == 1

5
assert [Link][0] == [Link][0]
z = 0.0
for i in range([Link][0]):
z += x[i] * y[i]
return z

def naive_matrix_vector_product(x, y):


assert len([Link]) == 2
assert len([Link]) == 1
assert [Link][1] == [Link][0]
z = [Link]([Link][0])
for i in range([Link][0]):
for j in range([Link][1]):
z[i] += x[i, j] * y[j]
return z

def naive_matrix_vector_product(x, y):


z = [Link]([Link][0])
for i in range([Link][0]):
z[i] = naive_vector_product(x[i, :], y)
return z

def naive_matrix_product(x, y):


assert len([Link]) == 2
assert len([Link]) == 2
assert [Link][1] == [Link][0]
z = [Link](([Link][0], [Link][1]))
for i in range([Link][0]):
for j in range([Link][1]):
row_x = x[i, :]
column_y = y[:, j]
z[i, j] = naive_vector_product(row_x, column_y)
return z

#Tensor reshaping

train_images = train_images.reshape((60000, 28 * 28))

x = [Link]([[0., 1.],
[2., 3.],
[4., 5.]])
[Link]

x = [Link]((6, 1))
x

x = [Link]((2, 3))

6
x

x = [Link]((300, 20))
x = [Link](x)
[Link]

#Geometric interpretation of tensor operations


#A geometric interpretation of deep learning
#The engine of neural networks: Gradient-based optimization
#What's a derivative?
#Derivative of a tensor operation: the gradient
#Stochastic gradient descent
#Chaining derivatives: The Backpropagation algorithm
#The chain rule
#Automatic differentiation with computation graphs

# Looking back at our first example

(train_images, train_labels), (test_images, test_labels) = mnist.load_data()


train_images = train_images.reshape((60000, 28 * 28))
train_images = train_images.astype("float32") / 255
test_images = test_images.reshape((10000, 28 * 28))
test_images = test_images.astype("float32") / 255

model = [Link](
[
[Link](512, activation="relu"),
[Link](10, activation="softmax"),
]
)

[Link](
optimizer="adam",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)

[Link](
train_images,
train_labels,
epochs=5,
batch_size=128,
)

# Reimplementing our first example from scratch


# A simple Dense class

7
import keras
from keras import ops

class NaiveDense:
def __init__(self, input_size, output_size, activation=None):
[Link] = activation
self.W = [Link](
shape=(input_size, output_size), initializer="uniform"
)
self.b = [Link](shape=(output_size,), initializer="zeros")

def __call__(self, inputs):


x = [Link](inputs, self.W)
x = x + self.b
if [Link] is not None:
x = [Link](x)
return x

@property
def weights(self):
return [self.W, self.b]

# A simple Sequential class

class NaiveSequential:
def __init__(self, layers):
[Link] = layers

def __call__(self, inputs):


x = inputs
for layer in [Link]:
x = layer(x)
return x

@property
def weights(self):
weights = []
for layer in [Link]:
weights += [Link]
return weights

model = NaiveSequential(
[
NaiveDense(input_size=28 * 28, output_size=512, activation=[Link]),
NaiveDense(input_size=512, output_size=10, activation=[Link]),
]
)

8
assert len([Link]) == 4

# A batch generator
import math

class BatchGenerator:
def __init__(self, images, labels, batch_size=128):
assert len(images) == len(labels)
[Link] = 0
[Link] = images
[Link] = labels
self.batch_size = batch_size
self.num_batches = [Link](len(images) / batch_size)

def next(self):
images = [Link][[Link] : [Link] + self.batch_size]
labels = [Link][[Link] : [Link] + self.batch_size]
[Link] += self.batch_size
return images, labels

#Running one training step


#The weight update step

learning_rate = 1e-3

def update_weights(gradients, weights):


for g, w in zip(gradients, weights):
[Link](w - g * learning_rate)

from keras import optimizers

optimizer = [Link](learning_rate=1e-3)

def update_weights(gradients, weights):


optimizer.apply_gradients(zip(gradients, weights))

# Gradient computation
%%backend tensorflow
import tensorflow as tf

x = [Link](shape=())
with [Link]() as tape:
y = 2 * x + 3
grad_of_y_wrt_x = [Link](y, x)

%%backend tensorflow
def one_training_step(model, images_batch, labels_batch):

9
with [Link]() as tape:
predictions = model(images_batch)
loss = ops.sparse_categorical_crossentropy(labels_batch, predictions)
average_loss = [Link](loss)
gradients = [Link](average_loss, [Link])
update_weights(gradients, [Link])
return average_loss

#The full training loop


%%backend tensorflow
def fit(model, images, labels, epochs, batch_size=128):
for epoch_counter in range(epochs):
print(f"Epoch {epoch_counter}")
batch_generator = BatchGenerator(images, labels)
for batch_counter in range(batch_generator.num_batches):
images_batch, labels_batch = batch_generator.next()
loss = one_training_step(model, images_batch, labels_batch)
if batch_counter % 100 == 0:
print(f"loss at batch {batch_counter}: {loss:.2f}")

%%backend tensorflow
from [Link] import mnist

(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

train_images = train_images.reshape((60000, 28 * 28))


train_images = train_images.astype("float32") / 255
test_images = test_images.reshape((10000, 28 * 28))
test_images = test_images.astype("float32") / 255

fit(model, train_images, train_labels, epochs=10, batch_size=128)

#Evaluating the model


%%backend tensorflow
predictions = model(test_images)
predicted_labels = [Link](predictions, axis=1)
matches = predicted_labels == test_labels
f"accuracy: {[Link](matches):.2f}"

10

You might also like