Spaces:
Running
Running
import streamlit as st | |
import torch | |
import io | |
import sys | |
# Function to execute the input code and capture print statements | |
def execute_code(code): | |
# Redirect stdout to capture print statements | |
old_stdout = sys.stdout | |
sys.stdout = mystdout = io.StringIO() | |
global_vars = {"torch": torch} | |
local_vars = {} | |
try: | |
exec(code, global_vars, local_vars) | |
output = mystdout.getvalue() | |
except Exception as e: | |
output = str(e) | |
finally: | |
# Reset redirect. | |
sys.stdout = old_stdout | |
return output, local_vars | |
# Dictionary with exercise details | |
exercises = { | |
"Exercise 1: Create and Manipulate Tensors": { | |
"description": "Tensors are the core data structure in PyTorch, similar to arrays in NumPy but with additional capabilities for GPU acceleration. This exercise introduces how to create tensors from various data sources such as lists and NumPy arrays. It also covers basic tensor operations like addition, subtraction, and element-wise multiplication, which are fundamental for manipulating data in PyTorch.", | |
"code": '''import torch | |
import numpy as np | |
# Creating tensors from Python lists | |
# This creates a 1D tensor from the list [1, 2, 3] | |
tensor_from_list = torch.tensor([1, 2, 3]) | |
print("Tensor from list:", tensor_from_list) | |
# Creating tensors from NumPy arrays | |
# This converts a NumPy array to a tensor | |
numpy_array = np.array([4, 5, 6]) | |
tensor_from_numpy = torch.tensor(numpy_array) | |
print("Tensor from NumPy array:", tensor_from_numpy) | |
# Performing basic tensor operations | |
tensor1 = torch.tensor([1, 2, 3]) | |
tensor2 = torch.tensor([4, 5, 6]) | |
# Addition | |
addition = tensor1 + tensor2 | |
print("Addition:", addition) | |
# Subtraction | |
subtraction = tensor1 - tensor2 | |
print("Subtraction:", subtraction) | |
# Element-wise multiplication | |
elementwise_multiplication = tensor1 * tensor2 | |
print("Element-wise Multiplication:", elementwise_multiplication) | |
''' | |
}, | |
"Exercise 2: Tensor Indexing and Slicing": { | |
"description": "Indexing and slicing allow you to access and manipulate specific elements and sub-tensors. This is crucial for tasks such as data preprocessing and manipulation in machine learning workflows. This exercise demonstrates how to index and slice tensors to extract and modify elements efficiently.", | |
"code": '''import torch | |
# Creating a 2D tensor (matrix) | |
tensor = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) | |
# Indexing elements | |
# Accessing the element at the 2nd row and 3rd column (indexing starts at 0) | |
element = tensor[1, 2] | |
print("Element at index [1, 2]:", element) | |
# Slicing sub-tensors | |
# Extracting the entire second row | |
row = tensor[1, :] | |
print("Second row:", row) | |
# Extracting the entire third column | |
column = tensor[:, 2] | |
print("Third column:", column) | |
# Modifying elements | |
# Changing the first element of the tensor to 10 | |
tensor[0, 0] = 10 | |
print("Modified tensor:", tensor) | |
''' | |
}, | |
"Exercise 3: Tensor Reshaping and Transposing": { | |
"description": "Reshaping and transposing tensors are common operations when preparing data for neural networks. Reshaping allows you to change the layout of tensor data without altering its data. Transposing changes the orientation of the data, which is useful for operations like matrix multiplication. This exercise covers reshaping using `view()` and `reshape()`, and transposing using `transpose()` and `permute()`.", | |
"code": '''import torch | |
# Creating a 2D tensor | |
tensor = torch.tensor([[1, 2, 3], [4, 5, 6]]) | |
# Reshaping the tensor | |
# Changing the shape of the tensor to (3, 2) | |
reshaped_tensor = tensor.view(3, 2) | |
print("Reshaped tensor:", reshaped_tensor) | |
# Another way to reshape using reshape() | |
reshaped_tensor2 = tensor.reshape(-1) # Flattening the tensor | |
print("Reshaped tensor (flattened):", reshaped_tensor2) | |
# Transposing the tensor | |
# Swapping the dimensions of the tensor (transpose rows and columns) | |
transposed_tensor = tensor.t() | |
print("Transposed tensor:", transposed_tensor) | |
# Using permute for higher-dimensional tensors | |
tensor_3d = torch.randn(2, 3, 4) # Creating a random 3D tensor | |
# Permuting dimensions | |
permuted_tensor = tensor_3d.permute(2, 0, 1) | |
print("Permuted tensor shape:", permuted_tensor.shape) | |
''' | |
}, | |
"Exercise 4: Tensor Operations and Broadcasting": { | |
"description": "Broadcasting in PyTorch allows you to perform arithmetic operations on tensors of different shapes. This can simplify code and reduce the need for explicit reshaping. This exercise demonstrates matrix multiplication, the concept of broadcasting, and the application of element-wise functions like sine and exponential.", | |
"code": '''import torch | |
# Matrix multiplication | |
tensor1 = torch.tensor([[1, 2], [3, 4]]) | |
tensor2 = torch.tensor([[5, 6], [7, 8]]) | |
# Performing matrix multiplication using matmul | |
matmul_result = torch.matmul(tensor1, tensor2) | |
print("Matrix multiplication result:", matmul_result) | |
# Broadcasting example | |
# Broadcasting allows tensor3 and tensor4 to be added despite their different shapes | |
tensor3 = torch.tensor([1, 2, 3]) | |
tensor4 = torch.tensor([[1], [2], [3]]) | |
broadcast_result = tensor3 + tensor4 | |
print("Broadcasting result:", broadcast_result) | |
# Element-wise functions | |
# Applying sine function element-wise | |
sin_result = torch.sin(tensor3) | |
print("Sine of tensor3:", sin_result) | |
# Applying exponential function element-wise | |
exp_result = torch.exp(tensor3) | |
print("Exponential of tensor3:", exp_result) | |
# Applying square root function element-wise | |
# Note: sqrt requires the tensor to be of floating-point type | |
sqrt_result = torch.sqrt(tensor3.float()) | |
print("Square root of tensor3:", sqrt_result) | |
''' | |
}, | |
"Exercise 5: Tensor Initialization": { | |
"description": "Proper initialization of tensors is crucial for neural network training and other machine learning tasks. This exercise explores various methods to initialize tensors, such as filling them with zeros, ones, random values, and specific distributions. Understanding these methods helps in setting up model parameters and test data.", | |
"code": '''import torch | |
# Creating tensors filled with zeros and ones | |
zeros_tensor = torch.zeros(3, 3) | |
print("Zeros tensor:", zeros_tensor) | |
ones_tensor = torch.ones(3, 3) | |
print("Ones tensor:", ones_tensor) | |
# Randomly initialized tensors | |
# Uniform distribution in the range [0, 1) | |
rand_tensor = torch.rand(3, 3) | |
print("Uniform random tensor:", rand_tensor) | |
# Normal distribution with mean 0 and variance 1 | |
randn_tensor = torch.randn(3, 3) | |
print("Normal random tensor:", randn_tensor) | |
# Random integers in the range [0, 10) | |
randint_tensor = torch.randint(0, 10, (3, 3)) | |
print("Random integer tensor:", randint_tensor) | |
# Initializing tensors with specific distributions | |
# Normal distribution with custom mean and standard deviation | |
normal_tensor = torch.normal(mean=0, std=1, size=(3, 3)) | |
print("Normal distribution tensor:", normal_tensor) | |
# Uniform distribution in a custom range [0, 1) | |
uniform_tensor = torch.empty(3, 3).uniform_(0, 1) | |
print("Uniform distribution tensor:", uniform_tensor) | |
''' | |
}, | |
"Exercise 6: Tensor Arithmetic Operations": { | |
"description": "Arithmetic operations on tensors are essential for numerous tasks in machine learning, including data preprocessing and neural network computations. This exercise covers basic arithmetic with tensors, in-place operations, and aggregation functions that compute summaries over tensor elements.", | |
"code": '''import torch | |
# Creating a 1D tensor | |
tensor = torch.tensor([1.0, 2.0, 3.0]) | |
# Scalar-tensor operations | |
# Adding a scalar to each element of the tensor | |
add_scalar = tensor + 5 | |
print("Addition with scalar:", add_scalar) | |
# Multiplying each element by a scalar | |
mul_scalar = tensor * 2 | |
print("Multiplication with scalar:", mul_scalar) | |
# In-place operations (modify the original tensor) | |
# Adding 5 to each element of the tensor | |
tensor.add_(5) | |
print("In-place addition:", tensor) | |
# Tensor aggregation operations | |
tensor = torch.tensor([[1, 2], [3, 4]]) | |
# Summing all elements | |
sum_all = tensor.sum() | |
print("Sum of all elements:", sum_all) | |
# Computing the mean of all elements | |
mean_all = tensor.mean() | |
print("Mean of all elements:", mean_all) | |
# Finding the maximum element | |
max_all = tensor.max() | |
print("Maximum element:", max_all) | |
''' | |
}, | |
"Exercise 7: Tensor Comparison and Logical Operations": { | |
"description": "Comparison and logical operations enable you to make conditional selections and perform logical checks on tensors. This is crucial for tasks such as filtering data and implementing decision-based logic in machine learning workflows. This exercise demonstrates the use of comparison operators and logical functions, which are often used in neural network training and data analysis.", | |
"code": '''import torch | |
# Creating two tensors for comparison | |
tensor1 = torch.tensor([1, 2, 3]) | |
tensor2 = torch.tensor([3, 2, 1]) | |
# Comparison operations | |
# Checking if elements in tensor1 are greater than corresponding elements in tensor2 | |
greater_than = tensor1 > tensor2 | |
print("Greater than comparison:", greater_than) | |
# Checking if elements in tensor1 are equal to corresponding elements in tensor2 | |
equal = tensor1 == tensor2 | |
print("Equality comparison:", equal) | |
# Logical operations | |
# Logical AND operation between comparison results | |
logical_and = torch.logical_and(tensor1 > 1, tensor2 < 3) | |
print("Logical AND:", logical_and) | |
# Logical OR operation between comparison results | |
logical_or = torch.logical_or(tensor1 < 3, tensor2 > 1) | |
print("Logical OR:", logical_or) | |
# Logical NOT operation | |
logical_not = torch.logical_not(tensor1 < 3) | |
print("Logical NOT:", logical_not) | |
# Conditional selection using torch.where | |
# Selecting elements from tensor1 if condition is True, otherwise from tensor2 | |
selected_tensor = torch.where(tensor1 > 2, tensor1, tensor2) | |
print("Conditional selection:", selected_tensor) | |
''' | |
}, | |
"Exercise 8: Tensor Reduction Operations": { | |
"description": "Reduction operations aggregate tensor elements along specified dimensions, providing summarized results like sums, means, and indices of maximum or minimum values. This exercise covers essential reduction operations, which are commonly used in loss functions and performance metrics in machine learning.", | |
"code": '''import torch | |
# Creating a 2D tensor (matrix) | |
tensor = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) | |
# Reduction operations along dimensions | |
# Summing elements along dimension 0 (rows) | |
sum_dim0 = tensor.sum(dim=0) | |
print("Sum along dimension 0:", sum_dim0) | |
# Computing the mean along dimension 1 (columns) | |
mean_dim1 = tensor.mean(dim=1) | |
print("Mean along dimension 1:", mean_dim1) | |
# Computing the product of all elements in the tensor | |
prod_all = tensor.prod() | |
print("Product of all elements:", prod_all) | |
# Advanced reduction operations | |
# Finding the index of the maximum element in the flattened tensor | |
argmax_all = tensor.argmax() | |
print("Index of maximum element:", argmax_all) | |
# Finding the indices of the minimum elements along dimension 0 | |
argmin_dim0 = tensor.argmin(dim=0) | |
print("Indices of minimum elements along dimension 0:", argmin_dim0) | |
''' | |
}, | |
"Exercise 9: Tensor Cloning and Detachment": { | |
"description": "Cloning and detaching tensors are important when working with gradients in neural networks. Cloning creates a copy of a tensor, while detaching removes it from the computation graph to prevent gradient tracking. This exercise demonstrates these operations, which are crucial for managing tensor operations in complex neural networks.", | |
"code": '''import torch | |
# Creating a tensor with gradient tracking enabled | |
tensor = torch.tensor([1.0, 2.0, 3.0], requires_grad=True) | |
# Cloning the tensor | |
# This creates a new tensor with the same data but no gradient history | |
cloned_tensor = tensor.clone() | |
print("Original tensor:", tensor) | |
print("Cloned tensor:", cloned_tensor) | |
# Detaching the tensor from the computation graph | |
# This removes the tensor from the gradient computation graph | |
detached_tensor = tensor.detach() | |
print("Detached tensor:", detached_tensor) | |
# Comparing original and detached tensors | |
# Modifying the original tensor (note that in-place operations modify the tensor in place) | |
tensor.add_(1) | |
print("Modified original tensor:", tensor) | |
print("Detached tensor remains unchanged:", detached_tensor) | |
''' | |
}, | |
"Exercise 10: GPU Operations with Tensors": { | |
"description": "Utilizing GPUs can significantly speed up tensor operations, which is crucial for training large neural networks. This exercise covers checking for GPU availability, moving tensors to GPU, and performing operations on GPU to compare performance with CPU operations.", | |
"code": '''import torch | |
import time | |
# Check for GPU availability | |
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
print("Using device:", device) | |
# Creating a tensor and moving it to GPU if available | |
tensor = torch.tensor([1.0, 2.0, 3.0]) | |
tensor = tensor.to(device) | |
print("Tensor on GPU:", tensor) | |
# Perform operations on GPU | |
# Adding a scalar to each element of the tensor on GPU | |
tensor_add = tensor + 5 | |
print("Tensor after addition on GPU:", tensor_add) | |
# Compare speed of operations on CPU vs GPU | |
# Creating a large tensor for performance comparison | |
cpu_tensor = torch.randn(10000, 10000) | |
# CPU operation | |
start_time = time.time() | |
cpu_result = cpu_tensor @ cpu_tensor.T # Matrix multiplication | |
end_time = time.time() | |
print("CPU operation time:", end_time - start_time, "seconds") | |
# GPU operation (if available) | |
if torch.cuda.is_available(): | |
gpu_tensor = cpu_tensor.to(device) | |
start_time = time.time() | |
gpu_result = gpu_tensor @ gpu_tensor.T # Matrix multiplication | |
torch.cuda.synchronize() # Wait for GPU operation to finish | |
end_time = time.time() | |
print("GPU operation time:", end_time - start_time, "seconds") | |
''' | |
} | |
} | |
st.title('PyTorch Code Runner') | |
# Side menu for exercises | |
exercise_choice = st.sidebar.radio("Choose an exercise", list(exercises.keys())) | |
# Display the chosen exercise description | |
st.subheader(exercise_choice) | |
st.write(exercises[exercise_choice]["description"]) | |
# Text area for inputting the PyTorch code | |
code_input = st.text_area("Enter your PyTorch code here", height=300, value=exercises[exercise_choice]["code"]) | |
# Button to execute the code | |
if st.button("Run Code"): | |
# Prepend the import statement | |
code_to_run = "import torch\n" + code_input | |
# Execute the code and capture the output | |
output, variables = execute_code(code_to_run) | |
# Display the output | |
st.subheader('Output') | |
st.text(output) | |
# Display returned variables | |
if variables: | |
st.subheader('Variables') | |
for key, value in variables.items(): | |
st.text(f"{key}: {value}") | |