mirror of
https://github.com/MLSysBook/TinyTorch.git
synced 2026-07-25 13:33:13 -05:00
- Clean up CLAUDE.md module structure from 10+ parts to 8 logical sections - Remove confusing 'Concept, Context, Connections' framework references - Simplify to clear flow: Introduction → Background → Implementation → Testing → Integration - Keep Build→Use→Understand compliance for Education Architect - Remove thinking face emoji from ML Systems Thinking section - Focus on substance over artificial framework constraints
50 KiB
50 KiB
In [ ]:
#| default_exp core.tensor
#| export
import numpy as np
import sys
from typing import Union, List, Tuple, Optional, AnyIn [ ]:
print("🔥 TinyTorch Tensor Module")
print(f"NumPy version: {np.__version__}")
print(f"Python version: {sys.version_info.major}.{sys.version_info.minor}")
print("Ready to build tensors!")In [ ]:
#| export
class Tensor:
"""
TinyTorch Tensor: N-dimensional array with ML operations.
The fundamental data structure for all TinyTorch operations.
Wraps NumPy arrays with ML-specific functionality.
"""
def __init__(self, data: Union[int, float, List, np.ndarray], dtype: Optional[str] = None):
"""
Create a new tensor from data.
Args:
data: Input data (scalar, list, or numpy array)
dtype: Data type ('float32', 'int32', etc.). Defaults to auto-detect.
TODO: Implement tensor creation with proper type handling.
STEP-BY-STEP:
1. Check if data is a scalar (int/float) - convert to numpy array
2. Check if data is a list - convert to numpy array
3. Check if data is already a numpy array - use as-is
4. Apply dtype conversion if specified
5. Store the result in self._data
EXAMPLE:
Tensor(5) → stores np.array(5)
Tensor([1, 2, 3]) → stores np.array([1, 2, 3])
Tensor(np.array([1, 2, 3])) → stores the array directly
HINTS:
- Use isinstance() to check data types
- Use np.array() for conversion
- Handle dtype parameter for type conversion
- Store the array in self._data
"""
### BEGIN SOLUTION
# Convert input to numpy array
if isinstance(data, (int, float, np.number)):
# Handle Python and NumPy scalars
if dtype is None:
# Auto-detect type: int for integers, float32 for floats
if isinstance(data, int) or (isinstance(data, np.number) and np.issubdtype(type(data), np.integer)):
dtype = 'int32'
else:
dtype = 'float32'
self._data = np.array(data, dtype=dtype)
elif isinstance(data, list):
# Let NumPy auto-detect type, then convert if needed
temp_array = np.array(data)
if dtype is None:
# Use NumPy's auto-detected type, but prefer float32 for floats
if temp_array.dtype == np.float64:
dtype = 'float32'
else:
dtype = str(temp_array.dtype)
self._data = np.array(data, dtype=dtype)
elif isinstance(data, np.ndarray):
# Already a numpy array
if dtype is None:
# Keep existing dtype, but prefer float32 for float64
if data.dtype == np.float64:
dtype = 'float32'
else:
dtype = str(data.dtype)
self._data = data.astype(dtype) if dtype != data.dtype else data.copy()
else:
# Try to convert unknown types
self._data = np.array(data, dtype=dtype)
### END SOLUTION
@property
def data(self) -> np.ndarray:
"""
Access underlying numpy array.
TODO: Return the stored numpy array.
HINT: Return self._data (the array you stored in __init__)
"""
### BEGIN SOLUTION
return self._data
### END SOLUTION
@property
def shape(self) -> Tuple[int, ...]:
"""
Get tensor shape.
TODO: Return the shape of the stored numpy array.
HINT: Use .shape attribute of the numpy array
EXAMPLE: Tensor([1, 2, 3]).shape should return (3,)
"""
### BEGIN SOLUTION
return self._data.shape
### END SOLUTION
@property
def size(self) -> int:
"""
Get total number of elements.
TODO: Return the total number of elements in the tensor.
HINT: Use .size attribute of the numpy array
EXAMPLE: Tensor([1, 2, 3]).size should return 3
"""
### BEGIN SOLUTION
return self._data.size
### END SOLUTION
@property
def dtype(self) -> np.dtype:
"""
Get data type as numpy dtype.
TODO: Return the data type of the stored numpy array.
HINT: Use .dtype attribute of the numpy array
EXAMPLE: Tensor([1, 2, 3]).dtype should return dtype('int32')
"""
### BEGIN SOLUTION
return self._data.dtype
### END SOLUTION
def __repr__(self) -> str:
"""
String representation.
TODO: Create a clear string representation of the tensor.
APPROACH:
1. Convert the numpy array to a list for readable output
2. Include the shape and dtype information
3. Format: "Tensor([data], shape=shape, dtype=dtype)"
EXAMPLE:
Tensor([1, 2, 3]) → "Tensor([1, 2, 3], shape=(3,), dtype=int32)"
HINTS:
- Use .tolist() to convert numpy array to list
- Include shape and dtype information
- Keep format consistent and readable
"""
### BEGIN SOLUTION
return f"Tensor({self._data.tolist()}, shape={self.shape}, dtype={self.dtype})"
### END SOLUTION
def add(self, other: 'Tensor') -> 'Tensor':
"""
Add two tensors element-wise.
TODO: Implement tensor addition.
APPROACH:
1. Add the numpy arrays using +
2. Return a new Tensor with the result
3. Handle broadcasting automatically
EXAMPLE:
Tensor([1, 2]) + Tensor([3, 4]) → Tensor([4, 6])
HINTS:
- Use self._data + other._data
- Return Tensor(result)
- NumPy handles broadcasting automatically
"""
### BEGIN SOLUTION
result = self._data + other._data
return Tensor(result)
### END SOLUTION
def multiply(self, other: 'Tensor') -> 'Tensor':
"""
Multiply two tensors element-wise.
TODO: Implement tensor multiplication.
APPROACH:
1. Multiply the numpy arrays using *
2. Return a new Tensor with the result
3. Handle broadcasting automatically
EXAMPLE:
Tensor([1, 2]) * Tensor([3, 4]) → Tensor([3, 8])
HINTS:
- Use self._data * other._data
- Return Tensor(result)
- This is element-wise, not matrix multiplication
"""
### BEGIN SOLUTION
result = self._data * other._data
return Tensor(result)
### END SOLUTION
def __add__(self, other: Union['Tensor', int, float]) -> 'Tensor':
"""
Addition operator: tensor + other
TODO: Implement + operator for tensors.
APPROACH:
1. If other is a Tensor, use tensor addition
2. If other is a scalar, convert to Tensor first
3. Return the result
EXAMPLE:
Tensor([1, 2]) + Tensor([3, 4]) → Tensor([4, 6])
Tensor([1, 2]) + 5 → Tensor([6, 7])
"""
### BEGIN SOLUTION
if isinstance(other, Tensor):
return self.add(other)
else:
return self.add(Tensor(other))
### END SOLUTION
def __mul__(self, other: Union['Tensor', int, float]) -> 'Tensor':
"""
Multiplication operator: tensor * other
TODO: Implement * operator for tensors.
APPROACH:
1. If other is a Tensor, use tensor multiplication
2. If other is a scalar, convert to Tensor first
3. Return the result
EXAMPLE:
Tensor([1, 2]) * Tensor([3, 4]) → Tensor([3, 8])
Tensor([1, 2]) * 3 → Tensor([3, 6])
"""
### BEGIN SOLUTION
if isinstance(other, Tensor):
return self.multiply(other)
else:
return self.multiply(Tensor(other))
### END SOLUTION
def __sub__(self, other: Union['Tensor', int, float]) -> 'Tensor':
"""
Subtraction operator: tensor - other
TODO: Implement - operator for tensors.
APPROACH:
1. Convert other to Tensor if needed
2. Subtract using numpy arrays
3. Return new Tensor with result
EXAMPLE:
Tensor([5, 6]) - Tensor([1, 2]) → Tensor([4, 4])
Tensor([5, 6]) - 1 → Tensor([4, 5])
"""
### BEGIN SOLUTION
if isinstance(other, Tensor):
result = self._data - other._data
else:
result = self._data - other
return Tensor(result)
### END SOLUTION
def __truediv__(self, other: Union['Tensor', int, float]) -> 'Tensor':
"""
Division operator: tensor / other
TODO: Implement / operator for tensors.
APPROACH:
1. Convert other to Tensor if needed
2. Divide using numpy arrays
3. Return new Tensor with result
EXAMPLE:
Tensor([6, 8]) / Tensor([2, 4]) → Tensor([3, 2])
Tensor([6, 8]) / 2 → Tensor([3, 4])
"""
### BEGIN SOLUTION
if isinstance(other, Tensor):
result = self._data / other._data
else:
result = self._data / other
return Tensor(result)
### END SOLUTION
def mean(self) -> 'Tensor':
"""Computes the mean of the tensor's elements."""
return Tensor(np.mean(self.data))
def matmul(self, other: 'Tensor') -> 'Tensor':
"""
Perform matrix multiplication between two tensors.
TODO: Implement matrix multiplication.
APPROACH:
1. Use np.matmul() to perform matrix multiplication
2. Return a new Tensor with the result
3. Handle broadcasting automatically
EXAMPLE:
Tensor([[1, 2], [3, 4]]) @ Tensor([[5, 6], [7, 8]]) → Tensor([[19, 22], [43, 50]])
HINTS:
- Use np.matmul(self._data, other._data)
- Return Tensor(result)
- This is matrix multiplication, not element-wise multiplication
"""
### BEGIN SOLUTION
result = np.matmul(self._data, other._data)
return Tensor(result)
### END SOLUTIONIn [ ]:
# Test tensor creation immediately after implementation
print("🔬 Unit Test: Tensor Creation...")
# Test basic tensor creation
try:
# Test scalar
scalar = Tensor(5.0)
assert hasattr(scalar, '_data'), "Tensor should have _data attribute"
assert scalar._data.shape == (), f"Scalar should have shape (), got {scalar._data.shape}"
print("✅ Scalar creation works")
# Test vector
vector = Tensor([1, 2, 3])
assert vector._data.shape == (3,), f"Vector should have shape (3,), got {vector._data.shape}"
print("✅ Vector creation works")
# Test matrix
matrix = Tensor([[1, 2], [3, 4]])
assert matrix._data.shape == (2, 2), f"Matrix should have shape (2, 2), got {matrix._data.shape}"
print("✅ Matrix creation works")
print("📈 Progress: Tensor Creation ✓")
except Exception as e:
print(f"❌ Tensor creation test failed: {e}")
raise
print("🎯 Tensor creation behavior:")
print(" Converts data to NumPy arrays")
print(" Preserves shape and data type")
print(" Stores in _data attribute")In [ ]:
# Test tensor properties immediately after implementation
print("🔬 Unit Test: Tensor Properties...")
# Test properties with simple examples
try:
# Test with a simple matrix
tensor = Tensor([[1, 2, 3], [4, 5, 6]])
# Test shape property
assert tensor.shape == (2, 3), f"Shape should be (2, 3), got {tensor.shape}"
print("✅ Shape property works")
# Test size property
assert tensor.size == 6, f"Size should be 6, got {tensor.size}"
print("✅ Size property works")
# Test data property
assert np.array_equal(tensor.data, np.array([[1, 2, 3], [4, 5, 6]])), "Data property should return numpy array"
print("✅ Data property works")
# Test dtype property
assert tensor.dtype in [np.int32, np.int64], f"Dtype should be int32 or int64, got {tensor.dtype}"
print("✅ Dtype property works")
print("📈 Progress: Tensor Properties ✓")
except Exception as e:
print(f"❌ Tensor properties test failed: {e}")
raise
print("🎯 Tensor properties behavior:")
print(" shape: Returns tuple of dimensions")
print(" size: Returns total number of elements")
print(" data: Returns underlying NumPy array")
print(" dtype: Returns NumPy data type")In [ ]:
# Test tensor arithmetic immediately after implementation
print("🔬 Unit Test: Tensor Arithmetic...")
# Test basic arithmetic with simple examples
try:
# Test addition
a = Tensor([1, 2, 3])
b = Tensor([4, 5, 6])
result = a + b
expected = np.array([5, 7, 9])
assert np.array_equal(result.data, expected), f"Addition failed: expected {expected}, got {result.data}"
print("✅ Addition works")
# Test scalar addition
result_scalar = a + 10
expected_scalar = np.array([11, 12, 13])
assert np.array_equal(result_scalar.data, expected_scalar), f"Scalar addition failed: expected {expected_scalar}, got {result_scalar.data}"
print("✅ Scalar addition works")
# Test multiplication
result_mul = a * b
expected_mul = np.array([4, 10, 18])
assert np.array_equal(result_mul.data, expected_mul), f"Multiplication failed: expected {expected_mul}, got {result_mul.data}"
print("✅ Multiplication works")
# Test scalar multiplication
result_scalar_mul = a * 2
expected_scalar_mul = np.array([2, 4, 6])
assert np.array_equal(result_scalar_mul.data, expected_scalar_mul), f"Scalar multiplication failed: expected {expected_scalar_mul}, got {result_scalar_mul.data}"
print("✅ Scalar multiplication works")
print("📈 Progress: Tensor Arithmetic ✓")
except Exception as e:
print(f"❌ Tensor arithmetic test failed: {e}")
raise
print("🎯 Tensor arithmetic behavior:")
print(" Element-wise operations on tensors")
print(" Broadcasting with scalars")
print(" Returns new Tensor objects")In [ ]:
def test_unit_tensor_creation():
"""Comprehensive test of tensor creation with all data types and shapes."""
print("🔬 Testing comprehensive tensor creation...")
# Test scalar creation
scalar_int = Tensor(42)
assert scalar_int.shape == ()
# Test vector creation
vector_int = Tensor([1, 2, 3])
assert vector_int.shape == (3,)
# Test matrix creation
matrix_2x2 = Tensor([[1, 2], [3, 4]])
assert matrix_2x2.shape == (2, 2)
print("✅ Tensor creation tests passed!")
# Run the test
test_unit_tensor_creation()In [ ]:
def test_unit_tensor_properties():
"""Comprehensive test of tensor properties (shape, size, dtype, data access)."""
print("🔬 Testing comprehensive tensor properties...")
tensor = Tensor([[1, 2, 3], [4, 5, 6]])
# Test shape property
assert tensor.shape == (2, 3)
# Test size property
assert tensor.size == 6
# Test data property
assert np.array_equal(tensor.data, np.array([[1, 2, 3], [4, 5, 6]]))
# Test dtype property
assert tensor.dtype in [np.int32, np.int64]
print("✅ Tensor properties tests passed!")
# Run the test
test_unit_tensor_properties()In [ ]:
def test_unit_tensor_arithmetic():
"""Comprehensive test of tensor arithmetic operations."""
print("🔬 Testing comprehensive tensor arithmetic...")
a = Tensor([1, 2, 3])
b = Tensor([4, 5, 6])
# Test addition
c = a + b
expected = np.array([5, 7, 9])
assert np.array_equal(c.data, expected)
# Test multiplication
d = a * b
expected = np.array([4, 10, 18])
assert np.array_equal(d.data, expected)
# Test subtraction
e = b - a
expected = np.array([3, 3, 3])
assert np.array_equal(e.data, expected)
# Test division
f = b / a
expected = np.array([4.0, 2.5, 2.0])
assert np.allclose(f.data, expected)
print("✅ Tensor arithmetic tests passed!")
# Run the test
test_unit_tensor_arithmetic()In [ ]:
def test_module_tensor_numpy_integration():
"""
Integration test for tensor operations with NumPy arrays.
Tests that tensors properly integrate with NumPy operations and maintain
compatibility with the scientific Python ecosystem.
"""
print("🔬 Running Integration Test: Tensor-NumPy Integration...")
# Test 1: Tensor from NumPy array
numpy_array = np.array([[1, 2, 3], [4, 5, 6]])
tensor_from_numpy = Tensor(numpy_array)
assert tensor_from_numpy.shape == (2, 3), "Tensor should preserve NumPy array shape"
assert np.array_equal(tensor_from_numpy.data, numpy_array), "Tensor should preserve NumPy array data"
# Test 2: Tensor arithmetic with NumPy-compatible operations
a = Tensor([1.0, 2.0, 3.0])
b = Tensor([4.0, 5.0, 6.0])
# Test operations that would be used in neural networks
dot_product_result = np.dot(a.data, b.data) # Common in layers
assert np.isclose(dot_product_result, 32.0), "Dot product should work with tensor data"
# Test 3: Broadcasting compatibility
matrix = Tensor([[1, 2], [3, 4]])
scalar = Tensor(10)
result = matrix + scalar
expected = np.array([[11, 12], [13, 14]])
assert np.array_equal(result.data, expected), "Broadcasting should work like NumPy"
# Test 4: Integration with scientific computing patterns
data = Tensor([1, 4, 9, 16, 25])
sqrt_result = Tensor(np.sqrt(data.data)) # Using NumPy functions on tensor data
expected_sqrt = np.array([1., 2., 3., 4., 5.])
assert np.allclose(sqrt_result.data, expected_sqrt), "Should integrate with NumPy functions"
print("✅ Integration Test Passed: Tensor-NumPy integration works correctly.")
# Run the integration test
test_module_tensor_numpy_integration()