Neural Networks From First Principles: A Visual Deep Dive
Back to all articles
Deep Learning
35 min read10 min read

Neural Networks From First Principles: A Visual Deep Dive

Everything you need to understand neural networks, from individual neurons to deep architectures. Rich visuals, interactive examples, and real code.

Debasish Maji
Debasish Maji
AI Engineering Lead
March 10, 2026
Neural NetworksDeep LearningMathematicsBackpropagation

Why This Guide Exists

Neural networks power everything from image recognition to ChatGPT. Yet most explanations either:

  • Jump straight to code without building intuition
  • Use complex math that obscures the simple ideas
  • Skip crucial details about WHY things work

This guide builds your understanding from the ground up. By the end, you'll truly understand neural networks.

•••

Part 1: The Big Picture

What Are Neural Networks Trying to Do?

At their core, neural networks are function approximators. They learn a function from examples.

Code
f(input) = output

Examples:
f(image) = "cat" or "dog"
f(email) = "spam" or "not spam"  
f(text) = next word
f(past prices) = future price

The magic: we don't write the function manually. The network LEARNS it from examples.

The Key Insight: Everything is Numbers

Neural networks only understand numbers. So first, convert everything to numbers:

Code
Image (28x28 pixels) → [0.1, 0.0, 0.9, 0.8, ...] (784 numbers)
Text "hello"         → [0.2, 0.5, 0.1, ...]       (embedding vector)
Category "cat"       → [1, 0, 0]                   (one-hot vector)

Then the neural network is just: numbers in → computation → numbers out

•••

Part 2: The Simplest Neural Network

A Single Neuron

The simplest neural network is one neuron:

Code
Inputs          Weights        Sum + Activation     Output
     
     x₁ ─────→ w₁ ───┐
                      │
     x₂ ─────→ w₂ ───┼──→ [Σ + bias] ──→ [activation] ──→ output
                      │
     x₃ ─────→ w₃ ───┘


Step 1: Weighted sum = w₁x₁ + w₂x₂ + w₃x₃ + bias
Step 2: Output = activation(weighted_sum)

Concrete Example: Predicting House Prices

Python
# Inputs
x1 = 2000  # square feet
x2 = 3     # bedrooms
x3 = 2     # bathrooms

# Learned weights (how important each feature is)
w1 = 100   # $/sqft
w2 = 20000 # $/bedroom
w3 = 15000 # $/bathroom
bias = 50000  # base price

# Calculation
weighted_sum = 100*2000 + 20000*3 + 15000*2 + 50000
             = 200000 + 60000 + 30000 + 50000
             = 340000

# Output: predicted price = $340,000

Why Weights and Bias?

Weights = how much each input matters

  • High weight → input has big impact
  • Low weight → input barely matters
  • Negative weight → input reduces output

Bias = the starting point regardless of inputs

  • Like the y-intercept in y = mx + b
  • Lets the neuron activate even when all inputs are zero
•••

Part 3: Why Do We Need Activation Functions?

The Problem with Linear Operations

Without activation functions, stacking layers is pointless:

Code
Layer 1: y₁ = w₁x + b₁
Layer 2: y₂ = w₂y₁ + b₂ = w₂(w₁x + b₁) + b₂ = (w₂w₁)x + (w₂b₁ + b₂)

This is still just: y = wx + b (a straight line!)

Multiple linear layers = one linear layer. No matter how many layers, you can only learn linear relationships.

Non-Linearity is the Secret

Activation functions add non-linearity. Now networks can learn curves, not just lines:

Code
Without activation:
     ┌────────────────────────────────────
     ��       Only straight lines
     │      ──────────────────────
     └────────────────────────────────────

With activation:
     ┌────────────────────────────────────
     │         Any shape!
     │      ~~~~~~~~~~~~~~~~~~~~
     │   ╱╲                 ╱╲
     │  ╱  ╲_______________╱  ╲
     └────────────────────────────────────

Common Activation Functions

ReLU (Rectified Linear Unit) - Most Common

Code
ReLU(x) = max(0, x)

If x > 0: output = x
If x ≤ 0: output = 0

     │
   3 │      ╱
   2 │    ╱
   1 │  ╱
  ───┼────────────
  -2 │0  1  2  3
     │

Why ReLU?

  • Simple and fast
  • Doesn't have vanishing gradient problem (for positive values)
  • Works great in practice

Sigmoid - For Probabilities

Code
sigmoid(x) = 1 / (1 + e^(-x))

Always outputs between 0 and 1

     │
   1 │        ────────
  .5 │      ╱
   0 │──────
     └────────────────
      -4  -2   0   2   4

Why sigmoid?

  • Output is a probability (0 to 1)
  • Good for binary classification

Softmax - For Multiple Classes

Code
softmax(xᵢ) = e^xᵢ / Σe^xⱼ

Converts scores to probabilities that sum to 1

Input: [2.0, 1.0, 0.5]
Output: [0.59, 0.24, 0.17]  (sums to 1.0)
•••

Part 4: Stacking Layers

Why Multiple Layers?

One layer can only learn simple patterns. More layers = more complex patterns:

Code
Layer 1: Learns edges (simple patterns)
    │
    ▼
Layer 2: Learns shapes (combinations of edges)
    │
    ▼
Layer 3: Learns parts (combinations of shapes)
    │
    ▼
Layer 4: Learns objects (combinations of parts)

Image Example:

Code
Layer 1: Detects edges       │▁│  ▁▁▁  ╱╲
Layer 2: Detects shapes      ○  □  △
Layer 3: Detects parts       👁  👃  👄
Layer 4: Detects faces       🙂  🙁  😮

Architecture of a Deep Network

Code
Input Layer        Hidden Layers           Output Layer
(raw data)      (learned features)        (prediction)

   x₁ ─────○─────○────���○─────○
           │╲   ╱│╲   ╱│
   x₂ ─────○──╳──○──╳──○─────○──────→ output
           │╱   ╲│╱   ╲│
   x₃ ─────○─────○─────○─────○

   784     128     64      32      10
 neurons  neurons neurons neurons neurons

Each connection has a weight. Each neuron has a bias.

Total parameters in this network:

  • Layer 1→2: 784 × 128 + 128 biases = 100,480
  • Layer 2→3: 128 × 64 + 64 biases = 8,256
  • Layer 3→4: 64 × 32 + 32 biases = 2,080
  • Layer 4→5: 32 × 10 + 10 biases = 330

Total: 111,146 learnable parameters

•••

Part 5: How Networks Learn - Forward Pass

Step-by-Step Forward Pass

Let's trace through a simple network:

Python
# Input: 2 features
x = [0.5, 0.8]

# Layer 1: 2 neurons
W1 = [[0.1, 0.2],    # weights to neuron 1
      [0.3, 0.4]]    # weights to neuron 2
b1 = [0.1, 0.2]      # biases

# Calculate Layer 1 outputs
z1 = [0.1*0.5 + 0.2*0.8 + 0.1,    # = 0.05 + 0.16 + 0.1 = 0.31
      0.3*0.5 + 0.4*0.8 + 0.2]    # = 0.15 + 0.32 + 0.2 = 0.67

a1 = [ReLU(0.31), ReLU(0.67)]     # = [0.31, 0.67]

# Layer 2: 1 output neuron
W2 = [[0.5, 0.6]]
b2 = [0.3]

# Calculate final output
z2 = [0.5*0.31 + 0.6*0.67 + 0.3]  # = 0.155 + 0.402 + 0.3 = 0.857
output = sigmoid(0.857)            # = 0.702

# Network predicts: 70.2% probability
•••

Part 6: How Networks Learn - Backpropagation

The Core Idea

Backpropagation answers: "How much did each weight contribute to the error?"

Code
1. Forward pass: compute prediction
2. Compute loss: how wrong was the prediction?
3. Backward pass: compute gradients (how to adjust each weight)
4. Update weights: nudge in the right direction
5. Repeat thousands of times

Loss Functions

Loss = how wrong is the prediction?

Mean Squared Error (MSE) - For regression:

Code
Loss = (prediction - actual)²

Prediction: 340,000
Actual: 350,000
Loss: (340000 - 350000)² = 100,000,000

Cross-Entropy - For classification:

Code
Loss = -Σ actual × log(prediction)

Actual: [0, 1, 0] (class 2)
Prediction: [0.1, 0.7, 0.2]
Loss = -[0×log(0.1) + 1×log(0.7) + 0×log(0.2)]
     = -log(0.7)
     = 0.357

The Chain Rule: Heart of Backpropagation

How does weight w affect the loss?

Code
Loss depends on output
Output depends on z
z depends on weight w

So: dLoss/dw = (dLoss/dOutput) × (dOutput/dz) × (dz/dw)
Diagram
flowchart LR subgraph Forward["Forward Pass →"] W[Weight w] --> Z1[z = wx + b] Z1 --> A1[Activation] A1 --> Z2[z = wa + b] Z2 --> A2[Output] A2 --> L[Loss] end subgraph Backward["����� Backward Pass"] L2[dL/dLoss] -.-> A3[dL/da] A3 -.-> Z3[dL/dz] Z3 -.-> W2[dL/dw] end style W fill:#14b8a6,color:#fff style L fill:#ef4444,color:#fff style W2 fill:#22c55e,color:#fff

Gradient Descent

Once we have gradients, update weights:

Python
learning_rate = 0.01

for each weight w:
    gradient = dLoss/dw
    w = w - learning_rate × gradient

Intuition:

  • Gradient tells us which direction increases loss
  • We go the OPPOSITE direction to decrease loss
  • Learning rate controls how big a step we take
Code
Loss
          │╲
          │ ╲
          │  ╲      ← gradient points up-right
          │   ●     ← we are here
          │    ╲
          │     ●   ← we want to go here (lower loss)
          └──────────
              Weight

Move opposite to gradient → loss decreases!
•••

Part 7: Training in Practice

Batch Training

Processing one example at a time is slow and noisy. Instead, use batches:

Python
batch_size = 32

for epoch in range(num_epochs):
    for batch in get_batches(training_data, batch_size):
        # Forward pass on 32 examples
        predictions = model(batch.inputs)
        
        # Average loss over 32 examples
        loss = compute_loss(predictions, batch.targets)
        
        # Gradients averaged over batch
        gradients = compute_gradients(loss)
        
        # Update weights
        update_weights(gradients)

Why batches?

  • Faster: GPUs process many examples in parallel
  • More stable: Averaged gradients are less noisy
  • Better generalization: Some noise helps escape bad solutions

The Training Loop Visualized

Code
Epoch 1                    Epoch 100
     
Loss │●                    Loss │
  10 │  ●                    10 │
   8 │    ●                   8 │
   6 │      ●  ●              6 │
   4 │          ●  ●          4 │
   2 │              ● ● ●     2 │● ● ● ● ● ●
     └────────────────────      └──────���─────────
        Iterations                 Iterations
        
    (training)                (converged - done!)
•••

Part 8: Common Architectures

Fully Connected (Dense) Networks

Every neuron connects to every neuron in next layer.

  • Good for: tabular data, simple classification
  • Parameters: lots (n × m for each layer)

Convolutional Neural Networks (CNNs)

Specialized for images. Neurons only connect locally.

Code
Image: 224×224×3 (RGB)
    ↓
Conv Layer: slide 3×3 filter across image
    ↓
Pool Layer: shrink to reduce size
    ↓
More Conv + Pool layers
    ↓
Fully connected layers
    ��
Output: class probabilities

Recurrent Neural Networks (RNNs)

Specialized for sequences. Hidden state carries information forward.

Code
x₁ → [h₁] → x₂ → [h₂] → x₃ → [h₃] → output
       ↓          ↓          ↓
    (pass hidden state forward)

Transformers (Modern NLP)

No recurrence - use attention instead.

  • All positions process in parallel
  • Self-attention lets each token see all others
  • Powers GPT, BERT, ChatGPT
•••

Part 9: Key Takeaways

  1. 1Neural networks are function approximators that learn from examples
  1. 2Neurons compute weighted sums + activation to introduce non-linearity
  1. 3Depth enables learning hierarchical features - simple → complex
  1. 4Backpropagation uses the chain rule to compute how each weight affects loss
  1. 5Gradient descent iteratively improves weights by going opposite to the gradient
  1. 6Architecture matters - CNNs for images, Transformers for text, Dense for tabular
•••

Interview Quick Reference

Q: What is a neural network? "A neural network is a function approximator composed of layers of neurons. Each neuron computes a weighted sum of inputs, adds a bias, and applies a non-linear activation function. The network learns by adjusting weights to minimize a loss function using gradient descent and backpropagation."

Q: Why do we need activation functions? "Without activation functions, stacking layers is equivalent to a single linear transformation. Activation functions introduce non-linearity, enabling networks to learn complex, non-linear relationships in data."

Q: How does backpropagation work? "Backpropagation computes gradients of the loss with respect to each weight using the chain rule. It propagates error backward from the output layer, computing how much each weight contributed to the error. These gradients are then used to update weights via gradient descent."

Found this helpful?

Share it with others who might benefit

TweetShare

Related articles