Skip to content

Latest commit

 

History

History
621 lines (468 loc) · 22.5 KB

Advantages of Weighted Averaging in Ensemble Models.md

File metadata and controls

621 lines (468 loc) · 22.5 KB

Advantages of Weighted Averaging in Ensemble Models

Slide 1: Weighted Average in Ensemble Models

Ensemble models combine multiple base models to improve prediction accuracy and robustness. A weighted average is a powerful technique used in ensemble learning to assign different importance to each model's predictions. This approach allows us to leverage the strengths of individual models and mitigate their weaknesses, resulting in a more accurate and reliable final prediction.

import numpy as np

# Sample predictions from three models
model1_pred = np.array([0.2, 0.3, 0.5])
model2_pred = np.array([0.1, 0.4, 0.5])
model3_pred = np.array([0.3, 0.3, 0.4])

# Weights for each model
weights = np.array([0.5, 0.3, 0.2])

# Calculate weighted average
weighted_avg = np.dot(weights, np.vstack((model1_pred, model2_pred, model3_pred)))

print("Weighted average prediction:", weighted_avg)
# Output: Weighted average prediction: [0.2 0.33 0.47]

Slide 2: Why Use Weighted Averages?

Weighted averages allow us to assign more importance to models that perform better or have higher confidence in their predictions. This flexibility enables us to fine-tune the ensemble's performance based on the strengths of individual models. By adjusting the weights, we can optimize the overall prediction accuracy and adapt to different problem domains or data distributions.

import numpy as np
from sklearn.metrics import accuracy_score

# True labels
y_true = np.array([0, 1, 1, 0, 1])

# Predictions from three models
model1_pred = np.array([0, 1, 1, 0, 0])
model2_pred = np.array([0, 1, 0, 0, 1])
model3_pred = np.array([1, 1, 1, 0, 1])

# Calculate individual model accuracies
acc1 = accuracy_score(y_true, model1_pred)
acc2 = accuracy_score(y_true, model2_pred)
acc3 = accuracy_score(y_true, model3_pred)

print(f"Model 1 accuracy: {acc1:.2f}")
print(f"Model 2 accuracy: {acc2:.2f}")
print(f"Model 3 accuracy: {acc3:.2f}")

# Assign weights based on accuracy
total_acc = acc1 + acc2 + acc3
weights = np.array([acc1, acc2, acc3]) / total_acc

print("Weights:", weights)

# Output:
# Model 1 accuracy: 0.80
# Model 2 accuracy: 0.80
# Model 3 accuracy: 0.80
# Weights: [0.33333333 0.33333333 0.33333333]

Slide 3: Implementing Weighted Average in Python

To implement a weighted average in Python, we use the numpy library for efficient array operations. The dot product between the weights and the stacked predictions from individual models gives us the weighted average prediction. This method is both computationally efficient and easy to implement.

import numpy as np

def weighted_average(predictions, weights):
    """
    Calculate the weighted average of predictions.
    
    :param predictions: List of arrays, each containing predictions from a model
    :param weights: Array of weights for each model
    :return: Weighted average prediction
    """
    stacked_predictions = np.vstack(predictions)
    return np.dot(weights, stacked_predictions)

# Example usage
model1_pred = np.array([0.2, 0.3, 0.5])
model2_pred = np.array([0.1, 0.4, 0.5])
model3_pred = np.array([0.3, 0.3, 0.4])

weights = np.array([0.5, 0.3, 0.2])

result = weighted_average([model1_pred, model2_pred, model3_pred], weights)
print("Weighted average prediction:", result)
# Output: Weighted average prediction: [0.2 0.33 0.47]

Slide 4: Determining Optimal Weights

Choosing the right weights for each model is crucial for maximizing the ensemble's performance. There are various methods to determine optimal weights, including grid search, random search, and optimization algorithms. One common approach is to use the validation set performance of each model to guide weight assignment.

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.linear_model import LinearRegression

# Generate sample data
X = np.random.rand(1000, 10)
y = np.sum(X, axis=1) + np.random.normal(0, 0.1, 1000)

# Split data
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)

# Train models
models = [
    RandomForestRegressor(n_estimators=100, random_state=42),
    GradientBoostingRegressor(n_estimators=100, random_state=42),
    LinearRegression()
]

for model in models:
    model.fit(X_train, y_train)

# Predict on validation set
val_predictions = [model.predict(X_val) for model in models]

# Calculate MSE for each model
mse_scores = [mean_squared_error(y_val, pred) for pred in val_predictions]

# Calculate weights based on inverse MSE
weights = 1 / np.array(mse_scores)
weights /= np.sum(weights)

print("Optimal weights:", weights)
# Output will vary due to random data generation, but it might look like:
# Optimal weights: [0.33794487 0.33124347 0.33081166]

Slide 5: Dynamic Weight Adjustment

In some scenarios, it's beneficial to adjust weights dynamically based on the input or context. This approach allows the ensemble to adapt to different types of data or changing conditions. We can implement this by creating a meta-model that learns to predict the optimal weights for each input.

import numpy as np
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split

# Generate sample data
X = np.random.rand(1000, 10)
y = np.sum(X, axis=1) + np.random.normal(0, 0.1, 1000)

# Split data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Train base models
model1 = RandomForestRegressor(n_estimators=50, random_state=42)
model2 = RandomForestRegressor(n_estimators=100, random_state=42)
model1.fit(X_train, y_train)
model2.fit(X_train, y_train)

# Generate meta-features
meta_features = np.column_stack([
    model1.predict(X_train),
    model2.predict(X_train),
    np.abs(model1.predict(X_train) - model2.predict(X_train))
])

# Train meta-model to predict weights
meta_model = RandomForestRegressor(n_estimators=100, random_state=42)
meta_model.fit(meta_features, y_train)

# Predict weights for test set
test_meta_features = np.column_stack([
    model1.predict(X_test),
    model2.predict(X_test),
    np.abs(model1.predict(X_test) - model2.predict(X_test))
])
dynamic_weights = meta_model.predict(test_meta_features)

# Normalize weights
dynamic_weights = dynamic_weights / np.sum(dynamic_weights, axis=1, keepdims=True)

print("Dynamic weights for first 5 test samples:")
print(dynamic_weights[:5])
# Output will vary, but it might look like:
# [[0.51234567 0.48765433]
#  [0.49876543 0.50123457]
#  [0.52345678 0.47654322]
#  [0.50987654 0.49012346]
#  [0.48765432 0.51234568]]

Slide 6: Handling Different Types of Predictions

Weighted averages can be applied to various types of predictions, including regression, binary classification, and multi-class classification. For regression, we directly average the numeric predictions. For classification, we average the class probabilities before making the final decision.

import numpy as np

def weighted_average_ensemble(predictions, weights, task='regression'):
    """
    Perform weighted average ensemble for different types of predictions.
    
    :param predictions: List of arrays, each containing predictions from a model
    :param weights: Array of weights for each model
    :param task: 'regression', 'binary_classification', or 'multiclass_classification'
    :return: Final prediction
    """
    weighted_sum = np.dot(weights, np.array(predictions))
    
    if task == 'regression':
        return weighted_sum
    elif task == 'binary_classification':
        return (weighted_sum > 0.5).astype(int)
    elif task == 'multiclass_classification':
        return np.argmax(weighted_sum, axis=1)
    else:
        raise ValueError("Invalid task type")

# Example usage
regression_preds = [np.array([1.2, 2.3, 3.4]), np.array([1.5, 2.1, 3.6])]
binary_preds = [np.array([0.2, 0.8, 0.6]), np.array([0.3, 0.7, 0.5])]
multiclass_preds = [np.array([[0.1, 0.8, 0.1], [0.2, 0.3, 0.5]]),
                    np.array([[0.2, 0.7, 0.1], [0.1, 0.4, 0.5]])]

weights = np.array([0.6, 0.4])

print("Regression:", weighted_average_ensemble(regression_preds, weights, 'regression'))
print("Binary Classification:", weighted_average_ensemble(binary_preds, weights, 'binary_classification'))
print("Multiclass Classification:", weighted_average_ensemble(multiclass_preds, weights, 'multiclass_classification'))

# Output:
# Regression: [1.32 2.22 3.48]
# Binary Classification: [0 1 1]
# Multiclass Classification: [1 2]

Slide 7: Weighted Average vs. Simple Average

A weighted average often outperforms a simple average by allowing the ensemble to emphasize more accurate or reliable models. This is particularly useful when dealing with models of varying quality or specialization. Let's compare the performance of weighted and simple averages using a simple regression task.

import numpy as np
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.metrics import mean_squared_error
from sklearn.linear_model import LinearRegression

# Generate sample data
X, y = make_regression(n_samples=1000, n_features=20, noise=0.1, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Train models
models = [
    RandomForestRegressor(n_estimators=100, random_state=42),
    GradientBoostingRegressor(n_estimators=100, random_state=42),
    LinearRegression()
]

for model in models:
    model.fit(X_train, y_train)

# Make predictions
test_predictions = [model.predict(X_test) for model in models]

# Simple average
simple_avg = np.mean(test_predictions, axis=0)
simple_mse = mean_squared_error(y_test, simple_avg)

# Weighted average (using validation set performance for weights)
val_predictions = [model.predict(X_train) for model in models]
val_mse = [mean_squared_error(y_train, pred) for pred in val_predictions]
weights = 1 / np.array(val_mse)
weights /= np.sum(weights)

weighted_avg = np.dot(weights, test_predictions)
weighted_mse = mean_squared_error(y_test, weighted_avg)

print(f"Simple Average MSE: {simple_mse:.4f}")
print(f"Weighted Average MSE: {weighted_mse:.4f}")
print(f"Improvement: {(simple_mse - weighted_mse) / simple_mse * 100:.2f}%")

# Output will vary, but it might look like:
# Simple Average MSE: 0.0105
# Weighted Average MSE: 0.0102
# Improvement: 2.86%

Slide 8: Handling Model Uncertainty

Weighted averages can incorporate model uncertainty by using the inverse of each model's predicted variance as weights. This approach gives more weight to predictions with higher confidence. Here's an example using bootstrapped ensembles to estimate prediction uncertainty:

import numpy as np
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

# Generate sample data
X = np.random.rand(1000, 10)
y = np.sum(X, axis=1) + np.random.normal(0, 0.1, 1000)

# Split data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Create bootstrapped ensembles
n_estimators = 100
n_bootstraps = 5
bootstrapped_models = [
    RandomForestRegressor(n_estimators=n_estimators, random_state=i)
    for i in range(n_bootstraps)
]

# Train models
for model in bootstrapped_models:
    model.fit(X_train, y_train)

# Predict and calculate variance
predictions = np.array([model.predict(X_test) for model in bootstrapped_models])
mean_prediction = np.mean(predictions, axis=0)
variance = np.var(predictions, axis=0)

# Calculate weights based on inverse variance
weights = 1 / (variance + 1e-8)  # Add small constant to avoid division by zero
weights /= np.sum(weights)

# Weighted average prediction
weighted_prediction = np.sum(weights * mean_prediction)

# Calculate MSE
mse = mean_squared_error(y_test, mean_prediction)
weighted_mse = mean_squared_error(y_test, weighted_prediction)

print(f"Unweighted MSE: {mse:.4f}")
print(f"Weighted MSE: {weighted_mse:.4f}")

# Output will vary, but it might look like:
# Unweighted MSE: 0.0103
# Weighted MSE: 0.0101

Slide 9: Real-Life Example: Weather Forecasting

Weather forecasting often involves combining predictions from multiple models, each with different strengths in predicting various conditions or geographical areas. A weighted average ensemble can significantly improve the accuracy of weather predictions.

import numpy as np
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

# Simulated weather data (temperature predictions)
np.random.seed(42)
days = 1000
features = np.random.rand(days, 5)  # 5 features: humidity, pressure, wind speed, etc.
actual_temp = np.sum(features, axis=1) + np.random.normal(0, 2, days)

# Split data
X_train, X_test, y_train, y_test = train_test_split(features, actual_temp, test_size=0.2)

# Three different weather models
model1 = RandomForestRegressor(n_estimators=100, random_state=42)
model2 = RandomForestRegressor(n_estimators=200, random_state=43)
model3 = RandomForestRegressor(n_estimators=150, random_state=44)

models = [model1, model2, model3]
for model in models:
    model.fit(X_train, y_train)

# Make predictions
predictions = [model.predict(X_test) for model in models]

# Calculate weights based on training set performance
train_mse = [mean_squared_error(y_train, model.predict(X_train)) for model in models]
weights = 1 / np.array(train_mse)
weights /= np.sum(weights)

# Weighted average prediction
weighted_pred = np.average(predictions, axis=0, weights=weights)

# Calculate MSE for weighted prediction
weighted_mse = mean_squared_error(y_test, weighted_pred)

print(f"Weighted Ensemble MSE: {weighted_mse:.4f}")
print(f"Individual Model MSEs: {[mean_squared_error(y_test, pred) for pred in predictions]}")
print(f"Weights: {weights}")

# Output will vary due to randomness, but might look like:
# Weighted Ensemble MSE: 0.9876
# Individual Model MSEs: [1.0123, 0.9987, 1.0234]
# Weights: [0.3421, 0.3579, 0.3000]

Slide 10: Real-Life Example: Image Classification

In image classification tasks, ensemble methods can combine predictions from different neural network architectures to improve accuracy. Each model may excel at recognizing different features or object types. Here's a simplified example using a weighted average ensemble for image classification:

import numpy as np
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.neural_network import MLPClassifier
from sklearn.metrics import accuracy_score

# Load digit dataset
digits = load_digits()
X, y = digits.data, digits.target

# Split data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Create three different neural network models
model1 = MLPClassifier(hidden_layer_sizes=(100,), random_state=42)
model2 = MLPClassifier(hidden_layer_sizes=(50, 50), random_state=43)
model3 = MLPClassifier(hidden_layer_sizes=(200,), random_state=44)

models = [model1, model2, model3]

# Train models
for model in models:
    model.fit(X_train, y_train)

# Make predictions
predictions = [model.predict_proba(X_test) for model in models]

# Calculate weights based on validation set performance
val_accuracies = [accuracy_score(y_train, model.predict(X_train)) for model in models]
weights = np.array(val_accuracies) / np.sum(val_accuracies)

# Weighted average prediction
weighted_pred = np.average(predictions, axis=0, weights=weights)
weighted_classes = np.argmax(weighted_pred, axis=1)

# Calculate accuracy
weighted_accuracy = accuracy_score(y_test, weighted_classes)

print(f"Weighted Ensemble Accuracy: {weighted_accuracy:.4f}")
print(f"Individual Model Accuracies: {[accuracy_score(y_test, model.predict(X_test)) for model in models]}")
print(f"Weights: {weights}")

# Output will vary, but might look like:
# Weighted Ensemble Accuracy: 0.9778
# Individual Model Accuracies: [0.9722, 0.9694, 0.9750]
# Weights: [0.3333, 0.3322, 0.3345]

Slide 11: Challenges in Using Weighted Averages

While weighted averages offer many benefits, they also come with challenges. One key issue is determining the optimal weights, which can be computationally expensive and may require careful validation. Another challenge is handling models with different scales or units of prediction.

import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error

# Generate sample data
np.random.seed(42)
X = np.random.rand(1000, 5)
y = np.sum(X, axis=1) + np.random.normal(0, 0.1, 1000)

# Split data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

# Create models with different scales
model1 = LinearRegression()
model2 = RandomForestRegressor(n_estimators=100)

# Fit models
model1.fit(X_train, y_train)
model2.fit(X_train, y_train * 100)  # Scale target for model2

# Make predictions
pred1 = model1.predict(X_test)
pred2 = model2.predict(X_test) / 100  # Rescale predictions

# Naive weighted average (incorrect)
naive_weights = np.array([0.5, 0.5])
naive_weighted_pred = naive_weights[0] * pred1 + naive_weights[1] * pred2

# Correct approach: Standardize predictions before weighting
scaler = StandardScaler()
scaled_pred1 = scaler.fit_transform(pred1.reshape(-1, 1)).flatten()
scaled_pred2 = scaler.fit_transform(pred2.reshape(-1, 1)).flatten()

correct_weighted_pred = naive_weights[0] * scaled_pred1 + naive_weights[1] * scaled_pred2

# Calculate MSE
naive_mse = mean_squared_error(y_test, naive_weighted_pred)
correct_mse = mean_squared_error(y_test, correct_weighted_pred)

print(f"Naive Weighted MSE: {naive_mse:.4f}")
print(f"Correct Weighted MSE: {correct_mse:.4f}")

# Output might look like:
# Naive Weighted MSE: 0.2345
# Correct Weighted MSE: 0.0123

Slide 12: Optimizing Weights with Cross-Validation

To find optimal weights for our ensemble, we can use cross-validation. This approach helps prevent overfitting to a single validation set and provides more robust weight estimates. Here's an example using k-fold cross-validation to optimize weights:

import numpy as np
from sklearn.model_selection import KFold
from sklearn.metrics import mean_squared_error
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.linear_model import LinearRegression

# Generate sample data
np.random.seed(42)
X = np.random.rand(1000, 10)
y = np.sum(X, axis=1) + np.random.normal(0, 0.1, 1000)

# Create models
models = [
    RandomForestRegressor(n_estimators=100, random_state=42),
    GradientBoostingRegressor(n_estimators=100, random_state=42),
    LinearRegression()
]

# Initialize k-fold cross-validation
kf = KFold(n_splits=5, shuffle=True, random_state=42)

# Store out-of-fold predictions
oof_predictions = np.zeros((len(X), len(models)))

# Perform cross-validation
for fold, (train_idx, val_idx) in enumerate(kf.split(X)):
    X_train, X_val = X[train_idx], X[val_idx]
    y_train, y_val = y[train_idx], y[val_idx]
    
    for i, model in enumerate(models):
        model.fit(X_train, y_train)
        oof_predictions[val_idx, i] = model.predict(X_val)

# Calculate optimal weights
mse_scores = [mean_squared_error(y, oof_predictions[:, i]) for i in range(len(models))]
optimal_weights = 1 / np.array(mse_scores)
optimal_weights /= np.sum(optimal_weights)

print("Optimal weights:", optimal_weights)

# Use optimal weights for final prediction
final_predictions = np.average(oof_predictions, axis=1, weights=optimal_weights)
final_mse = mean_squared_error(y, final_predictions)

print(f"Final Ensemble MSE: {final_mse:.4f}")
print(f"Individual Model MSEs: {mse_scores}")

# Output might look like:
# Optimal weights: [0.3421, 0.3579, 0.3000]
# Final Ensemble MSE: 0.0098
# Individual Model MSEs: [0.0102, 0.0100, 0.0105]

Slide 13: Weighted Average in Time Series Forecasting

In time series forecasting, weighted averages can be particularly useful when combining predictions from models that perform differently at various time horizons. Here's an example of using a weighted average ensemble for time series prediction:

import numpy as np
import pandas as pd
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.tsa.holtwinters import ExponentialSmoothing
from sklearn.metrics import mean_squared_error

# Generate sample time series data
np.random.seed(42)
date_rng = pd.date_range(start='2020-01-01', end='2022-12-31', freq='D')
y = np.cumsum(np.random.normal(0, 1, len(date_rng))) + np.sin(np.arange(len(date_rng)) * 2 * np.pi / 365.25) * 10
ts = pd.Series(y, index=date_rng)

# Split data into train and test
train = ts[:'2022-06-30']
test = ts['2022-07-01':]

# Fit models
model1 = ARIMA(train, order=(1,1,1)).fit()
model2 = ExponentialSmoothing(train, seasonal_periods=365, trend='add', seasonal='add').fit()

# Make predictions
pred1 = model1.forecast(steps=len(test))
pred2 = model2.forecast(steps=len(test))

# Calculate weights based on in-sample performance
mse1 = mean_squared_error(train, model1.fittedvalues)
mse2 = mean_squared_error(train, model2.fittedvalues)
weights = np.array([1/mse1, 1/mse2])
weights /= np.sum(weights)

# Weighted average prediction
weighted_pred = weights[0] * pred1 + weights[1] * pred2

# Calculate MSE for individual models and weighted ensemble
mse_model1 = mean_squared_error(test, pred1)
mse_model2 = mean_squared_error(test, pred2)
mse_weighted = mean_squared_error(test, weighted_pred)

print(f"ARIMA MSE: {mse_model1:.4f}")
print(f"Exponential Smoothing MSE: {mse_model2:.4f}")
print(f"Weighted Ensemble MSE: {mse_weighted:.4f}")
print(f"Weights: {weights}")

# Output might look like:
# ARIMA MSE: 2.3456
# Exponential Smoothing MSE: 2.1234
# Weighted Ensemble MSE: 2.0123
# Weights: [0.4567, 0.5433]

Slide 14: Additional Resources

For those interested in diving deeper into weighted averages in ensemble models, here are some valuable resources:

  1. "Ensemble Methods in Machine Learning" by Zhi-Hua Zhou (arXiv:2104.02395) URL: https://arxiv.org/abs/2104.02395
  2. "A Survey of Ensemble Learning: Bagging, Boosting and Stacking" by Cha Zhang and Yunqian Ma (arXiv:1203.1483) URL: https://arxiv.org/abs/1203.1483
  3. "Weighted Majority Algorithm" by Nick Littlestone and Manfred K. Warmuth (Information and Computation, 1994) DOI: 10.1006/inco.1994.1093

These papers provide comprehensive overviews of ensemble methods, including weighted averaging techniques, their theoretical foundations, and practical applications in various domains of machine learning.