- shufflenet_v2_x1_0 列在支持清单里却直接 AttributeError:ShuffleNetV2 既不是 扁平 children 也没有 .features,需要按 conv1/maxpool/stage2..4/conv5 自己拼。 该 bug 同时影响 SimCLR 与检测模型 - 新增参数化测试覆盖清单里的每个 arch:能前向、声明的 out_channels 与实际一致 - 配置支持 `_replace_: true`:子字典整块替换而不与 base 合并。换骨架时 tiny_conv 的 widths/depths 会残留并传给新骨架,此前无论用 --set 还是 _base_ 都绕不开,只能改原文件 - Registry.build 提前校验字段,报错直接指出是哪些字段不被接受、该怎么办, 取代裸 TypeError - README 补充可选骨架表与三种改法(含不该用 --set 改 name 的说明) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
75 lines
2.8 KiB
Python
75 lines
2.8 KiB
Python
"""极简注册表:按名字注册 / 构建模型、数据集等组件。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import inspect
|
|
from collections.abc import Callable, Iterator
|
|
from typing import Any, TypeVar
|
|
|
|
T = TypeVar("T")
|
|
|
|
|
|
class Registry:
|
|
def __init__(self, name: str) -> None:
|
|
self.name = name
|
|
self._entries: dict[str, Callable[..., Any]] = {}
|
|
|
|
def register(self, name: str | None = None) -> Callable[[T], T]:
|
|
"""装饰器;`name` 缺省时使用被装饰对象的 __name__。"""
|
|
|
|
def wrapper(obj: T) -> T:
|
|
key = name or getattr(obj, "__name__", None)
|
|
if key is None:
|
|
raise ValueError(f"无法推断注册名: {obj!r}")
|
|
if key in self._entries:
|
|
raise KeyError(f"{self.name} 中已存在同名条目: {key}")
|
|
self._entries[key] = obj # type: ignore[assignment]
|
|
return obj
|
|
|
|
return wrapper
|
|
|
|
def get(self, name: str) -> Callable[..., Any]:
|
|
if name not in self._entries:
|
|
raise KeyError(f"{self.name} 中找不到 '{name}',可用: {sorted(self._entries)}")
|
|
return self._entries[name]
|
|
|
|
def build(self, cfg: dict, **kwargs: Any) -> Any:
|
|
"""按 `{"name": ..., 其余作为构造参数}` 构建对象。"""
|
|
cfg = dict(cfg)
|
|
name = cfg.pop("name", None)
|
|
if name is None:
|
|
raise KeyError(f"构建 {self.name} 需要 'name' 字段: {cfg}")
|
|
target = self.get(name)
|
|
params = {**cfg, **kwargs}
|
|
self._check_params(name, target, params)
|
|
return target(**params)
|
|
|
|
@staticmethod
|
|
def _check_params(name: str, target: Callable[..., Any], params: dict) -> None:
|
|
"""提前挡下不认识的字段,给出比裸 TypeError 更可操作的提示。
|
|
|
|
最常见的触发方式:用 `--set xxx.name=其他骨架` 只改了名字,
|
|
yaml 里原骨架的专有字段(如 tiny_conv 的 widths/depths)仍然留在配置里。
|
|
"""
|
|
signature = inspect.signature(target)
|
|
if any(p.kind is p.VAR_KEYWORD for p in signature.parameters.values()):
|
|
return
|
|
unknown = sorted(set(params) - set(signature.parameters))
|
|
if not unknown:
|
|
return
|
|
accepted = sorted(p for p in signature.parameters if p != "self")
|
|
raise TypeError(
|
|
f"构建 '{name}' 时收到它不接受的字段 {unknown}。它接受的是 {accepted}。"
|
|
"如果你是用 --set 改了 name,注意 yaml 里原来那个组件的专有字段仍会保留——"
|
|
"换组件请直接编辑配置文件、整块替换。"
|
|
)
|
|
|
|
def __contains__(self, name: object) -> bool:
|
|
return name in self._entries
|
|
|
|
def __iter__(self) -> Iterator[str]:
|
|
return iter(self._entries)
|
|
|
|
def __len__(self) -> int:
|
|
return len(self._entries)
|