yrmo / cudagrad

CUDA C++ strided float tensor automatic differentiation engine with Python bindings
MIT License
0 stars 0 forks source link

Add convolutional and max pooling operations (2D) to Tensor #50

Closed yrmo closed 17 hours ago

yrmo commented 4 months ago

In PyTorch, the underlying tensor operations that perform convolution and max pooling are provided by specific functions and modules. Let's look at each in detail:

Convolution

The convolution operation in a convolutional layer is primarily implemented using the torch.nn.Conv2d class when dealing with 2D images (like those in MNIST). This class applies a 2D convolution over an input signal composed of several input planes.

Here's a breakdown of how torch.nn.Conv2d works:

This module automatically handles the weight tensors and bias tensors, which represent the kernels or filters and their respective biases. During the forward pass, these weights are convolved with the input tensor in a manner defined by the other parameters (stride, padding).

Max Pooling

Max pooling is implemented using the torch.nn.MaxPool2d class. This class applies a 2D max pooling over an input signal (usually the output of a convolutional layer).

Here’s what the torch.nn.MaxPool2d class does:

This operation simplifies the input by reducing its dimensionality and retaining only the maximum value in each window defined by kernel_size.

Low-Level Tensor Operations

While the high-level modules like torch.nn.Conv2d and torch.nn.MaxPool2d are commonly used for building neural networks, PyTorch also provides lower-level tensor operations that can directly apply these transformations. For convolution, you can use torch.nn.functional.conv2d, and for max pooling, torch.nn.functional.max_pool2d is available. These functions allow you to manually define the weights and other parameters, giving you finer control over the operation:

import torch
import torch.nn.functional as F

# Example tensor and weight
input = torch.randn(1, 1, 32, 32)  # Format: (batch_size, num_channels, height, width)
weights = torch.randn(1, 1, 5, 5)  # Format: (out_channels, in_channels, kernel_height, kernel_width)

# Applying convolution
output_conv = F.conv2d(input, weights, stride=1, padding=1)

# Applying max pooling
output_pool = F.max_pool2d(output_conv, kernel_size=2, stride=2)

These functional interfaces are useful for custom operations where you might not want to use the predefined classes that handle weights automatically.

yrmo commented 4 months ago

Certainly! Let's redo the convolution and max pooling operations using the smallest possible inputs that still illustrate the core concepts. Here, we'll use a 4x4 input image, a 3x3 kernel for the convolution, and a 2x2 kernel for max pooling.

Here's the modified code:

import torch
import torch.nn.functional as F

# Example tensor and weight
input = torch.randn(1, 1, 4, 4)  # Smaller input: Format (batch_size, num_channels, height, width)
weights = torch.randn(1, 1, 3, 3)  # Smaller filter: Format (out_channels, in_channels, kernel_height, kernel_width)

# Applying convolution with no padding (to keep it simple and see the reduction in size)
output_conv = F.conv2d(input, weights, stride=1, padding=0)

# Applying max pooling with a 2x2 window
output_pool = F.max_pool2d(output_conv, kernel_size=2, stride=2)

# Print outputs to see the results
print("Output after convolution:", output_conv)
print("Output after max pooling:", output_pool)

Explanation:

This example minimally demonstrates both operations without involving large tensors, making it clear how each transformation impacts the data.

yrmo commented 4 months ago
>>> import torch
>>> import torch.nn.functional as F
>>> 
>>> # Example tensor and weight
>>> input = torch.randn(1, 1, 4, 4)  # Smaller input: Format (batch_size, num_channels, height, width)
>>> weights = torch.randn(1, 1, 3, 3)  # Smaller filter: Format (out_channels, in_channels, kernel_height, kernel_width)
>>> 
>>> # Applying convolution with no padding (to keep it simple and see the reduction in size)
>>> output_conv = F.conv2d(input, weights, stride=1, padding=0)
>>> 
>>> # Applying max pooling with a 2x2 window
>>> output_pool = F.max_pool2d(output_conv, kernel_size=2, stride=2)
>>> 
>>> # Print outputs to see the results
>>> print("Output after convolution:", output_conv)
Output after convolution: tensor([[[[ 7.3777, -2.4016],
          [ 0.3701,  2.6517]]]])
>>> print("Output after max pooling:", output_pool)
Output after max pooling: tensor([[[[7.3777]]]])
yrmo commented 4 months ago

In PyTorch, the derivative of a MaxPool2d operation during backpropagation essentially acts as a "mask" that propagates the gradient only through the locations of the maximum values within each pooling window of the forward pass. Essentially, during the forward pass, MaxPool2d selects the maximum values from each window, and during the backward pass, only these maximum values receive gradients, which are passed unchanged, while all other gradients are set to zero.

Here's a simple example to demonstrate this in PyTorch:

import torch
import torch.nn as nn

# Create a simple input tensor and set requires_grad=True to track operations on it
input_tensor = torch.tensor([[1, 2, 3, 4],
                             [5, 6, 7, 8],
                             [9, 10, 11, 12],
                             [13, 14, 15, 16]], dtype=torch.float32, requires_grad=True)

# Define a MaxPool2d layer with kernel size 2 (2x2 window)
max_pool = nn.MaxPool2d(kernel_size=2)

# Forward pass
output = max_pool(input_tensor)

# Print the output of the max pooling
print("Output of MaxPool2d:")
print(output)

# Start a backward pass with gradient ones
output.backward(torch.ones_like(output))

# Print the gradients of the input
print("Gradient back to the input tensor:")
print(input_tensor.grad)

This code will demonstrate how gradients are propagated back through the locations where the maximum values were selected. You can run this code snippet to see how it works in practice. If you have any specific scenarios in mind or further questions on handling different aspects of pooling layers or their gradients in neural network training, feel free to ask!