Python Refresher
Deep Learning Foundations
Deep learning uses layered neural networks to learn hierarchical representations from data. Each layer transforms its input into a slightly more abstract representation, allowing the network to learn complex patterns that flat feature engineering cannot capture.
What Makes Deep Learning Different
Classical ML algorithms (Random Forest, SVMs) operate on hand-crafted feature vectors. Deep learning learns the feature representation itself — from raw pixels, raw audio, or raw sensor sequences — through end-to-end gradient-based optimization.
This is powerful when:
- Data is high-dimensional (images, audio, text, time-series)
- Human-engineered features miss important structural patterns
- Very large labeled datasets are available
This is unnecessary when:
- Dataset is small (< a few thousand labeled examples)
- Tabular data with clean, interpretable features
- Interpretability of decision process is required
- A tree ensemble already achieves target performance
Building Blocks
| Component | Role |
|---|---|
| Neuron | Computes z = wᵀx + b, then applies activation |
Weight w | Learned parameter controlling feature importance |
Bias b | Learned offset term |
| Activation function | Introduces non-linearity (ReLU, sigmoid, softmax, GELU) |
| Layer | A collection of neurons operating on the same input |
| Loss function | Measures prediction error (cross-entropy, MSE, CTC) |
| Optimizer | Updates weights to reduce loss (Adam, SGD, AdamW) |
Activation Functions
| Activation | Formula | Use When |
|---|---|---|
| ReLU | max(0, x) | Default hidden layers |
| Leaky ReLU | max(αx, x) | Dying ReLU prevention |
| Sigmoid | 1/(1+e^{-x}) | Binary output neuron |
| Softmax | e^{z_k}/Σe^{z_j} | Multi-class output layer |
| Tanh | (e^x - e^{-x})/(e^x + e^{-x}) | LSTM/RNN gates |
| GELU | x·Φ(x) | Transformers |
The Training Loop
Every deep learning training run executes this cycle:
- Forward pass — data flows through layers, producing a prediction
- Loss computation — loss function compares prediction to ground truth
- Backward pass (backpropagation) — computes gradient
∂L/∂wfor every weight using the chain rule - Optimizer step — updates each weight:
w ← w - η·∂L/∂w - Repeat for many mini-batches (iterations) across multiple epochs
Key Hyperparameters
| Hyperparameter | Effect |
|---|---|
| Learning rate η | Too large: unstable; too small: slow convergence |
| Batch size | Large → stable gradients, less noise; small → more regularization |
| Epochs | Full passes through training data |
| Optimizer | Adam is the default starting point |
Architecture 1: Dense Network (MLP)
Think of a game of telephone where each person in a line transforms a message slightly before passing it to the next person. Raw, complex input goes in at one end. By the time it reaches the last person, it has been condensed into a clear, simplified answer — a class label or a number. A Dense network does this with numbers, layer by layer, until the final layer gives the prediction.
A Multi-Layer Perceptron consists of fully connected (Dense) layers. Every neuron in one layer connects to every neuron in the next.
Use when: Tabular data, structured feature vectors, small classification/regression tasks.
Forward equations for a 3-layer MLP: h¹ = ReLU(W¹x + b¹) h² = ReLU(W²h¹ + b²) ŷ = softmax(W³h² + b³) (for multi-class)
1import tensorflow as tf2from tensorflow import keras3from sklearn.model_selection import train_test_split4from sklearn.datasets import make_classification5from sklearn.preprocessing import StandardScaler6import numpy as np78X, y = make_classification(n_samples=4000, n_features=20, n_classes=3,9 n_informative=12, random_state=42)10X = StandardScaler().fit_transform(X)11X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)1213model = keras.Sequential([14 keras.layers.Input(shape=(20,)),15 keras.layers.Dense(128, activation="relu"),16 keras.layers.BatchNormalization(),17 keras.layers.Dropout(0.3),18 keras.layers.Dense(64, activation="relu"),19 keras.layers.BatchNormalization(),20 keras.layers.Dropout(0.3),21 keras.layers.Dense(3, activation="softmax"),22])2324model.compile(25 optimizer=keras.optimizers.Adam(learning_rate=1e-3),26 loss="sparse_categorical_crossentropy",27 metrics=["accuracy"]28)29model.summary()3031history = model.fit(32 X_train, y_train,33 validation_data=(X_val, y_val),34 epochs=50, batch_size=64,35 callbacks=[keras.callbacks.EarlyStopping(patience=10, restore_best_weights=True)]36)Architecture 2: Convolutional Neural Network (CNN)
Imagine a team of inspectors scanning a newspaper photo with magnifying glasses. The first team looks only for straight edges. The second team combines those edges into corners and curves. The third team assembles the corners into faces and objects. Each layer reports its findings to the next, and the deeper you go, the more abstract and meaningful the pattern becomes.
CNNs apply learned filters that slide across the spatial dimensions of an input (image, 1D signal). Each filter learns to detect a specific local pattern — edges, textures, frequency components.
Use when: Image classification, object detection, signal pattern recognition, time-series with local structure.
Key Layers:
Conv2D(filters, kernel_size)— applies F filters over local regionsMaxPooling2D— downsamples, reducing spatial dimensions and computationGlobalAveragePooling2D— collapses spatial dimensions before Dense layersBatchNormalization— stabilizes training by normalizing layer inputs
Math — Convolution: (f * x)[i, j] = Σ_m Σ_n f[m, n] · x[i+m, j+n]
1# Example: image classification (e.g., 32x32 grayscale sensor images)2cnn = keras.Sequential([3 keras.layers.Input(shape=(32, 32, 1)),4 keras.layers.Conv2D(32, (3,3), activation="relu", padding="same"),5 keras.layers.BatchNormalization(),6 keras.layers.MaxPooling2D((2,2)),78 keras.layers.Conv2D(64, (3,3), activation="relu", padding="same"),9 keras.layers.BatchNormalization(),10 keras.layers.MaxPooling2D((2,2)),1112 keras.layers.Conv2D(128, (3,3), activation="relu", padding="same"),13 keras.layers.GlobalAveragePooling2D(),1415 keras.layers.Dense(64, activation="relu"),16 keras.layers.Dropout(0.4),17 keras.layers.Dense(10, activation="softmax"),18])1920cnn.compile(21 optimizer="adam",22 loss="sparse_categorical_crossentropy",23 metrics=["accuracy"]24)Architecture 3: Recurrent Networks (RNN, LSTM, GRU)
Imagine reading a mystery novel. What the detective discovers in Chapter 10 only makes sense because of clues from Chapters 1 through 9. A plain RNN tries to remember past chapters but forgets them quickly as the novel gets longer. An LSTM keeps a dedicated notebook — it selectively writes down what matters, crosses out what doesn’t, and holds onto critical plot points even 100 chapters later.
Recurrent networks maintain a hidden state across timesteps, allowing them to model sequential dependencies.
Use when: Time-series prediction, sensor data streams, text sequences, anomaly detection on sequential signals.
Why vanilla RNNs fail (Vanishing Gradient): During backpropagation through time, gradients are multiplied by weight matrices at every timestep. For long sequences, these multiplications shrink gradients exponentially → weights at early timesteps receive almost no learning signal.
LSTM gates (solution to vanishing gradient): The Long Short-Term Memory cell uses three learned gates:
- Forget gate
f_t = σ(W_f[h_{t-1}, x_t] + b_f)— what to discard from memory - Input gate
i_t = σ(W_i[h_{t-1}, x_t] + b_i)— what new info to write - Output gate
o_t = σ(W_o[h_{t-1}, x_t] + b_o)— what to expose to next layer
Cell state update: c_t = f_t ⊙ c_{t-1} + i_t ⊙ tanh(W_c[h_{t-1}, x_t] + b_c)
1# Time-series classification: sensor window → fault label2timesteps = 1003n_features = 64n_classes = 456lstm_model = keras.Sequential([7 keras.layers.Input(shape=(timesteps, n_features)),8 keras.layers.LSTM(128, return_sequences=True),9 keras.layers.LSTM(64, return_sequences=False),10 keras.layers.Dense(32, activation="relu"),11 keras.layers.Dropout(0.3),12 keras.layers.Dense(n_classes, activation="softmax"),13])1415lstm_model.compile(16 optimizer="adam",17 loss="sparse_categorical_crossentropy",18 metrics=["accuracy"]19)2021# GRU — Fewer parameters than LSTM, often comparable quality22gru_model = keras.Sequential([23 keras.layers.Input(shape=(timesteps, n_features)),24 keras.layers.GRU(128, return_sequences=True),25 keras.layers.GRU(64),26 keras.layers.Dense(n_classes, activation="softmax"),27])Architecture 4: Autoencoders
A student must summarise a 500-page textbook into a single A4 page — capturing only the most essential ideas. Then, using only that summary, they try to reconstruct the full textbook. If the reconstruction is accurate, the summary preserved what mattered. If it fails badly, the summary lost critical information. In machine learning, data that fails to reconstruct accurately is flagged as anomalous.
An autoencoder consists of an Encoder that compresses input into a compact latent vector z, and a Decoder that reconstructs the original input from z. Trained by minimizing reconstruction error.
Use when:
- Dimensionality reduction — latent vector is the compressed representation
- Anomaly detection — normal data reconstructs well; anomalies have high reconstruction error
- Denoising — train on (noisy input, clean output) pairs
- Pre-training for downstream tasks
1# Autoencoder for anomaly detection on tabular sensor data2input_dim = 203latent_dim = 445# Encoder6encoder_input = keras.Input(shape=(input_dim,))7x = keras.layers.Dense(64, activation="relu")(encoder_input)8x = keras.layers.Dense(32, activation="relu")(x)9latent = keras.layers.Dense(latent_dim, activation="relu", name="latent")(x)1011# Decoder12x = keras.layers.Dense(32, activation="relu")(latent)13x = keras.layers.Dense(64, activation="relu")(x)14output = keras.layers.Dense(input_dim, activation="linear")(x)1516autoencoder = keras.Model(encoder_input, output)17autoencoder.compile(optimizer="adam", loss="mse")1819# Train on NORMAL data only20# autoencoder.fit(X_normal, X_normal, epochs=50, validation_split=0.1)2122# Detect anomalies: high reconstruction error = anomaly23# recon_error = np.mean(np.abs(X_test - autoencoder.predict(X_test)), axis=1)24# anomalies = recon_error > thresholdRegularization Techniques
| Technique | How it Works | When to Apply |
|---|---|---|
| Dropout | Randomly zeros activations during training | Dense layers, CNNs |
| Batch Normalization | Normalizes layer inputs per mini-batch | After Conv/Dense layers |
| L2 Weight Decay | Penalizes large weights | When overfitting on small data |
| Early Stopping | Halts training when val loss stops improving | Every training run |
| Data Augmentation | Creates new examples via random transforms | CNNs with limited data |
1# Using all three regularization techniques together2model = keras.Sequential([3 keras.layers.Dense(256, activation="relu",4 kernel_regularizer=keras.regularizers.l2(1e-4)),5 keras.layers.BatchNormalization(),6 keras.layers.Dropout(0.4),7 keras.layers.Dense(128, activation="relu"),8 keras.layers.Dropout(0.3),9 keras.layers.Dense(3, activation="softmax"),10])Diagnosing Training Problems
| Symptom | Likely Cause | Fix |
|---|---|---|
| Train loss decreases, val loss increases | Overfitting | More dropout, L2 reg, early stopping, more data |
| Both train and val loss stay high | Underfitting | Larger model, lower regularization, more epochs |
| Loss explodes (NaN) | Learning rate too high | Reduce lr, add gradient clipping |
| Loss barely moves | Learning rate too low | Increase lr, use learning rate finder |
| Gradients all near zero | Vanishing gradients | Use LSTM/GRU, batch norm, residual connections |
1# Gradient clipping — essential for RNNs2optimizer = keras.optimizers.Adam(learning_rate=1e-3, clipnorm=1.0)Learning Rate Scheduling
The learning rate is the most important hyperparameter. Constant learning rates are rarely optimal — starting high and decaying helps convergence.
1# Reduce learning rate when validation loss plateaus2lr_scheduler = keras.callbacks.ReduceLROnPlateau(3 monitor="val_loss", factor=0.5,4 patience=5, min_lr=1e-6, verbose=15)67# Cosine decay schedule8cosine_decay = keras.optimizers.schedules.CosineDecay(9 initial_learning_rate=1e-3,10 decay_steps=100011)12optimizer = keras.optimizers.Adam(learning_rate=cosine_decay)Deploying to Edge Devices (TensorFlow Lite)
Embedded devices (ESP32-S3, STM32H7, Raspberry Pi) cannot run full TensorFlow models. TensorFlow Lite converts and compresses models for constrained hardware.
1import tensorflow as tf23# Step 1: Convert to TFLite4converter = tf.lite.TFLiteConverter.from_keras_model(model)5tflite_model = converter.convert()67# Step 2: Save8with open("model.tflite", "wb") as f:9 f.write(tflite_model)1011# Step 3: Quantize (float32 → int8) — reduces model size 4x, speeds up inference12converter.optimizations = [tf.lite.Optimize.DEFAULT]13quantized_model = converter.convert()1415with open("model_int8.tflite", "wb") as f:16 f.write(quantized_model)1718# Step 4: Run inference using the TFLite interpreter19interpreter = tf.lite.Interpreter(model_path="model_int8.tflite")20interpreter.allocate_tensors()2122input_details = interpreter.get_input_details()23output_details = interpreter.get_output_details()2425import numpy as np26test_input = np.array([X_val[0]], dtype=np.float32)27interpreter.set_tensor(input_details[0]["index"], test_input)28interpreter.invoke()29output = interpreter.get_tensor(output_details[0]["index"])30print("Prediction:", output)TFLite Micro for Microcontrollers
For deploying onto bare-metal MCUs like ESP32 or STM32 (no OS), use TensorFlow Lite for Microcontrollers. The quantized .tflite model is embedded as a C byte array in your firmware and runs entirely in on-chip SRAM.
Checklist Before Training
- [ ] Problem is clearly defined with a measurable target metric
- [ ] Dataset is split correctly (no leakage from validation/test into training)
- [ ] Data is normalized/scaled before feeding into the network
- [ ] Baseline ML model performance is recorded for comparison
- [ ] Architecture starts small — grow only if needed
- [ ] Validation curves are being monitored per epoch
- [ ] Early stopping callback is active
- [ ] Final evaluation is on a held-out test set that was never touched

