diff options
| author | Dennis Kobert <dennis@kobert.dev> | 2025-03-11 00:15:45 +0100 |
|---|---|---|
| committer | Dennis Kobert <dennis@kobert.dev> | 2025-03-25 15:52:04 +0100 |
| commit | 7f49625ee1cfadaf523da9e2c247fded90d82665 (patch) | |
| tree | 290fb6c6b84c817010b915dcf058371ebb831803 /best_counters.py | |
| parent | 776dee32aca73ded5c0720afbe25ef2aae7b67f3 (diff) | |
Select counters
Diffstat (limited to 'best_counters.py')
| -rw-r--r-- | best_counters.py | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/best_counters.py b/best_counters.py new file mode 100644 index 0000000..feb5900 --- /dev/null +++ b/best_counters.py @@ -0,0 +1,315 @@ +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt +from sklearn.model_selection import train_test_split, cross_val_score +from sklearn.ensemble import RandomForestRegressor +from sklearn.feature_selection import RFE, mutual_info_regression +from sklearn.preprocessing import StandardScaler +from sklearn.impute import KNNImputer +from sklearn.metrics import r2_score, mean_squared_error +from itertools import combinations + +def load_and_preprocess_data(csv_path): + # Load the CSV file + df = pd.read_csv(csv_path) + + # Extract target (power usage) + y = df['package_power_j'].values + + # Extract features (CPU frequency and performance counters) + X = df.iloc[:, 3:] # Skip timestamp, duration_ms, and power + + # Replace empty strings with NaN + X = X.replace('', np.nan) + + # Convert all values to float + X = X.astype(float) + + # Impute missing values using KNN + imputer = KNNImputer(n_neighbors=5) + X_imputed = imputer.fit_transform(X) + X = pd.DataFrame(X_imputed, columns=X.columns) + + return X, y + +def calculate_mutual_information(X, y): + # Calculate mutual information for each feature + mi_scores = mutual_info_regression(X, y) + + # Create a DataFrame of features and their importance scores + feature_importance = pd.DataFrame({ + 'Feature': X.columns, + 'Importance': mi_scores + }) + + # Sort by importance + feature_importance = feature_importance.sort_values('Importance', ascending=False) + + # Print top features + print("\nFeature importance by mutual information:") + for i, (_, row) in enumerate(feature_importance.head(10).iterrows(), 1): + print(f"{i}. {row['Feature']} - importance: {row['Importance']:.4f}") + + # Visualize feature importance + plt.figure(figsize=(10, 6)) + plt.barh(feature_importance['Feature'], feature_importance['Importance']) + plt.xlabel('Mutual Information Score') + plt.title('Feature Importance') + plt.tight_layout() + plt.savefig('feature_importance.png') + + return feature_importance + +def find_best_features_rfe(X, y, n_features=5): + print("\nRunning Recursive Feature Elimination...") + model = RandomForestRegressor(n_estimators=100, random_state=42) + selector = RFE(estimator=model, n_features_to_select=n_features, step=1) + selector.fit(X, y) + + # Get the selected features + selected_features = X.columns[selector.support_].tolist() + print(f"Selected features (RFE): {selected_features}") + return selected_features + +def select_uncorrelated_features(X, feature_importances, top_n=5, correlation_threshold=0.75): + print(f"\nSelecting {top_n} uncorrelated features...") + + # Convert feature importances to dictionary for easy lookup + importance_dict = {row['Feature']: row['Importance'] + for _, row in feature_importances.iterrows()} + + # Calculate correlation matrix + corr_matrix = X.corr().abs() + + # Start with the most important feature + top_feature = feature_importances.iloc[0]['Feature'] + selected = [top_feature] + print(f"Starting with top feature: {top_feature}") + + candidates = X.columns.tolist() + candidates.remove(top_feature) + + # Greedy selection of uncorrelated features + while len(selected) < top_n and candidates: + best_feature = None + max_importance = -np.inf + + for feature in candidates: + # Check correlation with already selected features + correlations = [corr_matrix.loc[feature, sel] for sel in selected] + if max(correlations) < correlation_threshold: + # Use mutual information score as importance + importance = importance_dict[feature] + if importance > max_importance: + max_importance = importance + best_feature = feature + + if best_feature: + selected.append(best_feature) + candidates.remove(best_feature) + print(f"Added {best_feature} (correlation with selected: {max([corr_matrix.loc[best_feature, sel] for sel in selected[:-1]])})") + else: + # If no feature satisfies correlation threshold, relax the threshold + old_threshold = correlation_threshold + correlation_threshold += 0.05 + print(f"No features satisfy threshold {old_threshold}, relaxing to {correlation_threshold}") + + print(f"Selected uncorrelated features: {selected}") + return selected + +def evaluate_feature_combination(X, y, feature_combo): + X_subset = X[list(feature_combo)] + model = RandomForestRegressor(n_estimators=100, random_state=42) + scores = cross_val_score(model, X_subset, y, cv=5, scoring='r2') + return scores.mean(), scores.std() + +def find_best_feature_combination(X, y, n_features=5, top_k=10): + print(f"\nFinding the best combination of {n_features} features...") + + # Always include frequency if available + freq_col = 'cpu_frequency_mhz' if 'cpu_frequency_mhz' in X.columns else None + + # For efficiency, limit the search to the top_k most important features + feature_importances = calculate_mutual_information(X, y) + top_features = feature_importances.head(top_k)['Feature'].tolist() + + if freq_col and freq_col not in top_features: + top_features = [feat for feat in top_features if feat != freq_col] + top_features = top_features[:n_features-1] # Make room for frequency + else: + top_features = top_features[:n_features] + + best_score = -np.inf + best_combo = None + best_std = 0 + + # Try all combinations + total_combos = sum(1 for _ in combinations(top_features, n_features if not freq_col else n_features-1)) + print(f"Testing {total_combos} combinations...") + + for i, combo in enumerate(combinations(top_features, n_features if not freq_col else n_features-1)): + if i % 10 == 0: + print(f"Evaluating combination {i+1}/{total_combos}") + + features = list(combo) + if freq_col and freq_col not in features: + features.append(freq_col) + + score, std = evaluate_feature_combination(X, y, features) + + if score > best_score: + best_score = score + best_std = std + best_combo = features + + print(f"Best feature combination (R²={best_score:.4f}±{best_std:.4f}): {best_combo}") + return best_combo, best_score, best_std + +def compare_feature_subsets(X, y, n_features=5): + print("\nComparing different feature selection methods...") + + # Always include frequency if available + freq_col = 'cpu_frequency_mhz' if 'cpu_frequency_mhz' in X.columns else None + + # Get feature importances + feature_importances = calculate_mutual_information(X, y) + + # Method 1: Top features by mutual information + if freq_col: + mi_top = feature_importances['Feature'].tolist() + if freq_col in mi_top: + mi_top.remove(freq_col) + mi_top = mi_top[:n_features-1] + [freq_col] + else: + mi_top = feature_importances['Feature'].tolist()[:n_features] + + # Method 2: Recursive Feature Elimination + rfe_selected = find_best_features_rfe(X, y, n_features if not freq_col else n_features-1) + if freq_col and freq_col not in rfe_selected: + rfe_selected.append(freq_col) + + # Method 3: Uncorrelated features + uncorrelated = select_uncorrelated_features(X, feature_importances, + n_features if not freq_col else n_features-1) + if freq_col and freq_col not in uncorrelated: + uncorrelated.append(freq_col) + + # Method 4: Best combination (limited search) + best_combo, _, _ = find_best_feature_combination(X, y, n_features, top_k=10) + + # Define subsets to test + subsets = { + 'mutual_info_top': mi_top, + 'rfe_selected': rfe_selected, + 'uncorrelated': uncorrelated, + 'best_combination': best_combo + } + + # Create a visual representation of the correlation matrix + plt.figure(figsize=(12, 10)) + corr_matrix = X.corr() + plt.imshow(corr_matrix, cmap='coolwarm', interpolation='none', vmin=-1, vmax=1) + plt.colorbar() + plt.xticks(range(len(X.columns)), X.columns, rotation=90) + plt.yticks(range(len(X.columns)), X.columns) + plt.title('Feature Correlation Matrix') + plt.tight_layout() + plt.savefig('correlation_matrix.png') + + # Evaluate each subset with a RandomForestRegressor + results = {} + for name, features in subsets.items(): + X_subset = X[features] + scores = cross_val_score(RandomForestRegressor(n_estimators=100, random_state=42), + X_subset, y, cv=5, scoring='r2') + + results[name] = { + 'r2_mean': scores.mean(), + 'r2_std': scores.std(), + 'features': features + } + + # Print results + print("\nResults for different feature subsets:") + for name, res in results.items(): + print(f"{name}: R²={res['r2_mean']:.4f}±{res['r2_std']:.4f}, Features: {res['features']}") + + # Create bar chart of results + plt.figure(figsize=(10, 6)) + methods = list(results.keys()) + scores = [results[m]['r2_mean'] for m in methods] + errors = [results[m]['r2_std'] for m in methods] + + plt.bar(methods, scores, yerr=errors, capsize=5) + plt.ylabel('R² Score') + plt.title('Performance of Different Feature Selection Methods') + plt.ylim(0.90, 1.0) # Adjust as needed + plt.tight_layout() + plt.savefig('feature_selection_comparison.png') + + # Find the best method + best_method = max(results.items(), key=lambda x: x[1]['r2_mean']) + print(f"\nBest method: {best_method[0]} with R²={best_method[1]['r2_mean']:.4f}") + print(f"Final recommended feature set: {best_method[1]['features']}") + + return results, best_method[1]['features'] + +def final_model_evaluation(X, y, selected_features): + print("\nEvaluating final model with selected features...") + + # Prepare data + X_selected = X[selected_features] + X_train, X_test, y_train, y_test = train_test_split( + X_selected, y, test_size=0.2, random_state=42) + + # Train model + model = RandomForestRegressor(n_estimators=100, random_state=42) + model.fit(X_train, y_train) + + # Evaluate + y_pred = model.predict(X_test) + r2 = r2_score(y_test, y_pred) + rmse = np.sqrt(mean_squared_error(y_test, y_pred)) + + print(f"Final model performance: R²={r2:.4f}, RMSE={rmse:.6f}") + + # Plot actual vs predicted + plt.figure(figsize=(8, 8)) + plt.scatter(y_test, y_pred, alpha=0.5) + plt.plot([min(y_test), max(y_test)], [min(y_test), max(y_test)], 'r--') + plt.xlabel('Actual Power Usage (J)') + plt.ylabel('Predicted Power Usage (J)') + plt.title(f'Actual vs Predicted Power Usage with Selected Features\nR²={r2:.4f}, RMSE={rmse:.6f}') + plt.tight_layout() + plt.savefig('final_model_performance.png') + + # Feature importances in the final model + importances = model.feature_importances_ + indices = np.argsort(importances)[::-1] + + plt.figure(figsize=(10, 6)) + plt.title('Feature Importances in Final Model') + plt.bar(range(X_selected.shape[1]), importances[indices]) + plt.xticks(range(X_selected.shape[1]), np.array(selected_features)[indices], rotation=90) + plt.tight_layout() + plt.savefig('final_model_feature_importances.png') + + return model, r2, rmse + +def main(): + # Load and preprocess data + print("Loading and preprocessing data...") + X, y = load_and_preprocess_data('logs.csv') + + # Compare different feature selection methods + results, best_features = compare_feature_subsets(X, y, n_features=5) + + # Evaluate final model with selected features + model, r2, rmse = final_model_evaluation(X, y, best_features) + + print("\nAnalysis complete! The optimal set of 5 performance counters has been identified.") + print(f"Final selected features: {best_features}") + print(f"Model performance: R²={r2:.4f}, RMSE={rmse:.6f}") + +if __name__ == "__main__": + main() |
