Files
StarPilot/scripts/speed_limit_vision/expand_classifier_checkpoint.py
T
firestar5683 8b7c2b5f36 oo buddy boy
2026-07-20 13:18:27 -05:00

75 lines
2.6 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import argparse
from pathlib import Path
import torch
if __package__ in (None, ""):
import sys
sys.path.insert(0, str(Path(__file__).resolve().parent))
from common import EXTENDED_CLASSIFIER_SPEED_VALUES # type: ignore # noqa: TID251
else:
from .common import EXTENDED_CLASSIFIER_SPEED_VALUES
DEFAULT_CLASSES = tuple(str(value) for value in EXTENDED_CLASSIFIER_SPEED_VALUES) + ("reject",)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Expand a YOLO classification head while preserving existing class rows.")
parser.add_argument("--model", type=Path, required=True, help="Existing Ultralytics classification checkpoint.")
parser.add_argument("--output", type=Path, required=True, help="Expanded checkpoint path.")
parser.add_argument("--classes", nargs="+", default=DEFAULT_CLASSES, help="New output classes in dataset index order.")
return parser.parse_args()
def main() -> int:
from ultralytics import YOLO
args = parse_args()
model = YOLO(str(args.model.expanduser().resolve()))
old_names = {int(index): str(name) for index, name in model.names.items()}
new_names = dict(enumerate(args.classes))
if len(set(new_names.values())) != len(new_names):
raise ValueError("--classes contains duplicate names")
if not set(old_names.values()).issubset(new_names.values()):
missing = sorted(set(old_names.values()) - set(new_names.values()))
raise ValueError(f"Expanded classes omit existing outputs: {missing}")
head = model.model.model[-1]
old_linear = head.linear
new_linear = torch.nn.Linear(
old_linear.in_features,
len(new_names),
bias=old_linear.bias is not None,
device=old_linear.weight.device,
dtype=old_linear.weight.dtype,
)
new_index_by_name = {name: index for index, name in new_names.items()}
with torch.no_grad():
for old_index, name in old_names.items():
new_index = new_index_by_name[name]
new_linear.weight[new_index].copy_(old_linear.weight[old_index])
if old_linear.bias is not None:
new_linear.bias[new_index].copy_(old_linear.bias[old_index])
head.linear = new_linear
head.np = sum(parameter.numel() for parameter in head.parameters())
model.model.names = new_names
model.model.yaml["nc"] = len(new_names)
model.model.args["classes"] = None
output = args.output.expanduser().resolve()
output.parent.mkdir(parents=True, exist_ok=True)
model.save(str(output))
print(f"Expanded {len(old_names)} outputs to {len(new_names)}: {output}")
print(new_names)
return 0
if __name__ == "__main__":
raise SystemExit(main())