Skip to content

NumPy Essentials

NumPy is the foundation of all numerical computing in Python. Every ML framework (including Neurogebra) uses NumPy under the hood.


What is NumPy?

NumPy provides fast array operations. Instead of using slow Python loops, NumPy operates on entire arrays at once.

import numpy as np

Convention

Everyone imports NumPy as np. This is a universal convention.


Creating Arrays

import numpy as np

# From a list
a = np.array([1, 2, 3, 4, 5])
print(a)        # [1 2 3 4 5]
print(type(a))  # <class 'numpy.ndarray'>

# Array of zeros
zeros = np.zeros(5)
print(zeros)  # [0. 0. 0. 0. 0.]

# Array of ones
ones = np.ones(5)
print(ones)  # [1. 1. 1. 1. 1.]

# Range of values
x = np.arange(0, 10, 2)  # start, stop, step
print(x)  # [0 2 4 6 8]

# Evenly spaced values (very common for plotting)
x = np.linspace(0, 1, 5)  # start, stop, num_points
print(x)  # [0.   0.25 0.5  0.75 1.  ]

# Random values
random_data = np.random.randn(5)  # 5 random numbers from normal distribution
print(random_data)

Array Properties

a = np.array([[1, 2, 3],
              [4, 5, 6]])

print(a.shape)   # (2, 3) — 2 rows, 3 columns
print(a.ndim)    # 2 — dimensions
print(a.size)    # 6 — total elements
print(a.dtype)   # int64 — data type

Shapes in ML

In ML, you'll constantly deal with shapes:

  • (100,) — 100 data points (1D)
  • (100, 10) — 100 samples, 10 features (2D)
  • (32, 28, 28) — 32 images of 28×28 pixels (3D)

Array Operations (Element-wise)

NumPy operations work on every element simultaneously:

a = np.array([1, 2, 3, 4, 5])

# Arithmetic
print(a + 10)     # [11 12 13 14 15]
print(a * 2)      # [ 2  4  6  8 10]
print(a ** 2)     # [ 1  4  9 16 25]

# Between arrays
b = np.array([10, 20, 30, 40, 50])
print(a + b)      # [11 22 33 44 55]
print(a * b)      # [ 10  40  90 160 250]

Why this matters: In ML, we compute things like predictions - targets on thousands of data points at once.


Mathematical Functions

x = np.array([-2, -1, 0, 1, 2])

# Common functions
print(np.abs(x))      # [2 1 0 1 2]
print(np.exp(x))      # [0.135 0.368 1.000 2.718 7.389]
print(np.log(np.abs(x) + 1))  # logarithm (add 1 to avoid log(0))
print(np.sqrt(np.abs(x)))     # [1.414 1.000 0.000 1.000 1.414]

# Implement ReLU with NumPy
print(np.maximum(0, x))  # [0 0 0 1 2] — ReLU!

# Implement Sigmoid with NumPy
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

print(sigmoid(x))  # [0.119 0.269 0.5 0.731 0.881]

Statistical Functions

data = np.array([85, 92, 78, 95, 88, 76, 91])

print(np.mean(data))    # 86.43 — average
print(np.std(data))     # 6.90  — standard deviation
print(np.min(data))     # 76
print(np.max(data))     # 95
print(np.sum(data))     # 605
print(np.median(data))  # 88.0

Reshaping Arrays

a = np.array([1, 2, 3, 4, 5, 6])

# Reshape to 2x3
b = a.reshape(2, 3)
print(b)
# [[1 2 3]
#  [4 5 6]]

# Reshape to column vector
c = a.reshape(-1, 1)  # -1 means "figure it out"
print(c.shape)  # (6, 1)

# Flatten back to 1D
d = b.flatten()
print(d)  # [1 2 3 4 5 6]

Indexing and Slicing

a = np.array([10, 20, 30, 40, 50])

# Basic indexing
print(a[0])     # 10
print(a[-1])    # 50

# Slicing
print(a[1:4])   # [20 30 40]
print(a[:3])    # [10 20 30]

# Boolean indexing (filtering)
print(a[a > 25])   # [30 40 50]
print(a[a % 20 == 0])  # [20 40]

# 2D indexing
matrix = np.array([[1, 2, 3],
                    [4, 5, 6],
                    [7, 8, 9]])
print(matrix[0, 0])    # 1 (row 0, col 0)
print(matrix[1, :])    # [4 5 6] (entire row 1)
print(matrix[:, 2])    # [3 6 9] (entire column 2)

The Dot Product

The dot product is the most important operation in neural networks:

# Vectors
weights = np.array([0.5, -0.3, 0.8])
inputs = np.array([1.0, 2.0, 3.0])

# Dot product: sum of element-wise multiplication
result = np.dot(weights, inputs)
print(result)  # 0.5*1 + (-0.3)*2 + 0.8*3 = 1.5

# This is exactly what a neuron does!
bias = 0.1
output = np.dot(weights, inputs) + bias
print(output)  # 1.6

Neuron = Dot Product + Bias + Activation

Every neuron in a neural network computes: \(output = activation(weights \cdot inputs + bias)\)


Generating Synthetic Data

This is very useful for testing ML models:

import numpy as np

# Linear data: y = 2x + 1 + noise
np.random.seed(42)  # For reproducibility
X = np.linspace(0, 10, 100)
noise = np.random.normal(0, 0.5, 100)
y = 2 * X + 1 + noise

print(f"X shape: {X.shape}")  # (100,)
print(f"y shape: {y.shape}")  # (100,)
print(f"X range: [{X.min():.1f}, {X.max():.1f}]")
print(f"y range: [{y.min():.1f}, {y.max():.1f}]")

NumPy in Neurogebra

Neurogebra expressions accept NumPy arrays:

from neurogebra import MathForge
import numpy as np

forge = MathForge()
relu = forge.get("relu")

# Evaluate on an entire array at once!
x = np.array([-3, -1, 0, 1, 3])
result = relu.eval(x=x)
print(result)  # [0 0 0 1 3]

Try It Yourself!

Exercise

import numpy as np

# 1. Create a dataset: y = 3x² - 2x + 1
X = np.linspace(-5, 5, 50)
y = 3 * X**2 - 2 * X + 1

# 2. Compute statistics
print(f"Mean of y: {np.mean(y):.2f}")
print(f"Std of y: {np.std(y):.2f}")

# 3. Implement sigmoid using NumPy
sigmoid = 1 / (1 + np.exp(-X))
print(f"Sigmoid range: [{sigmoid.min():.3f}, {sigmoid.max():.3f}]")

Next: Data Handling with Python →