Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

regularization.py

11_regularization/regularization.py  ·  144 lines  ·  view on GitHub

"""
Regularization from Scratch
Interview question: "Explain L1 vs L2 regularization"
"""
import numpy as np

# ==================== L1 Regularization (Lasso) ====================

def l1_regularization_loss(weights: np.ndarray, lambda_reg: float) -> float:
    """
    L1 Regularization: lambda * sum(|w|)
    
    Effect: Many weights become exactly 0 (sparsity)
    Use: Feature selection, interpretability
    """
    return lambda_reg * np.sum(np.abs(weights))

def l1_gradient(weights: np.ndarray, lambda_reg: float) -> np.ndarray:
    """Gradient of L1 regularization"""
    return lambda_reg * np.sign(weights)

# ==================== L2 Regularization (Ridge) ====================

def l2_regularization_loss(weights: np.ndarray, lambda_reg: float) -> float:
    """
    L2 Regularization: lambda * sum(w^2)
    
    Effect: Shrinks weights toward 0 (but not exactly 0)
    Use: Most common, improves generalization
    """
    return lambda_reg * np.sum(weights**2)

def l2_gradient(weights: np.ndarray, lambda_reg: float) -> np.ndarray:
    """Gradient of L2 regularization"""
    return 2 * lambda_reg * weights

# ==================== Elastic Net ====================

def elastic_net_loss(weights: np.ndarray, lambda_l1: float, 
                    lambda_l2: float) -> float:
    """
    Elastic Net: Combines L1 and L2
    lambda_l1 * |w| + lambda_l2 * w^2
    """
    return lambda_l1 * np.sum(np.abs(weights)) + lambda_l2 * np.sum(weights**2)

# ==================== Dropout ====================

def dropout(x: np.ndarray, dropout_rate: float, training: bool = True) -> np.ndarray:
    """
    Dropout: Randomly zero out activations during training
    
    Args:
        x: Input activations
        dropout_rate: Probability of dropping (0.0 to 1.0)
        training: If False, scale by (1-dropout_rate) but don't drop
    """
    if not training:
        # At inference: scale by (1-dropout_rate) to maintain expected value
        return x * (1 - dropout_rate)
    
    # During training: randomly drop and scale
    mask = np.random.binomial(1, 1 - dropout_rate, x.shape).astype(np.float32)
    return x * mask / (1 - dropout_rate)  # Scale to maintain expected value

# ==================== Early Stopping ====================

class EarlyStopping:
    """
    Early Stopping: Stop training when validation loss stops improving
    Prevents overfitting
    """
    
    def __init__(self, patience: int = 10, min_delta: float = 0.0):
        self.patience = patience
        self.min_delta = min_delta
        self.best_loss = float('inf')
        self.counter = 0
    
    def should_stop(self, val_loss: float) -> bool:
        """Check if should stop training"""
        if val_loss < self.best_loss - self.min_delta:
            self.best_loss = val_loss
            self.counter = 0
        else:
            self.counter += 1
        
        return self.counter >= self.patience


# Usage Example
if __name__ == "__main__":
    print("Regularization Techniques")
    print("=" * 60)
    
    # Example weights
    weights = np.array([0.5, -0.3, 1.2, -0.8, 0.1])
    lambda_reg = 0.1
    
    print(f"Weights: {weights}")
    print()
    
    # L1 regularization
    l1_loss = l1_regularization_loss(weights, lambda_reg)
    l1_grad = l1_gradient(weights, lambda_reg)
    print(f"L1 Regularization:")
    print(f"  Loss: {l1_loss:.4f}")
    print(f"  Gradient: {l1_grad}")
    print(f"  Effect: Promotes sparsity (many weights → 0)")
    
    print()
    
    # L2 regularization
    l2_loss = l2_regularization_loss(weights, lambda_reg)
    l2_grad = l2_gradient(weights, lambda_reg)
    print(f"L2 Regularization:")
    print(f"  Loss: {l2_loss:.4f}")
    print(f"  Gradient: {l2_grad}")
    print(f"  Effect: Shrinks weights toward 0")
    
    print()
    
    # Dropout
    x = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
    print(f"Dropout (rate=0.5):")
    print(f"  Input: {x}")
    np.random.seed(42)
    dropped = dropout(x, dropout_rate=0.5, training=True)
    print(f"  After dropout (training): {dropped}")
    print(f"  After dropout (inference): {dropout(x, dropout_rate=0.5, training=False)}")
    
    print()
    
    # Early stopping
    early_stop = EarlyStopping(patience=3)
    val_losses = [0.5, 0.4, 0.35, 0.34, 0.33, 0.33, 0.33]
    print("Early Stopping:")
    for i, loss in enumerate(val_losses):
        should_stop = early_stop.should_stop(loss)
        print(f"  Epoch {i+1}, Val Loss: {loss:.3f}, Stop: {should_stop}")
        if should_stop:
            print(f"  → Stopped at epoch {i+1}")
            break