goat / Scripts /model_soup.py
LightChuan's picture
Upload folder using huggingface_hub
6a5bb7e verified
Raw
History Blame Contribute Delete
3.34 kB
"""Model Soup: average weights of multiple seed models → single model.
Zero inference cost vs single model, near-ensemble performance.
"""
import sys, os
import copy
import numpy as np
PROJECT_DIR = '/home/user/goat'
os.chdir(PROJECT_DIR)
sys.path.insert(0, PROJECT_DIR)
import torch
from ultralytics import YOLO
from tqdm import tqdm
def soup_models(model_paths, output_path):
"""Average weights of multiple YOLO models."""
models = [YOLO(p) for p in model_paths]
base = models[0]
# Collect state dicts
state_dicts = []
for m in models:
sd = m.model.state_dict()
state_dicts.append(sd)
# Average
avg_sd = {}
for key in state_dicts[0].keys():
if any(kw in key.lower() for kw in ['num_batches_tracked', 'running_mean', 'running_var']):
# Use first model's BN stats
avg_sd[key] = state_dicts[0][key].clone()
elif 'bn' in key.lower() or 'batch_norm' in key.lower():
# Average BN stats
avg_sd[key] = sum(sd[key] for sd in state_dicts) / len(state_dicts)
else:
avg_sd[key] = sum(sd[key] for sd in state_dicts) / len(state_dicts)
base.model.load_state_dict(avg_sd)
base.model.eval()
base.save(output_path)
print(f'Model Soup saved to {output_path}')
return output_path
def evaluate_model(model_path, name):
"""Quick eval on val set."""
model = YOLO(model_path)
results = model.val(data='Data/Detection_dataset/dataset.yaml', imgsz=1536, batch=2, verbose=False)
return float(results.box.map50), float(results.box.map)
def main():
model_paths = [
'runs/detect/Detection_experiments/v6_1_s_refined/weights/best.pt',
'runs/detect/Detection_experiments/v12_seed_42/weights/best.pt',
'runs/detect/Detection_experiments/v12_seed_123/weights/best.pt',
]
# Verify all exist
valid = [p for p in model_paths if os.path.exists(p)]
print(f'Models available: {len(valid)}/{len(model_paths)}')
for p in valid:
print(f' {p}')
# Eval individual models
print(f'\n{"="*50}')
print('Individual models:')
singles = {}
for p in valid:
name = p.split('/')[-3]
m50, m5095 = evaluate_model(p, name)
singles[name] = (m50, m5095)
print(f' {name}: mAP50={m50:.4f}, mAP50-95={m5095:.4f}')
# Model Soup
print(f'\n{"="*50}')
print('Model Soup experiments:')
# Try all combinations
import itertools
for r in range(2, len(valid) + 1):
for combo in itertools.combinations(range(len(valid)), r):
paths = [valid[i] for i in combo]
names = [p.split('/')[-3] for p in paths]
label = ' + '.join(n.split('_')[0] + '_' + n.split('_')[-1][:4] for n in names)
output = f'runs/soup/soup_{len(paths)}models.pt'
os.makedirs('runs/soup', exist_ok=True)
soup_models(paths, output)
m50, m5095 = evaluate_model(output, label)
delta = m5095 - singles[names[0]][1]
print(f' {label}: mAP50={m50:.4f}, mAP50-95={m5095:.4f} (d={delta:+.4f} vs {names[0]})')
# WBF baseline for comparison
print(f'\n{"="*50}')
print('WBF Ensemble (for comparison):')
print(' (Run eval_ensemble.py for full WBF results)')
if __name__ == '__main__':
main()