# Demo 2: Interactive Regularization Explorer
def explore_regularization(lambda_val=0.01, method='Ridge'):
"""
Interactive function to explore Ridge and Lasso regularization.
Parameters:
- lambda_val: Regularization strength (λ)
- method: 'Ridge' or 'Lasso'
"""
# Choose model based on method
if method == 'Ridge':
model = Ridge(alpha=lambda_val)
color = '#2E86AB' # Blue
color_bg = '#E3F2FD'
else: # Lasso
model = Lasso(alpha=lambda_val, max_iter=10000)
color = '#06A77D' # Green
color_bg = '#E8F5E9'
# Fit model
model.fit(X_train_scaled, y_train)
# Predictions
X_plot = np.linspace(0, 10, 200).reshape(-1, 1)
X_plot_poly = poly_demo2.transform(X_plot)
X_plot_scaled = scaler.transform(X_plot_poly)
y_plot = model.predict(X_plot_scaled)
# Calculate R² scores
train_r2 = r2_score(y_train, model.predict(X_train_scaled))
test_r2 = r2_score(y_test, model.predict(X_test_scaled))
# Count non-zero coefficients (for Lasso)
non_zero = np.sum(np.abs(model.coef_) > 1e-5)
max_coef = np.max(np.abs(model.coef_))
total_coefs = len(model.coef_)
# Create subplots
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(18, 7))
# ========== LEFT PLOT: Fitted Curve ==========
ax1.scatter(X_train, y_train, alpha=0.6, s=120, label='Training',
color='#2E86AB', edgecolors='black', linewidth=1)
ax1.scatter(X_test, y_test, alpha=0.6, s=120, label='Test',
color='#F77F00', marker='^', edgecolors='black', linewidth=1)
ax1.plot(X_plot, y_plot, '-', linewidth=3, label=f'{method} Fit', color=color)
ax1.set_xlabel('House Age (years)', fontsize=14, fontweight='bold')
ax1.set_ylabel('House Price (£1000s)', fontsize=14, fontweight='bold')
ax1.set_title(f'{method} Regression (λ = {lambda_val:.4f})',
fontsize=18, fontweight='bold', pad=15)
ax1.legend(fontsize=12, loc='upper right', framealpha=0.9)
ax1.grid(True, alpha=0.3, linestyle='--')
ax1.set_xlim(-0.5, 10.5)
# Metrics text box
metrics_text = (f'Train R²: {train_r2:.3f}\n'
f'Test R²: {test_r2:.3f}\n'
f'Non-zero: {non_zero}/{total_coefs}\n'
f'Max |coef|: {max_coef:.1f}')
props = dict(boxstyle='round', facecolor='wheat', alpha=0.9,
edgecolor='black', linewidth=2)
ax1.text(0.02, 0.98, metrics_text, transform=ax1.transAxes, fontsize=14,
verticalalignment='top', bbox=props, fontweight='bold', family='monospace')
# ========== RIGHT PLOT: Coefficient Values ==========
coef_indices = np.arange(len(model.coef_))
colors_coef = [color if abs(c) > 1e-5 else '#E5E5E5' for c in model.coef_]
ax2.bar(coef_indices, model.coef_, color=colors_coef, alpha=0.8, edgecolor='black', linewidth=0.5)
ax2.axhline(y=0, color='black', linestyle='-', linewidth=1)
ax2.set_xlabel('Coefficient Index', fontsize=14, fontweight='bold')
ax2.set_ylabel('Coefficient Value', fontsize=14, fontweight='bold')
ax2.set_title(f'Coefficient Values ({method})', fontsize=18, fontweight='bold', pad=15)
ax2.grid(True, alpha=0.3, axis='y', linestyle='--')
# Add sparsity annotation for Lasso
if method == 'Lasso' and non_zero < total_coefs:
zeros_count = total_coefs - non_zero
sparsity_text = f'{zeros_count} coefficients = 0\n(Automatic feature selection!)'
ax2.text(0.98, 0.98, sparsity_text, transform=ax2.transAxes, fontsize=14,
verticalalignment='top', horizontalalignment='right',
color='#E63946', fontweight='bold',
bbox=dict(boxstyle='round', facecolor='white', alpha=0.9,
edgecolor='#E63946', linewidth=2))
plt.tight_layout()
plt.show()
# Print detailed analysis
gap = train_r2 - test_r2
print(f"\n{'='*70}")
print(f"{method} Regression with λ = {lambda_val:.4f}")
print(f"{'='*70}")
print(f"Training R²: {train_r2:.4f}")
print(f"Test R²: {test_r2:.4f}")
print(f"Train-Test Gap: {gap:.4f}")
print(f"Non-zero coeffs: {non_zero} / {total_coefs}")
print(f"Max |coefficient|: {max_coef:.2f}")
# Provide guidance
if lambda_val < 0.01:
print("\n⚠️ Very low λ - likely overfitting!")
print(" Try increasing λ to control the coefficients.")
elif lambda_val > 100:
print("\n⚠️ Very high λ - likely underfitting!")
print(" Try decreasing λ to allow more flexibility.")
elif test_r2 > 0.7 and gap < 0.15:
print("\n✓ Good balance! Test performance is strong and gap is small.")
if method == 'Lasso' and non_zero < total_coefs * 0.5:
print(f"\n🎯 Lasso achieved {(1-non_zero/total_coefs)*100:.0f}% sparsity!")
print(f" Only {non_zero} features are actually being used.")
# Create interactive widget
interact(explore_regularization,
lambda_val=FloatLogSlider(
value=0.01,
base=10,
min=-3, # 10^-3 = 0.001
max=3, # 10^3 = 1000
step=0.1,
description='λ (lambda):',
style={'description_width': '150px'},
layout={'width': '600px'},
readout_format='.4f'
),
method=RadioButtons(
options=['Ridge', 'Lasso'],
description='Method:',
style={'description_width': '150px'}
));