| import os |
| import torch |
| import numpy as np |
| import pandas as pd |
| import matplotlib |
| matplotlib.use('Agg') |
| import matplotlib.pyplot as plt |
| import seaborn as sns |
| from torch.utils.data import DataLoader |
| import random |
| import optuna |
| import gc |
| import warnings |
|
|
| from Ramandataset import RamanDataset |
| |
| from Raman_Task import ( |
| load_real_data, |
| load_class_names, |
| stratified_split_with_minimum_samples, |
| augment_minority_classes, |
| load_mae_model_for_classification, |
| train_predictor, |
| unique |
| ) |
|
|
| def mm_to_inches(mm): |
| return mm / 25.4 |
|
|
| def run_hyperparameter_optimization( |
| num_trials=10, |
| epochs_per_trial=50, |
| resume_existing=False, |
| study_suffix=None, |
| ): |
| """ |
| 执行超参数随机搜索,并生成报告和可视化图表。 |
| """ |
| |
| |
| |
| |
| |
| base_path = '/home/lion/Desktop/splendid_lion/project/2025/multi_source/Raman/support_file/data/' |
| |
| data_path = os.path.join(base_path, 'rruff/classifier_0_3500_spectra.npy') |
| labels_path = os.path.join(base_path, 'rruff/classifier_0_3500_labels.npy') |
| wavenumbers_path = os.path.join(base_path, 'rruff/classifier_0_3500_wavenumbers.npy') |
|
|
| print("Loading data for optimization...") |
| spectra, labels, wavenumbers = load_real_data(data_path, labels_path=labels_path, wavenumbers_path=wavenumbers_path, normalize=True) |
| num_classes = len(unique(labels)) |
| input_length = spectra.shape[1] |
| |
| |
| from sklearn.preprocessing import LabelEncoder |
| y = np.array(labels) |
| le = LabelEncoder() |
| y_encoded = le.fit_transform(y) |
| |
| X_train, X_val, X_test, y_train, y_val, y_test = stratified_split_with_minimum_samples( |
| spectra, y_encoded, test_size=0.15, val_size=0.15, min_samples_per_class=1, random_state=42 |
| ) |
| |
| |
| X_train_augmented, y_train_augmented = augment_minority_classes( |
| X_train, y_train, min_samples=30, target_samples=159 |
| ) |
| |
| |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
| print(f"\n🚀 Starting Hyperparameter Optimization with Optuna ({num_trials} trials)...") |
| print(f"Device: {device}") |
| |
| |
| results_list = [] |
|
|
| def objective(trial): |
| |
| gc.collect() |
| torch.cuda.empty_cache() |
|
|
| |
| lr = trial.suggest_categorical('lr', [1e-3, 5e-4, 1e-4, 5e-5, 1e-5]) |
| batch_size = trial.suggest_categorical('batch_size', [16, 32]) |
| weight_decay = trial.suggest_categorical('weight_decay', [1e-3, 5e-4, 1e-4, 5e-5, 1e-5]) |
| mask_ratio = trial.suggest_categorical('mask_ratio', [0.25, 0.5, 0.75]) |
|
|
| print(f"\n--- Trial {trial.number} ---") |
| print(f"Params: lr={lr}, batch_size={batch_size}, weight_decay={weight_decay}, mask_ratio={mask_ratio}") |
|
|
| |
| classifier = None |
| mae_model = None |
| train_loader = None |
| val_loader = None |
| test_loader = None |
|
|
| try: |
| |
| |
| train_dataset = RamanDataset(X_train_augmented, None, labels=y_train_augmented, transform=None, is_train=True) |
| train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, drop_last=True, num_workers=0) |
| |
| val_dataset = RamanDataset(X_val, None, labels=y_val, transform=None, is_train=True) |
| val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False, num_workers=0) |
| |
| test_dataset = RamanDataset(X_test, None, labels=y_test, transform=None, is_train=True) |
| test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, num_workers=0) |
| |
| |
| para = {"embedding_dim": 512, "num_heads": 16, "num_layers": 12, "patch_num": 100} |
| pretrained_path = ( |
| f"/home/lion/Desktop/splendid_lion/project/2025/multi_source/Raman/support_file/model/" |
| f"0.0001_{mask_ratio}_{para['embedding_dim']}_{para['num_heads']}_{para['num_layers']}_{para['patch_num']}/" |
| f"Fine_tuned_baseonALL.pth" |
| ) |
| |
| if not os.path.exists(pretrained_path): |
| print(f"⚠️ Trial {trial.number} pruned: pretrained model not found at {pretrained_path}") |
| raise optuna.exceptions.TrialPruned() |
|
|
| classifier, encoder, decoder, mae_model = load_mae_model_for_classification( |
| pretrained_path, input_length, para['patch_num'], para['embedding_dim'], |
| para['num_layers'], para['num_heads'], num_classes, device |
| ) |
| |
| save_dir = os.path.join(base_path, f"optimization_results/trial_{trial.number}") |
| os.makedirs(save_dir, exist_ok=True) |
| |
| |
| |
| trained_model, _ = train_predictor( |
| classifier=classifier, |
| mae_model=mae_model, |
| train_loader=train_loader, |
| val_loader=val_loader, |
| test_loader=test_loader, |
| device=device, |
| epochs=epochs_per_trial, |
| lr=lr, |
| weight_decay=weight_decay, |
| patience=10, |
| save_dir=save_dir, |
| model_name=f"trial_{trial.number}", |
| freeze_encoder=False |
| ) |
| |
| |
| classifier.eval() |
| correct = 0 |
| total = 0 |
| with torch.no_grad(): |
| for inputs, _, labels in val_loader: |
| inputs, labels = inputs.to(device), labels.to(device) |
| outputs, _ = classifier(inputs) |
| _, predicted = torch.max(outputs.data, 1) |
| total += labels.size(0) |
| correct += (predicted == labels.squeeze()).sum().item() |
| |
| val_acc = correct / total |
| |
| |
| result_record = { |
| 'trial_id': trial.number, |
| 'val_accuracy': val_acc, |
| 'lr': lr, |
| 'batch_size': batch_size, |
| 'weight_decay': weight_decay, |
| 'mask_ratio': mask_ratio |
| } |
| results_list.append(result_record) |
|
|
| |
| csv_file = "optuna_results.csv" |
| df_current = pd.DataFrame([result_record]) |
| if not os.path.exists(csv_file): |
| df_current.to_csv(csv_file, index=False, mode='w') |
| else: |
| df_current.to_csv(csv_file, index=False, mode='a', header=False) |
| print(f"✅ Trial {trial.number} Finished. Acc: {val_acc:.4f}") |
| |
| return val_acc |
|
|
| |
| except RuntimeError as e: |
| error_msg = str(e) |
| if "out of memory" in error_msg: |
| print(f"⚠️ Trial {trial.number} Pruned due to CUDA OOM.") |
| |
| raise optuna.exceptions.TrialPruned() |
| elif "nan" in error_msg.lower(): |
| print(f"⚠️ Trial {trial.number} Pruned due to NaN loss.") |
| raise optuna.exceptions.TrialPruned() |
| else: |
| print(f"❌ Trial {trial.number} Failed with RuntimeError: {e}") |
| return 0.0 |
| |
| except Exception as e: |
| print(f"❌ Trial {trial.number} Failed with unknown error: {e}") |
| return 0.0 |
| |
| finally: |
| |
| if classifier is not None: |
| del classifier |
| if mae_model is not None: |
| del mae_model |
| |
| del train_loader, val_loader, test_loader |
| |
| gc.collect() |
| torch.cuda.empty_cache() |
| print(f"🧹 Trial {trial.number} Cleanup Done.") |
|
|
| |
| if study_suffix is None: |
| study_suffix = pd.Timestamp.now().strftime("%Y%m%d_%H%M%S") |
| if resume_existing: |
| study_name = "raman_optimization" |
| else: |
| study_name = f"raman_optimization_fresh_{study_suffix}" |
| |
| |
| storage_url = "sqlite:///{}.db".format(study_name) |
| |
| study = optuna.create_study( |
| direction="maximize", |
| study_name=study_name, |
| storage=storage_url, |
| load_if_exists=resume_existing |
| ) |
| |
| |
| |
| study.optimize(objective, n_trials=num_trials, n_jobs=1, gc_after_trial=True) |
|
|
| |
| print("\n" + "="*50) |
| print("HYPERPARAMETER OPTIMIZATION REPORT (Optuna)") |
| print("="*50) |
| |
| if len(study.trials) > 0: |
| best_trial = study.best_trial |
| print(f"Total Trials: {len(study.trials)}") |
| print(f"Best Validation Accuracy: {best_trial.value:.4f}") |
| print("\nBest Hyperparameters:") |
| for key, value in best_trial.params.items(): |
| print(f" {key}: {value}") |
| else: |
| print("No successful trials.") |
| print("="*50 + "\n") |
| |
| all_trials_data = [] |
| print(f"Total trials in DB: {len(study.trials)}") |
| for t in study.trials: |
| |
| if t.state == optuna.trial.TrialState.COMPLETE and t.value is not None and t.value > 0.0001: |
| all_trials_data.append({ |
| 'trial_id': t.number, |
| 'val_accuracy': t.value, |
| 'lr': t.params.get('lr'), |
| 'batch_size': t.params.get('batch_size'), |
| 'weight_decay': t.params.get('weight_decay'), |
| 'mask_ratio': t.params.get('mask_ratio') |
| }) |
| |
| if all_trials_data: |
| df_results = pd.DataFrame(all_trials_data) |
| print(f"Visualizing {len(df_results)} total trials from database.") |
| df_results.to_csv("optuna_results_final.csv", index=False) |
| visualize_hyperparameters_full_width(df_results) |
| else: |
| print("No successful trials found in study.") |
|
|
| def visualize_hyperparameters_full_width(df): |
| """ |
| 绘制通栏大小 (170mm 宽度) 的超参数分析图。 |
| """ |
| |
| plt.rcParams.update({ |
| 'font.size': 7, |
| 'axes.labelsize': 8, |
| 'xtick.labelsize': 7, |
| 'ytick.labelsize': 7, |
| 'legend.fontsize': 7, |
| 'font.family': 'sans-serif', |
| 'lines.linewidth': 1.0, |
| 'axes.linewidth': 0.8 |
| }) |
| |
| fig, axes = plt.subplots(2, 2, figsize=(mm_to_inches(170), mm_to_inches(120)), constrained_layout=True) |
| axes = axes.flatten() |
| |
| |
| median_props = dict(linewidth=1.5, color='firebrick') |
|
|
| |
| |
| sns.boxplot(x='lr', y='val_accuracy', data=df, ax=axes[0], palette="Blues", linewidth=0.8, showfliers=False, medianprops=median_props) |
| sns.stripplot(x='lr', y='val_accuracy', data=df, ax=axes[0], color='darkblue', alpha=0.6, jitter=0.1, size=4) |
| axes[0].set_xlabel("Learning Rate") |
| axes[0].set_ylabel("Validation Accuracy") |
| axes[0].set_title("(a) Impact of Learning Rate") |
| axes[0].grid(axis='y', linestyle='--', alpha=0.5) |
|
|
| |
| sns.boxplot(x='batch_size', y='val_accuracy', data=df, ax=axes[1], palette="Greens", linewidth=0.8, showfliers=False, medianprops=median_props) |
| sns.stripplot(x='batch_size', y='val_accuracy', data=df, ax=axes[1], color='darkgreen', alpha=0.6, jitter=0.1, size=4) |
| axes[1].set_xlabel("Batch Size") |
| axes[1].set_ylabel("") |
| axes[1].set_title("(b) Impact of Batch Size") |
| axes[1].grid(axis='y', linestyle='--', alpha=0.5) |
|
|
| |
| sns.boxplot(x='weight_decay', y='val_accuracy', data=df, ax=axes[2], palette="Oranges", linewidth=0.8, showfliers=False, medianprops=median_props) |
| sns.stripplot(x='weight_decay', y='val_accuracy', data=df, ax=axes[2], color='darkred', alpha=0.6, jitter=0.1, size=4) |
| axes[2].set_xlabel("Weight Decay") |
| axes[2].set_ylabel("") |
| axes[2].set_title("(c) Impact of Weight Decay") |
| axes[2].grid(axis='y', linestyle='--', alpha=0.5) |
|
|
| |
| sns.boxplot(x='mask_ratio', y='val_accuracy', data=df, ax=axes[3], palette="Purples", linewidth=0.8, showfliers=False, medianprops=median_props) |
| sns.stripplot(x='mask_ratio', y='val_accuracy', data=df, ax=axes[3], color='indigo', alpha=0.6, jitter=0.1, size=4) |
| axes[3].set_xlabel("Mask Ratio") |
| axes[3].set_ylabel("") |
| axes[3].set_title("(d) Impact of Mask Ratio") |
| axes[3].grid(axis='y', linestyle='--', alpha=0.5) |
|
|
| for ax in axes: |
| for spine in ax.spines.values(): |
| spine.set_visible(True) |
| spine.set_edgecolor('black') |
| spine.set_linewidth(0.8) |
|
|
| save_path = "hyperparameter_optimization_analysis.png" |
| plt.savefig(save_path, dpi=300, bbox_inches='tight') |
| print(f"Visualization saved to {save_path}") |
|
|
| if __name__ == "__main__": |
| |
| run_hyperparameter_optimization(num_trials=25, epochs_per_trial=30, resume_existing=False) |