0% found this document useful (0 votes)
3 views12 pages

Normalization

The document provides a detailed implementation of Batch Normalization and Long Short-Term Memory (LSTM) networks in Python. It includes code for forward and backward passes, parameter updates, and visualization methods for analyzing the effects of batch normalization on data distributions. Additionally, it demonstrates how to integrate batch normalization within an LSTM architecture for improved performance.

Uploaded by

Fatih Çelik
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)
3 views12 pages

Normalization

The document provides a detailed implementation of Batch Normalization and Long Short-Term Memory (LSTM) networks in Python. It includes code for forward and backward passes, parameter updates, and visualization methods for analyzing the effects of batch normalization on data distributions. Additionally, it demonstrates how to integrate batch normalization within an LSTM architecture for improved performance.

Uploaded by

Fatih Çelik
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

Batch Normalization

May 15, 2024

1 Batch Normalization From Scratch in Python


By Cristian Leo

[1]: import numpy as np


import [Link] as plt

# Custom classes (built from scratch)


from [Link] import WeightInitializer
from [Link] import PlotManager, LSTMTrainer, TimeSeriesDataset

[2]: class BatchNorm:


def __init__(self, hidden_size):
self.hidden_size = hidden_size
self.x = None
[Link] = [Link]((hidden_size, 1))
[Link] = [Link]((hidden_size, 1))

1
def forward(self, x):
self.x = x
[Link] = [Link](x, axis=0)
[Link] = [Link](x, axis=0)
self.x_norm = (x - [Link]) / [Link]([Link] + 1e-6)
out = [Link] * self.x_norm + [Link]
return out

def backward(self, dout):


N = [Link][0]
dgamma = [Link](dout * self.x_norm, axis=0)
dbeta = [Link](dout, axis=0)
dx_norm = dout * [Link]
dvar = [Link](dx_norm * (self.x - [Link]) * -0.5 * ([Link] +␣
↪1e-8)**-1.5, axis=0)

dmu = [Link](dx_norm * -1 / [Link]([Link] + 1e-8), axis=0) + dvar *␣


↪[Link](-2 * (self.x - [Link]), axis=0)

dx = dx_norm / [Link]([Link] + 1e-8) + dvar * 2 * (self.x - [Link])␣


↪/ N + dmu / N

return dx, dgamma, dbeta

def plot_batch_norm(self):
# Compute the histograms of the pre-normalized and post-normalized data
pre_norm_hist, pre_norm_bins = [Link](self.x, bins=30)
post_norm_hist, post_norm_bins = [Link](self.x_norm, bins=30)

# Plot the pre-normalized data


[Link](pre_norm_bins[:-1], pre_norm_bins, weights=pre_norm_hist,␣
↪alpha=0.5, label='Pre-Normalization')

# Plot the post-normalized data


[Link](post_norm_bins[:-1], post_norm_bins, weights=post_norm_hist,␣
↪alpha=0.5, label='Post-Normalization')

# Add labels, a title, and a legend


[Link]('Value')
[Link]('Frequency')
[Link]('Pre-Normalization vs. Post-Normalization')
[Link]()

# Display the plot


[Link]()

def plot_activation_distribution(self, activation_function):


# Compute the activation before batch normalization
pre_norm_activation = activation_function(self.x)

2
# Compute the activation after batch normalization
post_norm_activation = activation_function(self.x_norm)

# Compute the histograms of the pre-normalized and post-normalized␣


↪activations
pre_norm_hist, pre_norm_bins = [Link](pre_norm_activation,␣
↪bins=30)

post_norm_hist, post_norm_bins = [Link](post_norm_activation,␣


↪bins=30)

# Plot the pre-normalized activation


[Link](pre_norm_bins[:-1], pre_norm_bins, weights=pre_norm_hist,␣
↪alpha=0.5, label='Pre-Normalization')

# Plot the post-normalized activation


[Link](post_norm_bins[:-1], post_norm_bins, weights=post_norm_hist,␣
↪alpha=0.5, label='Post-Normalization')

# Add labels, a title, and a legend


[Link]('Activation Value')
[Link]('Frequency')
[Link]('Activation Distribution: Pre-Normalization vs.␣
↪Post-Normalization')

[Link]()

# Display the plot


[Link]()

[3]: # Create an instance of BatchNorm with a hypothetical hidden size, for example,␣
↪50.

bn_layer = BatchNorm(50)

# Pass some data from a mixture of Gaussians through the BatchNorm layer
# Generate data from two different normal distributions and combine them
data1 = [Link](-2, 1, size=(500, 50))
data2 = [Link](2, 1, size=(500, 50))
data = [Link]([data1, data2])

bn_layer.forward(data.T)

# Now, call the visualization methods


bn_layer.plot_batch_norm()

3
[4]: # Create an instance of BatchNorm with a hypothetical hidden size, for example,␣
↪50.

bn_layer = BatchNorm(50)

bn_layer.forward(data.T)

# Now, call the visualization methods


bn_layer.plot_activation_distribution([Link]) # Use the tanh activation␣
↪function

4
[5]: class LSTM:
"""
Long Short-Term Memory (LSTM) network.

Parameters:
- input_size: int, dimensionality of input space
- hidden_size: int, number of LSTM units
- output_size: int, dimensionality of output space
- init_method: str, weight initialization method (default: 'xavier')
"""
def __init__(self, input_size, hidden_size, output_size,␣
↪init_method='xavier'):

self.input_size = input_size
self.hidden_size = hidden_size
self.output_size = output_size
self.weight_initializer = WeightInitializer(method=init_method)

# Initialize weights
[Link] = self.weight_initializer.initialize((hidden_size, hidden_size␣
↪+ input_size))

5
[Link] = self.weight_initializer.initialize((hidden_size, hidden_size␣
↪+ input_size))
[Link] = self.weight_initializer.initialize((hidden_size, hidden_size␣
↪+ input_size))

[Link] = self.weight_initializer.initialize((hidden_size, hidden_size␣


↪+ input_size))

# Initialize biases
[Link] = [Link]((hidden_size, 1))
[Link] = [Link]((hidden_size, 1))
[Link] = [Link]((hidden_size, 1))
[Link] = [Link]((hidden_size, 1))

# Initialize output layer weights and biases


[Link] = self.weight_initializer.initialize((output_size,␣
↪hidden_size))

[Link] = [Link]((output_size, 1))

# Initialize batch normalization layers


self.bn_f = BatchNorm(hidden_size)
self.bn_i = BatchNorm(hidden_size)
self.bn_o = BatchNorm(hidden_size)
self.bn_c = BatchNorm(hidden_size)

@staticmethod
def sigmoid(z):
"""
Sigmoid activation function.

Parameters:
- z: [Link], input to the activation function

Returns:
- [Link], output of the activation function
"""
return 1 / (1 + [Link](-z))

@staticmethod
def dsigmoid(y):
"""
Derivative of the sigmoid activation function.

Parameters:
- y: [Link], output of the sigmoid activation function

Returns:

6
- [Link], derivative of the sigmoid function
"""
return y * (1 - y)

@staticmethod
def dtanh(y):
"""
Derivative of the hyperbolic tangent activation function.

Parameters:
- y: [Link], output of the hyperbolic tangent activation function

Returns:
- [Link], derivative of the hyperbolic tangent function
"""
return 1 - y * y

def forward(self, x):


"""
Forward pass through the LSTM network.

Parameters:
- x: [Link], input to the network

Returns:
- [Link], output of the network
- list, caches containing intermediate values for backpropagation
"""
caches = []
h_prev = [Link]((self.hidden_size, 1))
c_prev = [Link]((self.hidden_size, 1))
h = h_prev
c = c_prev

for t in range([Link][0]):
x_t = x[t].reshape(-1, 1)
combined = [Link]((h_prev, x_t))

f = [Link](self.bn_f.forward([Link]([Link], combined) + self.


bf))

i = [Link](self.bn_i.forward([Link]([Link], combined) + self.


bi))

o = [Link](self.bn_o.forward([Link]([Link], combined) + self.


bo))

c_ = [Link](self.bn_c.forward([Link]([Link], combined) + [Link]))

7
c = f * c_prev + i * c_
h = o * [Link](c)

cache = (h_prev, c_prev, f, i, o, c_, x_t, combined, c, h)


[Link](cache)

h_prev, c_prev = h, c

y = [Link]([Link], h) + [Link]
return y, caches

def backward(self, dy, caches, clip_value=1.0):


"""
Backward pass through the LSTM network.

Parameters:
- dy: [Link], gradient of the loss with respect to the output
- caches: list, caches from the forward pass
- clip_value: float, value to clip gradients to (default: 1.0)

Returns:
- tuple, gradients of the loss with respect to the parameters
"""
dWf, dWi, dWo, dWc = [np.zeros_like(w) for w in ([Link], [Link], self.
↪wo, [Link])]

dbf, dbi, dbo, dbc = [np.zeros_like(b) for b in ([Link], [Link], self.


↪bo, [Link])]

dWhy = np.zeros_like([Link])
dby = np.zeros_like([Link])

dgamma_f, dbeta_f = np.zeros_like(self.bn_f.gamma), np.zeros_like(self.


↪bn_f.beta)
dgamma_i, dbeta_i = np.zeros_like(self.bn_i.gamma), np.zeros_like(self.
↪bn_i.beta)

dgamma_o, dbeta_o = np.zeros_like(self.bn_o.gamma), np.zeros_like(self.


↪bn_o.beta)

dgamma_c, dbeta_c = np.zeros_like(self.bn_c.gamma), np.zeros_like(self.


↪bn_c.beta)

dy = [Link](self.output_size, -1)
dh_next = [Link]((self.hidden_size, 1))
dc_next = np.zeros_like(dh_next)

for cache in reversed(caches):


h_prev, c_prev, f, i, o, c_, x_t, combined, c, h = cache

8
dh = [Link]([Link].T, dy) + dh_next
dc = dc_next + (dh * o * [Link]([Link](c)))

df = dc * c_prev * [Link](f)
di = dc * c_ * [Link](i)
do = dh * [Link]([Link](c))
dc_ = dc * i * [Link](c_)

df, dgamma_f_, dbeta_f_ = self.bn_f.backward(df)


di, dgamma_i_, dbeta_i_ = self.bn_i.backward(di)
do, dgamma_o_, dbeta_o_ = self.bn_o.backward(do)
dc_, dgamma_c_, dbeta_c_ = self.bn_c.backward(dc_)

dgamma_f += dgamma_f_
dbeta_f += dbeta_f_
dgamma_i += dgamma_i_
dbeta_i += dbeta_i_
dgamma_o += dgamma_o_
dbeta_o += dbeta_o_
dgamma_c += dgamma_c_
dbeta_c += dbeta_c_

dcombined_f = [Link]([Link].T, df)


dcombined_i = [Link]([Link].T, di)
dcombined_o = [Link]([Link].T, do)
dcombined_c = [Link]([Link].T, dc_)

dcombined = dcombined_f + dcombined_i + dcombined_o + dcombined_c


dh_next = dcombined[:self.hidden_size]
dc_next = f * dc

dWf += [Link](df, combined.T)


dWi += [Link](di, combined.T)
dWo += [Link](do, combined.T)
dWc += [Link](dc_, combined.T)

dbf += [Link](axis=1, keepdims=True)


dbi += [Link](axis=1, keepdims=True)
dbo += [Link](axis=1, keepdims=True)
dbc += dc_.sum(axis=1, keepdims=True)

dWhy += [Link](dy, h.T)


dby += dy

gradients = (dWf, dWi, dWo, dWc, dbf, dbi, dbo, dbc, dWhy, dby,␣
dgamma_f, dbeta_f, dgamma_i, dbeta_i, dgamma_o, dbeta_o, dgamma_c, dbeta_c)

9
for i in range(len(gradients)):
[Link](gradients[i], -clip_value, clip_value, out=gradients[i])

return gradients

def update_params(self, grads, learning_rate):


"""
Update the parameters of the network using the gradients.
"""
dWf, dWi, dWo, dWc, dbf, dbi, dbo, dbc, dWhy, dby, dgamma_f, dbeta_f,␣
↪dgamma_i, dbeta_i, dgamma_o, dbeta_o, dgamma_c, dbeta_c = grads

[Link] -= learning_rate * dWf


[Link] -= learning_rate * dWi
[Link] -= learning_rate * dWo
[Link] -= learning_rate * dWc
[Link] -= learning_rate * dbf

[Link] -= learning_rate * dbi


[Link] -= learning_rate * dbo
[Link] -= learning_rate * dbc

[Link] -= learning_rate * dWhy


[Link] -= learning_rate * dby

self.bn_f.gamma -= learning_rate * dgamma_f


self.bn_f.beta -= learning_rate * dbeta_f
self.bn_i.gamma -= learning_rate * dgamma_i
self.bn_i.beta -= learning_rate * dbeta_i
self.bn_o.gamma -= learning_rate * dgamma_o
self.bn_o.beta -= learning_rate * dbeta_o
self.bn_c.gamma -= learning_rate * dgamma_c
self.bn_c.beta -= learning_rate * dbeta_c

[6]: # Instantiate the dataset


dataset = TimeSeriesDataset('2005-01-01', '2020-12-31', train_size=0.7)
trainX, trainY, testX, testY = dataset.get_train_test()

# Plot the data


# Combine train and test data
combined = [Link]((trainY, testY))

# Plot the data


[Link](figsize=(14, 5))
[Link](combined, label='Google Stock Price', linewidth=2, color='dodgerblue')
[Link]('Google Stock Price', fontsize=20)
[Link]('Time', fontsize=16)

10
[Link]('Normalized Stock Price', fontsize=16)
[Link](True)
[Link](fontsize=14)
[Link](fontsize=12)
[Link](fontsize=12)
[Link]()

[7]: # Reshape input to be [samples, time steps, features]


trainX = [Link](trainX, ([Link][0], [Link][1], 1))
testX = [Link](testX, ([Link][0], [Link][1], 1))

look_back = 1 # Number of previous time steps to include in each sample


hidden_size = 256 # Number of LSTM units
output_size = 1 # Dimensionality of the output space

lstm = LSTM(input_size=1, hidden_size=hidden_size, output_size=output_size,␣


↪init_method='xavier')

# Create and train the LSTM using LSTMTrainer


trainer = LSTMTrainer(lstm, learning_rate=1e-3, patience=10, verbose=True,␣
↪delta=0.001)

[Link](trainX, trainY, testX, testY, epochs=100, batch_size=32)

Epoch 1/100 - Loss: 0.57602, Val Loss: 1.29345


Epoch 11/100 - Loss: 0.00009, Val Loss: 0.41595
Epoch 21/100 - Loss: 0.00006, Val Loss: 0.38544
Epoch 31/100 - Loss: 0.00005, Val Loss: 0.38508
Early stopping

[9]: plot_manager = PlotManager()

11
# Inside your training loop
plot_manager.plot_losses(trainer.train_losses, trainer.val_losses)

# After your training loop


plot_manager.show_plots()

12

You might also like