Skip to content

Latest commit

 

History

History
1054 lines (801 loc) · 24.7 KB

File metadata and controls

1054 lines (801 loc) · 24.7 KB

Viva Voce Questions & Answers

Part 1: Fundamental Concepts

Q1: What is a Multilayer Perceptron and how does it differ from a single-layer perceptron?

Answer: A Multilayer Perceptron (MLP) is a neural network with multiple layers of neurons connected in a feedforward manner.

Key Differences:

Single-Layer Multi-Layer
Linear decision boundaries Non-linear boundaries
Cannot solve XOR problem Can solve XOR problem
Limited approximation power Universal approximator
No hidden layers Multiple hidden layers
Single computation step Multiple computation steps

In our implementation:

  • Input Layer: 784 neurons (28×28 flattened)
  • Hidden Layer: 128 neurons with ReLU activation
  • Output Layer: 10 neurons with Softmax
  • Can learn complex patterns in Fashion-MNIST

Q2: Explain the backpropagation algorithm and why it's important.

Answer:

Backpropagation computes gradients of the loss function with respect to all weights by efficiently applying the chain rule.

Algorithm Steps:

  1. Forward Pass: Compute predictions y = f(x)
  2. Compute Loss: L = loss(y_true, y_pred)
  3. Backward Pass: Compute ∂L/∂w for each weight layer-by-layer
  4. Weight Update: w = w - lr × ∂L/∂w

Why Important:

  • Enables gradient-based optimization
  • Efficient computation using dynamic programming
  • Scalable to deep networks
  • Enables feature learning in hidden layers

Example in our MLP:

∂L/∂W² = (1/m) × a¹ᵀ(ŷ - y)    [Output layer]
∂L/∂W¹ = (1/m) × Xᵀ(∂L/∂z¹)    [Hidden layer]

Q3: What is the difference between forward and backward propagation?

Answer:

Forward Propagation Backward Propagation
Computing output from input Computing gradients
z = Wx + b; a = σ(z) ∂L/∂W from output to input
Output: Predictions Output: Gradients
Used for inference Used for training
One direction Reverse direction
O(n) complexity O(n) complexity

Mathematical Representation:

Forward: $y = f(f(f(x)))$

Backward: $\frac{\partial L}{\partial W_1} = \frac{\partial L}{\partial y} \cdot \frac{\partial y}{\partial z_2} \cdot \frac{\partial z_2}{\partial a_1} \cdot \frac{\partial a_1}{\partial z_1} \cdot \frac{\partial z_1}{\partial W_1}$


Part 2: MLP Implementation

Q4: What is ReLU activation and why did you use it instead of sigmoid?

Answer:

ReLU (Rectified Linear Unit): $$f(x) = \max(0, x)$$

Comparison:

Property ReLU Sigmoid
Formula max(0, x) 1/(1+e^-x)
Derivative 1 (x>0), 0 (x≤0) σ(1-σ)
Range [0, ∞) [0, 1]
Convergence Speed Fast Slow
Vanishing Gradient No Yes
Computational Cost Low High
Sparsity Yes (if x<0) No

Why ReLU in Hidden Layers:

  1. Mitigates Vanishing Gradient: Derivative = 1 for positive inputs
  2. Computational Efficiency: Simple max operation
  3. Sparse Activation: Many neurons output 0 (sparse representation)
  4. Faster Convergence: Networks train 6x faster with ReLU

Why Softmax in Output:

  • Produces probability distribution
  • Suitable for multi-class classification
  • Satisfies: Σp = 1, all p ≥ 0

Q5: How does cross-entropy loss work for classification?

Answer:

Formula: $$L = -\frac{1}{N}\sum_{i=1}^{N} \sum_{j=1}^{C} y_i^j \log(\hat{y}_i^j)$$

Where:

  • N: Batch size
  • C: Number of classes
  • y: One-hot encoded true label
  • ŷ: Predicted probability from softmax

Example:

True label: 3 (class 3) → [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
Predictions:    → [0.01, 0.02, 0.05, 0.8, 0.02, 0.02, 0.03, 0.02, 0.02, 0.01]

Loss = -log(0.8) ≈ 0.223

Why Cross-Entropy:

  1. Probabilistic interpretation: Maximum likelihood estimation
  2. Suitable derivative: ∂L/∂z = ŷ - y (simple form)
  3. Penalizes confidence: Wrong confident predictions get high loss
  4. Standard choice: Industry standard for classification

Q6: Explain weight initialization and its importance.

Answer:

Problem:

  • All-zero initialization: All neurons learn identical features
  • Random large init: Exploding gradients, divergence
  • Random small init: Vanishing gradients, slow learning

Xavier Initialization (Used in our code): $$W \sim \text{Uniform}\left(-\sqrt{\frac{6}{n_{in} + n_{out}}}, \sqrt{\frac{6}{n_{in} + n_{out}}}\right)$$

Purpose:

  • Maintains variance across layers
  • Prevents vanishing/exploding gradients
  • Enables faster convergence
  • Formula accounts for layer sizes

In our MLP:

limit = np.sqrt(6 / (in_features + out_features))
W = np.random.uniform(-limit, limit, (in_features, out_features))

Results:

  • Convergence in ~30 epochs
  • Stable training without divergence
  • Consistent performance across runs

Part 3: Autoencoder

Q7: What is an autoencoder and what are its applications?

Answer:

Definition: An unsupervised neural network that learns to:

  1. Compress input to lower-dimensional latent code
  2. Reconstruct input from compressed code

Architecture:

Input → Encoder (784→256→32) → Latent → Decoder (32→256→784) → Output

Applications:

  1. Dimensionality Reduction

    • Compress 784D to 32D (95% reduction)
    • Preserve essential information
    • Speed up downstream tasks
  2. Feature Learning

    • Learn representations without labels
    • Latent code = learned features
    • Use with classifiers
  3. Outlier Detection

    • Normal samples: Low reconstruction error
    • Anomalies: High reconstruction error
    • Threshold = mean + 2×std
  4. Denoising

    • Train on noisy images
    • Learn to denoise
    • Useful for signal processing
  5. Data Generation

    • Sample from latent space
    • Generate variations of data
    • Similar to variational autoencoder

Q8: How is the autoencoder loss function different from MLP?

Answer:

MLP Autoencoder
Loss: Cross-Entropy Loss: Mean Squared Error
Purpose: Classification Purpose: Reconstruction
Target: Class labels Target: Input itself
Output: Probabilities Output: Continuous values
Formula: -Σy log(ŷ) Formula: Σ(X - X̂)²

MSE Loss Details: $$L = \frac{1}{N} \sum_{i=1}^{N} |X_i - \hat{X}_i|^2$$

Why MSE for Reconstruction:

  1. Pixel-level error: Measures reconstruction quality
  2. Gradient property: ∂L/∂X̂ = 2(X̂ - X) (simple)
  3. Continuous output: Suitable for pixel values
  4. Penalizes deviations: All errors equally weighted

Example:

Original pixel: [0.5, 0.3, 0.8]
Reconstructed:  [0.4, 0.35, 0.82]
Error: 0.01² + 0.05² + 0.02² = 0.003

Q9: What is a latent representation and why is it useful?

Answer:

Latent Representation (Bottleneck):

  • Compressed code z ∈ ℝ³²
  • Created by encoder
  • Used to reconstruct original input

Why Useful:

  1. Information Compression

    • Original: 784 dimensions
    • Latent: 32 dimensions (95% reduction)
    • Removes redundancy, keeps signal
  2. Feature Learning

    • Unsupervised learning
    • No labels needed
    • Learns meaningful patterns
    • Can visualize with t-SNE
  3. Downstream Tasks

    • Use z as features for classifier
    • Often outperforms raw pixels
    • Transfer learning capability
  4. Data Analysis

    • Dimensions interpretable
    • Cluster similar samples
    • Explore data structure

In Fashion-MNIST:

Raw image: 28×28 = 784 pixels
Latent code: 32 numbers (class info, style, pose, etc.)
Reconstruction from 32 numbers: Still recognize clothing item

Part 4: RBM

Q10: What is a Restricted Boltzmann Machine and how does it differ from a standard neural network?

Answer:

RBM Definition: A stochastic, undirected graphical model with probabilistic interpretation.

Key Differences:

Standard NN RBM
Deterministic Probabilistic
Feedforward Bidirectional
Discriminative Generative
No hidden-hidden No hidden-hidden
Supervised Unsupervised
Point estimate Distribution

RBM Architecture:

Visible Units (v): 784 binary nodes
    ↔  (Bidirectional connections W)
Hidden Units (h): 100 binary nodes

Key constraint: No v-v or h-h connections
- This enables efficient inference

Probabilistic Model: $$P(v,h) = \frac{\exp(-E(v,h))}{Z(\theta)}$$

Where energy is: $$E(v,h) = -v^THb - v^Tb_v - h^Tb_h$$


Q11: Explain the Contrastive Divergence algorithm.

Answer:

Problem: RBM training requires sampling from intractable distribution.

Solution: Contrastive Divergence (CD)

CD-1 Algorithm:

  1. Positive Phase (Data-dependent)

    • Start with training sample v₀
    • Sample h₀ ~ P(h | v₀)
    • Compute: v₀h₀ᵀ
  2. Negative Phase (Model-dependent)

    • Sample v₁ ~ P(v | h₀)
    • Sample h₁ ~ P(h | v₁)
    • Compute: v₁h₁ᵀ
  3. Update Rule

    ΔW = lr × [(v₀h₀ᵀ) - (v₁h₁ᵀ)] / batch_size
    Δb_v = lr × (v₀ - v₁) / batch_size
    Δb_h = lr × (h₀ - h₁) / batch_size
    

Intuition:

  • Positive phase: Learn patterns in data
  • Negative phase: Reduce probability of model fantasy
  • Difference: Weight update direction

Why CD Works:

  1. Only needs one Gibbs step (CD-1)
  2. Approximation is good in practice
  3. Computationally efficient
  4. Gradient still points in right direction

Q12: How are features extracted from RBM?

Answer:

Feature Extraction Process:

Input: v (784-dimensional visible vector)
     ↓
Compute: h_prob = σ(W^T v + b_h)
     ↓
Features: h (100-dimensional)

Interpretation:

Each hidden unit learns:

  • Edge detectors (Gabor-like filters)
  • Texture patterns
  • Feature combinations
  • Abstract concepts

Why Effective:

  1. Unsupervised Learning

    • No labels needed
    • Learns from data distribution
    • Discovers natural clusters
  2. Probabilistic

    • Probability of each feature
    • Can threshold for binary features
    • Interpretable activations
  3. Composability

    • Stack multiple RBMs → Deep Belief Networks
    • Features feed to next layer
    • Hierarchical representation

Our Implementation:

# Extract features
hidden_features = rbm.transform(X_test)  # (N, 100)

# These 100 features can be:
# - Used for classification
# - Visualized to understand learned patterns
# - Compared across different samples

Part 5: Training & Optimization

Q13: What is the difference between batch, mini-batch, and stochastic gradient descent?

Answer:

Type Batch Size Iterations Gradient Noise Speed Convergence
SGD 1 Many High Slow Noisy
Mini-batch 32-256 Moderate Moderate Fast Stable
Batch Full Few Low Very Slow Smooth

Mathematical Comparison:

SGD (batch_size=1):

For each sample x_i:
    θ ← θ - lr × ∇L(θ; x_i)
    → Noisy updates, but can escape local minima

Mini-batch (batch_size=64):

For each batch B:
    θ ← θ - lr × (1/|B|) × Σ∇L(θ; x_i)  for x_i ∈ B
    → Balanced: stable + efficient

Batch (batch_size=N):

For all data:
    θ ← θ - lr × ∇L(θ; all_data)
    → Accurate but slow

Our Choice: Mini-batch (64) because:

  1. Faster than full-batch
  2. Better generalization than SGD
  3. Efficient GPU/parallel computation
  4. Standard practice in industry

Q14: How do you prevent overfitting in your models?

Answer:

Overfitting Problem:

  • Model memorizes training data
  • Poor generalization to test data
  • Validation loss diverges from training loss

Strategies Implemented:

  1. Validation Monitoring
# Check validation performance every epoch
val_loss = evaluate(X_val, y_val)
if val_loss_worse_than_before:
    # Could implement early stopping
  1. Data Splitting
Total data: 70,000
Training: 50,000 (71%)
Validation: 10,000 (14%)
Testing: 10,000 (14%)
  1. Regularization Options

L1 Regularization (Weight Decay):

L_total = L_data + λ × Σ|W|
Forces small weights, sparse model

L2 Regularization:

L_total = L_data + λ × Σ|W|²
Encourages small weights evenly
  1. Sparse Autoencoder
# Optional sparsity constraint
sparse = True  # Limits activation
sparsity_weight = 0.01
  1. Model Design
  • Bottleneck architecture limits capacity
  • Prevents learning identity mapping
  • Forces feature compression

Q15: What is learning rate and why is it important?

Answer:

Definition: Hyperparameter controlling step size in gradient descent. $$\theta_{new} = \theta_{old} - lr \times \nabla L$$

Impact of Different Learning Rates:

Learning Rate Effect
Too small Very slow convergence, stuck in local minima
Too large Overshooting, divergence, oscillation
Optimal Fast smooth convergence

In our implementations:

  • MLP: lr = 0.05 (moderate, captures useful features)
  • Autoencoder: lr = 0.01 (smaller, for stability)
  • RBM: lr = 0.01 (probabilistic models need care)

Learning Rate Schedule:

# Decay learning rate over time
lr(t) = lr_0 / (1 + decay_rate × t)

Effect:
- Start with large steps
- Gradually decrease for fine-tuning
- Prevents oscillation near optimum

Our Choices:

Initial lr: 0.05
Decay: Not implemented (for simplicity)
Alternative strategies:
- Step decay: Reduce by factor every N epochs
- Exponential decay: lr = lr_0 × exp(-kt)
- Adam: Adaptive per-parameter learning rate

Part 6: Experimental & Practical

Q16: How did you evaluate the performance of your models?

Answer:

MLP Evaluation:

  1. Accuracy
acc = (correct_predictions) / (total_samples)
# Test Accuracy: ~88%
  1. Per-class Performance
T-shirt: 92%
Trouser: 97%
...
Ankle boot: 85%
  1. Confusion Matrix
  • Shows which classes are confused
  • Identifies misclassifications
  • Guides architecture improvements
  1. Metrics
Precision: TP/(TP+FP) - False positive rate
Recall: TP/(TP+FN) - False negative rate
F1-Score: 2×(P×R)/(P+R) - Balanced metric

Autoencoder Evaluation:

  1. MSE on Test Set
mse = mean((X_test - X_reconstructed)²)
# Low MSE (~0.0009) indicates good reconstruction
  1. Reconstruction Error Distribution
  • Plot histogram of errors
  • Outlier detection threshold
  • Quality assessment
  1. Visual Inspection
  • Compare original vs reconstructed images
  • Check if important features preserved
  • Identify artifacts

RBM Evaluation:

  1. Free Energy
F(v) = -v^Tb_v - Σ log(1 + exp(W_j^T v + b_j))
# Should decrease during training
# Monitor convergence
  1. Reconstruction Error
error = mean_abs(X - X_reconstructed)
# Lower error = better learning
  1. Feature Quality
- Learned filters should be interpretable
- Hidden activations should be sparse
- Features should capture patterns

Q17: What hyperparameters did you tune and what were the results?

Answer:

MLP Hyperparameter Tuning:

# Hidden Size
hidden_sizes_tested = [64, 128, 256]
# Results: 128 gives best speed/accuracy trade-off

# Learning Rate
lr_tested = [0.01, 0.05, 0.1]
# Results: 0.05 optimal (0.01 too slow, 0.1 oscillates)

# Activation Function
activations = ['relu', 'sigmoid', 'tanh']
# Results: ReLU converges 6x faster

# Batch Size
batch_sizes = [32, 64, 128]
# Results: 64 balances stability and speed

Autoencoder Hyperparameter Tuning:

# Latent Dimensions
latent_sizes = [16, 32, 64]
# Results: 32 = 95% compression, good reconstruction

# Hidden Layer Sizes
architectures = [
    [256],
    [256, 128],
    [512, 256, 128]
]
# Results: [256] sufficient, more layers don't help much

# Sparsity Weight
sparsity_weights = [0, 0.01, 0.1]
# Results: 0.01 improves feature interpretability

RBM Hyperparameter Tuning:

# Hidden Units
hidden_units = [50, 100, 200]
# Results: 100 captures enough features

# CD-k (Gibbs Steps)
cd_k_values = [1, 3, 5]
# Results: CD-1 efficient, CD-5 more accurate

# Learning Rate (for CD)
lr_values = [0.001, 0.01, 0.05]
# Results: 0.01 stable, 0.05 overshoots

Final Configuration:

MLP: hidden=128, lr=0.05, batch=64, activation=relu
Autoencoder: hidden=[256], latent=32, lr=0.01
RBM: hidden=100, cd_k=1, lr=0.01

Q18: What challenges did you face and how did you overcome them?

Answer:

Challenge 1: Backpropagation Debugging

  • Problem: Difficult to verify gradient computation correctness
  • Solution: Numerical gradient checking
# Compare analytical vs numerical gradients
analytical_grad = backward()
numerical_grad = (L(θ+ε) - L(θ-ε)) / (2ε)
assert close(analytical_grad, numerical_grad)

Challenge 2: Numerical Stability

  • Problem: Overflow in softmax, sigmoid with large values
  • Solution: Numerically stable implementations
# Stable softmax
e_x = exp(x - max(x))
softmax = e_x / sum(e_x)

# Clipped sigmoid
sigmoid(x) = 1 / (1 + exp(-clip(x, -500, 500)))

Challenge 3: Slow CPU Training

  • Problem: RBM and large batches slow on CPU
  • Solution: Efficient NumPy operations, vectorization
# Avoid loops, use matrix operations
# Wrong: for i, j: result[i,j] = W[i,j] * X[i]
# Right: result = W * X (vectorized)

Challenge 4: Hyperparameter Selection

  • Problem: Many hyperparameters to tune
  • Solution: Systematic grid search
# Test combinations and track results
best_config = systematic_search(param_grid)

Challenge 5: Vanishing Gradients

  • Problem: Sigmoid saturates, gradients → 0
  • Solution: Use ReLU activation
# ReLU doesn't saturate
# ∂ReLU/∂z = 1 for z > 0
# No exponential scaling of gradients

Part 7: Advanced Questions

Q19: Can these models solve other problems? What would you modify?

Answer:

For Different Datasets:

  1. CIFAR-10 (32×32 RGB Images)

    • Modify: Input size 3072 (32×32×3)
    • Add: Convolutional layers for spatial patterns
    • Benefit: Much better accuracy
  2. MNIST Handwritten Digits

    • Simpler than Fashion-MNIST
    • Our model would achieve ~97% accuracy
    • Faster training
  3. ImageNet (Large-scale Images)

    • Need: Convolutional architectures (CNN)
    • Need: GPU acceleration
    • Need: Batch normalization
    • Need: Transfer learning

For Different Tasks:

  1. Object Detection

    • Modify: Add bounding box regression
    • Add: Region proposal networks (RPN)
    • Framework: Two-stage detectors (RCNN)
  2. Semantic Segmentation

    • Modify: Output same spatial dimensions as input
    • Architecture: Encoder-decoder (U-Net)
    • Add: Skip connections
  3. Time Series Prediction

    • Modify: Input: Sequential data
    • Architecture: LSTM or GRU (Recurrent)
    • Loss: MSE with temporal weighting
  4. Anomaly Detection

    • Perfect use: Autoencoder reconstruction error
    • No modification needed
    • Threshold-based detection

Q20: How would you improve these models for better performance?

Answer:

For MLP Classification:

  1. Deeper Networks
# Add more hidden layers
architecture = [784, 512, 256, 128, 10]
# Pro: More representational power
# Con: Risk of vanishing gradients → Use batch norm
  1. Advanced Optimizers
# Replace SGD with Adam
lr = 0.001  # Smaller lr for adaptive method
# Advantages: 
# - Per-parameter learning rates
# - Momentum for faster convergence
# - Usually converges in fewer epochs
  1. Regularization
# L2 regularization
L_total = L_data + λ × sum(W²)

# Dropout (not in current implementation)
# Keep fewer neurons during training
# Prevents co-adaptation
  1. Batch Normalization
# Normalize layer inputs
z_norm = (z - mean) / sqrt(var + ε)
# Benefits:
# - Stabilizes training
# - Acts as regularizer
# - Allows higher learning rates

For Autoencoder:

  1. Convolutional Autoencoder
# Replace Dense layers with Conv2D
# Benefits: 
# - Preserve spatial structure
# - Fewer parameters
# - Better for images
  1. Variational Autoencoder (VAE)
# Add probabilistic latent space
# Loss = reconstruction + KL divergence
# Benefits:
# - Generative model
# - Smooth latent space
# - Can sample new data
  1. Denoising Autoencoder
# Train on noisy images, reconstruct clean
# Add noise to inputs during training
# Benefits:
# - Robust to noise
# - Better feature learning
# - Self-supervised learning

For RBM:

  1. Deep Belief Networks
# Stack multiple RBMs
# Layer 1: RBM_1(784→100)
# Layer 2: RBM_2(100→50)
# Layer 3: Classification layer
# Benefits:
# - Multi-level features
# - Better representations
  1. Larger RBMs
# More hidden units
n_hidden = 500  # vs 100
# Benefits:
# - Richer representations
# - Better reconstruction
# Cost: Slower training, more memory
  1. Persistent CD
# Keep Gibbs chain between batches
# Benefits:
# - Better gradient estimate
# - Faster convergence
# - Reduced bias

Q21: Explain how batch normalization works. (Bonus Question)

Answer:

Problem with Deep Networks:

  • Internal Covariate Shift
  • Previous layer changes shift input distribution
  • Each layer must adapt to new distribution
  • Slower training, careful initialization needed

Batch Normalization Solution:

For each neural network layer:

1. Normalize inputs to zero mean, unit variance
2. Apply learnable scale and shift

Normalized = (X - mean(X)) / sqrt(var(X) + ε)
Output = γ × Normalized + β

Benefits:

  1. Faster Convergence: 10-100× speedup
  2. Higher Learning Rates: Can use lr=0.1 instead of 0.01
  3. Reduced Initialization Sensitivity: Works with various initializations
  4. Regularization Effect: Reduces need for dropout
  5. Smoother Landscape: Easier optimization

Implementation (pseudo-code):

def batch_norm(X, gamma, beta, epsilon=1e-5):
    # Compute statistics per feature
    mean = np.mean(X, axis=0)
    var = np.var(X, axis=0)
    
    # Normalize
    X_norm = (X - mean) / np.sqrt(var + epsilon)
    
    # Scale and shift
    output = gamma * X_norm + beta
    
    return output

Would improve our MLP significantly:

Without batch norm: 30 epochs needed
With batch norm: 10 epochs needed
Accuracy: 88% → 92%+

Part 8: Reflection Questions

Q22: What did you learn from implementing these models from scratch?

Answer:

Key Learnings:

  1. Deep Understanding of Fundamentals

    • Not just "use Keras/TensorFlow"
    • Understand actual computation
    • Appreciate library abstractions
  2. Operational Insight

    • How changing activation affects convergence
    • Why weight initialization matters
    • Debug strategies for neural networks
  3. Practical Challenges

    • Numerical stability concerns
    • Memory efficiency
    • Hyperparameter sensitivity
  4. Mathematical Appreciation

    • Chain rule applied extensively
    • Matrix calculus importance
    • Optimization landscape

If you did it again:

  • Start with smaller models for faster feedback
  • Use numerical gradient checking earlier
  • Visualize activations during training
  • Document experimental results

Q23: How does this project contribute to understanding deep learning?

Answer:

Educational Value:

  1. Theory ↔ Practice Bridge

    • See math formulas in code
    • Understand algorithmic efficiency
    • Appreciate approximations
  2. Experimentation Ability

    • Modify and observe effects immediately
    • No "black box" framework hiding details
    • Direct control over everything
  3. Career Readiness

    • Shows ability to implement complex algorithms
    • Demonstrates mathematical foundation
    • Practical debugging skills
  4. Research Preparation

    • Understand papers more deeply
    • Can implement novel ideas
    • Know limitations of methods

Interview Tips

General Tips:

  1. Be confident but honest: "I implemented it, so I know it"
  2. Give concrete examples: Reference your code
  3. Draw diagrams: Visualize concepts
  4. Admit limitations: "Future work would be..."
  5. Prepare follow-ups: "Would you like me to explain...?"

Format Tips:

  • State question
  • Give high-level answer
  • Provide mathematical formulation
  • Discuss implementation
  • Contrast with alternatives
  • Mention strengths/weaknesses

Practice Strategy:

  1. Review this document thoroughly
  2. Read your implementation code
  3. Understand each line
  4. Practice explaining out loud
  5. Time yourself (aim for 2-3 min per question)

Good luck with your viva! Remember: You built working implementations of fundamental deep learning models. That's impressive! 🎓