Files
TinyTorch/assignments/source/01_tensor/01_tensor.ipynb
Vijay Janapa Reddi 83fb269d9f Complete migration from modules/ to assignments/source/ structure
- Migrated all Python source files to assignments/source/ structure
- Updated nbdev configuration to use assignments/source as nbs_path
- Updated all tito commands (nbgrader, export, test) to use new structure
- Fixed hardcoded paths in Python files and documentation
- Updated config.py to use assignments/source instead of modules
- Fixed test command to use correct file naming (short names vs full module names)
- Regenerated all notebook files with clean metadata
- Verified complete workflow: Python source → NBGrader → nbdev export → testing

All systems now working: NBGrader (14 source assignments, 1 released), nbdev export (7 generated files), and pytest integration.

The modules/ directory has been retired and replaced with standard NBGrader structure.
2025-07-12 12:06:56 -04:00

1047 lines
44 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"id": "6186cbf0",
"metadata": {
"cell_marker": "\"\"\""
},
"source": [
"# Module 1: Tensor - Core Data Structure\n",
"\n",
"Welcome to the Tensor module! This is where TinyTorch really begins. You'll implement the fundamental data structure that powers all ML systems.\n",
"\n",
"## Learning Goals\n",
"- Understand tensors as N-dimensional arrays with ML-specific operations\n",
"- Implement a complete Tensor class with arithmetic operations\n",
"- Handle shape management, data types, and memory layout\n",
"- Build the foundation for neural networks and automatic differentiation"
]
},
{
"cell_type": "markdown",
"id": "c88b6562",
"metadata": {
"cell_marker": "\"\"\""
},
"source": [
"## \ud83d\udce6 Where This Code Lives in the Final Package\n",
"\n",
"**Learning Side:** You work in `assignments/source/01_tensor/tensor_dev.py` \n",
"**Building Side:** Code exports to `tinytorch.core.tensor`\n",
"\n",
"```python\n",
"# Final package structure:\n",
"from tinytorch.core.tensor import Tensor\n",
"from tinytorch.core.layers import Dense, Conv2D\n",
"from tinytorch.core.activations import ReLU, Sigmoid, Tanh\n",
"```\n",
"\n",
"**Why this matters:**\n",
"- **Learning:** Focused modules for deep understanding\n",
"- **Production:** Proper organization like PyTorch's `torch.tensor`\n",
"- **Consistency:** Core data structure lives in `core.tensor`"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ff79eeec",
"metadata": {},
"outputs": [],
"source": [
"#| default_exp core.tensor\n",
"\n",
"# Setup and imports\n",
"import numpy as np\n",
"import sys\n",
"from typing import Union, List, Tuple, Optional, Any\n",
"\n",
"print(\"\ud83d\udd25 TinyTorch Tensor Module\")\n",
"print(f\"NumPy version: {np.__version__}\")\n",
"print(f\"Python version: {sys.version_info.major}.{sys.version_info.minor}\")\n",
"print(\"Ready to build tensors!\")"
]
},
{
"cell_type": "markdown",
"id": "aacde3bd",
"metadata": {
"cell_marker": "\"\"\"",
"lines_to_next_cell": 1
},
"source": [
"## Step 1: What is a Tensor?\n",
"\n",
"### Definition\n",
"A **tensor** is an N-dimensional array with ML-specific operations. Think of it as a container that can hold data in multiple dimensions:\n",
"\n",
"- **Scalar** (0D): A single number - `5.0`\n",
"- **Vector** (1D): A list of numbers - `[1, 2, 3]` \n",
"- **Matrix** (2D): A 2D array - `[[1, 2], [3, 4]]`\n",
"- **Higher dimensions**: 3D, 4D, etc. for images, video, batches\n",
"\n",
"### Why Tensors Matter in ML\n",
"Tensors are the foundation of all machine learning because:\n",
"- **Neural networks** process tensors (images, text, audio)\n",
"- **Batch processing** requires multiple samples at once\n",
"- **GPU acceleration** works efficiently with tensors\n",
"- **Automatic differentiation** needs structured data\n",
"\n",
"### Real-World Examples\n",
"- **Image**: 3D tensor `(height, width, channels)` - `(224, 224, 3)` for RGB images\n",
"- **Batch of images**: 4D tensor `(batch_size, height, width, channels)` - `(32, 224, 224, 3)`\n",
"- **Text**: 2D tensor `(sequence_length, embedding_dim)` - `(100, 768)` for BERT embeddings\n",
"- **Audio**: 2D tensor `(time_steps, features)` - `(16000, 1)` for 1 second of audio\n",
"\n",
"### Why Not Just Use NumPy?\n",
"We will use NumPy internally, but our Tensor class adds:\n",
"- **ML-specific operations** (later: gradients, GPU support)\n",
"- **Consistent API** for neural networks\n",
"- **Type safety** and error checking\n",
"- **Integration** with the rest of TinyTorch\n",
"\n",
"### Visual Intuition\n",
"```\n",
"Scalar (0D): 5.0\n",
"Vector (1D): [1, 2, 3, 4]\n",
"Matrix (2D): [[1, 2, 3],\n",
" [4, 5, 6]]\n",
"3D Tensor: [[[1, 2], [3, 4]],\n",
" [[5, 6], [7, 8]]]\n",
"```\n",
"\n",
"Let's start building!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2dc8771d",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"#| export\n",
"class Tensor:\n",
" \"\"\"\n",
" TinyTorch Tensor: N-dimensional array with ML operations.\n",
" \n",
" The fundamental data structure for all TinyTorch operations.\n",
" Wraps NumPy arrays with ML-specific functionality.\n",
" \n",
" TODO: Implement the core Tensor class with data handling and properties.\n",
" \n",
" APPROACH:\n",
" 1. Store the input data as a NumPy array internally\n",
" 2. Handle different input types (scalars, lists, numpy arrays)\n",
" 3. Implement properties to access shape, size, and data type\n",
" 4. Create a clear string representation\n",
" \n",
" EXAMPLE:\n",
" Input: Tensor([1, 2, 3])\n",
" Expected: Tensor with shape (3,), size 3, dtype int32\n",
" \n",
" HINTS:\n",
" - Use NumPy's np.array() to convert inputs\n",
" - Handle dtype parameter for type conversion\n",
" - Store the array in a private attribute like self._data\n",
" - Properties should return information about the stored array\n",
" \"\"\"\n",
" \n",
" def __init__(self, data: Union[int, float, List, np.ndarray], dtype: Optional[str] = None):\n",
" \"\"\"\n",
" Create a new tensor from data.\n",
" \n",
" Args:\n",
" data: Input data (scalar, list, or numpy array)\n",
" dtype: Data type ('float32', 'int32', etc.). Defaults to auto-detect.\n",
" \n",
" TODO: Implement tensor creation with proper type handling.\n",
" \n",
" STEP-BY-STEP:\n",
" 1. Check if data is a scalar (int/float) - convert to numpy array\n",
" 2. Check if data is a list - convert to numpy array \n",
" 3. Check if data is already a numpy array - use as-is\n",
" 4. Apply dtype conversion if specified\n",
" 5. Store the result in self._data\n",
" \n",
" EXAMPLE:\n",
" Tensor(5) \u2192 stores np.array(5)\n",
" Tensor([1, 2, 3]) \u2192 stores np.array([1, 2, 3])\n",
" Tensor(np.array([1, 2, 3])) \u2192 stores the array directly\n",
" \"\"\"\n",
" raise NotImplementedError(\"Student implementation required\")\n",
" \n",
" @property\n",
" def data(self) -> np.ndarray:\n",
" \"\"\"\n",
" Access underlying numpy array.\n",
" \n",
" TODO: Return the stored numpy array.\n",
" \n",
" HINT: Return self._data (the array you stored in __init__)\n",
" \"\"\"\n",
" raise NotImplementedError(\"Student implementation required\")\n",
" \n",
" @property\n",
" def shape(self) -> Tuple[int, ...]:\n",
" \"\"\"\n",
" Get tensor shape.\n",
" \n",
" TODO: Return the shape of the stored numpy array.\n",
" \n",
" HINT: Use .shape attribute of the numpy array\n",
" EXAMPLE: Tensor([1, 2, 3]).shape should return (3,)\n",
" \"\"\"\n",
" raise NotImplementedError(\"Student implementation required\")\n",
" \n",
" @property\n",
" def size(self) -> int:\n",
" \"\"\"\n",
" Get total number of elements.\n",
" \n",
" TODO: Return the total number of elements in the tensor.\n",
" \n",
" HINT: Use .size attribute of the numpy array\n",
" EXAMPLE: Tensor([1, 2, 3]).size should return 3\n",
" \"\"\"\n",
" raise NotImplementedError(\"Student implementation required\")\n",
" \n",
" @property\n",
" def dtype(self) -> np.dtype:\n",
" \"\"\"\n",
" Get data type as numpy dtype.\n",
" \n",
" TODO: Return the data type of the stored numpy array.\n",
" \n",
" HINT: Use .dtype attribute of the numpy array\n",
" EXAMPLE: Tensor([1, 2, 3]).dtype should return dtype('int32')\n",
" \"\"\"\n",
" raise NotImplementedError(\"Student implementation required\")\n",
" \n",
" def __repr__(self) -> str:\n",
" \"\"\"\n",
" String representation.\n",
" \n",
" TODO: Create a clear string representation of the tensor.\n",
" \n",
" APPROACH:\n",
" 1. Convert the numpy array to a list for readable output\n",
" 2. Include the shape and dtype information\n",
" 3. Format: \"Tensor([data], shape=shape, dtype=dtype)\"\n",
" \n",
" EXAMPLE:\n",
" Tensor([1, 2, 3]) \u2192 \"Tensor([1, 2, 3], shape=(3,), dtype=int32)\"\n",
" \"\"\"\n",
" raise NotImplementedError(\"Student implementation required\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "707dd61c",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"#| hide\n",
"#| export\n",
"class Tensor:\n",
" \"\"\"\n",
" TinyTorch Tensor: N-dimensional array with ML operations.\n",
" \n",
" The fundamental data structure for all TinyTorch operations.\n",
" Wraps NumPy arrays with ML-specific functionality.\n",
" \"\"\"\n",
" \n",
" def __init__(self, data: Union[int, float, List, np.ndarray], dtype: Optional[str] = None):\n",
" \"\"\"\n",
" Create a new tensor from data.\n",
" \n",
" Args:\n",
" data: Input data (scalar, list, or numpy array)\n",
" dtype: Data type ('float32', 'int32', etc.). Defaults to auto-detect.\n",
" \"\"\"\n",
" # Convert input to numpy array\n",
" if isinstance(data, (int, float, np.number)):\n",
" # Handle Python and NumPy scalars\n",
" if dtype is None:\n",
" # Auto-detect type: int for integers, float32 for floats\n",
" if isinstance(data, int) or (isinstance(data, np.number) and np.issubdtype(type(data), np.integer)):\n",
" dtype = 'int32'\n",
" else:\n",
" dtype = 'float32'\n",
" self._data = np.array(data, dtype=dtype)\n",
" elif isinstance(data, list):\n",
" # Let NumPy auto-detect type, then convert if needed\n",
" temp_array = np.array(data)\n",
" if dtype is None:\n",
" # Keep NumPy's auto-detected type, but prefer common ML types\n",
" if np.issubdtype(temp_array.dtype, np.integer):\n",
" dtype = 'int32'\n",
" elif np.issubdtype(temp_array.dtype, np.floating):\n",
" dtype = 'float32'\n",
" else:\n",
" dtype = temp_array.dtype\n",
" self._data = temp_array.astype(dtype)\n",
" elif isinstance(data, np.ndarray):\n",
" self._data = data.astype(dtype or data.dtype)\n",
" else:\n",
" raise TypeError(f\"Cannot create tensor from {type(data)}\")\n",
" \n",
" @property\n",
" def data(self) -> np.ndarray:\n",
" \"\"\"Access underlying numpy array.\"\"\"\n",
" return self._data\n",
" \n",
" @property\n",
" def shape(self) -> Tuple[int, ...]:\n",
" \"\"\"Get tensor shape.\"\"\"\n",
" return self._data.shape\n",
" \n",
" @property\n",
" def size(self) -> int:\n",
" \"\"\"Get total number of elements.\"\"\"\n",
" return self._data.size\n",
" \n",
" @property\n",
" def dtype(self) -> np.dtype:\n",
" \"\"\"Get data type as numpy dtype.\"\"\"\n",
" return self._data.dtype\n",
" \n",
" def __repr__(self) -> str:\n",
" \"\"\"String representation.\"\"\"\n",
" return f\"Tensor({self._data.tolist()}, shape={self.shape}, dtype={self.dtype})\"\n",
" \n",
" def add(self, other: 'Tensor') -> 'Tensor':\n",
" \"\"\"\n",
" Add another tensor to this tensor.\n",
" \n",
" TODO: Implement tensor addition as a method.\n",
" \n",
" APPROACH:\n",
" 1. Use the add_tensors function you already implemented\n",
" 2. Or implement the addition directly using self._data + other._data\n",
" 3. Return a new Tensor with the result\n",
" \n",
" EXAMPLE:\n",
" Tensor([1, 2, 3]).add(Tensor([4, 5, 6])) \u2192 Tensor([5, 7, 9])\n",
" \n",
" HINTS:\n",
" - You can reuse add_tensors(self, other)\n",
" - Or implement directly: Tensor(self._data + other._data)\n",
" \"\"\"\n",
" raise NotImplementedError(\"Student implementation required\")\n",
" \n",
" def multiply(self, other: 'Tensor') -> 'Tensor':\n",
" \"\"\"\n",
" Multiply this tensor by another tensor.\n",
" \n",
" TODO: Implement tensor multiplication as a method.\n",
" \n",
" APPROACH:\n",
" 1. Use the multiply_tensors function you already implemented\n",
" 2. Or implement the multiplication directly using self._data * other._data\n",
" 3. Return a new Tensor with the result\n",
" \n",
" EXAMPLE:\n",
" Tensor([1, 2, 3]).multiply(Tensor([4, 5, 6])) \u2192 Tensor([4, 10, 18])\n",
" \n",
" HINTS:\n",
" - You can reuse multiply_tensors(self, other)\n",
" - Or implement directly: Tensor(self._data * other._data)\n",
" \"\"\"\n",
" raise NotImplementedError(\"Student implementation required\")\n",
" \n",
" # Arithmetic operators for natural syntax (a + b, a * b, etc.)\n",
" def __add__(self, other: Union['Tensor', int, float]) -> 'Tensor':\n",
" \"\"\"Addition: tensor + other\"\"\"\n",
" if isinstance(other, Tensor):\n",
" return Tensor(self._data + other._data)\n",
" else: # scalar\n",
" return Tensor(self._data + other)\n",
" \n",
" def __radd__(self, other: Union[int, float]) -> 'Tensor':\n",
" \"\"\"Reverse addition: scalar + tensor\"\"\"\n",
" return Tensor(other + self._data)\n",
" \n",
" def __sub__(self, other: Union['Tensor', int, float]) -> 'Tensor':\n",
" \"\"\"Subtraction: tensor - other\"\"\"\n",
" if isinstance(other, Tensor):\n",
" return Tensor(self._data - other._data)\n",
" else: # scalar\n",
" return Tensor(self._data - other)\n",
" \n",
" def __rsub__(self, other: Union[int, float]) -> 'Tensor':\n",
" \"\"\"Reverse subtraction: scalar - tensor\"\"\"\n",
" return Tensor(other - self._data)\n",
" \n",
" def __mul__(self, other: Union['Tensor', int, float]) -> 'Tensor':\n",
" \"\"\"Multiplication: tensor * other\"\"\"\n",
" if isinstance(other, Tensor):\n",
" return Tensor(self._data * other._data)\n",
" else: # scalar\n",
" return Tensor(self._data * other)\n",
" \n",
" def __rmul__(self, other: Union[int, float]) -> 'Tensor':\n",
" \"\"\"Reverse multiplication: scalar * tensor\"\"\"\n",
" return Tensor(other * self._data)\n",
" \n",
" def __truediv__(self, other: Union['Tensor', int, float]) -> 'Tensor':\n",
" \"\"\"Division: tensor / other\"\"\"\n",
" if isinstance(other, Tensor):\n",
" return Tensor(self._data / other._data)\n",
" else: # scalar\n",
" return Tensor(self._data / other)\n",
" \n",
" def __rtruediv__(self, other: Union[int, float]) -> 'Tensor':\n",
" \"\"\"Reverse division: scalar / tensor\"\"\"\n",
" return Tensor(other / self._data)"
]
},
{
"cell_type": "markdown",
"id": "ea197d3d",
"metadata": {
"cell_marker": "\"\"\""
},
"source": [
"### \ud83e\uddea Test Your Tensor Class\n",
"\n",
"Once you implement the Tensor class above, run this cell to test it:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e8a11c8f",
"metadata": {},
"outputs": [],
"source": [
"# Test basic tensor creation\n",
"print(\"Testing Tensor creation...\")\n",
"\n",
"try:\n",
" # Test scalar\n",
" t1 = Tensor(5)\n",
" print(f\"\u2705 Scalar: {t1} (shape: {t1.shape}, size: {t1.size})\")\n",
" \n",
" # Test vector\n",
" t2 = Tensor([1, 2, 3, 4])\n",
" print(f\"\u2705 Vector: {t2} (shape: {t2.shape}, size: {t2.size})\")\n",
" \n",
" # Test matrix\n",
" t3 = Tensor([[1, 2], [3, 4]])\n",
" print(f\"\u2705 Matrix: {t3} (shape: {t3.shape}, size: {t3.size})\")\n",
" \n",
" # Test numpy array\n",
" t4 = Tensor(np.array([1.0, 2.0, 3.0]))\n",
" print(f\"\u2705 Numpy: {t4} (shape: {t4.shape}, size: {t4.size})\")\n",
" \n",
" # Test dtype\n",
" t5 = Tensor([1, 2, 3], dtype='float32')\n",
" print(f\"\u2705 Dtype: {t5} (dtype: {t5.dtype})\")\n",
" \n",
" print(\"\\n\ud83c\udf89 All basic tests passed! Your Tensor class is working!\")\n",
" \n",
"except Exception as e:\n",
" print(f\"\u274c Error: {e}\")\n",
" print(\"Make sure to implement all the required methods!\")"
]
},
{
"cell_type": "markdown",
"id": "a025408c",
"metadata": {
"cell_marker": "\"\"\"",
"lines_to_next_cell": 1
},
"source": [
"## Step 2: Tensor Arithmetic Operations\n",
"\n",
"Now let's add the ability to perform mathematical operations on tensors. This is where tensors become powerful for ML!\n",
"\n",
"### Why Arithmetic Matters\n",
"- **Neural networks** perform millions of arithmetic operations\n",
"- **Gradients** require addition, multiplication, and other operations\n",
"- **Batch processing** needs element-wise operations\n",
"- **GPU acceleration** works with parallel arithmetic\n",
"\n",
"### Types of Operations\n",
"1. **Element-wise**: Add, subtract, multiply, divide\n",
"2. **Broadcasting**: Operations between different shapes\n",
"3. **Matrix operations**: Matrix multiplication (later)\n",
"4. **Reduction**: Sum, mean, max, min (later)\n",
"\n",
"Let's start with the basics!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2b3a5c33",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"#| export\n",
"def add_tensors(a: Tensor, b: Tensor) -> Tensor:\n",
" \"\"\"\n",
" Add two tensors element-wise.\n",
" \n",
" TODO: Implement element-wise addition of two tensors.\n",
" \n",
" APPROACH:\n",
" 1. Extract the numpy arrays from both tensors\n",
" 2. Use NumPy's + operator for element-wise addition\n",
" 3. Return a new Tensor with the result\n",
" \n",
" EXAMPLE:\n",
" add_tensors(Tensor([1, 2, 3]), Tensor([4, 5, 6])) \n",
" \u2192 Tensor([5, 7, 9])\n",
" \n",
" HINTS:\n",
" - Use a.data and b.data to get the numpy arrays\n",
" - NumPy handles broadcasting automatically\n",
" - Return Tensor(result) to wrap the result\n",
" \"\"\"\n",
" raise NotImplementedError(\"Student implementation required\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b3a85505",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"#| hide\n",
"#| export\n",
"def add_tensors(a: Tensor, b: Tensor) -> Tensor:\n",
" \"\"\"Add two tensors element-wise.\"\"\"\n",
" return Tensor(a.data + b.data)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "34940b0b",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"#| export\n",
"def multiply_tensors(a: Tensor, b: Tensor) -> Tensor:\n",
" \"\"\"\n",
" Multiply two tensors element-wise.\n",
" \n",
" TODO: Implement element-wise multiplication of two tensors.\n",
" \n",
" APPROACH:\n",
" 1. Extract the numpy arrays from both tensors\n",
" 2. Use NumPy's * operator for element-wise multiplication\n",
" 3. Return a new Tensor with the result\n",
" \n",
" EXAMPLE:\n",
" multiply_tensors(Tensor([1, 2, 3]), Tensor([4, 5, 6])) \n",
" \u2192 Tensor([4, 10, 18])\n",
" \n",
" HINTS:\n",
" - Use a.data and b.data to get the numpy arrays\n",
" - NumPy handles broadcasting automatically\n",
" - Return Tensor(result) to wrap the result\n",
" \"\"\"\n",
" raise NotImplementedError(\"Student implementation required\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fa876776",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"#| hide\n",
"#| export\n",
"def multiply_tensors(a: Tensor, b: Tensor) -> Tensor:\n",
" \"\"\"Multiply two tensors element-wise.\"\"\"\n",
" return Tensor(a.data * b.data)"
]
},
{
"cell_type": "markdown",
"id": "20c7fdef",
"metadata": {
"cell_marker": "\"\"\""
},
"source": [
"### \ud83e\uddea Test Your Arithmetic Operations"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1dd1a6b7",
"metadata": {},
"outputs": [],
"source": [
"# Test arithmetic operations\n",
"print(\"Testing tensor arithmetic...\")\n",
"\n",
"try:\n",
" # Test addition\n",
" a = Tensor([1, 2, 3])\n",
" b = Tensor([4, 5, 6])\n",
" c = add_tensors(a, b)\n",
" print(f\"\u2705 Addition: {a} + {b} = {c}\")\n",
" \n",
" # Test multiplication\n",
" d = multiply_tensors(a, b)\n",
" print(f\"\u2705 Multiplication: {a} * {b} = {d}\")\n",
" \n",
" # Test broadcasting (scalar + tensor)\n",
" scalar = Tensor(10)\n",
" e = add_tensors(scalar, a)\n",
" print(f\"\u2705 Broadcasting: {scalar} + {a} = {e}\")\n",
" \n",
" print(\"\\n\ud83c\udf89 All arithmetic tests passed!\")\n",
" \n",
"except Exception as e:\n",
" print(f\"\u274c Error: {e}\")\n",
" print(\"Make sure to implement add_tensors and multiply_tensors!\")"
]
},
{
"cell_type": "markdown",
"id": "a88c025e",
"metadata": {
"cell_marker": "\"\"\"",
"lines_to_next_cell": 1
},
"source": [
"## Step 3: Tensor Methods (Object-Oriented Approach)\n",
"\n",
"Now let's add methods to the Tensor class itself. This makes the API more intuitive and similar to PyTorch.\n",
"\n",
"### Why Methods Matter\n",
"- **Cleaner API**: `tensor.add(other)` instead of `add_tensors(tensor, other)`\n",
"- **Method chaining**: `tensor.add(other).multiply(scalar)`\n",
"- **Consistency**: Similar to PyTorch's tensor methods\n",
"- **Object-oriented**: Encapsulates operations with data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7c61792b",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"#| export\n",
"class Tensor:\n",
" \"\"\"\n",
" TinyTorch Tensor: N-dimensional array with ML operations.\n",
" \n",
" The fundamental data structure for all TinyTorch operations.\n",
" Wraps NumPy arrays with ML-specific functionality.\n",
" \"\"\"\n",
" \n",
" def __init__(self, data: Union[int, float, List, np.ndarray], dtype: Optional[str] = None):\n",
" \"\"\"\n",
" Create a new tensor from data.\n",
" \n",
" Args:\n",
" data: Input data (scalar, list, or numpy array)\n",
" dtype: Data type ('float32', 'int32', etc.). Defaults to auto-detect.\n",
" \"\"\"\n",
" # Convert input to numpy array\n",
" if isinstance(data, (int, float, np.number)):\n",
" # Handle Python and NumPy scalars\n",
" if dtype is None:\n",
" # Auto-detect type: int for integers, float32 for floats\n",
" if isinstance(data, int) or (isinstance(data, np.number) and np.issubdtype(type(data), np.integer)):\n",
" dtype = 'int32'\n",
" else:\n",
" dtype = 'float32'\n",
" self._data = np.array(data, dtype=dtype)\n",
" elif isinstance(data, list):\n",
" # Let NumPy auto-detect type, then convert if needed\n",
" temp_array = np.array(data)\n",
" if dtype is None:\n",
" # Keep NumPy's auto-detected type, but prefer common ML types\n",
" if np.issubdtype(temp_array.dtype, np.integer):\n",
" dtype = 'int32'\n",
" elif np.issubdtype(temp_array.dtype, np.floating):\n",
" dtype = 'float32'\n",
" else:\n",
" dtype = temp_array.dtype\n",
" self._data = temp_array.astype(dtype)\n",
" elif isinstance(data, np.ndarray):\n",
" self._data = data.astype(dtype or data.dtype)\n",
" else:\n",
" raise TypeError(f\"Cannot create tensor from {type(data)}\")\n",
" \n",
" @property\n",
" def data(self) -> np.ndarray:\n",
" \"\"\"Access underlying numpy array.\"\"\"\n",
" return self._data\n",
" \n",
" @property\n",
" def shape(self) -> Tuple[int, ...]:\n",
" \"\"\"Get tensor shape.\"\"\"\n",
" return self._data.shape\n",
" \n",
" @property\n",
" def size(self) -> int:\n",
" \"\"\"Get total number of elements.\"\"\"\n",
" return self._data.size\n",
" \n",
" @property\n",
" def dtype(self) -> np.dtype:\n",
" \"\"\"Get data type as numpy dtype.\"\"\"\n",
" return self._data.dtype\n",
" \n",
" def __repr__(self) -> str:\n",
" \"\"\"String representation.\"\"\"\n",
" return f\"Tensor({self._data.tolist()}, shape={self.shape}, dtype={self.dtype})\"\n",
" \n",
" def add(self, other: 'Tensor') -> 'Tensor':\n",
" \"\"\"\n",
" Add another tensor to this tensor.\n",
" \n",
" TODO: Implement tensor addition as a method.\n",
" \n",
" APPROACH:\n",
" 1. Use the add_tensors function you already implemented\n",
" 2. Or implement the addition directly using self._data + other._data\n",
" 3. Return a new Tensor with the result\n",
" \n",
" EXAMPLE:\n",
" Tensor([1, 2, 3]).add(Tensor([4, 5, 6])) \u2192 Tensor([5, 7, 9])\n",
" \n",
" HINTS:\n",
" - You can reuse add_tensors(self, other)\n",
" - Or implement directly: Tensor(self._data + other._data)\n",
" \"\"\"\n",
" raise NotImplementedError(\"Student implementation required\")\n",
" \n",
" def multiply(self, other: 'Tensor') -> 'Tensor':\n",
" \"\"\"\n",
" Multiply this tensor by another tensor.\n",
" \n",
" TODO: Implement tensor multiplication as a method.\n",
" \n",
" APPROACH:\n",
" 1. Use the multiply_tensors function you already implemented\n",
" 2. Or implement the multiplication directly using self._data * other._data\n",
" 3. Return a new Tensor with the result\n",
" \n",
" EXAMPLE:\n",
" Tensor([1, 2, 3]).multiply(Tensor([4, 5, 6])) \u2192 Tensor([4, 10, 18])\n",
" \n",
" HINTS:\n",
" - You can reuse multiply_tensors(self, other)\n",
" - Or implement directly: Tensor(self._data * other._data)\n",
" \"\"\"\n",
" raise NotImplementedError(\"Student implementation required\")\n",
" \n",
" # Arithmetic operators for natural syntax (a + b, a * b, etc.)\n",
" def __add__(self, other: Union['Tensor', int, float]) -> 'Tensor':\n",
" \"\"\"Addition: tensor + other\"\"\"\n",
" if isinstance(other, Tensor):\n",
" return Tensor(self._data + other._data)\n",
" else: # scalar\n",
" return Tensor(self._data + other)\n",
" \n",
" def __radd__(self, other: Union[int, float]) -> 'Tensor':\n",
" \"\"\"Reverse addition: scalar + tensor\"\"\"\n",
" return Tensor(other + self._data)\n",
" \n",
" def __sub__(self, other: Union['Tensor', int, float]) -> 'Tensor':\n",
" \"\"\"Subtraction: tensor - other\"\"\"\n",
" if isinstance(other, Tensor):\n",
" return Tensor(self._data - other._data)\n",
" else: # scalar\n",
" return Tensor(self._data - other)\n",
" \n",
" def __rsub__(self, other: Union[int, float]) -> 'Tensor':\n",
" \"\"\"Reverse subtraction: scalar - tensor\"\"\"\n",
" return Tensor(other - self._data)\n",
" \n",
" def __mul__(self, other: Union['Tensor', int, float]) -> 'Tensor':\n",
" \"\"\"Multiplication: tensor * other\"\"\"\n",
" if isinstance(other, Tensor):\n",
" return Tensor(self._data * other._data)\n",
" else: # scalar\n",
" return Tensor(self._data * other)\n",
" \n",
" def __rmul__(self, other: Union[int, float]) -> 'Tensor':\n",
" \"\"\"Reverse multiplication: scalar * tensor\"\"\"\n",
" return Tensor(other * self._data)\n",
" \n",
" def __truediv__(self, other: Union['Tensor', int, float]) -> 'Tensor':\n",
" \"\"\"Division: tensor / other\"\"\"\n",
" if isinstance(other, Tensor):\n",
" return Tensor(self._data / other._data)\n",
" else: # scalar\n",
" return Tensor(self._data / other)\n",
" \n",
" def __rtruediv__(self, other: Union[int, float]) -> 'Tensor':\n",
" \"\"\"Reverse division: scalar / tensor\"\"\"\n",
" return Tensor(other / self._data)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cf7db8ff",
"metadata": {
"lines_to_next_cell": 1
},
"outputs": [],
"source": [
"#| hide\n",
"#| export\n",
"class Tensor:\n",
" \"\"\"\n",
" TinyTorch Tensor: N-dimensional array with ML operations.\n",
" \n",
" The fundamental data structure for all TinyTorch operations.\n",
" Wraps NumPy arrays with ML-specific functionality.\n",
" \"\"\"\n",
" \n",
" def __init__(self, data: Union[int, float, List, np.ndarray], dtype: Optional[str] = None):\n",
" \"\"\"\n",
" Create a new tensor from data.\n",
" \n",
" Args:\n",
" data: Input data (scalar, list, or numpy array)\n",
" dtype: Data type ('float32', 'int32', etc.). Defaults to auto-detect.\n",
" \"\"\"\n",
" # Convert input to numpy array\n",
" if isinstance(data, (int, float, np.number)):\n",
" # Handle Python and NumPy scalars\n",
" if dtype is None:\n",
" # Auto-detect type: int for integers, float32 for floats\n",
" if isinstance(data, int) or (isinstance(data, np.number) and np.issubdtype(type(data), np.integer)):\n",
" dtype = 'int32'\n",
" else:\n",
" dtype = 'float32'\n",
" self._data = np.array(data, dtype=dtype)\n",
" elif isinstance(data, list):\n",
" # Let NumPy auto-detect type, then convert if needed\n",
" temp_array = np.array(data)\n",
" if dtype is None:\n",
" # Keep NumPy's auto-detected type, but prefer common ML types\n",
" if np.issubdtype(temp_array.dtype, np.integer):\n",
" dtype = 'int32'\n",
" elif np.issubdtype(temp_array.dtype, np.floating):\n",
" dtype = 'float32'\n",
" else:\n",
" dtype = temp_array.dtype\n",
" self._data = temp_array.astype(dtype)\n",
" elif isinstance(data, np.ndarray):\n",
" self._data = data.astype(dtype or data.dtype)\n",
" else:\n",
" raise TypeError(f\"Cannot create tensor from {type(data)}\")\n",
" \n",
" @property\n",
" def data(self) -> np.ndarray:\n",
" \"\"\"Access underlying numpy array.\"\"\"\n",
" return self._data\n",
" \n",
" @property\n",
" def shape(self) -> Tuple[int, ...]:\n",
" \"\"\"Get tensor shape.\"\"\"\n",
" return self._data.shape\n",
" \n",
" @property\n",
" def size(self) -> int:\n",
" \"\"\"Get total number of elements.\"\"\"\n",
" return self._data.size\n",
" \n",
" @property\n",
" def dtype(self) -> np.dtype:\n",
" \"\"\"Get data type as numpy dtype.\"\"\"\n",
" return self._data.dtype\n",
" \n",
" def __repr__(self) -> str:\n",
" \"\"\"String representation.\"\"\"\n",
" return f\"Tensor({self._data.tolist()}, shape={self.shape}, dtype={self.dtype})\"\n",
" \n",
" def add(self, other: 'Tensor') -> 'Tensor':\n",
" \"\"\"Add another tensor to this tensor.\"\"\"\n",
" return Tensor(self._data + other._data)\n",
" \n",
" def multiply(self, other: 'Tensor') -> 'Tensor':\n",
" \"\"\"Multiply this tensor by another tensor.\"\"\"\n",
" return Tensor(self._data * other._data)\n",
" \n",
" # Arithmetic operators for natural syntax (a + b, a * b, etc.)\n",
" def __add__(self, other: Union['Tensor', int, float]) -> 'Tensor':\n",
" \"\"\"Addition: tensor + other\"\"\"\n",
" if isinstance(other, Tensor):\n",
" return Tensor(self._data + other._data)\n",
" else: # scalar\n",
" return Tensor(self._data + other)\n",
" \n",
" def __radd__(self, other: Union[int, float]) -> 'Tensor':\n",
" \"\"\"Reverse addition: scalar + tensor\"\"\"\n",
" return Tensor(other + self._data)\n",
" \n",
" def __sub__(self, other: Union['Tensor', int, float]) -> 'Tensor':\n",
" \"\"\"Subtraction: tensor - other\"\"\"\n",
" if isinstance(other, Tensor):\n",
" return Tensor(self._data - other._data)\n",
" else: # scalar\n",
" return Tensor(self._data - other)\n",
" \n",
" def __rsub__(self, other: Union[int, float]) -> 'Tensor':\n",
" \"\"\"Reverse subtraction: scalar - tensor\"\"\"\n",
" return Tensor(other - self._data)\n",
" \n",
" def __mul__(self, other: Union['Tensor', int, float]) -> 'Tensor':\n",
" \"\"\"Multiplication: tensor * other\"\"\"\n",
" if isinstance(other, Tensor):\n",
" return Tensor(self._data * other._data)\n",
" else: # scalar\n",
" return Tensor(self._data * other)\n",
" \n",
" def __rmul__(self, other: Union[int, float]) -> 'Tensor':\n",
" \"\"\"Reverse multiplication: scalar * tensor\"\"\"\n",
" return Tensor(other * self._data)\n",
" \n",
" def __truediv__(self, other: Union['Tensor', int, float]) -> 'Tensor':\n",
" \"\"\"Division: tensor / other\"\"\"\n",
" if isinstance(other, Tensor):\n",
" return Tensor(self._data / other._data)\n",
" else: # scalar\n",
" return Tensor(self._data / other)\n",
" \n",
" def __rtruediv__(self, other: Union[int, float]) -> 'Tensor':\n",
" \"\"\"Reverse division: scalar / tensor\"\"\"\n",
" return Tensor(other / self._data)"
]
},
{
"cell_type": "markdown",
"id": "53b9cc39",
"metadata": {
"cell_marker": "\"\"\""
},
"source": [
"### \ud83e\uddea Test Your Tensor Methods"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7793077f",
"metadata": {},
"outputs": [],
"source": [
"# Test tensor methods\n",
"print(\"Testing tensor methods...\")\n",
"\n",
"try:\n",
" # Test method-based operations\n",
" a = Tensor([1, 2, 3])\n",
" b = Tensor([4, 5, 6])\n",
" \n",
" c = a.add(b)\n",
" print(f\"\u2705 Method addition: {a}.add({b}) = {c}\")\n",
" \n",
" d = a.multiply(b)\n",
" print(f\"\u2705 Method multiplication: {a}.multiply({b}) = {d}\")\n",
" \n",
" # Test method chaining\n",
" e = a.add(b).multiply(Tensor(2))\n",
" print(f\"\u2705 Method chaining: {a}.add({b}).multiply(2) = {e}\")\n",
" \n",
" print(\"\\n\ud83c\udf89 All method tests passed!\")\n",
" \n",
"except Exception as e:\n",
" print(f\"\u274c Error: {e}\")\n",
" print(\"Make sure to implement the add and multiply methods!\")"
]
},
{
"cell_type": "markdown",
"id": "5dad35d0",
"metadata": {
"cell_marker": "\"\"\""
},
"source": [
"## \ud83c\udfaf Module Summary\n",
"\n",
"Congratulations! You've built the foundation of TinyTorch:\n",
"\n",
"### What You've Accomplished\n",
"\u2705 **Tensor Creation**: Handle scalars, lists, and numpy arrays \n",
"\u2705 **Properties**: Access shape, size, and data type \n",
"\u2705 **Arithmetic**: Element-wise addition and multiplication \n",
"\u2705 **Methods**: Object-oriented API for operations \n",
"\u2705 **Testing**: Immediate feedback on your implementation \n",
"\n",
"### Key Concepts You've Learned\n",
"- **Tensors** are N-dimensional arrays with ML operations\n",
"- **NumPy integration** provides efficient computation\n",
"- **Element-wise operations** work on corresponding elements\n",
"- **Broadcasting** automatically handles different shapes\n",
"- **Object-oriented design** makes APIs intuitive\n",
"\n",
"### What's Next\n",
"In the next modules, you'll build on this foundation:\n",
"- **Layers**: Transform tensors with weights and biases\n",
"- **Activations**: Add nonlinearity to your networks\n",
"- **Networks**: Compose layers into complete models\n",
"- **Training**: Learn parameters with gradients and optimization\n",
"\n",
"### Real-World Connection\n",
"Your Tensor class is now ready to:\n",
"- Store neural network weights and biases\n",
"- Process batches of data efficiently\n",
"- Handle different data types (images, text, audio)\n",
"- Integrate with the rest of the TinyTorch ecosystem\n",
"\n",
"**Ready for the next challenge?** Let's move on to building layers that can transform your tensors!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cdd7d21e",
"metadata": {},
"outputs": [],
"source": [
"# Final verification\n",
"print(\"\\n\" + \"=\"*50)\n",
"print(\"\ud83c\udf89 TENSOR MODULE COMPLETE!\")\n",
"print(\"=\"*50)\n",
"print(\"\u2705 Tensor creation and properties\")\n",
"print(\"\u2705 Arithmetic operations\")\n",
"print(\"\u2705 Method-based API\")\n",
"print(\"\u2705 Comprehensive testing\")\n",
"print(\"\\n\ud83d\ude80 Ready to build layers in the next module!\") "
]
}
],
"metadata": {
"jupytext": {
"main_language": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}