A Python library for Deep Learning.

  • Tensor - N-dimensional arrays
  • Autograd - Automatic differentiation engine to compute gradients
  • Neural Network API - A modular framework for building Neural Networks with pre-defined layers, activation functions, and loss functions (nn.Module)
  • DataLoaders - Tools for efficient data handling. Allows for optimized training loops.

Syntax:

Imports:

torch: base
torch.nn: Neural Network model

import torch 
from torch import nn 

Device / Cuda support:

device = "cuda" if torch.cuda.is_available() else "cpu"

Creating a model:

class LinearRegressionModel(nn.Module): # Pass nn.Module always
	def __init__(self):
		super().__init__() # Call initializer of parent class (nn.Module) 
		
		# Creates random weights
		self.weights = nn.Parameter(torch.randn(1, dtype = torch.float))
		
		# Creates random Bias
		self.bias = nn.Parameter(torch.randn(1, dtype = torch.float))
 
	# Define forward pass
	def forward(self, x: torch.Tensor) -> torch.Tensor:
		return self.weights * x + self.bias
		

Training Loop / Model Options

Loss Functions

loss_functionn = nn.L1Loss()

Optimizers

  • torch.optim.Adam() - Adam
  • torch.optim.AdamW() - AdamW
  • torch.optim.SGD() - SGD
  • torch.optim.RMSprop() - RMSprop
  • torch.optim.LBFGS() - L-BFGS
optimizer = torch.optim.SGD(params=model_0.parameters(), lr=.01)

Activation Functions

relu = nn.ReLU()
layer = nn.Sequential( 
	nn.Linear(64, 32), 
	nn.ReLU() 
)

Layers

Linear Layers

nn.Linear(in_features=64, out_features=32)
nn.Identity()

Convolutional Layers

nn.Conv1d(in_channels=1, out_channels=16, kernel_size=3)
nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=1, padding=1) 
nn.Conv3d(in_channels=3, out_channels=16, kernel_size=3)

Pooling Layers

nn.MaxPool2d(kernel_size=2, stride=2)
nn.AvgPool2d(kernel_size=2, stride=2)
nn.AdaptiveAvgPool2d(output_size=(1, 1))

Recurrent Layers

nn.RNN(input_size=10, hidden_size=20, batch_first=True) 
nn.LSTM(input_size=10, hidden_size=20, num_layers=2, batch_first=True) 
nn.GRU(input_size=10, hidden_size=20, batch_first=True)

Transformer Layers

nn.MultiheadAttention(embed_dim=256, num_heads=8)
nn.TransformerEncoderLayer(d_model=256, nhead=8)

Regularization Layers

nn.Dropout(p=0.5) 
nn.Dropout2d(p=0.5) # For images (drops entire channels)

Normalization Layers

bn1d = nn.BatchNorm1d(num_features=32)
ln = nn.LayerNorm(normalized_shape=32)

Utility Layers

nn.Flatten()
nn.Embedding(num_embeddings=1000, embedding_dim=128)

Validation / Inference Loop

model.eval() #Sets model to evaluation mode (disables Dropout/BatchNorm).
with torch.no_grad(): #Disables gradient tracking (faster, saves memory)
	for data, target in dataloader: 
		data, target = data.to(device), target.to(device) 
		preds = model(data) # Calculate metrics (Accuracy/Loss) here

Learning Rate Schedulers

# 1. Standard (Step, Cosine, Exponential) 
for epoch in range(epochs): 
	train(...) 
	validate(...) 
	scheduler.step() # <-- Indented at EPOCH level
 
# 2. Reactive (ReduceLROnPlateau) 
for epoch in range(epochs): 
	train(...) 
	val_loss = validate(...) 
	scheduler.step(val_loss) # <-- Pass the metric here

Saving and Loading Models

# Saving torch.save
(model.state_dict(), "model_weights.pth") 
# Loading 
model = LinearRegressionModel() # Must initialize structure first 
model.load_state_dict(torch.load("model_weights.pth")) 
model.to(device)

Full Example Code

import torch
from torch import nn
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pathlib import Path
import save_model
 
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Device: {device}")
weight = 0.7
bias = 0.3
 
start = 0
end = 1
step = 0.02
 
X = torch.arange(start, end, step).unsqueeze(dim=1)
y = weight * X + bias
 
train_split = int(0.8 * len(X))
X_train, y_train = X[:train_split], y[:train_split]
X_test, y_test = X[train_split:], y[train_split:]
 
def plot_predictions(train_data=X_train, 
                     train_labels=y_train, 
                     test_data=X_test, 
                     test_labels=y_test, 
                     predictions=None):
    plt.figure(figsize=(10, 7))
    plt.scatter(train_data, train_labels, c="b", s=4, label="Training data")
    plt.scatter(test_data, test_labels, c="g", s=4, label="Testing data")
    if predictions is not None:
        plt.scatter(test_data, predictions, c="r", s=4, label="Predictions")
    plt.legend(prop={"size": 14})
    plt.show()
 
class LinearRegressionModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.weights = nn.Parameter(torch.randn(1, 
                                               dtype = torch.float))
        self.bias = nn.Parameter(torch.randn(1,
                                            dtype = torch.float))
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return self.weights * x + self.bias
        
torch.manual_seed(42)        
 
model_0 = LinearRegressionModel()
 
# Loss Function
loss_fn = nn.L1Loss()
 
# Optimizer
optimizer = torch.optim.SGD(params=model_0.parameters(), lr=.01) 
 
epochs = 200
 
for epoch in range(epochs):
    # Set model to training mode
    model_0.train()
 
    # 1. Forward pass
    y_pred = model_0(X_train)
 
    # 2. Calculate loss
    loss = loss_fn(y_pred, y_train)
 
    # 3. Optimizer zero grad
    optimizer.zero_grad()
 
    # 4. Perform backpropogation on the loss with respect to parameters
    # of model
    loss.backward()
 
    # 5. Step the optimizer (perform gradient descent)
    optimizer.step()
 
    ### Testing
    model_0.eval()
    with torch.inference_mode():
        # 1. Forward pass
        test_pred = model_0(X_test)
 
        # 2. Calculate loss
        test_loss = loss_fn(test_pred,y_test)
 
    if epoch % 10 == 0:
        epoch_count.append(epoch)
        loss_values.append(loss)
        test_loss_values.append(test_loss)
        print(f"Epoch: {epoch} | Loss: {loss} | Test loss: {test_loss}")
 
        print(model_0.state_dict())
 
with torch.inference_mode():
    y_preds_new = model_0(X_test)
 
model_name = "linear_regression_model_0.pth"
save_model.save_model(model_0, model_name)⏎ 

PyTorch Geometric

Used for Graph Neural Networks (GNN).

from torch_geometric.nn import ...

Data Structure

from torch_geometric.data import Data
 
# 1. Node Features (x)
# Shape: [Num_Nodes, Num_Features]
# Example: 3 atoms, each with 5 chemical features (atomic mass, electronegativity, etc.)
x = torch.tensor([[1, ...], [1, ...], [6, ...]], dtype=torch.float)
 
# 2. Graph Connectivity (edge_index)
# Shape: [2, Num_Edges] -> COO Format (Coordinate List)
# Top row is Source Node, Bottom row is Target Node.
edge_index = torch.tensor([[0, 1, 1, 2],
                           [1, 0, 2, 1]], dtype=torch.long)
 
# 3. Target Label (y)
# Shape: [1] (Solubility value)
y = torch.tensor([1.24], dtype=torch.float)
 
data = Data(x=x, edge_index=edge_index, y=y)

Layers

from torch_geometric.nn import GCNConv 
conv1 = GCNConv(in_channels=16, out_channels=32)

Complete GNN

import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConv, global_mean_pool
 
class MoleculeGNN(torch.nn.Module):
    def __init__(self, num_node_features):
        super().__init__()
        # Layer 1: Message Passing
        self.conv1 = GCNConv(num_node_features, 64)
        # Layer 2: Message Passing
        self.conv2 = GCNConv(64, 64)
        # Layer 3: Message Passing
        self.conv3 = GCNConv(64, 64)
        
        # Output Head
        self.lin = torch.nn.Linear(64, 1) # Regression output (1 value)
 
    def forward(self, x, edge_index, batch):
        # 1. Obtain node embeddings
        x = self.conv1(x, edge_index)
        x = x.relu()
        x = self.conv2(x, edge_index)
        x = x.relu()
        x = self.conv3(x, edge_index)
 
        # 2. Readout layer (Pool atoms -> Molecule)
		# 'batch' vector tells the pool which atoms belong to which molecule
        x = global_mean_pool(x, batch) 
 
        # 3. Final Prediction
        x = F.dropout(x, p=0.5, training=self.training)
        x = self.lin(x)
        return x