This tutorial will guide you through building an Artificial Neural Network (ANN) using Python, starting with a boolean logic-based system and moving to analog predictions using a custom ANN implementation. Finally, we’ll simplify the process using PyTorch and introduce the concept of training with weights and biases.
We start with a simple truth table for predicting rain using three inputs: 1. Cloudy (True/False) 2. Monsoon (True/False) 3. Bright Daylight (True/False)
The output is Rain/No Rain (True/False).
We assume: - Rain occurs if it is Cloudy AND Monsoon. - No Rain occurs if it's NOT Cloudy OR Bright Daylight.
| Cloudy | Monsoon | Bright Daylight | Rain |
|---|---|---|---|
| True | True | False | True |
| True | False | False | False |
| False | True | False | False |
| False | False | True | False |
def predict_rain_with_logic(cloudy, monsoon, bright_daylight):
"""
Boolean logic-based predictor for rain.
"""
# Rain occurs if it's cloudy AND monsoon
if cloudy and monsoon and not bright_daylight:
return True # Rain
else:
return False # No Rain
# Test the logic
inputs = [
(True, True, False), # Cloudy, Monsoon, Not Bright
(True, False, False), # Cloudy, Not Monsoon, Not Bright
(False, True, False), # Not Cloudy, Monsoon, Not Bright
(False, False, True) # Not Cloudy, Not Monsoon, Bright
]
print("Boolean Logic Predictions:")
for cloudy, monsoon, bright_daylight in inputs:
result = predict_rain_with_logic(cloudy, monsoon, bright_daylight)
print(f"Cloudy: {cloudy}, Monsoon: {monsoon}, Bright: {bright_daylight} → Rain: {result}")In the real world, things are rarely black and white (True/False). Weather predictions involve many factors, and inputs like cloudiness, monsoon intensity, and brightness are analog values (e.g., percentages or probabilities). We need a system to handle such variability and provide probabilistic outputs.
We'll create an analog system where:
1. Inputs like Cloudy, Monsoon, and Bright Daylight are real numbers (e.g., 0.8, 0.6, etc.).
2. Outputs are probabilities (e.g., 0.9 for Rain, 0.3 for No Rain).
3. The system uses weights and biases to compute predictions.
import numpy as np
class SimpleANN:
"""
A custom Artificial Neural Network with:
- 3 input features (Cloudy, Monsoon, Bright Daylight)
- 1 output (Probability of Rain)
"""
def __init__(self):
# Initialize weights and biases
self.weights = np.array([0.6, 0.8, -0.4]) # Weights for the 3 inputs
self.bias = -0.2 # Bias term
def sigmoid(self, x):
"""Sigmoid activation function to convert outputs to probabilities."""
return 1 / (1 + np.exp(-x))
def predict(self, inputs):
"""
Compute the output using:
output = sigmoid(dot(inputs, weights) + bias)
"""
linear_output = np.dot(inputs, self.weights) + self.bias
return self.sigmoid(linear_output)
# Initialize the model
model = SimpleANN()
# Analog inputs: Cloudy, Monsoon, Bright Daylight (values between 0 and 1)
inputs = np.array([
[0.8, 1.0, 0.2], # High cloudiness, strong monsoon, low brightness
[0.4, 0.6, 0.8], # Moderate values
[0.2, 0.1, 0.9], # Low cloudiness, no monsoon, very bright
])
# Predict probabilities
outputs = model.predict(inputs)
print("\nAnalog Predictions (Probabilities of Rain):")
for i, prob in enumerate(outputs):
print(f"Input {i+1}: {inputs[i]} → Rain Probability: {prob:.2f}")Weights:
Cloudy (0.6) contributes more to rain prediction than Bright Daylight (-0.4).Bias:
Sigmoid Activation:
PyTorch automates the creation and training of neural networks. We’ll use a single-layer ANN for the same problem.
import torch
import torch.nn as nn
class RainPredictorANN(nn.Module):
"""
A simple ANN using PyTorch with:
- 3 inputs
- 1 output (Rain/No Rain Probability)
"""
def __init__(self):
super(RainPredictorANN, self).__init__()
self.fc = nn.Linear(3, 1) # Fully connected layer: 3 inputs → 1 output
self.sigmoid = nn.Sigmoid()
def forward(self, x):
"""
Perform forward pass:
output = sigmoid(fc(inputs))
"""
return self.sigmoid(self.fc(x))
# Initialize the model
model = RainPredictorANN()
# Define inputs (Cloudy, Monsoon, Bright Daylight)
inputs = torch.tensor([
[0.8, 1.0, 0.2],
[0.4, 0.6, 0.8],
[0.2, 0.1, 0.9]
], dtype=torch.float32)
# Perform predictions
outputs = model(inputs)
print("\nPyTorch Predictions (Probabilities of Rain):")
for i, prob in enumerate(outputs.detach().numpy()):
print(f"Input {i+1}: {inputs[i].numpy()} → Rain Probability: {prob[0]:.2f}")During training: 1. The ANN adjusts its weights and biases to minimize error. 2. Each epoch experiments with new values for weights and biases, learning from the errors in predictions.
Now, let's build upon the PyTorch-based ANN we created earlier and train it to predict Rain or No Rain based on real-world data. During training, the model will adjust its weights and biases to minimize the error between its predictions and the actual labels.
import torch
import torch.nn as nn
import torch.optim as optim
# Define the ANN model
class RainPredictorANN(nn.Module):
"""
A simple ANN for predicting Rain/No Rain.
- Input: 3 features (Cloudy, Monsoon, Bright Daylight)
- Output: 1 (Probability of Rain)
"""
def __init__(self):
super(RainPredictorANN, self).__init__()
self.fc = nn.Linear(3, 1) # Fully connected layer: 3 inputs → 1 output
self.sigmoid = nn.Sigmoid()
def forward(self, x):
return self.sigmoid(self.fc(x))
# Initialize the model
model = RainPredictorANN()
# Define inputs (features) and labels (ground truth)
inputs = torch.tensor([
[0.8, 1.0, 0.2], # High cloudiness, strong monsoon, low brightness → Rain
[0.4, 0.6, 0.8], # Moderate values → No Rain
[0.2, 0.1, 0.9], # Low cloudiness, no monsoon, very bright → No Rain
[0.9, 0.8, 0.1], # Very cloudy, strong monsoon, dim brightness → Rain
], dtype=torch.float32)
labels = torch.tensor([
[1], # Rain
[0], # No Rain
[0], # No Rain
[1], # Rain
], dtype=torch.float32)
# Define the loss function and optimizer
criterion = nn.BCELoss() # Binary Cross-Entropy Loss
optimizer = optim.SGD(model.parameters(), lr=0.1) # Stochastic Gradient Descent
# Training loop
epochs = 500
for epoch in range(epochs):
# Forward pass: Compute predictions
outputs = model(inputs)
# Compute the loss
loss = criterion(outputs, labels)
# Backward pass: Compute gradients
optimizer.zero_grad()
loss.backward()
# Update weights and biases
optimizer.step()
# Print loss every 50 epochs
if (epoch + 1) % 50 == 0:
print(f"Epoch [{epoch + 1}/{epochs}], Loss: {loss.item():.4f}")
# Test the trained model
with torch.no_grad(): # No need to calculate gradients for evaluation
predictions = model(inputs)
binary_predictions = (predictions > 0.5).int() # Convert probabilities to binary (0 or 1)
print("\nFinal Predictions (Probabilities):")
print(predictions)
print("\nBinary Predictions (Rain=1, No Rain=0):")
print(binary_predictions)fc = nn.Linear(3, 1):
sigmoid = nn.Sigmoid():
Epoch [50/500], Loss: 0.6732
Epoch [100/500], Loss: 0.5253
Epoch [150/500], Loss: 0.4345
...
Epoch [500/500], Loss: 0.2448Probabilities:
tensor([[0.8823], # High probability → Rain
[0.3045], # Low probability → No Rain
[0.1457], # Very low probability → No Rain
[0.9102]]) # Very high probability → Rain
Binary Predictions:
tensor([[1], # Rain
[0], # No Rain
[0], # No Rain
[1]]) # Rain
Inputs and Outputs:
Weights and Biases:
Training:
Libraries:
nn.Linear, BCELoss, and optim.SGD.Let’s take things to the next level by creating a Multi-Layer Neural Network (MLP) using PyTorch for rain prediction. Unlike our single-layer model, this one will have multiple layers, enabling it to learn more complex patterns in the data.
More Complex Decision Boundaries:
Hierarchical Learning:
Generalization:
This network will have:
1. Three input features (Cloudy, Monsoon, Bright Daylight).
2. One hidden layer with 4 neurons.
3. One output layer with 1 neuron that predicts the probability of rain.
import torch
import torch.nn as nn
import torch.optim as optim
# Define the Multi-Layer ANN model
class MultiLayerRainPredictor(nn.Module):
"""
A multi-layer ANN for predicting Rain/No Rain.
- Input: 3 features (Cloudy, Monsoon, Bright Daylight)
- Hidden Layer: 4 neurons with ReLU activation
- Output: 1 (Probability of Rain)
"""
def __init__(self):
super(MultiLayerRainPredictor, self).__init__()
self.hidden = nn.Linear(3, 4) # Hidden layer: 3 inputs → 4 neurons
self.output = nn.Linear(4, 1) # Output layer: 4 neurons → 1 output
self.relu = nn.ReLU() # Activation for hidden layer
self.sigmoid = nn.Sigmoid() # Activation for output layer
def forward(self, x):
x = self.relu(self.hidden(x)) # Hidden layer with ReLU
return self.sigmoid(self.output(x)) # Output layer with Sigmoid
# Initialize the model
model = MultiLayerRainPredictor()
# Define inputs (features) and labels (ground truth)
inputs = torch.tensor([
[0.8, 1.0, 0.2], # High cloudiness, strong monsoon, low brightness → Rain
[0.4, 0.6, 0.8], # Moderate values → No Rain
[0.2, 0.1, 0.9], # Low cloudiness, no monsoon, very bright → No Rain
[0.9, 0.8, 0.1], # Very cloudy, strong monsoon, dim brightness → Rain
], dtype=torch.float32)
labels = torch.tensor([
[1], # Rain
[0], # No Rain
[0], # No Rain
[1], # Rain
], dtype=torch.float32)
# Define the loss function and optimizer
criterion = nn.BCELoss() # Binary Cross-Entropy Loss
optimizer = optim.Adam(model.parameters(), lr=0.01) # Adam optimizer for faster convergence
# Training loop
epochs = 1000
for epoch in range(epochs):
# Forward pass: Compute predictions
outputs = model(inputs)
# Compute the loss
loss = criterion(outputs, labels)
# Backward pass: Compute gradients
optimizer.zero_grad()
loss.backward()
# Update weights and biases
optimizer.step()
# Print loss every 100 epochs
if (epoch + 1) % 100 == 0:
print(f"Epoch [{epoch + 1}/{epochs}], Loss: {loss.item():.4f}")
# Test the trained model
with torch.no_grad(): # No need to calculate gradients for evaluation
predictions = model(inputs)
binary_predictions = (predictions > 0.5).int() # Convert probabilities to binary (0 or 1)
print("\nFinal Predictions (Probabilities):")
print(predictions)
print("\nBinary Predictions (Rain=1, No Rain=0):")
print(binary_predictions)Epoch [100/1000], Loss: 0.4567
Epoch [200/1000], Loss: 0.3289
Epoch [300/1000], Loss: 0.2482
...
Epoch [1000/1000], Loss: 0.0853Probabilities:
tensor([[0.9214], # High probability → Rain
[0.2105], # Low probability → No Rain
[0.1323], # Very low probability → No Rain
[0.9451]]) # Very high probability → Rain
Binary Predictions:
tensor([[1], # Rain
[0], # No Rain
[0], # No Rain
[1]]) # Rain
Here's an overview of the architecture:
Inputs: [Cloudy, Monsoon, Bright Daylight]
↓
Hidden Layer (4 neurons, ReLU activation)
↓
Output Layer (1 neuron, Sigmoid activation)
↓
Prediction: Probability of Rain (0–1)Hidden Layers and Non-Linearity:
ReLU Activation:
Sigmoid Activation:
Loss Function and Optimizer:
More Layers = More Power:
Training:
PyTorch Simplifies Everything:
Let’s move to more advanced architectures and methods for improving your model's performance. Here, we'll explore:
Let’s say we want to predict Rain or No Rain based on satellite images of clouds. Here’s how we’d set up a simple CNN for this:
import torch
import torch.nn as nn
import torch.optim as optim
class RainImageCNN(nn.Module):
"""
A basic Convolutional Neural Network for classifying images as Rain/No Rain.
"""
def __init__(self):
super(RainImageCNN, self).__init__()
# Convolutional layers
self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=1, padding=1) # 1 input channel → 16 filters
self.conv2 = nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1) # 16 filters → 32 filters
self.pool = nn.MaxPool2d(2, 2) # Reduce spatial dimensions by 2x
self.fc1 = nn.Linear(32 * 8 * 8, 64) # Fully connected layer
self.fc2 = nn.Linear(64, 1) # Output layer for binary classification
self.sigmoid = nn.Sigmoid()
def forward(self, x):
x = self.pool(torch.relu(self.conv1(x))) # Convolution → ReLU → MaxPool
x = self.pool(torch.relu(self.conv2(x))) # Convolution → ReLU → MaxPool
x = x.view(-1, 32 * 8 * 8) # Flatten for fully connected layer
x = torch.relu(self.fc1(x)) # Fully connected layer → ReLU
return self.sigmoid(self.fc2(x)) # Output layer → Sigmoid
# Instantiate the model
model = RainImageCNN()
# Print the architecture
print(model)After training, you need to evaluate your model’s performance. Here are key metrics:
Accuracy:
Precision:
Recall:
F1-Score:
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
# Ground truth labels (example)
true_labels = [1, 0, 1, 0, 1]
# Predicted labels from the model
predicted_labels = [1, 0, 1, 1, 0]
# Calculate metrics
accuracy = accuracy_score(true_labels, predicted_labels)
precision = precision_score(true_labels, predicted_labels)
recall = recall_score(true_labels, predicted_labels)
f1 = f1_score(true_labels, predicted_labels)
print(f"Accuracy: {accuracy:.2f}")
print(f"Precision: {precision:.2f}")
print(f"Recall: {recall:.2f}")
print(f"F1-Score: {f1:.2f}")Hyperparameters are settings for your model that aren’t learned during training, such as: 1. Learning Rate: Controls the step size for weight updates. 2. Number of Hidden Layers: Determines the model's depth. 3. Batch Size: Number of training samples processed at once. 4. Number of Epochs: Total passes over the training dataset.
You can manually adjust hyperparameters, train your model, and evaluate its performance.
Automate the tuning process by testing combinations of hyperparameters.
sklearn's Grid Search:from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier
# Example: Hyperparameter tuning for a random forest
param_grid = {
'n_estimators': [50, 100, 150], # Number of trees
'max_depth': [5, 10, 15], # Maximum depth of each tree
'min_samples_split': [2, 5, 10] # Minimum samples to split a node
}
# Instantiate and tune the model
grid_search = GridSearchCV(RandomForestClassifier(), param_grid, cv=3)
grid_search.fit(X_train, y_train)
print("Best Parameters:", grid_search.best_params_)Would you like to: 1. Explore how to visualize training results (e.g., loss and accuracy curves)? 2. Learn about saving and loading trained models? 3. Understand advanced techniques like transfer learning?
Recurrent Neural Networks (RNNs) and Long Short-Term Memory Networks (LSTMs) are designed to process sequential data, like time-series, text, or audio. They are particularly effective in tasks where context from previous steps is essential.
[ ht = f(Wx xt + Wh h{t-1} + b) ] [ yt = g(Wy ht + c) ] - ( ht ): Hidden state at time ( t ). - ( f, g ): Activation functions (e.g., ReLU, Sigmoid). - ( Wx, Wh, Wy ): Weights. - ( b, c ): Biases.
LSTMs are a type of RNN designed to remember long-term dependencies. They introduce a sophisticated memory system using gates.
Let’s use an LSTM to predict rain based on sequential weather data (e.g., temperature, humidity, cloud cover).
import torch
import torch.nn as nn
import torch.optim as optim
# Define the LSTM-based model
class RainPredictorLSTM(nn.Module):
def __init__(self, input_size, hidden_size, output_size, num_layers=1):
super(RainPredictorLSTM, self).__init__()
self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
self.fc = nn.Linear(hidden_size, output_size) # Fully connected layer for output
def forward(self, x):
# LSTM output: (all hidden states, final hidden state)
out, _ = self.lstm(x) # We don't need the hidden/cell states for now
out = self.fc(out[:, -1, :]) # Use the last time step's hidden state for output
return torch.sigmoid(out) # Sigmoid for binary classification
# Hyperparameters
input_size = 3 # Features: temperature, humidity, cloud cover
hidden_size = 32 # LSTM hidden units
output_size = 1 # Probability of rain
num_layers = 1 # Single-layer LSTM
learning_rate = 0.01
epochs = 500
# Initialize the model
model = RainPredictorLSTM(input_size, hidden_size, output_size, num_layers)
# Sample sequential data (temperature, humidity, cloud cover)
# Each row is a sequence (e.g., 5 days of weather data)
inputs = torch.tensor([
[[0.8, 0.7, 0.9], [0.7, 0.6, 0.8], [0.6, 0.5, 0.7], [0.5, 0.4, 0.6], [0.4, 0.3, 0.5]],
[[0.3, 0.2, 0.1], [0.2, 0.1, 0.0], [0.1, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.1, 0.1]]
], dtype=torch.float32) # Shape: (2 sequences, 5 time steps, 3 features)
# Labels for each sequence (Rain/No Rain)
labels = torch.tensor([[1], [0]], dtype=torch.float32)
# Loss function and optimizer
criterion = nn.BCELoss() # Binary Cross-Entropy Loss
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
# Training loop
for epoch in range(epochs):
outputs = model(inputs) # Forward pass
loss = criterion(outputs, labels) # Compute loss
optimizer.zero_grad() # Zero gradients
loss.backward() # Backward pass
optimizer.step() # Update weights
if (epoch + 1) % 50 == 0:
print(f"Epoch [{epoch + 1}/{epochs}], Loss: {loss.item():.4f}")
# Testing the model
with torch.no_grad():
predictions = model(inputs)
binary_predictions = (predictions > 0.5).int()
print("\nFinal Predictions (Probabilities):")
print(predictions)
print("\nBinary Predictions (Rain=1, No Rain=0):")
print(binary_predictions)(batch_size, sequence_length, num_features).(batch_size, 1) for binary classification.Sequential Data:
Memory and Gates:
Training:
The Transformer architecture revolutionized machine learning, especially in natural language processing (NLP). Introduced in the 2017 paper "Attention Is All You Need", Transformers address the limitations of traditional sequential models like RNNs and LSTMs by processing all elements in a sequence simultaneously, using a mechanism called self-attention.
Input Embedding:
Positional Encoding:
Self-Attention Mechanism:
Feed-Forward Network (FFN):
Layer Normalization:
Output Layer:
Input Sequence → Embedding → Positional Encoding
↓
Multi-Head Self-Attention
↓
Add & Normalize
↓
Feed-Forward Network
↓
Add & Normalize
↓
Output (e.g., Prediction or Next Token)Self-attention calculates how much focus each token in a sequence should have on every other token.
Query, Key, and Value Vectors:
Attention Scores:
Softmax:
Weighted Sum:
Let’s build a Transformer model using PyTorch.
import torch
import torch.nn as nn
import torch.optim as optim
class SimpleTransformer(nn.Module):
def __init__(self, input_dim, embed_dim, num_heads, ff_dim, num_layers):
super(SimpleTransformer, self).__init__()
self.embedding = nn.Embedding(input_dim, embed_dim) # Token embedding
self.positional_encoding = nn.Parameter(torch.zeros(1, 100, embed_dim)) # Positional encoding
# Transformer Encoder Layer
encoder_layer = nn.TransformerEncoderLayer(d_model=embed_dim, nhead=num_heads, dim_feedforward=ff_dim)
self.transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
self.fc = nn.Linear(embed_dim, 1) # Fully connected layer for binary classification
def forward(self, x):
# Add embedding and positional encoding
x = self.embedding(x) + self.positional_encoding[:, :x.size(1), :]
x = self.transformer_encoder(x) # Pass through transformer encoder
x = x.mean(dim=1) # Average pooling over sequence length
return torch.sigmoid(self.fc(x)) # Sigmoid activation for binary classification
# Hyperparameters
input_dim = 5000 # Vocabulary size
embed_dim = 64 # Embedding dimension
num_heads = 4 # Number of attention heads
ff_dim = 128 # Feed-forward dimension
num_layers = 2 # Number of transformer layers
# Initialize the model
model = SimpleTransformer(input_dim, embed_dim, num_heads, ff_dim, num_layers)
# Print the model architecture
print(model)Embedding Layer:
Positional Encoding:
Transformer Encoder:
Output:
Text:
Vision:
Audio:
Let’s take the Transformer model we implemented earlier and train it on a binary classification task. We’ll walk through:
For simplicity, we’ll create a synthetic dataset where each sample is a sequence of integers, and the label depends on the sum of the sequence. If the sum is greater than a threshold, the label will be 1 (e.g., Rain); otherwise, it will be 0 (e.g., No Rain).
import torch
from torch.utils.data import Dataset, DataLoader
class SyntheticDataset(Dataset):
def __init__(self, num_samples=1000, seq_length=10, vocab_size=5000, threshold=25):
self.data = torch.randint(0, vocab_size, (num_samples, seq_length)) # Random sequences
self.labels = (self.data.sum(dim=1) > threshold).float().unsqueeze(1) # Label: sum > threshold
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
return self.data[idx], self.labels[idx]
# Create dataset and data loader
train_dataset = SyntheticDataset(num_samples=800, seq_length=10)
test_dataset = SyntheticDataset(num_samples=200, seq_length=10)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)Transformers require input sequences in a specific format: 1. Tokenized Inputs: - Each input sequence is already a tensor of integers representing tokens. 2. Positional Encoding: - Added automatically in the model as part of the forward pass.
Now we’ll train the Transformer model. We’ll use Binary Cross-Entropy Loss to optimize the predictions.
import torch.nn as nn
import torch.optim as optim
# Define the Transformer-based model
class SimpleTransformer(nn.Module):
def __init__(self, input_dim, embed_dim, num_heads, ff_dim, num_layers):
super(SimpleTransformer, self).__init__()
self.embedding = nn.Embedding(input_dim, embed_dim) # Token embedding
self.positional_encoding = nn.Parameter(torch.zeros(1, 100, embed_dim)) # Positional encoding
# Transformer Encoder Layer
encoder_layer = nn.TransformerEncoderLayer(d_model=embed_dim, nhead=num_heads, dim_feedforward=ff_dim)
self.transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
self.fc = nn.Linear(embed_dim, 1) # Fully connected layer for binary classification
def forward(self, x):
# Add embedding and positional encoding
x = self.embedding(x) + self.positional_encoding[:, :x.size(1), :]
x = self.transformer_encoder(x) # Pass through transformer encoder
x = x.mean(dim=1) # Average pooling over sequence length
return torch.sigmoid(self.fc(x)) # Sigmoid activation for binary classification
# Initialize the model
input_dim = 5000
embed_dim = 64
num_heads = 4
ff_dim = 128
num_layers = 2
model = SimpleTransformer(input_dim, embed_dim, num_heads, ff_dim, num_layers)
# Define the loss function and optimizer
criterion = nn.BCELoss() # Binary Cross-Entropy Loss
optimizer = optim.Adam(model.parameters(), lr=0.001)
# Training loop
epochs = 10
for epoch in range(epochs):
model.train()
total_loss = 0
for batch in train_loader:
inputs, labels = batch
optimizer.zero_grad()
# Forward pass
outputs = model(inputs)
# Compute loss
loss = criterion(outputs, labels)
total_loss += loss.item()
# Backward pass and optimize
loss.backward()
optimizer.step()
print(f"Epoch [{epoch + 1}/{epochs}], Loss: {total_loss / len(train_loader):.4f}")After training, we evaluate the model on the test dataset.
from sklearn.metrics import accuracy_score
model.eval()
all_preds = []
all_labels = []
with torch.no_grad():
for batch in test_loader:
inputs, labels = batch
outputs = model(inputs)
preds = (outputs > 0.5).int() # Convert probabilities to binary predictions
all_preds.extend(preds.numpy())
all_labels.extend(labels.numpy())
# Calculate accuracy
accuracy = accuracy_score(all_labels, all_preds)
print(f"Test Accuracy: {accuracy:.2f}")To visualize how the loss decreases over time:
import matplotlib.pyplot as plt
losses = []
for epoch in range(epochs):
model.train()
total_loss = 0
for batch in train_loader:
inputs, labels = batch
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
total_loss += loss.item()
loss.backward()
optimizer.step()
avg_loss = total_loss / len(train_loader)
losses.append(avg_loss)
print(f"Epoch [{epoch + 1}/{epochs}], Loss: {avg_loss:.4f}")
# Plot loss curve
plt.plot(range(1, epochs + 1), losses, marker='o')
plt.title("Training Loss Over Epochs")
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.show()Transformer Components:
Training Dynamics:
Evaluation: