Instructions to use dsaint31/bb_mlp_224 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use dsaint31/bb_mlp_224 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-classification", model="dsaint31/bb_mlp_224") pipe("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/hub/parrots.png")# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("dsaint31/bb_mlp_224", device_map="auto") - Notebooks
- Google Colab
- Kaggle
Add/Update backbone checkpoints (count=6)
Browse files- manifest_20260210_111913.json +41 -0
- models/google__efficientnet-b0/config.json +2 -1
- models/google__efficientnet-b0/ds_cfg.py +136 -45
- models/google__efficientnet-b0/ds_model.py +167 -167
- models/google__efficientnet-b0/ds_proc.py +332 -0
- models/google__efficientnet-b0/model.safetensors +1 -1
- models/google__efficientnet-b0/preprocessor_config.json +8 -0
- models/google__vit-base-patch16-224/config.json +2 -1
- models/google__vit-base-patch16-224/ds_cfg.py +136 -45
- models/google__vit-base-patch16-224/ds_model.py +167 -167
- models/google__vit-base-patch16-224/ds_proc.py +332 -0
- models/google__vit-base-patch16-224/model.safetensors +1 -1
- models/google__vit-base-patch16-224/preprocessor_config.json +8 -0
- models/microsoft__resnet-50/config.json +2 -1
- models/microsoft__resnet-50/ds_cfg.py +136 -45
- models/microsoft__resnet-50/ds_model.py +167 -167
- models/microsoft__resnet-50/ds_proc.py +332 -0
- models/microsoft__resnet-50/model.safetensors +1 -1
- models/microsoft__resnet-50/preprocessor_config.json +8 -0
- models/microsoft__swin-tiny-patch4-window7-224/config.json +2 -1
- models/microsoft__swin-tiny-patch4-window7-224/ds_cfg.py +136 -45
- models/microsoft__swin-tiny-patch4-window7-224/ds_model.py +167 -167
- models/microsoft__swin-tiny-patch4-window7-224/ds_proc.py +332 -0
- models/microsoft__swin-tiny-patch4-window7-224/model.safetensors +1 -1
- models/microsoft__swin-tiny-patch4-window7-224/preprocessor_config.json +8 -0
- models/timm__densenet121.tv_in1k/config.json +2 -1
- models/timm__densenet121.tv_in1k/ds_cfg.py +136 -45
- models/timm__densenet121.tv_in1k/ds_model.py +167 -167
- models/timm__densenet121.tv_in1k/ds_proc.py +332 -0
- models/timm__densenet121.tv_in1k/model.safetensors +1 -1
- models/timm__densenet121.tv_in1k/preprocessor_config.json +8 -0
- models/torchvision__densenet121/config.json +2 -1
- models/torchvision__densenet121/ds_cfg.py +136 -45
- models/torchvision__densenet121/ds_model.py +167 -167
- models/torchvision__densenet121/ds_proc.py +332 -0
- models/torchvision__densenet121/model.safetensors +1 -1
- models/torchvision__densenet121/preprocessor_config.json +8 -0
manifest_20260210_111913.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"timestamp": "20260210_111913",
|
| 3 |
+
"device": "mps",
|
| 4 |
+
"repo_id": "dsaint31/bb_mlp_224",
|
| 5 |
+
"revision": "main",
|
| 6 |
+
"tag": null,
|
| 7 |
+
"num_labels": 3,
|
| 8 |
+
"count": 6,
|
| 9 |
+
"items": [
|
| 10 |
+
{
|
| 11 |
+
"backbone": "google/vit-base-patch16-224",
|
| 12 |
+
"subdir": "models/google__vit-base-patch16-224",
|
| 13 |
+
"dirname": "google__vit-base-patch16-224"
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"backbone": "microsoft/swin-tiny-patch4-window7-224",
|
| 17 |
+
"subdir": "models/microsoft__swin-tiny-patch4-window7-224",
|
| 18 |
+
"dirname": "microsoft__swin-tiny-patch4-window7-224"
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"backbone": "microsoft/resnet-50",
|
| 22 |
+
"subdir": "models/microsoft__resnet-50",
|
| 23 |
+
"dirname": "microsoft__resnet-50"
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
"backbone": "google/efficientnet-b0",
|
| 27 |
+
"subdir": "models/google__efficientnet-b0",
|
| 28 |
+
"dirname": "google__efficientnet-b0"
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"backbone": "timm/densenet121.tv_in1k",
|
| 32 |
+
"subdir": "models/timm__densenet121.tv_in1k",
|
| 33 |
+
"dirname": "timm__densenet121.tv_in1k"
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"backbone": "torchvision/densenet121",
|
| 37 |
+
"subdir": "models/torchvision__densenet121",
|
| 38 |
+
"dirname": "torchvision__densenet121"
|
| 39 |
+
}
|
| 40 |
+
]
|
| 41 |
+
}
|
models/google__efficientnet-b0/config.json
CHANGED
|
@@ -24,10 +24,11 @@
|
|
| 24 |
"num_labels": 3,
|
| 25 |
"transformers_version": "5.1.0",
|
| 26 |
"ds_provenance": {
|
| 27 |
-
"created_at": "
|
| 28 |
"repo_id": "dsaint31/bb_mlp_224",
|
| 29 |
"subdir": "models/google__efficientnet-b0",
|
| 30 |
"wrapper_class": "BackboneWithMLPHeadForImageClassification",
|
|
|
|
| 31 |
"backbone_id": "google/efficientnet-b0",
|
| 32 |
"pretrained_injected": true,
|
| 33 |
"policy_init_pretrained_forbidden": true,
|
|
|
|
| 24 |
"num_labels": 3,
|
| 25 |
"transformers_version": "5.1.0",
|
| 26 |
"ds_provenance": {
|
| 27 |
+
"created_at": "20260210_111913",
|
| 28 |
"repo_id": "dsaint31/bb_mlp_224",
|
| 29 |
"subdir": "models/google__efficientnet-b0",
|
| 30 |
"wrapper_class": "BackboneWithMLPHeadForImageClassification",
|
| 31 |
+
"processor_class": "BackboneMLPHead224ImageProcessor",
|
| 32 |
"backbone_id": "google/efficientnet-b0",
|
| 33 |
"pretrained_injected": true,
|
| 34 |
"policy_init_pretrained_forbidden": true,
|
models/google__efficientnet-b0/ds_cfg.py
CHANGED
|
@@ -1,84 +1,175 @@
|
|
| 1 |
-
from typing import Literal, Union
|
| 2 |
-
|
| 3 |
-
from transformers import AutoConfig
|
| 4 |
from transformers import PretrainedConfig
|
|
|
|
| 5 |
from ds_meta import BackboneID, BACKBONE_META
|
| 6 |
|
| 7 |
-
|
| 8 |
-
# 3) Config
|
| 9 |
-
# ============================================================
|
| 10 |
class BackboneMLPHeadConfig(PretrainedConfig):
|
| 11 |
"""
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
----------
|
| 16 |
-
- transformers ๋ด๋ถ ๊ฒฝ๋ก์์ "๋ฌด์ธ์ ์์ฑ"์ด ๋ฐ์ํ ์ ์์ผ๋ฏ๋ก,
|
| 17 |
-
backbone_name_or_path=None์ธ ๊ฒฝ์ฐ๋ ๊ฒ์ฆ ์์ด ๊ธฐ๋ณธ๊ฐ๋ง ์ธํ
ํ๊ณ return
|
| 18 |
-
ํ๋ ๊ตฌํ์ด ๋ฐ๋์ ํ์ํจ.
|
| 19 |
-
- ๋จ, ์ค์ ์ฌ์ฉ์์๋ backbone_name_or_path๋ฅผ ๋ฐ๋์ ์ง์ ํด์ ์์ฑํด์ผ ํจ.
|
| 20 |
"""
|
| 21 |
|
| 22 |
-
#
|
|
|
|
| 23 |
model_type = "backbone-mlphead-224-fixed"
|
| 24 |
|
| 25 |
-
# ์์ฑ์์์ ๋ชจ๋ ์ธ์์ ๊ธฐ๋ณธ๊ฐ ๋ถ์ฌ ๋ฐ๋์ ํ์!
|
| 26 |
def __init__(
|
| 27 |
self,
|
| 28 |
backbone_name_or_path: BackboneID | None = None,
|
| 29 |
mlp_head_bottleneck: int = 256,
|
| 30 |
mlp_head_dropout: float = 0.2,
|
| 31 |
-
label2id: dict[str, int]|None = None,
|
| 32 |
-
id2label: dict[int, str]|None = None,
|
| 33 |
**kwargs,
|
| 34 |
):
|
| 35 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
if backbone_name_or_path is None:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
super().__init__(**kwargs)
|
|
|
|
|
|
|
|
|
|
| 38 |
self.backbone_name_or_path = None
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
#
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
return
|
| 44 |
|
| 45 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
if backbone_name_or_path not in BACKBONE_META:
|
| 47 |
raise ValueError(
|
| 48 |
f"Unsupported backbone_name_or_path={backbone_name_or_path}. "
|
| 49 |
f"Allowed: {sorted(BACKBONE_META.keys())}"
|
| 50 |
)
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
#
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
kwargs["num_labels"] = num_labels
|
| 62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
super().__init__(
|
| 64 |
-
label2id=
|
| 65 |
-
id2label=
|
| 66 |
**kwargs,
|
| 67 |
)
|
| 68 |
-
|
| 69 |
-
# 4) ์ค์: ๋ถ๋ชจ ํ๋์๋ num_labels ๋ฐ์(์ ์ฅ/๋ก๋ ์์ )
|
| 70 |
-
self.num_labels = num_labels
|
| 71 |
-
self.label2id = label2id
|
| 72 |
-
self.id2label = id2label
|
| 73 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
self.backbone_name_or_path = backbone_name_or_path
|
| 75 |
-
self.mlp_head_bottleneck
|
| 76 |
-
self.mlp_head_dropout
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
def to_dict(self):
|
|
|
|
|
|
|
| 79 |
output = super().to_dict()
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
return output
|
| 83 |
|
|
|
|
|
|
|
|
|
|
| 84 |
BackboneMLPHeadConfig.register_for_auto_class("AutoConfig")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from transformers import PretrainedConfig
|
| 2 |
+
|
| 3 |
from ds_meta import BackboneID, BACKBONE_META
|
| 4 |
|
| 5 |
+
|
|
|
|
|
|
|
| 6 |
class BackboneMLPHeadConfig(PretrainedConfig):
|
| 7 |
"""
|
| 8 |
+
Configuration for Backbone + MLP Head models.
|
| 9 |
+
|
| 10 |
+
Backbone + MLP Head ๋ชจ๋ธ์ ์ํ ์ค์ ํด๋์ค์
๋๋ค.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
"""
|
| 12 |
|
| 13 |
+
# This string is used by Hugging Face AutoConfig to identify the config class.
|
| 14 |
+
# Hugging Face AutoConfig๊ฐ ์ด config๋ฅผ ์๋ณํ๊ธฐ ์ํด ์ฌ์ฉํ๋ ๊ณ ์ ID์
๋๋ค.
|
| 15 |
model_type = "backbone-mlphead-224-fixed"
|
| 16 |
|
|
|
|
| 17 |
def __init__(
|
| 18 |
self,
|
| 19 |
backbone_name_or_path: BackboneID | None = None,
|
| 20 |
mlp_head_bottleneck: int = 256,
|
| 21 |
mlp_head_dropout: float = 0.2,
|
| 22 |
+
label2id: dict[str, int] | None = None,
|
| 23 |
+
id2label: dict[int, str] | None = None,
|
| 24 |
**kwargs,
|
| 25 |
):
|
| 26 |
+
# ============================================================
|
| 27 |
+
# 0) Guard for argument-less construction
|
| 28 |
+
# 0) ๋ฌด์ธ์ ์์ฑ ๊ฒฝ๋ก ๋ฐฉ์ด
|
| 29 |
+
# ============================================================
|
| 30 |
+
# Transformers may internally construct this config without arguments
|
| 31 |
+
# (e.g., during AutoConfig resolution or Hub loading).
|
| 32 |
+
# Transformers ๋ด๋ถ์์ AutoConfig ๋๋ Hub ๋ก๋ ๊ณผ์ ์ค
|
| 33 |
+
# ์ธ์ ์์ด config๋ฅผ ์์ฑํ๋ ๊ฒฝ๋ก๊ฐ ์ค์ ๋ก ์กด์ฌํฉ๋๋ค.
|
| 34 |
+
#
|
| 35 |
+
# In this case, we must NOT validate or raise errors.
|
| 36 |
+
# ์ด ๊ฒฝ์ฐ ๊ฒ์ฆ์ด๋ ์์ธ๋ฅผ ๋ฐ์์ํค๋ฉด ์ ๋ฉ๋๋ค.
|
| 37 |
+
#
|
| 38 |
+
# The goal here is to provide a minimal, serialization-safe config.
|
| 39 |
+
# ๋ชฉํ๋ ์ต์ํ์ ๊ฐ๋ง ์ฑ์ ์ง๋ ฌํ/์ญ์ง๋ ฌํ๊ฐ ๊นจ์ง์ง ์๊ฒ ํ๋ ๊ฒ์
๋๋ค.
|
| 40 |
if backbone_name_or_path is None:
|
| 41 |
+
# num_labels may be implicitly assumed by downstream code,
|
| 42 |
+
# so we explicitly set a safe default.
|
| 43 |
+
# num_labels๋ downstream ์ฝ๋์์ ์๋ฌต์ ์ผ๋ก ๊ฐ์ ๋๋ฏ๋ก
|
| 44 |
+
# ์์ ํ ๊ธฐ๋ณธ๊ฐ์ ๋ช
์์ ์ผ๋ก ๋ฃ์ด์ค๋๋ค.
|
| 45 |
+
if "num_labels" not in kwargs:
|
| 46 |
+
kwargs["num_labels"] = 0
|
| 47 |
+
|
| 48 |
super().__init__(**kwargs)
|
| 49 |
+
|
| 50 |
+
# Backbone is intentionally unset in this path.
|
| 51 |
+
# ์ด ๊ฒฝ๋ก์์๋ backbone์ ์๋์ ์ผ๋ก ์ค์ ํ์ง ์์ต๋๋ค.
|
| 52 |
self.backbone_name_or_path = None
|
| 53 |
+
|
| 54 |
+
# Store MLP head hyperparameters for completeness.
|
| 55 |
+
# MLP head ๊ด๋ จ ํ์ดํผํ๋ผ๋ฏธํฐ๋ ํํ ์ ์ง๋ฅผ ์ํด ์ ์ฅํฉ๋๋ค.
|
| 56 |
+
self.mlp_head_bottleneck = int(mlp_head_bottleneck)
|
| 57 |
+
self.mlp_head_dropout = float(mlp_head_dropout)
|
| 58 |
+
|
| 59 |
+
# Empty label mappings ensure stable save/load behavior.
|
| 60 |
+
# ๋น label ๋งคํ์ ์ ์ฅ/๋ก๋ ์์ ์ฑ์ ๋ณด์ฅํฉ๋๋ค.
|
| 61 |
+
self.label2id = {}
|
| 62 |
+
self.id2label = {}
|
| 63 |
+
self.num_labels = int(kwargs.get("num_labels", 0))
|
| 64 |
return
|
| 65 |
|
| 66 |
+
# ============================================================
|
| 67 |
+
# 1) Backbone whitelist validation
|
| 68 |
+
# 1) Backbone ํ์ดํธ๋ฆฌ์คํธ ๊ฒ์ฆ
|
| 69 |
+
# ============================================================
|
| 70 |
+
# Only backbones explicitly registered in BACKBONE_META are allowed.
|
| 71 |
+
# BACKBONE_META์ ๋ฑ๋ก๋ backbone๋ง ํ์ฉํฉ๋๋ค.
|
| 72 |
+
#
|
| 73 |
+
# This prevents accidental usage of unsupported or inconsistent backbones.
|
| 74 |
+
# ์ง์๋์ง ์๊ฑฐ๋ ๋ถ์ผ์นํ backbone ์ฌ์ฉ์ ์์ฒ ์ฐจ๋จํฉ๋๋ค.
|
| 75 |
if backbone_name_or_path not in BACKBONE_META:
|
| 76 |
raise ValueError(
|
| 77 |
f"Unsupported backbone_name_or_path={backbone_name_or_path}. "
|
| 78 |
f"Allowed: {sorted(BACKBONE_META.keys())}"
|
| 79 |
)
|
| 80 |
+
|
| 81 |
+
# ============================================================
|
| 82 |
+
# 2) Label mapping normalization
|
| 83 |
+
# 2) ๋ผ๋ฒจ ๋งคํ ์ ๊ทํ
|
| 84 |
+
# ============================================================
|
| 85 |
+
# Both label2id and id2label may be None during pure loading scenarios.
|
| 86 |
+
# ๋จ์ ๋ก๋(from_pretrained) ์ label2id/id2label์ด None์ผ ์ ์์ต๋๋ค.
|
| 87 |
+
#
|
| 88 |
+
# We allow this here to keep Hub loading and verification stable.
|
| 89 |
+
# Hub ๋ก๋ ๋ฐ ๊ฒ์ฆ ์์ ์ฑ์ ์ํด ์ฌ๊ธฐ์๋ ์ด๋ฅผ ํ์ฉํฉ๋๋ค.
|
| 90 |
+
#
|
| 91 |
+
# Fail-fast validation should happen at model or training level instead.
|
| 92 |
+
# ์ค์ ์ฌ์ฉ ๊ฒ์ฆ์ ๋ชจ๋ธ ๋๋ ํ์ต ๋จ๊ณ์์ fail-fast๋ก ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค.
|
| 93 |
+
if label2id is None and id2label is None:
|
| 94 |
+
# Respect num_labels if explicitly provided, otherwise default to 0.
|
| 95 |
+
# num_labels๊ฐ ์ฃผ์ด์ก๋ค๋ฉด ์กด์คํ๊ณ , ์๋๋ฉด 0์ผ๋ก ๋ก๋๋ค.
|
| 96 |
+
num_labels = int(kwargs.get("num_labels", 0))
|
| 97 |
+
label2id_norm: dict[str, int] = {}
|
| 98 |
+
id2label_norm: dict[int, str] = {}
|
| 99 |
+
else:
|
| 100 |
+
# If only one mapping is provided, derive the other.
|
| 101 |
+
# ํ๋๋ง ์ฃผ์ด์ง ๊ฒฝ์ฐ ๋๋จธ์ง๋ฅผ ์๋์ผ๋ก ์์ฑํฉ๋๋ค.
|
| 102 |
+
if id2label is None:
|
| 103 |
+
id2label = {v: k for k, v in label2id.items()}
|
| 104 |
+
if label2id is None:
|
| 105 |
+
label2id = {v: k for k, v in id2label.items()}
|
| 106 |
+
|
| 107 |
+
# Ensure both mappings are consistent in size.
|
| 108 |
+
# ๋ ๋งคํ์ ํฌ๊ธฐ๊ฐ ์ผ์นํ๋์ง ํ์ธํฉ๋๋ค.
|
| 109 |
+
if len(label2id) != len(id2label):
|
| 110 |
+
raise ValueError(
|
| 111 |
+
f"label2id/id2label size mismatch: "
|
| 112 |
+
f"{len(label2id)} vs {len(id2label)}"
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
num_labels = len(id2label)
|
| 116 |
+
label2id_norm = dict(label2id)
|
| 117 |
+
id2label_norm = dict(id2label)
|
| 118 |
+
|
| 119 |
+
# ============================================================
|
| 120 |
+
# 3) num_labels consistency enforcement
|
| 121 |
+
# 3) num_labels ์ผ๊ด์ฑ ๊ฐ์
|
| 122 |
+
# ============================================================
|
| 123 |
+
# If num_labels is provided via kwargs, it must match inferred labels.
|
| 124 |
+
# kwargs๋ก num_labels๊ฐ ๋ค์ด์จ ๊ฒฝ์ฐ ์ถ๋ก ๋ ๊ฐ๊ณผ ๋ฐ๋์ ์ผ์นํด์ผ ํฉ๋๋ค.
|
| 125 |
+
if "num_labels" in kwargs:
|
| 126 |
+
if (label2id is not None or id2label is not None) and int(kwargs["num_labels"]) != num_labels:
|
| 127 |
+
raise ValueError(
|
| 128 |
+
f"kwargs['num_labels']={kwargs['num_labels']} "
|
| 129 |
+
f"!= inferred num_labels={num_labels}"
|
| 130 |
+
)
|
| 131 |
+
else:
|
| 132 |
kwargs["num_labels"] = num_labels
|
| 133 |
|
| 134 |
+
# ============================================================
|
| 135 |
+
# 4) Parent initialization
|
| 136 |
+
# 4) ๋ถ๋ชจ ํด๋์ค ์ด๊ธฐํ
|
| 137 |
+
# ============================================================
|
| 138 |
+
# Initialize PretrainedConfig with normalized label mappings.
|
| 139 |
+
# ์ ๊ทํ๋ ๋ผ๋ฒจ ๋งคํ์ ์ฌ์ฉํ์ฌ PretrainedConfig๋ฅผ ์ด๊ธฐํํฉ๋๋ค.
|
| 140 |
super().__init__(
|
| 141 |
+
label2id=label2id_norm,
|
| 142 |
+
id2label=id2label_norm,
|
| 143 |
**kwargs,
|
| 144 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
+
# ============================================================
|
| 147 |
+
# 5) Explicit attribute assignment for save/load stability
|
| 148 |
+
# 5) ์ ์ฅ/๋ก๋ ์์ ์ฑ์ ์ํ ๋ช
์์ ์์ฑ ๊ณ ์
|
| 149 |
+
# ============================================================
|
| 150 |
+
# Explicitly reassign critical fields to avoid subtle serialization issues.
|
| 151 |
+
# ๋ฏธ๋ฌํ ์ง๋ ฌํ ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ํต์ฌ ํ๋๋ฅผ ๋ช
์์ ์ผ๋ก ๋ค์ ์ค์ ํฉ๋๋ค.
|
| 152 |
self.backbone_name_or_path = backbone_name_or_path
|
| 153 |
+
self.mlp_head_bottleneck = int(mlp_head_bottleneck)
|
| 154 |
+
self.mlp_head_dropout = float(mlp_head_dropout)
|
| 155 |
+
|
| 156 |
+
self.label2id = label2id_norm
|
| 157 |
+
self.id2label = id2label_norm
|
| 158 |
+
self.num_labels = int(kwargs["num_labels"])
|
| 159 |
|
| 160 |
def to_dict(self):
|
| 161 |
+
# Call the parent implementation first.
|
| 162 |
+
# ๋จผ์ ๋ถ๋ชจ ๊ตฌํ์ ํธ์ถํฉ๋๋ค.
|
| 163 |
output = super().to_dict()
|
| 164 |
+
|
| 165 |
+
# Force num_labels to be present and consistent.
|
| 166 |
+
# num_labels๊ฐ ๋ฐ๋์ ์กด์ฌํ๊ณ ์ผ๊ด๋๋๋ก ๊ฐ์ ํฉ๋๋ค.
|
| 167 |
+
output["num_labels"] = int(
|
| 168 |
+
getattr(self, "num_labels", output.get("num_labels", 0))
|
| 169 |
+
)
|
| 170 |
return output
|
| 171 |
|
| 172 |
+
|
| 173 |
+
# Register this config so it can be resolved via AutoConfig.
|
| 174 |
+
# AutoConfig๋ฅผ ํตํด ์ด config๊ฐ ํด์๋ ์ ์๋๋ก ๋ฑ๋กํฉ๋๋ค.
|
| 175 |
BackboneMLPHeadConfig.register_for_auto_class("AutoConfig")
|
models/google__efficientnet-b0/ds_model.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
# -*- coding: utf-8 -*-
|
| 3 |
|
| 4 |
-
|
| 5 |
|
| 6 |
-
from
|
| 7 |
-
from typing import Dict, Any, Optional, Tuple, Set, List
|
| 8 |
|
| 9 |
import torch
|
| 10 |
import torch.nn as nn
|
|
@@ -12,146 +11,88 @@ import torch.nn.functional as F
|
|
| 12 |
|
| 13 |
# --- transformers core ---
|
| 14 |
from transformers.modeling_utils import PreTrainedModel
|
| 15 |
-
from transformers import AutoModel, AutoConfig
|
| 16 |
from transformers.modeling_outputs import ImageClassifierOutput
|
| 17 |
|
| 18 |
# --- torchvision ---
|
| 19 |
from torchvision import models as tv_models
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
# - ๊ธฐ์กด ds_cfg.BackboneMLPHeadConfig์ ํ๋๋ช
์ ๋ง์ถ์์ต๋๋ค.
|
| 25 |
-
# - ์ด๋ฏธ ds_cfg.py๊ฐ ์๋ค๋ฉด: ์ด ๋ธ๋ก์ ์ง์ฐ๊ณ "from ds_cfg import BackboneMLPHeadConfig"๋ก ๋ฐ๊ฟ๋ ๋ฉ๋๋ค.
|
| 26 |
-
# ============================================================
|
| 27 |
-
class BackboneMLPHeadConfig(PretrainedConfig):
|
| 28 |
-
model_type = "backbone_mlphead"
|
| 29 |
-
|
| 30 |
-
def __init__(
|
| 31 |
-
self,
|
| 32 |
-
backbone_name_or_path: Optional[str] = None,
|
| 33 |
-
num_labels: int = 2,
|
| 34 |
-
id2label: Optional[Dict[int, str]] = None,
|
| 35 |
-
label2id: Optional[Dict[str, int]] = None,
|
| 36 |
-
mlp_head_bottleneck: int = 256,
|
| 37 |
-
mlp_head_dropout: float = 0.2,
|
| 38 |
-
**kwargs,
|
| 39 |
-
):
|
| 40 |
-
super().__init__(**kwargs)
|
| 41 |
-
self.backbone_name_or_path = backbone_name_or_path
|
| 42 |
-
self.num_labels = num_labels
|
| 43 |
-
self.id2label = id2label or {i: f"LABEL_{i}" for i in range(num_labels)}
|
| 44 |
-
self.label2id = label2id or {v: k for k, v in self.id2label.items()}
|
| 45 |
-
self.mlp_head_bottleneck = mlp_head_bottleneck
|
| 46 |
-
self.mlp_head_dropout = mlp_head_dropout
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
# ============================================================
|
| 50 |
-
# (1) BACKBONE_META
|
| 51 |
-
# - ์ด๋ฏธ ds_meta.py๊ฐ ์๋ค๋ฉด: ์ด ๋ธ๋ก์ ์ง์ฐ๊ณ "from ds_meta import BACKBONE_META"๋ก ๋ฐ๊ฟ๋ ๋ฉ๋๋ค.
|
| 52 |
-
# ============================================================
|
| 53 |
-
BACKBONE_META: Dict[str, Dict[str, Any]] = {
|
| 54 |
-
# transformers
|
| 55 |
-
"google/vit-base-patch16-224": {
|
| 56 |
-
"type": "vit",
|
| 57 |
-
"feat_rule": "cls",
|
| 58 |
-
"feat_dim": 768,
|
| 59 |
-
"has_bn": False,
|
| 60 |
-
"unfreeze": "last_n",
|
| 61 |
-
},
|
| 62 |
-
"microsoft/swin-tiny-patch4-window7-224": {
|
| 63 |
-
"type": "swin",
|
| 64 |
-
"feat_rule": "pool_or_mean",
|
| 65 |
-
"feat_dim": 768,
|
| 66 |
-
"has_bn": False,
|
| 67 |
-
"unfreeze": "last_n",
|
| 68 |
-
},
|
| 69 |
-
"microsoft/resnet-50": {
|
| 70 |
-
"type": "resnet",
|
| 71 |
-
"feat_rule": "pool_or_gap",
|
| 72 |
-
"feat_dim": 2048,
|
| 73 |
-
"has_bn": True,
|
| 74 |
-
"unfreeze": "last_n",
|
| 75 |
-
},
|
| 76 |
-
"google/efficientnet-b0": {
|
| 77 |
-
"type": "efficientnet",
|
| 78 |
-
"feat_rule": "pool_or_gap",
|
| 79 |
-
"feat_dim": 1280,
|
| 80 |
-
"has_bn": True,
|
| 81 |
-
"unfreeze": "last_n",
|
| 82 |
-
},
|
| 83 |
-
|
| 84 |
-
# densenet
|
| 85 |
-
"timm/densenet121.tv_in1k": {
|
| 86 |
-
"type": "timm_densenet",
|
| 87 |
-
"feat_rule": "timm_gap",
|
| 88 |
-
"feat_dim": 1024,
|
| 89 |
-
"has_bn": True,
|
| 90 |
-
"unfreeze": "last_n",
|
| 91 |
-
},
|
| 92 |
-
"torchvision/densenet121": {
|
| 93 |
-
"type": "torchvision_densenet",
|
| 94 |
-
"feat_rule": "torchvision_densenet_gap",
|
| 95 |
-
"feat_dim": 1024,
|
| 96 |
-
"has_bn": True,
|
| 97 |
-
"unfreeze": "last_n",
|
| 98 |
-
},
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
# ============================================================
|
| 103 |
-
# (2) MLP Head
|
| 104 |
-
# - ์ด๋ฏธ mlp_head.py๊ฐ ์๋ค๋ฉด: ์ด ๋ธ๋ก์ ์ง์ฐ๊ณ "from mlp_head import MLPHead"๋ก ๋ฐ๊ฟ๋ ๋ฉ๋๋ค.
|
| 105 |
-
# ============================================================
|
| 106 |
-
class MLPHead(nn.Module):
|
| 107 |
-
def __init__(self, in_dim: int, num_labels: int, bottleneck: int = 256, p: float = 0.2):
|
| 108 |
-
super().__init__()
|
| 109 |
-
self.fc2 = nn.Linear(in_dim, bottleneck)
|
| 110 |
-
self.act2 = nn.GELU()
|
| 111 |
-
self.drop2 = nn.Dropout(p)
|
| 112 |
-
self.fc3 = nn.Linear(bottleneck, num_labels)
|
| 113 |
-
|
| 114 |
-
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 115 |
-
x = self.drop2(self.act2(self.fc2(x)))
|
| 116 |
-
return self.fc3(x)
|
| 117 |
-
|
| 118 |
|
| 119 |
# ============================================================
|
| 120 |
# (3) Model: backbone + MLP head
|
| 121 |
-
#
|
| 122 |
-
# - __init__: backbone ๊ตฌ์กฐ(skeleton)๋ง ์์ฑ. pretrained๋ฅผ ์ ๋ ๋ก๋ํ์ง ์์.
|
| 123 |
-
# - pretrained ์ฃผ์
: model.load_backbone_pretrained_()๋ฅผ "fresh-start์์๋ง" ์ง์ ํธ์ถ.
|
| 124 |
-
# - from_pretrained override ์์: HF ๊ธฐ๋ณธ ๋ก๋๊ฐ checkpoint๋ฅผ ๊ทธ๋๋ก ๋ณต์.
|
| 125 |
# ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
|
|
|
|
|
| 127 |
config_class = BackboneMLPHeadConfig
|
| 128 |
|
| 129 |
def __init__(self, config: BackboneMLPHeadConfig):
|
|
|
|
|
|
|
| 130 |
super().__init__(config)
|
| 131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
if config.backbone_name_or_path is None:
|
| 133 |
-
raise ValueError(
|
|
|
|
|
|
|
|
|
|
| 134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
self._meta = BACKBONE_META[config.backbone_name_or_path]
|
| 136 |
|
| 137 |
-
#
|
|
|
|
| 138 |
self.backbone = self._build_backbone_skeleton(config.backbone_name_or_path)
|
| 139 |
|
| 140 |
-
#
|
|
|
|
| 141 |
self.classifier = MLPHead(
|
| 142 |
in_dim=int(self._meta["feat_dim"]),
|
| 143 |
-
num_labels=config.num_labels,
|
| 144 |
-
bottleneck=config.mlp_head_bottleneck,
|
| 145 |
-
p=config.mlp_head_dropout,
|
| 146 |
)
|
| 147 |
|
| 148 |
-
# HF
|
|
|
|
| 149 |
self.post_init()
|
| 150 |
|
| 151 |
def init_weights(self):
|
| 152 |
"""
|
| 153 |
-
|
| 154 |
-
head๋ง ์ด๊ธฐํ
|
|
|
|
|
|
|
|
|
|
| 155 |
"""
|
| 156 |
if getattr(self, "classifier", None) is not None:
|
| 157 |
self.classifier.apply(self._init_weights)
|
|
@@ -159,8 +100,11 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 159 |
|
| 160 |
# ----------------------------
|
| 161 |
# backbone skeleton builders
|
|
|
|
| 162 |
# ----------------------------
|
| 163 |
def _build_backbone_skeleton(self, backbone_id: str) -> nn.Module:
|
|
|
|
|
|
|
| 164 |
meta = BACKBONE_META[backbone_id]
|
| 165 |
t = meta["type"]
|
| 166 |
|
|
@@ -170,18 +114,24 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 170 |
if t == "torchvision_densenet":
|
| 171 |
return self._build_torchvision_densenet_skeleton(backbone_id)
|
| 172 |
|
| 173 |
-
# transformers:
|
|
|
|
| 174 |
bb_cfg = AutoConfig.from_pretrained(backbone_id)
|
| 175 |
return AutoModel.from_config(bb_cfg)
|
| 176 |
|
| 177 |
@staticmethod
|
| 178 |
def _build_timm_densenet_skeleton(hf_repo_id: str) -> nn.Module:
|
|
|
|
|
|
|
| 179 |
try:
|
| 180 |
import timm
|
| 181 |
except Exception as e:
|
| 182 |
-
raise ImportError(
|
|
|
|
|
|
|
| 183 |
|
| 184 |
-
#
|
|
|
|
| 185 |
return timm.create_model(
|
| 186 |
f"hf_hub:{hf_repo_id}",
|
| 187 |
pretrained=False,
|
|
@@ -190,33 +140,33 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 190 |
|
| 191 |
@staticmethod
|
| 192 |
def _build_torchvision_densenet_skeleton(model_id: str) -> nn.Module:
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
"torchvision
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
arch = mapping[model_id]
|
| 203 |
-
fn = getattr(tv_models, arch)
|
| 204 |
-
|
| 205 |
-
# ๊ตฌ์กฐ๋ง (weights=None)
|
| 206 |
-
m = fn(weights=None).eval()
|
| 207 |
return m
|
| 208 |
|
| 209 |
# ------------------------------------------------------------
|
| 210 |
-
#
|
|
|
|
| 211 |
# ------------------------------------------------------------
|
| 212 |
@torch.no_grad()
|
| 213 |
-
def load_backbone_pretrained_(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
"""
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
model.load_backbone_pretrained_()
|
| 218 |
|
| 219 |
-
|
|
|
|
| 220 |
"""
|
| 221 |
bb = self.config.backbone_name_or_path
|
| 222 |
meta = BACKBONE_META[bb]
|
|
@@ -230,48 +180,58 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 230 |
self._load_torchvision_pretrained_into_skeleton_(bb)
|
| 231 |
return
|
| 232 |
|
| 233 |
-
# transformers pretrained
|
|
|
|
| 234 |
ref = AutoModel.from_pretrained(
|
| 235 |
bb,
|
| 236 |
low_cpu_mem_usage=low_cpu_mem_usage,
|
| 237 |
device_map=device_map,
|
| 238 |
)
|
|
|
|
|
|
|
|
|
|
| 239 |
self.backbone.load_state_dict(ref.state_dict(), strict=False)
|
| 240 |
del ref
|
| 241 |
|
| 242 |
@torch.no_grad()
|
| 243 |
def _load_timm_pretrained_into_skeleton_(self, hf_repo_id: str):
|
|
|
|
|
|
|
| 244 |
import timm
|
|
|
|
|
|
|
|
|
|
| 245 |
ref = timm.create_model(
|
| 246 |
f"hf_hub:{hf_repo_id}",
|
| 247 |
pretrained=True,
|
| 248 |
num_classes=0,
|
| 249 |
).eval()
|
|
|
|
| 250 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 251 |
del ref
|
| 252 |
|
| 253 |
@torch.no_grad()
|
| 254 |
def _load_torchvision_pretrained_into_skeleton_(self, model_id: str):
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
"torchvision
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
arch, weights = mapping[model_id]
|
| 265 |
-
fn = getattr(tv_models, arch)
|
| 266 |
-
ref = fn(weights=weights).eval()
|
| 267 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 268 |
del ref
|
| 269 |
|
| 270 |
# ----------------------------
|
| 271 |
# feature extraction
|
|
|
|
| 272 |
# ----------------------------
|
| 273 |
@staticmethod
|
| 274 |
def _pool_or_gap(outputs) -> torch.Tensor:
|
|
|
|
|
|
|
| 275 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 276 |
x = outputs.pooler_output
|
| 277 |
if x.dim() == 2:
|
|
@@ -280,29 +240,42 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 280 |
return x.flatten(1)
|
| 281 |
raise RuntimeError(f"Unexpected pooler_output shape: {tuple(x.shape)}")
|
| 282 |
|
|
|
|
|
|
|
| 283 |
x = outputs.last_hidden_state
|
| 284 |
if x.dim() == 4:
|
| 285 |
return x.mean(dim=(2, 3))
|
|
|
|
| 286 |
raise RuntimeError(
|
| 287 |
"Expected pooler_output or (B,C,H,W) last_hidden_state for CNN backbones. "
|
| 288 |
f"Got last_hidden_state shape={tuple(x.shape)}"
|
| 289 |
)
|
| 290 |
|
| 291 |
def _extract_features(self, outputs, pixel_values: Optional[torch.Tensor] = None) -> torch.Tensor:
|
|
|
|
|
|
|
| 292 |
rule = self._meta["feat_rule"]
|
| 293 |
|
| 294 |
if rule == "cls":
|
|
|
|
|
|
|
| 295 |
return outputs.last_hidden_state[:, 0, :]
|
| 296 |
|
| 297 |
if rule == "pool_or_mean":
|
|
|
|
|
|
|
| 298 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 299 |
return outputs.pooler_output
|
| 300 |
return outputs.last_hidden_state.mean(dim=1)
|
| 301 |
|
| 302 |
if rule == "pool_or_gap":
|
|
|
|
|
|
|
| 303 |
return self._pool_or_gap(outputs)
|
| 304 |
|
| 305 |
if rule == "timm_gap":
|
|
|
|
|
|
|
| 306 |
if not isinstance(outputs, torch.Tensor):
|
| 307 |
raise TypeError(f"timm_gap expects Tensor features, got {type(outputs)}")
|
| 308 |
if outputs.dim() != 4:
|
|
@@ -310,6 +283,8 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 310 |
return outputs.mean(dim=(2, 3))
|
| 311 |
|
| 312 |
if rule == "torchvision_densenet_gap":
|
|
|
|
|
|
|
| 313 |
if not isinstance(outputs, torch.Tensor):
|
| 314 |
raise TypeError(f"torchvision_densenet_gap expects Tensor, got {type(outputs)}")
|
| 315 |
if outputs.dim() != 4:
|
|
@@ -327,9 +302,13 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 327 |
return_dict=True,
|
| 328 |
**kwargs,
|
| 329 |
):
|
|
|
|
|
|
|
| 330 |
t = self._meta["type"]
|
| 331 |
|
| 332 |
if t == "timm_densenet":
|
|
|
|
|
|
|
| 333 |
if pixel_values is None:
|
| 334 |
raise ValueError("timm DenseNet backbone requires pixel_values.")
|
| 335 |
if pixel_values.dim() != 4:
|
|
@@ -341,6 +320,8 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 341 |
attentions = None
|
| 342 |
|
| 343 |
elif t == "torchvision_densenet":
|
|
|
|
|
|
|
| 344 |
if pixel_values is None:
|
| 345 |
raise ValueError("torchvision DenseNet backbone requires pixel_values.")
|
| 346 |
if pixel_values.dim() != 4:
|
|
@@ -353,6 +334,8 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 353 |
attentions = None
|
| 354 |
|
| 355 |
else:
|
|
|
|
|
|
|
| 356 |
outputs = self.backbone(
|
| 357 |
pixel_values=pixel_values,
|
| 358 |
output_attentions=output_attentions,
|
|
@@ -364,10 +347,14 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 364 |
hidden_states = getattr(outputs, "hidden_states", None)
|
| 365 |
attentions = getattr(outputs, "attentions", None)
|
| 366 |
|
|
|
|
|
|
|
| 367 |
logits = self.classifier(feats)
|
| 368 |
|
| 369 |
loss = None
|
| 370 |
if labels is not None:
|
|
|
|
|
|
|
| 371 |
loss = F.cross_entropy(logits, labels)
|
| 372 |
|
| 373 |
if not return_dict:
|
|
@@ -383,20 +370,27 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 383 |
|
| 384 |
|
| 385 |
# ============================================================
|
| 386 |
-
# Freeze / Unfreeze utilities
|
|
|
|
| 387 |
# ============================================================
|
| 388 |
def _set_requires_grad(module: nn.Module, flag: bool):
|
|
|
|
|
|
|
| 389 |
for p in module.parameters():
|
| 390 |
p.requires_grad = flag
|
| 391 |
|
| 392 |
|
| 393 |
def set_bn_eval(module: nn.Module):
|
|
|
|
|
|
|
| 394 |
for m in module.modules():
|
| 395 |
if isinstance(m, (nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d, nn.SyncBatchNorm)):
|
| 396 |
m.eval()
|
| 397 |
|
| 398 |
|
| 399 |
def freeze_backbone(model: BackboneWithMLPHeadForImageClassification, freeze_bn: bool = True):
|
|
|
|
|
|
|
| 400 |
_set_requires_grad(model.backbone, False)
|
| 401 |
_set_requires_grad(model.classifier, True)
|
| 402 |
|
|
@@ -406,6 +400,8 @@ def freeze_backbone(model: BackboneWithMLPHeadForImageClassification, freeze_bn:
|
|
| 406 |
|
| 407 |
|
| 408 |
def finetune_train_mode(model: BackboneWithMLPHeadForImageClassification, keep_bn_eval: bool = True):
|
|
|
|
|
|
|
| 409 |
model.train()
|
| 410 |
meta = BACKBONE_META[model.config.backbone_name_or_path]
|
| 411 |
if keep_bn_eval and meta.get("has_bn", False):
|
|
@@ -413,6 +409,8 @@ def finetune_train_mode(model: BackboneWithMLPHeadForImageClassification, keep_b
|
|
| 413 |
|
| 414 |
|
| 415 |
def trainable_summary(model: nn.Module):
|
|
|
|
|
|
|
| 416 |
total = sum(p.numel() for p in model.parameters())
|
| 417 |
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 418 |
ratio = trainable / total if total > 0 else 0.0
|
|
@@ -425,6 +423,8 @@ def unfreeze_last_stage(
|
|
| 425 |
last_n: int = 2,
|
| 426 |
keep_bn_eval: bool = True,
|
| 427 |
):
|
|
|
|
|
|
|
| 428 |
freeze_backbone(model, freeze_bn=keep_bn_eval)
|
| 429 |
|
| 430 |
n = int(last_n)
|
|
@@ -438,12 +438,16 @@ def unfreeze_last_stage(
|
|
| 438 |
bb_type = meta["type"]
|
| 439 |
|
| 440 |
if bb_type == "vit":
|
|
|
|
|
|
|
| 441 |
blocks = list(model.backbone.encoder.layer)
|
| 442 |
for blk in blocks[-n:]:
|
| 443 |
_set_requires_grad(blk, True)
|
| 444 |
return
|
| 445 |
|
| 446 |
if bb_type == "swin":
|
|
|
|
|
|
|
| 447 |
stages = list(model.backbone.encoder.layers)
|
| 448 |
blocks: List[nn.Module] = []
|
| 449 |
for st in stages:
|
|
@@ -453,6 +457,8 @@ def unfreeze_last_stage(
|
|
| 453 |
return
|
| 454 |
|
| 455 |
if bb_type == "resnet":
|
|
|
|
|
|
|
| 456 |
bb = model.backbone
|
| 457 |
for name in ("layer1", "layer2", "layer3", "layer4"):
|
| 458 |
if not hasattr(bb, name):
|
|
@@ -472,6 +478,8 @@ def unfreeze_last_stage(
|
|
| 472 |
return
|
| 473 |
|
| 474 |
if bb_type == "efficientnet":
|
|
|
|
|
|
|
| 475 |
bb = model.backbone
|
| 476 |
if not hasattr(bb, "features"):
|
| 477 |
raise RuntimeError("Unexpected EfficientNet structure: missing features")
|
|
@@ -488,6 +496,8 @@ def unfreeze_last_stage(
|
|
| 488 |
return
|
| 489 |
|
| 490 |
if bb_type in ("timm_densenet", "torchvision_densenet"):
|
|
|
|
|
|
|
| 491 |
bb = model.backbone
|
| 492 |
if not hasattr(bb, "features"):
|
| 493 |
raise RuntimeError("Unexpected DenseNet: missing features")
|
|
@@ -505,6 +515,8 @@ def unfreeze_last_stage(
|
|
| 505 |
raise RuntimeError(f"Unexpected DenseNet features: missing {name}")
|
| 506 |
|
| 507 |
def _denselayers(db: nn.Module) -> List[nn.Module]:
|
|
|
|
|
|
|
| 508 |
return list(db.children())
|
| 509 |
|
| 510 |
blocks: List[nn.Module] = []
|
|
@@ -526,20 +538,8 @@ def unfreeze_last_stage(
|
|
| 526 |
|
| 527 |
# -------------------------
|
| 528 |
# register
|
|
|
|
|
|
|
|
|
|
|
|
|
| 529 |
BackboneWithMLPHeadForImageClassification.register_for_auto_class("AutoModelForImageClassification")
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
# ==========================================================
|
| 533 |
-
|
| 534 |
-
# # fresh-start
|
| 535 |
-
# cfg = BackboneMLPHeadConfig(backbone_name_or_path="timm/densenet121.tv_in1k", num_labels=3)
|
| 536 |
-
# model = BackboneWithMLPHeadForImageClassification(cfg)
|
| 537 |
-
|
| 538 |
-
# # pretrained๋ฅผ ์ฐ๋ ค๋ฉด ๋ช
์์ ์ผ๋ก ํธ์ถ
|
| 539 |
-
# model.load_backbone_pretrained_()
|
| 540 |
-
|
| 541 |
-
# # ์ดํ ํ์ต
|
| 542 |
-
|
| 543 |
-
# # checkpoint์์ ์์
|
| 544 |
-
# model = BackboneWithMLPHeadForImageClassification.from_pretrained("checkpoints/xxx")
|
| 545 |
-
# # ์ฌ๊ธฐ์๋ load_backbone_pretrained_() ํธ์ถํ๋ฉด ์ ๋ฉ๋๋ค.
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
# -*- coding: utf-8 -*-
|
| 3 |
|
| 4 |
+
# src/ds_model.py
|
| 5 |
|
| 6 |
+
from typing import Optional, List
|
|
|
|
| 7 |
|
| 8 |
import torch
|
| 9 |
import torch.nn as nn
|
|
|
|
| 11 |
|
| 12 |
# --- transformers core ---
|
| 13 |
from transformers.modeling_utils import PreTrainedModel
|
| 14 |
+
from transformers import AutoModel, AutoConfig
|
| 15 |
from transformers.modeling_outputs import ImageClassifierOutput
|
| 16 |
|
| 17 |
# --- torchvision ---
|
| 18 |
from torchvision import models as tv_models
|
| 19 |
|
| 20 |
+
from ds_cfg import BackboneMLPHeadConfig
|
| 21 |
+
from ds_meta import BACKBONE_META
|
| 22 |
+
from mlp_head import MLPHead
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
# ============================================================
|
| 25 |
# (3) Model: backbone + MLP head
|
| 26 |
+
# (3) ๋ชจ๋ธ: backbone + MLP head
|
|
|
|
|
|
|
|
|
|
| 27 |
# ============================================================
|
| 28 |
+
# Design principle: __init__ builds only a skeleton and MUST NOT load pretrained weights.
|
| 29 |
+
# ์ค๊ณ ์์น: __init__์์๋ skeleton๋ง ๋ง๋ค๊ณ pretrained weight ๋ก๋๋ ์ ๋ ํ๋ฉด ์ ๋ฉ๋๋ค.
|
| 30 |
+
#
|
| 31 |
+
# Pretrained injection is allowed ONLY via an explicit call in fresh-start flows.
|
| 32 |
+
# pretrained ์ฃผ์
์ fresh-start ํ๋ฆ์์์ ๋ช
์์ ํธ์ถ๋ก๋ง ํ์ฉํฉ๋๋ค.
|
| 33 |
+
#
|
| 34 |
+
# HF from_pretrained should restore checkpoints as-is without side effects.
|
| 35 |
+
# HF from_pretrained๋ ๋ถ์์ฉ ์์ด ์ฒดํฌํฌ์ธํธ๋ฅผ ๊ทธ๋๋ก ๋ณต์ํด์ผ ํฉ๋๋ค.
|
| 36 |
class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
| 37 |
+
# This links the model to its custom config for AutoClass usage.
|
| 38 |
+
# AutoClass ์ฌ์ฉ์ ์ํด ๋ชจ๋ธ๊ณผ ์ปค์คํ
config๋ฅผ ์ฐ๊ฒฐํฉ๋๋ค.
|
| 39 |
config_class = BackboneMLPHeadConfig
|
| 40 |
|
| 41 |
def __init__(self, config: BackboneMLPHeadConfig):
|
| 42 |
+
# PreTrainedModel expects a config object and stores it internally.
|
| 43 |
+
# PreTrainedModel์ config ๊ฐ์ฒด๋ฅผ ๋ฐ์ ๋ด๋ถ์ ์ ์ฅํ๋ ์ ์ ๋ฅผ ๊ฐ์ง๋๋ค.
|
| 44 |
super().__init__(config)
|
| 45 |
|
| 46 |
+
# Fail-fast: the model is not meant to be instantiated without a valid backbone id.
|
| 47 |
+
# fail-fast: ์ ํจํ backbone id ์์ด ๋ชจ๋ธ์ ๋ง๋๋ ์ฌ์ฉ ์๋๋ฆฌ์ค๋ ํ์ฉํ์ง ์์ต๋๋ค.
|
| 48 |
+
#
|
| 49 |
+
# Note: Transformers may create configs with no args, but models are conventionally created with configs.
|
| 50 |
+
# ์ฐธ๊ณ : Transformers๋ config ๋ฌด์ธ์ ์์ฑ์ด ์์ ์ ์์ผ๋, ๋ชจ๋ธ์ ๊ด๋ก์ ์ผ๋ก config๋ฅผ ๋ฐ์ ์์ฑ๋ฉ๋๋ค.
|
| 51 |
if config.backbone_name_or_path is None:
|
| 52 |
+
raise ValueError(
|
| 53 |
+
"config.backbone_name_or_path is None. "
|
| 54 |
+
"Provide a valid backbone id (whitelist key in BACKBONE_META)."
|
| 55 |
+
)
|
| 56 |
|
| 57 |
+
# Fail-fast: training/inference requires a positive number of labels.
|
| 58 |
+
# fail-fast: ํ์ต/์ถ๋ก ์ num_labels๊ฐ ์์์ฌ์ผ ํฉ๋๋ค.
|
| 59 |
+
#
|
| 60 |
+
# Config may exist in a minimal form for internal serialization paths, but the model should not.
|
| 61 |
+
# config๋ ๋ด๋ถ ์ง๋ ฌํ ๊ฒฝ๋ก์์ ์ต์ ํํ๋ก ์กด์ฌํ ์ ์์ผ๋ ๋ชจ๋ธ์ ๊ทธ๋ฌ๋ฉด ์ ๋ฉ๋๋ค.
|
| 62 |
+
if int(getattr(config, "num_labels", 0)) <= 0:
|
| 63 |
+
raise ValueError(
|
| 64 |
+
f"config.num_labels must be > 0, got {getattr(config, 'num_labels', None)}. "
|
| 65 |
+
"Set num_labels (or id2label/label2id) when creating the config."
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
# Meta is a single source of truth for extraction and fine-tuning rules.
|
| 69 |
+
# meta๋ feature ์ถ์ถ ๋ฐ ๋ฏธ์ธ์กฐ์ ๊ท์น์ ๋จ์ผ ๊ธฐ์ค์
๋๋ค.
|
| 70 |
self._meta = BACKBONE_META[config.backbone_name_or_path]
|
| 71 |
|
| 72 |
+
# Backbone skeleton is always created without pretrained weights.
|
| 73 |
+
# backbone skeleton์ ํญ์ pretrained weight ์์ด ์์ฑํฉ๋๋ค.
|
| 74 |
self.backbone = self._build_backbone_skeleton(config.backbone_name_or_path)
|
| 75 |
|
| 76 |
+
# Head shape is driven by meta feat_dim and config.num_labels.
|
| 77 |
+
# head shape์ meta์ feat_dim๊ณผ config.num_labels๋ก ๊ฒฐ์ ๋ฉ๋๋ค.
|
| 78 |
self.classifier = MLPHead(
|
| 79 |
in_dim=int(self._meta["feat_dim"]),
|
| 80 |
+
num_labels=int(config.num_labels),
|
| 81 |
+
bottleneck=int(config.mlp_head_bottleneck),
|
| 82 |
+
p=float(config.mlp_head_dropout),
|
| 83 |
)
|
| 84 |
|
| 85 |
+
# HF initialization hook, but we override init_weights to initialize head-only.
|
| 86 |
+
# HF ์ด๊ธฐํ ํ
์ด์ง๋ง init_weights๋ฅผ overrideํ์ฌ head๋ง ์ด๊ธฐํํฉ๋๋ค.
|
| 87 |
self.post_init()
|
| 88 |
|
| 89 |
def init_weights(self):
|
| 90 |
"""
|
| 91 |
+
Initialize only the head to avoid touching the backbone skeleton.
|
| 92 |
+
backbone skeleton์ ๊ฑด๋๋ฆฌ์ง ์๊ธฐ ์ํด head๋ง ์ด๊ธฐํํฉ๋๋ค.
|
| 93 |
+
|
| 94 |
+
HF's default init may traverse the entire module tree, which is undesirable here.
|
| 95 |
+
HF ๊ธฐ๋ณธ init์ ์ ์ฒด ๋ชจ๋ ํธ๋ฆฌ๋ฅผ ์ํํ ์ ์์ด ์ฌ๊ธฐ์๋ ๋ถ์ ์ ํฉ๋๋ค.
|
| 96 |
"""
|
| 97 |
if getattr(self, "classifier", None) is not None:
|
| 98 |
self.classifier.apply(self._init_weights)
|
|
|
|
| 100 |
|
| 101 |
# ----------------------------
|
| 102 |
# backbone skeleton builders
|
| 103 |
+
# backbone skeleton ์์ฑ๊ธฐ
|
| 104 |
# ----------------------------
|
| 105 |
def _build_backbone_skeleton(self, backbone_id: str) -> nn.Module:
|
| 106 |
+
# Meta decides which loader path to use.
|
| 107 |
+
# meta๊ฐ ์ด๋ค ๋ก๋ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ ์ง ๊ฒฐ์ ํฉ๋๋ค.
|
| 108 |
meta = BACKBONE_META[backbone_id]
|
| 109 |
t = meta["type"]
|
| 110 |
|
|
|
|
| 114 |
if t == "torchvision_densenet":
|
| 115 |
return self._build_torchvision_densenet_skeleton(backbone_id)
|
| 116 |
|
| 117 |
+
# For transformers backbones: build a random-weight skeleton from config only.
|
| 118 |
+
# transformers ๋ฐฑ๋ณธ: config๋ก๋ถํฐ ๋๋ค ์ด๊ธฐํ skeleton๋ง ์์ฑํฉ๋๋ค.
|
| 119 |
bb_cfg = AutoConfig.from_pretrained(backbone_id)
|
| 120 |
return AutoModel.from_config(bb_cfg)
|
| 121 |
|
| 122 |
@staticmethod
|
| 123 |
def _build_timm_densenet_skeleton(hf_repo_id: str) -> nn.Module:
|
| 124 |
+
# timm is an optional dependency and should be imported lazily.
|
| 125 |
+
# timm์ ์ต์
์์กด์ฑ์ด๋ฏ๋ก ์ง์ฐ import ํฉ๋๋ค.
|
| 126 |
try:
|
| 127 |
import timm
|
| 128 |
except Exception as e:
|
| 129 |
+
raise ImportError(
|
| 130 |
+
"DenseNet(timm) backbone requires `timm`. Install: pip install timm"
|
| 131 |
+
) from e
|
| 132 |
|
| 133 |
+
# Build structure only (pretrained=False) and remove classifier head (num_classes=0).
|
| 134 |
+
# ๊ตฌ์กฐ๋ง ์์ฑ(pretrained=False)ํ๊ณ ๋ถ๋ฅ๊ธฐ head๋ ์ ๊ฑฐ(num_classes=0)ํฉ๋๋ค.
|
| 135 |
return timm.create_model(
|
| 136 |
f"hf_hub:{hf_repo_id}",
|
| 137 |
pretrained=False,
|
|
|
|
| 140 |
|
| 141 |
@staticmethod
|
| 142 |
def _build_torchvision_densenet_skeleton(model_id: str) -> nn.Module:
|
| 143 |
+
# This project intentionally supports only torchvision/densenet121 in the 224 whitelist.
|
| 144 |
+
# ์ด ํ๋ก์ ํธ๋ 224 ํ์ดํธ๋ฆฌ์คํธ์์ torchvision/densenet121๋ง ์๋์ ์ผ๋ก ์ง์ํฉ๋๋ค.
|
| 145 |
+
if model_id != "torchvision/densenet121":
|
| 146 |
+
raise ValueError(f"Unsupported torchvision DenseNet id (224 whitelist only): {model_id}")
|
| 147 |
+
|
| 148 |
+
# Build structure only (weights=None) to avoid implicit pretrained loading.
|
| 149 |
+
# implicit pretrained ๋ก๋๋ฅผ ํผํ๊ธฐ ์ํด ๊ตฌ์กฐ๋ง ์์ฑ(weights=None)ํฉ๋๋ค.
|
| 150 |
+
m = tv_models.densenet121(weights=None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
return m
|
| 152 |
|
| 153 |
# ------------------------------------------------------------
|
| 154 |
+
# Pretrained loading is explicit and fresh-start only
|
| 155 |
+
# pretrained ๋ก๋ฉ์ ๋ช
์์ ํธ์ถ์ด๋ฉฐ fresh-start ์ ์ฉ
|
| 156 |
# ------------------------------------------------------------
|
| 157 |
@torch.no_grad()
|
| 158 |
+
def load_backbone_pretrained_(
|
| 159 |
+
self,
|
| 160 |
+
*,
|
| 161 |
+
low_cpu_mem_usage: bool = False,
|
| 162 |
+
device_map=None,
|
| 163 |
+
):
|
| 164 |
"""
|
| 165 |
+
Fresh-start only: inject pretrained backbone weights into the skeleton.
|
| 166 |
+
fresh-start ์ ์ฉ: skeleton backbone์ pretrained ๊ฐ์ค์น๋ฅผ ์ฃผ์
ํฉ๋๋ค.
|
|
|
|
| 167 |
|
| 168 |
+
Do NOT call this after from_pretrained() because it would overwrite checkpoint weights.
|
| 169 |
+
from_pretrained() ์ดํ ํธ์ถํ๋ฉด ์ฒดํฌํฌ์ธํธ ๊ฐ์ค์น๋ฅผ ๋ฎ์ด์ฐ๋ฏ๋ก ์ ๋ ํธ์ถํ๋ฉด ์ ๋ฉ๋๋ค.
|
| 170 |
"""
|
| 171 |
bb = self.config.backbone_name_or_path
|
| 172 |
meta = BACKBONE_META[bb]
|
|
|
|
| 180 |
self._load_torchvision_pretrained_into_skeleton_(bb)
|
| 181 |
return
|
| 182 |
|
| 183 |
+
# For transformers backbones, load a reference pretrained model and copy weights into our skeleton.
|
| 184 |
+
# transformers ๋ฐฑ๋ณธ์ reference pretrained ๋ชจ๋ธ์ ๋ก๋ํ ๋ค skeleton์ ๊ฐ์ค์น๋ฅผ ๋ณต์ฌํฉ๋๋ค.
|
| 185 |
ref = AutoModel.from_pretrained(
|
| 186 |
bb,
|
| 187 |
low_cpu_mem_usage=low_cpu_mem_usage,
|
| 188 |
device_map=device_map,
|
| 189 |
)
|
| 190 |
+
|
| 191 |
+
# strict=False is used to tolerate harmless key differences across minor versions.
|
| 192 |
+
# strict=False๋ ๋ง์ด๋ ๋ฒ์ ์ฐจ์ด๋ก ์ธํ ๋ฌดํดํ ํค ์ฐจ์ด๋ฅผ ํ์ฉํ๊ธฐ ์ํด ์ฌ์ฉํฉ๋๋ค.
|
| 193 |
self.backbone.load_state_dict(ref.state_dict(), strict=False)
|
| 194 |
del ref
|
| 195 |
|
| 196 |
@torch.no_grad()
|
| 197 |
def _load_timm_pretrained_into_skeleton_(self, hf_repo_id: str):
|
| 198 |
+
# timm must be present for timm backbones.
|
| 199 |
+
# timm ๋ฐฑ๋ณธ์๋ timm ์ค์น๊ฐ ํ์ํฉ๋๋ค.
|
| 200 |
import timm
|
| 201 |
+
|
| 202 |
+
# Create a pretrained reference model and copy its weights strictly.
|
| 203 |
+
# pretrained reference ๋ชจ๋ธ์ ๋ง๋ค๊ณ ๊ฐ์ค์น๋ฅผ strictํ๊ฒ ๋ณต์ฌํฉ๋๋ค.
|
| 204 |
ref = timm.create_model(
|
| 205 |
f"hf_hub:{hf_repo_id}",
|
| 206 |
pretrained=True,
|
| 207 |
num_classes=0,
|
| 208 |
).eval()
|
| 209 |
+
|
| 210 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 211 |
del ref
|
| 212 |
|
| 213 |
@torch.no_grad()
|
| 214 |
def _load_torchvision_pretrained_into_skeleton_(self, model_id: str):
|
| 215 |
+
# This project intentionally supports only torchvision/densenet121 in the 224 whitelist.
|
| 216 |
+
# ์ด ํ๋ก์ ํธ๋ 224 ํ์ดํธ๋ฆฌ์คํธ์์ torchvision/densenet121๋ง ์๋์ ์ผ๋ก ์ง์ํฉ๋๋ค.
|
| 217 |
+
if model_id != "torchvision/densenet121":
|
| 218 |
+
raise ValueError(f"Unsupported torchvision DenseNet id (224 whitelist only): {model_id}")
|
| 219 |
+
|
| 220 |
+
# Use torchvision's default pretrained weights for densenet121.
|
| 221 |
+
# torchvision์ densenet121 ๊ธฐ๋ณธ pretrained weights๋ฅผ ์ฌ์ฉํฉ๋๋ค.
|
| 222 |
+
ref = tv_models.densenet121(weights=tv_models.DenseNet121_Weights.DEFAULT).eval()
|
| 223 |
+
|
|
|
|
|
|
|
|
|
|
| 224 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 225 |
del ref
|
| 226 |
|
| 227 |
# ----------------------------
|
| 228 |
# feature extraction
|
| 229 |
+
# feature ์ถ์ถ
|
| 230 |
# ----------------------------
|
| 231 |
@staticmethod
|
| 232 |
def _pool_or_gap(outputs) -> torch.Tensor:
|
| 233 |
+
# Some transformers vision CNNs provide pooler_output explicitly.
|
| 234 |
+
# ์ผ๋ถ transformers vision CNN์ pooler_output์ ๋ช
์์ ์ผ๋ก ์ ๊ณตํฉ๋๋ค.
|
| 235 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 236 |
x = outputs.pooler_output
|
| 237 |
if x.dim() == 2:
|
|
|
|
| 240 |
return x.flatten(1)
|
| 241 |
raise RuntimeError(f"Unexpected pooler_output shape: {tuple(x.shape)}")
|
| 242 |
|
| 243 |
+
# Otherwise we expect a CNN-style last_hidden_state=(B,C,H,W) and apply GAP.
|
| 244 |
+
# ๊ทธ๋ ์ง ์์ผ๋ฉด CNN ์คํ์ผ last_hidden_state=(B,C,H,W)๋ฅผ ๊ธฐ๋ํ๊ณ GAP์ ์ ์ฉํฉ๋๋ค.
|
| 245 |
x = outputs.last_hidden_state
|
| 246 |
if x.dim() == 4:
|
| 247 |
return x.mean(dim=(2, 3))
|
| 248 |
+
|
| 249 |
raise RuntimeError(
|
| 250 |
"Expected pooler_output or (B,C,H,W) last_hidden_state for CNN backbones. "
|
| 251 |
f"Got last_hidden_state shape={tuple(x.shape)}"
|
| 252 |
)
|
| 253 |
|
| 254 |
def _extract_features(self, outputs, pixel_values: Optional[torch.Tensor] = None) -> torch.Tensor:
|
| 255 |
+
# Feature rule is defined by BACKBONE_META and must remain stable across saves/loads.
|
| 256 |
+
# feature ๊ท์น์ BACKBONE_META๋ก ์ ์๋๋ฉฐ ์ ์ฅ/๋ก๋ ๊ฐ ์์ ์ ์ผ๋ก ์ ์ง๋์ด์ผ ํฉ๋๋ค.
|
| 257 |
rule = self._meta["feat_rule"]
|
| 258 |
|
| 259 |
if rule == "cls":
|
| 260 |
+
# ViT-style: use CLS token embedding from last_hidden_state.
|
| 261 |
+
# ViT ์คํ์ผ: last_hidden_state์์ CLS ํ ํฐ ์๋ฒ ๋ฉ์ ์ฌ์ฉํฉ๋๋ค.
|
| 262 |
return outputs.last_hidden_state[:, 0, :]
|
| 263 |
|
| 264 |
if rule == "pool_or_mean":
|
| 265 |
+
# Swin-style: prefer pooler_output if present, else mean-pool over tokens.
|
| 266 |
+
# Swin ์คํ์ผ: pooler_output์ด ์์ผ๋ฉด ์ฐ์ ์ฌ์ฉํ๊ณ , ์์ผ๋ฉด ํ ํฐ ํ๊ท ํ๋ง์ ์ฌ์ฉํฉ๋๋ค.
|
| 267 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 268 |
return outputs.pooler_output
|
| 269 |
return outputs.last_hidden_state.mean(dim=1)
|
| 270 |
|
| 271 |
if rule == "pool_or_gap":
|
| 272 |
+
# CNN-style: use pooler_output if present, else GAP over spatial dims.
|
| 273 |
+
# CNN ์คํ์ผ: pooler_output์ด ์์ผ๋ฉด ์ฌ์ฉํ๊ณ , ์์ผ๋ฉด ๊ณต๊ฐ ์ฐจ์ GAP์ ์ฌ์ฉํฉ๋๋ค.
|
| 274 |
return self._pool_or_gap(outputs)
|
| 275 |
|
| 276 |
if rule == "timm_gap":
|
| 277 |
+
# timm forward_features returns a feature map (B,C,H,W) which we GAP to (B,C).
|
| 278 |
+
# timm forward_features๋ (B,C,H,W) feature map์ ๋ฐํํ๋ฉฐ ์ด๋ฅผ GAP์ผ๋ก (B,C)๋ก ๋ง๋ญ๋๋ค.
|
| 279 |
if not isinstance(outputs, torch.Tensor):
|
| 280 |
raise TypeError(f"timm_gap expects Tensor features, got {type(outputs)}")
|
| 281 |
if outputs.dim() != 4:
|
|
|
|
| 283 |
return outputs.mean(dim=(2, 3))
|
| 284 |
|
| 285 |
if rule == "torchvision_densenet_gap":
|
| 286 |
+
# torchvision DenseNet features are feature maps (B,C,H,W) and require GAP.
|
| 287 |
+
# torchvision DenseNet features๋ (B,C,H,W) feature map์ด๋ฉฐ GAP์ด ํ์ํฉ๋๋ค.
|
| 288 |
if not isinstance(outputs, torch.Tensor):
|
| 289 |
raise TypeError(f"torchvision_densenet_gap expects Tensor, got {type(outputs)}")
|
| 290 |
if outputs.dim() != 4:
|
|
|
|
| 302 |
return_dict=True,
|
| 303 |
**kwargs,
|
| 304 |
):
|
| 305 |
+
# Type decides the backbone forward path and output format.
|
| 306 |
+
# type์ด backbone forward ๊ฒฝ๋ก ๋ฐ ์ถ๋ ฅ ํฌ๋งท์ ๊ฒฐ์ ํฉ๋๋ค.
|
| 307 |
t = self._meta["type"]
|
| 308 |
|
| 309 |
if t == "timm_densenet":
|
| 310 |
+
# timm DenseNet consumes pixel_values as a 4D tensor (B,C,H,W).
|
| 311 |
+
# timm DenseNet์ pixel_values๋ฅผ 4D ํ
์ (B,C,H,W)๋ก ๋ฐ์ต๋๋ค.
|
| 312 |
if pixel_values is None:
|
| 313 |
raise ValueError("timm DenseNet backbone requires pixel_values.")
|
| 314 |
if pixel_values.dim() != 4:
|
|
|
|
| 320 |
attentions = None
|
| 321 |
|
| 322 |
elif t == "torchvision_densenet":
|
| 323 |
+
# torchvision DenseNet consumes pixel_values as a 4D tensor (B,C,H,W).
|
| 324 |
+
# torchvision DenseNet์ pixel_values๋ฅผ 4D ํ
์ (B,C,H,W)๋ก ๋ฐ์ต๋๋ค.
|
| 325 |
if pixel_values is None:
|
| 326 |
raise ValueError("torchvision DenseNet backbone requires pixel_values.")
|
| 327 |
if pixel_values.dim() != 4:
|
|
|
|
| 334 |
attentions = None
|
| 335 |
|
| 336 |
else:
|
| 337 |
+
# Transformers vision models are called with pixel_values and return ModelOutput.
|
| 338 |
+
# transformers vision ๋ชจ๋ธ์ pixel_values๋ก ํธ์ถ๋๋ฉฐ ModelOutput์ ๋ฐํํฉ๋๋ค.
|
| 339 |
outputs = self.backbone(
|
| 340 |
pixel_values=pixel_values,
|
| 341 |
output_attentions=output_attentions,
|
|
|
|
| 347 |
hidden_states = getattr(outputs, "hidden_states", None)
|
| 348 |
attentions = getattr(outputs, "attentions", None)
|
| 349 |
|
| 350 |
+
# Classifier consumes (B, feat_dim) and returns logits (B, num_labels).
|
| 351 |
+
# classifier๋ (B, feat_dim)์ ๋ฐ์ logits (B, num_labels)๋ฅผ ๋ฐํํฉ๋๋ค.
|
| 352 |
logits = self.classifier(feats)
|
| 353 |
|
| 354 |
loss = None
|
| 355 |
if labels is not None:
|
| 356 |
+
# Cross entropy expects labels as class indices in [0, num_labels).
|
| 357 |
+
# cross entropy๋ labels๊ฐ [0, num_labels) ๋ฒ์์ class index์ด๊ธธ ๊ธฐ๋ํฉ๋๋ค.
|
| 358 |
loss = F.cross_entropy(logits, labels)
|
| 359 |
|
| 360 |
if not return_dict:
|
|
|
|
| 370 |
|
| 371 |
|
| 372 |
# ============================================================
|
| 373 |
+
# Freeze / Unfreeze utilities
|
| 374 |
+
# Freeze / Unfreeze ์ ํธ๋ฆฌํฐ
|
| 375 |
# ============================================================
|
| 376 |
def _set_requires_grad(module: nn.Module, flag: bool):
|
| 377 |
+
# Toggle requires_grad for all parameters in a module.
|
| 378 |
+
# ๋ชจ๋์ ๋ชจ๋ ํ๋ผ๋ฏธํฐ์ ๋ํด requires_grad๋ฅผ ํ ๊ธํฉ๋๋ค.
|
| 379 |
for p in module.parameters():
|
| 380 |
p.requires_grad = flag
|
| 381 |
|
| 382 |
|
| 383 |
def set_bn_eval(module: nn.Module):
|
| 384 |
+
# Put BatchNorm layers into eval mode to freeze running stats.
|
| 385 |
+
# BatchNorm ๋ ์ด์ด๋ฅผ eval ๋ชจ๋๋ก ๋์ด running stats๋ฅผ ๊ณ ์ ํฉ๋๋ค.
|
| 386 |
for m in module.modules():
|
| 387 |
if isinstance(m, (nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d, nn.SyncBatchNorm)):
|
| 388 |
m.eval()
|
| 389 |
|
| 390 |
|
| 391 |
def freeze_backbone(model: BackboneWithMLPHeadForImageClassification, freeze_bn: bool = True):
|
| 392 |
+
# Stage1: freeze backbone and train only the head.
|
| 393 |
+
# stage1: backbone์ freezeํ๊ณ head๋ง ํ์ตํฉ๋๋ค.
|
| 394 |
_set_requires_grad(model.backbone, False)
|
| 395 |
_set_requires_grad(model.classifier, True)
|
| 396 |
|
|
|
|
| 400 |
|
| 401 |
|
| 402 |
def finetune_train_mode(model: BackboneWithMLPHeadForImageClassification, keep_bn_eval: bool = True):
|
| 403 |
+
# Stage2: train mode, optionally keeping BN layers in eval for stability.
|
| 404 |
+
# stage2: train ๋ชจ๋๋ก ๋๋ ์์ ์ฑ์ ์ํด BN์ eval๋ก ์ ์งํ ์ ์์ต๋๋ค.
|
| 405 |
model.train()
|
| 406 |
meta = BACKBONE_META[model.config.backbone_name_or_path]
|
| 407 |
if keep_bn_eval and meta.get("has_bn", False):
|
|
|
|
| 409 |
|
| 410 |
|
| 411 |
def trainable_summary(model: nn.Module):
|
| 412 |
+
# Print a compact summary of trainable parameters.
|
| 413 |
+
# ํ์ต ๊ฐ๋ฅ ํ๋ผ๋ฏธํฐ ์์ฝ์ ๊ฐ๋จํ ์ถ๋ ฅํฉ๋๋ค.
|
| 414 |
total = sum(p.numel() for p in model.parameters())
|
| 415 |
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 416 |
ratio = trainable / total if total > 0 else 0.0
|
|
|
|
| 423 |
last_n: int = 2,
|
| 424 |
keep_bn_eval: bool = True,
|
| 425 |
):
|
| 426 |
+
# This utility implements BACKBONE_META['unfreeze']=="last_n" across supported backbones.
|
| 427 |
+
# ์ด ์ ํธ์ ์ง์ ๋ฐฑ๋ณธ๋ค์ ๋ํด BACKBONE_META['unfreeze']=="last_n"์ ๊ตฌํํฉ๋๋ค.
|
| 428 |
freeze_backbone(model, freeze_bn=keep_bn_eval)
|
| 429 |
|
| 430 |
n = int(last_n)
|
|
|
|
| 438 |
bb_type = meta["type"]
|
| 439 |
|
| 440 |
if bb_type == "vit":
|
| 441 |
+
# ViT blocks live under backbone.encoder.layer in the transformers implementation.
|
| 442 |
+
# ViT ๋ธ๋ก์ transformers ๊ตฌํ์์ backbone.encoder.layer ์๋์ ์์ต๋๋ค.
|
| 443 |
blocks = list(model.backbone.encoder.layer)
|
| 444 |
for blk in blocks[-n:]:
|
| 445 |
_set_requires_grad(blk, True)
|
| 446 |
return
|
| 447 |
|
| 448 |
if bb_type == "swin":
|
| 449 |
+
# Swin blocks are nested by stages and blocks; we flatten and unfreeze last n blocks.
|
| 450 |
+
# Swin ๋ธ๋ก์ stage์ block์ผ๋ก ์ค์ฒฉ๋์ด ์์ด ํผ์น ํ ๋ง์ง๋ง n๊ฐ๋ฅผ unfreeze ํฉ๋๋ค.
|
| 451 |
stages = list(model.backbone.encoder.layers)
|
| 452 |
blocks: List[nn.Module] = []
|
| 453 |
for st in stages:
|
|
|
|
| 457 |
return
|
| 458 |
|
| 459 |
if bb_type == "resnet":
|
| 460 |
+
# ResNet uses layer1..layer4 stages; we unfreeze at block granularity.
|
| 461 |
+
# ResNet์ layer1..layer4 stage๋ฅผ ์ฌ์ฉํ๋ฉฐ block ๋จ์๋ก unfreeze ํฉ๋๋ค.
|
| 462 |
bb = model.backbone
|
| 463 |
for name in ("layer1", "layer2", "layer3", "layer4"):
|
| 464 |
if not hasattr(bb, name):
|
|
|
|
| 478 |
return
|
| 479 |
|
| 480 |
if bb_type == "efficientnet":
|
| 481 |
+
# EfficientNet in transformers exposes features; we unfreeze from the tail blocks.
|
| 482 |
+
# transformers EfficientNet์ features๋ฅผ ๋
ธ์ถํ๋ฉฐ ๋ค์ชฝ ๋ธ๋ก๋ถํฐ unfreeze ํฉ๋๋ค.
|
| 483 |
bb = model.backbone
|
| 484 |
if not hasattr(bb, "features"):
|
| 485 |
raise RuntimeError("Unexpected EfficientNet structure: missing features")
|
|
|
|
| 496 |
return
|
| 497 |
|
| 498 |
if bb_type in ("timm_densenet", "torchvision_densenet"):
|
| 499 |
+
# DenseNet exposes a .features module with named blocks; we unfreeze last n submodules.
|
| 500 |
+
# DenseNet์ .features ๋ชจ๋์ ๋ธ๋ก๋ค์ด ์ด๋ฆ์ผ๋ก ์กด์ฌํ๋ฉฐ ๋ง์ง๋ง n๊ฐ ์๋ธ๋ชจ๋์ unfreeze ํฉ๋๋ค.
|
| 501 |
bb = model.backbone
|
| 502 |
if not hasattr(bb, "features"):
|
| 503 |
raise RuntimeError("Unexpected DenseNet: missing features")
|
|
|
|
| 515 |
raise RuntimeError(f"Unexpected DenseNet features: missing {name}")
|
| 516 |
|
| 517 |
def _denselayers(db: nn.Module) -> List[nn.Module]:
|
| 518 |
+
# Dense blocks contain multiple DenseLayer children; we return them for fine-grained unfreezing.
|
| 519 |
+
# denseblock์ DenseLayer ์์๋ค์ ๊ฐ์ง๋ฏ๋ก ์ธ๋ฐํ unfreeze๋ฅผ ์ํด ์ด๋ฅผ ๋ฐํํฉ๋๋ค.
|
| 520 |
return list(db.children())
|
| 521 |
|
| 522 |
blocks: List[nn.Module] = []
|
|
|
|
| 538 |
|
| 539 |
# -------------------------
|
| 540 |
# register
|
| 541 |
+
# register
|
| 542 |
+
# -------------------------
|
| 543 |
+
# Register for AutoModelForImageClassification so from_pretrained can resolve this custom class.
|
| 544 |
+
# from_pretrained๊ฐ ์ด ์ปค์คํ
ํด๋์ค๋ฅผ ํด์ํ ์ ์๋๋ก AutoModelForImageClassification์ ๋ฑ๋กํฉ๋๋ค.
|
| 545 |
BackboneWithMLPHeadForImageClassification.register_for_auto_class("AutoModelForImageClassification")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
models/google__efficientnet-b0/ds_proc.py
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# src/ds_proc.py
|
| 5 |
+
|
| 6 |
+
# ============================================================
|
| 7 |
+
# (4) ImageProcessor (AutoImageProcessor integration)
|
| 8 |
+
# (4) ImageProcessor (AutoImageProcessor ์ฐ๋)
|
| 9 |
+
# ============================================================
|
| 10 |
+
|
| 11 |
+
from typing import Any
|
| 12 |
+
|
| 13 |
+
import numpy as np
|
| 14 |
+
import torch
|
| 15 |
+
from transformers import AutoImageProcessor, AutoConfig
|
| 16 |
+
from transformers.image_processing_base import ImageProcessingMixin
|
| 17 |
+
from transformers.utils.generic import TensorType
|
| 18 |
+
|
| 19 |
+
from ds_meta import BackboneID, BACKBONE_META
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class BackboneMLPHead224ImageProcessor(ImageProcessingMixin):
|
| 23 |
+
"""
|
| 24 |
+
This processor performs image preprocessing and outputs {"pixel_values": ...}.
|
| 25 |
+
์ด processor๋ ์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ๋ฅผ ์ํํ๊ณ {"pixel_values": ...}๋ฅผ ์ถ๋ ฅํฉ๋๋ค.
|
| 26 |
+
|
| 27 |
+
Key requirements:
|
| 28 |
+
ํต์ฌ ์๊ตฌ์ฌํญ:
|
| 29 |
+
|
| 30 |
+
1) save_pretrained() must produce a JSON-serializable preprocessor_config.json.
|
| 31 |
+
save_pretrained()๋ JSON ์ง๋ ฌํ ๊ฐ๋ฅํ preprocessor_config.json์ ์์ฑํด์ผ ํฉ๋๋ค.
|
| 32 |
+
|
| 33 |
+
2) Runtime-only objects (delegate processor, timm/torchvision transforms) must NOT be serialized.
|
| 34 |
+
๋ฐํ์ ๊ฐ์ฒด(delegate processor, timm/torchvision transform)๋ ์ ๋ ์ง๋ ฌํํ๋ฉด ์ ๋ฉ๋๋ค.
|
| 35 |
+
|
| 36 |
+
3) Runtime objects are rebuilt at init/load time based on backbone meta.
|
| 37 |
+
๋ฐํ์ ๊ฐ์ฒด๋ backbone meta์ ๋ฐ๋ผ init/load ์์ ์ ์ฌ๊ตฌ์ฑ๋ฉ๋๋ค.
|
| 38 |
+
"""
|
| 39 |
+
|
| 40 |
+
# HF vision models conventionally expect "pixel_values" as the primary input key.
|
| 41 |
+
# HF vision ๋ชจ๋ธ์ ๊ด๋ก์ ์ผ๋ก ์
๋ ฅ ํค๋ก "pixel_values"๋ฅผ ๊ธฐ๋ํฉ๋๋ค.
|
| 42 |
+
model_input_names = ["pixel_values"]
|
| 43 |
+
|
| 44 |
+
def __init__(self, backbone_name_or_path: BackboneID, is_training: bool = False, **kwargs):
|
| 45 |
+
# ImageProcessingMixin stores extra kwargs and manages auto_map metadata.
|
| 46 |
+
# ImageProcessingMixin์ ์ถ๊ฐ kwargs๋ฅผ ์ ์ฅํ๊ณ auto_map ๋ฉํ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.
|
| 47 |
+
super().__init__(**kwargs)
|
| 48 |
+
|
| 49 |
+
# Enforce whitelist via BACKBONE_META to keep behavior stable.
|
| 50 |
+
# ๋์ ์์ ์ฑ์ ์ํด BACKBONE_META ๊ธฐ๋ฐ ํ์ดํธ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ ํฉ๋๋ค.
|
| 51 |
+
if backbone_name_or_path not in BACKBONE_META:
|
| 52 |
+
raise ValueError(
|
| 53 |
+
f"Unsupported backbone_name_or_path={backbone_name_or_path}. "
|
| 54 |
+
f"Allowed: {sorted(BACKBONE_META.keys())}"
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
# Serializable fields only: these should appear in preprocessor_config.json.
|
| 58 |
+
# ์ง๋ ฌํ ๊ฐ๋ฅํ ํ๋๋ง: ์ด ๊ฐ๋ค๋ง preprocessor_config.json์ ๋ค์ด๊ฐ์ผ ํฉ๋๋ค.
|
| 59 |
+
self.backbone_name_or_path = backbone_name_or_path
|
| 60 |
+
self.is_training = bool(is_training)
|
| 61 |
+
|
| 62 |
+
# Runtime-only fields: must never be serialized.
|
| 63 |
+
# ๋ฐํ์ ์ ์ฉ ํ๋: ์ ๋ ์ง๋ ฌํ๋๋ฉด ์ ๋ฉ๋๋ค.
|
| 64 |
+
self._meta = None
|
| 65 |
+
self._delegate = None
|
| 66 |
+
self._timm_transform = None
|
| 67 |
+
self._torchvision_transform = None
|
| 68 |
+
|
| 69 |
+
# Build runtime objects according to backbone type.
|
| 70 |
+
# backbone type์ ๋ฐ๋ผ ๋ฐํ์ ๊ฐ์ฒด๋ฅผ ๊ตฌ์ฑํฉ๋๋ค.
|
| 71 |
+
self._build_runtime()
|
| 72 |
+
|
| 73 |
+
# ============================================================
|
| 74 |
+
# Runtime builders
|
| 75 |
+
# ๋ฐํ์ ๋น๋
|
| 76 |
+
# ============================================================
|
| 77 |
+
def _build_runtime(self):
|
| 78 |
+
"""
|
| 79 |
+
Build runtime delegate/transform based on BACKBONE_META["type"].
|
| 80 |
+
BACKBONE_META["type"]์ ๋ฐ๋ผ ๋ฐํ์ delegate/transform์ ๊ตฌ์ฑํฉ๋๋ค.
|
| 81 |
+
"""
|
| 82 |
+
meta = BACKBONE_META[self.backbone_name_or_path]
|
| 83 |
+
self._meta = meta
|
| 84 |
+
|
| 85 |
+
# Always reset runtime fields before rebuilding.
|
| 86 |
+
# ์ฌ๊ตฌ์ฑ ์ ๋ฐํ์ ํ๋๋ ํญ์ ์ด๊ธฐํํฉ๋๋ค.
|
| 87 |
+
self._delegate = None
|
| 88 |
+
self._timm_transform = None
|
| 89 |
+
self._torchvision_transform = None
|
| 90 |
+
|
| 91 |
+
t = meta["type"]
|
| 92 |
+
|
| 93 |
+
if t == "timm_densenet":
|
| 94 |
+
# timm DenseNet uses timm.data transforms for ImageNet-style preprocessing.
|
| 95 |
+
# timm DenseNet์ ImageNet ์ ์ฒ๋ฆฌ๋ฅผ ์ํด timm.data transform์ ์ฌ์ฉํฉ๋๋ค.
|
| 96 |
+
self._timm_transform = self._build_timm_transform(
|
| 97 |
+
backbone_id=self.backbone_name_or_path,
|
| 98 |
+
is_training=self.is_training,
|
| 99 |
+
)
|
| 100 |
+
return
|
| 101 |
+
|
| 102 |
+
if t == "torchvision_densenet":
|
| 103 |
+
# torchvision DenseNet requires torchvision-style preprocessing (resize/crop/tensor/normalize).
|
| 104 |
+
# torchvision DenseNet์ torchvision ์คํ์ผ ์ ์ฒ๋ฆฌ(resize/crop/tensor/normalize)๊ฐ ํ์ํฉ๋๋ค.
|
| 105 |
+
self._torchvision_transform = self._build_torchvision_densenet_transform(
|
| 106 |
+
is_training=self.is_training
|
| 107 |
+
)
|
| 108 |
+
return
|
| 109 |
+
|
| 110 |
+
# Default: transformers backbone delegates to its official AutoImageProcessor.
|
| 111 |
+
# ๊ธฐ๋ณธ: transformers ๋ฐฑ๋ณธ์ ๊ณต์ AutoImageProcessor์ ์์ํฉ๋๋ค.
|
| 112 |
+
self._delegate = AutoImageProcessor.from_pretrained(self.backbone_name_or_path)
|
| 113 |
+
|
| 114 |
+
@staticmethod
|
| 115 |
+
def _build_timm_transform(*, backbone_id: str, is_training: bool):
|
| 116 |
+
"""
|
| 117 |
+
Create timm transform without storing non-serializable objects in config.
|
| 118 |
+
๋น์ง๋ ฌํ ๊ฐ์ฒด๋ฅผ config์ ์ ์ฅํ์ง ์๊ณ timm transform์ ์์ฑํฉ๋๋ค.
|
| 119 |
+
"""
|
| 120 |
+
try:
|
| 121 |
+
import timm
|
| 122 |
+
from timm.data import resolve_model_data_config, create_transform
|
| 123 |
+
except Exception as e:
|
| 124 |
+
raise ImportError(
|
| 125 |
+
"timm backbone processor requires `timm`. Install: pip install timm"
|
| 126 |
+
) from e
|
| 127 |
+
|
| 128 |
+
# We only need model metadata to resolve data config, so pretrained=False is preferred.
|
| 129 |
+
# data config ์ถ์ถ๋ง ํ์ํ๋ฏ๋ก pretrained=False๋ฅผ ์ฐ์ ์ฌ์ฉํฉ๋๋ค.
|
| 130 |
+
m = timm.create_model(f"hf_hub:{backbone_id}", pretrained=False, num_classes=0)
|
| 131 |
+
dc = resolve_model_data_config(m)
|
| 132 |
+
|
| 133 |
+
# create_transform returns a torchvision-like callable that maps PIL -> torch.Tensor(C,H,W).
|
| 134 |
+
# create_transform์ PIL -> torch.Tensor(C,H,W)๋ก ๋งคํํ๋ callable์ ๋ฐํํฉ๋๋ค.
|
| 135 |
+
tfm = create_transform(**dc, is_training=is_training)
|
| 136 |
+
return tfm
|
| 137 |
+
|
| 138 |
+
@staticmethod
|
| 139 |
+
def _build_torchvision_densenet_transform(*, is_training: bool):
|
| 140 |
+
"""
|
| 141 |
+
Build torchvision preprocessing for DenseNet-121 (224 pipeline).
|
| 142 |
+
DenseNet-121์ฉ torchvision ์ ์ฒ๋ฆฌ(224 ํ์ดํ๋ผ์ธ)๋ฅผ ๊ตฌ์ฑํฉ๋๋ค.
|
| 143 |
+
"""
|
| 144 |
+
try:
|
| 145 |
+
from torchvision import transforms
|
| 146 |
+
except Exception as e:
|
| 147 |
+
raise ImportError(
|
| 148 |
+
"torchvision DenseNet processor requires `torchvision`. Install: pip install torchvision"
|
| 149 |
+
) from e
|
| 150 |
+
|
| 151 |
+
# These are the standard ImageNet normalization stats used by torchvision weights.
|
| 152 |
+
# ์ด ๊ฐ๋ค์ torchvision weights๊ฐ ์ฌ์ฉํ๋ ํ์ค ImageNet ์ ๊ทํ ํต๊ณ์
๋๋ค.
|
| 153 |
+
mean = (0.485, 0.456, 0.406)
|
| 154 |
+
std = (0.229, 0.224, 0.225)
|
| 155 |
+
|
| 156 |
+
# Training pipeline typically uses RandomResizedCrop and horizontal flip.
|
| 157 |
+
# ํ์ต ํ์ดํ๋ผ์ธ์ ๋ณดํต RandomResizedCrop๊ณผ ์ข์ฐ๋ฐ์ ์ ์ฌ์ฉํฉ๋๋ค.
|
| 158 |
+
if is_training:
|
| 159 |
+
return transforms.Compose(
|
| 160 |
+
[
|
| 161 |
+
transforms.RandomResizedCrop(224),
|
| 162 |
+
transforms.RandomHorizontalFlip(p=0.5),
|
| 163 |
+
transforms.ToTensor(),
|
| 164 |
+
transforms.Normalize(mean=mean, std=std),
|
| 165 |
+
]
|
| 166 |
+
)
|
| 167 |
+
|
| 168 |
+
# Inference pipeline typically uses Resize(256) + CenterCrop(224).
|
| 169 |
+
# ์ถ๋ก ํ์ดํ๋ผ์ธ์ ๋ณดํต Resize(256) + CenterCrop(224)๋ฅผ ์ฌ์ฉํฉ๋๋ค.
|
| 170 |
+
return transforms.Compose(
|
| 171 |
+
[
|
| 172 |
+
transforms.Resize(256),
|
| 173 |
+
transforms.CenterCrop(224),
|
| 174 |
+
transforms.ToTensor(),
|
| 175 |
+
transforms.Normalize(mean=mean, std=std),
|
| 176 |
+
]
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
# ============================================================
|
| 180 |
+
# Serialization
|
| 181 |
+
# ์ง๋ ฌํ
|
| 182 |
+
# ============================================================
|
| 183 |
+
def to_dict(self) -> dict[str, Any]:
|
| 184 |
+
"""
|
| 185 |
+
Return a JSON-serializable dict for preprocessor_config.json.
|
| 186 |
+
preprocessor_config.json์ ๋ค์ด๊ฐ JSON ์ง๋ ฌํ dict๋ฅผ ๋ฐํํฉ๋๋ค.
|
| 187 |
+
|
| 188 |
+
Important: do not leak runtime objects into the serialized dict.
|
| 189 |
+
์ค์: ๋ฐํ์ ๊ฐ์ฒด๊ฐ ์ง๋ ฌํ dict์ ์์ด๋ฉด ์ ๋ฉ๋๋ค.
|
| 190 |
+
"""
|
| 191 |
+
# ImageProcessingMixin.to_dict() adds metadata such as image_processor_type/auto_map.
|
| 192 |
+
# ImageProcessingMixin.to_dict()๋ image_processor_type/auto_map ๊ฐ์ ๋ฉํ๋ฅผ ์ถ๊ฐํฉ๋๋ค.
|
| 193 |
+
d = super().to_dict()
|
| 194 |
+
|
| 195 |
+
# Force minimal stable fields for long-term compatibility.
|
| 196 |
+
# ์ฅ๊ธฐ ํธํ์ ์ํด ์ต์ ์์ ํ๋๋ฅผ ๊ฐ์ ํฉ๋๋ค.
|
| 197 |
+
d["image_processor_type"] = self.__class__.__name__
|
| 198 |
+
d["backbone_name_or_path"] = self.backbone_name_or_path
|
| 199 |
+
d["is_training"] = self.is_training
|
| 200 |
+
|
| 201 |
+
# Remove any runtime-only fields defensively.
|
| 202 |
+
# ๋ฐํ์ ์ ์ฉ ํ๋๋ ๋ณด์์ ์ผ๋ก ์ ๊ฑฐํฉ๋๋ค.
|
| 203 |
+
for key in ["_meta", "_delegate", "_timm_transform", "_torchvision_transform"]:
|
| 204 |
+
d.pop(key, None)
|
| 205 |
+
|
| 206 |
+
return d
|
| 207 |
+
|
| 208 |
+
@classmethod
|
| 209 |
+
def from_dict(cls, image_processor_dict: dict[str, Any], **kwargs):
|
| 210 |
+
"""
|
| 211 |
+
Standard load path used by BaseImageProcessor / AutoImageProcessor.
|
| 212 |
+
BaseImageProcessor / AutoImageProcessor๊ฐ ์ฌ์ฉํ๋ ํ์ค ๋ก๋ ๊ฒฝ๋ก์
๋๋ค.
|
| 213 |
+
"""
|
| 214 |
+
backbone = image_processor_dict.get("backbone_name_or_path", None)
|
| 215 |
+
if backbone is None:
|
| 216 |
+
raise ValueError("preprocessor_config.json missing key: backbone_name_or_path")
|
| 217 |
+
|
| 218 |
+
is_training = bool(image_processor_dict.get("is_training", False))
|
| 219 |
+
return cls(backbone_name_or_path=backbone, is_training=is_training, **kwargs)
|
| 220 |
+
|
| 221 |
+
@classmethod
|
| 222 |
+
def from_pretrained(cls, pretrained_model_name_or_path: str, **kwargs):
|
| 223 |
+
"""
|
| 224 |
+
Fallback path if AutoImageProcessor calls class.from_pretrained directly.
|
| 225 |
+
AutoImageProcessor๊ฐ class.from_pretrained๋ฅผ ์ง์ ํธ์ถํ๋ ๊ฒฝ์ฐ๋ฅผ ๋๋นํ ๊ฒฝ๋ก์
๋๋ค.
|
| 226 |
+
|
| 227 |
+
Strategy:
|
| 228 |
+
์ ๋ต:
|
| 229 |
+
|
| 230 |
+
- Read config.json via AutoConfig and recover backbone_name_or_path.
|
| 231 |
+
AutoConfig๋ก config.json์ ์ฝ๊ณ backbone_name_or_path๋ฅผ ๋ณต๊ตฌํฉ๋๋ค.
|
| 232 |
+
"""
|
| 233 |
+
cfg = AutoConfig.from_pretrained(pretrained_model_name_or_path, **kwargs)
|
| 234 |
+
backbone = getattr(cfg, "backbone_name_or_path", None)
|
| 235 |
+
if backbone is None:
|
| 236 |
+
raise ValueError("Cannot build processor: backbone_name_or_path not found in config.json")
|
| 237 |
+
|
| 238 |
+
# is_training is runtime-only and should default to False for inference/serving.
|
| 239 |
+
# is_training์ ๋ฐํ์ ์ ์ฉ์ด๋ฉฐ ์ถ๋ก /์๋น ๊ธฐ๋ณธ๊ฐ์ False๊ฐ ๋ง์ต๋๋ค.
|
| 240 |
+
return cls(backbone_name_or_path=backbone, is_training=False)
|
| 241 |
+
|
| 242 |
+
# ============================================================
|
| 243 |
+
# Call interface
|
| 244 |
+
# ํธ์ถ ์ธํฐํ์ด์ค
|
| 245 |
+
# ============================================================
|
| 246 |
+
@staticmethod
|
| 247 |
+
def _ensure_list(images: Any) -> list[Any]:
|
| 248 |
+
# Normalize scalar image input to a list for uniform processing.
|
| 249 |
+
# ๋จ์ผ ์
๋ ฅ์ ๋ฆฌ์คํธ๋ก ์ ๊ทํํ์ฌ ๋์ผํ ์ฒ๋ฆฌ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํฉ๋๋ค.
|
| 250 |
+
if isinstance(images, (list, tuple)):
|
| 251 |
+
return list(images)
|
| 252 |
+
return [images]
|
| 253 |
+
|
| 254 |
+
@staticmethod
|
| 255 |
+
def _to_pil_rgb(x: Any):
|
| 256 |
+
# Convert common image inputs into PIL RGB images.
|
| 257 |
+
# ์ผ๋ฐ์ ์ธ ์
๋ ฅ์ PIL RGB ์ด๋ฏธ์ง๋ก ๋ณํํฉ๋๋ค.
|
| 258 |
+
from PIL import Image as PILImage
|
| 259 |
+
|
| 260 |
+
if isinstance(x, PILImage.Image):
|
| 261 |
+
return x.convert("RGB")
|
| 262 |
+
if isinstance(x, np.ndarray) and x.ndim == 3:
|
| 263 |
+
return PILImage.fromarray(x).convert("RGB")
|
| 264 |
+
raise TypeError(f"Unsupported image type: {type(x)}")
|
| 265 |
+
|
| 266 |
+
def __call__(
|
| 267 |
+
self,
|
| 268 |
+
images: Any | list[Any],
|
| 269 |
+
return_tensors: str | TensorType | None = "pt",
|
| 270 |
+
**kwargs,
|
| 271 |
+
) -> dict[str, Any]:
|
| 272 |
+
"""
|
| 273 |
+
Convert images into {"pixel_values": Tensor/ndarray}.
|
| 274 |
+
์ด๋ฏธ์ง๋ฅผ {"pixel_values": Tensor/ndarray}๋ก ๋ณํํฉ๋๋ค.
|
| 275 |
+
"""
|
| 276 |
+
images = self._ensure_list(images)
|
| 277 |
+
|
| 278 |
+
# Rebuild runtime if needed (e.g., right after deserialization).
|
| 279 |
+
# ์ง๋ ฌํ ๋ณต์ ์งํ ๋ฑ ๋ฐํ์์ด ๋น์ด์์ ์ ์์ผ๋ฏ๋ก ์ฌ๊ตฌ์ฑํฉ๋๋ค.
|
| 280 |
+
if (self._delegate is None) and (self._timm_transform is None) and (self._torchvision_transform is None):
|
| 281 |
+
self._build_runtime()
|
| 282 |
+
|
| 283 |
+
# timm path: PIL -> torch.Tensor(C,H,W) normalized float32.
|
| 284 |
+
# timm ๊ฒฝ๋ก: PIL -> torch.Tensor(C,H,W) ์ ๊ทํ float32์
๋๋ค.
|
| 285 |
+
if self._timm_transform is not None:
|
| 286 |
+
pv: list[torch.Tensor] = []
|
| 287 |
+
for im in images:
|
| 288 |
+
pil = self._to_pil_rgb(im)
|
| 289 |
+
t = self._timm_transform(pil)
|
| 290 |
+
if not isinstance(t, torch.Tensor):
|
| 291 |
+
raise RuntimeError("Unexpected timm transform output (expected torch.Tensor).")
|
| 292 |
+
pv.append(t)
|
| 293 |
+
pixel_values = torch.stack(pv, dim=0) # (B,C,H,W)
|
| 294 |
+
return self._format_return(pixel_values, return_tensors)
|
| 295 |
+
|
| 296 |
+
# torchvision path: PIL -> torch.Tensor(C,H,W) normalized float32.
|
| 297 |
+
# torchvision ๊ฒฝ๋ก: PIL -> torch.Tensor(C,H,W) ์ ๊ทํ float32์
๋๋ค.
|
| 298 |
+
if self._torchvision_transform is not None:
|
| 299 |
+
pv: list[torch.Tensor] = []
|
| 300 |
+
for im in images:
|
| 301 |
+
pil = self._to_pil_rgb(im)
|
| 302 |
+
t = self._torchvision_transform(pil)
|
| 303 |
+
if not isinstance(t, torch.Tensor):
|
| 304 |
+
raise RuntimeError("Unexpected torchvision transform output (expected torch.Tensor).")
|
| 305 |
+
pv.append(t)
|
| 306 |
+
pixel_values = torch.stack(pv, dim=0) # (B,C,H,W)
|
| 307 |
+
return self._format_return(pixel_values, return_tensors)
|
| 308 |
+
|
| 309 |
+
# transformers delegate path: rely on official processor behavior.
|
| 310 |
+
# transformers ์์ ๊ฒฝ๋ก: ๊ณต์ processor ๋์์ ๊ทธ๋๋ก ์ฌ์ฉํฉ๋๋ค.
|
| 311 |
+
if self._delegate is None:
|
| 312 |
+
raise RuntimeError("Processor runtime not built: delegate is None and no transforms are available.")
|
| 313 |
+
|
| 314 |
+
return self._delegate(images, return_tensors=return_tensors, **kwargs)
|
| 315 |
+
|
| 316 |
+
@staticmethod
|
| 317 |
+
def _format_return(pixel_values: torch.Tensor, return_tensors: str | TensorType | None) -> dict[str, Any]:
|
| 318 |
+
"""
|
| 319 |
+
Format pixel_values according to return_tensors.
|
| 320 |
+
return_tensors์ ๋ง์ถฐ pixel_values ๋ฐํ ํฌ๋งท์ ๋ง์ถฅ๋๋ค.
|
| 321 |
+
"""
|
| 322 |
+
if return_tensors is None or return_tensors in ("pt", TensorType.PYTORCH):
|
| 323 |
+
return {"pixel_values": pixel_values}
|
| 324 |
+
if return_tensors in ("np", TensorType.NUMPY):
|
| 325 |
+
return {"pixel_values": pixel_values.detach().cpu().numpy()}
|
| 326 |
+
raise ValueError(f"Unsupported return_tensors={return_tensors}. Use 'pt' or 'np'.")
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
# Register this processor for AutoImageProcessor resolution.
|
| 330 |
+
# AutoImageProcessor ํด์์ ์ํด ์ด processor๋ฅผ ๋ฑ๋กํฉ๋๋ค.
|
| 331 |
+
if __name__ != "__main__":
|
| 332 |
+
BackboneMLPHead224ImageProcessor.register_for_auto_class("AutoImageProcessor")
|
models/google__efficientnet-b0/model.safetensors
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 17558436
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:01add885928661dde020c6cc5adaf1e6b8d6684a9effa7ef2903ac8d03fc9a5c
|
| 3 |
size 17558436
|
models/google__efficientnet-b0/preprocessor_config.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"auto_map": {
|
| 3 |
+
"AutoImageProcessor": "ds_proc.BackboneMLPHead224ImageProcessor"
|
| 4 |
+
},
|
| 5 |
+
"backbone_name_or_path": "google/efficientnet-b0",
|
| 6 |
+
"image_processor_type": "BackboneMLPHead224ImageProcessor",
|
| 7 |
+
"is_training": false
|
| 8 |
+
}
|
models/google__vit-base-patch16-224/config.json
CHANGED
|
@@ -24,10 +24,11 @@
|
|
| 24 |
"num_labels": 3,
|
| 25 |
"transformers_version": "5.1.0",
|
| 26 |
"ds_provenance": {
|
| 27 |
-
"created_at": "
|
| 28 |
"repo_id": "dsaint31/bb_mlp_224",
|
| 29 |
"subdir": "models/google__vit-base-patch16-224",
|
| 30 |
"wrapper_class": "BackboneWithMLPHeadForImageClassification",
|
|
|
|
| 31 |
"backbone_id": "google/vit-base-patch16-224",
|
| 32 |
"pretrained_injected": true,
|
| 33 |
"policy_init_pretrained_forbidden": true,
|
|
|
|
| 24 |
"num_labels": 3,
|
| 25 |
"transformers_version": "5.1.0",
|
| 26 |
"ds_provenance": {
|
| 27 |
+
"created_at": "20260210_111913",
|
| 28 |
"repo_id": "dsaint31/bb_mlp_224",
|
| 29 |
"subdir": "models/google__vit-base-patch16-224",
|
| 30 |
"wrapper_class": "BackboneWithMLPHeadForImageClassification",
|
| 31 |
+
"processor_class": "BackboneMLPHead224ImageProcessor",
|
| 32 |
"backbone_id": "google/vit-base-patch16-224",
|
| 33 |
"pretrained_injected": true,
|
| 34 |
"policy_init_pretrained_forbidden": true,
|
models/google__vit-base-patch16-224/ds_cfg.py
CHANGED
|
@@ -1,84 +1,175 @@
|
|
| 1 |
-
from typing import Literal, Union
|
| 2 |
-
|
| 3 |
-
from transformers import AutoConfig
|
| 4 |
from transformers import PretrainedConfig
|
|
|
|
| 5 |
from ds_meta import BackboneID, BACKBONE_META
|
| 6 |
|
| 7 |
-
|
| 8 |
-
# 3) Config
|
| 9 |
-
# ============================================================
|
| 10 |
class BackboneMLPHeadConfig(PretrainedConfig):
|
| 11 |
"""
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
----------
|
| 16 |
-
- transformers ๋ด๋ถ ๊ฒฝ๋ก์์ "๋ฌด์ธ์ ์์ฑ"์ด ๋ฐ์ํ ์ ์์ผ๋ฏ๋ก,
|
| 17 |
-
backbone_name_or_path=None์ธ ๊ฒฝ์ฐ๋ ๊ฒ์ฆ ์์ด ๊ธฐ๋ณธ๊ฐ๋ง ์ธํ
ํ๊ณ return
|
| 18 |
-
ํ๋ ๊ตฌํ์ด ๋ฐ๋์ ํ์ํจ.
|
| 19 |
-
- ๋จ, ์ค์ ์ฌ์ฉ์์๋ backbone_name_or_path๋ฅผ ๋ฐ๋์ ์ง์ ํด์ ์์ฑํด์ผ ํจ.
|
| 20 |
"""
|
| 21 |
|
| 22 |
-
#
|
|
|
|
| 23 |
model_type = "backbone-mlphead-224-fixed"
|
| 24 |
|
| 25 |
-
# ์์ฑ์์์ ๋ชจ๋ ์ธ์์ ๊ธฐ๋ณธ๊ฐ ๋ถ์ฌ ๋ฐ๋์ ํ์!
|
| 26 |
def __init__(
|
| 27 |
self,
|
| 28 |
backbone_name_or_path: BackboneID | None = None,
|
| 29 |
mlp_head_bottleneck: int = 256,
|
| 30 |
mlp_head_dropout: float = 0.2,
|
| 31 |
-
label2id: dict[str, int]|None = None,
|
| 32 |
-
id2label: dict[int, str]|None = None,
|
| 33 |
**kwargs,
|
| 34 |
):
|
| 35 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
if backbone_name_or_path is None:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
super().__init__(**kwargs)
|
|
|
|
|
|
|
|
|
|
| 38 |
self.backbone_name_or_path = None
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
#
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
return
|
| 44 |
|
| 45 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
if backbone_name_or_path not in BACKBONE_META:
|
| 47 |
raise ValueError(
|
| 48 |
f"Unsupported backbone_name_or_path={backbone_name_or_path}. "
|
| 49 |
f"Allowed: {sorted(BACKBONE_META.keys())}"
|
| 50 |
)
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
#
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
kwargs["num_labels"] = num_labels
|
| 62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
super().__init__(
|
| 64 |
-
label2id=
|
| 65 |
-
id2label=
|
| 66 |
**kwargs,
|
| 67 |
)
|
| 68 |
-
|
| 69 |
-
# 4) ์ค์: ๋ถ๋ชจ ํ๋์๋ num_labels ๋ฐ์(์ ์ฅ/๋ก๋ ์์ )
|
| 70 |
-
self.num_labels = num_labels
|
| 71 |
-
self.label2id = label2id
|
| 72 |
-
self.id2label = id2label
|
| 73 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
self.backbone_name_or_path = backbone_name_or_path
|
| 75 |
-
self.mlp_head_bottleneck
|
| 76 |
-
self.mlp_head_dropout
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
def to_dict(self):
|
|
|
|
|
|
|
| 79 |
output = super().to_dict()
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
return output
|
| 83 |
|
|
|
|
|
|
|
|
|
|
| 84 |
BackboneMLPHeadConfig.register_for_auto_class("AutoConfig")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from transformers import PretrainedConfig
|
| 2 |
+
|
| 3 |
from ds_meta import BackboneID, BACKBONE_META
|
| 4 |
|
| 5 |
+
|
|
|
|
|
|
|
| 6 |
class BackboneMLPHeadConfig(PretrainedConfig):
|
| 7 |
"""
|
| 8 |
+
Configuration for Backbone + MLP Head models.
|
| 9 |
+
|
| 10 |
+
Backbone + MLP Head ๋ชจ๋ธ์ ์ํ ์ค์ ํด๋์ค์
๋๋ค.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
"""
|
| 12 |
|
| 13 |
+
# This string is used by Hugging Face AutoConfig to identify the config class.
|
| 14 |
+
# Hugging Face AutoConfig๊ฐ ์ด config๋ฅผ ์๋ณํ๊ธฐ ์ํด ์ฌ์ฉํ๋ ๊ณ ์ ID์
๋๋ค.
|
| 15 |
model_type = "backbone-mlphead-224-fixed"
|
| 16 |
|
|
|
|
| 17 |
def __init__(
|
| 18 |
self,
|
| 19 |
backbone_name_or_path: BackboneID | None = None,
|
| 20 |
mlp_head_bottleneck: int = 256,
|
| 21 |
mlp_head_dropout: float = 0.2,
|
| 22 |
+
label2id: dict[str, int] | None = None,
|
| 23 |
+
id2label: dict[int, str] | None = None,
|
| 24 |
**kwargs,
|
| 25 |
):
|
| 26 |
+
# ============================================================
|
| 27 |
+
# 0) Guard for argument-less construction
|
| 28 |
+
# 0) ๋ฌด์ธ์ ์์ฑ ๊ฒฝ๋ก ๋ฐฉ์ด
|
| 29 |
+
# ============================================================
|
| 30 |
+
# Transformers may internally construct this config without arguments
|
| 31 |
+
# (e.g., during AutoConfig resolution or Hub loading).
|
| 32 |
+
# Transformers ๋ด๋ถ์์ AutoConfig ๋๋ Hub ๋ก๋ ๊ณผ์ ์ค
|
| 33 |
+
# ์ธ์ ์์ด config๋ฅผ ์์ฑํ๋ ๊ฒฝ๋ก๊ฐ ์ค์ ๋ก ์กด์ฌํฉ๋๋ค.
|
| 34 |
+
#
|
| 35 |
+
# In this case, we must NOT validate or raise errors.
|
| 36 |
+
# ์ด ๊ฒฝ์ฐ ๊ฒ์ฆ์ด๋ ์์ธ๋ฅผ ๋ฐ์์ํค๋ฉด ์ ๋ฉ๋๋ค.
|
| 37 |
+
#
|
| 38 |
+
# The goal here is to provide a minimal, serialization-safe config.
|
| 39 |
+
# ๋ชฉํ๋ ์ต์ํ์ ๊ฐ๋ง ์ฑ์ ์ง๋ ฌํ/์ญ์ง๋ ฌํ๊ฐ ๊นจ์ง์ง ์๊ฒ ํ๋ ๊ฒ์
๋๋ค.
|
| 40 |
if backbone_name_or_path is None:
|
| 41 |
+
# num_labels may be implicitly assumed by downstream code,
|
| 42 |
+
# so we explicitly set a safe default.
|
| 43 |
+
# num_labels๋ downstream ์ฝ๋์์ ์๋ฌต์ ์ผ๋ก ๊ฐ์ ๋๋ฏ๋ก
|
| 44 |
+
# ์์ ํ ๊ธฐ๋ณธ๊ฐ์ ๋ช
์์ ์ผ๋ก ๋ฃ์ด์ค๋๋ค.
|
| 45 |
+
if "num_labels" not in kwargs:
|
| 46 |
+
kwargs["num_labels"] = 0
|
| 47 |
+
|
| 48 |
super().__init__(**kwargs)
|
| 49 |
+
|
| 50 |
+
# Backbone is intentionally unset in this path.
|
| 51 |
+
# ์ด ๊ฒฝ๋ก์์๋ backbone์ ์๋์ ์ผ๋ก ์ค์ ํ์ง ์์ต๋๋ค.
|
| 52 |
self.backbone_name_or_path = None
|
| 53 |
+
|
| 54 |
+
# Store MLP head hyperparameters for completeness.
|
| 55 |
+
# MLP head ๊ด๋ จ ํ์ดํผํ๋ผ๋ฏธํฐ๋ ํํ ์ ์ง๋ฅผ ์ํด ์ ์ฅํฉ๋๋ค.
|
| 56 |
+
self.mlp_head_bottleneck = int(mlp_head_bottleneck)
|
| 57 |
+
self.mlp_head_dropout = float(mlp_head_dropout)
|
| 58 |
+
|
| 59 |
+
# Empty label mappings ensure stable save/load behavior.
|
| 60 |
+
# ๋น label ๋งคํ์ ์ ์ฅ/๋ก๋ ์์ ์ฑ์ ๋ณด์ฅํฉ๋๋ค.
|
| 61 |
+
self.label2id = {}
|
| 62 |
+
self.id2label = {}
|
| 63 |
+
self.num_labels = int(kwargs.get("num_labels", 0))
|
| 64 |
return
|
| 65 |
|
| 66 |
+
# ============================================================
|
| 67 |
+
# 1) Backbone whitelist validation
|
| 68 |
+
# 1) Backbone ํ์ดํธ๋ฆฌ์คํธ ๊ฒ์ฆ
|
| 69 |
+
# ============================================================
|
| 70 |
+
# Only backbones explicitly registered in BACKBONE_META are allowed.
|
| 71 |
+
# BACKBONE_META์ ๋ฑ๋ก๋ backbone๋ง ํ์ฉํฉ๋๋ค.
|
| 72 |
+
#
|
| 73 |
+
# This prevents accidental usage of unsupported or inconsistent backbones.
|
| 74 |
+
# ์ง์๋์ง ์๊ฑฐ๋ ๋ถ์ผ์นํ backbone ์ฌ์ฉ์ ์์ฒ ์ฐจ๋จํฉ๋๋ค.
|
| 75 |
if backbone_name_or_path not in BACKBONE_META:
|
| 76 |
raise ValueError(
|
| 77 |
f"Unsupported backbone_name_or_path={backbone_name_or_path}. "
|
| 78 |
f"Allowed: {sorted(BACKBONE_META.keys())}"
|
| 79 |
)
|
| 80 |
+
|
| 81 |
+
# ============================================================
|
| 82 |
+
# 2) Label mapping normalization
|
| 83 |
+
# 2) ๋ผ๋ฒจ ๋งคํ ์ ๊ทํ
|
| 84 |
+
# ============================================================
|
| 85 |
+
# Both label2id and id2label may be None during pure loading scenarios.
|
| 86 |
+
# ๋จ์ ๋ก๋(from_pretrained) ์ label2id/id2label์ด None์ผ ์ ์์ต๋๋ค.
|
| 87 |
+
#
|
| 88 |
+
# We allow this here to keep Hub loading and verification stable.
|
| 89 |
+
# Hub ๋ก๋ ๋ฐ ๊ฒ์ฆ ์์ ์ฑ์ ์ํด ์ฌ๊ธฐ์๋ ์ด๋ฅผ ํ์ฉํฉ๋๋ค.
|
| 90 |
+
#
|
| 91 |
+
# Fail-fast validation should happen at model or training level instead.
|
| 92 |
+
# ์ค์ ์ฌ์ฉ ๊ฒ์ฆ์ ๋ชจ๋ธ ๋๋ ํ์ต ๋จ๊ณ์์ fail-fast๋ก ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค.
|
| 93 |
+
if label2id is None and id2label is None:
|
| 94 |
+
# Respect num_labels if explicitly provided, otherwise default to 0.
|
| 95 |
+
# num_labels๊ฐ ์ฃผ์ด์ก๋ค๋ฉด ์กด์คํ๊ณ , ์๋๋ฉด 0์ผ๋ก ๋ก๋๋ค.
|
| 96 |
+
num_labels = int(kwargs.get("num_labels", 0))
|
| 97 |
+
label2id_norm: dict[str, int] = {}
|
| 98 |
+
id2label_norm: dict[int, str] = {}
|
| 99 |
+
else:
|
| 100 |
+
# If only one mapping is provided, derive the other.
|
| 101 |
+
# ํ๋๋ง ์ฃผ์ด์ง ๊ฒฝ์ฐ ๋๋จธ์ง๋ฅผ ์๋์ผ๋ก ์์ฑํฉ๋๋ค.
|
| 102 |
+
if id2label is None:
|
| 103 |
+
id2label = {v: k for k, v in label2id.items()}
|
| 104 |
+
if label2id is None:
|
| 105 |
+
label2id = {v: k for k, v in id2label.items()}
|
| 106 |
+
|
| 107 |
+
# Ensure both mappings are consistent in size.
|
| 108 |
+
# ๋ ๋งคํ์ ํฌ๊ธฐ๊ฐ ์ผ์นํ๋์ง ํ์ธํฉ๋๋ค.
|
| 109 |
+
if len(label2id) != len(id2label):
|
| 110 |
+
raise ValueError(
|
| 111 |
+
f"label2id/id2label size mismatch: "
|
| 112 |
+
f"{len(label2id)} vs {len(id2label)}"
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
num_labels = len(id2label)
|
| 116 |
+
label2id_norm = dict(label2id)
|
| 117 |
+
id2label_norm = dict(id2label)
|
| 118 |
+
|
| 119 |
+
# ============================================================
|
| 120 |
+
# 3) num_labels consistency enforcement
|
| 121 |
+
# 3) num_labels ์ผ๊ด์ฑ ๊ฐ์
|
| 122 |
+
# ============================================================
|
| 123 |
+
# If num_labels is provided via kwargs, it must match inferred labels.
|
| 124 |
+
# kwargs๋ก num_labels๊ฐ ๋ค์ด์จ ๊ฒฝ์ฐ ์ถ๋ก ๋ ๊ฐ๊ณผ ๋ฐ๋์ ์ผ์นํด์ผ ํฉ๋๋ค.
|
| 125 |
+
if "num_labels" in kwargs:
|
| 126 |
+
if (label2id is not None or id2label is not None) and int(kwargs["num_labels"]) != num_labels:
|
| 127 |
+
raise ValueError(
|
| 128 |
+
f"kwargs['num_labels']={kwargs['num_labels']} "
|
| 129 |
+
f"!= inferred num_labels={num_labels}"
|
| 130 |
+
)
|
| 131 |
+
else:
|
| 132 |
kwargs["num_labels"] = num_labels
|
| 133 |
|
| 134 |
+
# ============================================================
|
| 135 |
+
# 4) Parent initialization
|
| 136 |
+
# 4) ๋ถ๋ชจ ํด๋์ค ์ด๊ธฐํ
|
| 137 |
+
# ============================================================
|
| 138 |
+
# Initialize PretrainedConfig with normalized label mappings.
|
| 139 |
+
# ์ ๊ทํ๋ ๋ผ๋ฒจ ๋งคํ์ ์ฌ์ฉํ์ฌ PretrainedConfig๋ฅผ ์ด๊ธฐํํฉ๋๋ค.
|
| 140 |
super().__init__(
|
| 141 |
+
label2id=label2id_norm,
|
| 142 |
+
id2label=id2label_norm,
|
| 143 |
**kwargs,
|
| 144 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
+
# ============================================================
|
| 147 |
+
# 5) Explicit attribute assignment for save/load stability
|
| 148 |
+
# 5) ์ ์ฅ/๋ก๋ ์์ ์ฑ์ ์ํ ๋ช
์์ ์์ฑ ๊ณ ์
|
| 149 |
+
# ============================================================
|
| 150 |
+
# Explicitly reassign critical fields to avoid subtle serialization issues.
|
| 151 |
+
# ๋ฏธ๋ฌํ ์ง๋ ฌํ ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ํต์ฌ ํ๋๋ฅผ ๋ช
์์ ์ผ๋ก ๋ค์ ์ค์ ํฉ๋๋ค.
|
| 152 |
self.backbone_name_or_path = backbone_name_or_path
|
| 153 |
+
self.mlp_head_bottleneck = int(mlp_head_bottleneck)
|
| 154 |
+
self.mlp_head_dropout = float(mlp_head_dropout)
|
| 155 |
+
|
| 156 |
+
self.label2id = label2id_norm
|
| 157 |
+
self.id2label = id2label_norm
|
| 158 |
+
self.num_labels = int(kwargs["num_labels"])
|
| 159 |
|
| 160 |
def to_dict(self):
|
| 161 |
+
# Call the parent implementation first.
|
| 162 |
+
# ๋จผ์ ๋ถ๋ชจ ๊ตฌํ์ ํธ์ถํฉ๋๋ค.
|
| 163 |
output = super().to_dict()
|
| 164 |
+
|
| 165 |
+
# Force num_labels to be present and consistent.
|
| 166 |
+
# num_labels๊ฐ ๋ฐ๋์ ์กด์ฌํ๊ณ ์ผ๊ด๋๋๋ก ๊ฐ์ ํฉ๋๋ค.
|
| 167 |
+
output["num_labels"] = int(
|
| 168 |
+
getattr(self, "num_labels", output.get("num_labels", 0))
|
| 169 |
+
)
|
| 170 |
return output
|
| 171 |
|
| 172 |
+
|
| 173 |
+
# Register this config so it can be resolved via AutoConfig.
|
| 174 |
+
# AutoConfig๋ฅผ ํตํด ์ด config๊ฐ ํด์๋ ์ ์๋๋ก ๋ฑ๋กํฉ๋๋ค.
|
| 175 |
BackboneMLPHeadConfig.register_for_auto_class("AutoConfig")
|
models/google__vit-base-patch16-224/ds_model.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
# -*- coding: utf-8 -*-
|
| 3 |
|
| 4 |
-
|
| 5 |
|
| 6 |
-
from
|
| 7 |
-
from typing import Dict, Any, Optional, Tuple, Set, List
|
| 8 |
|
| 9 |
import torch
|
| 10 |
import torch.nn as nn
|
|
@@ -12,146 +11,88 @@ import torch.nn.functional as F
|
|
| 12 |
|
| 13 |
# --- transformers core ---
|
| 14 |
from transformers.modeling_utils import PreTrainedModel
|
| 15 |
-
from transformers import AutoModel, AutoConfig
|
| 16 |
from transformers.modeling_outputs import ImageClassifierOutput
|
| 17 |
|
| 18 |
# --- torchvision ---
|
| 19 |
from torchvision import models as tv_models
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
# - ๊ธฐ์กด ds_cfg.BackboneMLPHeadConfig์ ํ๋๋ช
์ ๋ง์ถ์์ต๋๋ค.
|
| 25 |
-
# - ์ด๋ฏธ ds_cfg.py๊ฐ ์๋ค๋ฉด: ์ด ๋ธ๋ก์ ์ง์ฐ๊ณ "from ds_cfg import BackboneMLPHeadConfig"๋ก ๋ฐ๊ฟ๋ ๋ฉ๋๋ค.
|
| 26 |
-
# ============================================================
|
| 27 |
-
class BackboneMLPHeadConfig(PretrainedConfig):
|
| 28 |
-
model_type = "backbone_mlphead"
|
| 29 |
-
|
| 30 |
-
def __init__(
|
| 31 |
-
self,
|
| 32 |
-
backbone_name_or_path: Optional[str] = None,
|
| 33 |
-
num_labels: int = 2,
|
| 34 |
-
id2label: Optional[Dict[int, str]] = None,
|
| 35 |
-
label2id: Optional[Dict[str, int]] = None,
|
| 36 |
-
mlp_head_bottleneck: int = 256,
|
| 37 |
-
mlp_head_dropout: float = 0.2,
|
| 38 |
-
**kwargs,
|
| 39 |
-
):
|
| 40 |
-
super().__init__(**kwargs)
|
| 41 |
-
self.backbone_name_or_path = backbone_name_or_path
|
| 42 |
-
self.num_labels = num_labels
|
| 43 |
-
self.id2label = id2label or {i: f"LABEL_{i}" for i in range(num_labels)}
|
| 44 |
-
self.label2id = label2id or {v: k for k, v in self.id2label.items()}
|
| 45 |
-
self.mlp_head_bottleneck = mlp_head_bottleneck
|
| 46 |
-
self.mlp_head_dropout = mlp_head_dropout
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
# ============================================================
|
| 50 |
-
# (1) BACKBONE_META
|
| 51 |
-
# - ์ด๋ฏธ ds_meta.py๊ฐ ์๋ค๋ฉด: ์ด ๋ธ๋ก์ ์ง์ฐ๊ณ "from ds_meta import BACKBONE_META"๋ก ๋ฐ๊ฟ๋ ๋ฉ๋๋ค.
|
| 52 |
-
# ============================================================
|
| 53 |
-
BACKBONE_META: Dict[str, Dict[str, Any]] = {
|
| 54 |
-
# transformers
|
| 55 |
-
"google/vit-base-patch16-224": {
|
| 56 |
-
"type": "vit",
|
| 57 |
-
"feat_rule": "cls",
|
| 58 |
-
"feat_dim": 768,
|
| 59 |
-
"has_bn": False,
|
| 60 |
-
"unfreeze": "last_n",
|
| 61 |
-
},
|
| 62 |
-
"microsoft/swin-tiny-patch4-window7-224": {
|
| 63 |
-
"type": "swin",
|
| 64 |
-
"feat_rule": "pool_or_mean",
|
| 65 |
-
"feat_dim": 768,
|
| 66 |
-
"has_bn": False,
|
| 67 |
-
"unfreeze": "last_n",
|
| 68 |
-
},
|
| 69 |
-
"microsoft/resnet-50": {
|
| 70 |
-
"type": "resnet",
|
| 71 |
-
"feat_rule": "pool_or_gap",
|
| 72 |
-
"feat_dim": 2048,
|
| 73 |
-
"has_bn": True,
|
| 74 |
-
"unfreeze": "last_n",
|
| 75 |
-
},
|
| 76 |
-
"google/efficientnet-b0": {
|
| 77 |
-
"type": "efficientnet",
|
| 78 |
-
"feat_rule": "pool_or_gap",
|
| 79 |
-
"feat_dim": 1280,
|
| 80 |
-
"has_bn": True,
|
| 81 |
-
"unfreeze": "last_n",
|
| 82 |
-
},
|
| 83 |
-
|
| 84 |
-
# densenet
|
| 85 |
-
"timm/densenet121.tv_in1k": {
|
| 86 |
-
"type": "timm_densenet",
|
| 87 |
-
"feat_rule": "timm_gap",
|
| 88 |
-
"feat_dim": 1024,
|
| 89 |
-
"has_bn": True,
|
| 90 |
-
"unfreeze": "last_n",
|
| 91 |
-
},
|
| 92 |
-
"torchvision/densenet121": {
|
| 93 |
-
"type": "torchvision_densenet",
|
| 94 |
-
"feat_rule": "torchvision_densenet_gap",
|
| 95 |
-
"feat_dim": 1024,
|
| 96 |
-
"has_bn": True,
|
| 97 |
-
"unfreeze": "last_n",
|
| 98 |
-
},
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
# ============================================================
|
| 103 |
-
# (2) MLP Head
|
| 104 |
-
# - ์ด๋ฏธ mlp_head.py๊ฐ ์๋ค๋ฉด: ์ด ๋ธ๋ก์ ์ง์ฐ๊ณ "from mlp_head import MLPHead"๋ก ๋ฐ๊ฟ๋ ๋ฉ๋๋ค.
|
| 105 |
-
# ============================================================
|
| 106 |
-
class MLPHead(nn.Module):
|
| 107 |
-
def __init__(self, in_dim: int, num_labels: int, bottleneck: int = 256, p: float = 0.2):
|
| 108 |
-
super().__init__()
|
| 109 |
-
self.fc2 = nn.Linear(in_dim, bottleneck)
|
| 110 |
-
self.act2 = nn.GELU()
|
| 111 |
-
self.drop2 = nn.Dropout(p)
|
| 112 |
-
self.fc3 = nn.Linear(bottleneck, num_labels)
|
| 113 |
-
|
| 114 |
-
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 115 |
-
x = self.drop2(self.act2(self.fc2(x)))
|
| 116 |
-
return self.fc3(x)
|
| 117 |
-
|
| 118 |
|
| 119 |
# ============================================================
|
| 120 |
# (3) Model: backbone + MLP head
|
| 121 |
-
#
|
| 122 |
-
# - __init__: backbone ๊ตฌ์กฐ(skeleton)๋ง ์์ฑ. pretrained๋ฅผ ์ ๋ ๋ก๋ํ์ง ์์.
|
| 123 |
-
# - pretrained ์ฃผ์
: model.load_backbone_pretrained_()๋ฅผ "fresh-start์์๋ง" ์ง์ ํธ์ถ.
|
| 124 |
-
# - from_pretrained override ์์: HF ๊ธฐ๋ณธ ๋ก๋๊ฐ checkpoint๋ฅผ ๊ทธ๋๋ก ๋ณต์.
|
| 125 |
# ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
|
|
|
|
|
| 127 |
config_class = BackboneMLPHeadConfig
|
| 128 |
|
| 129 |
def __init__(self, config: BackboneMLPHeadConfig):
|
|
|
|
|
|
|
| 130 |
super().__init__(config)
|
| 131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
if config.backbone_name_or_path is None:
|
| 133 |
-
raise ValueError(
|
|
|
|
|
|
|
|
|
|
| 134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
self._meta = BACKBONE_META[config.backbone_name_or_path]
|
| 136 |
|
| 137 |
-
#
|
|
|
|
| 138 |
self.backbone = self._build_backbone_skeleton(config.backbone_name_or_path)
|
| 139 |
|
| 140 |
-
#
|
|
|
|
| 141 |
self.classifier = MLPHead(
|
| 142 |
in_dim=int(self._meta["feat_dim"]),
|
| 143 |
-
num_labels=config.num_labels,
|
| 144 |
-
bottleneck=config.mlp_head_bottleneck,
|
| 145 |
-
p=config.mlp_head_dropout,
|
| 146 |
)
|
| 147 |
|
| 148 |
-
# HF
|
|
|
|
| 149 |
self.post_init()
|
| 150 |
|
| 151 |
def init_weights(self):
|
| 152 |
"""
|
| 153 |
-
|
| 154 |
-
head๋ง ์ด๊ธฐํ
|
|
|
|
|
|
|
|
|
|
| 155 |
"""
|
| 156 |
if getattr(self, "classifier", None) is not None:
|
| 157 |
self.classifier.apply(self._init_weights)
|
|
@@ -159,8 +100,11 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 159 |
|
| 160 |
# ----------------------------
|
| 161 |
# backbone skeleton builders
|
|
|
|
| 162 |
# ----------------------------
|
| 163 |
def _build_backbone_skeleton(self, backbone_id: str) -> nn.Module:
|
|
|
|
|
|
|
| 164 |
meta = BACKBONE_META[backbone_id]
|
| 165 |
t = meta["type"]
|
| 166 |
|
|
@@ -170,18 +114,24 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 170 |
if t == "torchvision_densenet":
|
| 171 |
return self._build_torchvision_densenet_skeleton(backbone_id)
|
| 172 |
|
| 173 |
-
# transformers:
|
|
|
|
| 174 |
bb_cfg = AutoConfig.from_pretrained(backbone_id)
|
| 175 |
return AutoModel.from_config(bb_cfg)
|
| 176 |
|
| 177 |
@staticmethod
|
| 178 |
def _build_timm_densenet_skeleton(hf_repo_id: str) -> nn.Module:
|
|
|
|
|
|
|
| 179 |
try:
|
| 180 |
import timm
|
| 181 |
except Exception as e:
|
| 182 |
-
raise ImportError(
|
|
|
|
|
|
|
| 183 |
|
| 184 |
-
#
|
|
|
|
| 185 |
return timm.create_model(
|
| 186 |
f"hf_hub:{hf_repo_id}",
|
| 187 |
pretrained=False,
|
|
@@ -190,33 +140,33 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 190 |
|
| 191 |
@staticmethod
|
| 192 |
def _build_torchvision_densenet_skeleton(model_id: str) -> nn.Module:
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
"torchvision
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
arch = mapping[model_id]
|
| 203 |
-
fn = getattr(tv_models, arch)
|
| 204 |
-
|
| 205 |
-
# ๊ตฌ์กฐ๋ง (weights=None)
|
| 206 |
-
m = fn(weights=None).eval()
|
| 207 |
return m
|
| 208 |
|
| 209 |
# ------------------------------------------------------------
|
| 210 |
-
#
|
|
|
|
| 211 |
# ------------------------------------------------------------
|
| 212 |
@torch.no_grad()
|
| 213 |
-
def load_backbone_pretrained_(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
"""
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
model.load_backbone_pretrained_()
|
| 218 |
|
| 219 |
-
|
|
|
|
| 220 |
"""
|
| 221 |
bb = self.config.backbone_name_or_path
|
| 222 |
meta = BACKBONE_META[bb]
|
|
@@ -230,48 +180,58 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 230 |
self._load_torchvision_pretrained_into_skeleton_(bb)
|
| 231 |
return
|
| 232 |
|
| 233 |
-
# transformers pretrained
|
|
|
|
| 234 |
ref = AutoModel.from_pretrained(
|
| 235 |
bb,
|
| 236 |
low_cpu_mem_usage=low_cpu_mem_usage,
|
| 237 |
device_map=device_map,
|
| 238 |
)
|
|
|
|
|
|
|
|
|
|
| 239 |
self.backbone.load_state_dict(ref.state_dict(), strict=False)
|
| 240 |
del ref
|
| 241 |
|
| 242 |
@torch.no_grad()
|
| 243 |
def _load_timm_pretrained_into_skeleton_(self, hf_repo_id: str):
|
|
|
|
|
|
|
| 244 |
import timm
|
|
|
|
|
|
|
|
|
|
| 245 |
ref = timm.create_model(
|
| 246 |
f"hf_hub:{hf_repo_id}",
|
| 247 |
pretrained=True,
|
| 248 |
num_classes=0,
|
| 249 |
).eval()
|
|
|
|
| 250 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 251 |
del ref
|
| 252 |
|
| 253 |
@torch.no_grad()
|
| 254 |
def _load_torchvision_pretrained_into_skeleton_(self, model_id: str):
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
"torchvision
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
arch, weights = mapping[model_id]
|
| 265 |
-
fn = getattr(tv_models, arch)
|
| 266 |
-
ref = fn(weights=weights).eval()
|
| 267 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 268 |
del ref
|
| 269 |
|
| 270 |
# ----------------------------
|
| 271 |
# feature extraction
|
|
|
|
| 272 |
# ----------------------------
|
| 273 |
@staticmethod
|
| 274 |
def _pool_or_gap(outputs) -> torch.Tensor:
|
|
|
|
|
|
|
| 275 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 276 |
x = outputs.pooler_output
|
| 277 |
if x.dim() == 2:
|
|
@@ -280,29 +240,42 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 280 |
return x.flatten(1)
|
| 281 |
raise RuntimeError(f"Unexpected pooler_output shape: {tuple(x.shape)}")
|
| 282 |
|
|
|
|
|
|
|
| 283 |
x = outputs.last_hidden_state
|
| 284 |
if x.dim() == 4:
|
| 285 |
return x.mean(dim=(2, 3))
|
|
|
|
| 286 |
raise RuntimeError(
|
| 287 |
"Expected pooler_output or (B,C,H,W) last_hidden_state for CNN backbones. "
|
| 288 |
f"Got last_hidden_state shape={tuple(x.shape)}"
|
| 289 |
)
|
| 290 |
|
| 291 |
def _extract_features(self, outputs, pixel_values: Optional[torch.Tensor] = None) -> torch.Tensor:
|
|
|
|
|
|
|
| 292 |
rule = self._meta["feat_rule"]
|
| 293 |
|
| 294 |
if rule == "cls":
|
|
|
|
|
|
|
| 295 |
return outputs.last_hidden_state[:, 0, :]
|
| 296 |
|
| 297 |
if rule == "pool_or_mean":
|
|
|
|
|
|
|
| 298 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 299 |
return outputs.pooler_output
|
| 300 |
return outputs.last_hidden_state.mean(dim=1)
|
| 301 |
|
| 302 |
if rule == "pool_or_gap":
|
|
|
|
|
|
|
| 303 |
return self._pool_or_gap(outputs)
|
| 304 |
|
| 305 |
if rule == "timm_gap":
|
|
|
|
|
|
|
| 306 |
if not isinstance(outputs, torch.Tensor):
|
| 307 |
raise TypeError(f"timm_gap expects Tensor features, got {type(outputs)}")
|
| 308 |
if outputs.dim() != 4:
|
|
@@ -310,6 +283,8 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 310 |
return outputs.mean(dim=(2, 3))
|
| 311 |
|
| 312 |
if rule == "torchvision_densenet_gap":
|
|
|
|
|
|
|
| 313 |
if not isinstance(outputs, torch.Tensor):
|
| 314 |
raise TypeError(f"torchvision_densenet_gap expects Tensor, got {type(outputs)}")
|
| 315 |
if outputs.dim() != 4:
|
|
@@ -327,9 +302,13 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 327 |
return_dict=True,
|
| 328 |
**kwargs,
|
| 329 |
):
|
|
|
|
|
|
|
| 330 |
t = self._meta["type"]
|
| 331 |
|
| 332 |
if t == "timm_densenet":
|
|
|
|
|
|
|
| 333 |
if pixel_values is None:
|
| 334 |
raise ValueError("timm DenseNet backbone requires pixel_values.")
|
| 335 |
if pixel_values.dim() != 4:
|
|
@@ -341,6 +320,8 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 341 |
attentions = None
|
| 342 |
|
| 343 |
elif t == "torchvision_densenet":
|
|
|
|
|
|
|
| 344 |
if pixel_values is None:
|
| 345 |
raise ValueError("torchvision DenseNet backbone requires pixel_values.")
|
| 346 |
if pixel_values.dim() != 4:
|
|
@@ -353,6 +334,8 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 353 |
attentions = None
|
| 354 |
|
| 355 |
else:
|
|
|
|
|
|
|
| 356 |
outputs = self.backbone(
|
| 357 |
pixel_values=pixel_values,
|
| 358 |
output_attentions=output_attentions,
|
|
@@ -364,10 +347,14 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 364 |
hidden_states = getattr(outputs, "hidden_states", None)
|
| 365 |
attentions = getattr(outputs, "attentions", None)
|
| 366 |
|
|
|
|
|
|
|
| 367 |
logits = self.classifier(feats)
|
| 368 |
|
| 369 |
loss = None
|
| 370 |
if labels is not None:
|
|
|
|
|
|
|
| 371 |
loss = F.cross_entropy(logits, labels)
|
| 372 |
|
| 373 |
if not return_dict:
|
|
@@ -383,20 +370,27 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 383 |
|
| 384 |
|
| 385 |
# ============================================================
|
| 386 |
-
# Freeze / Unfreeze utilities
|
|
|
|
| 387 |
# ============================================================
|
| 388 |
def _set_requires_grad(module: nn.Module, flag: bool):
|
|
|
|
|
|
|
| 389 |
for p in module.parameters():
|
| 390 |
p.requires_grad = flag
|
| 391 |
|
| 392 |
|
| 393 |
def set_bn_eval(module: nn.Module):
|
|
|
|
|
|
|
| 394 |
for m in module.modules():
|
| 395 |
if isinstance(m, (nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d, nn.SyncBatchNorm)):
|
| 396 |
m.eval()
|
| 397 |
|
| 398 |
|
| 399 |
def freeze_backbone(model: BackboneWithMLPHeadForImageClassification, freeze_bn: bool = True):
|
|
|
|
|
|
|
| 400 |
_set_requires_grad(model.backbone, False)
|
| 401 |
_set_requires_grad(model.classifier, True)
|
| 402 |
|
|
@@ -406,6 +400,8 @@ def freeze_backbone(model: BackboneWithMLPHeadForImageClassification, freeze_bn:
|
|
| 406 |
|
| 407 |
|
| 408 |
def finetune_train_mode(model: BackboneWithMLPHeadForImageClassification, keep_bn_eval: bool = True):
|
|
|
|
|
|
|
| 409 |
model.train()
|
| 410 |
meta = BACKBONE_META[model.config.backbone_name_or_path]
|
| 411 |
if keep_bn_eval and meta.get("has_bn", False):
|
|
@@ -413,6 +409,8 @@ def finetune_train_mode(model: BackboneWithMLPHeadForImageClassification, keep_b
|
|
| 413 |
|
| 414 |
|
| 415 |
def trainable_summary(model: nn.Module):
|
|
|
|
|
|
|
| 416 |
total = sum(p.numel() for p in model.parameters())
|
| 417 |
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 418 |
ratio = trainable / total if total > 0 else 0.0
|
|
@@ -425,6 +423,8 @@ def unfreeze_last_stage(
|
|
| 425 |
last_n: int = 2,
|
| 426 |
keep_bn_eval: bool = True,
|
| 427 |
):
|
|
|
|
|
|
|
| 428 |
freeze_backbone(model, freeze_bn=keep_bn_eval)
|
| 429 |
|
| 430 |
n = int(last_n)
|
|
@@ -438,12 +438,16 @@ def unfreeze_last_stage(
|
|
| 438 |
bb_type = meta["type"]
|
| 439 |
|
| 440 |
if bb_type == "vit":
|
|
|
|
|
|
|
| 441 |
blocks = list(model.backbone.encoder.layer)
|
| 442 |
for blk in blocks[-n:]:
|
| 443 |
_set_requires_grad(blk, True)
|
| 444 |
return
|
| 445 |
|
| 446 |
if bb_type == "swin":
|
|
|
|
|
|
|
| 447 |
stages = list(model.backbone.encoder.layers)
|
| 448 |
blocks: List[nn.Module] = []
|
| 449 |
for st in stages:
|
|
@@ -453,6 +457,8 @@ def unfreeze_last_stage(
|
|
| 453 |
return
|
| 454 |
|
| 455 |
if bb_type == "resnet":
|
|
|
|
|
|
|
| 456 |
bb = model.backbone
|
| 457 |
for name in ("layer1", "layer2", "layer3", "layer4"):
|
| 458 |
if not hasattr(bb, name):
|
|
@@ -472,6 +478,8 @@ def unfreeze_last_stage(
|
|
| 472 |
return
|
| 473 |
|
| 474 |
if bb_type == "efficientnet":
|
|
|
|
|
|
|
| 475 |
bb = model.backbone
|
| 476 |
if not hasattr(bb, "features"):
|
| 477 |
raise RuntimeError("Unexpected EfficientNet structure: missing features")
|
|
@@ -488,6 +496,8 @@ def unfreeze_last_stage(
|
|
| 488 |
return
|
| 489 |
|
| 490 |
if bb_type in ("timm_densenet", "torchvision_densenet"):
|
|
|
|
|
|
|
| 491 |
bb = model.backbone
|
| 492 |
if not hasattr(bb, "features"):
|
| 493 |
raise RuntimeError("Unexpected DenseNet: missing features")
|
|
@@ -505,6 +515,8 @@ def unfreeze_last_stage(
|
|
| 505 |
raise RuntimeError(f"Unexpected DenseNet features: missing {name}")
|
| 506 |
|
| 507 |
def _denselayers(db: nn.Module) -> List[nn.Module]:
|
|
|
|
|
|
|
| 508 |
return list(db.children())
|
| 509 |
|
| 510 |
blocks: List[nn.Module] = []
|
|
@@ -526,20 +538,8 @@ def unfreeze_last_stage(
|
|
| 526 |
|
| 527 |
# -------------------------
|
| 528 |
# register
|
|
|
|
|
|
|
|
|
|
|
|
|
| 529 |
BackboneWithMLPHeadForImageClassification.register_for_auto_class("AutoModelForImageClassification")
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
# ==========================================================
|
| 533 |
-
|
| 534 |
-
# # fresh-start
|
| 535 |
-
# cfg = BackboneMLPHeadConfig(backbone_name_or_path="timm/densenet121.tv_in1k", num_labels=3)
|
| 536 |
-
# model = BackboneWithMLPHeadForImageClassification(cfg)
|
| 537 |
-
|
| 538 |
-
# # pretrained๋ฅผ ์ฐ๋ ค๋ฉด ๋ช
์์ ์ผ๋ก ํธ์ถ
|
| 539 |
-
# model.load_backbone_pretrained_()
|
| 540 |
-
|
| 541 |
-
# # ์ดํ ํ์ต
|
| 542 |
-
|
| 543 |
-
# # checkpoint์์ ์์
|
| 544 |
-
# model = BackboneWithMLPHeadForImageClassification.from_pretrained("checkpoints/xxx")
|
| 545 |
-
# # ์ฌ๊ธฐ์๋ load_backbone_pretrained_() ํธ์ถํ๋ฉด ์ ๋ฉ๋๋ค.
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
# -*- coding: utf-8 -*-
|
| 3 |
|
| 4 |
+
# src/ds_model.py
|
| 5 |
|
| 6 |
+
from typing import Optional, List
|
|
|
|
| 7 |
|
| 8 |
import torch
|
| 9 |
import torch.nn as nn
|
|
|
|
| 11 |
|
| 12 |
# --- transformers core ---
|
| 13 |
from transformers.modeling_utils import PreTrainedModel
|
| 14 |
+
from transformers import AutoModel, AutoConfig
|
| 15 |
from transformers.modeling_outputs import ImageClassifierOutput
|
| 16 |
|
| 17 |
# --- torchvision ---
|
| 18 |
from torchvision import models as tv_models
|
| 19 |
|
| 20 |
+
from ds_cfg import BackboneMLPHeadConfig
|
| 21 |
+
from ds_meta import BACKBONE_META
|
| 22 |
+
from mlp_head import MLPHead
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
# ============================================================
|
| 25 |
# (3) Model: backbone + MLP head
|
| 26 |
+
# (3) ๋ชจ๋ธ: backbone + MLP head
|
|
|
|
|
|
|
|
|
|
| 27 |
# ============================================================
|
| 28 |
+
# Design principle: __init__ builds only a skeleton and MUST NOT load pretrained weights.
|
| 29 |
+
# ์ค๊ณ ์์น: __init__์์๋ skeleton๋ง ๋ง๋ค๊ณ pretrained weight ๋ก๋๋ ์ ๋ ํ๋ฉด ์ ๋ฉ๋๋ค.
|
| 30 |
+
#
|
| 31 |
+
# Pretrained injection is allowed ONLY via an explicit call in fresh-start flows.
|
| 32 |
+
# pretrained ์ฃผ์
์ fresh-start ํ๋ฆ์์์ ๋ช
์์ ํธ์ถ๋ก๋ง ํ์ฉํฉ๋๋ค.
|
| 33 |
+
#
|
| 34 |
+
# HF from_pretrained should restore checkpoints as-is without side effects.
|
| 35 |
+
# HF from_pretrained๋ ๋ถ์์ฉ ์์ด ์ฒดํฌํฌ์ธํธ๋ฅผ ๊ทธ๋๋ก ๋ณต์ํด์ผ ํฉ๋๋ค.
|
| 36 |
class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
| 37 |
+
# This links the model to its custom config for AutoClass usage.
|
| 38 |
+
# AutoClass ์ฌ์ฉ์ ์ํด ๋ชจ๋ธ๊ณผ ์ปค์คํ
config๋ฅผ ์ฐ๊ฒฐํฉ๋๋ค.
|
| 39 |
config_class = BackboneMLPHeadConfig
|
| 40 |
|
| 41 |
def __init__(self, config: BackboneMLPHeadConfig):
|
| 42 |
+
# PreTrainedModel expects a config object and stores it internally.
|
| 43 |
+
# PreTrainedModel์ config ๊ฐ์ฒด๋ฅผ ๋ฐ์ ๋ด๋ถ์ ์ ์ฅํ๋ ์ ์ ๋ฅผ ๊ฐ์ง๋๋ค.
|
| 44 |
super().__init__(config)
|
| 45 |
|
| 46 |
+
# Fail-fast: the model is not meant to be instantiated without a valid backbone id.
|
| 47 |
+
# fail-fast: ์ ํจํ backbone id ์์ด ๋ชจ๋ธ์ ๋ง๋๋ ์ฌ์ฉ ์๋๋ฆฌ์ค๋ ํ์ฉํ์ง ์์ต๋๋ค.
|
| 48 |
+
#
|
| 49 |
+
# Note: Transformers may create configs with no args, but models are conventionally created with configs.
|
| 50 |
+
# ์ฐธ๊ณ : Transformers๋ config ๋ฌด์ธ์ ์์ฑ์ด ์์ ์ ์์ผ๋, ๋ชจ๋ธ์ ๊ด๋ก์ ์ผ๋ก config๋ฅผ ๋ฐ์ ์์ฑ๋ฉ๋๋ค.
|
| 51 |
if config.backbone_name_or_path is None:
|
| 52 |
+
raise ValueError(
|
| 53 |
+
"config.backbone_name_or_path is None. "
|
| 54 |
+
"Provide a valid backbone id (whitelist key in BACKBONE_META)."
|
| 55 |
+
)
|
| 56 |
|
| 57 |
+
# Fail-fast: training/inference requires a positive number of labels.
|
| 58 |
+
# fail-fast: ํ์ต/์ถ๋ก ์ num_labels๊ฐ ์์์ฌ์ผ ํฉ๋๋ค.
|
| 59 |
+
#
|
| 60 |
+
# Config may exist in a minimal form for internal serialization paths, but the model should not.
|
| 61 |
+
# config๋ ๋ด๋ถ ์ง๋ ฌํ ๊ฒฝ๋ก์์ ์ต์ ํํ๋ก ์กด์ฌํ ์ ์์ผ๋ ๋ชจ๋ธ์ ๊ทธ๋ฌ๋ฉด ์ ๋ฉ๋๋ค.
|
| 62 |
+
if int(getattr(config, "num_labels", 0)) <= 0:
|
| 63 |
+
raise ValueError(
|
| 64 |
+
f"config.num_labels must be > 0, got {getattr(config, 'num_labels', None)}. "
|
| 65 |
+
"Set num_labels (or id2label/label2id) when creating the config."
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
# Meta is a single source of truth for extraction and fine-tuning rules.
|
| 69 |
+
# meta๋ feature ์ถ์ถ ๋ฐ ๋ฏธ์ธ์กฐ์ ๊ท์น์ ๋จ์ผ ๊ธฐ์ค์
๋๋ค.
|
| 70 |
self._meta = BACKBONE_META[config.backbone_name_or_path]
|
| 71 |
|
| 72 |
+
# Backbone skeleton is always created without pretrained weights.
|
| 73 |
+
# backbone skeleton์ ํญ์ pretrained weight ์์ด ์์ฑํฉ๋๋ค.
|
| 74 |
self.backbone = self._build_backbone_skeleton(config.backbone_name_or_path)
|
| 75 |
|
| 76 |
+
# Head shape is driven by meta feat_dim and config.num_labels.
|
| 77 |
+
# head shape์ meta์ feat_dim๊ณผ config.num_labels๋ก ๊ฒฐ์ ๋ฉ๋๋ค.
|
| 78 |
self.classifier = MLPHead(
|
| 79 |
in_dim=int(self._meta["feat_dim"]),
|
| 80 |
+
num_labels=int(config.num_labels),
|
| 81 |
+
bottleneck=int(config.mlp_head_bottleneck),
|
| 82 |
+
p=float(config.mlp_head_dropout),
|
| 83 |
)
|
| 84 |
|
| 85 |
+
# HF initialization hook, but we override init_weights to initialize head-only.
|
| 86 |
+
# HF ์ด๊ธฐํ ํ
์ด์ง๋ง init_weights๋ฅผ overrideํ์ฌ head๋ง ์ด๊ธฐํํฉ๋๋ค.
|
| 87 |
self.post_init()
|
| 88 |
|
| 89 |
def init_weights(self):
|
| 90 |
"""
|
| 91 |
+
Initialize only the head to avoid touching the backbone skeleton.
|
| 92 |
+
backbone skeleton์ ๊ฑด๋๋ฆฌ์ง ์๊ธฐ ์ํด head๋ง ์ด๊ธฐํํฉ๋๋ค.
|
| 93 |
+
|
| 94 |
+
HF's default init may traverse the entire module tree, which is undesirable here.
|
| 95 |
+
HF ๊ธฐ๋ณธ init์ ์ ์ฒด ๋ชจ๋ ํธ๋ฆฌ๋ฅผ ์ํํ ์ ์์ด ์ฌ๊ธฐ์๋ ๋ถ์ ์ ํฉ๋๋ค.
|
| 96 |
"""
|
| 97 |
if getattr(self, "classifier", None) is not None:
|
| 98 |
self.classifier.apply(self._init_weights)
|
|
|
|
| 100 |
|
| 101 |
# ----------------------------
|
| 102 |
# backbone skeleton builders
|
| 103 |
+
# backbone skeleton ์์ฑ๊ธฐ
|
| 104 |
# ----------------------------
|
| 105 |
def _build_backbone_skeleton(self, backbone_id: str) -> nn.Module:
|
| 106 |
+
# Meta decides which loader path to use.
|
| 107 |
+
# meta๊ฐ ์ด๋ค ๋ก๋ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ ์ง ๊ฒฐ์ ํฉ๋๋ค.
|
| 108 |
meta = BACKBONE_META[backbone_id]
|
| 109 |
t = meta["type"]
|
| 110 |
|
|
|
|
| 114 |
if t == "torchvision_densenet":
|
| 115 |
return self._build_torchvision_densenet_skeleton(backbone_id)
|
| 116 |
|
| 117 |
+
# For transformers backbones: build a random-weight skeleton from config only.
|
| 118 |
+
# transformers ๋ฐฑ๋ณธ: config๋ก๋ถํฐ ๋๋ค ์ด๊ธฐํ skeleton๋ง ์์ฑํฉ๋๋ค.
|
| 119 |
bb_cfg = AutoConfig.from_pretrained(backbone_id)
|
| 120 |
return AutoModel.from_config(bb_cfg)
|
| 121 |
|
| 122 |
@staticmethod
|
| 123 |
def _build_timm_densenet_skeleton(hf_repo_id: str) -> nn.Module:
|
| 124 |
+
# timm is an optional dependency and should be imported lazily.
|
| 125 |
+
# timm์ ์ต์
์์กด์ฑ์ด๋ฏ๋ก ์ง์ฐ import ํฉ๋๋ค.
|
| 126 |
try:
|
| 127 |
import timm
|
| 128 |
except Exception as e:
|
| 129 |
+
raise ImportError(
|
| 130 |
+
"DenseNet(timm) backbone requires `timm`. Install: pip install timm"
|
| 131 |
+
) from e
|
| 132 |
|
| 133 |
+
# Build structure only (pretrained=False) and remove classifier head (num_classes=0).
|
| 134 |
+
# ๊ตฌ์กฐ๋ง ์์ฑ(pretrained=False)ํ๊ณ ๋ถ๋ฅ๊ธฐ head๋ ์ ๊ฑฐ(num_classes=0)ํฉ๋๋ค.
|
| 135 |
return timm.create_model(
|
| 136 |
f"hf_hub:{hf_repo_id}",
|
| 137 |
pretrained=False,
|
|
|
|
| 140 |
|
| 141 |
@staticmethod
|
| 142 |
def _build_torchvision_densenet_skeleton(model_id: str) -> nn.Module:
|
| 143 |
+
# This project intentionally supports only torchvision/densenet121 in the 224 whitelist.
|
| 144 |
+
# ์ด ํ๋ก์ ํธ๋ 224 ํ์ดํธ๋ฆฌ์คํธ์์ torchvision/densenet121๋ง ์๋์ ์ผ๋ก ์ง์ํฉ๋๋ค.
|
| 145 |
+
if model_id != "torchvision/densenet121":
|
| 146 |
+
raise ValueError(f"Unsupported torchvision DenseNet id (224 whitelist only): {model_id}")
|
| 147 |
+
|
| 148 |
+
# Build structure only (weights=None) to avoid implicit pretrained loading.
|
| 149 |
+
# implicit pretrained ๋ก๋๋ฅผ ํผํ๊ธฐ ์ํด ๊ตฌ์กฐ๋ง ์์ฑ(weights=None)ํฉ๋๋ค.
|
| 150 |
+
m = tv_models.densenet121(weights=None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
return m
|
| 152 |
|
| 153 |
# ------------------------------------------------------------
|
| 154 |
+
# Pretrained loading is explicit and fresh-start only
|
| 155 |
+
# pretrained ๋ก๋ฉ์ ๋ช
์์ ํธ์ถ์ด๋ฉฐ fresh-start ์ ์ฉ
|
| 156 |
# ------------------------------------------------------------
|
| 157 |
@torch.no_grad()
|
| 158 |
+
def load_backbone_pretrained_(
|
| 159 |
+
self,
|
| 160 |
+
*,
|
| 161 |
+
low_cpu_mem_usage: bool = False,
|
| 162 |
+
device_map=None,
|
| 163 |
+
):
|
| 164 |
"""
|
| 165 |
+
Fresh-start only: inject pretrained backbone weights into the skeleton.
|
| 166 |
+
fresh-start ์ ์ฉ: skeleton backbone์ pretrained ๊ฐ์ค์น๋ฅผ ์ฃผ์
ํฉ๋๋ค.
|
|
|
|
| 167 |
|
| 168 |
+
Do NOT call this after from_pretrained() because it would overwrite checkpoint weights.
|
| 169 |
+
from_pretrained() ์ดํ ํธ์ถํ๋ฉด ์ฒดํฌํฌ์ธํธ ๊ฐ์ค์น๋ฅผ ๋ฎ์ด์ฐ๋ฏ๋ก ์ ๋ ํธ์ถํ๋ฉด ์ ๋ฉ๋๋ค.
|
| 170 |
"""
|
| 171 |
bb = self.config.backbone_name_or_path
|
| 172 |
meta = BACKBONE_META[bb]
|
|
|
|
| 180 |
self._load_torchvision_pretrained_into_skeleton_(bb)
|
| 181 |
return
|
| 182 |
|
| 183 |
+
# For transformers backbones, load a reference pretrained model and copy weights into our skeleton.
|
| 184 |
+
# transformers ๋ฐฑ๋ณธ์ reference pretrained ๋ชจ๋ธ์ ๋ก๋ํ ๋ค skeleton์ ๊ฐ์ค์น๋ฅผ ๋ณต์ฌํฉ๋๋ค.
|
| 185 |
ref = AutoModel.from_pretrained(
|
| 186 |
bb,
|
| 187 |
low_cpu_mem_usage=low_cpu_mem_usage,
|
| 188 |
device_map=device_map,
|
| 189 |
)
|
| 190 |
+
|
| 191 |
+
# strict=False is used to tolerate harmless key differences across minor versions.
|
| 192 |
+
# strict=False๋ ๋ง์ด๋ ๋ฒ์ ์ฐจ์ด๋ก ์ธํ ๋ฌดํดํ ํค ์ฐจ์ด๋ฅผ ํ์ฉํ๊ธฐ ์ํด ์ฌ์ฉํฉ๋๋ค.
|
| 193 |
self.backbone.load_state_dict(ref.state_dict(), strict=False)
|
| 194 |
del ref
|
| 195 |
|
| 196 |
@torch.no_grad()
|
| 197 |
def _load_timm_pretrained_into_skeleton_(self, hf_repo_id: str):
|
| 198 |
+
# timm must be present for timm backbones.
|
| 199 |
+
# timm ๋ฐฑ๋ณธ์๋ timm ์ค์น๊ฐ ํ์ํฉ๋๋ค.
|
| 200 |
import timm
|
| 201 |
+
|
| 202 |
+
# Create a pretrained reference model and copy its weights strictly.
|
| 203 |
+
# pretrained reference ๋ชจ๋ธ์ ๋ง๋ค๊ณ ๊ฐ์ค์น๋ฅผ strictํ๊ฒ ๋ณต์ฌํฉ๋๋ค.
|
| 204 |
ref = timm.create_model(
|
| 205 |
f"hf_hub:{hf_repo_id}",
|
| 206 |
pretrained=True,
|
| 207 |
num_classes=0,
|
| 208 |
).eval()
|
| 209 |
+
|
| 210 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 211 |
del ref
|
| 212 |
|
| 213 |
@torch.no_grad()
|
| 214 |
def _load_torchvision_pretrained_into_skeleton_(self, model_id: str):
|
| 215 |
+
# This project intentionally supports only torchvision/densenet121 in the 224 whitelist.
|
| 216 |
+
# ์ด ํ๋ก์ ํธ๋ 224 ํ์ดํธ๋ฆฌ์คํธ์์ torchvision/densenet121๋ง ์๋์ ์ผ๋ก ์ง์ํฉ๋๋ค.
|
| 217 |
+
if model_id != "torchvision/densenet121":
|
| 218 |
+
raise ValueError(f"Unsupported torchvision DenseNet id (224 whitelist only): {model_id}")
|
| 219 |
+
|
| 220 |
+
# Use torchvision's default pretrained weights for densenet121.
|
| 221 |
+
# torchvision์ densenet121 ๊ธฐ๋ณธ pretrained weights๋ฅผ ์ฌ์ฉํฉ๋๋ค.
|
| 222 |
+
ref = tv_models.densenet121(weights=tv_models.DenseNet121_Weights.DEFAULT).eval()
|
| 223 |
+
|
|
|
|
|
|
|
|
|
|
| 224 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 225 |
del ref
|
| 226 |
|
| 227 |
# ----------------------------
|
| 228 |
# feature extraction
|
| 229 |
+
# feature ์ถ์ถ
|
| 230 |
# ----------------------------
|
| 231 |
@staticmethod
|
| 232 |
def _pool_or_gap(outputs) -> torch.Tensor:
|
| 233 |
+
# Some transformers vision CNNs provide pooler_output explicitly.
|
| 234 |
+
# ์ผ๋ถ transformers vision CNN์ pooler_output์ ๋ช
์์ ์ผ๋ก ์ ๊ณตํฉ๋๋ค.
|
| 235 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 236 |
x = outputs.pooler_output
|
| 237 |
if x.dim() == 2:
|
|
|
|
| 240 |
return x.flatten(1)
|
| 241 |
raise RuntimeError(f"Unexpected pooler_output shape: {tuple(x.shape)}")
|
| 242 |
|
| 243 |
+
# Otherwise we expect a CNN-style last_hidden_state=(B,C,H,W) and apply GAP.
|
| 244 |
+
# ๊ทธ๋ ์ง ์์ผ๋ฉด CNN ์คํ์ผ last_hidden_state=(B,C,H,W)๋ฅผ ๊ธฐ๋ํ๊ณ GAP์ ์ ์ฉํฉ๋๋ค.
|
| 245 |
x = outputs.last_hidden_state
|
| 246 |
if x.dim() == 4:
|
| 247 |
return x.mean(dim=(2, 3))
|
| 248 |
+
|
| 249 |
raise RuntimeError(
|
| 250 |
"Expected pooler_output or (B,C,H,W) last_hidden_state for CNN backbones. "
|
| 251 |
f"Got last_hidden_state shape={tuple(x.shape)}"
|
| 252 |
)
|
| 253 |
|
| 254 |
def _extract_features(self, outputs, pixel_values: Optional[torch.Tensor] = None) -> torch.Tensor:
|
| 255 |
+
# Feature rule is defined by BACKBONE_META and must remain stable across saves/loads.
|
| 256 |
+
# feature ๊ท์น์ BACKBONE_META๋ก ์ ์๋๋ฉฐ ์ ์ฅ/๋ก๋ ๊ฐ ์์ ์ ์ผ๋ก ์ ์ง๋์ด์ผ ํฉ๋๋ค.
|
| 257 |
rule = self._meta["feat_rule"]
|
| 258 |
|
| 259 |
if rule == "cls":
|
| 260 |
+
# ViT-style: use CLS token embedding from last_hidden_state.
|
| 261 |
+
# ViT ์คํ์ผ: last_hidden_state์์ CLS ํ ํฐ ์๋ฒ ๋ฉ์ ์ฌ์ฉํฉ๋๋ค.
|
| 262 |
return outputs.last_hidden_state[:, 0, :]
|
| 263 |
|
| 264 |
if rule == "pool_or_mean":
|
| 265 |
+
# Swin-style: prefer pooler_output if present, else mean-pool over tokens.
|
| 266 |
+
# Swin ์คํ์ผ: pooler_output์ด ์์ผ๋ฉด ์ฐ์ ์ฌ์ฉํ๊ณ , ์์ผ๋ฉด ํ ํฐ ํ๊ท ํ๋ง์ ์ฌ์ฉํฉ๋๋ค.
|
| 267 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 268 |
return outputs.pooler_output
|
| 269 |
return outputs.last_hidden_state.mean(dim=1)
|
| 270 |
|
| 271 |
if rule == "pool_or_gap":
|
| 272 |
+
# CNN-style: use pooler_output if present, else GAP over spatial dims.
|
| 273 |
+
# CNN ์คํ์ผ: pooler_output์ด ์์ผ๋ฉด ์ฌ์ฉํ๊ณ , ์์ผ๋ฉด ๊ณต๊ฐ ์ฐจ์ GAP์ ์ฌ์ฉํฉ๋๋ค.
|
| 274 |
return self._pool_or_gap(outputs)
|
| 275 |
|
| 276 |
if rule == "timm_gap":
|
| 277 |
+
# timm forward_features returns a feature map (B,C,H,W) which we GAP to (B,C).
|
| 278 |
+
# timm forward_features๋ (B,C,H,W) feature map์ ๋ฐํํ๋ฉฐ ์ด๋ฅผ GAP์ผ๋ก (B,C)๋ก ๋ง๋ญ๋๋ค.
|
| 279 |
if not isinstance(outputs, torch.Tensor):
|
| 280 |
raise TypeError(f"timm_gap expects Tensor features, got {type(outputs)}")
|
| 281 |
if outputs.dim() != 4:
|
|
|
|
| 283 |
return outputs.mean(dim=(2, 3))
|
| 284 |
|
| 285 |
if rule == "torchvision_densenet_gap":
|
| 286 |
+
# torchvision DenseNet features are feature maps (B,C,H,W) and require GAP.
|
| 287 |
+
# torchvision DenseNet features๋ (B,C,H,W) feature map์ด๋ฉฐ GAP์ด ํ์ํฉ๋๋ค.
|
| 288 |
if not isinstance(outputs, torch.Tensor):
|
| 289 |
raise TypeError(f"torchvision_densenet_gap expects Tensor, got {type(outputs)}")
|
| 290 |
if outputs.dim() != 4:
|
|
|
|
| 302 |
return_dict=True,
|
| 303 |
**kwargs,
|
| 304 |
):
|
| 305 |
+
# Type decides the backbone forward path and output format.
|
| 306 |
+
# type์ด backbone forward ๊ฒฝ๋ก ๋ฐ ์ถ๋ ฅ ํฌ๋งท์ ๊ฒฐ์ ํฉ๋๋ค.
|
| 307 |
t = self._meta["type"]
|
| 308 |
|
| 309 |
if t == "timm_densenet":
|
| 310 |
+
# timm DenseNet consumes pixel_values as a 4D tensor (B,C,H,W).
|
| 311 |
+
# timm DenseNet์ pixel_values๋ฅผ 4D ํ
์ (B,C,H,W)๋ก ๋ฐ์ต๋๋ค.
|
| 312 |
if pixel_values is None:
|
| 313 |
raise ValueError("timm DenseNet backbone requires pixel_values.")
|
| 314 |
if pixel_values.dim() != 4:
|
|
|
|
| 320 |
attentions = None
|
| 321 |
|
| 322 |
elif t == "torchvision_densenet":
|
| 323 |
+
# torchvision DenseNet consumes pixel_values as a 4D tensor (B,C,H,W).
|
| 324 |
+
# torchvision DenseNet์ pixel_values๋ฅผ 4D ํ
์ (B,C,H,W)๋ก ๋ฐ์ต๋๋ค.
|
| 325 |
if pixel_values is None:
|
| 326 |
raise ValueError("torchvision DenseNet backbone requires pixel_values.")
|
| 327 |
if pixel_values.dim() != 4:
|
|
|
|
| 334 |
attentions = None
|
| 335 |
|
| 336 |
else:
|
| 337 |
+
# Transformers vision models are called with pixel_values and return ModelOutput.
|
| 338 |
+
# transformers vision ๋ชจ๋ธ์ pixel_values๋ก ํธ์ถ๋๋ฉฐ ModelOutput์ ๋ฐํํฉ๋๋ค.
|
| 339 |
outputs = self.backbone(
|
| 340 |
pixel_values=pixel_values,
|
| 341 |
output_attentions=output_attentions,
|
|
|
|
| 347 |
hidden_states = getattr(outputs, "hidden_states", None)
|
| 348 |
attentions = getattr(outputs, "attentions", None)
|
| 349 |
|
| 350 |
+
# Classifier consumes (B, feat_dim) and returns logits (B, num_labels).
|
| 351 |
+
# classifier๋ (B, feat_dim)์ ๋ฐ์ logits (B, num_labels)๋ฅผ ๋ฐํํฉ๋๋ค.
|
| 352 |
logits = self.classifier(feats)
|
| 353 |
|
| 354 |
loss = None
|
| 355 |
if labels is not None:
|
| 356 |
+
# Cross entropy expects labels as class indices in [0, num_labels).
|
| 357 |
+
# cross entropy๋ labels๊ฐ [0, num_labels) ๋ฒ์์ class index์ด๊ธธ ๊ธฐ๋ํฉ๋๋ค.
|
| 358 |
loss = F.cross_entropy(logits, labels)
|
| 359 |
|
| 360 |
if not return_dict:
|
|
|
|
| 370 |
|
| 371 |
|
| 372 |
# ============================================================
|
| 373 |
+
# Freeze / Unfreeze utilities
|
| 374 |
+
# Freeze / Unfreeze ์ ํธ๋ฆฌํฐ
|
| 375 |
# ============================================================
|
| 376 |
def _set_requires_grad(module: nn.Module, flag: bool):
|
| 377 |
+
# Toggle requires_grad for all parameters in a module.
|
| 378 |
+
# ๋ชจ๋์ ๋ชจ๋ ํ๋ผ๋ฏธํฐ์ ๋ํด requires_grad๋ฅผ ํ ๊ธํฉ๋๋ค.
|
| 379 |
for p in module.parameters():
|
| 380 |
p.requires_grad = flag
|
| 381 |
|
| 382 |
|
| 383 |
def set_bn_eval(module: nn.Module):
|
| 384 |
+
# Put BatchNorm layers into eval mode to freeze running stats.
|
| 385 |
+
# BatchNorm ๋ ์ด์ด๋ฅผ eval ๋ชจ๋๋ก ๋์ด running stats๋ฅผ ๊ณ ์ ํฉ๋๋ค.
|
| 386 |
for m in module.modules():
|
| 387 |
if isinstance(m, (nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d, nn.SyncBatchNorm)):
|
| 388 |
m.eval()
|
| 389 |
|
| 390 |
|
| 391 |
def freeze_backbone(model: BackboneWithMLPHeadForImageClassification, freeze_bn: bool = True):
|
| 392 |
+
# Stage1: freeze backbone and train only the head.
|
| 393 |
+
# stage1: backbone์ freezeํ๊ณ head๋ง ํ์ตํฉ๋๋ค.
|
| 394 |
_set_requires_grad(model.backbone, False)
|
| 395 |
_set_requires_grad(model.classifier, True)
|
| 396 |
|
|
|
|
| 400 |
|
| 401 |
|
| 402 |
def finetune_train_mode(model: BackboneWithMLPHeadForImageClassification, keep_bn_eval: bool = True):
|
| 403 |
+
# Stage2: train mode, optionally keeping BN layers in eval for stability.
|
| 404 |
+
# stage2: train ๋ชจ๋๋ก ๋๋ ์์ ์ฑ์ ์ํด BN์ eval๋ก ์ ์งํ ์ ์์ต๋๋ค.
|
| 405 |
model.train()
|
| 406 |
meta = BACKBONE_META[model.config.backbone_name_or_path]
|
| 407 |
if keep_bn_eval and meta.get("has_bn", False):
|
|
|
|
| 409 |
|
| 410 |
|
| 411 |
def trainable_summary(model: nn.Module):
|
| 412 |
+
# Print a compact summary of trainable parameters.
|
| 413 |
+
# ํ์ต ๊ฐ๋ฅ ํ๋ผ๋ฏธํฐ ์์ฝ์ ๊ฐ๋จํ ์ถ๋ ฅํฉ๋๋ค.
|
| 414 |
total = sum(p.numel() for p in model.parameters())
|
| 415 |
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 416 |
ratio = trainable / total if total > 0 else 0.0
|
|
|
|
| 423 |
last_n: int = 2,
|
| 424 |
keep_bn_eval: bool = True,
|
| 425 |
):
|
| 426 |
+
# This utility implements BACKBONE_META['unfreeze']=="last_n" across supported backbones.
|
| 427 |
+
# ์ด ์ ํธ์ ์ง์ ๋ฐฑ๋ณธ๋ค์ ๋ํด BACKBONE_META['unfreeze']=="last_n"์ ๊ตฌํํฉ๋๋ค.
|
| 428 |
freeze_backbone(model, freeze_bn=keep_bn_eval)
|
| 429 |
|
| 430 |
n = int(last_n)
|
|
|
|
| 438 |
bb_type = meta["type"]
|
| 439 |
|
| 440 |
if bb_type == "vit":
|
| 441 |
+
# ViT blocks live under backbone.encoder.layer in the transformers implementation.
|
| 442 |
+
# ViT ๋ธ๋ก์ transformers ๊ตฌํ์์ backbone.encoder.layer ์๋์ ์์ต๋๋ค.
|
| 443 |
blocks = list(model.backbone.encoder.layer)
|
| 444 |
for blk in blocks[-n:]:
|
| 445 |
_set_requires_grad(blk, True)
|
| 446 |
return
|
| 447 |
|
| 448 |
if bb_type == "swin":
|
| 449 |
+
# Swin blocks are nested by stages and blocks; we flatten and unfreeze last n blocks.
|
| 450 |
+
# Swin ๋ธ๋ก์ stage์ block์ผ๋ก ์ค์ฒฉ๋์ด ์์ด ํผ์น ํ ๋ง์ง๋ง n๊ฐ๋ฅผ unfreeze ํฉ๋๋ค.
|
| 451 |
stages = list(model.backbone.encoder.layers)
|
| 452 |
blocks: List[nn.Module] = []
|
| 453 |
for st in stages:
|
|
|
|
| 457 |
return
|
| 458 |
|
| 459 |
if bb_type == "resnet":
|
| 460 |
+
# ResNet uses layer1..layer4 stages; we unfreeze at block granularity.
|
| 461 |
+
# ResNet์ layer1..layer4 stage๋ฅผ ์ฌ์ฉํ๋ฉฐ block ๋จ์๋ก unfreeze ํฉ๋๋ค.
|
| 462 |
bb = model.backbone
|
| 463 |
for name in ("layer1", "layer2", "layer3", "layer4"):
|
| 464 |
if not hasattr(bb, name):
|
|
|
|
| 478 |
return
|
| 479 |
|
| 480 |
if bb_type == "efficientnet":
|
| 481 |
+
# EfficientNet in transformers exposes features; we unfreeze from the tail blocks.
|
| 482 |
+
# transformers EfficientNet์ features๋ฅผ ๋
ธ์ถํ๋ฉฐ ๋ค์ชฝ ๋ธ๋ก๋ถํฐ unfreeze ํฉ๋๋ค.
|
| 483 |
bb = model.backbone
|
| 484 |
if not hasattr(bb, "features"):
|
| 485 |
raise RuntimeError("Unexpected EfficientNet structure: missing features")
|
|
|
|
| 496 |
return
|
| 497 |
|
| 498 |
if bb_type in ("timm_densenet", "torchvision_densenet"):
|
| 499 |
+
# DenseNet exposes a .features module with named blocks; we unfreeze last n submodules.
|
| 500 |
+
# DenseNet์ .features ๋ชจ๋์ ๋ธ๋ก๋ค์ด ์ด๋ฆ์ผ๋ก ์กด์ฌํ๋ฉฐ ๋ง์ง๋ง n๊ฐ ์๋ธ๋ชจ๋์ unfreeze ํฉ๋๋ค.
|
| 501 |
bb = model.backbone
|
| 502 |
if not hasattr(bb, "features"):
|
| 503 |
raise RuntimeError("Unexpected DenseNet: missing features")
|
|
|
|
| 515 |
raise RuntimeError(f"Unexpected DenseNet features: missing {name}")
|
| 516 |
|
| 517 |
def _denselayers(db: nn.Module) -> List[nn.Module]:
|
| 518 |
+
# Dense blocks contain multiple DenseLayer children; we return them for fine-grained unfreezing.
|
| 519 |
+
# denseblock์ DenseLayer ์์๋ค์ ๊ฐ์ง๋ฏ๋ก ์ธ๋ฐํ unfreeze๋ฅผ ์ํด ์ด๋ฅผ ๋ฐํํฉ๋๋ค.
|
| 520 |
return list(db.children())
|
| 521 |
|
| 522 |
blocks: List[nn.Module] = []
|
|
|
|
| 538 |
|
| 539 |
# -------------------------
|
| 540 |
# register
|
| 541 |
+
# register
|
| 542 |
+
# -------------------------
|
| 543 |
+
# Register for AutoModelForImageClassification so from_pretrained can resolve this custom class.
|
| 544 |
+
# from_pretrained๊ฐ ์ด ์ปค์คํ
ํด๋์ค๋ฅผ ํด์ํ ์ ์๋๋ก AutoModelForImageClassification์ ๋ฑ๋กํฉ๋๋ค.
|
| 545 |
BackboneWithMLPHeadForImageClassification.register_for_auto_class("AutoModelForImageClassification")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
models/google__vit-base-patch16-224/ds_proc.py
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# src/ds_proc.py
|
| 5 |
+
|
| 6 |
+
# ============================================================
|
| 7 |
+
# (4) ImageProcessor (AutoImageProcessor integration)
|
| 8 |
+
# (4) ImageProcessor (AutoImageProcessor ์ฐ๋)
|
| 9 |
+
# ============================================================
|
| 10 |
+
|
| 11 |
+
from typing import Any
|
| 12 |
+
|
| 13 |
+
import numpy as np
|
| 14 |
+
import torch
|
| 15 |
+
from transformers import AutoImageProcessor, AutoConfig
|
| 16 |
+
from transformers.image_processing_base import ImageProcessingMixin
|
| 17 |
+
from transformers.utils.generic import TensorType
|
| 18 |
+
|
| 19 |
+
from ds_meta import BackboneID, BACKBONE_META
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class BackboneMLPHead224ImageProcessor(ImageProcessingMixin):
|
| 23 |
+
"""
|
| 24 |
+
This processor performs image preprocessing and outputs {"pixel_values": ...}.
|
| 25 |
+
์ด processor๋ ์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ๋ฅผ ์ํํ๊ณ {"pixel_values": ...}๋ฅผ ์ถ๋ ฅํฉ๋๋ค.
|
| 26 |
+
|
| 27 |
+
Key requirements:
|
| 28 |
+
ํต์ฌ ์๊ตฌ์ฌํญ:
|
| 29 |
+
|
| 30 |
+
1) save_pretrained() must produce a JSON-serializable preprocessor_config.json.
|
| 31 |
+
save_pretrained()๋ JSON ์ง๋ ฌํ ๊ฐ๋ฅํ preprocessor_config.json์ ์์ฑํด์ผ ํฉ๋๋ค.
|
| 32 |
+
|
| 33 |
+
2) Runtime-only objects (delegate processor, timm/torchvision transforms) must NOT be serialized.
|
| 34 |
+
๋ฐํ์ ๊ฐ์ฒด(delegate processor, timm/torchvision transform)๋ ์ ๋ ์ง๋ ฌํํ๋ฉด ์ ๋ฉ๋๋ค.
|
| 35 |
+
|
| 36 |
+
3) Runtime objects are rebuilt at init/load time based on backbone meta.
|
| 37 |
+
๋ฐํ์ ๊ฐ์ฒด๋ backbone meta์ ๋ฐ๋ผ init/load ์์ ์ ์ฌ๊ตฌ์ฑ๋ฉ๋๋ค.
|
| 38 |
+
"""
|
| 39 |
+
|
| 40 |
+
# HF vision models conventionally expect "pixel_values" as the primary input key.
|
| 41 |
+
# HF vision ๋ชจ๋ธ์ ๊ด๋ก์ ์ผ๋ก ์
๋ ฅ ํค๋ก "pixel_values"๋ฅผ ๊ธฐ๋ํฉ๋๋ค.
|
| 42 |
+
model_input_names = ["pixel_values"]
|
| 43 |
+
|
| 44 |
+
def __init__(self, backbone_name_or_path: BackboneID, is_training: bool = False, **kwargs):
|
| 45 |
+
# ImageProcessingMixin stores extra kwargs and manages auto_map metadata.
|
| 46 |
+
# ImageProcessingMixin์ ์ถ๊ฐ kwargs๋ฅผ ์ ์ฅํ๊ณ auto_map ๋ฉํ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.
|
| 47 |
+
super().__init__(**kwargs)
|
| 48 |
+
|
| 49 |
+
# Enforce whitelist via BACKBONE_META to keep behavior stable.
|
| 50 |
+
# ๋์ ์์ ์ฑ์ ์ํด BACKBONE_META ๊ธฐ๋ฐ ํ์ดํธ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ ํฉ๋๋ค.
|
| 51 |
+
if backbone_name_or_path not in BACKBONE_META:
|
| 52 |
+
raise ValueError(
|
| 53 |
+
f"Unsupported backbone_name_or_path={backbone_name_or_path}. "
|
| 54 |
+
f"Allowed: {sorted(BACKBONE_META.keys())}"
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
# Serializable fields only: these should appear in preprocessor_config.json.
|
| 58 |
+
# ์ง๋ ฌํ ๊ฐ๋ฅํ ํ๋๋ง: ์ด ๊ฐ๋ค๋ง preprocessor_config.json์ ๋ค์ด๊ฐ์ผ ํฉ๋๋ค.
|
| 59 |
+
self.backbone_name_or_path = backbone_name_or_path
|
| 60 |
+
self.is_training = bool(is_training)
|
| 61 |
+
|
| 62 |
+
# Runtime-only fields: must never be serialized.
|
| 63 |
+
# ๋ฐํ์ ์ ์ฉ ํ๋: ์ ๋ ์ง๋ ฌํ๋๋ฉด ์ ๋ฉ๋๋ค.
|
| 64 |
+
self._meta = None
|
| 65 |
+
self._delegate = None
|
| 66 |
+
self._timm_transform = None
|
| 67 |
+
self._torchvision_transform = None
|
| 68 |
+
|
| 69 |
+
# Build runtime objects according to backbone type.
|
| 70 |
+
# backbone type์ ๋ฐ๋ผ ๋ฐํ์ ๊ฐ์ฒด๋ฅผ ๊ตฌ์ฑํฉ๋๋ค.
|
| 71 |
+
self._build_runtime()
|
| 72 |
+
|
| 73 |
+
# ============================================================
|
| 74 |
+
# Runtime builders
|
| 75 |
+
# ๋ฐํ์ ๋น๋
|
| 76 |
+
# ============================================================
|
| 77 |
+
def _build_runtime(self):
|
| 78 |
+
"""
|
| 79 |
+
Build runtime delegate/transform based on BACKBONE_META["type"].
|
| 80 |
+
BACKBONE_META["type"]์ ๋ฐ๋ผ ๋ฐํ์ delegate/transform์ ๊ตฌ์ฑํฉ๋๋ค.
|
| 81 |
+
"""
|
| 82 |
+
meta = BACKBONE_META[self.backbone_name_or_path]
|
| 83 |
+
self._meta = meta
|
| 84 |
+
|
| 85 |
+
# Always reset runtime fields before rebuilding.
|
| 86 |
+
# ์ฌ๊ตฌ์ฑ ์ ๋ฐํ์ ํ๋๋ ํญ์ ์ด๊ธฐํํฉ๋๋ค.
|
| 87 |
+
self._delegate = None
|
| 88 |
+
self._timm_transform = None
|
| 89 |
+
self._torchvision_transform = None
|
| 90 |
+
|
| 91 |
+
t = meta["type"]
|
| 92 |
+
|
| 93 |
+
if t == "timm_densenet":
|
| 94 |
+
# timm DenseNet uses timm.data transforms for ImageNet-style preprocessing.
|
| 95 |
+
# timm DenseNet์ ImageNet ์ ์ฒ๋ฆฌ๋ฅผ ์ํด timm.data transform์ ์ฌ์ฉํฉ๋๋ค.
|
| 96 |
+
self._timm_transform = self._build_timm_transform(
|
| 97 |
+
backbone_id=self.backbone_name_or_path,
|
| 98 |
+
is_training=self.is_training,
|
| 99 |
+
)
|
| 100 |
+
return
|
| 101 |
+
|
| 102 |
+
if t == "torchvision_densenet":
|
| 103 |
+
# torchvision DenseNet requires torchvision-style preprocessing (resize/crop/tensor/normalize).
|
| 104 |
+
# torchvision DenseNet์ torchvision ์คํ์ผ ์ ์ฒ๋ฆฌ(resize/crop/tensor/normalize)๊ฐ ํ์ํฉ๋๋ค.
|
| 105 |
+
self._torchvision_transform = self._build_torchvision_densenet_transform(
|
| 106 |
+
is_training=self.is_training
|
| 107 |
+
)
|
| 108 |
+
return
|
| 109 |
+
|
| 110 |
+
# Default: transformers backbone delegates to its official AutoImageProcessor.
|
| 111 |
+
# ๊ธฐ๋ณธ: transformers ๋ฐฑ๋ณธ์ ๊ณต์ AutoImageProcessor์ ์์ํฉ๋๋ค.
|
| 112 |
+
self._delegate = AutoImageProcessor.from_pretrained(self.backbone_name_or_path)
|
| 113 |
+
|
| 114 |
+
@staticmethod
|
| 115 |
+
def _build_timm_transform(*, backbone_id: str, is_training: bool):
|
| 116 |
+
"""
|
| 117 |
+
Create timm transform without storing non-serializable objects in config.
|
| 118 |
+
๋น์ง๋ ฌํ ๊ฐ์ฒด๋ฅผ config์ ์ ์ฅํ์ง ์๊ณ timm transform์ ์์ฑํฉ๋๋ค.
|
| 119 |
+
"""
|
| 120 |
+
try:
|
| 121 |
+
import timm
|
| 122 |
+
from timm.data import resolve_model_data_config, create_transform
|
| 123 |
+
except Exception as e:
|
| 124 |
+
raise ImportError(
|
| 125 |
+
"timm backbone processor requires `timm`. Install: pip install timm"
|
| 126 |
+
) from e
|
| 127 |
+
|
| 128 |
+
# We only need model metadata to resolve data config, so pretrained=False is preferred.
|
| 129 |
+
# data config ์ถ์ถ๋ง ํ์ํ๋ฏ๋ก pretrained=False๋ฅผ ์ฐ์ ์ฌ์ฉํฉ๋๋ค.
|
| 130 |
+
m = timm.create_model(f"hf_hub:{backbone_id}", pretrained=False, num_classes=0)
|
| 131 |
+
dc = resolve_model_data_config(m)
|
| 132 |
+
|
| 133 |
+
# create_transform returns a torchvision-like callable that maps PIL -> torch.Tensor(C,H,W).
|
| 134 |
+
# create_transform์ PIL -> torch.Tensor(C,H,W)๋ก ๋งคํํ๋ callable์ ๋ฐํํฉ๋๋ค.
|
| 135 |
+
tfm = create_transform(**dc, is_training=is_training)
|
| 136 |
+
return tfm
|
| 137 |
+
|
| 138 |
+
@staticmethod
|
| 139 |
+
def _build_torchvision_densenet_transform(*, is_training: bool):
|
| 140 |
+
"""
|
| 141 |
+
Build torchvision preprocessing for DenseNet-121 (224 pipeline).
|
| 142 |
+
DenseNet-121์ฉ torchvision ์ ์ฒ๋ฆฌ(224 ํ์ดํ๋ผ์ธ)๋ฅผ ๊ตฌ์ฑํฉ๋๋ค.
|
| 143 |
+
"""
|
| 144 |
+
try:
|
| 145 |
+
from torchvision import transforms
|
| 146 |
+
except Exception as e:
|
| 147 |
+
raise ImportError(
|
| 148 |
+
"torchvision DenseNet processor requires `torchvision`. Install: pip install torchvision"
|
| 149 |
+
) from e
|
| 150 |
+
|
| 151 |
+
# These are the standard ImageNet normalization stats used by torchvision weights.
|
| 152 |
+
# ์ด ๊ฐ๋ค์ torchvision weights๊ฐ ์ฌ์ฉํ๋ ํ์ค ImageNet ์ ๊ทํ ํต๊ณ์
๋๋ค.
|
| 153 |
+
mean = (0.485, 0.456, 0.406)
|
| 154 |
+
std = (0.229, 0.224, 0.225)
|
| 155 |
+
|
| 156 |
+
# Training pipeline typically uses RandomResizedCrop and horizontal flip.
|
| 157 |
+
# ํ์ต ํ์ดํ๋ผ์ธ์ ๋ณดํต RandomResizedCrop๊ณผ ์ข์ฐ๋ฐ์ ์ ์ฌ์ฉํฉ๋๋ค.
|
| 158 |
+
if is_training:
|
| 159 |
+
return transforms.Compose(
|
| 160 |
+
[
|
| 161 |
+
transforms.RandomResizedCrop(224),
|
| 162 |
+
transforms.RandomHorizontalFlip(p=0.5),
|
| 163 |
+
transforms.ToTensor(),
|
| 164 |
+
transforms.Normalize(mean=mean, std=std),
|
| 165 |
+
]
|
| 166 |
+
)
|
| 167 |
+
|
| 168 |
+
# Inference pipeline typically uses Resize(256) + CenterCrop(224).
|
| 169 |
+
# ์ถ๋ก ํ์ดํ๋ผ์ธ์ ๋ณดํต Resize(256) + CenterCrop(224)๋ฅผ ์ฌ์ฉํฉ๋๋ค.
|
| 170 |
+
return transforms.Compose(
|
| 171 |
+
[
|
| 172 |
+
transforms.Resize(256),
|
| 173 |
+
transforms.CenterCrop(224),
|
| 174 |
+
transforms.ToTensor(),
|
| 175 |
+
transforms.Normalize(mean=mean, std=std),
|
| 176 |
+
]
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
# ============================================================
|
| 180 |
+
# Serialization
|
| 181 |
+
# ์ง๋ ฌํ
|
| 182 |
+
# ============================================================
|
| 183 |
+
def to_dict(self) -> dict[str, Any]:
|
| 184 |
+
"""
|
| 185 |
+
Return a JSON-serializable dict for preprocessor_config.json.
|
| 186 |
+
preprocessor_config.json์ ๋ค์ด๊ฐ JSON ์ง๋ ฌํ dict๋ฅผ ๋ฐํํฉ๋๋ค.
|
| 187 |
+
|
| 188 |
+
Important: do not leak runtime objects into the serialized dict.
|
| 189 |
+
์ค์: ๋ฐํ์ ๊ฐ์ฒด๊ฐ ์ง๋ ฌํ dict์ ์์ด๋ฉด ์ ๋ฉ๋๋ค.
|
| 190 |
+
"""
|
| 191 |
+
# ImageProcessingMixin.to_dict() adds metadata such as image_processor_type/auto_map.
|
| 192 |
+
# ImageProcessingMixin.to_dict()๋ image_processor_type/auto_map ๊ฐ์ ๋ฉํ๋ฅผ ์ถ๊ฐํฉ๋๋ค.
|
| 193 |
+
d = super().to_dict()
|
| 194 |
+
|
| 195 |
+
# Force minimal stable fields for long-term compatibility.
|
| 196 |
+
# ์ฅ๊ธฐ ํธํ์ ์ํด ์ต์ ์์ ํ๋๋ฅผ ๊ฐ์ ํฉ๋๋ค.
|
| 197 |
+
d["image_processor_type"] = self.__class__.__name__
|
| 198 |
+
d["backbone_name_or_path"] = self.backbone_name_or_path
|
| 199 |
+
d["is_training"] = self.is_training
|
| 200 |
+
|
| 201 |
+
# Remove any runtime-only fields defensively.
|
| 202 |
+
# ๋ฐํ์ ์ ์ฉ ํ๋๋ ๋ณด์์ ์ผ๋ก ์ ๊ฑฐํฉ๋๋ค.
|
| 203 |
+
for key in ["_meta", "_delegate", "_timm_transform", "_torchvision_transform"]:
|
| 204 |
+
d.pop(key, None)
|
| 205 |
+
|
| 206 |
+
return d
|
| 207 |
+
|
| 208 |
+
@classmethod
|
| 209 |
+
def from_dict(cls, image_processor_dict: dict[str, Any], **kwargs):
|
| 210 |
+
"""
|
| 211 |
+
Standard load path used by BaseImageProcessor / AutoImageProcessor.
|
| 212 |
+
BaseImageProcessor / AutoImageProcessor๊ฐ ์ฌ์ฉํ๋ ํ์ค ๋ก๋ ๊ฒฝ๋ก์
๋๋ค.
|
| 213 |
+
"""
|
| 214 |
+
backbone = image_processor_dict.get("backbone_name_or_path", None)
|
| 215 |
+
if backbone is None:
|
| 216 |
+
raise ValueError("preprocessor_config.json missing key: backbone_name_or_path")
|
| 217 |
+
|
| 218 |
+
is_training = bool(image_processor_dict.get("is_training", False))
|
| 219 |
+
return cls(backbone_name_or_path=backbone, is_training=is_training, **kwargs)
|
| 220 |
+
|
| 221 |
+
@classmethod
|
| 222 |
+
def from_pretrained(cls, pretrained_model_name_or_path: str, **kwargs):
|
| 223 |
+
"""
|
| 224 |
+
Fallback path if AutoImageProcessor calls class.from_pretrained directly.
|
| 225 |
+
AutoImageProcessor๊ฐ class.from_pretrained๋ฅผ ์ง์ ํธ์ถํ๋ ๊ฒฝ์ฐ๋ฅผ ๋๋นํ ๊ฒฝ๋ก์
๋๋ค.
|
| 226 |
+
|
| 227 |
+
Strategy:
|
| 228 |
+
์ ๋ต:
|
| 229 |
+
|
| 230 |
+
- Read config.json via AutoConfig and recover backbone_name_or_path.
|
| 231 |
+
AutoConfig๋ก config.json์ ์ฝ๊ณ backbone_name_or_path๋ฅผ ๋ณต๊ตฌํฉ๋๋ค.
|
| 232 |
+
"""
|
| 233 |
+
cfg = AutoConfig.from_pretrained(pretrained_model_name_or_path, **kwargs)
|
| 234 |
+
backbone = getattr(cfg, "backbone_name_or_path", None)
|
| 235 |
+
if backbone is None:
|
| 236 |
+
raise ValueError("Cannot build processor: backbone_name_or_path not found in config.json")
|
| 237 |
+
|
| 238 |
+
# is_training is runtime-only and should default to False for inference/serving.
|
| 239 |
+
# is_training์ ๋ฐํ์ ์ ์ฉ์ด๋ฉฐ ์ถ๋ก /์๋น ๊ธฐ๋ณธ๊ฐ์ False๊ฐ ๋ง์ต๋๋ค.
|
| 240 |
+
return cls(backbone_name_or_path=backbone, is_training=False)
|
| 241 |
+
|
| 242 |
+
# ============================================================
|
| 243 |
+
# Call interface
|
| 244 |
+
# ํธ์ถ ์ธํฐํ์ด์ค
|
| 245 |
+
# ============================================================
|
| 246 |
+
@staticmethod
|
| 247 |
+
def _ensure_list(images: Any) -> list[Any]:
|
| 248 |
+
# Normalize scalar image input to a list for uniform processing.
|
| 249 |
+
# ๋จ์ผ ์
๋ ฅ์ ๋ฆฌ์คํธ๋ก ์ ๊ทํํ์ฌ ๋์ผํ ์ฒ๋ฆฌ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํฉ๋๋ค.
|
| 250 |
+
if isinstance(images, (list, tuple)):
|
| 251 |
+
return list(images)
|
| 252 |
+
return [images]
|
| 253 |
+
|
| 254 |
+
@staticmethod
|
| 255 |
+
def _to_pil_rgb(x: Any):
|
| 256 |
+
# Convert common image inputs into PIL RGB images.
|
| 257 |
+
# ์ผ๋ฐ์ ์ธ ์
๋ ฅ์ PIL RGB ์ด๋ฏธ์ง๋ก ๋ณํํฉ๋๋ค.
|
| 258 |
+
from PIL import Image as PILImage
|
| 259 |
+
|
| 260 |
+
if isinstance(x, PILImage.Image):
|
| 261 |
+
return x.convert("RGB")
|
| 262 |
+
if isinstance(x, np.ndarray) and x.ndim == 3:
|
| 263 |
+
return PILImage.fromarray(x).convert("RGB")
|
| 264 |
+
raise TypeError(f"Unsupported image type: {type(x)}")
|
| 265 |
+
|
| 266 |
+
def __call__(
|
| 267 |
+
self,
|
| 268 |
+
images: Any | list[Any],
|
| 269 |
+
return_tensors: str | TensorType | None = "pt",
|
| 270 |
+
**kwargs,
|
| 271 |
+
) -> dict[str, Any]:
|
| 272 |
+
"""
|
| 273 |
+
Convert images into {"pixel_values": Tensor/ndarray}.
|
| 274 |
+
์ด๋ฏธ์ง๋ฅผ {"pixel_values": Tensor/ndarray}๋ก ๋ณํํฉ๋๋ค.
|
| 275 |
+
"""
|
| 276 |
+
images = self._ensure_list(images)
|
| 277 |
+
|
| 278 |
+
# Rebuild runtime if needed (e.g., right after deserialization).
|
| 279 |
+
# ์ง๋ ฌํ ๋ณต์ ์งํ ๋ฑ ๋ฐํ์์ด ๋น์ด์์ ์ ์์ผ๋ฏ๋ก ์ฌ๊ตฌ์ฑํฉ๋๋ค.
|
| 280 |
+
if (self._delegate is None) and (self._timm_transform is None) and (self._torchvision_transform is None):
|
| 281 |
+
self._build_runtime()
|
| 282 |
+
|
| 283 |
+
# timm path: PIL -> torch.Tensor(C,H,W) normalized float32.
|
| 284 |
+
# timm ๊ฒฝ๋ก: PIL -> torch.Tensor(C,H,W) ์ ๊ทํ float32์
๋๋ค.
|
| 285 |
+
if self._timm_transform is not None:
|
| 286 |
+
pv: list[torch.Tensor] = []
|
| 287 |
+
for im in images:
|
| 288 |
+
pil = self._to_pil_rgb(im)
|
| 289 |
+
t = self._timm_transform(pil)
|
| 290 |
+
if not isinstance(t, torch.Tensor):
|
| 291 |
+
raise RuntimeError("Unexpected timm transform output (expected torch.Tensor).")
|
| 292 |
+
pv.append(t)
|
| 293 |
+
pixel_values = torch.stack(pv, dim=0) # (B,C,H,W)
|
| 294 |
+
return self._format_return(pixel_values, return_tensors)
|
| 295 |
+
|
| 296 |
+
# torchvision path: PIL -> torch.Tensor(C,H,W) normalized float32.
|
| 297 |
+
# torchvision ๊ฒฝ๋ก: PIL -> torch.Tensor(C,H,W) ์ ๊ทํ float32์
๋๋ค.
|
| 298 |
+
if self._torchvision_transform is not None:
|
| 299 |
+
pv: list[torch.Tensor] = []
|
| 300 |
+
for im in images:
|
| 301 |
+
pil = self._to_pil_rgb(im)
|
| 302 |
+
t = self._torchvision_transform(pil)
|
| 303 |
+
if not isinstance(t, torch.Tensor):
|
| 304 |
+
raise RuntimeError("Unexpected torchvision transform output (expected torch.Tensor).")
|
| 305 |
+
pv.append(t)
|
| 306 |
+
pixel_values = torch.stack(pv, dim=0) # (B,C,H,W)
|
| 307 |
+
return self._format_return(pixel_values, return_tensors)
|
| 308 |
+
|
| 309 |
+
# transformers delegate path: rely on official processor behavior.
|
| 310 |
+
# transformers ์์ ๊ฒฝ๋ก: ๊ณต์ processor ๋์์ ๊ทธ๋๋ก ์ฌ์ฉํฉ๋๋ค.
|
| 311 |
+
if self._delegate is None:
|
| 312 |
+
raise RuntimeError("Processor runtime not built: delegate is None and no transforms are available.")
|
| 313 |
+
|
| 314 |
+
return self._delegate(images, return_tensors=return_tensors, **kwargs)
|
| 315 |
+
|
| 316 |
+
@staticmethod
|
| 317 |
+
def _format_return(pixel_values: torch.Tensor, return_tensors: str | TensorType | None) -> dict[str, Any]:
|
| 318 |
+
"""
|
| 319 |
+
Format pixel_values according to return_tensors.
|
| 320 |
+
return_tensors์ ๋ง์ถฐ pixel_values ๋ฐํ ํฌ๋งท์ ๋ง์ถฅ๋๋ค.
|
| 321 |
+
"""
|
| 322 |
+
if return_tensors is None or return_tensors in ("pt", TensorType.PYTORCH):
|
| 323 |
+
return {"pixel_values": pixel_values}
|
| 324 |
+
if return_tensors in ("np", TensorType.NUMPY):
|
| 325 |
+
return {"pixel_values": pixel_values.detach().cpu().numpy()}
|
| 326 |
+
raise ValueError(f"Unsupported return_tensors={return_tensors}. Use 'pt' or 'np'.")
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
# Register this processor for AutoImageProcessor resolution.
|
| 330 |
+
# AutoImageProcessor ํด์์ ์ํด ์ด processor๋ฅผ ๋ฑ๋กํฉ๋๋ค.
|
| 331 |
+
if __name__ != "__main__":
|
| 332 |
+
BackboneMLPHead224ImageProcessor.register_for_auto_class("AutoImageProcessor")
|
models/google__vit-base-patch16-224/model.safetensors
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 346372132
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5ca403809fcb589b9b176f4712c6f6aa3c31d2cb7465aaaa6cd4391d46947e8e
|
| 3 |
size 346372132
|
models/google__vit-base-patch16-224/preprocessor_config.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"auto_map": {
|
| 3 |
+
"AutoImageProcessor": "ds_proc.BackboneMLPHead224ImageProcessor"
|
| 4 |
+
},
|
| 5 |
+
"backbone_name_or_path": "google/vit-base-patch16-224",
|
| 6 |
+
"image_processor_type": "BackboneMLPHead224ImageProcessor",
|
| 7 |
+
"is_training": false
|
| 8 |
+
}
|
models/microsoft__resnet-50/config.json
CHANGED
|
@@ -24,10 +24,11 @@
|
|
| 24 |
"num_labels": 3,
|
| 25 |
"transformers_version": "5.1.0",
|
| 26 |
"ds_provenance": {
|
| 27 |
-
"created_at": "
|
| 28 |
"repo_id": "dsaint31/bb_mlp_224",
|
| 29 |
"subdir": "models/microsoft__resnet-50",
|
| 30 |
"wrapper_class": "BackboneWithMLPHeadForImageClassification",
|
|
|
|
| 31 |
"backbone_id": "microsoft/resnet-50",
|
| 32 |
"pretrained_injected": true,
|
| 33 |
"policy_init_pretrained_forbidden": true,
|
|
|
|
| 24 |
"num_labels": 3,
|
| 25 |
"transformers_version": "5.1.0",
|
| 26 |
"ds_provenance": {
|
| 27 |
+
"created_at": "20260210_111913",
|
| 28 |
"repo_id": "dsaint31/bb_mlp_224",
|
| 29 |
"subdir": "models/microsoft__resnet-50",
|
| 30 |
"wrapper_class": "BackboneWithMLPHeadForImageClassification",
|
| 31 |
+
"processor_class": "BackboneMLPHead224ImageProcessor",
|
| 32 |
"backbone_id": "microsoft/resnet-50",
|
| 33 |
"pretrained_injected": true,
|
| 34 |
"policy_init_pretrained_forbidden": true,
|
models/microsoft__resnet-50/ds_cfg.py
CHANGED
|
@@ -1,84 +1,175 @@
|
|
| 1 |
-
from typing import Literal, Union
|
| 2 |
-
|
| 3 |
-
from transformers import AutoConfig
|
| 4 |
from transformers import PretrainedConfig
|
|
|
|
| 5 |
from ds_meta import BackboneID, BACKBONE_META
|
| 6 |
|
| 7 |
-
|
| 8 |
-
# 3) Config
|
| 9 |
-
# ============================================================
|
| 10 |
class BackboneMLPHeadConfig(PretrainedConfig):
|
| 11 |
"""
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
----------
|
| 16 |
-
- transformers ๋ด๋ถ ๊ฒฝ๋ก์์ "๋ฌด์ธ์ ์์ฑ"์ด ๋ฐ์ํ ์ ์์ผ๋ฏ๋ก,
|
| 17 |
-
backbone_name_or_path=None์ธ ๊ฒฝ์ฐ๋ ๊ฒ์ฆ ์์ด ๊ธฐ๋ณธ๊ฐ๋ง ์ธํ
ํ๊ณ return
|
| 18 |
-
ํ๋ ๊ตฌํ์ด ๋ฐ๋์ ํ์ํจ.
|
| 19 |
-
- ๋จ, ์ค์ ์ฌ์ฉ์์๋ backbone_name_or_path๋ฅผ ๋ฐ๋์ ์ง์ ํด์ ์์ฑํด์ผ ํจ.
|
| 20 |
"""
|
| 21 |
|
| 22 |
-
#
|
|
|
|
| 23 |
model_type = "backbone-mlphead-224-fixed"
|
| 24 |
|
| 25 |
-
# ์์ฑ์์์ ๋ชจ๋ ์ธ์์ ๊ธฐ๋ณธ๊ฐ ๋ถ์ฌ ๋ฐ๋์ ํ์!
|
| 26 |
def __init__(
|
| 27 |
self,
|
| 28 |
backbone_name_or_path: BackboneID | None = None,
|
| 29 |
mlp_head_bottleneck: int = 256,
|
| 30 |
mlp_head_dropout: float = 0.2,
|
| 31 |
-
label2id: dict[str, int]|None = None,
|
| 32 |
-
id2label: dict[int, str]|None = None,
|
| 33 |
**kwargs,
|
| 34 |
):
|
| 35 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
if backbone_name_or_path is None:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
super().__init__(**kwargs)
|
|
|
|
|
|
|
|
|
|
| 38 |
self.backbone_name_or_path = None
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
#
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
return
|
| 44 |
|
| 45 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
if backbone_name_or_path not in BACKBONE_META:
|
| 47 |
raise ValueError(
|
| 48 |
f"Unsupported backbone_name_or_path={backbone_name_or_path}. "
|
| 49 |
f"Allowed: {sorted(BACKBONE_META.keys())}"
|
| 50 |
)
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
#
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
kwargs["num_labels"] = num_labels
|
| 62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
super().__init__(
|
| 64 |
-
label2id=
|
| 65 |
-
id2label=
|
| 66 |
**kwargs,
|
| 67 |
)
|
| 68 |
-
|
| 69 |
-
# 4) ์ค์: ๋ถ๋ชจ ํ๋์๋ num_labels ๋ฐ์(์ ์ฅ/๋ก๋ ์์ )
|
| 70 |
-
self.num_labels = num_labels
|
| 71 |
-
self.label2id = label2id
|
| 72 |
-
self.id2label = id2label
|
| 73 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
self.backbone_name_or_path = backbone_name_or_path
|
| 75 |
-
self.mlp_head_bottleneck
|
| 76 |
-
self.mlp_head_dropout
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
def to_dict(self):
|
|
|
|
|
|
|
| 79 |
output = super().to_dict()
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
return output
|
| 83 |
|
|
|
|
|
|
|
|
|
|
| 84 |
BackboneMLPHeadConfig.register_for_auto_class("AutoConfig")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from transformers import PretrainedConfig
|
| 2 |
+
|
| 3 |
from ds_meta import BackboneID, BACKBONE_META
|
| 4 |
|
| 5 |
+
|
|
|
|
|
|
|
| 6 |
class BackboneMLPHeadConfig(PretrainedConfig):
|
| 7 |
"""
|
| 8 |
+
Configuration for Backbone + MLP Head models.
|
| 9 |
+
|
| 10 |
+
Backbone + MLP Head ๋ชจ๋ธ์ ์ํ ์ค์ ํด๋์ค์
๋๋ค.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
"""
|
| 12 |
|
| 13 |
+
# This string is used by Hugging Face AutoConfig to identify the config class.
|
| 14 |
+
# Hugging Face AutoConfig๊ฐ ์ด config๋ฅผ ์๋ณํ๊ธฐ ์ํด ์ฌ์ฉํ๋ ๊ณ ์ ID์
๋๋ค.
|
| 15 |
model_type = "backbone-mlphead-224-fixed"
|
| 16 |
|
|
|
|
| 17 |
def __init__(
|
| 18 |
self,
|
| 19 |
backbone_name_or_path: BackboneID | None = None,
|
| 20 |
mlp_head_bottleneck: int = 256,
|
| 21 |
mlp_head_dropout: float = 0.2,
|
| 22 |
+
label2id: dict[str, int] | None = None,
|
| 23 |
+
id2label: dict[int, str] | None = None,
|
| 24 |
**kwargs,
|
| 25 |
):
|
| 26 |
+
# ============================================================
|
| 27 |
+
# 0) Guard for argument-less construction
|
| 28 |
+
# 0) ๋ฌด์ธ์ ์์ฑ ๊ฒฝ๋ก ๋ฐฉ์ด
|
| 29 |
+
# ============================================================
|
| 30 |
+
# Transformers may internally construct this config without arguments
|
| 31 |
+
# (e.g., during AutoConfig resolution or Hub loading).
|
| 32 |
+
# Transformers ๋ด๋ถ์์ AutoConfig ๋๋ Hub ๋ก๋ ๊ณผ์ ์ค
|
| 33 |
+
# ์ธ์ ์์ด config๋ฅผ ์์ฑํ๋ ๊ฒฝ๋ก๊ฐ ์ค์ ๋ก ์กด์ฌํฉ๋๋ค.
|
| 34 |
+
#
|
| 35 |
+
# In this case, we must NOT validate or raise errors.
|
| 36 |
+
# ์ด ๊ฒฝ์ฐ ๊ฒ์ฆ์ด๋ ์์ธ๋ฅผ ๋ฐ์์ํค๋ฉด ์ ๋ฉ๋๋ค.
|
| 37 |
+
#
|
| 38 |
+
# The goal here is to provide a minimal, serialization-safe config.
|
| 39 |
+
# ๋ชฉํ๋ ์ต์ํ์ ๊ฐ๋ง ์ฑ์ ์ง๋ ฌํ/์ญ์ง๋ ฌํ๊ฐ ๊นจ์ง์ง ์๊ฒ ํ๋ ๊ฒ์
๋๋ค.
|
| 40 |
if backbone_name_or_path is None:
|
| 41 |
+
# num_labels may be implicitly assumed by downstream code,
|
| 42 |
+
# so we explicitly set a safe default.
|
| 43 |
+
# num_labels๋ downstream ์ฝ๋์์ ์๋ฌต์ ์ผ๋ก ๊ฐ์ ๋๋ฏ๋ก
|
| 44 |
+
# ์์ ํ ๊ธฐ๋ณธ๊ฐ์ ๋ช
์์ ์ผ๋ก ๋ฃ์ด์ค๋๋ค.
|
| 45 |
+
if "num_labels" not in kwargs:
|
| 46 |
+
kwargs["num_labels"] = 0
|
| 47 |
+
|
| 48 |
super().__init__(**kwargs)
|
| 49 |
+
|
| 50 |
+
# Backbone is intentionally unset in this path.
|
| 51 |
+
# ์ด ๊ฒฝ๋ก์์๋ backbone์ ์๋์ ์ผ๋ก ์ค์ ํ์ง ์์ต๋๋ค.
|
| 52 |
self.backbone_name_or_path = None
|
| 53 |
+
|
| 54 |
+
# Store MLP head hyperparameters for completeness.
|
| 55 |
+
# MLP head ๊ด๋ จ ํ์ดํผํ๋ผ๋ฏธํฐ๋ ํํ ์ ์ง๋ฅผ ์ํด ์ ์ฅํฉ๋๋ค.
|
| 56 |
+
self.mlp_head_bottleneck = int(mlp_head_bottleneck)
|
| 57 |
+
self.mlp_head_dropout = float(mlp_head_dropout)
|
| 58 |
+
|
| 59 |
+
# Empty label mappings ensure stable save/load behavior.
|
| 60 |
+
# ๋น label ๋งคํ์ ์ ์ฅ/๋ก๋ ์์ ์ฑ์ ๋ณด์ฅํฉ๋๋ค.
|
| 61 |
+
self.label2id = {}
|
| 62 |
+
self.id2label = {}
|
| 63 |
+
self.num_labels = int(kwargs.get("num_labels", 0))
|
| 64 |
return
|
| 65 |
|
| 66 |
+
# ============================================================
|
| 67 |
+
# 1) Backbone whitelist validation
|
| 68 |
+
# 1) Backbone ํ์ดํธ๋ฆฌ์คํธ ๊ฒ์ฆ
|
| 69 |
+
# ============================================================
|
| 70 |
+
# Only backbones explicitly registered in BACKBONE_META are allowed.
|
| 71 |
+
# BACKBONE_META์ ๋ฑ๋ก๋ backbone๋ง ํ์ฉํฉ๋๋ค.
|
| 72 |
+
#
|
| 73 |
+
# This prevents accidental usage of unsupported or inconsistent backbones.
|
| 74 |
+
# ์ง์๋์ง ์๊ฑฐ๋ ๋ถ์ผ์นํ backbone ์ฌ์ฉ์ ์์ฒ ์ฐจ๋จํฉ๋๋ค.
|
| 75 |
if backbone_name_or_path not in BACKBONE_META:
|
| 76 |
raise ValueError(
|
| 77 |
f"Unsupported backbone_name_or_path={backbone_name_or_path}. "
|
| 78 |
f"Allowed: {sorted(BACKBONE_META.keys())}"
|
| 79 |
)
|
| 80 |
+
|
| 81 |
+
# ============================================================
|
| 82 |
+
# 2) Label mapping normalization
|
| 83 |
+
# 2) ๋ผ๋ฒจ ๋งคํ ์ ๊ทํ
|
| 84 |
+
# ============================================================
|
| 85 |
+
# Both label2id and id2label may be None during pure loading scenarios.
|
| 86 |
+
# ๋จ์ ๋ก๋(from_pretrained) ์ label2id/id2label์ด None์ผ ์ ์์ต๋๋ค.
|
| 87 |
+
#
|
| 88 |
+
# We allow this here to keep Hub loading and verification stable.
|
| 89 |
+
# Hub ๋ก๋ ๋ฐ ๊ฒ์ฆ ์์ ์ฑ์ ์ํด ์ฌ๊ธฐ์๋ ์ด๋ฅผ ํ์ฉํฉ๋๋ค.
|
| 90 |
+
#
|
| 91 |
+
# Fail-fast validation should happen at model or training level instead.
|
| 92 |
+
# ์ค์ ์ฌ์ฉ ๊ฒ์ฆ์ ๋ชจ๋ธ ๋๋ ํ์ต ๋จ๊ณ์์ fail-fast๋ก ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค.
|
| 93 |
+
if label2id is None and id2label is None:
|
| 94 |
+
# Respect num_labels if explicitly provided, otherwise default to 0.
|
| 95 |
+
# num_labels๊ฐ ์ฃผ์ด์ก๋ค๋ฉด ์กด์คํ๊ณ , ์๋๋ฉด 0์ผ๋ก ๋ก๋๋ค.
|
| 96 |
+
num_labels = int(kwargs.get("num_labels", 0))
|
| 97 |
+
label2id_norm: dict[str, int] = {}
|
| 98 |
+
id2label_norm: dict[int, str] = {}
|
| 99 |
+
else:
|
| 100 |
+
# If only one mapping is provided, derive the other.
|
| 101 |
+
# ํ๋๋ง ์ฃผ์ด์ง ๊ฒฝ์ฐ ๋๋จธ์ง๋ฅผ ์๋์ผ๋ก ์์ฑํฉ๋๋ค.
|
| 102 |
+
if id2label is None:
|
| 103 |
+
id2label = {v: k for k, v in label2id.items()}
|
| 104 |
+
if label2id is None:
|
| 105 |
+
label2id = {v: k for k, v in id2label.items()}
|
| 106 |
+
|
| 107 |
+
# Ensure both mappings are consistent in size.
|
| 108 |
+
# ๋ ๋งคํ์ ํฌ๊ธฐ๊ฐ ์ผ์นํ๋์ง ํ์ธํฉ๋๋ค.
|
| 109 |
+
if len(label2id) != len(id2label):
|
| 110 |
+
raise ValueError(
|
| 111 |
+
f"label2id/id2label size mismatch: "
|
| 112 |
+
f"{len(label2id)} vs {len(id2label)}"
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
num_labels = len(id2label)
|
| 116 |
+
label2id_norm = dict(label2id)
|
| 117 |
+
id2label_norm = dict(id2label)
|
| 118 |
+
|
| 119 |
+
# ============================================================
|
| 120 |
+
# 3) num_labels consistency enforcement
|
| 121 |
+
# 3) num_labels ์ผ๊ด์ฑ ๊ฐ์
|
| 122 |
+
# ============================================================
|
| 123 |
+
# If num_labels is provided via kwargs, it must match inferred labels.
|
| 124 |
+
# kwargs๋ก num_labels๊ฐ ๋ค์ด์จ ๊ฒฝ์ฐ ์ถ๋ก ๋ ๊ฐ๊ณผ ๋ฐ๋์ ์ผ์นํด์ผ ํฉ๋๋ค.
|
| 125 |
+
if "num_labels" in kwargs:
|
| 126 |
+
if (label2id is not None or id2label is not None) and int(kwargs["num_labels"]) != num_labels:
|
| 127 |
+
raise ValueError(
|
| 128 |
+
f"kwargs['num_labels']={kwargs['num_labels']} "
|
| 129 |
+
f"!= inferred num_labels={num_labels}"
|
| 130 |
+
)
|
| 131 |
+
else:
|
| 132 |
kwargs["num_labels"] = num_labels
|
| 133 |
|
| 134 |
+
# ============================================================
|
| 135 |
+
# 4) Parent initialization
|
| 136 |
+
# 4) ๋ถ๋ชจ ํด๋์ค ์ด๊ธฐํ
|
| 137 |
+
# ============================================================
|
| 138 |
+
# Initialize PretrainedConfig with normalized label mappings.
|
| 139 |
+
# ์ ๊ทํ๋ ๋ผ๋ฒจ ๋งคํ์ ์ฌ์ฉํ์ฌ PretrainedConfig๋ฅผ ์ด๊ธฐํํฉ๋๋ค.
|
| 140 |
super().__init__(
|
| 141 |
+
label2id=label2id_norm,
|
| 142 |
+
id2label=id2label_norm,
|
| 143 |
**kwargs,
|
| 144 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
+
# ============================================================
|
| 147 |
+
# 5) Explicit attribute assignment for save/load stability
|
| 148 |
+
# 5) ์ ์ฅ/๋ก๋ ์์ ์ฑ์ ์ํ ๋ช
์์ ์์ฑ ๊ณ ์
|
| 149 |
+
# ============================================================
|
| 150 |
+
# Explicitly reassign critical fields to avoid subtle serialization issues.
|
| 151 |
+
# ๋ฏธ๋ฌํ ์ง๋ ฌํ ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ํต์ฌ ํ๋๋ฅผ ๋ช
์์ ์ผ๋ก ๋ค์ ์ค์ ํฉ๋๋ค.
|
| 152 |
self.backbone_name_or_path = backbone_name_or_path
|
| 153 |
+
self.mlp_head_bottleneck = int(mlp_head_bottleneck)
|
| 154 |
+
self.mlp_head_dropout = float(mlp_head_dropout)
|
| 155 |
+
|
| 156 |
+
self.label2id = label2id_norm
|
| 157 |
+
self.id2label = id2label_norm
|
| 158 |
+
self.num_labels = int(kwargs["num_labels"])
|
| 159 |
|
| 160 |
def to_dict(self):
|
| 161 |
+
# Call the parent implementation first.
|
| 162 |
+
# ๋จผ์ ๋ถ๋ชจ ๊ตฌํ์ ํธ์ถํฉ๋๋ค.
|
| 163 |
output = super().to_dict()
|
| 164 |
+
|
| 165 |
+
# Force num_labels to be present and consistent.
|
| 166 |
+
# num_labels๊ฐ ๋ฐ๋์ ์กด์ฌํ๊ณ ์ผ๊ด๋๋๋ก ๊ฐ์ ํฉ๋๋ค.
|
| 167 |
+
output["num_labels"] = int(
|
| 168 |
+
getattr(self, "num_labels", output.get("num_labels", 0))
|
| 169 |
+
)
|
| 170 |
return output
|
| 171 |
|
| 172 |
+
|
| 173 |
+
# Register this config so it can be resolved via AutoConfig.
|
| 174 |
+
# AutoConfig๋ฅผ ํตํด ์ด config๊ฐ ํด์๋ ์ ์๋๋ก ๋ฑ๋กํฉ๋๋ค.
|
| 175 |
BackboneMLPHeadConfig.register_for_auto_class("AutoConfig")
|
models/microsoft__resnet-50/ds_model.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
# -*- coding: utf-8 -*-
|
| 3 |
|
| 4 |
-
|
| 5 |
|
| 6 |
-
from
|
| 7 |
-
from typing import Dict, Any, Optional, Tuple, Set, List
|
| 8 |
|
| 9 |
import torch
|
| 10 |
import torch.nn as nn
|
|
@@ -12,146 +11,88 @@ import torch.nn.functional as F
|
|
| 12 |
|
| 13 |
# --- transformers core ---
|
| 14 |
from transformers.modeling_utils import PreTrainedModel
|
| 15 |
-
from transformers import AutoModel, AutoConfig
|
| 16 |
from transformers.modeling_outputs import ImageClassifierOutput
|
| 17 |
|
| 18 |
# --- torchvision ---
|
| 19 |
from torchvision import models as tv_models
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
# - ๊ธฐ์กด ds_cfg.BackboneMLPHeadConfig์ ํ๋๋ช
์ ๋ง์ถ์์ต๋๋ค.
|
| 25 |
-
# - ์ด๋ฏธ ds_cfg.py๊ฐ ์๋ค๋ฉด: ์ด ๋ธ๋ก์ ์ง์ฐ๊ณ "from ds_cfg import BackboneMLPHeadConfig"๋ก ๋ฐ๊ฟ๋ ๋ฉ๋๋ค.
|
| 26 |
-
# ============================================================
|
| 27 |
-
class BackboneMLPHeadConfig(PretrainedConfig):
|
| 28 |
-
model_type = "backbone_mlphead"
|
| 29 |
-
|
| 30 |
-
def __init__(
|
| 31 |
-
self,
|
| 32 |
-
backbone_name_or_path: Optional[str] = None,
|
| 33 |
-
num_labels: int = 2,
|
| 34 |
-
id2label: Optional[Dict[int, str]] = None,
|
| 35 |
-
label2id: Optional[Dict[str, int]] = None,
|
| 36 |
-
mlp_head_bottleneck: int = 256,
|
| 37 |
-
mlp_head_dropout: float = 0.2,
|
| 38 |
-
**kwargs,
|
| 39 |
-
):
|
| 40 |
-
super().__init__(**kwargs)
|
| 41 |
-
self.backbone_name_or_path = backbone_name_or_path
|
| 42 |
-
self.num_labels = num_labels
|
| 43 |
-
self.id2label = id2label or {i: f"LABEL_{i}" for i in range(num_labels)}
|
| 44 |
-
self.label2id = label2id or {v: k for k, v in self.id2label.items()}
|
| 45 |
-
self.mlp_head_bottleneck = mlp_head_bottleneck
|
| 46 |
-
self.mlp_head_dropout = mlp_head_dropout
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
# ============================================================
|
| 50 |
-
# (1) BACKBONE_META
|
| 51 |
-
# - ์ด๋ฏธ ds_meta.py๊ฐ ์๋ค๋ฉด: ์ด ๋ธ๋ก์ ์ง์ฐ๊ณ "from ds_meta import BACKBONE_META"๋ก ๋ฐ๊ฟ๋ ๋ฉ๋๋ค.
|
| 52 |
-
# ============================================================
|
| 53 |
-
BACKBONE_META: Dict[str, Dict[str, Any]] = {
|
| 54 |
-
# transformers
|
| 55 |
-
"google/vit-base-patch16-224": {
|
| 56 |
-
"type": "vit",
|
| 57 |
-
"feat_rule": "cls",
|
| 58 |
-
"feat_dim": 768,
|
| 59 |
-
"has_bn": False,
|
| 60 |
-
"unfreeze": "last_n",
|
| 61 |
-
},
|
| 62 |
-
"microsoft/swin-tiny-patch4-window7-224": {
|
| 63 |
-
"type": "swin",
|
| 64 |
-
"feat_rule": "pool_or_mean",
|
| 65 |
-
"feat_dim": 768,
|
| 66 |
-
"has_bn": False,
|
| 67 |
-
"unfreeze": "last_n",
|
| 68 |
-
},
|
| 69 |
-
"microsoft/resnet-50": {
|
| 70 |
-
"type": "resnet",
|
| 71 |
-
"feat_rule": "pool_or_gap",
|
| 72 |
-
"feat_dim": 2048,
|
| 73 |
-
"has_bn": True,
|
| 74 |
-
"unfreeze": "last_n",
|
| 75 |
-
},
|
| 76 |
-
"google/efficientnet-b0": {
|
| 77 |
-
"type": "efficientnet",
|
| 78 |
-
"feat_rule": "pool_or_gap",
|
| 79 |
-
"feat_dim": 1280,
|
| 80 |
-
"has_bn": True,
|
| 81 |
-
"unfreeze": "last_n",
|
| 82 |
-
},
|
| 83 |
-
|
| 84 |
-
# densenet
|
| 85 |
-
"timm/densenet121.tv_in1k": {
|
| 86 |
-
"type": "timm_densenet",
|
| 87 |
-
"feat_rule": "timm_gap",
|
| 88 |
-
"feat_dim": 1024,
|
| 89 |
-
"has_bn": True,
|
| 90 |
-
"unfreeze": "last_n",
|
| 91 |
-
},
|
| 92 |
-
"torchvision/densenet121": {
|
| 93 |
-
"type": "torchvision_densenet",
|
| 94 |
-
"feat_rule": "torchvision_densenet_gap",
|
| 95 |
-
"feat_dim": 1024,
|
| 96 |
-
"has_bn": True,
|
| 97 |
-
"unfreeze": "last_n",
|
| 98 |
-
},
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
# ============================================================
|
| 103 |
-
# (2) MLP Head
|
| 104 |
-
# - ์ด๋ฏธ mlp_head.py๊ฐ ์๋ค๋ฉด: ์ด ๋ธ๋ก์ ์ง์ฐ๊ณ "from mlp_head import MLPHead"๋ก ๋ฐ๊ฟ๋ ๋ฉ๋๋ค.
|
| 105 |
-
# ============================================================
|
| 106 |
-
class MLPHead(nn.Module):
|
| 107 |
-
def __init__(self, in_dim: int, num_labels: int, bottleneck: int = 256, p: float = 0.2):
|
| 108 |
-
super().__init__()
|
| 109 |
-
self.fc2 = nn.Linear(in_dim, bottleneck)
|
| 110 |
-
self.act2 = nn.GELU()
|
| 111 |
-
self.drop2 = nn.Dropout(p)
|
| 112 |
-
self.fc3 = nn.Linear(bottleneck, num_labels)
|
| 113 |
-
|
| 114 |
-
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 115 |
-
x = self.drop2(self.act2(self.fc2(x)))
|
| 116 |
-
return self.fc3(x)
|
| 117 |
-
|
| 118 |
|
| 119 |
# ============================================================
|
| 120 |
# (3) Model: backbone + MLP head
|
| 121 |
-
#
|
| 122 |
-
# - __init__: backbone ๊ตฌ์กฐ(skeleton)๋ง ์์ฑ. pretrained๋ฅผ ์ ๋ ๋ก๋ํ์ง ์์.
|
| 123 |
-
# - pretrained ์ฃผ์
: model.load_backbone_pretrained_()๋ฅผ "fresh-start์์๋ง" ์ง์ ํธ์ถ.
|
| 124 |
-
# - from_pretrained override ์์: HF ๊ธฐ๋ณธ ๋ก๋๊ฐ checkpoint๋ฅผ ๊ทธ๋๋ก ๋ณต์.
|
| 125 |
# ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
|
|
|
|
|
| 127 |
config_class = BackboneMLPHeadConfig
|
| 128 |
|
| 129 |
def __init__(self, config: BackboneMLPHeadConfig):
|
|
|
|
|
|
|
| 130 |
super().__init__(config)
|
| 131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
if config.backbone_name_or_path is None:
|
| 133 |
-
raise ValueError(
|
|
|
|
|
|
|
|
|
|
| 134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
self._meta = BACKBONE_META[config.backbone_name_or_path]
|
| 136 |
|
| 137 |
-
#
|
|
|
|
| 138 |
self.backbone = self._build_backbone_skeleton(config.backbone_name_or_path)
|
| 139 |
|
| 140 |
-
#
|
|
|
|
| 141 |
self.classifier = MLPHead(
|
| 142 |
in_dim=int(self._meta["feat_dim"]),
|
| 143 |
-
num_labels=config.num_labels,
|
| 144 |
-
bottleneck=config.mlp_head_bottleneck,
|
| 145 |
-
p=config.mlp_head_dropout,
|
| 146 |
)
|
| 147 |
|
| 148 |
-
# HF
|
|
|
|
| 149 |
self.post_init()
|
| 150 |
|
| 151 |
def init_weights(self):
|
| 152 |
"""
|
| 153 |
-
|
| 154 |
-
head๋ง ์ด๊ธฐํ
|
|
|
|
|
|
|
|
|
|
| 155 |
"""
|
| 156 |
if getattr(self, "classifier", None) is not None:
|
| 157 |
self.classifier.apply(self._init_weights)
|
|
@@ -159,8 +100,11 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 159 |
|
| 160 |
# ----------------------------
|
| 161 |
# backbone skeleton builders
|
|
|
|
| 162 |
# ----------------------------
|
| 163 |
def _build_backbone_skeleton(self, backbone_id: str) -> nn.Module:
|
|
|
|
|
|
|
| 164 |
meta = BACKBONE_META[backbone_id]
|
| 165 |
t = meta["type"]
|
| 166 |
|
|
@@ -170,18 +114,24 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 170 |
if t == "torchvision_densenet":
|
| 171 |
return self._build_torchvision_densenet_skeleton(backbone_id)
|
| 172 |
|
| 173 |
-
# transformers:
|
|
|
|
| 174 |
bb_cfg = AutoConfig.from_pretrained(backbone_id)
|
| 175 |
return AutoModel.from_config(bb_cfg)
|
| 176 |
|
| 177 |
@staticmethod
|
| 178 |
def _build_timm_densenet_skeleton(hf_repo_id: str) -> nn.Module:
|
|
|
|
|
|
|
| 179 |
try:
|
| 180 |
import timm
|
| 181 |
except Exception as e:
|
| 182 |
-
raise ImportError(
|
|
|
|
|
|
|
| 183 |
|
| 184 |
-
#
|
|
|
|
| 185 |
return timm.create_model(
|
| 186 |
f"hf_hub:{hf_repo_id}",
|
| 187 |
pretrained=False,
|
|
@@ -190,33 +140,33 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 190 |
|
| 191 |
@staticmethod
|
| 192 |
def _build_torchvision_densenet_skeleton(model_id: str) -> nn.Module:
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
"torchvision
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
arch = mapping[model_id]
|
| 203 |
-
fn = getattr(tv_models, arch)
|
| 204 |
-
|
| 205 |
-
# ๊ตฌ์กฐ๋ง (weights=None)
|
| 206 |
-
m = fn(weights=None).eval()
|
| 207 |
return m
|
| 208 |
|
| 209 |
# ------------------------------------------------------------
|
| 210 |
-
#
|
|
|
|
| 211 |
# ------------------------------------------------------------
|
| 212 |
@torch.no_grad()
|
| 213 |
-
def load_backbone_pretrained_(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
"""
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
model.load_backbone_pretrained_()
|
| 218 |
|
| 219 |
-
|
|
|
|
| 220 |
"""
|
| 221 |
bb = self.config.backbone_name_or_path
|
| 222 |
meta = BACKBONE_META[bb]
|
|
@@ -230,48 +180,58 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 230 |
self._load_torchvision_pretrained_into_skeleton_(bb)
|
| 231 |
return
|
| 232 |
|
| 233 |
-
# transformers pretrained
|
|
|
|
| 234 |
ref = AutoModel.from_pretrained(
|
| 235 |
bb,
|
| 236 |
low_cpu_mem_usage=low_cpu_mem_usage,
|
| 237 |
device_map=device_map,
|
| 238 |
)
|
|
|
|
|
|
|
|
|
|
| 239 |
self.backbone.load_state_dict(ref.state_dict(), strict=False)
|
| 240 |
del ref
|
| 241 |
|
| 242 |
@torch.no_grad()
|
| 243 |
def _load_timm_pretrained_into_skeleton_(self, hf_repo_id: str):
|
|
|
|
|
|
|
| 244 |
import timm
|
|
|
|
|
|
|
|
|
|
| 245 |
ref = timm.create_model(
|
| 246 |
f"hf_hub:{hf_repo_id}",
|
| 247 |
pretrained=True,
|
| 248 |
num_classes=0,
|
| 249 |
).eval()
|
|
|
|
| 250 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 251 |
del ref
|
| 252 |
|
| 253 |
@torch.no_grad()
|
| 254 |
def _load_torchvision_pretrained_into_skeleton_(self, model_id: str):
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
"torchvision
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
arch, weights = mapping[model_id]
|
| 265 |
-
fn = getattr(tv_models, arch)
|
| 266 |
-
ref = fn(weights=weights).eval()
|
| 267 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 268 |
del ref
|
| 269 |
|
| 270 |
# ----------------------------
|
| 271 |
# feature extraction
|
|
|
|
| 272 |
# ----------------------------
|
| 273 |
@staticmethod
|
| 274 |
def _pool_or_gap(outputs) -> torch.Tensor:
|
|
|
|
|
|
|
| 275 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 276 |
x = outputs.pooler_output
|
| 277 |
if x.dim() == 2:
|
|
@@ -280,29 +240,42 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 280 |
return x.flatten(1)
|
| 281 |
raise RuntimeError(f"Unexpected pooler_output shape: {tuple(x.shape)}")
|
| 282 |
|
|
|
|
|
|
|
| 283 |
x = outputs.last_hidden_state
|
| 284 |
if x.dim() == 4:
|
| 285 |
return x.mean(dim=(2, 3))
|
|
|
|
| 286 |
raise RuntimeError(
|
| 287 |
"Expected pooler_output or (B,C,H,W) last_hidden_state for CNN backbones. "
|
| 288 |
f"Got last_hidden_state shape={tuple(x.shape)}"
|
| 289 |
)
|
| 290 |
|
| 291 |
def _extract_features(self, outputs, pixel_values: Optional[torch.Tensor] = None) -> torch.Tensor:
|
|
|
|
|
|
|
| 292 |
rule = self._meta["feat_rule"]
|
| 293 |
|
| 294 |
if rule == "cls":
|
|
|
|
|
|
|
| 295 |
return outputs.last_hidden_state[:, 0, :]
|
| 296 |
|
| 297 |
if rule == "pool_or_mean":
|
|
|
|
|
|
|
| 298 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 299 |
return outputs.pooler_output
|
| 300 |
return outputs.last_hidden_state.mean(dim=1)
|
| 301 |
|
| 302 |
if rule == "pool_or_gap":
|
|
|
|
|
|
|
| 303 |
return self._pool_or_gap(outputs)
|
| 304 |
|
| 305 |
if rule == "timm_gap":
|
|
|
|
|
|
|
| 306 |
if not isinstance(outputs, torch.Tensor):
|
| 307 |
raise TypeError(f"timm_gap expects Tensor features, got {type(outputs)}")
|
| 308 |
if outputs.dim() != 4:
|
|
@@ -310,6 +283,8 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 310 |
return outputs.mean(dim=(2, 3))
|
| 311 |
|
| 312 |
if rule == "torchvision_densenet_gap":
|
|
|
|
|
|
|
| 313 |
if not isinstance(outputs, torch.Tensor):
|
| 314 |
raise TypeError(f"torchvision_densenet_gap expects Tensor, got {type(outputs)}")
|
| 315 |
if outputs.dim() != 4:
|
|
@@ -327,9 +302,13 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 327 |
return_dict=True,
|
| 328 |
**kwargs,
|
| 329 |
):
|
|
|
|
|
|
|
| 330 |
t = self._meta["type"]
|
| 331 |
|
| 332 |
if t == "timm_densenet":
|
|
|
|
|
|
|
| 333 |
if pixel_values is None:
|
| 334 |
raise ValueError("timm DenseNet backbone requires pixel_values.")
|
| 335 |
if pixel_values.dim() != 4:
|
|
@@ -341,6 +320,8 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 341 |
attentions = None
|
| 342 |
|
| 343 |
elif t == "torchvision_densenet":
|
|
|
|
|
|
|
| 344 |
if pixel_values is None:
|
| 345 |
raise ValueError("torchvision DenseNet backbone requires pixel_values.")
|
| 346 |
if pixel_values.dim() != 4:
|
|
@@ -353,6 +334,8 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 353 |
attentions = None
|
| 354 |
|
| 355 |
else:
|
|
|
|
|
|
|
| 356 |
outputs = self.backbone(
|
| 357 |
pixel_values=pixel_values,
|
| 358 |
output_attentions=output_attentions,
|
|
@@ -364,10 +347,14 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 364 |
hidden_states = getattr(outputs, "hidden_states", None)
|
| 365 |
attentions = getattr(outputs, "attentions", None)
|
| 366 |
|
|
|
|
|
|
|
| 367 |
logits = self.classifier(feats)
|
| 368 |
|
| 369 |
loss = None
|
| 370 |
if labels is not None:
|
|
|
|
|
|
|
| 371 |
loss = F.cross_entropy(logits, labels)
|
| 372 |
|
| 373 |
if not return_dict:
|
|
@@ -383,20 +370,27 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 383 |
|
| 384 |
|
| 385 |
# ============================================================
|
| 386 |
-
# Freeze / Unfreeze utilities
|
|
|
|
| 387 |
# ============================================================
|
| 388 |
def _set_requires_grad(module: nn.Module, flag: bool):
|
|
|
|
|
|
|
| 389 |
for p in module.parameters():
|
| 390 |
p.requires_grad = flag
|
| 391 |
|
| 392 |
|
| 393 |
def set_bn_eval(module: nn.Module):
|
|
|
|
|
|
|
| 394 |
for m in module.modules():
|
| 395 |
if isinstance(m, (nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d, nn.SyncBatchNorm)):
|
| 396 |
m.eval()
|
| 397 |
|
| 398 |
|
| 399 |
def freeze_backbone(model: BackboneWithMLPHeadForImageClassification, freeze_bn: bool = True):
|
|
|
|
|
|
|
| 400 |
_set_requires_grad(model.backbone, False)
|
| 401 |
_set_requires_grad(model.classifier, True)
|
| 402 |
|
|
@@ -406,6 +400,8 @@ def freeze_backbone(model: BackboneWithMLPHeadForImageClassification, freeze_bn:
|
|
| 406 |
|
| 407 |
|
| 408 |
def finetune_train_mode(model: BackboneWithMLPHeadForImageClassification, keep_bn_eval: bool = True):
|
|
|
|
|
|
|
| 409 |
model.train()
|
| 410 |
meta = BACKBONE_META[model.config.backbone_name_or_path]
|
| 411 |
if keep_bn_eval and meta.get("has_bn", False):
|
|
@@ -413,6 +409,8 @@ def finetune_train_mode(model: BackboneWithMLPHeadForImageClassification, keep_b
|
|
| 413 |
|
| 414 |
|
| 415 |
def trainable_summary(model: nn.Module):
|
|
|
|
|
|
|
| 416 |
total = sum(p.numel() for p in model.parameters())
|
| 417 |
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 418 |
ratio = trainable / total if total > 0 else 0.0
|
|
@@ -425,6 +423,8 @@ def unfreeze_last_stage(
|
|
| 425 |
last_n: int = 2,
|
| 426 |
keep_bn_eval: bool = True,
|
| 427 |
):
|
|
|
|
|
|
|
| 428 |
freeze_backbone(model, freeze_bn=keep_bn_eval)
|
| 429 |
|
| 430 |
n = int(last_n)
|
|
@@ -438,12 +438,16 @@ def unfreeze_last_stage(
|
|
| 438 |
bb_type = meta["type"]
|
| 439 |
|
| 440 |
if bb_type == "vit":
|
|
|
|
|
|
|
| 441 |
blocks = list(model.backbone.encoder.layer)
|
| 442 |
for blk in blocks[-n:]:
|
| 443 |
_set_requires_grad(blk, True)
|
| 444 |
return
|
| 445 |
|
| 446 |
if bb_type == "swin":
|
|
|
|
|
|
|
| 447 |
stages = list(model.backbone.encoder.layers)
|
| 448 |
blocks: List[nn.Module] = []
|
| 449 |
for st in stages:
|
|
@@ -453,6 +457,8 @@ def unfreeze_last_stage(
|
|
| 453 |
return
|
| 454 |
|
| 455 |
if bb_type == "resnet":
|
|
|
|
|
|
|
| 456 |
bb = model.backbone
|
| 457 |
for name in ("layer1", "layer2", "layer3", "layer4"):
|
| 458 |
if not hasattr(bb, name):
|
|
@@ -472,6 +478,8 @@ def unfreeze_last_stage(
|
|
| 472 |
return
|
| 473 |
|
| 474 |
if bb_type == "efficientnet":
|
|
|
|
|
|
|
| 475 |
bb = model.backbone
|
| 476 |
if not hasattr(bb, "features"):
|
| 477 |
raise RuntimeError("Unexpected EfficientNet structure: missing features")
|
|
@@ -488,6 +496,8 @@ def unfreeze_last_stage(
|
|
| 488 |
return
|
| 489 |
|
| 490 |
if bb_type in ("timm_densenet", "torchvision_densenet"):
|
|
|
|
|
|
|
| 491 |
bb = model.backbone
|
| 492 |
if not hasattr(bb, "features"):
|
| 493 |
raise RuntimeError("Unexpected DenseNet: missing features")
|
|
@@ -505,6 +515,8 @@ def unfreeze_last_stage(
|
|
| 505 |
raise RuntimeError(f"Unexpected DenseNet features: missing {name}")
|
| 506 |
|
| 507 |
def _denselayers(db: nn.Module) -> List[nn.Module]:
|
|
|
|
|
|
|
| 508 |
return list(db.children())
|
| 509 |
|
| 510 |
blocks: List[nn.Module] = []
|
|
@@ -526,20 +538,8 @@ def unfreeze_last_stage(
|
|
| 526 |
|
| 527 |
# -------------------------
|
| 528 |
# register
|
|
|
|
|
|
|
|
|
|
|
|
|
| 529 |
BackboneWithMLPHeadForImageClassification.register_for_auto_class("AutoModelForImageClassification")
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
# ==========================================================
|
| 533 |
-
|
| 534 |
-
# # fresh-start
|
| 535 |
-
# cfg = BackboneMLPHeadConfig(backbone_name_or_path="timm/densenet121.tv_in1k", num_labels=3)
|
| 536 |
-
# model = BackboneWithMLPHeadForImageClassification(cfg)
|
| 537 |
-
|
| 538 |
-
# # pretrained๋ฅผ ์ฐ๋ ค๋ฉด ๋ช
์์ ์ผ๋ก ํธ์ถ
|
| 539 |
-
# model.load_backbone_pretrained_()
|
| 540 |
-
|
| 541 |
-
# # ์ดํ ํ์ต
|
| 542 |
-
|
| 543 |
-
# # checkpoint์์ ์์
|
| 544 |
-
# model = BackboneWithMLPHeadForImageClassification.from_pretrained("checkpoints/xxx")
|
| 545 |
-
# # ์ฌ๊ธฐ์๋ load_backbone_pretrained_() ํธ์ถํ๋ฉด ์ ๋ฉ๋๋ค.
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
# -*- coding: utf-8 -*-
|
| 3 |
|
| 4 |
+
# src/ds_model.py
|
| 5 |
|
| 6 |
+
from typing import Optional, List
|
|
|
|
| 7 |
|
| 8 |
import torch
|
| 9 |
import torch.nn as nn
|
|
|
|
| 11 |
|
| 12 |
# --- transformers core ---
|
| 13 |
from transformers.modeling_utils import PreTrainedModel
|
| 14 |
+
from transformers import AutoModel, AutoConfig
|
| 15 |
from transformers.modeling_outputs import ImageClassifierOutput
|
| 16 |
|
| 17 |
# --- torchvision ---
|
| 18 |
from torchvision import models as tv_models
|
| 19 |
|
| 20 |
+
from ds_cfg import BackboneMLPHeadConfig
|
| 21 |
+
from ds_meta import BACKBONE_META
|
| 22 |
+
from mlp_head import MLPHead
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
# ============================================================
|
| 25 |
# (3) Model: backbone + MLP head
|
| 26 |
+
# (3) ๋ชจ๋ธ: backbone + MLP head
|
|
|
|
|
|
|
|
|
|
| 27 |
# ============================================================
|
| 28 |
+
# Design principle: __init__ builds only a skeleton and MUST NOT load pretrained weights.
|
| 29 |
+
# ์ค๊ณ ์์น: __init__์์๋ skeleton๋ง ๋ง๋ค๊ณ pretrained weight ๋ก๋๋ ์ ๋ ํ๋ฉด ์ ๋ฉ๋๋ค.
|
| 30 |
+
#
|
| 31 |
+
# Pretrained injection is allowed ONLY via an explicit call in fresh-start flows.
|
| 32 |
+
# pretrained ์ฃผ์
์ fresh-start ํ๋ฆ์์์ ๋ช
์์ ํธ์ถ๋ก๋ง ํ์ฉํฉ๋๋ค.
|
| 33 |
+
#
|
| 34 |
+
# HF from_pretrained should restore checkpoints as-is without side effects.
|
| 35 |
+
# HF from_pretrained๋ ๋ถ์์ฉ ์์ด ์ฒดํฌํฌ์ธํธ๋ฅผ ๊ทธ๋๋ก ๋ณต์ํด์ผ ํฉ๋๋ค.
|
| 36 |
class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
| 37 |
+
# This links the model to its custom config for AutoClass usage.
|
| 38 |
+
# AutoClass ์ฌ์ฉ์ ์ํด ๋ชจ๋ธ๊ณผ ์ปค์คํ
config๋ฅผ ์ฐ๊ฒฐํฉ๋๋ค.
|
| 39 |
config_class = BackboneMLPHeadConfig
|
| 40 |
|
| 41 |
def __init__(self, config: BackboneMLPHeadConfig):
|
| 42 |
+
# PreTrainedModel expects a config object and stores it internally.
|
| 43 |
+
# PreTrainedModel์ config ๊ฐ์ฒด๋ฅผ ๋ฐ์ ๋ด๋ถ์ ์ ์ฅํ๋ ์ ์ ๋ฅผ ๊ฐ์ง๋๋ค.
|
| 44 |
super().__init__(config)
|
| 45 |
|
| 46 |
+
# Fail-fast: the model is not meant to be instantiated without a valid backbone id.
|
| 47 |
+
# fail-fast: ์ ํจํ backbone id ์์ด ๋ชจ๋ธ์ ๋ง๋๋ ์ฌ์ฉ ์๋๋ฆฌ์ค๋ ํ์ฉํ์ง ์์ต๋๋ค.
|
| 48 |
+
#
|
| 49 |
+
# Note: Transformers may create configs with no args, but models are conventionally created with configs.
|
| 50 |
+
# ์ฐธ๊ณ : Transformers๋ config ๋ฌด์ธ์ ์์ฑ์ด ์์ ์ ์์ผ๋, ๋ชจ๋ธ์ ๊ด๋ก์ ์ผ๋ก config๋ฅผ ๋ฐ์ ์์ฑ๋ฉ๋๋ค.
|
| 51 |
if config.backbone_name_or_path is None:
|
| 52 |
+
raise ValueError(
|
| 53 |
+
"config.backbone_name_or_path is None. "
|
| 54 |
+
"Provide a valid backbone id (whitelist key in BACKBONE_META)."
|
| 55 |
+
)
|
| 56 |
|
| 57 |
+
# Fail-fast: training/inference requires a positive number of labels.
|
| 58 |
+
# fail-fast: ํ์ต/์ถ๋ก ์ num_labels๊ฐ ์์์ฌ์ผ ํฉ๋๋ค.
|
| 59 |
+
#
|
| 60 |
+
# Config may exist in a minimal form for internal serialization paths, but the model should not.
|
| 61 |
+
# config๋ ๋ด๋ถ ์ง๋ ฌํ ๊ฒฝ๋ก์์ ์ต์ ํํ๋ก ์กด์ฌํ ์ ์์ผ๋ ๋ชจ๋ธ์ ๊ทธ๋ฌ๋ฉด ์ ๋ฉ๋๋ค.
|
| 62 |
+
if int(getattr(config, "num_labels", 0)) <= 0:
|
| 63 |
+
raise ValueError(
|
| 64 |
+
f"config.num_labels must be > 0, got {getattr(config, 'num_labels', None)}. "
|
| 65 |
+
"Set num_labels (or id2label/label2id) when creating the config."
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
# Meta is a single source of truth for extraction and fine-tuning rules.
|
| 69 |
+
# meta๋ feature ์ถ์ถ ๋ฐ ๋ฏธ์ธ์กฐ์ ๊ท์น์ ๋จ์ผ ๊ธฐ์ค์
๋๋ค.
|
| 70 |
self._meta = BACKBONE_META[config.backbone_name_or_path]
|
| 71 |
|
| 72 |
+
# Backbone skeleton is always created without pretrained weights.
|
| 73 |
+
# backbone skeleton์ ํญ์ pretrained weight ์์ด ์์ฑํฉ๋๋ค.
|
| 74 |
self.backbone = self._build_backbone_skeleton(config.backbone_name_or_path)
|
| 75 |
|
| 76 |
+
# Head shape is driven by meta feat_dim and config.num_labels.
|
| 77 |
+
# head shape์ meta์ feat_dim๊ณผ config.num_labels๋ก ๊ฒฐ์ ๋ฉ๋๋ค.
|
| 78 |
self.classifier = MLPHead(
|
| 79 |
in_dim=int(self._meta["feat_dim"]),
|
| 80 |
+
num_labels=int(config.num_labels),
|
| 81 |
+
bottleneck=int(config.mlp_head_bottleneck),
|
| 82 |
+
p=float(config.mlp_head_dropout),
|
| 83 |
)
|
| 84 |
|
| 85 |
+
# HF initialization hook, but we override init_weights to initialize head-only.
|
| 86 |
+
# HF ์ด๊ธฐํ ํ
์ด์ง๋ง init_weights๋ฅผ overrideํ์ฌ head๋ง ์ด๊ธฐํํฉ๋๋ค.
|
| 87 |
self.post_init()
|
| 88 |
|
| 89 |
def init_weights(self):
|
| 90 |
"""
|
| 91 |
+
Initialize only the head to avoid touching the backbone skeleton.
|
| 92 |
+
backbone skeleton์ ๊ฑด๋๋ฆฌ์ง ์๊ธฐ ์ํด head๋ง ์ด๊ธฐํํฉ๋๋ค.
|
| 93 |
+
|
| 94 |
+
HF's default init may traverse the entire module tree, which is undesirable here.
|
| 95 |
+
HF ๊ธฐ๋ณธ init์ ์ ์ฒด ๋ชจ๋ ํธ๋ฆฌ๋ฅผ ์ํํ ์ ์์ด ์ฌ๊ธฐ์๋ ๋ถ์ ์ ํฉ๋๋ค.
|
| 96 |
"""
|
| 97 |
if getattr(self, "classifier", None) is not None:
|
| 98 |
self.classifier.apply(self._init_weights)
|
|
|
|
| 100 |
|
| 101 |
# ----------------------------
|
| 102 |
# backbone skeleton builders
|
| 103 |
+
# backbone skeleton ์์ฑ๊ธฐ
|
| 104 |
# ----------------------------
|
| 105 |
def _build_backbone_skeleton(self, backbone_id: str) -> nn.Module:
|
| 106 |
+
# Meta decides which loader path to use.
|
| 107 |
+
# meta๊ฐ ์ด๋ค ๋ก๋ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ ์ง ๊ฒฐ์ ํฉ๋๋ค.
|
| 108 |
meta = BACKBONE_META[backbone_id]
|
| 109 |
t = meta["type"]
|
| 110 |
|
|
|
|
| 114 |
if t == "torchvision_densenet":
|
| 115 |
return self._build_torchvision_densenet_skeleton(backbone_id)
|
| 116 |
|
| 117 |
+
# For transformers backbones: build a random-weight skeleton from config only.
|
| 118 |
+
# transformers ๋ฐฑ๋ณธ: config๋ก๋ถํฐ ๋๋ค ์ด๊ธฐํ skeleton๋ง ์์ฑํฉ๋๋ค.
|
| 119 |
bb_cfg = AutoConfig.from_pretrained(backbone_id)
|
| 120 |
return AutoModel.from_config(bb_cfg)
|
| 121 |
|
| 122 |
@staticmethod
|
| 123 |
def _build_timm_densenet_skeleton(hf_repo_id: str) -> nn.Module:
|
| 124 |
+
# timm is an optional dependency and should be imported lazily.
|
| 125 |
+
# timm์ ์ต์
์์กด์ฑ์ด๋ฏ๋ก ์ง์ฐ import ํฉ๋๋ค.
|
| 126 |
try:
|
| 127 |
import timm
|
| 128 |
except Exception as e:
|
| 129 |
+
raise ImportError(
|
| 130 |
+
"DenseNet(timm) backbone requires `timm`. Install: pip install timm"
|
| 131 |
+
) from e
|
| 132 |
|
| 133 |
+
# Build structure only (pretrained=False) and remove classifier head (num_classes=0).
|
| 134 |
+
# ๊ตฌ์กฐ๋ง ์์ฑ(pretrained=False)ํ๊ณ ๋ถ๋ฅ๊ธฐ head๋ ์ ๊ฑฐ(num_classes=0)ํฉ๋๋ค.
|
| 135 |
return timm.create_model(
|
| 136 |
f"hf_hub:{hf_repo_id}",
|
| 137 |
pretrained=False,
|
|
|
|
| 140 |
|
| 141 |
@staticmethod
|
| 142 |
def _build_torchvision_densenet_skeleton(model_id: str) -> nn.Module:
|
| 143 |
+
# This project intentionally supports only torchvision/densenet121 in the 224 whitelist.
|
| 144 |
+
# ์ด ํ๋ก์ ํธ๋ 224 ํ์ดํธ๋ฆฌ์คํธ์์ torchvision/densenet121๋ง ์๋์ ์ผ๋ก ์ง์ํฉ๋๋ค.
|
| 145 |
+
if model_id != "torchvision/densenet121":
|
| 146 |
+
raise ValueError(f"Unsupported torchvision DenseNet id (224 whitelist only): {model_id}")
|
| 147 |
+
|
| 148 |
+
# Build structure only (weights=None) to avoid implicit pretrained loading.
|
| 149 |
+
# implicit pretrained ๋ก๋๋ฅผ ํผํ๊ธฐ ์ํด ๊ตฌ์กฐ๋ง ์์ฑ(weights=None)ํฉ๋๋ค.
|
| 150 |
+
m = tv_models.densenet121(weights=None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
return m
|
| 152 |
|
| 153 |
# ------------------------------------------------------------
|
| 154 |
+
# Pretrained loading is explicit and fresh-start only
|
| 155 |
+
# pretrained ๋ก๋ฉ์ ๋ช
์์ ํธ์ถ์ด๋ฉฐ fresh-start ์ ์ฉ
|
| 156 |
# ------------------------------------------------------------
|
| 157 |
@torch.no_grad()
|
| 158 |
+
def load_backbone_pretrained_(
|
| 159 |
+
self,
|
| 160 |
+
*,
|
| 161 |
+
low_cpu_mem_usage: bool = False,
|
| 162 |
+
device_map=None,
|
| 163 |
+
):
|
| 164 |
"""
|
| 165 |
+
Fresh-start only: inject pretrained backbone weights into the skeleton.
|
| 166 |
+
fresh-start ์ ์ฉ: skeleton backbone์ pretrained ๊ฐ์ค์น๋ฅผ ์ฃผ์
ํฉ๋๋ค.
|
|
|
|
| 167 |
|
| 168 |
+
Do NOT call this after from_pretrained() because it would overwrite checkpoint weights.
|
| 169 |
+
from_pretrained() ์ดํ ํธ์ถํ๋ฉด ์ฒดํฌํฌ์ธํธ ๊ฐ์ค์น๋ฅผ ๋ฎ์ด์ฐ๋ฏ๋ก ์ ๋ ํธ์ถํ๋ฉด ์ ๋ฉ๋๋ค.
|
| 170 |
"""
|
| 171 |
bb = self.config.backbone_name_or_path
|
| 172 |
meta = BACKBONE_META[bb]
|
|
|
|
| 180 |
self._load_torchvision_pretrained_into_skeleton_(bb)
|
| 181 |
return
|
| 182 |
|
| 183 |
+
# For transformers backbones, load a reference pretrained model and copy weights into our skeleton.
|
| 184 |
+
# transformers ๋ฐฑ๋ณธ์ reference pretrained ๋ชจ๋ธ์ ๋ก๋ํ ๋ค skeleton์ ๊ฐ์ค์น๋ฅผ ๋ณต์ฌํฉ๋๋ค.
|
| 185 |
ref = AutoModel.from_pretrained(
|
| 186 |
bb,
|
| 187 |
low_cpu_mem_usage=low_cpu_mem_usage,
|
| 188 |
device_map=device_map,
|
| 189 |
)
|
| 190 |
+
|
| 191 |
+
# strict=False is used to tolerate harmless key differences across minor versions.
|
| 192 |
+
# strict=False๋ ๋ง์ด๋ ๋ฒ์ ์ฐจ์ด๋ก ์ธํ ๋ฌดํดํ ํค ์ฐจ์ด๋ฅผ ํ์ฉํ๊ธฐ ์ํด ์ฌ์ฉํฉ๋๋ค.
|
| 193 |
self.backbone.load_state_dict(ref.state_dict(), strict=False)
|
| 194 |
del ref
|
| 195 |
|
| 196 |
@torch.no_grad()
|
| 197 |
def _load_timm_pretrained_into_skeleton_(self, hf_repo_id: str):
|
| 198 |
+
# timm must be present for timm backbones.
|
| 199 |
+
# timm ๋ฐฑ๋ณธ์๋ timm ์ค์น๊ฐ ํ์ํฉ๋๋ค.
|
| 200 |
import timm
|
| 201 |
+
|
| 202 |
+
# Create a pretrained reference model and copy its weights strictly.
|
| 203 |
+
# pretrained reference ๋ชจ๋ธ์ ๋ง๋ค๊ณ ๊ฐ์ค์น๋ฅผ strictํ๊ฒ ๋ณต์ฌํฉ๋๋ค.
|
| 204 |
ref = timm.create_model(
|
| 205 |
f"hf_hub:{hf_repo_id}",
|
| 206 |
pretrained=True,
|
| 207 |
num_classes=0,
|
| 208 |
).eval()
|
| 209 |
+
|
| 210 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 211 |
del ref
|
| 212 |
|
| 213 |
@torch.no_grad()
|
| 214 |
def _load_torchvision_pretrained_into_skeleton_(self, model_id: str):
|
| 215 |
+
# This project intentionally supports only torchvision/densenet121 in the 224 whitelist.
|
| 216 |
+
# ์ด ํ๋ก์ ํธ๋ 224 ํ์ดํธ๋ฆฌ์คํธ์์ torchvision/densenet121๋ง ์๋์ ์ผ๋ก ์ง์ํฉ๋๋ค.
|
| 217 |
+
if model_id != "torchvision/densenet121":
|
| 218 |
+
raise ValueError(f"Unsupported torchvision DenseNet id (224 whitelist only): {model_id}")
|
| 219 |
+
|
| 220 |
+
# Use torchvision's default pretrained weights for densenet121.
|
| 221 |
+
# torchvision์ densenet121 ๊ธฐ๋ณธ pretrained weights๋ฅผ ์ฌ์ฉํฉ๋๋ค.
|
| 222 |
+
ref = tv_models.densenet121(weights=tv_models.DenseNet121_Weights.DEFAULT).eval()
|
| 223 |
+
|
|
|
|
|
|
|
|
|
|
| 224 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 225 |
del ref
|
| 226 |
|
| 227 |
# ----------------------------
|
| 228 |
# feature extraction
|
| 229 |
+
# feature ์ถ์ถ
|
| 230 |
# ----------------------------
|
| 231 |
@staticmethod
|
| 232 |
def _pool_or_gap(outputs) -> torch.Tensor:
|
| 233 |
+
# Some transformers vision CNNs provide pooler_output explicitly.
|
| 234 |
+
# ์ผ๋ถ transformers vision CNN์ pooler_output์ ๋ช
์์ ์ผ๋ก ์ ๊ณตํฉ๋๋ค.
|
| 235 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 236 |
x = outputs.pooler_output
|
| 237 |
if x.dim() == 2:
|
|
|
|
| 240 |
return x.flatten(1)
|
| 241 |
raise RuntimeError(f"Unexpected pooler_output shape: {tuple(x.shape)}")
|
| 242 |
|
| 243 |
+
# Otherwise we expect a CNN-style last_hidden_state=(B,C,H,W) and apply GAP.
|
| 244 |
+
# ๊ทธ๋ ์ง ์์ผ๋ฉด CNN ์คํ์ผ last_hidden_state=(B,C,H,W)๋ฅผ ๊ธฐ๋ํ๊ณ GAP์ ์ ์ฉํฉ๋๋ค.
|
| 245 |
x = outputs.last_hidden_state
|
| 246 |
if x.dim() == 4:
|
| 247 |
return x.mean(dim=(2, 3))
|
| 248 |
+
|
| 249 |
raise RuntimeError(
|
| 250 |
"Expected pooler_output or (B,C,H,W) last_hidden_state for CNN backbones. "
|
| 251 |
f"Got last_hidden_state shape={tuple(x.shape)}"
|
| 252 |
)
|
| 253 |
|
| 254 |
def _extract_features(self, outputs, pixel_values: Optional[torch.Tensor] = None) -> torch.Tensor:
|
| 255 |
+
# Feature rule is defined by BACKBONE_META and must remain stable across saves/loads.
|
| 256 |
+
# feature ๊ท์น์ BACKBONE_META๋ก ์ ์๋๋ฉฐ ์ ์ฅ/๋ก๋ ๊ฐ ์์ ์ ์ผ๋ก ์ ์ง๋์ด์ผ ํฉ๋๋ค.
|
| 257 |
rule = self._meta["feat_rule"]
|
| 258 |
|
| 259 |
if rule == "cls":
|
| 260 |
+
# ViT-style: use CLS token embedding from last_hidden_state.
|
| 261 |
+
# ViT ์คํ์ผ: last_hidden_state์์ CLS ํ ํฐ ์๋ฒ ๋ฉ์ ์ฌ์ฉํฉ๋๋ค.
|
| 262 |
return outputs.last_hidden_state[:, 0, :]
|
| 263 |
|
| 264 |
if rule == "pool_or_mean":
|
| 265 |
+
# Swin-style: prefer pooler_output if present, else mean-pool over tokens.
|
| 266 |
+
# Swin ์คํ์ผ: pooler_output์ด ์์ผ๋ฉด ์ฐ์ ์ฌ์ฉํ๊ณ , ์์ผ๋ฉด ํ ํฐ ํ๊ท ํ๋ง์ ์ฌ์ฉํฉ๋๋ค.
|
| 267 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 268 |
return outputs.pooler_output
|
| 269 |
return outputs.last_hidden_state.mean(dim=1)
|
| 270 |
|
| 271 |
if rule == "pool_or_gap":
|
| 272 |
+
# CNN-style: use pooler_output if present, else GAP over spatial dims.
|
| 273 |
+
# CNN ์คํ์ผ: pooler_output์ด ์์ผ๋ฉด ์ฌ์ฉํ๊ณ , ์์ผ๋ฉด ๊ณต๊ฐ ์ฐจ์ GAP์ ์ฌ์ฉํฉ๋๋ค.
|
| 274 |
return self._pool_or_gap(outputs)
|
| 275 |
|
| 276 |
if rule == "timm_gap":
|
| 277 |
+
# timm forward_features returns a feature map (B,C,H,W) which we GAP to (B,C).
|
| 278 |
+
# timm forward_features๋ (B,C,H,W) feature map์ ๋ฐํํ๋ฉฐ ์ด๋ฅผ GAP์ผ๋ก (B,C)๋ก ๋ง๋ญ๋๋ค.
|
| 279 |
if not isinstance(outputs, torch.Tensor):
|
| 280 |
raise TypeError(f"timm_gap expects Tensor features, got {type(outputs)}")
|
| 281 |
if outputs.dim() != 4:
|
|
|
|
| 283 |
return outputs.mean(dim=(2, 3))
|
| 284 |
|
| 285 |
if rule == "torchvision_densenet_gap":
|
| 286 |
+
# torchvision DenseNet features are feature maps (B,C,H,W) and require GAP.
|
| 287 |
+
# torchvision DenseNet features๋ (B,C,H,W) feature map์ด๋ฉฐ GAP์ด ํ์ํฉ๋๋ค.
|
| 288 |
if not isinstance(outputs, torch.Tensor):
|
| 289 |
raise TypeError(f"torchvision_densenet_gap expects Tensor, got {type(outputs)}")
|
| 290 |
if outputs.dim() != 4:
|
|
|
|
| 302 |
return_dict=True,
|
| 303 |
**kwargs,
|
| 304 |
):
|
| 305 |
+
# Type decides the backbone forward path and output format.
|
| 306 |
+
# type์ด backbone forward ๊ฒฝ๋ก ๋ฐ ์ถ๋ ฅ ํฌ๋งท์ ๊ฒฐ์ ํฉ๋๋ค.
|
| 307 |
t = self._meta["type"]
|
| 308 |
|
| 309 |
if t == "timm_densenet":
|
| 310 |
+
# timm DenseNet consumes pixel_values as a 4D tensor (B,C,H,W).
|
| 311 |
+
# timm DenseNet์ pixel_values๋ฅผ 4D ํ
์ (B,C,H,W)๋ก ๋ฐ์ต๋๋ค.
|
| 312 |
if pixel_values is None:
|
| 313 |
raise ValueError("timm DenseNet backbone requires pixel_values.")
|
| 314 |
if pixel_values.dim() != 4:
|
|
|
|
| 320 |
attentions = None
|
| 321 |
|
| 322 |
elif t == "torchvision_densenet":
|
| 323 |
+
# torchvision DenseNet consumes pixel_values as a 4D tensor (B,C,H,W).
|
| 324 |
+
# torchvision DenseNet์ pixel_values๋ฅผ 4D ํ
์ (B,C,H,W)๋ก ๋ฐ์ต๋๋ค.
|
| 325 |
if pixel_values is None:
|
| 326 |
raise ValueError("torchvision DenseNet backbone requires pixel_values.")
|
| 327 |
if pixel_values.dim() != 4:
|
|
|
|
| 334 |
attentions = None
|
| 335 |
|
| 336 |
else:
|
| 337 |
+
# Transformers vision models are called with pixel_values and return ModelOutput.
|
| 338 |
+
# transformers vision ๋ชจ๋ธ์ pixel_values๋ก ํธ์ถ๋๋ฉฐ ModelOutput์ ๋ฐํํฉ๋๋ค.
|
| 339 |
outputs = self.backbone(
|
| 340 |
pixel_values=pixel_values,
|
| 341 |
output_attentions=output_attentions,
|
|
|
|
| 347 |
hidden_states = getattr(outputs, "hidden_states", None)
|
| 348 |
attentions = getattr(outputs, "attentions", None)
|
| 349 |
|
| 350 |
+
# Classifier consumes (B, feat_dim) and returns logits (B, num_labels).
|
| 351 |
+
# classifier๋ (B, feat_dim)์ ๋ฐ์ logits (B, num_labels)๋ฅผ ๋ฐํํฉ๋๋ค.
|
| 352 |
logits = self.classifier(feats)
|
| 353 |
|
| 354 |
loss = None
|
| 355 |
if labels is not None:
|
| 356 |
+
# Cross entropy expects labels as class indices in [0, num_labels).
|
| 357 |
+
# cross entropy๋ labels๊ฐ [0, num_labels) ๋ฒ์์ class index์ด๊ธธ ๊ธฐ๋ํฉ๋๋ค.
|
| 358 |
loss = F.cross_entropy(logits, labels)
|
| 359 |
|
| 360 |
if not return_dict:
|
|
|
|
| 370 |
|
| 371 |
|
| 372 |
# ============================================================
|
| 373 |
+
# Freeze / Unfreeze utilities
|
| 374 |
+
# Freeze / Unfreeze ์ ํธ๋ฆฌํฐ
|
| 375 |
# ============================================================
|
| 376 |
def _set_requires_grad(module: nn.Module, flag: bool):
|
| 377 |
+
# Toggle requires_grad for all parameters in a module.
|
| 378 |
+
# ๋ชจ๋์ ๋ชจ๋ ํ๋ผ๋ฏธํฐ์ ๋ํด requires_grad๋ฅผ ํ ๊ธํฉ๋๋ค.
|
| 379 |
for p in module.parameters():
|
| 380 |
p.requires_grad = flag
|
| 381 |
|
| 382 |
|
| 383 |
def set_bn_eval(module: nn.Module):
|
| 384 |
+
# Put BatchNorm layers into eval mode to freeze running stats.
|
| 385 |
+
# BatchNorm ๋ ์ด์ด๋ฅผ eval ๋ชจ๋๋ก ๋์ด running stats๋ฅผ ๊ณ ์ ํฉ๋๋ค.
|
| 386 |
for m in module.modules():
|
| 387 |
if isinstance(m, (nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d, nn.SyncBatchNorm)):
|
| 388 |
m.eval()
|
| 389 |
|
| 390 |
|
| 391 |
def freeze_backbone(model: BackboneWithMLPHeadForImageClassification, freeze_bn: bool = True):
|
| 392 |
+
# Stage1: freeze backbone and train only the head.
|
| 393 |
+
# stage1: backbone์ freezeํ๊ณ head๋ง ํ์ตํฉ๋๋ค.
|
| 394 |
_set_requires_grad(model.backbone, False)
|
| 395 |
_set_requires_grad(model.classifier, True)
|
| 396 |
|
|
|
|
| 400 |
|
| 401 |
|
| 402 |
def finetune_train_mode(model: BackboneWithMLPHeadForImageClassification, keep_bn_eval: bool = True):
|
| 403 |
+
# Stage2: train mode, optionally keeping BN layers in eval for stability.
|
| 404 |
+
# stage2: train ๋ชจ๋๋ก ๋๋ ์์ ์ฑ์ ์ํด BN์ eval๋ก ์ ์งํ ์ ์์ต๋๋ค.
|
| 405 |
model.train()
|
| 406 |
meta = BACKBONE_META[model.config.backbone_name_or_path]
|
| 407 |
if keep_bn_eval and meta.get("has_bn", False):
|
|
|
|
| 409 |
|
| 410 |
|
| 411 |
def trainable_summary(model: nn.Module):
|
| 412 |
+
# Print a compact summary of trainable parameters.
|
| 413 |
+
# ํ์ต ๊ฐ๋ฅ ํ๋ผ๋ฏธํฐ ์์ฝ์ ๊ฐ๋จํ ์ถ๋ ฅํฉ๋๋ค.
|
| 414 |
total = sum(p.numel() for p in model.parameters())
|
| 415 |
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 416 |
ratio = trainable / total if total > 0 else 0.0
|
|
|
|
| 423 |
last_n: int = 2,
|
| 424 |
keep_bn_eval: bool = True,
|
| 425 |
):
|
| 426 |
+
# This utility implements BACKBONE_META['unfreeze']=="last_n" across supported backbones.
|
| 427 |
+
# ์ด ์ ํธ์ ์ง์ ๋ฐฑ๋ณธ๋ค์ ๋ํด BACKBONE_META['unfreeze']=="last_n"์ ๊ตฌํํฉ๋๋ค.
|
| 428 |
freeze_backbone(model, freeze_bn=keep_bn_eval)
|
| 429 |
|
| 430 |
n = int(last_n)
|
|
|
|
| 438 |
bb_type = meta["type"]
|
| 439 |
|
| 440 |
if bb_type == "vit":
|
| 441 |
+
# ViT blocks live under backbone.encoder.layer in the transformers implementation.
|
| 442 |
+
# ViT ๋ธ๋ก์ transformers ๊ตฌํ์์ backbone.encoder.layer ์๋์ ์์ต๋๋ค.
|
| 443 |
blocks = list(model.backbone.encoder.layer)
|
| 444 |
for blk in blocks[-n:]:
|
| 445 |
_set_requires_grad(blk, True)
|
| 446 |
return
|
| 447 |
|
| 448 |
if bb_type == "swin":
|
| 449 |
+
# Swin blocks are nested by stages and blocks; we flatten and unfreeze last n blocks.
|
| 450 |
+
# Swin ๋ธ๋ก์ stage์ block์ผ๋ก ์ค์ฒฉ๋์ด ์์ด ํผ์น ํ ๋ง์ง๋ง n๊ฐ๋ฅผ unfreeze ํฉ๋๋ค.
|
| 451 |
stages = list(model.backbone.encoder.layers)
|
| 452 |
blocks: List[nn.Module] = []
|
| 453 |
for st in stages:
|
|
|
|
| 457 |
return
|
| 458 |
|
| 459 |
if bb_type == "resnet":
|
| 460 |
+
# ResNet uses layer1..layer4 stages; we unfreeze at block granularity.
|
| 461 |
+
# ResNet์ layer1..layer4 stage๋ฅผ ์ฌ์ฉํ๋ฉฐ block ๋จ์๋ก unfreeze ํฉ๋๋ค.
|
| 462 |
bb = model.backbone
|
| 463 |
for name in ("layer1", "layer2", "layer3", "layer4"):
|
| 464 |
if not hasattr(bb, name):
|
|
|
|
| 478 |
return
|
| 479 |
|
| 480 |
if bb_type == "efficientnet":
|
| 481 |
+
# EfficientNet in transformers exposes features; we unfreeze from the tail blocks.
|
| 482 |
+
# transformers EfficientNet์ features๋ฅผ ๋
ธ์ถํ๋ฉฐ ๋ค์ชฝ ๋ธ๋ก๋ถํฐ unfreeze ํฉ๋๋ค.
|
| 483 |
bb = model.backbone
|
| 484 |
if not hasattr(bb, "features"):
|
| 485 |
raise RuntimeError("Unexpected EfficientNet structure: missing features")
|
|
|
|
| 496 |
return
|
| 497 |
|
| 498 |
if bb_type in ("timm_densenet", "torchvision_densenet"):
|
| 499 |
+
# DenseNet exposes a .features module with named blocks; we unfreeze last n submodules.
|
| 500 |
+
# DenseNet์ .features ๋ชจ๋์ ๋ธ๋ก๋ค์ด ์ด๋ฆ์ผ๋ก ์กด์ฌํ๋ฉฐ ๋ง์ง๋ง n๊ฐ ์๋ธ๋ชจ๋์ unfreeze ํฉ๋๋ค.
|
| 501 |
bb = model.backbone
|
| 502 |
if not hasattr(bb, "features"):
|
| 503 |
raise RuntimeError("Unexpected DenseNet: missing features")
|
|
|
|
| 515 |
raise RuntimeError(f"Unexpected DenseNet features: missing {name}")
|
| 516 |
|
| 517 |
def _denselayers(db: nn.Module) -> List[nn.Module]:
|
| 518 |
+
# Dense blocks contain multiple DenseLayer children; we return them for fine-grained unfreezing.
|
| 519 |
+
# denseblock์ DenseLayer ์์๋ค์ ๊ฐ์ง๋ฏ๋ก ์ธ๋ฐํ unfreeze๋ฅผ ์ํด ์ด๋ฅผ ๋ฐํํฉ๋๋ค.
|
| 520 |
return list(db.children())
|
| 521 |
|
| 522 |
blocks: List[nn.Module] = []
|
|
|
|
| 538 |
|
| 539 |
# -------------------------
|
| 540 |
# register
|
| 541 |
+
# register
|
| 542 |
+
# -------------------------
|
| 543 |
+
# Register for AutoModelForImageClassification so from_pretrained can resolve this custom class.
|
| 544 |
+
# from_pretrained๊ฐ ์ด ์ปค์คํ
ํด๋์ค๋ฅผ ํด์ํ ์ ์๋๋ก AutoModelForImageClassification์ ๋ฑ๋กํฉ๋๋ค.
|
| 545 |
BackboneWithMLPHeadForImageClassification.register_for_auto_class("AutoModelForImageClassification")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
models/microsoft__resnet-50/ds_proc.py
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# src/ds_proc.py
|
| 5 |
+
|
| 6 |
+
# ============================================================
|
| 7 |
+
# (4) ImageProcessor (AutoImageProcessor integration)
|
| 8 |
+
# (4) ImageProcessor (AutoImageProcessor ์ฐ๋)
|
| 9 |
+
# ============================================================
|
| 10 |
+
|
| 11 |
+
from typing import Any
|
| 12 |
+
|
| 13 |
+
import numpy as np
|
| 14 |
+
import torch
|
| 15 |
+
from transformers import AutoImageProcessor, AutoConfig
|
| 16 |
+
from transformers.image_processing_base import ImageProcessingMixin
|
| 17 |
+
from transformers.utils.generic import TensorType
|
| 18 |
+
|
| 19 |
+
from ds_meta import BackboneID, BACKBONE_META
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class BackboneMLPHead224ImageProcessor(ImageProcessingMixin):
|
| 23 |
+
"""
|
| 24 |
+
This processor performs image preprocessing and outputs {"pixel_values": ...}.
|
| 25 |
+
์ด processor๋ ์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ๋ฅผ ์ํํ๊ณ {"pixel_values": ...}๋ฅผ ์ถ๋ ฅํฉ๋๋ค.
|
| 26 |
+
|
| 27 |
+
Key requirements:
|
| 28 |
+
ํต์ฌ ์๊ตฌ์ฌํญ:
|
| 29 |
+
|
| 30 |
+
1) save_pretrained() must produce a JSON-serializable preprocessor_config.json.
|
| 31 |
+
save_pretrained()๋ JSON ์ง๋ ฌํ ๊ฐ๋ฅํ preprocessor_config.json์ ์์ฑํด์ผ ํฉ๋๋ค.
|
| 32 |
+
|
| 33 |
+
2) Runtime-only objects (delegate processor, timm/torchvision transforms) must NOT be serialized.
|
| 34 |
+
๋ฐํ์ ๊ฐ์ฒด(delegate processor, timm/torchvision transform)๋ ์ ๋ ์ง๋ ฌํํ๋ฉด ์ ๋ฉ๋๋ค.
|
| 35 |
+
|
| 36 |
+
3) Runtime objects are rebuilt at init/load time based on backbone meta.
|
| 37 |
+
๋ฐํ์ ๊ฐ์ฒด๋ backbone meta์ ๋ฐ๋ผ init/load ์์ ์ ์ฌ๊ตฌ์ฑ๋ฉ๋๋ค.
|
| 38 |
+
"""
|
| 39 |
+
|
| 40 |
+
# HF vision models conventionally expect "pixel_values" as the primary input key.
|
| 41 |
+
# HF vision ๋ชจ๋ธ์ ๊ด๋ก์ ์ผ๋ก ์
๋ ฅ ํค๋ก "pixel_values"๋ฅผ ๊ธฐ๋ํฉ๋๋ค.
|
| 42 |
+
model_input_names = ["pixel_values"]
|
| 43 |
+
|
| 44 |
+
def __init__(self, backbone_name_or_path: BackboneID, is_training: bool = False, **kwargs):
|
| 45 |
+
# ImageProcessingMixin stores extra kwargs and manages auto_map metadata.
|
| 46 |
+
# ImageProcessingMixin์ ์ถ๊ฐ kwargs๋ฅผ ์ ์ฅํ๊ณ auto_map ๋ฉํ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.
|
| 47 |
+
super().__init__(**kwargs)
|
| 48 |
+
|
| 49 |
+
# Enforce whitelist via BACKBONE_META to keep behavior stable.
|
| 50 |
+
# ๋์ ์์ ์ฑ์ ์ํด BACKBONE_META ๊ธฐ๋ฐ ํ์ดํธ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ ํฉ๋๋ค.
|
| 51 |
+
if backbone_name_or_path not in BACKBONE_META:
|
| 52 |
+
raise ValueError(
|
| 53 |
+
f"Unsupported backbone_name_or_path={backbone_name_or_path}. "
|
| 54 |
+
f"Allowed: {sorted(BACKBONE_META.keys())}"
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
# Serializable fields only: these should appear in preprocessor_config.json.
|
| 58 |
+
# ์ง๋ ฌํ ๊ฐ๋ฅํ ํ๋๋ง: ์ด ๊ฐ๋ค๋ง preprocessor_config.json์ ๋ค์ด๊ฐ์ผ ํฉ๋๋ค.
|
| 59 |
+
self.backbone_name_or_path = backbone_name_or_path
|
| 60 |
+
self.is_training = bool(is_training)
|
| 61 |
+
|
| 62 |
+
# Runtime-only fields: must never be serialized.
|
| 63 |
+
# ๋ฐํ์ ์ ์ฉ ํ๋: ์ ๋ ์ง๋ ฌํ๋๋ฉด ์ ๋ฉ๋๋ค.
|
| 64 |
+
self._meta = None
|
| 65 |
+
self._delegate = None
|
| 66 |
+
self._timm_transform = None
|
| 67 |
+
self._torchvision_transform = None
|
| 68 |
+
|
| 69 |
+
# Build runtime objects according to backbone type.
|
| 70 |
+
# backbone type์ ๋ฐ๋ผ ๋ฐํ์ ๊ฐ์ฒด๋ฅผ ๊ตฌ์ฑํฉ๋๋ค.
|
| 71 |
+
self._build_runtime()
|
| 72 |
+
|
| 73 |
+
# ============================================================
|
| 74 |
+
# Runtime builders
|
| 75 |
+
# ๋ฐํ์ ๋น๋
|
| 76 |
+
# ============================================================
|
| 77 |
+
def _build_runtime(self):
|
| 78 |
+
"""
|
| 79 |
+
Build runtime delegate/transform based on BACKBONE_META["type"].
|
| 80 |
+
BACKBONE_META["type"]์ ๋ฐ๋ผ ๋ฐํ์ delegate/transform์ ๊ตฌ์ฑํฉ๋๋ค.
|
| 81 |
+
"""
|
| 82 |
+
meta = BACKBONE_META[self.backbone_name_or_path]
|
| 83 |
+
self._meta = meta
|
| 84 |
+
|
| 85 |
+
# Always reset runtime fields before rebuilding.
|
| 86 |
+
# ์ฌ๊ตฌ์ฑ ์ ๋ฐํ์ ํ๋๋ ํญ์ ์ด๊ธฐํํฉ๋๋ค.
|
| 87 |
+
self._delegate = None
|
| 88 |
+
self._timm_transform = None
|
| 89 |
+
self._torchvision_transform = None
|
| 90 |
+
|
| 91 |
+
t = meta["type"]
|
| 92 |
+
|
| 93 |
+
if t == "timm_densenet":
|
| 94 |
+
# timm DenseNet uses timm.data transforms for ImageNet-style preprocessing.
|
| 95 |
+
# timm DenseNet์ ImageNet ์ ์ฒ๋ฆฌ๋ฅผ ์ํด timm.data transform์ ์ฌ์ฉํฉ๋๋ค.
|
| 96 |
+
self._timm_transform = self._build_timm_transform(
|
| 97 |
+
backbone_id=self.backbone_name_or_path,
|
| 98 |
+
is_training=self.is_training,
|
| 99 |
+
)
|
| 100 |
+
return
|
| 101 |
+
|
| 102 |
+
if t == "torchvision_densenet":
|
| 103 |
+
# torchvision DenseNet requires torchvision-style preprocessing (resize/crop/tensor/normalize).
|
| 104 |
+
# torchvision DenseNet์ torchvision ์คํ์ผ ์ ์ฒ๋ฆฌ(resize/crop/tensor/normalize)๊ฐ ํ์ํฉ๋๋ค.
|
| 105 |
+
self._torchvision_transform = self._build_torchvision_densenet_transform(
|
| 106 |
+
is_training=self.is_training
|
| 107 |
+
)
|
| 108 |
+
return
|
| 109 |
+
|
| 110 |
+
# Default: transformers backbone delegates to its official AutoImageProcessor.
|
| 111 |
+
# ๊ธฐ๋ณธ: transformers ๋ฐฑ๋ณธ์ ๊ณต์ AutoImageProcessor์ ์์ํฉ๋๋ค.
|
| 112 |
+
self._delegate = AutoImageProcessor.from_pretrained(self.backbone_name_or_path)
|
| 113 |
+
|
| 114 |
+
@staticmethod
|
| 115 |
+
def _build_timm_transform(*, backbone_id: str, is_training: bool):
|
| 116 |
+
"""
|
| 117 |
+
Create timm transform without storing non-serializable objects in config.
|
| 118 |
+
๋น์ง๋ ฌํ ๊ฐ์ฒด๋ฅผ config์ ์ ์ฅํ์ง ์๊ณ timm transform์ ์์ฑํฉ๋๋ค.
|
| 119 |
+
"""
|
| 120 |
+
try:
|
| 121 |
+
import timm
|
| 122 |
+
from timm.data import resolve_model_data_config, create_transform
|
| 123 |
+
except Exception as e:
|
| 124 |
+
raise ImportError(
|
| 125 |
+
"timm backbone processor requires `timm`. Install: pip install timm"
|
| 126 |
+
) from e
|
| 127 |
+
|
| 128 |
+
# We only need model metadata to resolve data config, so pretrained=False is preferred.
|
| 129 |
+
# data config ์ถ์ถ๋ง ํ์ํ๋ฏ๋ก pretrained=False๋ฅผ ์ฐ์ ์ฌ์ฉํฉ๋๋ค.
|
| 130 |
+
m = timm.create_model(f"hf_hub:{backbone_id}", pretrained=False, num_classes=0)
|
| 131 |
+
dc = resolve_model_data_config(m)
|
| 132 |
+
|
| 133 |
+
# create_transform returns a torchvision-like callable that maps PIL -> torch.Tensor(C,H,W).
|
| 134 |
+
# create_transform์ PIL -> torch.Tensor(C,H,W)๋ก ๋งคํํ๋ callable์ ๋ฐํํฉ๋๋ค.
|
| 135 |
+
tfm = create_transform(**dc, is_training=is_training)
|
| 136 |
+
return tfm
|
| 137 |
+
|
| 138 |
+
@staticmethod
|
| 139 |
+
def _build_torchvision_densenet_transform(*, is_training: bool):
|
| 140 |
+
"""
|
| 141 |
+
Build torchvision preprocessing for DenseNet-121 (224 pipeline).
|
| 142 |
+
DenseNet-121์ฉ torchvision ์ ์ฒ๋ฆฌ(224 ํ์ดํ๋ผ์ธ)๋ฅผ ๊ตฌ์ฑํฉ๋๋ค.
|
| 143 |
+
"""
|
| 144 |
+
try:
|
| 145 |
+
from torchvision import transforms
|
| 146 |
+
except Exception as e:
|
| 147 |
+
raise ImportError(
|
| 148 |
+
"torchvision DenseNet processor requires `torchvision`. Install: pip install torchvision"
|
| 149 |
+
) from e
|
| 150 |
+
|
| 151 |
+
# These are the standard ImageNet normalization stats used by torchvision weights.
|
| 152 |
+
# ์ด ๊ฐ๋ค์ torchvision weights๊ฐ ์ฌ์ฉํ๋ ํ์ค ImageNet ์ ๊ทํ ํต๊ณ์
๋๋ค.
|
| 153 |
+
mean = (0.485, 0.456, 0.406)
|
| 154 |
+
std = (0.229, 0.224, 0.225)
|
| 155 |
+
|
| 156 |
+
# Training pipeline typically uses RandomResizedCrop and horizontal flip.
|
| 157 |
+
# ํ์ต ํ์ดํ๋ผ์ธ์ ๋ณดํต RandomResizedCrop๊ณผ ์ข์ฐ๋ฐ์ ์ ์ฌ์ฉํฉ๋๋ค.
|
| 158 |
+
if is_training:
|
| 159 |
+
return transforms.Compose(
|
| 160 |
+
[
|
| 161 |
+
transforms.RandomResizedCrop(224),
|
| 162 |
+
transforms.RandomHorizontalFlip(p=0.5),
|
| 163 |
+
transforms.ToTensor(),
|
| 164 |
+
transforms.Normalize(mean=mean, std=std),
|
| 165 |
+
]
|
| 166 |
+
)
|
| 167 |
+
|
| 168 |
+
# Inference pipeline typically uses Resize(256) + CenterCrop(224).
|
| 169 |
+
# ์ถ๋ก ํ์ดํ๋ผ์ธ์ ๋ณดํต Resize(256) + CenterCrop(224)๋ฅผ ์ฌ์ฉํฉ๋๋ค.
|
| 170 |
+
return transforms.Compose(
|
| 171 |
+
[
|
| 172 |
+
transforms.Resize(256),
|
| 173 |
+
transforms.CenterCrop(224),
|
| 174 |
+
transforms.ToTensor(),
|
| 175 |
+
transforms.Normalize(mean=mean, std=std),
|
| 176 |
+
]
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
# ============================================================
|
| 180 |
+
# Serialization
|
| 181 |
+
# ์ง๋ ฌํ
|
| 182 |
+
# ============================================================
|
| 183 |
+
def to_dict(self) -> dict[str, Any]:
|
| 184 |
+
"""
|
| 185 |
+
Return a JSON-serializable dict for preprocessor_config.json.
|
| 186 |
+
preprocessor_config.json์ ๋ค์ด๊ฐ JSON ์ง๋ ฌํ dict๋ฅผ ๋ฐํํฉ๋๋ค.
|
| 187 |
+
|
| 188 |
+
Important: do not leak runtime objects into the serialized dict.
|
| 189 |
+
์ค์: ๋ฐํ์ ๊ฐ์ฒด๊ฐ ์ง๋ ฌํ dict์ ์์ด๋ฉด ์ ๋ฉ๋๋ค.
|
| 190 |
+
"""
|
| 191 |
+
# ImageProcessingMixin.to_dict() adds metadata such as image_processor_type/auto_map.
|
| 192 |
+
# ImageProcessingMixin.to_dict()๋ image_processor_type/auto_map ๊ฐ์ ๋ฉํ๋ฅผ ์ถ๊ฐํฉ๋๋ค.
|
| 193 |
+
d = super().to_dict()
|
| 194 |
+
|
| 195 |
+
# Force minimal stable fields for long-term compatibility.
|
| 196 |
+
# ์ฅ๊ธฐ ํธํ์ ์ํด ์ต์ ์์ ํ๋๋ฅผ ๊ฐ์ ํฉ๋๋ค.
|
| 197 |
+
d["image_processor_type"] = self.__class__.__name__
|
| 198 |
+
d["backbone_name_or_path"] = self.backbone_name_or_path
|
| 199 |
+
d["is_training"] = self.is_training
|
| 200 |
+
|
| 201 |
+
# Remove any runtime-only fields defensively.
|
| 202 |
+
# ๋ฐํ์ ์ ์ฉ ํ๋๋ ๋ณด์์ ์ผ๋ก ์ ๊ฑฐํฉ๋๋ค.
|
| 203 |
+
for key in ["_meta", "_delegate", "_timm_transform", "_torchvision_transform"]:
|
| 204 |
+
d.pop(key, None)
|
| 205 |
+
|
| 206 |
+
return d
|
| 207 |
+
|
| 208 |
+
@classmethod
|
| 209 |
+
def from_dict(cls, image_processor_dict: dict[str, Any], **kwargs):
|
| 210 |
+
"""
|
| 211 |
+
Standard load path used by BaseImageProcessor / AutoImageProcessor.
|
| 212 |
+
BaseImageProcessor / AutoImageProcessor๊ฐ ์ฌ์ฉํ๋ ํ์ค ๋ก๋ ๊ฒฝ๋ก์
๋๋ค.
|
| 213 |
+
"""
|
| 214 |
+
backbone = image_processor_dict.get("backbone_name_or_path", None)
|
| 215 |
+
if backbone is None:
|
| 216 |
+
raise ValueError("preprocessor_config.json missing key: backbone_name_or_path")
|
| 217 |
+
|
| 218 |
+
is_training = bool(image_processor_dict.get("is_training", False))
|
| 219 |
+
return cls(backbone_name_or_path=backbone, is_training=is_training, **kwargs)
|
| 220 |
+
|
| 221 |
+
@classmethod
|
| 222 |
+
def from_pretrained(cls, pretrained_model_name_or_path: str, **kwargs):
|
| 223 |
+
"""
|
| 224 |
+
Fallback path if AutoImageProcessor calls class.from_pretrained directly.
|
| 225 |
+
AutoImageProcessor๊ฐ class.from_pretrained๋ฅผ ์ง์ ํธ์ถํ๋ ๊ฒฝ์ฐ๋ฅผ ๋๋นํ ๊ฒฝ๋ก์
๋๋ค.
|
| 226 |
+
|
| 227 |
+
Strategy:
|
| 228 |
+
์ ๋ต:
|
| 229 |
+
|
| 230 |
+
- Read config.json via AutoConfig and recover backbone_name_or_path.
|
| 231 |
+
AutoConfig๋ก config.json์ ์ฝ๊ณ backbone_name_or_path๋ฅผ ๋ณต๊ตฌํฉ๋๋ค.
|
| 232 |
+
"""
|
| 233 |
+
cfg = AutoConfig.from_pretrained(pretrained_model_name_or_path, **kwargs)
|
| 234 |
+
backbone = getattr(cfg, "backbone_name_or_path", None)
|
| 235 |
+
if backbone is None:
|
| 236 |
+
raise ValueError("Cannot build processor: backbone_name_or_path not found in config.json")
|
| 237 |
+
|
| 238 |
+
# is_training is runtime-only and should default to False for inference/serving.
|
| 239 |
+
# is_training์ ๋ฐํ์ ์ ์ฉ์ด๋ฉฐ ์ถ๋ก /์๋น ๊ธฐ๋ณธ๊ฐ์ False๊ฐ ๋ง์ต๋๋ค.
|
| 240 |
+
return cls(backbone_name_or_path=backbone, is_training=False)
|
| 241 |
+
|
| 242 |
+
# ============================================================
|
| 243 |
+
# Call interface
|
| 244 |
+
# ํธ์ถ ์ธํฐํ์ด์ค
|
| 245 |
+
# ============================================================
|
| 246 |
+
@staticmethod
|
| 247 |
+
def _ensure_list(images: Any) -> list[Any]:
|
| 248 |
+
# Normalize scalar image input to a list for uniform processing.
|
| 249 |
+
# ๋จ์ผ ์
๋ ฅ์ ๋ฆฌ์คํธ๋ก ์ ๊ทํํ์ฌ ๋์ผํ ์ฒ๋ฆฌ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํฉ๋๋ค.
|
| 250 |
+
if isinstance(images, (list, tuple)):
|
| 251 |
+
return list(images)
|
| 252 |
+
return [images]
|
| 253 |
+
|
| 254 |
+
@staticmethod
|
| 255 |
+
def _to_pil_rgb(x: Any):
|
| 256 |
+
# Convert common image inputs into PIL RGB images.
|
| 257 |
+
# ์ผ๋ฐ์ ์ธ ์
๋ ฅ์ PIL RGB ์ด๋ฏธ์ง๋ก ๋ณํํฉ๋๋ค.
|
| 258 |
+
from PIL import Image as PILImage
|
| 259 |
+
|
| 260 |
+
if isinstance(x, PILImage.Image):
|
| 261 |
+
return x.convert("RGB")
|
| 262 |
+
if isinstance(x, np.ndarray) and x.ndim == 3:
|
| 263 |
+
return PILImage.fromarray(x).convert("RGB")
|
| 264 |
+
raise TypeError(f"Unsupported image type: {type(x)}")
|
| 265 |
+
|
| 266 |
+
def __call__(
|
| 267 |
+
self,
|
| 268 |
+
images: Any | list[Any],
|
| 269 |
+
return_tensors: str | TensorType | None = "pt",
|
| 270 |
+
**kwargs,
|
| 271 |
+
) -> dict[str, Any]:
|
| 272 |
+
"""
|
| 273 |
+
Convert images into {"pixel_values": Tensor/ndarray}.
|
| 274 |
+
์ด๋ฏธ์ง๋ฅผ {"pixel_values": Tensor/ndarray}๋ก ๋ณํํฉ๋๋ค.
|
| 275 |
+
"""
|
| 276 |
+
images = self._ensure_list(images)
|
| 277 |
+
|
| 278 |
+
# Rebuild runtime if needed (e.g., right after deserialization).
|
| 279 |
+
# ์ง๋ ฌํ ๋ณต์ ์งํ ๋ฑ ๋ฐํ์์ด ๋น์ด์์ ์ ์์ผ๋ฏ๋ก ์ฌ๊ตฌ์ฑํฉ๋๋ค.
|
| 280 |
+
if (self._delegate is None) and (self._timm_transform is None) and (self._torchvision_transform is None):
|
| 281 |
+
self._build_runtime()
|
| 282 |
+
|
| 283 |
+
# timm path: PIL -> torch.Tensor(C,H,W) normalized float32.
|
| 284 |
+
# timm ๊ฒฝ๋ก: PIL -> torch.Tensor(C,H,W) ์ ๊ทํ float32์
๋๋ค.
|
| 285 |
+
if self._timm_transform is not None:
|
| 286 |
+
pv: list[torch.Tensor] = []
|
| 287 |
+
for im in images:
|
| 288 |
+
pil = self._to_pil_rgb(im)
|
| 289 |
+
t = self._timm_transform(pil)
|
| 290 |
+
if not isinstance(t, torch.Tensor):
|
| 291 |
+
raise RuntimeError("Unexpected timm transform output (expected torch.Tensor).")
|
| 292 |
+
pv.append(t)
|
| 293 |
+
pixel_values = torch.stack(pv, dim=0) # (B,C,H,W)
|
| 294 |
+
return self._format_return(pixel_values, return_tensors)
|
| 295 |
+
|
| 296 |
+
# torchvision path: PIL -> torch.Tensor(C,H,W) normalized float32.
|
| 297 |
+
# torchvision ๊ฒฝ๋ก: PIL -> torch.Tensor(C,H,W) ์ ๊ทํ float32์
๋๋ค.
|
| 298 |
+
if self._torchvision_transform is not None:
|
| 299 |
+
pv: list[torch.Tensor] = []
|
| 300 |
+
for im in images:
|
| 301 |
+
pil = self._to_pil_rgb(im)
|
| 302 |
+
t = self._torchvision_transform(pil)
|
| 303 |
+
if not isinstance(t, torch.Tensor):
|
| 304 |
+
raise RuntimeError("Unexpected torchvision transform output (expected torch.Tensor).")
|
| 305 |
+
pv.append(t)
|
| 306 |
+
pixel_values = torch.stack(pv, dim=0) # (B,C,H,W)
|
| 307 |
+
return self._format_return(pixel_values, return_tensors)
|
| 308 |
+
|
| 309 |
+
# transformers delegate path: rely on official processor behavior.
|
| 310 |
+
# transformers ์์ ๊ฒฝ๋ก: ๊ณต์ processor ๋์์ ๊ทธ๋๋ก ์ฌ์ฉํฉ๋๋ค.
|
| 311 |
+
if self._delegate is None:
|
| 312 |
+
raise RuntimeError("Processor runtime not built: delegate is None and no transforms are available.")
|
| 313 |
+
|
| 314 |
+
return self._delegate(images, return_tensors=return_tensors, **kwargs)
|
| 315 |
+
|
| 316 |
+
@staticmethod
|
| 317 |
+
def _format_return(pixel_values: torch.Tensor, return_tensors: str | TensorType | None) -> dict[str, Any]:
|
| 318 |
+
"""
|
| 319 |
+
Format pixel_values according to return_tensors.
|
| 320 |
+
return_tensors์ ๋ง์ถฐ pixel_values ๋ฐํ ํฌ๋งท์ ๋ง์ถฅ๋๋ค.
|
| 321 |
+
"""
|
| 322 |
+
if return_tensors is None or return_tensors in ("pt", TensorType.PYTORCH):
|
| 323 |
+
return {"pixel_values": pixel_values}
|
| 324 |
+
if return_tensors in ("np", TensorType.NUMPY):
|
| 325 |
+
return {"pixel_values": pixel_values.detach().cpu().numpy()}
|
| 326 |
+
raise ValueError(f"Unsupported return_tensors={return_tensors}. Use 'pt' or 'np'.")
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
# Register this processor for AutoImageProcessor resolution.
|
| 330 |
+
# AutoImageProcessor ํด์์ ์ํด ์ด processor๋ฅผ ๋ฑ๋กํฉ๋๋ค.
|
| 331 |
+
if __name__ != "__main__":
|
| 332 |
+
BackboneMLPHead224ImageProcessor.register_for_auto_class("AutoImageProcessor")
|
models/microsoft__resnet-50/model.safetensors
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 96388660
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:229f77319aec60efa8fff986455e304b9f4f2d46440550b69490da095a11d170
|
| 3 |
size 96388660
|
models/microsoft__resnet-50/preprocessor_config.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"auto_map": {
|
| 3 |
+
"AutoImageProcessor": "ds_proc.BackboneMLPHead224ImageProcessor"
|
| 4 |
+
},
|
| 5 |
+
"backbone_name_or_path": "microsoft/resnet-50",
|
| 6 |
+
"image_processor_type": "BackboneMLPHead224ImageProcessor",
|
| 7 |
+
"is_training": false
|
| 8 |
+
}
|
models/microsoft__swin-tiny-patch4-window7-224/config.json
CHANGED
|
@@ -24,10 +24,11 @@
|
|
| 24 |
"num_labels": 3,
|
| 25 |
"transformers_version": "5.1.0",
|
| 26 |
"ds_provenance": {
|
| 27 |
-
"created_at": "
|
| 28 |
"repo_id": "dsaint31/bb_mlp_224",
|
| 29 |
"subdir": "models/microsoft__swin-tiny-patch4-window7-224",
|
| 30 |
"wrapper_class": "BackboneWithMLPHeadForImageClassification",
|
|
|
|
| 31 |
"backbone_id": "microsoft/swin-tiny-patch4-window7-224",
|
| 32 |
"pretrained_injected": true,
|
| 33 |
"policy_init_pretrained_forbidden": true,
|
|
|
|
| 24 |
"num_labels": 3,
|
| 25 |
"transformers_version": "5.1.0",
|
| 26 |
"ds_provenance": {
|
| 27 |
+
"created_at": "20260210_111913",
|
| 28 |
"repo_id": "dsaint31/bb_mlp_224",
|
| 29 |
"subdir": "models/microsoft__swin-tiny-patch4-window7-224",
|
| 30 |
"wrapper_class": "BackboneWithMLPHeadForImageClassification",
|
| 31 |
+
"processor_class": "BackboneMLPHead224ImageProcessor",
|
| 32 |
"backbone_id": "microsoft/swin-tiny-patch4-window7-224",
|
| 33 |
"pretrained_injected": true,
|
| 34 |
"policy_init_pretrained_forbidden": true,
|
models/microsoft__swin-tiny-patch4-window7-224/ds_cfg.py
CHANGED
|
@@ -1,84 +1,175 @@
|
|
| 1 |
-
from typing import Literal, Union
|
| 2 |
-
|
| 3 |
-
from transformers import AutoConfig
|
| 4 |
from transformers import PretrainedConfig
|
|
|
|
| 5 |
from ds_meta import BackboneID, BACKBONE_META
|
| 6 |
|
| 7 |
-
|
| 8 |
-
# 3) Config
|
| 9 |
-
# ============================================================
|
| 10 |
class BackboneMLPHeadConfig(PretrainedConfig):
|
| 11 |
"""
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
----------
|
| 16 |
-
- transformers ๋ด๋ถ ๊ฒฝ๋ก์์ "๋ฌด์ธ์ ์์ฑ"์ด ๋ฐ์ํ ์ ์์ผ๋ฏ๋ก,
|
| 17 |
-
backbone_name_or_path=None์ธ ๊ฒฝ์ฐ๋ ๊ฒ์ฆ ์์ด ๊ธฐ๋ณธ๊ฐ๋ง ์ธํ
ํ๊ณ return
|
| 18 |
-
ํ๋ ๊ตฌํ์ด ๋ฐ๋์ ํ์ํจ.
|
| 19 |
-
- ๋จ, ์ค์ ์ฌ์ฉ์์๋ backbone_name_or_path๋ฅผ ๋ฐ๋์ ์ง์ ํด์ ์์ฑํด์ผ ํจ.
|
| 20 |
"""
|
| 21 |
|
| 22 |
-
#
|
|
|
|
| 23 |
model_type = "backbone-mlphead-224-fixed"
|
| 24 |
|
| 25 |
-
# ์์ฑ์์์ ๋ชจ๋ ์ธ์์ ๊ธฐ๋ณธ๊ฐ ๋ถ์ฌ ๋ฐ๋์ ํ์!
|
| 26 |
def __init__(
|
| 27 |
self,
|
| 28 |
backbone_name_or_path: BackboneID | None = None,
|
| 29 |
mlp_head_bottleneck: int = 256,
|
| 30 |
mlp_head_dropout: float = 0.2,
|
| 31 |
-
label2id: dict[str, int]|None = None,
|
| 32 |
-
id2label: dict[int, str]|None = None,
|
| 33 |
**kwargs,
|
| 34 |
):
|
| 35 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
if backbone_name_or_path is None:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
super().__init__(**kwargs)
|
|
|
|
|
|
|
|
|
|
| 38 |
self.backbone_name_or_path = None
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
#
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
return
|
| 44 |
|
| 45 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
if backbone_name_or_path not in BACKBONE_META:
|
| 47 |
raise ValueError(
|
| 48 |
f"Unsupported backbone_name_or_path={backbone_name_or_path}. "
|
| 49 |
f"Allowed: {sorted(BACKBONE_META.keys())}"
|
| 50 |
)
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
#
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
kwargs["num_labels"] = num_labels
|
| 62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
super().__init__(
|
| 64 |
-
label2id=
|
| 65 |
-
id2label=
|
| 66 |
**kwargs,
|
| 67 |
)
|
| 68 |
-
|
| 69 |
-
# 4) ์ค์: ๋ถ๋ชจ ํ๋์๋ num_labels ๋ฐ์(์ ์ฅ/๋ก๋ ์์ )
|
| 70 |
-
self.num_labels = num_labels
|
| 71 |
-
self.label2id = label2id
|
| 72 |
-
self.id2label = id2label
|
| 73 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
self.backbone_name_or_path = backbone_name_or_path
|
| 75 |
-
self.mlp_head_bottleneck
|
| 76 |
-
self.mlp_head_dropout
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
def to_dict(self):
|
|
|
|
|
|
|
| 79 |
output = super().to_dict()
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
return output
|
| 83 |
|
|
|
|
|
|
|
|
|
|
| 84 |
BackboneMLPHeadConfig.register_for_auto_class("AutoConfig")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from transformers import PretrainedConfig
|
| 2 |
+
|
| 3 |
from ds_meta import BackboneID, BACKBONE_META
|
| 4 |
|
| 5 |
+
|
|
|
|
|
|
|
| 6 |
class BackboneMLPHeadConfig(PretrainedConfig):
|
| 7 |
"""
|
| 8 |
+
Configuration for Backbone + MLP Head models.
|
| 9 |
+
|
| 10 |
+
Backbone + MLP Head ๋ชจ๋ธ์ ์ํ ์ค์ ํด๋์ค์
๋๋ค.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
"""
|
| 12 |
|
| 13 |
+
# This string is used by Hugging Face AutoConfig to identify the config class.
|
| 14 |
+
# Hugging Face AutoConfig๊ฐ ์ด config๋ฅผ ์๋ณํ๊ธฐ ์ํด ์ฌ์ฉํ๋ ๊ณ ์ ID์
๋๋ค.
|
| 15 |
model_type = "backbone-mlphead-224-fixed"
|
| 16 |
|
|
|
|
| 17 |
def __init__(
|
| 18 |
self,
|
| 19 |
backbone_name_or_path: BackboneID | None = None,
|
| 20 |
mlp_head_bottleneck: int = 256,
|
| 21 |
mlp_head_dropout: float = 0.2,
|
| 22 |
+
label2id: dict[str, int] | None = None,
|
| 23 |
+
id2label: dict[int, str] | None = None,
|
| 24 |
**kwargs,
|
| 25 |
):
|
| 26 |
+
# ============================================================
|
| 27 |
+
# 0) Guard for argument-less construction
|
| 28 |
+
# 0) ๋ฌด์ธ์ ์์ฑ ๊ฒฝ๋ก ๋ฐฉ์ด
|
| 29 |
+
# ============================================================
|
| 30 |
+
# Transformers may internally construct this config without arguments
|
| 31 |
+
# (e.g., during AutoConfig resolution or Hub loading).
|
| 32 |
+
# Transformers ๋ด๋ถ์์ AutoConfig ๋๋ Hub ๋ก๋ ๊ณผ์ ์ค
|
| 33 |
+
# ์ธ์ ์์ด config๋ฅผ ์์ฑํ๋ ๊ฒฝ๋ก๊ฐ ์ค์ ๋ก ์กด์ฌํฉ๋๋ค.
|
| 34 |
+
#
|
| 35 |
+
# In this case, we must NOT validate or raise errors.
|
| 36 |
+
# ์ด ๊ฒฝ์ฐ ๊ฒ์ฆ์ด๋ ์์ธ๋ฅผ ๋ฐ์์ํค๋ฉด ์ ๋ฉ๋๋ค.
|
| 37 |
+
#
|
| 38 |
+
# The goal here is to provide a minimal, serialization-safe config.
|
| 39 |
+
# ๋ชฉํ๋ ์ต์ํ์ ๊ฐ๋ง ์ฑ์ ์ง๋ ฌํ/์ญ์ง๋ ฌํ๊ฐ ๊นจ์ง์ง ์๊ฒ ํ๋ ๊ฒ์
๋๋ค.
|
| 40 |
if backbone_name_or_path is None:
|
| 41 |
+
# num_labels may be implicitly assumed by downstream code,
|
| 42 |
+
# so we explicitly set a safe default.
|
| 43 |
+
# num_labels๋ downstream ์ฝ๋์์ ์๋ฌต์ ์ผ๋ก ๊ฐ์ ๋๋ฏ๋ก
|
| 44 |
+
# ์์ ํ ๊ธฐ๋ณธ๊ฐ์ ๋ช
์์ ์ผ๋ก ๋ฃ์ด์ค๋๋ค.
|
| 45 |
+
if "num_labels" not in kwargs:
|
| 46 |
+
kwargs["num_labels"] = 0
|
| 47 |
+
|
| 48 |
super().__init__(**kwargs)
|
| 49 |
+
|
| 50 |
+
# Backbone is intentionally unset in this path.
|
| 51 |
+
# ์ด ๊ฒฝ๋ก์์๋ backbone์ ์๋์ ์ผ๋ก ์ค์ ํ์ง ์์ต๋๋ค.
|
| 52 |
self.backbone_name_or_path = None
|
| 53 |
+
|
| 54 |
+
# Store MLP head hyperparameters for completeness.
|
| 55 |
+
# MLP head ๊ด๋ จ ํ์ดํผํ๋ผ๋ฏธํฐ๋ ํํ ์ ์ง๋ฅผ ์ํด ์ ์ฅํฉ๋๋ค.
|
| 56 |
+
self.mlp_head_bottleneck = int(mlp_head_bottleneck)
|
| 57 |
+
self.mlp_head_dropout = float(mlp_head_dropout)
|
| 58 |
+
|
| 59 |
+
# Empty label mappings ensure stable save/load behavior.
|
| 60 |
+
# ๋น label ๋งคํ์ ์ ์ฅ/๋ก๋ ์์ ์ฑ์ ๋ณด์ฅํฉ๋๋ค.
|
| 61 |
+
self.label2id = {}
|
| 62 |
+
self.id2label = {}
|
| 63 |
+
self.num_labels = int(kwargs.get("num_labels", 0))
|
| 64 |
return
|
| 65 |
|
| 66 |
+
# ============================================================
|
| 67 |
+
# 1) Backbone whitelist validation
|
| 68 |
+
# 1) Backbone ํ์ดํธ๋ฆฌ์คํธ ๊ฒ์ฆ
|
| 69 |
+
# ============================================================
|
| 70 |
+
# Only backbones explicitly registered in BACKBONE_META are allowed.
|
| 71 |
+
# BACKBONE_META์ ๋ฑ๋ก๋ backbone๋ง ํ์ฉํฉ๋๋ค.
|
| 72 |
+
#
|
| 73 |
+
# This prevents accidental usage of unsupported or inconsistent backbones.
|
| 74 |
+
# ์ง์๋์ง ์๊ฑฐ๋ ๋ถ์ผ์นํ backbone ์ฌ์ฉ์ ์์ฒ ์ฐจ๋จํฉ๋๋ค.
|
| 75 |
if backbone_name_or_path not in BACKBONE_META:
|
| 76 |
raise ValueError(
|
| 77 |
f"Unsupported backbone_name_or_path={backbone_name_or_path}. "
|
| 78 |
f"Allowed: {sorted(BACKBONE_META.keys())}"
|
| 79 |
)
|
| 80 |
+
|
| 81 |
+
# ============================================================
|
| 82 |
+
# 2) Label mapping normalization
|
| 83 |
+
# 2) ๋ผ๋ฒจ ๋งคํ ์ ๊ทํ
|
| 84 |
+
# ============================================================
|
| 85 |
+
# Both label2id and id2label may be None during pure loading scenarios.
|
| 86 |
+
# ๋จ์ ๋ก๋(from_pretrained) ์ label2id/id2label์ด None์ผ ์ ์์ต๋๋ค.
|
| 87 |
+
#
|
| 88 |
+
# We allow this here to keep Hub loading and verification stable.
|
| 89 |
+
# Hub ๋ก๋ ๋ฐ ๊ฒ์ฆ ์์ ์ฑ์ ์ํด ์ฌ๊ธฐ์๋ ์ด๋ฅผ ํ์ฉํฉ๋๋ค.
|
| 90 |
+
#
|
| 91 |
+
# Fail-fast validation should happen at model or training level instead.
|
| 92 |
+
# ์ค์ ์ฌ์ฉ ๊ฒ์ฆ์ ๋ชจ๋ธ ๋๋ ํ์ต ๋จ๊ณ์์ fail-fast๋ก ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค.
|
| 93 |
+
if label2id is None and id2label is None:
|
| 94 |
+
# Respect num_labels if explicitly provided, otherwise default to 0.
|
| 95 |
+
# num_labels๊ฐ ์ฃผ์ด์ก๋ค๋ฉด ์กด์คํ๊ณ , ์๋๋ฉด 0์ผ๋ก ๋ก๋๋ค.
|
| 96 |
+
num_labels = int(kwargs.get("num_labels", 0))
|
| 97 |
+
label2id_norm: dict[str, int] = {}
|
| 98 |
+
id2label_norm: dict[int, str] = {}
|
| 99 |
+
else:
|
| 100 |
+
# If only one mapping is provided, derive the other.
|
| 101 |
+
# ํ๋๋ง ์ฃผ์ด์ง ๊ฒฝ์ฐ ๋๋จธ์ง๋ฅผ ์๋์ผ๋ก ์์ฑํฉ๋๋ค.
|
| 102 |
+
if id2label is None:
|
| 103 |
+
id2label = {v: k for k, v in label2id.items()}
|
| 104 |
+
if label2id is None:
|
| 105 |
+
label2id = {v: k for k, v in id2label.items()}
|
| 106 |
+
|
| 107 |
+
# Ensure both mappings are consistent in size.
|
| 108 |
+
# ๋ ๋งคํ์ ํฌ๊ธฐ๊ฐ ์ผ์นํ๋์ง ํ์ธํฉ๋๋ค.
|
| 109 |
+
if len(label2id) != len(id2label):
|
| 110 |
+
raise ValueError(
|
| 111 |
+
f"label2id/id2label size mismatch: "
|
| 112 |
+
f"{len(label2id)} vs {len(id2label)}"
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
num_labels = len(id2label)
|
| 116 |
+
label2id_norm = dict(label2id)
|
| 117 |
+
id2label_norm = dict(id2label)
|
| 118 |
+
|
| 119 |
+
# ============================================================
|
| 120 |
+
# 3) num_labels consistency enforcement
|
| 121 |
+
# 3) num_labels ์ผ๊ด์ฑ ๊ฐ์
|
| 122 |
+
# ============================================================
|
| 123 |
+
# If num_labels is provided via kwargs, it must match inferred labels.
|
| 124 |
+
# kwargs๋ก num_labels๊ฐ ๋ค์ด์จ ๊ฒฝ์ฐ ์ถ๋ก ๋ ๊ฐ๊ณผ ๋ฐ๋์ ์ผ์นํด์ผ ํฉ๋๋ค.
|
| 125 |
+
if "num_labels" in kwargs:
|
| 126 |
+
if (label2id is not None or id2label is not None) and int(kwargs["num_labels"]) != num_labels:
|
| 127 |
+
raise ValueError(
|
| 128 |
+
f"kwargs['num_labels']={kwargs['num_labels']} "
|
| 129 |
+
f"!= inferred num_labels={num_labels}"
|
| 130 |
+
)
|
| 131 |
+
else:
|
| 132 |
kwargs["num_labels"] = num_labels
|
| 133 |
|
| 134 |
+
# ============================================================
|
| 135 |
+
# 4) Parent initialization
|
| 136 |
+
# 4) ๋ถ๋ชจ ํด๋์ค ์ด๊ธฐํ
|
| 137 |
+
# ============================================================
|
| 138 |
+
# Initialize PretrainedConfig with normalized label mappings.
|
| 139 |
+
# ์ ๊ทํ๋ ๋ผ๋ฒจ ๋งคํ์ ์ฌ์ฉํ์ฌ PretrainedConfig๋ฅผ ์ด๊ธฐํํฉ๋๋ค.
|
| 140 |
super().__init__(
|
| 141 |
+
label2id=label2id_norm,
|
| 142 |
+
id2label=id2label_norm,
|
| 143 |
**kwargs,
|
| 144 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
+
# ============================================================
|
| 147 |
+
# 5) Explicit attribute assignment for save/load stability
|
| 148 |
+
# 5) ์ ์ฅ/๋ก๋ ์์ ์ฑ์ ์ํ ๋ช
์์ ์์ฑ ๊ณ ์
|
| 149 |
+
# ============================================================
|
| 150 |
+
# Explicitly reassign critical fields to avoid subtle serialization issues.
|
| 151 |
+
# ๋ฏธ๋ฌํ ์ง๋ ฌํ ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ํต์ฌ ํ๋๋ฅผ ๋ช
์์ ์ผ๋ก ๋ค์ ์ค์ ํฉ๋๋ค.
|
| 152 |
self.backbone_name_or_path = backbone_name_or_path
|
| 153 |
+
self.mlp_head_bottleneck = int(mlp_head_bottleneck)
|
| 154 |
+
self.mlp_head_dropout = float(mlp_head_dropout)
|
| 155 |
+
|
| 156 |
+
self.label2id = label2id_norm
|
| 157 |
+
self.id2label = id2label_norm
|
| 158 |
+
self.num_labels = int(kwargs["num_labels"])
|
| 159 |
|
| 160 |
def to_dict(self):
|
| 161 |
+
# Call the parent implementation first.
|
| 162 |
+
# ๋จผ์ ๋ถ๋ชจ ๊ตฌํ์ ํธ์ถํฉ๋๋ค.
|
| 163 |
output = super().to_dict()
|
| 164 |
+
|
| 165 |
+
# Force num_labels to be present and consistent.
|
| 166 |
+
# num_labels๊ฐ ๋ฐ๋์ ์กด์ฌํ๊ณ ์ผ๊ด๋๋๋ก ๊ฐ์ ํฉ๋๋ค.
|
| 167 |
+
output["num_labels"] = int(
|
| 168 |
+
getattr(self, "num_labels", output.get("num_labels", 0))
|
| 169 |
+
)
|
| 170 |
return output
|
| 171 |
|
| 172 |
+
|
| 173 |
+
# Register this config so it can be resolved via AutoConfig.
|
| 174 |
+
# AutoConfig๋ฅผ ํตํด ์ด config๊ฐ ํด์๋ ์ ์๋๋ก ๋ฑ๋กํฉ๋๋ค.
|
| 175 |
BackboneMLPHeadConfig.register_for_auto_class("AutoConfig")
|
models/microsoft__swin-tiny-patch4-window7-224/ds_model.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
# -*- coding: utf-8 -*-
|
| 3 |
|
| 4 |
-
|
| 5 |
|
| 6 |
-
from
|
| 7 |
-
from typing import Dict, Any, Optional, Tuple, Set, List
|
| 8 |
|
| 9 |
import torch
|
| 10 |
import torch.nn as nn
|
|
@@ -12,146 +11,88 @@ import torch.nn.functional as F
|
|
| 12 |
|
| 13 |
# --- transformers core ---
|
| 14 |
from transformers.modeling_utils import PreTrainedModel
|
| 15 |
-
from transformers import AutoModel, AutoConfig
|
| 16 |
from transformers.modeling_outputs import ImageClassifierOutput
|
| 17 |
|
| 18 |
# --- torchvision ---
|
| 19 |
from torchvision import models as tv_models
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
# - ๊ธฐ์กด ds_cfg.BackboneMLPHeadConfig์ ํ๋๋ช
์ ๋ง์ถ์์ต๋๋ค.
|
| 25 |
-
# - ์ด๋ฏธ ds_cfg.py๊ฐ ์๋ค๋ฉด: ์ด ๋ธ๋ก์ ์ง์ฐ๊ณ "from ds_cfg import BackboneMLPHeadConfig"๋ก ๋ฐ๊ฟ๋ ๋ฉ๋๋ค.
|
| 26 |
-
# ============================================================
|
| 27 |
-
class BackboneMLPHeadConfig(PretrainedConfig):
|
| 28 |
-
model_type = "backbone_mlphead"
|
| 29 |
-
|
| 30 |
-
def __init__(
|
| 31 |
-
self,
|
| 32 |
-
backbone_name_or_path: Optional[str] = None,
|
| 33 |
-
num_labels: int = 2,
|
| 34 |
-
id2label: Optional[Dict[int, str]] = None,
|
| 35 |
-
label2id: Optional[Dict[str, int]] = None,
|
| 36 |
-
mlp_head_bottleneck: int = 256,
|
| 37 |
-
mlp_head_dropout: float = 0.2,
|
| 38 |
-
**kwargs,
|
| 39 |
-
):
|
| 40 |
-
super().__init__(**kwargs)
|
| 41 |
-
self.backbone_name_or_path = backbone_name_or_path
|
| 42 |
-
self.num_labels = num_labels
|
| 43 |
-
self.id2label = id2label or {i: f"LABEL_{i}" for i in range(num_labels)}
|
| 44 |
-
self.label2id = label2id or {v: k for k, v in self.id2label.items()}
|
| 45 |
-
self.mlp_head_bottleneck = mlp_head_bottleneck
|
| 46 |
-
self.mlp_head_dropout = mlp_head_dropout
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
# ============================================================
|
| 50 |
-
# (1) BACKBONE_META
|
| 51 |
-
# - ์ด๋ฏธ ds_meta.py๊ฐ ์๋ค๋ฉด: ์ด ๋ธ๋ก์ ์ง์ฐ๊ณ "from ds_meta import BACKBONE_META"๋ก ๋ฐ๊ฟ๋ ๋ฉ๋๋ค.
|
| 52 |
-
# ============================================================
|
| 53 |
-
BACKBONE_META: Dict[str, Dict[str, Any]] = {
|
| 54 |
-
# transformers
|
| 55 |
-
"google/vit-base-patch16-224": {
|
| 56 |
-
"type": "vit",
|
| 57 |
-
"feat_rule": "cls",
|
| 58 |
-
"feat_dim": 768,
|
| 59 |
-
"has_bn": False,
|
| 60 |
-
"unfreeze": "last_n",
|
| 61 |
-
},
|
| 62 |
-
"microsoft/swin-tiny-patch4-window7-224": {
|
| 63 |
-
"type": "swin",
|
| 64 |
-
"feat_rule": "pool_or_mean",
|
| 65 |
-
"feat_dim": 768,
|
| 66 |
-
"has_bn": False,
|
| 67 |
-
"unfreeze": "last_n",
|
| 68 |
-
},
|
| 69 |
-
"microsoft/resnet-50": {
|
| 70 |
-
"type": "resnet",
|
| 71 |
-
"feat_rule": "pool_or_gap",
|
| 72 |
-
"feat_dim": 2048,
|
| 73 |
-
"has_bn": True,
|
| 74 |
-
"unfreeze": "last_n",
|
| 75 |
-
},
|
| 76 |
-
"google/efficientnet-b0": {
|
| 77 |
-
"type": "efficientnet",
|
| 78 |
-
"feat_rule": "pool_or_gap",
|
| 79 |
-
"feat_dim": 1280,
|
| 80 |
-
"has_bn": True,
|
| 81 |
-
"unfreeze": "last_n",
|
| 82 |
-
},
|
| 83 |
-
|
| 84 |
-
# densenet
|
| 85 |
-
"timm/densenet121.tv_in1k": {
|
| 86 |
-
"type": "timm_densenet",
|
| 87 |
-
"feat_rule": "timm_gap",
|
| 88 |
-
"feat_dim": 1024,
|
| 89 |
-
"has_bn": True,
|
| 90 |
-
"unfreeze": "last_n",
|
| 91 |
-
},
|
| 92 |
-
"torchvision/densenet121": {
|
| 93 |
-
"type": "torchvision_densenet",
|
| 94 |
-
"feat_rule": "torchvision_densenet_gap",
|
| 95 |
-
"feat_dim": 1024,
|
| 96 |
-
"has_bn": True,
|
| 97 |
-
"unfreeze": "last_n",
|
| 98 |
-
},
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
# ============================================================
|
| 103 |
-
# (2) MLP Head
|
| 104 |
-
# - ์ด๋ฏธ mlp_head.py๊ฐ ์๋ค๋ฉด: ์ด ๋ธ๋ก์ ์ง์ฐ๊ณ "from mlp_head import MLPHead"๋ก ๋ฐ๊ฟ๋ ๋ฉ๋๋ค.
|
| 105 |
-
# ============================================================
|
| 106 |
-
class MLPHead(nn.Module):
|
| 107 |
-
def __init__(self, in_dim: int, num_labels: int, bottleneck: int = 256, p: float = 0.2):
|
| 108 |
-
super().__init__()
|
| 109 |
-
self.fc2 = nn.Linear(in_dim, bottleneck)
|
| 110 |
-
self.act2 = nn.GELU()
|
| 111 |
-
self.drop2 = nn.Dropout(p)
|
| 112 |
-
self.fc3 = nn.Linear(bottleneck, num_labels)
|
| 113 |
-
|
| 114 |
-
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 115 |
-
x = self.drop2(self.act2(self.fc2(x)))
|
| 116 |
-
return self.fc3(x)
|
| 117 |
-
|
| 118 |
|
| 119 |
# ============================================================
|
| 120 |
# (3) Model: backbone + MLP head
|
| 121 |
-
#
|
| 122 |
-
# - __init__: backbone ๊ตฌ์กฐ(skeleton)๋ง ์์ฑ. pretrained๋ฅผ ์ ๋ ๋ก๋ํ์ง ์์.
|
| 123 |
-
# - pretrained ์ฃผ์
: model.load_backbone_pretrained_()๋ฅผ "fresh-start์์๋ง" ์ง์ ํธ์ถ.
|
| 124 |
-
# - from_pretrained override ์์: HF ๊ธฐ๋ณธ ๋ก๋๊ฐ checkpoint๋ฅผ ๊ทธ๋๋ก ๋ณต์.
|
| 125 |
# ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
|
|
|
|
|
| 127 |
config_class = BackboneMLPHeadConfig
|
| 128 |
|
| 129 |
def __init__(self, config: BackboneMLPHeadConfig):
|
|
|
|
|
|
|
| 130 |
super().__init__(config)
|
| 131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
if config.backbone_name_or_path is None:
|
| 133 |
-
raise ValueError(
|
|
|
|
|
|
|
|
|
|
| 134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
self._meta = BACKBONE_META[config.backbone_name_or_path]
|
| 136 |
|
| 137 |
-
#
|
|
|
|
| 138 |
self.backbone = self._build_backbone_skeleton(config.backbone_name_or_path)
|
| 139 |
|
| 140 |
-
#
|
|
|
|
| 141 |
self.classifier = MLPHead(
|
| 142 |
in_dim=int(self._meta["feat_dim"]),
|
| 143 |
-
num_labels=config.num_labels,
|
| 144 |
-
bottleneck=config.mlp_head_bottleneck,
|
| 145 |
-
p=config.mlp_head_dropout,
|
| 146 |
)
|
| 147 |
|
| 148 |
-
# HF
|
|
|
|
| 149 |
self.post_init()
|
| 150 |
|
| 151 |
def init_weights(self):
|
| 152 |
"""
|
| 153 |
-
|
| 154 |
-
head๋ง ์ด๊ธฐํ
|
|
|
|
|
|
|
|
|
|
| 155 |
"""
|
| 156 |
if getattr(self, "classifier", None) is not None:
|
| 157 |
self.classifier.apply(self._init_weights)
|
|
@@ -159,8 +100,11 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 159 |
|
| 160 |
# ----------------------------
|
| 161 |
# backbone skeleton builders
|
|
|
|
| 162 |
# ----------------------------
|
| 163 |
def _build_backbone_skeleton(self, backbone_id: str) -> nn.Module:
|
|
|
|
|
|
|
| 164 |
meta = BACKBONE_META[backbone_id]
|
| 165 |
t = meta["type"]
|
| 166 |
|
|
@@ -170,18 +114,24 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 170 |
if t == "torchvision_densenet":
|
| 171 |
return self._build_torchvision_densenet_skeleton(backbone_id)
|
| 172 |
|
| 173 |
-
# transformers:
|
|
|
|
| 174 |
bb_cfg = AutoConfig.from_pretrained(backbone_id)
|
| 175 |
return AutoModel.from_config(bb_cfg)
|
| 176 |
|
| 177 |
@staticmethod
|
| 178 |
def _build_timm_densenet_skeleton(hf_repo_id: str) -> nn.Module:
|
|
|
|
|
|
|
| 179 |
try:
|
| 180 |
import timm
|
| 181 |
except Exception as e:
|
| 182 |
-
raise ImportError(
|
|
|
|
|
|
|
| 183 |
|
| 184 |
-
#
|
|
|
|
| 185 |
return timm.create_model(
|
| 186 |
f"hf_hub:{hf_repo_id}",
|
| 187 |
pretrained=False,
|
|
@@ -190,33 +140,33 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 190 |
|
| 191 |
@staticmethod
|
| 192 |
def _build_torchvision_densenet_skeleton(model_id: str) -> nn.Module:
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
"torchvision
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
arch = mapping[model_id]
|
| 203 |
-
fn = getattr(tv_models, arch)
|
| 204 |
-
|
| 205 |
-
# ๊ตฌ์กฐ๋ง (weights=None)
|
| 206 |
-
m = fn(weights=None).eval()
|
| 207 |
return m
|
| 208 |
|
| 209 |
# ------------------------------------------------------------
|
| 210 |
-
#
|
|
|
|
| 211 |
# ------------------------------------------------------------
|
| 212 |
@torch.no_grad()
|
| 213 |
-
def load_backbone_pretrained_(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
"""
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
model.load_backbone_pretrained_()
|
| 218 |
|
| 219 |
-
|
|
|
|
| 220 |
"""
|
| 221 |
bb = self.config.backbone_name_or_path
|
| 222 |
meta = BACKBONE_META[bb]
|
|
@@ -230,48 +180,58 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 230 |
self._load_torchvision_pretrained_into_skeleton_(bb)
|
| 231 |
return
|
| 232 |
|
| 233 |
-
# transformers pretrained
|
|
|
|
| 234 |
ref = AutoModel.from_pretrained(
|
| 235 |
bb,
|
| 236 |
low_cpu_mem_usage=low_cpu_mem_usage,
|
| 237 |
device_map=device_map,
|
| 238 |
)
|
|
|
|
|
|
|
|
|
|
| 239 |
self.backbone.load_state_dict(ref.state_dict(), strict=False)
|
| 240 |
del ref
|
| 241 |
|
| 242 |
@torch.no_grad()
|
| 243 |
def _load_timm_pretrained_into_skeleton_(self, hf_repo_id: str):
|
|
|
|
|
|
|
| 244 |
import timm
|
|
|
|
|
|
|
|
|
|
| 245 |
ref = timm.create_model(
|
| 246 |
f"hf_hub:{hf_repo_id}",
|
| 247 |
pretrained=True,
|
| 248 |
num_classes=0,
|
| 249 |
).eval()
|
|
|
|
| 250 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 251 |
del ref
|
| 252 |
|
| 253 |
@torch.no_grad()
|
| 254 |
def _load_torchvision_pretrained_into_skeleton_(self, model_id: str):
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
"torchvision
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
arch, weights = mapping[model_id]
|
| 265 |
-
fn = getattr(tv_models, arch)
|
| 266 |
-
ref = fn(weights=weights).eval()
|
| 267 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 268 |
del ref
|
| 269 |
|
| 270 |
# ----------------------------
|
| 271 |
# feature extraction
|
|
|
|
| 272 |
# ----------------------------
|
| 273 |
@staticmethod
|
| 274 |
def _pool_or_gap(outputs) -> torch.Tensor:
|
|
|
|
|
|
|
| 275 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 276 |
x = outputs.pooler_output
|
| 277 |
if x.dim() == 2:
|
|
@@ -280,29 +240,42 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 280 |
return x.flatten(1)
|
| 281 |
raise RuntimeError(f"Unexpected pooler_output shape: {tuple(x.shape)}")
|
| 282 |
|
|
|
|
|
|
|
| 283 |
x = outputs.last_hidden_state
|
| 284 |
if x.dim() == 4:
|
| 285 |
return x.mean(dim=(2, 3))
|
|
|
|
| 286 |
raise RuntimeError(
|
| 287 |
"Expected pooler_output or (B,C,H,W) last_hidden_state for CNN backbones. "
|
| 288 |
f"Got last_hidden_state shape={tuple(x.shape)}"
|
| 289 |
)
|
| 290 |
|
| 291 |
def _extract_features(self, outputs, pixel_values: Optional[torch.Tensor] = None) -> torch.Tensor:
|
|
|
|
|
|
|
| 292 |
rule = self._meta["feat_rule"]
|
| 293 |
|
| 294 |
if rule == "cls":
|
|
|
|
|
|
|
| 295 |
return outputs.last_hidden_state[:, 0, :]
|
| 296 |
|
| 297 |
if rule == "pool_or_mean":
|
|
|
|
|
|
|
| 298 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 299 |
return outputs.pooler_output
|
| 300 |
return outputs.last_hidden_state.mean(dim=1)
|
| 301 |
|
| 302 |
if rule == "pool_or_gap":
|
|
|
|
|
|
|
| 303 |
return self._pool_or_gap(outputs)
|
| 304 |
|
| 305 |
if rule == "timm_gap":
|
|
|
|
|
|
|
| 306 |
if not isinstance(outputs, torch.Tensor):
|
| 307 |
raise TypeError(f"timm_gap expects Tensor features, got {type(outputs)}")
|
| 308 |
if outputs.dim() != 4:
|
|
@@ -310,6 +283,8 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 310 |
return outputs.mean(dim=(2, 3))
|
| 311 |
|
| 312 |
if rule == "torchvision_densenet_gap":
|
|
|
|
|
|
|
| 313 |
if not isinstance(outputs, torch.Tensor):
|
| 314 |
raise TypeError(f"torchvision_densenet_gap expects Tensor, got {type(outputs)}")
|
| 315 |
if outputs.dim() != 4:
|
|
@@ -327,9 +302,13 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 327 |
return_dict=True,
|
| 328 |
**kwargs,
|
| 329 |
):
|
|
|
|
|
|
|
| 330 |
t = self._meta["type"]
|
| 331 |
|
| 332 |
if t == "timm_densenet":
|
|
|
|
|
|
|
| 333 |
if pixel_values is None:
|
| 334 |
raise ValueError("timm DenseNet backbone requires pixel_values.")
|
| 335 |
if pixel_values.dim() != 4:
|
|
@@ -341,6 +320,8 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 341 |
attentions = None
|
| 342 |
|
| 343 |
elif t == "torchvision_densenet":
|
|
|
|
|
|
|
| 344 |
if pixel_values is None:
|
| 345 |
raise ValueError("torchvision DenseNet backbone requires pixel_values.")
|
| 346 |
if pixel_values.dim() != 4:
|
|
@@ -353,6 +334,8 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 353 |
attentions = None
|
| 354 |
|
| 355 |
else:
|
|
|
|
|
|
|
| 356 |
outputs = self.backbone(
|
| 357 |
pixel_values=pixel_values,
|
| 358 |
output_attentions=output_attentions,
|
|
@@ -364,10 +347,14 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 364 |
hidden_states = getattr(outputs, "hidden_states", None)
|
| 365 |
attentions = getattr(outputs, "attentions", None)
|
| 366 |
|
|
|
|
|
|
|
| 367 |
logits = self.classifier(feats)
|
| 368 |
|
| 369 |
loss = None
|
| 370 |
if labels is not None:
|
|
|
|
|
|
|
| 371 |
loss = F.cross_entropy(logits, labels)
|
| 372 |
|
| 373 |
if not return_dict:
|
|
@@ -383,20 +370,27 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 383 |
|
| 384 |
|
| 385 |
# ============================================================
|
| 386 |
-
# Freeze / Unfreeze utilities
|
|
|
|
| 387 |
# ============================================================
|
| 388 |
def _set_requires_grad(module: nn.Module, flag: bool):
|
|
|
|
|
|
|
| 389 |
for p in module.parameters():
|
| 390 |
p.requires_grad = flag
|
| 391 |
|
| 392 |
|
| 393 |
def set_bn_eval(module: nn.Module):
|
|
|
|
|
|
|
| 394 |
for m in module.modules():
|
| 395 |
if isinstance(m, (nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d, nn.SyncBatchNorm)):
|
| 396 |
m.eval()
|
| 397 |
|
| 398 |
|
| 399 |
def freeze_backbone(model: BackboneWithMLPHeadForImageClassification, freeze_bn: bool = True):
|
|
|
|
|
|
|
| 400 |
_set_requires_grad(model.backbone, False)
|
| 401 |
_set_requires_grad(model.classifier, True)
|
| 402 |
|
|
@@ -406,6 +400,8 @@ def freeze_backbone(model: BackboneWithMLPHeadForImageClassification, freeze_bn:
|
|
| 406 |
|
| 407 |
|
| 408 |
def finetune_train_mode(model: BackboneWithMLPHeadForImageClassification, keep_bn_eval: bool = True):
|
|
|
|
|
|
|
| 409 |
model.train()
|
| 410 |
meta = BACKBONE_META[model.config.backbone_name_or_path]
|
| 411 |
if keep_bn_eval and meta.get("has_bn", False):
|
|
@@ -413,6 +409,8 @@ def finetune_train_mode(model: BackboneWithMLPHeadForImageClassification, keep_b
|
|
| 413 |
|
| 414 |
|
| 415 |
def trainable_summary(model: nn.Module):
|
|
|
|
|
|
|
| 416 |
total = sum(p.numel() for p in model.parameters())
|
| 417 |
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 418 |
ratio = trainable / total if total > 0 else 0.0
|
|
@@ -425,6 +423,8 @@ def unfreeze_last_stage(
|
|
| 425 |
last_n: int = 2,
|
| 426 |
keep_bn_eval: bool = True,
|
| 427 |
):
|
|
|
|
|
|
|
| 428 |
freeze_backbone(model, freeze_bn=keep_bn_eval)
|
| 429 |
|
| 430 |
n = int(last_n)
|
|
@@ -438,12 +438,16 @@ def unfreeze_last_stage(
|
|
| 438 |
bb_type = meta["type"]
|
| 439 |
|
| 440 |
if bb_type == "vit":
|
|
|
|
|
|
|
| 441 |
blocks = list(model.backbone.encoder.layer)
|
| 442 |
for blk in blocks[-n:]:
|
| 443 |
_set_requires_grad(blk, True)
|
| 444 |
return
|
| 445 |
|
| 446 |
if bb_type == "swin":
|
|
|
|
|
|
|
| 447 |
stages = list(model.backbone.encoder.layers)
|
| 448 |
blocks: List[nn.Module] = []
|
| 449 |
for st in stages:
|
|
@@ -453,6 +457,8 @@ def unfreeze_last_stage(
|
|
| 453 |
return
|
| 454 |
|
| 455 |
if bb_type == "resnet":
|
|
|
|
|
|
|
| 456 |
bb = model.backbone
|
| 457 |
for name in ("layer1", "layer2", "layer3", "layer4"):
|
| 458 |
if not hasattr(bb, name):
|
|
@@ -472,6 +478,8 @@ def unfreeze_last_stage(
|
|
| 472 |
return
|
| 473 |
|
| 474 |
if bb_type == "efficientnet":
|
|
|
|
|
|
|
| 475 |
bb = model.backbone
|
| 476 |
if not hasattr(bb, "features"):
|
| 477 |
raise RuntimeError("Unexpected EfficientNet structure: missing features")
|
|
@@ -488,6 +496,8 @@ def unfreeze_last_stage(
|
|
| 488 |
return
|
| 489 |
|
| 490 |
if bb_type in ("timm_densenet", "torchvision_densenet"):
|
|
|
|
|
|
|
| 491 |
bb = model.backbone
|
| 492 |
if not hasattr(bb, "features"):
|
| 493 |
raise RuntimeError("Unexpected DenseNet: missing features")
|
|
@@ -505,6 +515,8 @@ def unfreeze_last_stage(
|
|
| 505 |
raise RuntimeError(f"Unexpected DenseNet features: missing {name}")
|
| 506 |
|
| 507 |
def _denselayers(db: nn.Module) -> List[nn.Module]:
|
|
|
|
|
|
|
| 508 |
return list(db.children())
|
| 509 |
|
| 510 |
blocks: List[nn.Module] = []
|
|
@@ -526,20 +538,8 @@ def unfreeze_last_stage(
|
|
| 526 |
|
| 527 |
# -------------------------
|
| 528 |
# register
|
|
|
|
|
|
|
|
|
|
|
|
|
| 529 |
BackboneWithMLPHeadForImageClassification.register_for_auto_class("AutoModelForImageClassification")
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
# ==========================================================
|
| 533 |
-
|
| 534 |
-
# # fresh-start
|
| 535 |
-
# cfg = BackboneMLPHeadConfig(backbone_name_or_path="timm/densenet121.tv_in1k", num_labels=3)
|
| 536 |
-
# model = BackboneWithMLPHeadForImageClassification(cfg)
|
| 537 |
-
|
| 538 |
-
# # pretrained๋ฅผ ์ฐ๋ ค๋ฉด ๋ช
์์ ์ผ๋ก ํธ์ถ
|
| 539 |
-
# model.load_backbone_pretrained_()
|
| 540 |
-
|
| 541 |
-
# # ์ดํ ํ์ต
|
| 542 |
-
|
| 543 |
-
# # checkpoint์์ ์์
|
| 544 |
-
# model = BackboneWithMLPHeadForImageClassification.from_pretrained("checkpoints/xxx")
|
| 545 |
-
# # ์ฌ๊ธฐ์๋ load_backbone_pretrained_() ํธ์ถํ๋ฉด ์ ๋ฉ๋๋ค.
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
# -*- coding: utf-8 -*-
|
| 3 |
|
| 4 |
+
# src/ds_model.py
|
| 5 |
|
| 6 |
+
from typing import Optional, List
|
|
|
|
| 7 |
|
| 8 |
import torch
|
| 9 |
import torch.nn as nn
|
|
|
|
| 11 |
|
| 12 |
# --- transformers core ---
|
| 13 |
from transformers.modeling_utils import PreTrainedModel
|
| 14 |
+
from transformers import AutoModel, AutoConfig
|
| 15 |
from transformers.modeling_outputs import ImageClassifierOutput
|
| 16 |
|
| 17 |
# --- torchvision ---
|
| 18 |
from torchvision import models as tv_models
|
| 19 |
|
| 20 |
+
from ds_cfg import BackboneMLPHeadConfig
|
| 21 |
+
from ds_meta import BACKBONE_META
|
| 22 |
+
from mlp_head import MLPHead
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
# ============================================================
|
| 25 |
# (3) Model: backbone + MLP head
|
| 26 |
+
# (3) ๋ชจ๋ธ: backbone + MLP head
|
|
|
|
|
|
|
|
|
|
| 27 |
# ============================================================
|
| 28 |
+
# Design principle: __init__ builds only a skeleton and MUST NOT load pretrained weights.
|
| 29 |
+
# ์ค๊ณ ์์น: __init__์์๋ skeleton๋ง ๋ง๋ค๊ณ pretrained weight ๋ก๋๋ ์ ๋ ํ๋ฉด ์ ๋ฉ๋๋ค.
|
| 30 |
+
#
|
| 31 |
+
# Pretrained injection is allowed ONLY via an explicit call in fresh-start flows.
|
| 32 |
+
# pretrained ์ฃผ์
์ fresh-start ํ๋ฆ์์์ ๋ช
์์ ํธ์ถ๋ก๋ง ํ์ฉํฉ๋๋ค.
|
| 33 |
+
#
|
| 34 |
+
# HF from_pretrained should restore checkpoints as-is without side effects.
|
| 35 |
+
# HF from_pretrained๋ ๋ถ์์ฉ ์์ด ์ฒดํฌํฌ์ธํธ๋ฅผ ๊ทธ๋๋ก ๋ณต์ํด์ผ ํฉ๋๋ค.
|
| 36 |
class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
| 37 |
+
# This links the model to its custom config for AutoClass usage.
|
| 38 |
+
# AutoClass ์ฌ์ฉ์ ์ํด ๋ชจ๋ธ๊ณผ ์ปค์คํ
config๋ฅผ ์ฐ๊ฒฐํฉ๋๋ค.
|
| 39 |
config_class = BackboneMLPHeadConfig
|
| 40 |
|
| 41 |
def __init__(self, config: BackboneMLPHeadConfig):
|
| 42 |
+
# PreTrainedModel expects a config object and stores it internally.
|
| 43 |
+
# PreTrainedModel์ config ๊ฐ์ฒด๋ฅผ ๋ฐ์ ๋ด๋ถ์ ์ ์ฅํ๋ ์ ์ ๋ฅผ ๊ฐ์ง๋๋ค.
|
| 44 |
super().__init__(config)
|
| 45 |
|
| 46 |
+
# Fail-fast: the model is not meant to be instantiated without a valid backbone id.
|
| 47 |
+
# fail-fast: ์ ํจํ backbone id ์์ด ๋ชจ๋ธ์ ๋ง๋๋ ์ฌ์ฉ ์๋๋ฆฌ์ค๋ ํ์ฉํ์ง ์์ต๋๋ค.
|
| 48 |
+
#
|
| 49 |
+
# Note: Transformers may create configs with no args, but models are conventionally created with configs.
|
| 50 |
+
# ์ฐธ๊ณ : Transformers๋ config ๋ฌด์ธ์ ์์ฑ์ด ์์ ์ ์์ผ๋, ๋ชจ๋ธ์ ๊ด๋ก์ ์ผ๋ก config๋ฅผ ๋ฐ์ ์์ฑ๋ฉ๋๋ค.
|
| 51 |
if config.backbone_name_or_path is None:
|
| 52 |
+
raise ValueError(
|
| 53 |
+
"config.backbone_name_or_path is None. "
|
| 54 |
+
"Provide a valid backbone id (whitelist key in BACKBONE_META)."
|
| 55 |
+
)
|
| 56 |
|
| 57 |
+
# Fail-fast: training/inference requires a positive number of labels.
|
| 58 |
+
# fail-fast: ํ์ต/์ถ๋ก ์ num_labels๊ฐ ์์์ฌ์ผ ํฉ๋๋ค.
|
| 59 |
+
#
|
| 60 |
+
# Config may exist in a minimal form for internal serialization paths, but the model should not.
|
| 61 |
+
# config๋ ๋ด๋ถ ์ง๋ ฌํ ๊ฒฝ๋ก์์ ์ต์ ํํ๋ก ์กด์ฌํ ์ ์์ผ๋ ๋ชจ๋ธ์ ๊ทธ๋ฌ๋ฉด ์ ๋ฉ๋๋ค.
|
| 62 |
+
if int(getattr(config, "num_labels", 0)) <= 0:
|
| 63 |
+
raise ValueError(
|
| 64 |
+
f"config.num_labels must be > 0, got {getattr(config, 'num_labels', None)}. "
|
| 65 |
+
"Set num_labels (or id2label/label2id) when creating the config."
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
# Meta is a single source of truth for extraction and fine-tuning rules.
|
| 69 |
+
# meta๋ feature ์ถ์ถ ๋ฐ ๋ฏธ์ธ์กฐ์ ๊ท์น์ ๋จ์ผ ๊ธฐ์ค์
๋๋ค.
|
| 70 |
self._meta = BACKBONE_META[config.backbone_name_or_path]
|
| 71 |
|
| 72 |
+
# Backbone skeleton is always created without pretrained weights.
|
| 73 |
+
# backbone skeleton์ ํญ์ pretrained weight ์์ด ์์ฑํฉ๋๋ค.
|
| 74 |
self.backbone = self._build_backbone_skeleton(config.backbone_name_or_path)
|
| 75 |
|
| 76 |
+
# Head shape is driven by meta feat_dim and config.num_labels.
|
| 77 |
+
# head shape์ meta์ feat_dim๊ณผ config.num_labels๋ก ๊ฒฐ์ ๋ฉ๋๋ค.
|
| 78 |
self.classifier = MLPHead(
|
| 79 |
in_dim=int(self._meta["feat_dim"]),
|
| 80 |
+
num_labels=int(config.num_labels),
|
| 81 |
+
bottleneck=int(config.mlp_head_bottleneck),
|
| 82 |
+
p=float(config.mlp_head_dropout),
|
| 83 |
)
|
| 84 |
|
| 85 |
+
# HF initialization hook, but we override init_weights to initialize head-only.
|
| 86 |
+
# HF ์ด๊ธฐํ ํ
์ด์ง๋ง init_weights๋ฅผ overrideํ์ฌ head๋ง ์ด๊ธฐํํฉ๋๋ค.
|
| 87 |
self.post_init()
|
| 88 |
|
| 89 |
def init_weights(self):
|
| 90 |
"""
|
| 91 |
+
Initialize only the head to avoid touching the backbone skeleton.
|
| 92 |
+
backbone skeleton์ ๊ฑด๋๋ฆฌ์ง ์๊ธฐ ์ํด head๋ง ์ด๊ธฐํํฉ๋๋ค.
|
| 93 |
+
|
| 94 |
+
HF's default init may traverse the entire module tree, which is undesirable here.
|
| 95 |
+
HF ๊ธฐ๋ณธ init์ ์ ์ฒด ๋ชจ๋ ํธ๋ฆฌ๋ฅผ ์ํํ ์ ์์ด ์ฌ๊ธฐ์๋ ๋ถ์ ์ ํฉ๋๋ค.
|
| 96 |
"""
|
| 97 |
if getattr(self, "classifier", None) is not None:
|
| 98 |
self.classifier.apply(self._init_weights)
|
|
|
|
| 100 |
|
| 101 |
# ----------------------------
|
| 102 |
# backbone skeleton builders
|
| 103 |
+
# backbone skeleton ์์ฑ๊ธฐ
|
| 104 |
# ----------------------------
|
| 105 |
def _build_backbone_skeleton(self, backbone_id: str) -> nn.Module:
|
| 106 |
+
# Meta decides which loader path to use.
|
| 107 |
+
# meta๊ฐ ์ด๋ค ๋ก๋ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ ์ง ๊ฒฐ์ ํฉ๋๋ค.
|
| 108 |
meta = BACKBONE_META[backbone_id]
|
| 109 |
t = meta["type"]
|
| 110 |
|
|
|
|
| 114 |
if t == "torchvision_densenet":
|
| 115 |
return self._build_torchvision_densenet_skeleton(backbone_id)
|
| 116 |
|
| 117 |
+
# For transformers backbones: build a random-weight skeleton from config only.
|
| 118 |
+
# transformers ๋ฐฑ๋ณธ: config๋ก๋ถํฐ ๋๋ค ์ด๊ธฐํ skeleton๋ง ์์ฑํฉ๋๋ค.
|
| 119 |
bb_cfg = AutoConfig.from_pretrained(backbone_id)
|
| 120 |
return AutoModel.from_config(bb_cfg)
|
| 121 |
|
| 122 |
@staticmethod
|
| 123 |
def _build_timm_densenet_skeleton(hf_repo_id: str) -> nn.Module:
|
| 124 |
+
# timm is an optional dependency and should be imported lazily.
|
| 125 |
+
# timm์ ์ต์
์์กด์ฑ์ด๋ฏ๋ก ์ง์ฐ import ํฉ๋๋ค.
|
| 126 |
try:
|
| 127 |
import timm
|
| 128 |
except Exception as e:
|
| 129 |
+
raise ImportError(
|
| 130 |
+
"DenseNet(timm) backbone requires `timm`. Install: pip install timm"
|
| 131 |
+
) from e
|
| 132 |
|
| 133 |
+
# Build structure only (pretrained=False) and remove classifier head (num_classes=0).
|
| 134 |
+
# ๊ตฌ์กฐ๋ง ์์ฑ(pretrained=False)ํ๊ณ ๋ถ๋ฅ๊ธฐ head๋ ์ ๊ฑฐ(num_classes=0)ํฉ๋๋ค.
|
| 135 |
return timm.create_model(
|
| 136 |
f"hf_hub:{hf_repo_id}",
|
| 137 |
pretrained=False,
|
|
|
|
| 140 |
|
| 141 |
@staticmethod
|
| 142 |
def _build_torchvision_densenet_skeleton(model_id: str) -> nn.Module:
|
| 143 |
+
# This project intentionally supports only torchvision/densenet121 in the 224 whitelist.
|
| 144 |
+
# ์ด ํ๋ก์ ํธ๋ 224 ํ์ดํธ๋ฆฌ์คํธ์์ torchvision/densenet121๋ง ์๋์ ์ผ๋ก ์ง์ํฉ๋๋ค.
|
| 145 |
+
if model_id != "torchvision/densenet121":
|
| 146 |
+
raise ValueError(f"Unsupported torchvision DenseNet id (224 whitelist only): {model_id}")
|
| 147 |
+
|
| 148 |
+
# Build structure only (weights=None) to avoid implicit pretrained loading.
|
| 149 |
+
# implicit pretrained ๋ก๋๋ฅผ ํผํ๊ธฐ ์ํด ๊ตฌ์กฐ๋ง ์์ฑ(weights=None)ํฉ๋๋ค.
|
| 150 |
+
m = tv_models.densenet121(weights=None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
return m
|
| 152 |
|
| 153 |
# ------------------------------------------------------------
|
| 154 |
+
# Pretrained loading is explicit and fresh-start only
|
| 155 |
+
# pretrained ๋ก๋ฉ์ ๋ช
์์ ํธ์ถ์ด๋ฉฐ fresh-start ์ ์ฉ
|
| 156 |
# ------------------------------------------------------------
|
| 157 |
@torch.no_grad()
|
| 158 |
+
def load_backbone_pretrained_(
|
| 159 |
+
self,
|
| 160 |
+
*,
|
| 161 |
+
low_cpu_mem_usage: bool = False,
|
| 162 |
+
device_map=None,
|
| 163 |
+
):
|
| 164 |
"""
|
| 165 |
+
Fresh-start only: inject pretrained backbone weights into the skeleton.
|
| 166 |
+
fresh-start ์ ์ฉ: skeleton backbone์ pretrained ๊ฐ์ค์น๋ฅผ ์ฃผ์
ํฉ๋๋ค.
|
|
|
|
| 167 |
|
| 168 |
+
Do NOT call this after from_pretrained() because it would overwrite checkpoint weights.
|
| 169 |
+
from_pretrained() ์ดํ ํธ์ถํ๋ฉด ์ฒดํฌํฌ์ธํธ ๊ฐ์ค์น๋ฅผ ๋ฎ์ด์ฐ๋ฏ๋ก ์ ๋ ํธ์ถํ๋ฉด ์ ๋ฉ๋๋ค.
|
| 170 |
"""
|
| 171 |
bb = self.config.backbone_name_or_path
|
| 172 |
meta = BACKBONE_META[bb]
|
|
|
|
| 180 |
self._load_torchvision_pretrained_into_skeleton_(bb)
|
| 181 |
return
|
| 182 |
|
| 183 |
+
# For transformers backbones, load a reference pretrained model and copy weights into our skeleton.
|
| 184 |
+
# transformers ๋ฐฑ๋ณธ์ reference pretrained ๋ชจ๋ธ์ ๋ก๋ํ ๋ค skeleton์ ๊ฐ์ค์น๋ฅผ ๋ณต์ฌํฉ๋๋ค.
|
| 185 |
ref = AutoModel.from_pretrained(
|
| 186 |
bb,
|
| 187 |
low_cpu_mem_usage=low_cpu_mem_usage,
|
| 188 |
device_map=device_map,
|
| 189 |
)
|
| 190 |
+
|
| 191 |
+
# strict=False is used to tolerate harmless key differences across minor versions.
|
| 192 |
+
# strict=False๋ ๋ง์ด๋ ๋ฒ์ ์ฐจ์ด๋ก ์ธํ ๋ฌดํดํ ํค ์ฐจ์ด๋ฅผ ํ์ฉํ๊ธฐ ์ํด ์ฌ์ฉํฉ๋๋ค.
|
| 193 |
self.backbone.load_state_dict(ref.state_dict(), strict=False)
|
| 194 |
del ref
|
| 195 |
|
| 196 |
@torch.no_grad()
|
| 197 |
def _load_timm_pretrained_into_skeleton_(self, hf_repo_id: str):
|
| 198 |
+
# timm must be present for timm backbones.
|
| 199 |
+
# timm ๋ฐฑ๋ณธ์๋ timm ์ค์น๊ฐ ํ์ํฉ๋๋ค.
|
| 200 |
import timm
|
| 201 |
+
|
| 202 |
+
# Create a pretrained reference model and copy its weights strictly.
|
| 203 |
+
# pretrained reference ๋ชจ๋ธ์ ๋ง๋ค๊ณ ๊ฐ์ค์น๋ฅผ strictํ๊ฒ ๋ณต์ฌํฉ๋๋ค.
|
| 204 |
ref = timm.create_model(
|
| 205 |
f"hf_hub:{hf_repo_id}",
|
| 206 |
pretrained=True,
|
| 207 |
num_classes=0,
|
| 208 |
).eval()
|
| 209 |
+
|
| 210 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 211 |
del ref
|
| 212 |
|
| 213 |
@torch.no_grad()
|
| 214 |
def _load_torchvision_pretrained_into_skeleton_(self, model_id: str):
|
| 215 |
+
# This project intentionally supports only torchvision/densenet121 in the 224 whitelist.
|
| 216 |
+
# ์ด ํ๋ก์ ํธ๋ 224 ํ์ดํธ๋ฆฌ์คํธ์์ torchvision/densenet121๋ง ์๋์ ์ผ๋ก ์ง์ํฉ๋๋ค.
|
| 217 |
+
if model_id != "torchvision/densenet121":
|
| 218 |
+
raise ValueError(f"Unsupported torchvision DenseNet id (224 whitelist only): {model_id}")
|
| 219 |
+
|
| 220 |
+
# Use torchvision's default pretrained weights for densenet121.
|
| 221 |
+
# torchvision์ densenet121 ๊ธฐ๋ณธ pretrained weights๋ฅผ ์ฌ์ฉํฉ๋๋ค.
|
| 222 |
+
ref = tv_models.densenet121(weights=tv_models.DenseNet121_Weights.DEFAULT).eval()
|
| 223 |
+
|
|
|
|
|
|
|
|
|
|
| 224 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 225 |
del ref
|
| 226 |
|
| 227 |
# ----------------------------
|
| 228 |
# feature extraction
|
| 229 |
+
# feature ์ถ์ถ
|
| 230 |
# ----------------------------
|
| 231 |
@staticmethod
|
| 232 |
def _pool_or_gap(outputs) -> torch.Tensor:
|
| 233 |
+
# Some transformers vision CNNs provide pooler_output explicitly.
|
| 234 |
+
# ์ผ๋ถ transformers vision CNN์ pooler_output์ ๋ช
์์ ์ผ๋ก ์ ๊ณตํฉ๋๋ค.
|
| 235 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 236 |
x = outputs.pooler_output
|
| 237 |
if x.dim() == 2:
|
|
|
|
| 240 |
return x.flatten(1)
|
| 241 |
raise RuntimeError(f"Unexpected pooler_output shape: {tuple(x.shape)}")
|
| 242 |
|
| 243 |
+
# Otherwise we expect a CNN-style last_hidden_state=(B,C,H,W) and apply GAP.
|
| 244 |
+
# ๊ทธ๋ ์ง ์์ผ๋ฉด CNN ์คํ์ผ last_hidden_state=(B,C,H,W)๋ฅผ ๊ธฐ๋ํ๊ณ GAP์ ์ ์ฉํฉ๋๋ค.
|
| 245 |
x = outputs.last_hidden_state
|
| 246 |
if x.dim() == 4:
|
| 247 |
return x.mean(dim=(2, 3))
|
| 248 |
+
|
| 249 |
raise RuntimeError(
|
| 250 |
"Expected pooler_output or (B,C,H,W) last_hidden_state for CNN backbones. "
|
| 251 |
f"Got last_hidden_state shape={tuple(x.shape)}"
|
| 252 |
)
|
| 253 |
|
| 254 |
def _extract_features(self, outputs, pixel_values: Optional[torch.Tensor] = None) -> torch.Tensor:
|
| 255 |
+
# Feature rule is defined by BACKBONE_META and must remain stable across saves/loads.
|
| 256 |
+
# feature ๊ท์น์ BACKBONE_META๋ก ์ ์๋๋ฉฐ ์ ์ฅ/๋ก๋ ๊ฐ ์์ ์ ์ผ๋ก ์ ์ง๋์ด์ผ ํฉ๋๋ค.
|
| 257 |
rule = self._meta["feat_rule"]
|
| 258 |
|
| 259 |
if rule == "cls":
|
| 260 |
+
# ViT-style: use CLS token embedding from last_hidden_state.
|
| 261 |
+
# ViT ์คํ์ผ: last_hidden_state์์ CLS ํ ํฐ ์๋ฒ ๋ฉ์ ์ฌ์ฉํฉ๋๋ค.
|
| 262 |
return outputs.last_hidden_state[:, 0, :]
|
| 263 |
|
| 264 |
if rule == "pool_or_mean":
|
| 265 |
+
# Swin-style: prefer pooler_output if present, else mean-pool over tokens.
|
| 266 |
+
# Swin ์คํ์ผ: pooler_output์ด ์์ผ๋ฉด ์ฐ์ ์ฌ์ฉํ๊ณ , ์์ผ๋ฉด ํ ํฐ ํ๊ท ํ๋ง์ ์ฌ์ฉํฉ๋๋ค.
|
| 267 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 268 |
return outputs.pooler_output
|
| 269 |
return outputs.last_hidden_state.mean(dim=1)
|
| 270 |
|
| 271 |
if rule == "pool_or_gap":
|
| 272 |
+
# CNN-style: use pooler_output if present, else GAP over spatial dims.
|
| 273 |
+
# CNN ์คํ์ผ: pooler_output์ด ์์ผ๋ฉด ์ฌ์ฉํ๊ณ , ์์ผ๋ฉด ๊ณต๊ฐ ์ฐจ์ GAP์ ์ฌ์ฉํฉ๋๋ค.
|
| 274 |
return self._pool_or_gap(outputs)
|
| 275 |
|
| 276 |
if rule == "timm_gap":
|
| 277 |
+
# timm forward_features returns a feature map (B,C,H,W) which we GAP to (B,C).
|
| 278 |
+
# timm forward_features๋ (B,C,H,W) feature map์ ๋ฐํํ๋ฉฐ ์ด๋ฅผ GAP์ผ๋ก (B,C)๋ก ๋ง๋ญ๋๋ค.
|
| 279 |
if not isinstance(outputs, torch.Tensor):
|
| 280 |
raise TypeError(f"timm_gap expects Tensor features, got {type(outputs)}")
|
| 281 |
if outputs.dim() != 4:
|
|
|
|
| 283 |
return outputs.mean(dim=(2, 3))
|
| 284 |
|
| 285 |
if rule == "torchvision_densenet_gap":
|
| 286 |
+
# torchvision DenseNet features are feature maps (B,C,H,W) and require GAP.
|
| 287 |
+
# torchvision DenseNet features๋ (B,C,H,W) feature map์ด๋ฉฐ GAP์ด ํ์ํฉ๋๋ค.
|
| 288 |
if not isinstance(outputs, torch.Tensor):
|
| 289 |
raise TypeError(f"torchvision_densenet_gap expects Tensor, got {type(outputs)}")
|
| 290 |
if outputs.dim() != 4:
|
|
|
|
| 302 |
return_dict=True,
|
| 303 |
**kwargs,
|
| 304 |
):
|
| 305 |
+
# Type decides the backbone forward path and output format.
|
| 306 |
+
# type์ด backbone forward ๊ฒฝ๋ก ๋ฐ ์ถ๋ ฅ ํฌ๋งท์ ๊ฒฐ์ ํฉ๋๋ค.
|
| 307 |
t = self._meta["type"]
|
| 308 |
|
| 309 |
if t == "timm_densenet":
|
| 310 |
+
# timm DenseNet consumes pixel_values as a 4D tensor (B,C,H,W).
|
| 311 |
+
# timm DenseNet์ pixel_values๋ฅผ 4D ํ
์ (B,C,H,W)๋ก ๋ฐ์ต๋๋ค.
|
| 312 |
if pixel_values is None:
|
| 313 |
raise ValueError("timm DenseNet backbone requires pixel_values.")
|
| 314 |
if pixel_values.dim() != 4:
|
|
|
|
| 320 |
attentions = None
|
| 321 |
|
| 322 |
elif t == "torchvision_densenet":
|
| 323 |
+
# torchvision DenseNet consumes pixel_values as a 4D tensor (B,C,H,W).
|
| 324 |
+
# torchvision DenseNet์ pixel_values๋ฅผ 4D ํ
์ (B,C,H,W)๋ก ๋ฐ์ต๋๋ค.
|
| 325 |
if pixel_values is None:
|
| 326 |
raise ValueError("torchvision DenseNet backbone requires pixel_values.")
|
| 327 |
if pixel_values.dim() != 4:
|
|
|
|
| 334 |
attentions = None
|
| 335 |
|
| 336 |
else:
|
| 337 |
+
# Transformers vision models are called with pixel_values and return ModelOutput.
|
| 338 |
+
# transformers vision ๋ชจ๋ธ์ pixel_values๋ก ํธ์ถ๋๋ฉฐ ModelOutput์ ๋ฐํํฉ๋๋ค.
|
| 339 |
outputs = self.backbone(
|
| 340 |
pixel_values=pixel_values,
|
| 341 |
output_attentions=output_attentions,
|
|
|
|
| 347 |
hidden_states = getattr(outputs, "hidden_states", None)
|
| 348 |
attentions = getattr(outputs, "attentions", None)
|
| 349 |
|
| 350 |
+
# Classifier consumes (B, feat_dim) and returns logits (B, num_labels).
|
| 351 |
+
# classifier๋ (B, feat_dim)์ ๋ฐ์ logits (B, num_labels)๋ฅผ ๋ฐํํฉ๋๋ค.
|
| 352 |
logits = self.classifier(feats)
|
| 353 |
|
| 354 |
loss = None
|
| 355 |
if labels is not None:
|
| 356 |
+
# Cross entropy expects labels as class indices in [0, num_labels).
|
| 357 |
+
# cross entropy๋ labels๊ฐ [0, num_labels) ๋ฒ์์ class index์ด๊ธธ ๊ธฐ๋ํฉ๋๋ค.
|
| 358 |
loss = F.cross_entropy(logits, labels)
|
| 359 |
|
| 360 |
if not return_dict:
|
|
|
|
| 370 |
|
| 371 |
|
| 372 |
# ============================================================
|
| 373 |
+
# Freeze / Unfreeze utilities
|
| 374 |
+
# Freeze / Unfreeze ์ ํธ๋ฆฌํฐ
|
| 375 |
# ============================================================
|
| 376 |
def _set_requires_grad(module: nn.Module, flag: bool):
|
| 377 |
+
# Toggle requires_grad for all parameters in a module.
|
| 378 |
+
# ๋ชจ๋์ ๋ชจ๋ ํ๋ผ๋ฏธํฐ์ ๋ํด requires_grad๋ฅผ ํ ๊ธํฉ๋๋ค.
|
| 379 |
for p in module.parameters():
|
| 380 |
p.requires_grad = flag
|
| 381 |
|
| 382 |
|
| 383 |
def set_bn_eval(module: nn.Module):
|
| 384 |
+
# Put BatchNorm layers into eval mode to freeze running stats.
|
| 385 |
+
# BatchNorm ๋ ์ด์ด๋ฅผ eval ๋ชจ๋๋ก ๋์ด running stats๋ฅผ ๊ณ ์ ํฉ๋๋ค.
|
| 386 |
for m in module.modules():
|
| 387 |
if isinstance(m, (nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d, nn.SyncBatchNorm)):
|
| 388 |
m.eval()
|
| 389 |
|
| 390 |
|
| 391 |
def freeze_backbone(model: BackboneWithMLPHeadForImageClassification, freeze_bn: bool = True):
|
| 392 |
+
# Stage1: freeze backbone and train only the head.
|
| 393 |
+
# stage1: backbone์ freezeํ๊ณ head๋ง ํ์ตํฉ๋๋ค.
|
| 394 |
_set_requires_grad(model.backbone, False)
|
| 395 |
_set_requires_grad(model.classifier, True)
|
| 396 |
|
|
|
|
| 400 |
|
| 401 |
|
| 402 |
def finetune_train_mode(model: BackboneWithMLPHeadForImageClassification, keep_bn_eval: bool = True):
|
| 403 |
+
# Stage2: train mode, optionally keeping BN layers in eval for stability.
|
| 404 |
+
# stage2: train ๋ชจ๋๋ก ๋๋ ์์ ์ฑ์ ์ํด BN์ eval๋ก ์ ์งํ ์ ์์ต๋๋ค.
|
| 405 |
model.train()
|
| 406 |
meta = BACKBONE_META[model.config.backbone_name_or_path]
|
| 407 |
if keep_bn_eval and meta.get("has_bn", False):
|
|
|
|
| 409 |
|
| 410 |
|
| 411 |
def trainable_summary(model: nn.Module):
|
| 412 |
+
# Print a compact summary of trainable parameters.
|
| 413 |
+
# ํ์ต ๊ฐ๋ฅ ํ๋ผ๋ฏธํฐ ์์ฝ์ ๊ฐ๋จํ ์ถ๋ ฅํฉ๋๋ค.
|
| 414 |
total = sum(p.numel() for p in model.parameters())
|
| 415 |
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 416 |
ratio = trainable / total if total > 0 else 0.0
|
|
|
|
| 423 |
last_n: int = 2,
|
| 424 |
keep_bn_eval: bool = True,
|
| 425 |
):
|
| 426 |
+
# This utility implements BACKBONE_META['unfreeze']=="last_n" across supported backbones.
|
| 427 |
+
# ์ด ์ ํธ์ ์ง์ ๋ฐฑ๋ณธ๋ค์ ๋ํด BACKBONE_META['unfreeze']=="last_n"์ ๊ตฌํํฉ๋๋ค.
|
| 428 |
freeze_backbone(model, freeze_bn=keep_bn_eval)
|
| 429 |
|
| 430 |
n = int(last_n)
|
|
|
|
| 438 |
bb_type = meta["type"]
|
| 439 |
|
| 440 |
if bb_type == "vit":
|
| 441 |
+
# ViT blocks live under backbone.encoder.layer in the transformers implementation.
|
| 442 |
+
# ViT ๋ธ๋ก์ transformers ๊ตฌํ์์ backbone.encoder.layer ์๋์ ์์ต๋๋ค.
|
| 443 |
blocks = list(model.backbone.encoder.layer)
|
| 444 |
for blk in blocks[-n:]:
|
| 445 |
_set_requires_grad(blk, True)
|
| 446 |
return
|
| 447 |
|
| 448 |
if bb_type == "swin":
|
| 449 |
+
# Swin blocks are nested by stages and blocks; we flatten and unfreeze last n blocks.
|
| 450 |
+
# Swin ๋ธ๋ก์ stage์ block์ผ๋ก ์ค์ฒฉ๋์ด ์์ด ํผ์น ํ ๋ง์ง๋ง n๊ฐ๋ฅผ unfreeze ํฉ๋๋ค.
|
| 451 |
stages = list(model.backbone.encoder.layers)
|
| 452 |
blocks: List[nn.Module] = []
|
| 453 |
for st in stages:
|
|
|
|
| 457 |
return
|
| 458 |
|
| 459 |
if bb_type == "resnet":
|
| 460 |
+
# ResNet uses layer1..layer4 stages; we unfreeze at block granularity.
|
| 461 |
+
# ResNet์ layer1..layer4 stage๋ฅผ ์ฌ์ฉํ๋ฉฐ block ๋จ์๋ก unfreeze ํฉ๋๋ค.
|
| 462 |
bb = model.backbone
|
| 463 |
for name in ("layer1", "layer2", "layer3", "layer4"):
|
| 464 |
if not hasattr(bb, name):
|
|
|
|
| 478 |
return
|
| 479 |
|
| 480 |
if bb_type == "efficientnet":
|
| 481 |
+
# EfficientNet in transformers exposes features; we unfreeze from the tail blocks.
|
| 482 |
+
# transformers EfficientNet์ features๋ฅผ ๋
ธ์ถํ๋ฉฐ ๋ค์ชฝ ๋ธ๋ก๋ถํฐ unfreeze ํฉ๋๋ค.
|
| 483 |
bb = model.backbone
|
| 484 |
if not hasattr(bb, "features"):
|
| 485 |
raise RuntimeError("Unexpected EfficientNet structure: missing features")
|
|
|
|
| 496 |
return
|
| 497 |
|
| 498 |
if bb_type in ("timm_densenet", "torchvision_densenet"):
|
| 499 |
+
# DenseNet exposes a .features module with named blocks; we unfreeze last n submodules.
|
| 500 |
+
# DenseNet์ .features ๋ชจ๋์ ๋ธ๋ก๋ค์ด ์ด๋ฆ์ผ๋ก ์กด์ฌํ๋ฉฐ ๋ง์ง๋ง n๊ฐ ์๋ธ๋ชจ๋์ unfreeze ํฉ๋๋ค.
|
| 501 |
bb = model.backbone
|
| 502 |
if not hasattr(bb, "features"):
|
| 503 |
raise RuntimeError("Unexpected DenseNet: missing features")
|
|
|
|
| 515 |
raise RuntimeError(f"Unexpected DenseNet features: missing {name}")
|
| 516 |
|
| 517 |
def _denselayers(db: nn.Module) -> List[nn.Module]:
|
| 518 |
+
# Dense blocks contain multiple DenseLayer children; we return them for fine-grained unfreezing.
|
| 519 |
+
# denseblock์ DenseLayer ์์๋ค์ ๊ฐ์ง๋ฏ๋ก ์ธ๋ฐํ unfreeze๋ฅผ ์ํด ์ด๋ฅผ ๋ฐํํฉ๋๋ค.
|
| 520 |
return list(db.children())
|
| 521 |
|
| 522 |
blocks: List[nn.Module] = []
|
|
|
|
| 538 |
|
| 539 |
# -------------------------
|
| 540 |
# register
|
| 541 |
+
# register
|
| 542 |
+
# -------------------------
|
| 543 |
+
# Register for AutoModelForImageClassification so from_pretrained can resolve this custom class.
|
| 544 |
+
# from_pretrained๊ฐ ์ด ์ปค์คํ
ํด๋์ค๋ฅผ ํด์ํ ์ ์๋๋ก AutoModelForImageClassification์ ๋ฑ๋กํฉ๋๋ค.
|
| 545 |
BackboneWithMLPHeadForImageClassification.register_for_auto_class("AutoModelForImageClassification")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
models/microsoft__swin-tiny-patch4-window7-224/ds_proc.py
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# src/ds_proc.py
|
| 5 |
+
|
| 6 |
+
# ============================================================
|
| 7 |
+
# (4) ImageProcessor (AutoImageProcessor integration)
|
| 8 |
+
# (4) ImageProcessor (AutoImageProcessor ์ฐ๋)
|
| 9 |
+
# ============================================================
|
| 10 |
+
|
| 11 |
+
from typing import Any
|
| 12 |
+
|
| 13 |
+
import numpy as np
|
| 14 |
+
import torch
|
| 15 |
+
from transformers import AutoImageProcessor, AutoConfig
|
| 16 |
+
from transformers.image_processing_base import ImageProcessingMixin
|
| 17 |
+
from transformers.utils.generic import TensorType
|
| 18 |
+
|
| 19 |
+
from ds_meta import BackboneID, BACKBONE_META
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class BackboneMLPHead224ImageProcessor(ImageProcessingMixin):
|
| 23 |
+
"""
|
| 24 |
+
This processor performs image preprocessing and outputs {"pixel_values": ...}.
|
| 25 |
+
์ด processor๋ ์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ๋ฅผ ์ํํ๊ณ {"pixel_values": ...}๋ฅผ ์ถ๋ ฅํฉ๋๋ค.
|
| 26 |
+
|
| 27 |
+
Key requirements:
|
| 28 |
+
ํต์ฌ ์๊ตฌ์ฌํญ:
|
| 29 |
+
|
| 30 |
+
1) save_pretrained() must produce a JSON-serializable preprocessor_config.json.
|
| 31 |
+
save_pretrained()๋ JSON ์ง๋ ฌํ ๊ฐ๋ฅํ preprocessor_config.json์ ์์ฑํด์ผ ํฉ๋๋ค.
|
| 32 |
+
|
| 33 |
+
2) Runtime-only objects (delegate processor, timm/torchvision transforms) must NOT be serialized.
|
| 34 |
+
๋ฐํ์ ๊ฐ์ฒด(delegate processor, timm/torchvision transform)๋ ์ ๋ ์ง๋ ฌํํ๋ฉด ์ ๋ฉ๋๋ค.
|
| 35 |
+
|
| 36 |
+
3) Runtime objects are rebuilt at init/load time based on backbone meta.
|
| 37 |
+
๋ฐํ์ ๊ฐ์ฒด๋ backbone meta์ ๋ฐ๋ผ init/load ์์ ์ ์ฌ๊ตฌ์ฑ๋ฉ๋๋ค.
|
| 38 |
+
"""
|
| 39 |
+
|
| 40 |
+
# HF vision models conventionally expect "pixel_values" as the primary input key.
|
| 41 |
+
# HF vision ๋ชจ๋ธ์ ๊ด๋ก์ ์ผ๋ก ์
๋ ฅ ํค๋ก "pixel_values"๋ฅผ ๊ธฐ๋ํฉ๋๋ค.
|
| 42 |
+
model_input_names = ["pixel_values"]
|
| 43 |
+
|
| 44 |
+
def __init__(self, backbone_name_or_path: BackboneID, is_training: bool = False, **kwargs):
|
| 45 |
+
# ImageProcessingMixin stores extra kwargs and manages auto_map metadata.
|
| 46 |
+
# ImageProcessingMixin์ ์ถ๊ฐ kwargs๋ฅผ ์ ์ฅํ๊ณ auto_map ๋ฉํ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.
|
| 47 |
+
super().__init__(**kwargs)
|
| 48 |
+
|
| 49 |
+
# Enforce whitelist via BACKBONE_META to keep behavior stable.
|
| 50 |
+
# ๋์ ์์ ์ฑ์ ์ํด BACKBONE_META ๊ธฐ๋ฐ ํ์ดํธ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ ํฉ๋๋ค.
|
| 51 |
+
if backbone_name_or_path not in BACKBONE_META:
|
| 52 |
+
raise ValueError(
|
| 53 |
+
f"Unsupported backbone_name_or_path={backbone_name_or_path}. "
|
| 54 |
+
f"Allowed: {sorted(BACKBONE_META.keys())}"
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
# Serializable fields only: these should appear in preprocessor_config.json.
|
| 58 |
+
# ์ง๋ ฌํ ๊ฐ๋ฅํ ํ๋๋ง: ์ด ๊ฐ๋ค๋ง preprocessor_config.json์ ๋ค์ด๊ฐ์ผ ํฉ๋๋ค.
|
| 59 |
+
self.backbone_name_or_path = backbone_name_or_path
|
| 60 |
+
self.is_training = bool(is_training)
|
| 61 |
+
|
| 62 |
+
# Runtime-only fields: must never be serialized.
|
| 63 |
+
# ๋ฐํ์ ์ ์ฉ ํ๋: ์ ๋ ์ง๋ ฌํ๋๋ฉด ์ ๋ฉ๋๋ค.
|
| 64 |
+
self._meta = None
|
| 65 |
+
self._delegate = None
|
| 66 |
+
self._timm_transform = None
|
| 67 |
+
self._torchvision_transform = None
|
| 68 |
+
|
| 69 |
+
# Build runtime objects according to backbone type.
|
| 70 |
+
# backbone type์ ๋ฐ๋ผ ๋ฐํ์ ๊ฐ์ฒด๋ฅผ ๊ตฌ์ฑํฉ๋๋ค.
|
| 71 |
+
self._build_runtime()
|
| 72 |
+
|
| 73 |
+
# ============================================================
|
| 74 |
+
# Runtime builders
|
| 75 |
+
# ๋ฐํ์ ๋น๋
|
| 76 |
+
# ============================================================
|
| 77 |
+
def _build_runtime(self):
|
| 78 |
+
"""
|
| 79 |
+
Build runtime delegate/transform based on BACKBONE_META["type"].
|
| 80 |
+
BACKBONE_META["type"]์ ๋ฐ๋ผ ๋ฐํ์ delegate/transform์ ๊ตฌ์ฑํฉ๋๋ค.
|
| 81 |
+
"""
|
| 82 |
+
meta = BACKBONE_META[self.backbone_name_or_path]
|
| 83 |
+
self._meta = meta
|
| 84 |
+
|
| 85 |
+
# Always reset runtime fields before rebuilding.
|
| 86 |
+
# ์ฌ๊ตฌ์ฑ ์ ๋ฐํ์ ํ๋๋ ํญ์ ์ด๊ธฐํํฉ๋๋ค.
|
| 87 |
+
self._delegate = None
|
| 88 |
+
self._timm_transform = None
|
| 89 |
+
self._torchvision_transform = None
|
| 90 |
+
|
| 91 |
+
t = meta["type"]
|
| 92 |
+
|
| 93 |
+
if t == "timm_densenet":
|
| 94 |
+
# timm DenseNet uses timm.data transforms for ImageNet-style preprocessing.
|
| 95 |
+
# timm DenseNet์ ImageNet ์ ์ฒ๋ฆฌ๋ฅผ ์ํด timm.data transform์ ์ฌ์ฉํฉ๋๋ค.
|
| 96 |
+
self._timm_transform = self._build_timm_transform(
|
| 97 |
+
backbone_id=self.backbone_name_or_path,
|
| 98 |
+
is_training=self.is_training,
|
| 99 |
+
)
|
| 100 |
+
return
|
| 101 |
+
|
| 102 |
+
if t == "torchvision_densenet":
|
| 103 |
+
# torchvision DenseNet requires torchvision-style preprocessing (resize/crop/tensor/normalize).
|
| 104 |
+
# torchvision DenseNet์ torchvision ์คํ์ผ ์ ์ฒ๋ฆฌ(resize/crop/tensor/normalize)๊ฐ ํ์ํฉ๋๋ค.
|
| 105 |
+
self._torchvision_transform = self._build_torchvision_densenet_transform(
|
| 106 |
+
is_training=self.is_training
|
| 107 |
+
)
|
| 108 |
+
return
|
| 109 |
+
|
| 110 |
+
# Default: transformers backbone delegates to its official AutoImageProcessor.
|
| 111 |
+
# ๊ธฐ๋ณธ: transformers ๋ฐฑ๋ณธ์ ๊ณต์ AutoImageProcessor์ ์์ํฉ๋๋ค.
|
| 112 |
+
self._delegate = AutoImageProcessor.from_pretrained(self.backbone_name_or_path)
|
| 113 |
+
|
| 114 |
+
@staticmethod
|
| 115 |
+
def _build_timm_transform(*, backbone_id: str, is_training: bool):
|
| 116 |
+
"""
|
| 117 |
+
Create timm transform without storing non-serializable objects in config.
|
| 118 |
+
๋น์ง๋ ฌํ ๊ฐ์ฒด๋ฅผ config์ ์ ์ฅํ์ง ์๊ณ timm transform์ ์์ฑํฉ๋๋ค.
|
| 119 |
+
"""
|
| 120 |
+
try:
|
| 121 |
+
import timm
|
| 122 |
+
from timm.data import resolve_model_data_config, create_transform
|
| 123 |
+
except Exception as e:
|
| 124 |
+
raise ImportError(
|
| 125 |
+
"timm backbone processor requires `timm`. Install: pip install timm"
|
| 126 |
+
) from e
|
| 127 |
+
|
| 128 |
+
# We only need model metadata to resolve data config, so pretrained=False is preferred.
|
| 129 |
+
# data config ์ถ์ถ๋ง ํ์ํ๋ฏ๋ก pretrained=False๋ฅผ ์ฐ์ ์ฌ์ฉํฉ๋๋ค.
|
| 130 |
+
m = timm.create_model(f"hf_hub:{backbone_id}", pretrained=False, num_classes=0)
|
| 131 |
+
dc = resolve_model_data_config(m)
|
| 132 |
+
|
| 133 |
+
# create_transform returns a torchvision-like callable that maps PIL -> torch.Tensor(C,H,W).
|
| 134 |
+
# create_transform์ PIL -> torch.Tensor(C,H,W)๋ก ๋งคํํ๋ callable์ ๋ฐํํฉ๋๋ค.
|
| 135 |
+
tfm = create_transform(**dc, is_training=is_training)
|
| 136 |
+
return tfm
|
| 137 |
+
|
| 138 |
+
@staticmethod
|
| 139 |
+
def _build_torchvision_densenet_transform(*, is_training: bool):
|
| 140 |
+
"""
|
| 141 |
+
Build torchvision preprocessing for DenseNet-121 (224 pipeline).
|
| 142 |
+
DenseNet-121์ฉ torchvision ์ ์ฒ๋ฆฌ(224 ํ์ดํ๋ผ์ธ)๋ฅผ ๊ตฌ์ฑํฉ๋๋ค.
|
| 143 |
+
"""
|
| 144 |
+
try:
|
| 145 |
+
from torchvision import transforms
|
| 146 |
+
except Exception as e:
|
| 147 |
+
raise ImportError(
|
| 148 |
+
"torchvision DenseNet processor requires `torchvision`. Install: pip install torchvision"
|
| 149 |
+
) from e
|
| 150 |
+
|
| 151 |
+
# These are the standard ImageNet normalization stats used by torchvision weights.
|
| 152 |
+
# ์ด ๊ฐ๋ค์ torchvision weights๊ฐ ์ฌ์ฉํ๋ ํ์ค ImageNet ์ ๊ทํ ํต๊ณ์
๋๋ค.
|
| 153 |
+
mean = (0.485, 0.456, 0.406)
|
| 154 |
+
std = (0.229, 0.224, 0.225)
|
| 155 |
+
|
| 156 |
+
# Training pipeline typically uses RandomResizedCrop and horizontal flip.
|
| 157 |
+
# ํ์ต ํ์ดํ๋ผ์ธ์ ๋ณดํต RandomResizedCrop๊ณผ ์ข์ฐ๋ฐ์ ์ ์ฌ์ฉํฉ๋๋ค.
|
| 158 |
+
if is_training:
|
| 159 |
+
return transforms.Compose(
|
| 160 |
+
[
|
| 161 |
+
transforms.RandomResizedCrop(224),
|
| 162 |
+
transforms.RandomHorizontalFlip(p=0.5),
|
| 163 |
+
transforms.ToTensor(),
|
| 164 |
+
transforms.Normalize(mean=mean, std=std),
|
| 165 |
+
]
|
| 166 |
+
)
|
| 167 |
+
|
| 168 |
+
# Inference pipeline typically uses Resize(256) + CenterCrop(224).
|
| 169 |
+
# ์ถ๋ก ํ์ดํ๋ผ์ธ์ ๋ณดํต Resize(256) + CenterCrop(224)๋ฅผ ์ฌ์ฉํฉ๋๋ค.
|
| 170 |
+
return transforms.Compose(
|
| 171 |
+
[
|
| 172 |
+
transforms.Resize(256),
|
| 173 |
+
transforms.CenterCrop(224),
|
| 174 |
+
transforms.ToTensor(),
|
| 175 |
+
transforms.Normalize(mean=mean, std=std),
|
| 176 |
+
]
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
# ============================================================
|
| 180 |
+
# Serialization
|
| 181 |
+
# ์ง๋ ฌํ
|
| 182 |
+
# ============================================================
|
| 183 |
+
def to_dict(self) -> dict[str, Any]:
|
| 184 |
+
"""
|
| 185 |
+
Return a JSON-serializable dict for preprocessor_config.json.
|
| 186 |
+
preprocessor_config.json์ ๋ค์ด๊ฐ JSON ์ง๋ ฌํ dict๋ฅผ ๋ฐํํฉ๋๋ค.
|
| 187 |
+
|
| 188 |
+
Important: do not leak runtime objects into the serialized dict.
|
| 189 |
+
์ค์: ๋ฐํ์ ๊ฐ์ฒด๊ฐ ์ง๋ ฌํ dict์ ์์ด๋ฉด ์ ๋ฉ๋๋ค.
|
| 190 |
+
"""
|
| 191 |
+
# ImageProcessingMixin.to_dict() adds metadata such as image_processor_type/auto_map.
|
| 192 |
+
# ImageProcessingMixin.to_dict()๋ image_processor_type/auto_map ๊ฐ์ ๋ฉํ๋ฅผ ์ถ๊ฐํฉ๋๋ค.
|
| 193 |
+
d = super().to_dict()
|
| 194 |
+
|
| 195 |
+
# Force minimal stable fields for long-term compatibility.
|
| 196 |
+
# ์ฅ๊ธฐ ํธํ์ ์ํด ์ต์ ์์ ํ๋๋ฅผ ๊ฐ์ ํฉ๋๋ค.
|
| 197 |
+
d["image_processor_type"] = self.__class__.__name__
|
| 198 |
+
d["backbone_name_or_path"] = self.backbone_name_or_path
|
| 199 |
+
d["is_training"] = self.is_training
|
| 200 |
+
|
| 201 |
+
# Remove any runtime-only fields defensively.
|
| 202 |
+
# ๋ฐํ์ ์ ์ฉ ํ๋๋ ๋ณด์์ ์ผ๋ก ์ ๊ฑฐํฉ๋๋ค.
|
| 203 |
+
for key in ["_meta", "_delegate", "_timm_transform", "_torchvision_transform"]:
|
| 204 |
+
d.pop(key, None)
|
| 205 |
+
|
| 206 |
+
return d
|
| 207 |
+
|
| 208 |
+
@classmethod
|
| 209 |
+
def from_dict(cls, image_processor_dict: dict[str, Any], **kwargs):
|
| 210 |
+
"""
|
| 211 |
+
Standard load path used by BaseImageProcessor / AutoImageProcessor.
|
| 212 |
+
BaseImageProcessor / AutoImageProcessor๊ฐ ์ฌ์ฉํ๋ ํ์ค ๋ก๋ ๊ฒฝ๋ก์
๋๋ค.
|
| 213 |
+
"""
|
| 214 |
+
backbone = image_processor_dict.get("backbone_name_or_path", None)
|
| 215 |
+
if backbone is None:
|
| 216 |
+
raise ValueError("preprocessor_config.json missing key: backbone_name_or_path")
|
| 217 |
+
|
| 218 |
+
is_training = bool(image_processor_dict.get("is_training", False))
|
| 219 |
+
return cls(backbone_name_or_path=backbone, is_training=is_training, **kwargs)
|
| 220 |
+
|
| 221 |
+
@classmethod
|
| 222 |
+
def from_pretrained(cls, pretrained_model_name_or_path: str, **kwargs):
|
| 223 |
+
"""
|
| 224 |
+
Fallback path if AutoImageProcessor calls class.from_pretrained directly.
|
| 225 |
+
AutoImageProcessor๊ฐ class.from_pretrained๋ฅผ ์ง์ ํธ์ถํ๋ ๊ฒฝ์ฐ๋ฅผ ๋๋นํ ๊ฒฝ๋ก์
๋๋ค.
|
| 226 |
+
|
| 227 |
+
Strategy:
|
| 228 |
+
์ ๋ต:
|
| 229 |
+
|
| 230 |
+
- Read config.json via AutoConfig and recover backbone_name_or_path.
|
| 231 |
+
AutoConfig๋ก config.json์ ์ฝ๊ณ backbone_name_or_path๋ฅผ ๋ณต๊ตฌํฉ๋๋ค.
|
| 232 |
+
"""
|
| 233 |
+
cfg = AutoConfig.from_pretrained(pretrained_model_name_or_path, **kwargs)
|
| 234 |
+
backbone = getattr(cfg, "backbone_name_or_path", None)
|
| 235 |
+
if backbone is None:
|
| 236 |
+
raise ValueError("Cannot build processor: backbone_name_or_path not found in config.json")
|
| 237 |
+
|
| 238 |
+
# is_training is runtime-only and should default to False for inference/serving.
|
| 239 |
+
# is_training์ ๋ฐํ์ ์ ์ฉ์ด๋ฉฐ ์ถ๋ก /์๋น ๊ธฐ๋ณธ๊ฐ์ False๊ฐ ๋ง์ต๋๋ค.
|
| 240 |
+
return cls(backbone_name_or_path=backbone, is_training=False)
|
| 241 |
+
|
| 242 |
+
# ============================================================
|
| 243 |
+
# Call interface
|
| 244 |
+
# ํธ์ถ ์ธํฐํ์ด์ค
|
| 245 |
+
# ============================================================
|
| 246 |
+
@staticmethod
|
| 247 |
+
def _ensure_list(images: Any) -> list[Any]:
|
| 248 |
+
# Normalize scalar image input to a list for uniform processing.
|
| 249 |
+
# ๋จ์ผ ์
๋ ฅ์ ๋ฆฌ์คํธ๋ก ์ ๊ทํํ์ฌ ๋์ผํ ์ฒ๋ฆฌ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํฉ๋๋ค.
|
| 250 |
+
if isinstance(images, (list, tuple)):
|
| 251 |
+
return list(images)
|
| 252 |
+
return [images]
|
| 253 |
+
|
| 254 |
+
@staticmethod
|
| 255 |
+
def _to_pil_rgb(x: Any):
|
| 256 |
+
# Convert common image inputs into PIL RGB images.
|
| 257 |
+
# ์ผ๋ฐ์ ์ธ ์
๋ ฅ์ PIL RGB ์ด๋ฏธ์ง๋ก ๋ณํํฉ๋๋ค.
|
| 258 |
+
from PIL import Image as PILImage
|
| 259 |
+
|
| 260 |
+
if isinstance(x, PILImage.Image):
|
| 261 |
+
return x.convert("RGB")
|
| 262 |
+
if isinstance(x, np.ndarray) and x.ndim == 3:
|
| 263 |
+
return PILImage.fromarray(x).convert("RGB")
|
| 264 |
+
raise TypeError(f"Unsupported image type: {type(x)}")
|
| 265 |
+
|
| 266 |
+
def __call__(
|
| 267 |
+
self,
|
| 268 |
+
images: Any | list[Any],
|
| 269 |
+
return_tensors: str | TensorType | None = "pt",
|
| 270 |
+
**kwargs,
|
| 271 |
+
) -> dict[str, Any]:
|
| 272 |
+
"""
|
| 273 |
+
Convert images into {"pixel_values": Tensor/ndarray}.
|
| 274 |
+
์ด๋ฏธ์ง๋ฅผ {"pixel_values": Tensor/ndarray}๋ก ๋ณํํฉ๋๋ค.
|
| 275 |
+
"""
|
| 276 |
+
images = self._ensure_list(images)
|
| 277 |
+
|
| 278 |
+
# Rebuild runtime if needed (e.g., right after deserialization).
|
| 279 |
+
# ์ง๋ ฌํ ๋ณต์ ์งํ ๋ฑ ๋ฐํ์์ด ๋น์ด์์ ์ ์์ผ๋ฏ๋ก ์ฌ๊ตฌ์ฑํฉ๋๋ค.
|
| 280 |
+
if (self._delegate is None) and (self._timm_transform is None) and (self._torchvision_transform is None):
|
| 281 |
+
self._build_runtime()
|
| 282 |
+
|
| 283 |
+
# timm path: PIL -> torch.Tensor(C,H,W) normalized float32.
|
| 284 |
+
# timm ๊ฒฝ๋ก: PIL -> torch.Tensor(C,H,W) ์ ๊ทํ float32์
๋๋ค.
|
| 285 |
+
if self._timm_transform is not None:
|
| 286 |
+
pv: list[torch.Tensor] = []
|
| 287 |
+
for im in images:
|
| 288 |
+
pil = self._to_pil_rgb(im)
|
| 289 |
+
t = self._timm_transform(pil)
|
| 290 |
+
if not isinstance(t, torch.Tensor):
|
| 291 |
+
raise RuntimeError("Unexpected timm transform output (expected torch.Tensor).")
|
| 292 |
+
pv.append(t)
|
| 293 |
+
pixel_values = torch.stack(pv, dim=0) # (B,C,H,W)
|
| 294 |
+
return self._format_return(pixel_values, return_tensors)
|
| 295 |
+
|
| 296 |
+
# torchvision path: PIL -> torch.Tensor(C,H,W) normalized float32.
|
| 297 |
+
# torchvision ๊ฒฝ๋ก: PIL -> torch.Tensor(C,H,W) ์ ๊ทํ float32์
๋๋ค.
|
| 298 |
+
if self._torchvision_transform is not None:
|
| 299 |
+
pv: list[torch.Tensor] = []
|
| 300 |
+
for im in images:
|
| 301 |
+
pil = self._to_pil_rgb(im)
|
| 302 |
+
t = self._torchvision_transform(pil)
|
| 303 |
+
if not isinstance(t, torch.Tensor):
|
| 304 |
+
raise RuntimeError("Unexpected torchvision transform output (expected torch.Tensor).")
|
| 305 |
+
pv.append(t)
|
| 306 |
+
pixel_values = torch.stack(pv, dim=0) # (B,C,H,W)
|
| 307 |
+
return self._format_return(pixel_values, return_tensors)
|
| 308 |
+
|
| 309 |
+
# transformers delegate path: rely on official processor behavior.
|
| 310 |
+
# transformers ์์ ๊ฒฝ๋ก: ๊ณต์ processor ๋์์ ๊ทธ๋๋ก ์ฌ์ฉํฉ๋๋ค.
|
| 311 |
+
if self._delegate is None:
|
| 312 |
+
raise RuntimeError("Processor runtime not built: delegate is None and no transforms are available.")
|
| 313 |
+
|
| 314 |
+
return self._delegate(images, return_tensors=return_tensors, **kwargs)
|
| 315 |
+
|
| 316 |
+
@staticmethod
|
| 317 |
+
def _format_return(pixel_values: torch.Tensor, return_tensors: str | TensorType | None) -> dict[str, Any]:
|
| 318 |
+
"""
|
| 319 |
+
Format pixel_values according to return_tensors.
|
| 320 |
+
return_tensors์ ๋ง์ถฐ pixel_values ๋ฐํ ํฌ๋งท์ ๋ง์ถฅ๋๋ค.
|
| 321 |
+
"""
|
| 322 |
+
if return_tensors is None or return_tensors in ("pt", TensorType.PYTORCH):
|
| 323 |
+
return {"pixel_values": pixel_values}
|
| 324 |
+
if return_tensors in ("np", TensorType.NUMPY):
|
| 325 |
+
return {"pixel_values": pixel_values.detach().cpu().numpy()}
|
| 326 |
+
raise ValueError(f"Unsupported return_tensors={return_tensors}. Use 'pt' or 'np'.")
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
# Register this processor for AutoImageProcessor resolution.
|
| 330 |
+
# AutoImageProcessor ํด์์ ์ํด ์ด processor๋ฅผ ๋ฑ๋กํฉ๋๋ค.
|
| 331 |
+
if __name__ != "__main__":
|
| 332 |
+
BackboneMLPHead224ImageProcessor.register_for_auto_class("AutoImageProcessor")
|
models/microsoft__swin-tiny-patch4-window7-224/model.safetensors
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 111128348
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:61d4b000d39bc1301116050226ee05a3cc3851359f4dc38febbb5f397fbb844f
|
| 3 |
size 111128348
|
models/microsoft__swin-tiny-patch4-window7-224/preprocessor_config.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"auto_map": {
|
| 3 |
+
"AutoImageProcessor": "ds_proc.BackboneMLPHead224ImageProcessor"
|
| 4 |
+
},
|
| 5 |
+
"backbone_name_or_path": "microsoft/swin-tiny-patch4-window7-224",
|
| 6 |
+
"image_processor_type": "BackboneMLPHead224ImageProcessor",
|
| 7 |
+
"is_training": false
|
| 8 |
+
}
|
models/timm__densenet121.tv_in1k/config.json
CHANGED
|
@@ -24,10 +24,11 @@
|
|
| 24 |
"num_labels": 3,
|
| 25 |
"transformers_version": "5.1.0",
|
| 26 |
"ds_provenance": {
|
| 27 |
-
"created_at": "
|
| 28 |
"repo_id": "dsaint31/bb_mlp_224",
|
| 29 |
"subdir": "models/timm__densenet121.tv_in1k",
|
| 30 |
"wrapper_class": "BackboneWithMLPHeadForImageClassification",
|
|
|
|
| 31 |
"backbone_id": "timm/densenet121.tv_in1k",
|
| 32 |
"pretrained_injected": true,
|
| 33 |
"policy_init_pretrained_forbidden": true,
|
|
|
|
| 24 |
"num_labels": 3,
|
| 25 |
"transformers_version": "5.1.0",
|
| 26 |
"ds_provenance": {
|
| 27 |
+
"created_at": "20260210_111913",
|
| 28 |
"repo_id": "dsaint31/bb_mlp_224",
|
| 29 |
"subdir": "models/timm__densenet121.tv_in1k",
|
| 30 |
"wrapper_class": "BackboneWithMLPHeadForImageClassification",
|
| 31 |
+
"processor_class": "BackboneMLPHead224ImageProcessor",
|
| 32 |
"backbone_id": "timm/densenet121.tv_in1k",
|
| 33 |
"pretrained_injected": true,
|
| 34 |
"policy_init_pretrained_forbidden": true,
|
models/timm__densenet121.tv_in1k/ds_cfg.py
CHANGED
|
@@ -1,84 +1,175 @@
|
|
| 1 |
-
from typing import Literal, Union
|
| 2 |
-
|
| 3 |
-
from transformers import AutoConfig
|
| 4 |
from transformers import PretrainedConfig
|
|
|
|
| 5 |
from ds_meta import BackboneID, BACKBONE_META
|
| 6 |
|
| 7 |
-
|
| 8 |
-
# 3) Config
|
| 9 |
-
# ============================================================
|
| 10 |
class BackboneMLPHeadConfig(PretrainedConfig):
|
| 11 |
"""
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
----------
|
| 16 |
-
- transformers ๋ด๋ถ ๊ฒฝ๋ก์์ "๋ฌด์ธ์ ์์ฑ"์ด ๋ฐ์ํ ์ ์์ผ๋ฏ๋ก,
|
| 17 |
-
backbone_name_or_path=None์ธ ๊ฒฝ์ฐ๋ ๊ฒ์ฆ ์์ด ๊ธฐ๋ณธ๊ฐ๋ง ์ธํ
ํ๊ณ return
|
| 18 |
-
ํ๋ ๊ตฌํ์ด ๋ฐ๋์ ํ์ํจ.
|
| 19 |
-
- ๋จ, ์ค์ ์ฌ์ฉ์์๋ backbone_name_or_path๋ฅผ ๋ฐ๋์ ์ง์ ํด์ ์์ฑํด์ผ ํจ.
|
| 20 |
"""
|
| 21 |
|
| 22 |
-
#
|
|
|
|
| 23 |
model_type = "backbone-mlphead-224-fixed"
|
| 24 |
|
| 25 |
-
# ์์ฑ์์์ ๋ชจ๋ ์ธ์์ ๊ธฐ๋ณธ๊ฐ ๋ถ์ฌ ๋ฐ๋์ ํ์!
|
| 26 |
def __init__(
|
| 27 |
self,
|
| 28 |
backbone_name_or_path: BackboneID | None = None,
|
| 29 |
mlp_head_bottleneck: int = 256,
|
| 30 |
mlp_head_dropout: float = 0.2,
|
| 31 |
-
label2id: dict[str, int]|None = None,
|
| 32 |
-
id2label: dict[int, str]|None = None,
|
| 33 |
**kwargs,
|
| 34 |
):
|
| 35 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
if backbone_name_or_path is None:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
super().__init__(**kwargs)
|
|
|
|
|
|
|
|
|
|
| 38 |
self.backbone_name_or_path = None
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
#
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
return
|
| 44 |
|
| 45 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
if backbone_name_or_path not in BACKBONE_META:
|
| 47 |
raise ValueError(
|
| 48 |
f"Unsupported backbone_name_or_path={backbone_name_or_path}. "
|
| 49 |
f"Allowed: {sorted(BACKBONE_META.keys())}"
|
| 50 |
)
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
#
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
kwargs["num_labels"] = num_labels
|
| 62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
super().__init__(
|
| 64 |
-
label2id=
|
| 65 |
-
id2label=
|
| 66 |
**kwargs,
|
| 67 |
)
|
| 68 |
-
|
| 69 |
-
# 4) ์ค์: ๋ถ๋ชจ ํ๋์๋ num_labels ๋ฐ์(์ ์ฅ/๋ก๋ ์์ )
|
| 70 |
-
self.num_labels = num_labels
|
| 71 |
-
self.label2id = label2id
|
| 72 |
-
self.id2label = id2label
|
| 73 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
self.backbone_name_or_path = backbone_name_or_path
|
| 75 |
-
self.mlp_head_bottleneck
|
| 76 |
-
self.mlp_head_dropout
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
def to_dict(self):
|
|
|
|
|
|
|
| 79 |
output = super().to_dict()
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
return output
|
| 83 |
|
|
|
|
|
|
|
|
|
|
| 84 |
BackboneMLPHeadConfig.register_for_auto_class("AutoConfig")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from transformers import PretrainedConfig
|
| 2 |
+
|
| 3 |
from ds_meta import BackboneID, BACKBONE_META
|
| 4 |
|
| 5 |
+
|
|
|
|
|
|
|
| 6 |
class BackboneMLPHeadConfig(PretrainedConfig):
|
| 7 |
"""
|
| 8 |
+
Configuration for Backbone + MLP Head models.
|
| 9 |
+
|
| 10 |
+
Backbone + MLP Head ๋ชจ๋ธ์ ์ํ ์ค์ ํด๋์ค์
๋๋ค.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
"""
|
| 12 |
|
| 13 |
+
# This string is used by Hugging Face AutoConfig to identify the config class.
|
| 14 |
+
# Hugging Face AutoConfig๊ฐ ์ด config๋ฅผ ์๋ณํ๊ธฐ ์ํด ์ฌ์ฉํ๋ ๊ณ ์ ID์
๋๋ค.
|
| 15 |
model_type = "backbone-mlphead-224-fixed"
|
| 16 |
|
|
|
|
| 17 |
def __init__(
|
| 18 |
self,
|
| 19 |
backbone_name_or_path: BackboneID | None = None,
|
| 20 |
mlp_head_bottleneck: int = 256,
|
| 21 |
mlp_head_dropout: float = 0.2,
|
| 22 |
+
label2id: dict[str, int] | None = None,
|
| 23 |
+
id2label: dict[int, str] | None = None,
|
| 24 |
**kwargs,
|
| 25 |
):
|
| 26 |
+
# ============================================================
|
| 27 |
+
# 0) Guard for argument-less construction
|
| 28 |
+
# 0) ๋ฌด์ธ์ ์์ฑ ๊ฒฝ๋ก ๋ฐฉ์ด
|
| 29 |
+
# ============================================================
|
| 30 |
+
# Transformers may internally construct this config without arguments
|
| 31 |
+
# (e.g., during AutoConfig resolution or Hub loading).
|
| 32 |
+
# Transformers ๋ด๋ถ์์ AutoConfig ๋๋ Hub ๋ก๋ ๊ณผ์ ์ค
|
| 33 |
+
# ์ธ์ ์์ด config๋ฅผ ์์ฑํ๋ ๊ฒฝ๋ก๊ฐ ์ค์ ๋ก ์กด์ฌํฉ๋๋ค.
|
| 34 |
+
#
|
| 35 |
+
# In this case, we must NOT validate or raise errors.
|
| 36 |
+
# ์ด ๊ฒฝ์ฐ ๊ฒ์ฆ์ด๋ ์์ธ๋ฅผ ๋ฐ์์ํค๋ฉด ์ ๋ฉ๋๋ค.
|
| 37 |
+
#
|
| 38 |
+
# The goal here is to provide a minimal, serialization-safe config.
|
| 39 |
+
# ๋ชฉํ๋ ์ต์ํ์ ๊ฐ๋ง ์ฑ์ ์ง๋ ฌํ/์ญ์ง๋ ฌํ๊ฐ ๊นจ์ง์ง ์๊ฒ ํ๋ ๊ฒ์
๋๋ค.
|
| 40 |
if backbone_name_or_path is None:
|
| 41 |
+
# num_labels may be implicitly assumed by downstream code,
|
| 42 |
+
# so we explicitly set a safe default.
|
| 43 |
+
# num_labels๋ downstream ์ฝ๋์์ ์๋ฌต์ ์ผ๋ก ๊ฐ์ ๋๋ฏ๋ก
|
| 44 |
+
# ์์ ํ ๊ธฐ๋ณธ๊ฐ์ ๋ช
์์ ์ผ๋ก ๋ฃ์ด์ค๋๋ค.
|
| 45 |
+
if "num_labels" not in kwargs:
|
| 46 |
+
kwargs["num_labels"] = 0
|
| 47 |
+
|
| 48 |
super().__init__(**kwargs)
|
| 49 |
+
|
| 50 |
+
# Backbone is intentionally unset in this path.
|
| 51 |
+
# ์ด ๊ฒฝ๋ก์์๋ backbone์ ์๋์ ์ผ๋ก ์ค์ ํ์ง ์์ต๋๋ค.
|
| 52 |
self.backbone_name_or_path = None
|
| 53 |
+
|
| 54 |
+
# Store MLP head hyperparameters for completeness.
|
| 55 |
+
# MLP head ๊ด๋ จ ํ์ดํผํ๋ผ๋ฏธํฐ๋ ํํ ์ ์ง๋ฅผ ์ํด ์ ์ฅํฉ๋๋ค.
|
| 56 |
+
self.mlp_head_bottleneck = int(mlp_head_bottleneck)
|
| 57 |
+
self.mlp_head_dropout = float(mlp_head_dropout)
|
| 58 |
+
|
| 59 |
+
# Empty label mappings ensure stable save/load behavior.
|
| 60 |
+
# ๋น label ๋งคํ์ ์ ์ฅ/๋ก๋ ์์ ์ฑ์ ๋ณด์ฅํฉ๋๋ค.
|
| 61 |
+
self.label2id = {}
|
| 62 |
+
self.id2label = {}
|
| 63 |
+
self.num_labels = int(kwargs.get("num_labels", 0))
|
| 64 |
return
|
| 65 |
|
| 66 |
+
# ============================================================
|
| 67 |
+
# 1) Backbone whitelist validation
|
| 68 |
+
# 1) Backbone ํ์ดํธ๋ฆฌ์คํธ ๊ฒ์ฆ
|
| 69 |
+
# ============================================================
|
| 70 |
+
# Only backbones explicitly registered in BACKBONE_META are allowed.
|
| 71 |
+
# BACKBONE_META์ ๋ฑ๋ก๋ backbone๋ง ํ์ฉํฉ๋๋ค.
|
| 72 |
+
#
|
| 73 |
+
# This prevents accidental usage of unsupported or inconsistent backbones.
|
| 74 |
+
# ์ง์๋์ง ์๊ฑฐ๋ ๋ถ์ผ์นํ backbone ์ฌ์ฉ์ ์์ฒ ์ฐจ๋จํฉ๋๋ค.
|
| 75 |
if backbone_name_or_path not in BACKBONE_META:
|
| 76 |
raise ValueError(
|
| 77 |
f"Unsupported backbone_name_or_path={backbone_name_or_path}. "
|
| 78 |
f"Allowed: {sorted(BACKBONE_META.keys())}"
|
| 79 |
)
|
| 80 |
+
|
| 81 |
+
# ============================================================
|
| 82 |
+
# 2) Label mapping normalization
|
| 83 |
+
# 2) ๋ผ๋ฒจ ๋งคํ ์ ๊ทํ
|
| 84 |
+
# ============================================================
|
| 85 |
+
# Both label2id and id2label may be None during pure loading scenarios.
|
| 86 |
+
# ๋จ์ ๋ก๋(from_pretrained) ์ label2id/id2label์ด None์ผ ์ ์์ต๋๋ค.
|
| 87 |
+
#
|
| 88 |
+
# We allow this here to keep Hub loading and verification stable.
|
| 89 |
+
# Hub ๋ก๋ ๋ฐ ๊ฒ์ฆ ์์ ์ฑ์ ์ํด ์ฌ๊ธฐ์๋ ์ด๋ฅผ ํ์ฉํฉ๋๋ค.
|
| 90 |
+
#
|
| 91 |
+
# Fail-fast validation should happen at model or training level instead.
|
| 92 |
+
# ์ค์ ์ฌ์ฉ ๊ฒ์ฆ์ ๋ชจ๋ธ ๋๋ ํ์ต ๋จ๊ณ์์ fail-fast๋ก ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค.
|
| 93 |
+
if label2id is None and id2label is None:
|
| 94 |
+
# Respect num_labels if explicitly provided, otherwise default to 0.
|
| 95 |
+
# num_labels๊ฐ ์ฃผ์ด์ก๋ค๋ฉด ์กด์คํ๊ณ , ์๋๋ฉด 0์ผ๋ก ๋ก๋๋ค.
|
| 96 |
+
num_labels = int(kwargs.get("num_labels", 0))
|
| 97 |
+
label2id_norm: dict[str, int] = {}
|
| 98 |
+
id2label_norm: dict[int, str] = {}
|
| 99 |
+
else:
|
| 100 |
+
# If only one mapping is provided, derive the other.
|
| 101 |
+
# ํ๋๋ง ์ฃผ์ด์ง ๊ฒฝ์ฐ ๋๋จธ์ง๋ฅผ ์๋์ผ๋ก ์์ฑํฉ๋๋ค.
|
| 102 |
+
if id2label is None:
|
| 103 |
+
id2label = {v: k for k, v in label2id.items()}
|
| 104 |
+
if label2id is None:
|
| 105 |
+
label2id = {v: k for k, v in id2label.items()}
|
| 106 |
+
|
| 107 |
+
# Ensure both mappings are consistent in size.
|
| 108 |
+
# ๋ ๋งคํ์ ํฌ๊ธฐ๊ฐ ์ผ์นํ๋์ง ํ์ธํฉ๋๋ค.
|
| 109 |
+
if len(label2id) != len(id2label):
|
| 110 |
+
raise ValueError(
|
| 111 |
+
f"label2id/id2label size mismatch: "
|
| 112 |
+
f"{len(label2id)} vs {len(id2label)}"
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
num_labels = len(id2label)
|
| 116 |
+
label2id_norm = dict(label2id)
|
| 117 |
+
id2label_norm = dict(id2label)
|
| 118 |
+
|
| 119 |
+
# ============================================================
|
| 120 |
+
# 3) num_labels consistency enforcement
|
| 121 |
+
# 3) num_labels ์ผ๊ด์ฑ ๊ฐ์
|
| 122 |
+
# ============================================================
|
| 123 |
+
# If num_labels is provided via kwargs, it must match inferred labels.
|
| 124 |
+
# kwargs๋ก num_labels๊ฐ ๋ค์ด์จ ๊ฒฝ์ฐ ์ถ๋ก ๋ ๊ฐ๊ณผ ๋ฐ๋์ ์ผ์นํด์ผ ํฉ๋๋ค.
|
| 125 |
+
if "num_labels" in kwargs:
|
| 126 |
+
if (label2id is not None or id2label is not None) and int(kwargs["num_labels"]) != num_labels:
|
| 127 |
+
raise ValueError(
|
| 128 |
+
f"kwargs['num_labels']={kwargs['num_labels']} "
|
| 129 |
+
f"!= inferred num_labels={num_labels}"
|
| 130 |
+
)
|
| 131 |
+
else:
|
| 132 |
kwargs["num_labels"] = num_labels
|
| 133 |
|
| 134 |
+
# ============================================================
|
| 135 |
+
# 4) Parent initialization
|
| 136 |
+
# 4) ๋ถ๋ชจ ํด๋์ค ์ด๊ธฐํ
|
| 137 |
+
# ============================================================
|
| 138 |
+
# Initialize PretrainedConfig with normalized label mappings.
|
| 139 |
+
# ์ ๊ทํ๋ ๋ผ๋ฒจ ๋งคํ์ ์ฌ์ฉํ์ฌ PretrainedConfig๋ฅผ ์ด๊ธฐํํฉ๋๋ค.
|
| 140 |
super().__init__(
|
| 141 |
+
label2id=label2id_norm,
|
| 142 |
+
id2label=id2label_norm,
|
| 143 |
**kwargs,
|
| 144 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
+
# ============================================================
|
| 147 |
+
# 5) Explicit attribute assignment for save/load stability
|
| 148 |
+
# 5) ์ ์ฅ/๋ก๋ ์์ ์ฑ์ ์ํ ๋ช
์์ ์์ฑ ๊ณ ์
|
| 149 |
+
# ============================================================
|
| 150 |
+
# Explicitly reassign critical fields to avoid subtle serialization issues.
|
| 151 |
+
# ๋ฏธ๋ฌํ ์ง๋ ฌํ ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ํต์ฌ ํ๋๋ฅผ ๋ช
์์ ์ผ๋ก ๋ค์ ์ค์ ํฉ๋๋ค.
|
| 152 |
self.backbone_name_or_path = backbone_name_or_path
|
| 153 |
+
self.mlp_head_bottleneck = int(mlp_head_bottleneck)
|
| 154 |
+
self.mlp_head_dropout = float(mlp_head_dropout)
|
| 155 |
+
|
| 156 |
+
self.label2id = label2id_norm
|
| 157 |
+
self.id2label = id2label_norm
|
| 158 |
+
self.num_labels = int(kwargs["num_labels"])
|
| 159 |
|
| 160 |
def to_dict(self):
|
| 161 |
+
# Call the parent implementation first.
|
| 162 |
+
# ๋จผ์ ๋ถ๋ชจ ๊ตฌํ์ ํธ์ถํฉ๋๋ค.
|
| 163 |
output = super().to_dict()
|
| 164 |
+
|
| 165 |
+
# Force num_labels to be present and consistent.
|
| 166 |
+
# num_labels๊ฐ ๋ฐ๋์ ์กด์ฌํ๊ณ ์ผ๊ด๋๋๋ก ๊ฐ์ ํฉ๋๋ค.
|
| 167 |
+
output["num_labels"] = int(
|
| 168 |
+
getattr(self, "num_labels", output.get("num_labels", 0))
|
| 169 |
+
)
|
| 170 |
return output
|
| 171 |
|
| 172 |
+
|
| 173 |
+
# Register this config so it can be resolved via AutoConfig.
|
| 174 |
+
# AutoConfig๋ฅผ ํตํด ์ด config๊ฐ ํด์๋ ์ ์๋๋ก ๋ฑ๋กํฉ๋๋ค.
|
| 175 |
BackboneMLPHeadConfig.register_for_auto_class("AutoConfig")
|
models/timm__densenet121.tv_in1k/ds_model.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
# -*- coding: utf-8 -*-
|
| 3 |
|
| 4 |
-
|
| 5 |
|
| 6 |
-
from
|
| 7 |
-
from typing import Dict, Any, Optional, Tuple, Set, List
|
| 8 |
|
| 9 |
import torch
|
| 10 |
import torch.nn as nn
|
|
@@ -12,146 +11,88 @@ import torch.nn.functional as F
|
|
| 12 |
|
| 13 |
# --- transformers core ---
|
| 14 |
from transformers.modeling_utils import PreTrainedModel
|
| 15 |
-
from transformers import AutoModel, AutoConfig
|
| 16 |
from transformers.modeling_outputs import ImageClassifierOutput
|
| 17 |
|
| 18 |
# --- torchvision ---
|
| 19 |
from torchvision import models as tv_models
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
# - ๊ธฐ์กด ds_cfg.BackboneMLPHeadConfig์ ํ๋๋ช
์ ๋ง์ถ์์ต๋๋ค.
|
| 25 |
-
# - ์ด๋ฏธ ds_cfg.py๊ฐ ์๋ค๋ฉด: ์ด ๋ธ๋ก์ ์ง์ฐ๊ณ "from ds_cfg import BackboneMLPHeadConfig"๋ก ๋ฐ๊ฟ๋ ๋ฉ๋๋ค.
|
| 26 |
-
# ============================================================
|
| 27 |
-
class BackboneMLPHeadConfig(PretrainedConfig):
|
| 28 |
-
model_type = "backbone_mlphead"
|
| 29 |
-
|
| 30 |
-
def __init__(
|
| 31 |
-
self,
|
| 32 |
-
backbone_name_or_path: Optional[str] = None,
|
| 33 |
-
num_labels: int = 2,
|
| 34 |
-
id2label: Optional[Dict[int, str]] = None,
|
| 35 |
-
label2id: Optional[Dict[str, int]] = None,
|
| 36 |
-
mlp_head_bottleneck: int = 256,
|
| 37 |
-
mlp_head_dropout: float = 0.2,
|
| 38 |
-
**kwargs,
|
| 39 |
-
):
|
| 40 |
-
super().__init__(**kwargs)
|
| 41 |
-
self.backbone_name_or_path = backbone_name_or_path
|
| 42 |
-
self.num_labels = num_labels
|
| 43 |
-
self.id2label = id2label or {i: f"LABEL_{i}" for i in range(num_labels)}
|
| 44 |
-
self.label2id = label2id or {v: k for k, v in self.id2label.items()}
|
| 45 |
-
self.mlp_head_bottleneck = mlp_head_bottleneck
|
| 46 |
-
self.mlp_head_dropout = mlp_head_dropout
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
# ============================================================
|
| 50 |
-
# (1) BACKBONE_META
|
| 51 |
-
# - ์ด๋ฏธ ds_meta.py๊ฐ ์๋ค๋ฉด: ์ด ๋ธ๋ก์ ์ง์ฐ๊ณ "from ds_meta import BACKBONE_META"๋ก ๋ฐ๊ฟ๋ ๋ฉ๋๋ค.
|
| 52 |
-
# ============================================================
|
| 53 |
-
BACKBONE_META: Dict[str, Dict[str, Any]] = {
|
| 54 |
-
# transformers
|
| 55 |
-
"google/vit-base-patch16-224": {
|
| 56 |
-
"type": "vit",
|
| 57 |
-
"feat_rule": "cls",
|
| 58 |
-
"feat_dim": 768,
|
| 59 |
-
"has_bn": False,
|
| 60 |
-
"unfreeze": "last_n",
|
| 61 |
-
},
|
| 62 |
-
"microsoft/swin-tiny-patch4-window7-224": {
|
| 63 |
-
"type": "swin",
|
| 64 |
-
"feat_rule": "pool_or_mean",
|
| 65 |
-
"feat_dim": 768,
|
| 66 |
-
"has_bn": False,
|
| 67 |
-
"unfreeze": "last_n",
|
| 68 |
-
},
|
| 69 |
-
"microsoft/resnet-50": {
|
| 70 |
-
"type": "resnet",
|
| 71 |
-
"feat_rule": "pool_or_gap",
|
| 72 |
-
"feat_dim": 2048,
|
| 73 |
-
"has_bn": True,
|
| 74 |
-
"unfreeze": "last_n",
|
| 75 |
-
},
|
| 76 |
-
"google/efficientnet-b0": {
|
| 77 |
-
"type": "efficientnet",
|
| 78 |
-
"feat_rule": "pool_or_gap",
|
| 79 |
-
"feat_dim": 1280,
|
| 80 |
-
"has_bn": True,
|
| 81 |
-
"unfreeze": "last_n",
|
| 82 |
-
},
|
| 83 |
-
|
| 84 |
-
# densenet
|
| 85 |
-
"timm/densenet121.tv_in1k": {
|
| 86 |
-
"type": "timm_densenet",
|
| 87 |
-
"feat_rule": "timm_gap",
|
| 88 |
-
"feat_dim": 1024,
|
| 89 |
-
"has_bn": True,
|
| 90 |
-
"unfreeze": "last_n",
|
| 91 |
-
},
|
| 92 |
-
"torchvision/densenet121": {
|
| 93 |
-
"type": "torchvision_densenet",
|
| 94 |
-
"feat_rule": "torchvision_densenet_gap",
|
| 95 |
-
"feat_dim": 1024,
|
| 96 |
-
"has_bn": True,
|
| 97 |
-
"unfreeze": "last_n",
|
| 98 |
-
},
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
# ============================================================
|
| 103 |
-
# (2) MLP Head
|
| 104 |
-
# - ์ด๋ฏธ mlp_head.py๊ฐ ์๋ค๋ฉด: ์ด ๋ธ๋ก์ ์ง์ฐ๊ณ "from mlp_head import MLPHead"๋ก ๋ฐ๊ฟ๋ ๋ฉ๋๋ค.
|
| 105 |
-
# ============================================================
|
| 106 |
-
class MLPHead(nn.Module):
|
| 107 |
-
def __init__(self, in_dim: int, num_labels: int, bottleneck: int = 256, p: float = 0.2):
|
| 108 |
-
super().__init__()
|
| 109 |
-
self.fc2 = nn.Linear(in_dim, bottleneck)
|
| 110 |
-
self.act2 = nn.GELU()
|
| 111 |
-
self.drop2 = nn.Dropout(p)
|
| 112 |
-
self.fc3 = nn.Linear(bottleneck, num_labels)
|
| 113 |
-
|
| 114 |
-
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 115 |
-
x = self.drop2(self.act2(self.fc2(x)))
|
| 116 |
-
return self.fc3(x)
|
| 117 |
-
|
| 118 |
|
| 119 |
# ============================================================
|
| 120 |
# (3) Model: backbone + MLP head
|
| 121 |
-
#
|
| 122 |
-
# - __init__: backbone ๊ตฌ์กฐ(skeleton)๋ง ์์ฑ. pretrained๋ฅผ ์ ๋ ๋ก๋ํ์ง ์์.
|
| 123 |
-
# - pretrained ์ฃผ์
: model.load_backbone_pretrained_()๋ฅผ "fresh-start์์๋ง" ์ง์ ํธ์ถ.
|
| 124 |
-
# - from_pretrained override ์์: HF ๊ธฐ๋ณธ ๋ก๋๊ฐ checkpoint๋ฅผ ๊ทธ๋๋ก ๋ณต์.
|
| 125 |
# ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
|
|
|
|
|
| 127 |
config_class = BackboneMLPHeadConfig
|
| 128 |
|
| 129 |
def __init__(self, config: BackboneMLPHeadConfig):
|
|
|
|
|
|
|
| 130 |
super().__init__(config)
|
| 131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
if config.backbone_name_or_path is None:
|
| 133 |
-
raise ValueError(
|
|
|
|
|
|
|
|
|
|
| 134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
self._meta = BACKBONE_META[config.backbone_name_or_path]
|
| 136 |
|
| 137 |
-
#
|
|
|
|
| 138 |
self.backbone = self._build_backbone_skeleton(config.backbone_name_or_path)
|
| 139 |
|
| 140 |
-
#
|
|
|
|
| 141 |
self.classifier = MLPHead(
|
| 142 |
in_dim=int(self._meta["feat_dim"]),
|
| 143 |
-
num_labels=config.num_labels,
|
| 144 |
-
bottleneck=config.mlp_head_bottleneck,
|
| 145 |
-
p=config.mlp_head_dropout,
|
| 146 |
)
|
| 147 |
|
| 148 |
-
# HF
|
|
|
|
| 149 |
self.post_init()
|
| 150 |
|
| 151 |
def init_weights(self):
|
| 152 |
"""
|
| 153 |
-
|
| 154 |
-
head๋ง ์ด๊ธฐํ
|
|
|
|
|
|
|
|
|
|
| 155 |
"""
|
| 156 |
if getattr(self, "classifier", None) is not None:
|
| 157 |
self.classifier.apply(self._init_weights)
|
|
@@ -159,8 +100,11 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 159 |
|
| 160 |
# ----------------------------
|
| 161 |
# backbone skeleton builders
|
|
|
|
| 162 |
# ----------------------------
|
| 163 |
def _build_backbone_skeleton(self, backbone_id: str) -> nn.Module:
|
|
|
|
|
|
|
| 164 |
meta = BACKBONE_META[backbone_id]
|
| 165 |
t = meta["type"]
|
| 166 |
|
|
@@ -170,18 +114,24 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 170 |
if t == "torchvision_densenet":
|
| 171 |
return self._build_torchvision_densenet_skeleton(backbone_id)
|
| 172 |
|
| 173 |
-
# transformers:
|
|
|
|
| 174 |
bb_cfg = AutoConfig.from_pretrained(backbone_id)
|
| 175 |
return AutoModel.from_config(bb_cfg)
|
| 176 |
|
| 177 |
@staticmethod
|
| 178 |
def _build_timm_densenet_skeleton(hf_repo_id: str) -> nn.Module:
|
|
|
|
|
|
|
| 179 |
try:
|
| 180 |
import timm
|
| 181 |
except Exception as e:
|
| 182 |
-
raise ImportError(
|
|
|
|
|
|
|
| 183 |
|
| 184 |
-
#
|
|
|
|
| 185 |
return timm.create_model(
|
| 186 |
f"hf_hub:{hf_repo_id}",
|
| 187 |
pretrained=False,
|
|
@@ -190,33 +140,33 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 190 |
|
| 191 |
@staticmethod
|
| 192 |
def _build_torchvision_densenet_skeleton(model_id: str) -> nn.Module:
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
"torchvision
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
arch = mapping[model_id]
|
| 203 |
-
fn = getattr(tv_models, arch)
|
| 204 |
-
|
| 205 |
-
# ๊ตฌ์กฐ๋ง (weights=None)
|
| 206 |
-
m = fn(weights=None).eval()
|
| 207 |
return m
|
| 208 |
|
| 209 |
# ------------------------------------------------------------
|
| 210 |
-
#
|
|
|
|
| 211 |
# ------------------------------------------------------------
|
| 212 |
@torch.no_grad()
|
| 213 |
-
def load_backbone_pretrained_(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
"""
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
model.load_backbone_pretrained_()
|
| 218 |
|
| 219 |
-
|
|
|
|
| 220 |
"""
|
| 221 |
bb = self.config.backbone_name_or_path
|
| 222 |
meta = BACKBONE_META[bb]
|
|
@@ -230,48 +180,58 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 230 |
self._load_torchvision_pretrained_into_skeleton_(bb)
|
| 231 |
return
|
| 232 |
|
| 233 |
-
# transformers pretrained
|
|
|
|
| 234 |
ref = AutoModel.from_pretrained(
|
| 235 |
bb,
|
| 236 |
low_cpu_mem_usage=low_cpu_mem_usage,
|
| 237 |
device_map=device_map,
|
| 238 |
)
|
|
|
|
|
|
|
|
|
|
| 239 |
self.backbone.load_state_dict(ref.state_dict(), strict=False)
|
| 240 |
del ref
|
| 241 |
|
| 242 |
@torch.no_grad()
|
| 243 |
def _load_timm_pretrained_into_skeleton_(self, hf_repo_id: str):
|
|
|
|
|
|
|
| 244 |
import timm
|
|
|
|
|
|
|
|
|
|
| 245 |
ref = timm.create_model(
|
| 246 |
f"hf_hub:{hf_repo_id}",
|
| 247 |
pretrained=True,
|
| 248 |
num_classes=0,
|
| 249 |
).eval()
|
|
|
|
| 250 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 251 |
del ref
|
| 252 |
|
| 253 |
@torch.no_grad()
|
| 254 |
def _load_torchvision_pretrained_into_skeleton_(self, model_id: str):
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
"torchvision
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
arch, weights = mapping[model_id]
|
| 265 |
-
fn = getattr(tv_models, arch)
|
| 266 |
-
ref = fn(weights=weights).eval()
|
| 267 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 268 |
del ref
|
| 269 |
|
| 270 |
# ----------------------------
|
| 271 |
# feature extraction
|
|
|
|
| 272 |
# ----------------------------
|
| 273 |
@staticmethod
|
| 274 |
def _pool_or_gap(outputs) -> torch.Tensor:
|
|
|
|
|
|
|
| 275 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 276 |
x = outputs.pooler_output
|
| 277 |
if x.dim() == 2:
|
|
@@ -280,29 +240,42 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 280 |
return x.flatten(1)
|
| 281 |
raise RuntimeError(f"Unexpected pooler_output shape: {tuple(x.shape)}")
|
| 282 |
|
|
|
|
|
|
|
| 283 |
x = outputs.last_hidden_state
|
| 284 |
if x.dim() == 4:
|
| 285 |
return x.mean(dim=(2, 3))
|
|
|
|
| 286 |
raise RuntimeError(
|
| 287 |
"Expected pooler_output or (B,C,H,W) last_hidden_state for CNN backbones. "
|
| 288 |
f"Got last_hidden_state shape={tuple(x.shape)}"
|
| 289 |
)
|
| 290 |
|
| 291 |
def _extract_features(self, outputs, pixel_values: Optional[torch.Tensor] = None) -> torch.Tensor:
|
|
|
|
|
|
|
| 292 |
rule = self._meta["feat_rule"]
|
| 293 |
|
| 294 |
if rule == "cls":
|
|
|
|
|
|
|
| 295 |
return outputs.last_hidden_state[:, 0, :]
|
| 296 |
|
| 297 |
if rule == "pool_or_mean":
|
|
|
|
|
|
|
| 298 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 299 |
return outputs.pooler_output
|
| 300 |
return outputs.last_hidden_state.mean(dim=1)
|
| 301 |
|
| 302 |
if rule == "pool_or_gap":
|
|
|
|
|
|
|
| 303 |
return self._pool_or_gap(outputs)
|
| 304 |
|
| 305 |
if rule == "timm_gap":
|
|
|
|
|
|
|
| 306 |
if not isinstance(outputs, torch.Tensor):
|
| 307 |
raise TypeError(f"timm_gap expects Tensor features, got {type(outputs)}")
|
| 308 |
if outputs.dim() != 4:
|
|
@@ -310,6 +283,8 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 310 |
return outputs.mean(dim=(2, 3))
|
| 311 |
|
| 312 |
if rule == "torchvision_densenet_gap":
|
|
|
|
|
|
|
| 313 |
if not isinstance(outputs, torch.Tensor):
|
| 314 |
raise TypeError(f"torchvision_densenet_gap expects Tensor, got {type(outputs)}")
|
| 315 |
if outputs.dim() != 4:
|
|
@@ -327,9 +302,13 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 327 |
return_dict=True,
|
| 328 |
**kwargs,
|
| 329 |
):
|
|
|
|
|
|
|
| 330 |
t = self._meta["type"]
|
| 331 |
|
| 332 |
if t == "timm_densenet":
|
|
|
|
|
|
|
| 333 |
if pixel_values is None:
|
| 334 |
raise ValueError("timm DenseNet backbone requires pixel_values.")
|
| 335 |
if pixel_values.dim() != 4:
|
|
@@ -341,6 +320,8 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 341 |
attentions = None
|
| 342 |
|
| 343 |
elif t == "torchvision_densenet":
|
|
|
|
|
|
|
| 344 |
if pixel_values is None:
|
| 345 |
raise ValueError("torchvision DenseNet backbone requires pixel_values.")
|
| 346 |
if pixel_values.dim() != 4:
|
|
@@ -353,6 +334,8 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 353 |
attentions = None
|
| 354 |
|
| 355 |
else:
|
|
|
|
|
|
|
| 356 |
outputs = self.backbone(
|
| 357 |
pixel_values=pixel_values,
|
| 358 |
output_attentions=output_attentions,
|
|
@@ -364,10 +347,14 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 364 |
hidden_states = getattr(outputs, "hidden_states", None)
|
| 365 |
attentions = getattr(outputs, "attentions", None)
|
| 366 |
|
|
|
|
|
|
|
| 367 |
logits = self.classifier(feats)
|
| 368 |
|
| 369 |
loss = None
|
| 370 |
if labels is not None:
|
|
|
|
|
|
|
| 371 |
loss = F.cross_entropy(logits, labels)
|
| 372 |
|
| 373 |
if not return_dict:
|
|
@@ -383,20 +370,27 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 383 |
|
| 384 |
|
| 385 |
# ============================================================
|
| 386 |
-
# Freeze / Unfreeze utilities
|
|
|
|
| 387 |
# ============================================================
|
| 388 |
def _set_requires_grad(module: nn.Module, flag: bool):
|
|
|
|
|
|
|
| 389 |
for p in module.parameters():
|
| 390 |
p.requires_grad = flag
|
| 391 |
|
| 392 |
|
| 393 |
def set_bn_eval(module: nn.Module):
|
|
|
|
|
|
|
| 394 |
for m in module.modules():
|
| 395 |
if isinstance(m, (nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d, nn.SyncBatchNorm)):
|
| 396 |
m.eval()
|
| 397 |
|
| 398 |
|
| 399 |
def freeze_backbone(model: BackboneWithMLPHeadForImageClassification, freeze_bn: bool = True):
|
|
|
|
|
|
|
| 400 |
_set_requires_grad(model.backbone, False)
|
| 401 |
_set_requires_grad(model.classifier, True)
|
| 402 |
|
|
@@ -406,6 +400,8 @@ def freeze_backbone(model: BackboneWithMLPHeadForImageClassification, freeze_bn:
|
|
| 406 |
|
| 407 |
|
| 408 |
def finetune_train_mode(model: BackboneWithMLPHeadForImageClassification, keep_bn_eval: bool = True):
|
|
|
|
|
|
|
| 409 |
model.train()
|
| 410 |
meta = BACKBONE_META[model.config.backbone_name_or_path]
|
| 411 |
if keep_bn_eval and meta.get("has_bn", False):
|
|
@@ -413,6 +409,8 @@ def finetune_train_mode(model: BackboneWithMLPHeadForImageClassification, keep_b
|
|
| 413 |
|
| 414 |
|
| 415 |
def trainable_summary(model: nn.Module):
|
|
|
|
|
|
|
| 416 |
total = sum(p.numel() for p in model.parameters())
|
| 417 |
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 418 |
ratio = trainable / total if total > 0 else 0.0
|
|
@@ -425,6 +423,8 @@ def unfreeze_last_stage(
|
|
| 425 |
last_n: int = 2,
|
| 426 |
keep_bn_eval: bool = True,
|
| 427 |
):
|
|
|
|
|
|
|
| 428 |
freeze_backbone(model, freeze_bn=keep_bn_eval)
|
| 429 |
|
| 430 |
n = int(last_n)
|
|
@@ -438,12 +438,16 @@ def unfreeze_last_stage(
|
|
| 438 |
bb_type = meta["type"]
|
| 439 |
|
| 440 |
if bb_type == "vit":
|
|
|
|
|
|
|
| 441 |
blocks = list(model.backbone.encoder.layer)
|
| 442 |
for blk in blocks[-n:]:
|
| 443 |
_set_requires_grad(blk, True)
|
| 444 |
return
|
| 445 |
|
| 446 |
if bb_type == "swin":
|
|
|
|
|
|
|
| 447 |
stages = list(model.backbone.encoder.layers)
|
| 448 |
blocks: List[nn.Module] = []
|
| 449 |
for st in stages:
|
|
@@ -453,6 +457,8 @@ def unfreeze_last_stage(
|
|
| 453 |
return
|
| 454 |
|
| 455 |
if bb_type == "resnet":
|
|
|
|
|
|
|
| 456 |
bb = model.backbone
|
| 457 |
for name in ("layer1", "layer2", "layer3", "layer4"):
|
| 458 |
if not hasattr(bb, name):
|
|
@@ -472,6 +478,8 @@ def unfreeze_last_stage(
|
|
| 472 |
return
|
| 473 |
|
| 474 |
if bb_type == "efficientnet":
|
|
|
|
|
|
|
| 475 |
bb = model.backbone
|
| 476 |
if not hasattr(bb, "features"):
|
| 477 |
raise RuntimeError("Unexpected EfficientNet structure: missing features")
|
|
@@ -488,6 +496,8 @@ def unfreeze_last_stage(
|
|
| 488 |
return
|
| 489 |
|
| 490 |
if bb_type in ("timm_densenet", "torchvision_densenet"):
|
|
|
|
|
|
|
| 491 |
bb = model.backbone
|
| 492 |
if not hasattr(bb, "features"):
|
| 493 |
raise RuntimeError("Unexpected DenseNet: missing features")
|
|
@@ -505,6 +515,8 @@ def unfreeze_last_stage(
|
|
| 505 |
raise RuntimeError(f"Unexpected DenseNet features: missing {name}")
|
| 506 |
|
| 507 |
def _denselayers(db: nn.Module) -> List[nn.Module]:
|
|
|
|
|
|
|
| 508 |
return list(db.children())
|
| 509 |
|
| 510 |
blocks: List[nn.Module] = []
|
|
@@ -526,20 +538,8 @@ def unfreeze_last_stage(
|
|
| 526 |
|
| 527 |
# -------------------------
|
| 528 |
# register
|
|
|
|
|
|
|
|
|
|
|
|
|
| 529 |
BackboneWithMLPHeadForImageClassification.register_for_auto_class("AutoModelForImageClassification")
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
# ==========================================================
|
| 533 |
-
|
| 534 |
-
# # fresh-start
|
| 535 |
-
# cfg = BackboneMLPHeadConfig(backbone_name_or_path="timm/densenet121.tv_in1k", num_labels=3)
|
| 536 |
-
# model = BackboneWithMLPHeadForImageClassification(cfg)
|
| 537 |
-
|
| 538 |
-
# # pretrained๋ฅผ ์ฐ๋ ค๋ฉด ๋ช
์์ ์ผ๋ก ํธ์ถ
|
| 539 |
-
# model.load_backbone_pretrained_()
|
| 540 |
-
|
| 541 |
-
# # ์ดํ ํ์ต
|
| 542 |
-
|
| 543 |
-
# # checkpoint์์ ์์
|
| 544 |
-
# model = BackboneWithMLPHeadForImageClassification.from_pretrained("checkpoints/xxx")
|
| 545 |
-
# # ์ฌ๊ธฐ์๋ load_backbone_pretrained_() ํธ์ถํ๋ฉด ์ ๋ฉ๋๋ค.
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
# -*- coding: utf-8 -*-
|
| 3 |
|
| 4 |
+
# src/ds_model.py
|
| 5 |
|
| 6 |
+
from typing import Optional, List
|
|
|
|
| 7 |
|
| 8 |
import torch
|
| 9 |
import torch.nn as nn
|
|
|
|
| 11 |
|
| 12 |
# --- transformers core ---
|
| 13 |
from transformers.modeling_utils import PreTrainedModel
|
| 14 |
+
from transformers import AutoModel, AutoConfig
|
| 15 |
from transformers.modeling_outputs import ImageClassifierOutput
|
| 16 |
|
| 17 |
# --- torchvision ---
|
| 18 |
from torchvision import models as tv_models
|
| 19 |
|
| 20 |
+
from ds_cfg import BackboneMLPHeadConfig
|
| 21 |
+
from ds_meta import BACKBONE_META
|
| 22 |
+
from mlp_head import MLPHead
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
# ============================================================
|
| 25 |
# (3) Model: backbone + MLP head
|
| 26 |
+
# (3) ๋ชจ๋ธ: backbone + MLP head
|
|
|
|
|
|
|
|
|
|
| 27 |
# ============================================================
|
| 28 |
+
# Design principle: __init__ builds only a skeleton and MUST NOT load pretrained weights.
|
| 29 |
+
# ์ค๊ณ ์์น: __init__์์๋ skeleton๋ง ๋ง๋ค๊ณ pretrained weight ๋ก๋๋ ์ ๋ ํ๋ฉด ์ ๋ฉ๋๋ค.
|
| 30 |
+
#
|
| 31 |
+
# Pretrained injection is allowed ONLY via an explicit call in fresh-start flows.
|
| 32 |
+
# pretrained ์ฃผ์
์ fresh-start ํ๋ฆ์์์ ๋ช
์์ ํธ์ถ๋ก๋ง ํ์ฉํฉ๋๋ค.
|
| 33 |
+
#
|
| 34 |
+
# HF from_pretrained should restore checkpoints as-is without side effects.
|
| 35 |
+
# HF from_pretrained๋ ๋ถ์์ฉ ์์ด ์ฒดํฌํฌ์ธํธ๋ฅผ ๊ทธ๋๋ก ๋ณต์ํด์ผ ํฉ๋๋ค.
|
| 36 |
class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
| 37 |
+
# This links the model to its custom config for AutoClass usage.
|
| 38 |
+
# AutoClass ์ฌ์ฉ์ ์ํด ๋ชจ๋ธ๊ณผ ์ปค์คํ
config๋ฅผ ์ฐ๊ฒฐํฉ๋๋ค.
|
| 39 |
config_class = BackboneMLPHeadConfig
|
| 40 |
|
| 41 |
def __init__(self, config: BackboneMLPHeadConfig):
|
| 42 |
+
# PreTrainedModel expects a config object and stores it internally.
|
| 43 |
+
# PreTrainedModel์ config ๊ฐ์ฒด๋ฅผ ๋ฐ์ ๋ด๋ถ์ ์ ์ฅํ๋ ์ ์ ๋ฅผ ๊ฐ์ง๋๋ค.
|
| 44 |
super().__init__(config)
|
| 45 |
|
| 46 |
+
# Fail-fast: the model is not meant to be instantiated without a valid backbone id.
|
| 47 |
+
# fail-fast: ์ ํจํ backbone id ์์ด ๋ชจ๋ธ์ ๋ง๋๋ ์ฌ์ฉ ์๋๋ฆฌ์ค๋ ํ์ฉํ์ง ์์ต๋๋ค.
|
| 48 |
+
#
|
| 49 |
+
# Note: Transformers may create configs with no args, but models are conventionally created with configs.
|
| 50 |
+
# ์ฐธ๊ณ : Transformers๋ config ๋ฌด์ธ์ ์์ฑ์ด ์์ ์ ์์ผ๋, ๋ชจ๋ธ์ ๊ด๋ก์ ์ผ๋ก config๋ฅผ ๋ฐ์ ์์ฑ๋ฉ๋๋ค.
|
| 51 |
if config.backbone_name_or_path is None:
|
| 52 |
+
raise ValueError(
|
| 53 |
+
"config.backbone_name_or_path is None. "
|
| 54 |
+
"Provide a valid backbone id (whitelist key in BACKBONE_META)."
|
| 55 |
+
)
|
| 56 |
|
| 57 |
+
# Fail-fast: training/inference requires a positive number of labels.
|
| 58 |
+
# fail-fast: ํ์ต/์ถ๋ก ์ num_labels๊ฐ ์์์ฌ์ผ ํฉ๋๋ค.
|
| 59 |
+
#
|
| 60 |
+
# Config may exist in a minimal form for internal serialization paths, but the model should not.
|
| 61 |
+
# config๋ ๋ด๋ถ ์ง๋ ฌํ ๊ฒฝ๋ก์์ ์ต์ ํํ๋ก ์กด์ฌํ ์ ์์ผ๋ ๋ชจ๋ธ์ ๊ทธ๋ฌ๋ฉด ์ ๋ฉ๋๋ค.
|
| 62 |
+
if int(getattr(config, "num_labels", 0)) <= 0:
|
| 63 |
+
raise ValueError(
|
| 64 |
+
f"config.num_labels must be > 0, got {getattr(config, 'num_labels', None)}. "
|
| 65 |
+
"Set num_labels (or id2label/label2id) when creating the config."
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
# Meta is a single source of truth for extraction and fine-tuning rules.
|
| 69 |
+
# meta๋ feature ์ถ์ถ ๋ฐ ๋ฏธ์ธ์กฐ์ ๊ท์น์ ๋จ์ผ ๊ธฐ์ค์
๋๋ค.
|
| 70 |
self._meta = BACKBONE_META[config.backbone_name_or_path]
|
| 71 |
|
| 72 |
+
# Backbone skeleton is always created without pretrained weights.
|
| 73 |
+
# backbone skeleton์ ํญ์ pretrained weight ์์ด ์์ฑํฉ๋๋ค.
|
| 74 |
self.backbone = self._build_backbone_skeleton(config.backbone_name_or_path)
|
| 75 |
|
| 76 |
+
# Head shape is driven by meta feat_dim and config.num_labels.
|
| 77 |
+
# head shape์ meta์ feat_dim๊ณผ config.num_labels๋ก ๊ฒฐ์ ๋ฉ๋๋ค.
|
| 78 |
self.classifier = MLPHead(
|
| 79 |
in_dim=int(self._meta["feat_dim"]),
|
| 80 |
+
num_labels=int(config.num_labels),
|
| 81 |
+
bottleneck=int(config.mlp_head_bottleneck),
|
| 82 |
+
p=float(config.mlp_head_dropout),
|
| 83 |
)
|
| 84 |
|
| 85 |
+
# HF initialization hook, but we override init_weights to initialize head-only.
|
| 86 |
+
# HF ์ด๊ธฐํ ํ
์ด์ง๋ง init_weights๋ฅผ overrideํ์ฌ head๋ง ์ด๊ธฐํํฉ๋๋ค.
|
| 87 |
self.post_init()
|
| 88 |
|
| 89 |
def init_weights(self):
|
| 90 |
"""
|
| 91 |
+
Initialize only the head to avoid touching the backbone skeleton.
|
| 92 |
+
backbone skeleton์ ๊ฑด๋๋ฆฌ์ง ์๊ธฐ ์ํด head๋ง ์ด๊ธฐํํฉ๋๋ค.
|
| 93 |
+
|
| 94 |
+
HF's default init may traverse the entire module tree, which is undesirable here.
|
| 95 |
+
HF ๊ธฐ๋ณธ init์ ์ ์ฒด ๋ชจ๋ ํธ๋ฆฌ๋ฅผ ์ํํ ์ ์์ด ์ฌ๊ธฐ์๋ ๋ถ์ ์ ํฉ๋๋ค.
|
| 96 |
"""
|
| 97 |
if getattr(self, "classifier", None) is not None:
|
| 98 |
self.classifier.apply(self._init_weights)
|
|
|
|
| 100 |
|
| 101 |
# ----------------------------
|
| 102 |
# backbone skeleton builders
|
| 103 |
+
# backbone skeleton ์์ฑ๊ธฐ
|
| 104 |
# ----------------------------
|
| 105 |
def _build_backbone_skeleton(self, backbone_id: str) -> nn.Module:
|
| 106 |
+
# Meta decides which loader path to use.
|
| 107 |
+
# meta๊ฐ ์ด๋ค ๋ก๋ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ ์ง ๊ฒฐ์ ํฉ๋๋ค.
|
| 108 |
meta = BACKBONE_META[backbone_id]
|
| 109 |
t = meta["type"]
|
| 110 |
|
|
|
|
| 114 |
if t == "torchvision_densenet":
|
| 115 |
return self._build_torchvision_densenet_skeleton(backbone_id)
|
| 116 |
|
| 117 |
+
# For transformers backbones: build a random-weight skeleton from config only.
|
| 118 |
+
# transformers ๋ฐฑ๋ณธ: config๋ก๋ถํฐ ๋๋ค ์ด๊ธฐํ skeleton๋ง ์์ฑํฉ๋๋ค.
|
| 119 |
bb_cfg = AutoConfig.from_pretrained(backbone_id)
|
| 120 |
return AutoModel.from_config(bb_cfg)
|
| 121 |
|
| 122 |
@staticmethod
|
| 123 |
def _build_timm_densenet_skeleton(hf_repo_id: str) -> nn.Module:
|
| 124 |
+
# timm is an optional dependency and should be imported lazily.
|
| 125 |
+
# timm์ ์ต์
์์กด์ฑ์ด๋ฏ๋ก ์ง์ฐ import ํฉ๋๋ค.
|
| 126 |
try:
|
| 127 |
import timm
|
| 128 |
except Exception as e:
|
| 129 |
+
raise ImportError(
|
| 130 |
+
"DenseNet(timm) backbone requires `timm`. Install: pip install timm"
|
| 131 |
+
) from e
|
| 132 |
|
| 133 |
+
# Build structure only (pretrained=False) and remove classifier head (num_classes=0).
|
| 134 |
+
# ๊ตฌ์กฐ๋ง ์์ฑ(pretrained=False)ํ๊ณ ๋ถ๋ฅ๊ธฐ head๋ ์ ๊ฑฐ(num_classes=0)ํฉ๋๋ค.
|
| 135 |
return timm.create_model(
|
| 136 |
f"hf_hub:{hf_repo_id}",
|
| 137 |
pretrained=False,
|
|
|
|
| 140 |
|
| 141 |
@staticmethod
|
| 142 |
def _build_torchvision_densenet_skeleton(model_id: str) -> nn.Module:
|
| 143 |
+
# This project intentionally supports only torchvision/densenet121 in the 224 whitelist.
|
| 144 |
+
# ์ด ํ๋ก์ ํธ๋ 224 ํ์ดํธ๋ฆฌ์คํธ์์ torchvision/densenet121๋ง ์๋์ ์ผ๋ก ์ง์ํฉ๋๋ค.
|
| 145 |
+
if model_id != "torchvision/densenet121":
|
| 146 |
+
raise ValueError(f"Unsupported torchvision DenseNet id (224 whitelist only): {model_id}")
|
| 147 |
+
|
| 148 |
+
# Build structure only (weights=None) to avoid implicit pretrained loading.
|
| 149 |
+
# implicit pretrained ๋ก๋๋ฅผ ํผํ๊ธฐ ์ํด ๊ตฌ์กฐ๋ง ์์ฑ(weights=None)ํฉ๋๋ค.
|
| 150 |
+
m = tv_models.densenet121(weights=None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
return m
|
| 152 |
|
| 153 |
# ------------------------------------------------------------
|
| 154 |
+
# Pretrained loading is explicit and fresh-start only
|
| 155 |
+
# pretrained ๋ก๋ฉ์ ๋ช
์์ ํธ์ถ์ด๋ฉฐ fresh-start ์ ์ฉ
|
| 156 |
# ------------------------------------------------------------
|
| 157 |
@torch.no_grad()
|
| 158 |
+
def load_backbone_pretrained_(
|
| 159 |
+
self,
|
| 160 |
+
*,
|
| 161 |
+
low_cpu_mem_usage: bool = False,
|
| 162 |
+
device_map=None,
|
| 163 |
+
):
|
| 164 |
"""
|
| 165 |
+
Fresh-start only: inject pretrained backbone weights into the skeleton.
|
| 166 |
+
fresh-start ์ ์ฉ: skeleton backbone์ pretrained ๊ฐ์ค์น๋ฅผ ์ฃผ์
ํฉ๋๋ค.
|
|
|
|
| 167 |
|
| 168 |
+
Do NOT call this after from_pretrained() because it would overwrite checkpoint weights.
|
| 169 |
+
from_pretrained() ์ดํ ํธ์ถํ๋ฉด ์ฒดํฌํฌ์ธํธ ๊ฐ์ค์น๋ฅผ ๋ฎ์ด์ฐ๋ฏ๋ก ์ ๋ ํธ์ถํ๋ฉด ์ ๋ฉ๋๋ค.
|
| 170 |
"""
|
| 171 |
bb = self.config.backbone_name_or_path
|
| 172 |
meta = BACKBONE_META[bb]
|
|
|
|
| 180 |
self._load_torchvision_pretrained_into_skeleton_(bb)
|
| 181 |
return
|
| 182 |
|
| 183 |
+
# For transformers backbones, load a reference pretrained model and copy weights into our skeleton.
|
| 184 |
+
# transformers ๋ฐฑ๋ณธ์ reference pretrained ๋ชจ๋ธ์ ๋ก๋ํ ๋ค skeleton์ ๊ฐ์ค์น๋ฅผ ๋ณต์ฌํฉ๋๋ค.
|
| 185 |
ref = AutoModel.from_pretrained(
|
| 186 |
bb,
|
| 187 |
low_cpu_mem_usage=low_cpu_mem_usage,
|
| 188 |
device_map=device_map,
|
| 189 |
)
|
| 190 |
+
|
| 191 |
+
# strict=False is used to tolerate harmless key differences across minor versions.
|
| 192 |
+
# strict=False๋ ๋ง์ด๋ ๋ฒ์ ์ฐจ์ด๋ก ์ธํ ๋ฌดํดํ ํค ์ฐจ์ด๋ฅผ ํ์ฉํ๊ธฐ ์ํด ์ฌ์ฉํฉ๋๋ค.
|
| 193 |
self.backbone.load_state_dict(ref.state_dict(), strict=False)
|
| 194 |
del ref
|
| 195 |
|
| 196 |
@torch.no_grad()
|
| 197 |
def _load_timm_pretrained_into_skeleton_(self, hf_repo_id: str):
|
| 198 |
+
# timm must be present for timm backbones.
|
| 199 |
+
# timm ๋ฐฑ๋ณธ์๋ timm ์ค์น๊ฐ ํ์ํฉ๋๋ค.
|
| 200 |
import timm
|
| 201 |
+
|
| 202 |
+
# Create a pretrained reference model and copy its weights strictly.
|
| 203 |
+
# pretrained reference ๋ชจ๋ธ์ ๋ง๋ค๊ณ ๊ฐ์ค์น๋ฅผ strictํ๊ฒ ๋ณต์ฌํฉ๋๋ค.
|
| 204 |
ref = timm.create_model(
|
| 205 |
f"hf_hub:{hf_repo_id}",
|
| 206 |
pretrained=True,
|
| 207 |
num_classes=0,
|
| 208 |
).eval()
|
| 209 |
+
|
| 210 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 211 |
del ref
|
| 212 |
|
| 213 |
@torch.no_grad()
|
| 214 |
def _load_torchvision_pretrained_into_skeleton_(self, model_id: str):
|
| 215 |
+
# This project intentionally supports only torchvision/densenet121 in the 224 whitelist.
|
| 216 |
+
# ์ด ํ๋ก์ ํธ๋ 224 ํ์ดํธ๋ฆฌ์คํธ์์ torchvision/densenet121๋ง ์๋์ ์ผ๋ก ์ง์ํฉ๋๋ค.
|
| 217 |
+
if model_id != "torchvision/densenet121":
|
| 218 |
+
raise ValueError(f"Unsupported torchvision DenseNet id (224 whitelist only): {model_id}")
|
| 219 |
+
|
| 220 |
+
# Use torchvision's default pretrained weights for densenet121.
|
| 221 |
+
# torchvision์ densenet121 ๊ธฐ๋ณธ pretrained weights๋ฅผ ์ฌ์ฉํฉ๋๋ค.
|
| 222 |
+
ref = tv_models.densenet121(weights=tv_models.DenseNet121_Weights.DEFAULT).eval()
|
| 223 |
+
|
|
|
|
|
|
|
|
|
|
| 224 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 225 |
del ref
|
| 226 |
|
| 227 |
# ----------------------------
|
| 228 |
# feature extraction
|
| 229 |
+
# feature ์ถ์ถ
|
| 230 |
# ----------------------------
|
| 231 |
@staticmethod
|
| 232 |
def _pool_or_gap(outputs) -> torch.Tensor:
|
| 233 |
+
# Some transformers vision CNNs provide pooler_output explicitly.
|
| 234 |
+
# ์ผ๋ถ transformers vision CNN์ pooler_output์ ๋ช
์์ ์ผ๋ก ์ ๊ณตํฉ๋๋ค.
|
| 235 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 236 |
x = outputs.pooler_output
|
| 237 |
if x.dim() == 2:
|
|
|
|
| 240 |
return x.flatten(1)
|
| 241 |
raise RuntimeError(f"Unexpected pooler_output shape: {tuple(x.shape)}")
|
| 242 |
|
| 243 |
+
# Otherwise we expect a CNN-style last_hidden_state=(B,C,H,W) and apply GAP.
|
| 244 |
+
# ๊ทธ๋ ์ง ์์ผ๋ฉด CNN ์คํ์ผ last_hidden_state=(B,C,H,W)๋ฅผ ๊ธฐ๋ํ๊ณ GAP์ ์ ์ฉํฉ๋๋ค.
|
| 245 |
x = outputs.last_hidden_state
|
| 246 |
if x.dim() == 4:
|
| 247 |
return x.mean(dim=(2, 3))
|
| 248 |
+
|
| 249 |
raise RuntimeError(
|
| 250 |
"Expected pooler_output or (B,C,H,W) last_hidden_state for CNN backbones. "
|
| 251 |
f"Got last_hidden_state shape={tuple(x.shape)}"
|
| 252 |
)
|
| 253 |
|
| 254 |
def _extract_features(self, outputs, pixel_values: Optional[torch.Tensor] = None) -> torch.Tensor:
|
| 255 |
+
# Feature rule is defined by BACKBONE_META and must remain stable across saves/loads.
|
| 256 |
+
# feature ๊ท์น์ BACKBONE_META๋ก ์ ์๋๋ฉฐ ์ ์ฅ/๋ก๋ ๊ฐ ์์ ์ ์ผ๋ก ์ ์ง๋์ด์ผ ํฉ๋๋ค.
|
| 257 |
rule = self._meta["feat_rule"]
|
| 258 |
|
| 259 |
if rule == "cls":
|
| 260 |
+
# ViT-style: use CLS token embedding from last_hidden_state.
|
| 261 |
+
# ViT ์คํ์ผ: last_hidden_state์์ CLS ํ ํฐ ์๋ฒ ๋ฉ์ ์ฌ์ฉํฉ๋๋ค.
|
| 262 |
return outputs.last_hidden_state[:, 0, :]
|
| 263 |
|
| 264 |
if rule == "pool_or_mean":
|
| 265 |
+
# Swin-style: prefer pooler_output if present, else mean-pool over tokens.
|
| 266 |
+
# Swin ์คํ์ผ: pooler_output์ด ์์ผ๋ฉด ์ฐ์ ์ฌ์ฉํ๊ณ , ์์ผ๋ฉด ํ ํฐ ํ๊ท ํ๋ง์ ์ฌ์ฉํฉ๋๋ค.
|
| 267 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 268 |
return outputs.pooler_output
|
| 269 |
return outputs.last_hidden_state.mean(dim=1)
|
| 270 |
|
| 271 |
if rule == "pool_or_gap":
|
| 272 |
+
# CNN-style: use pooler_output if present, else GAP over spatial dims.
|
| 273 |
+
# CNN ์คํ์ผ: pooler_output์ด ์์ผ๋ฉด ์ฌ์ฉํ๊ณ , ์์ผ๋ฉด ๊ณต๊ฐ ์ฐจ์ GAP์ ์ฌ์ฉํฉ๋๋ค.
|
| 274 |
return self._pool_or_gap(outputs)
|
| 275 |
|
| 276 |
if rule == "timm_gap":
|
| 277 |
+
# timm forward_features returns a feature map (B,C,H,W) which we GAP to (B,C).
|
| 278 |
+
# timm forward_features๋ (B,C,H,W) feature map์ ๋ฐํํ๋ฉฐ ์ด๋ฅผ GAP์ผ๋ก (B,C)๋ก ๋ง๋ญ๋๋ค.
|
| 279 |
if not isinstance(outputs, torch.Tensor):
|
| 280 |
raise TypeError(f"timm_gap expects Tensor features, got {type(outputs)}")
|
| 281 |
if outputs.dim() != 4:
|
|
|
|
| 283 |
return outputs.mean(dim=(2, 3))
|
| 284 |
|
| 285 |
if rule == "torchvision_densenet_gap":
|
| 286 |
+
# torchvision DenseNet features are feature maps (B,C,H,W) and require GAP.
|
| 287 |
+
# torchvision DenseNet features๋ (B,C,H,W) feature map์ด๋ฉฐ GAP์ด ํ์ํฉ๋๋ค.
|
| 288 |
if not isinstance(outputs, torch.Tensor):
|
| 289 |
raise TypeError(f"torchvision_densenet_gap expects Tensor, got {type(outputs)}")
|
| 290 |
if outputs.dim() != 4:
|
|
|
|
| 302 |
return_dict=True,
|
| 303 |
**kwargs,
|
| 304 |
):
|
| 305 |
+
# Type decides the backbone forward path and output format.
|
| 306 |
+
# type์ด backbone forward ๊ฒฝ๋ก ๋ฐ ์ถ๋ ฅ ํฌ๋งท์ ๊ฒฐ์ ํฉ๋๋ค.
|
| 307 |
t = self._meta["type"]
|
| 308 |
|
| 309 |
if t == "timm_densenet":
|
| 310 |
+
# timm DenseNet consumes pixel_values as a 4D tensor (B,C,H,W).
|
| 311 |
+
# timm DenseNet์ pixel_values๋ฅผ 4D ํ
์ (B,C,H,W)๋ก ๋ฐ์ต๋๋ค.
|
| 312 |
if pixel_values is None:
|
| 313 |
raise ValueError("timm DenseNet backbone requires pixel_values.")
|
| 314 |
if pixel_values.dim() != 4:
|
|
|
|
| 320 |
attentions = None
|
| 321 |
|
| 322 |
elif t == "torchvision_densenet":
|
| 323 |
+
# torchvision DenseNet consumes pixel_values as a 4D tensor (B,C,H,W).
|
| 324 |
+
# torchvision DenseNet์ pixel_values๋ฅผ 4D ํ
์ (B,C,H,W)๋ก ๋ฐ์ต๋๋ค.
|
| 325 |
if pixel_values is None:
|
| 326 |
raise ValueError("torchvision DenseNet backbone requires pixel_values.")
|
| 327 |
if pixel_values.dim() != 4:
|
|
|
|
| 334 |
attentions = None
|
| 335 |
|
| 336 |
else:
|
| 337 |
+
# Transformers vision models are called with pixel_values and return ModelOutput.
|
| 338 |
+
# transformers vision ๋ชจ๋ธ์ pixel_values๋ก ํธ์ถ๋๋ฉฐ ModelOutput์ ๋ฐํํฉ๋๋ค.
|
| 339 |
outputs = self.backbone(
|
| 340 |
pixel_values=pixel_values,
|
| 341 |
output_attentions=output_attentions,
|
|
|
|
| 347 |
hidden_states = getattr(outputs, "hidden_states", None)
|
| 348 |
attentions = getattr(outputs, "attentions", None)
|
| 349 |
|
| 350 |
+
# Classifier consumes (B, feat_dim) and returns logits (B, num_labels).
|
| 351 |
+
# classifier๋ (B, feat_dim)์ ๋ฐ์ logits (B, num_labels)๋ฅผ ๋ฐํํฉ๋๋ค.
|
| 352 |
logits = self.classifier(feats)
|
| 353 |
|
| 354 |
loss = None
|
| 355 |
if labels is not None:
|
| 356 |
+
# Cross entropy expects labels as class indices in [0, num_labels).
|
| 357 |
+
# cross entropy๋ labels๊ฐ [0, num_labels) ๋ฒ์์ class index์ด๊ธธ ๊ธฐ๋ํฉ๋๋ค.
|
| 358 |
loss = F.cross_entropy(logits, labels)
|
| 359 |
|
| 360 |
if not return_dict:
|
|
|
|
| 370 |
|
| 371 |
|
| 372 |
# ============================================================
|
| 373 |
+
# Freeze / Unfreeze utilities
|
| 374 |
+
# Freeze / Unfreeze ์ ํธ๋ฆฌํฐ
|
| 375 |
# ============================================================
|
| 376 |
def _set_requires_grad(module: nn.Module, flag: bool):
|
| 377 |
+
# Toggle requires_grad for all parameters in a module.
|
| 378 |
+
# ๋ชจ๋์ ๋ชจ๋ ํ๋ผ๋ฏธํฐ์ ๋ํด requires_grad๋ฅผ ํ ๊ธํฉ๋๋ค.
|
| 379 |
for p in module.parameters():
|
| 380 |
p.requires_grad = flag
|
| 381 |
|
| 382 |
|
| 383 |
def set_bn_eval(module: nn.Module):
|
| 384 |
+
# Put BatchNorm layers into eval mode to freeze running stats.
|
| 385 |
+
# BatchNorm ๋ ์ด์ด๋ฅผ eval ๋ชจ๋๋ก ๋์ด running stats๋ฅผ ๊ณ ์ ํฉ๋๋ค.
|
| 386 |
for m in module.modules():
|
| 387 |
if isinstance(m, (nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d, nn.SyncBatchNorm)):
|
| 388 |
m.eval()
|
| 389 |
|
| 390 |
|
| 391 |
def freeze_backbone(model: BackboneWithMLPHeadForImageClassification, freeze_bn: bool = True):
|
| 392 |
+
# Stage1: freeze backbone and train only the head.
|
| 393 |
+
# stage1: backbone์ freezeํ๊ณ head๋ง ํ์ตํฉ๋๋ค.
|
| 394 |
_set_requires_grad(model.backbone, False)
|
| 395 |
_set_requires_grad(model.classifier, True)
|
| 396 |
|
|
|
|
| 400 |
|
| 401 |
|
| 402 |
def finetune_train_mode(model: BackboneWithMLPHeadForImageClassification, keep_bn_eval: bool = True):
|
| 403 |
+
# Stage2: train mode, optionally keeping BN layers in eval for stability.
|
| 404 |
+
# stage2: train ๋ชจ๋๋ก ๋๋ ์์ ์ฑ์ ์ํด BN์ eval๋ก ์ ์งํ ์ ์์ต๋๋ค.
|
| 405 |
model.train()
|
| 406 |
meta = BACKBONE_META[model.config.backbone_name_or_path]
|
| 407 |
if keep_bn_eval and meta.get("has_bn", False):
|
|
|
|
| 409 |
|
| 410 |
|
| 411 |
def trainable_summary(model: nn.Module):
|
| 412 |
+
# Print a compact summary of trainable parameters.
|
| 413 |
+
# ํ์ต ๊ฐ๋ฅ ํ๋ผ๋ฏธํฐ ์์ฝ์ ๊ฐ๋จํ ์ถ๋ ฅํฉ๋๋ค.
|
| 414 |
total = sum(p.numel() for p in model.parameters())
|
| 415 |
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 416 |
ratio = trainable / total if total > 0 else 0.0
|
|
|
|
| 423 |
last_n: int = 2,
|
| 424 |
keep_bn_eval: bool = True,
|
| 425 |
):
|
| 426 |
+
# This utility implements BACKBONE_META['unfreeze']=="last_n" across supported backbones.
|
| 427 |
+
# ์ด ์ ํธ์ ์ง์ ๋ฐฑ๋ณธ๋ค์ ๋ํด BACKBONE_META['unfreeze']=="last_n"์ ๊ตฌํํฉ๋๋ค.
|
| 428 |
freeze_backbone(model, freeze_bn=keep_bn_eval)
|
| 429 |
|
| 430 |
n = int(last_n)
|
|
|
|
| 438 |
bb_type = meta["type"]
|
| 439 |
|
| 440 |
if bb_type == "vit":
|
| 441 |
+
# ViT blocks live under backbone.encoder.layer in the transformers implementation.
|
| 442 |
+
# ViT ๋ธ๋ก์ transformers ๊ตฌํ์์ backbone.encoder.layer ์๋์ ์์ต๋๋ค.
|
| 443 |
blocks = list(model.backbone.encoder.layer)
|
| 444 |
for blk in blocks[-n:]:
|
| 445 |
_set_requires_grad(blk, True)
|
| 446 |
return
|
| 447 |
|
| 448 |
if bb_type == "swin":
|
| 449 |
+
# Swin blocks are nested by stages and blocks; we flatten and unfreeze last n blocks.
|
| 450 |
+
# Swin ๋ธ๋ก์ stage์ block์ผ๋ก ์ค์ฒฉ๋์ด ์์ด ํผ์น ํ ๋ง์ง๋ง n๊ฐ๋ฅผ unfreeze ํฉ๋๋ค.
|
| 451 |
stages = list(model.backbone.encoder.layers)
|
| 452 |
blocks: List[nn.Module] = []
|
| 453 |
for st in stages:
|
|
|
|
| 457 |
return
|
| 458 |
|
| 459 |
if bb_type == "resnet":
|
| 460 |
+
# ResNet uses layer1..layer4 stages; we unfreeze at block granularity.
|
| 461 |
+
# ResNet์ layer1..layer4 stage๋ฅผ ์ฌ์ฉํ๋ฉฐ block ๋จ์๋ก unfreeze ํฉ๋๋ค.
|
| 462 |
bb = model.backbone
|
| 463 |
for name in ("layer1", "layer2", "layer3", "layer4"):
|
| 464 |
if not hasattr(bb, name):
|
|
|
|
| 478 |
return
|
| 479 |
|
| 480 |
if bb_type == "efficientnet":
|
| 481 |
+
# EfficientNet in transformers exposes features; we unfreeze from the tail blocks.
|
| 482 |
+
# transformers EfficientNet์ features๋ฅผ ๋
ธ์ถํ๋ฉฐ ๋ค์ชฝ ๋ธ๋ก๋ถํฐ unfreeze ํฉ๋๋ค.
|
| 483 |
bb = model.backbone
|
| 484 |
if not hasattr(bb, "features"):
|
| 485 |
raise RuntimeError("Unexpected EfficientNet structure: missing features")
|
|
|
|
| 496 |
return
|
| 497 |
|
| 498 |
if bb_type in ("timm_densenet", "torchvision_densenet"):
|
| 499 |
+
# DenseNet exposes a .features module with named blocks; we unfreeze last n submodules.
|
| 500 |
+
# DenseNet์ .features ๋ชจ๋์ ๋ธ๋ก๋ค์ด ์ด๋ฆ์ผ๋ก ์กด์ฌํ๋ฉฐ ๋ง์ง๋ง n๊ฐ ์๋ธ๋ชจ๋์ unfreeze ํฉ๋๋ค.
|
| 501 |
bb = model.backbone
|
| 502 |
if not hasattr(bb, "features"):
|
| 503 |
raise RuntimeError("Unexpected DenseNet: missing features")
|
|
|
|
| 515 |
raise RuntimeError(f"Unexpected DenseNet features: missing {name}")
|
| 516 |
|
| 517 |
def _denselayers(db: nn.Module) -> List[nn.Module]:
|
| 518 |
+
# Dense blocks contain multiple DenseLayer children; we return them for fine-grained unfreezing.
|
| 519 |
+
# denseblock์ DenseLayer ์์๋ค์ ๊ฐ์ง๋ฏ๋ก ์ธ๋ฐํ unfreeze๋ฅผ ์ํด ์ด๋ฅผ ๋ฐํํฉ๋๋ค.
|
| 520 |
return list(db.children())
|
| 521 |
|
| 522 |
blocks: List[nn.Module] = []
|
|
|
|
| 538 |
|
| 539 |
# -------------------------
|
| 540 |
# register
|
| 541 |
+
# register
|
| 542 |
+
# -------------------------
|
| 543 |
+
# Register for AutoModelForImageClassification so from_pretrained can resolve this custom class.
|
| 544 |
+
# from_pretrained๊ฐ ์ด ์ปค์คํ
ํด๋์ค๋ฅผ ํด์ํ ์ ์๋๋ก AutoModelForImageClassification์ ๋ฑ๋กํฉ๋๋ค.
|
| 545 |
BackboneWithMLPHeadForImageClassification.register_for_auto_class("AutoModelForImageClassification")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
models/timm__densenet121.tv_in1k/ds_proc.py
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# src/ds_proc.py
|
| 5 |
+
|
| 6 |
+
# ============================================================
|
| 7 |
+
# (4) ImageProcessor (AutoImageProcessor integration)
|
| 8 |
+
# (4) ImageProcessor (AutoImageProcessor ์ฐ๋)
|
| 9 |
+
# ============================================================
|
| 10 |
+
|
| 11 |
+
from typing import Any
|
| 12 |
+
|
| 13 |
+
import numpy as np
|
| 14 |
+
import torch
|
| 15 |
+
from transformers import AutoImageProcessor, AutoConfig
|
| 16 |
+
from transformers.image_processing_base import ImageProcessingMixin
|
| 17 |
+
from transformers.utils.generic import TensorType
|
| 18 |
+
|
| 19 |
+
from ds_meta import BackboneID, BACKBONE_META
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class BackboneMLPHead224ImageProcessor(ImageProcessingMixin):
|
| 23 |
+
"""
|
| 24 |
+
This processor performs image preprocessing and outputs {"pixel_values": ...}.
|
| 25 |
+
์ด processor๋ ์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ๋ฅผ ์ํํ๊ณ {"pixel_values": ...}๋ฅผ ์ถ๋ ฅํฉ๋๋ค.
|
| 26 |
+
|
| 27 |
+
Key requirements:
|
| 28 |
+
ํต์ฌ ์๊ตฌ์ฌํญ:
|
| 29 |
+
|
| 30 |
+
1) save_pretrained() must produce a JSON-serializable preprocessor_config.json.
|
| 31 |
+
save_pretrained()๋ JSON ์ง๋ ฌํ ๊ฐ๋ฅํ preprocessor_config.json์ ์์ฑํด์ผ ํฉ๋๋ค.
|
| 32 |
+
|
| 33 |
+
2) Runtime-only objects (delegate processor, timm/torchvision transforms) must NOT be serialized.
|
| 34 |
+
๋ฐํ์ ๊ฐ์ฒด(delegate processor, timm/torchvision transform)๋ ์ ๋ ์ง๋ ฌํํ๋ฉด ์ ๋ฉ๋๋ค.
|
| 35 |
+
|
| 36 |
+
3) Runtime objects are rebuilt at init/load time based on backbone meta.
|
| 37 |
+
๋ฐํ์ ๊ฐ์ฒด๋ backbone meta์ ๋ฐ๋ผ init/load ์์ ์ ์ฌ๊ตฌ์ฑ๋ฉ๋๋ค.
|
| 38 |
+
"""
|
| 39 |
+
|
| 40 |
+
# HF vision models conventionally expect "pixel_values" as the primary input key.
|
| 41 |
+
# HF vision ๋ชจ๋ธ์ ๊ด๋ก์ ์ผ๋ก ์
๋ ฅ ํค๋ก "pixel_values"๋ฅผ ๊ธฐ๋ํฉ๋๋ค.
|
| 42 |
+
model_input_names = ["pixel_values"]
|
| 43 |
+
|
| 44 |
+
def __init__(self, backbone_name_or_path: BackboneID, is_training: bool = False, **kwargs):
|
| 45 |
+
# ImageProcessingMixin stores extra kwargs and manages auto_map metadata.
|
| 46 |
+
# ImageProcessingMixin์ ์ถ๊ฐ kwargs๋ฅผ ์ ์ฅํ๊ณ auto_map ๋ฉํ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.
|
| 47 |
+
super().__init__(**kwargs)
|
| 48 |
+
|
| 49 |
+
# Enforce whitelist via BACKBONE_META to keep behavior stable.
|
| 50 |
+
# ๋์ ์์ ์ฑ์ ์ํด BACKBONE_META ๊ธฐ๋ฐ ํ์ดํธ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ ํฉ๋๋ค.
|
| 51 |
+
if backbone_name_or_path not in BACKBONE_META:
|
| 52 |
+
raise ValueError(
|
| 53 |
+
f"Unsupported backbone_name_or_path={backbone_name_or_path}. "
|
| 54 |
+
f"Allowed: {sorted(BACKBONE_META.keys())}"
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
# Serializable fields only: these should appear in preprocessor_config.json.
|
| 58 |
+
# ์ง๋ ฌํ ๊ฐ๋ฅํ ํ๋๋ง: ์ด ๊ฐ๋ค๋ง preprocessor_config.json์ ๋ค์ด๊ฐ์ผ ํฉ๋๋ค.
|
| 59 |
+
self.backbone_name_or_path = backbone_name_or_path
|
| 60 |
+
self.is_training = bool(is_training)
|
| 61 |
+
|
| 62 |
+
# Runtime-only fields: must never be serialized.
|
| 63 |
+
# ๋ฐํ์ ์ ์ฉ ํ๋: ์ ๋ ์ง๋ ฌํ๋๋ฉด ์ ๋ฉ๋๋ค.
|
| 64 |
+
self._meta = None
|
| 65 |
+
self._delegate = None
|
| 66 |
+
self._timm_transform = None
|
| 67 |
+
self._torchvision_transform = None
|
| 68 |
+
|
| 69 |
+
# Build runtime objects according to backbone type.
|
| 70 |
+
# backbone type์ ๋ฐ๋ผ ๋ฐํ์ ๊ฐ์ฒด๋ฅผ ๊ตฌ์ฑํฉ๋๋ค.
|
| 71 |
+
self._build_runtime()
|
| 72 |
+
|
| 73 |
+
# ============================================================
|
| 74 |
+
# Runtime builders
|
| 75 |
+
# ๋ฐํ์ ๋น๋
|
| 76 |
+
# ============================================================
|
| 77 |
+
def _build_runtime(self):
|
| 78 |
+
"""
|
| 79 |
+
Build runtime delegate/transform based on BACKBONE_META["type"].
|
| 80 |
+
BACKBONE_META["type"]์ ๋ฐ๋ผ ๋ฐํ์ delegate/transform์ ๊ตฌ์ฑํฉ๋๋ค.
|
| 81 |
+
"""
|
| 82 |
+
meta = BACKBONE_META[self.backbone_name_or_path]
|
| 83 |
+
self._meta = meta
|
| 84 |
+
|
| 85 |
+
# Always reset runtime fields before rebuilding.
|
| 86 |
+
# ์ฌ๊ตฌ์ฑ ์ ๋ฐํ์ ํ๋๋ ํญ์ ์ด๊ธฐํํฉ๋๋ค.
|
| 87 |
+
self._delegate = None
|
| 88 |
+
self._timm_transform = None
|
| 89 |
+
self._torchvision_transform = None
|
| 90 |
+
|
| 91 |
+
t = meta["type"]
|
| 92 |
+
|
| 93 |
+
if t == "timm_densenet":
|
| 94 |
+
# timm DenseNet uses timm.data transforms for ImageNet-style preprocessing.
|
| 95 |
+
# timm DenseNet์ ImageNet ์ ์ฒ๋ฆฌ๋ฅผ ์ํด timm.data transform์ ์ฌ์ฉํฉ๋๋ค.
|
| 96 |
+
self._timm_transform = self._build_timm_transform(
|
| 97 |
+
backbone_id=self.backbone_name_or_path,
|
| 98 |
+
is_training=self.is_training,
|
| 99 |
+
)
|
| 100 |
+
return
|
| 101 |
+
|
| 102 |
+
if t == "torchvision_densenet":
|
| 103 |
+
# torchvision DenseNet requires torchvision-style preprocessing (resize/crop/tensor/normalize).
|
| 104 |
+
# torchvision DenseNet์ torchvision ์คํ์ผ ์ ์ฒ๋ฆฌ(resize/crop/tensor/normalize)๊ฐ ํ์ํฉ๋๋ค.
|
| 105 |
+
self._torchvision_transform = self._build_torchvision_densenet_transform(
|
| 106 |
+
is_training=self.is_training
|
| 107 |
+
)
|
| 108 |
+
return
|
| 109 |
+
|
| 110 |
+
# Default: transformers backbone delegates to its official AutoImageProcessor.
|
| 111 |
+
# ๊ธฐ๋ณธ: transformers ๋ฐฑ๋ณธ์ ๊ณต์ AutoImageProcessor์ ์์ํฉ๋๋ค.
|
| 112 |
+
self._delegate = AutoImageProcessor.from_pretrained(self.backbone_name_or_path)
|
| 113 |
+
|
| 114 |
+
@staticmethod
|
| 115 |
+
def _build_timm_transform(*, backbone_id: str, is_training: bool):
|
| 116 |
+
"""
|
| 117 |
+
Create timm transform without storing non-serializable objects in config.
|
| 118 |
+
๋น์ง๋ ฌํ ๊ฐ์ฒด๋ฅผ config์ ์ ์ฅํ์ง ์๊ณ timm transform์ ์์ฑํฉ๋๋ค.
|
| 119 |
+
"""
|
| 120 |
+
try:
|
| 121 |
+
import timm
|
| 122 |
+
from timm.data import resolve_model_data_config, create_transform
|
| 123 |
+
except Exception as e:
|
| 124 |
+
raise ImportError(
|
| 125 |
+
"timm backbone processor requires `timm`. Install: pip install timm"
|
| 126 |
+
) from e
|
| 127 |
+
|
| 128 |
+
# We only need model metadata to resolve data config, so pretrained=False is preferred.
|
| 129 |
+
# data config ์ถ์ถ๋ง ํ์ํ๋ฏ๋ก pretrained=False๋ฅผ ์ฐ์ ์ฌ์ฉํฉ๋๋ค.
|
| 130 |
+
m = timm.create_model(f"hf_hub:{backbone_id}", pretrained=False, num_classes=0)
|
| 131 |
+
dc = resolve_model_data_config(m)
|
| 132 |
+
|
| 133 |
+
# create_transform returns a torchvision-like callable that maps PIL -> torch.Tensor(C,H,W).
|
| 134 |
+
# create_transform์ PIL -> torch.Tensor(C,H,W)๋ก ๋งคํํ๋ callable์ ๋ฐํํฉ๋๋ค.
|
| 135 |
+
tfm = create_transform(**dc, is_training=is_training)
|
| 136 |
+
return tfm
|
| 137 |
+
|
| 138 |
+
@staticmethod
|
| 139 |
+
def _build_torchvision_densenet_transform(*, is_training: bool):
|
| 140 |
+
"""
|
| 141 |
+
Build torchvision preprocessing for DenseNet-121 (224 pipeline).
|
| 142 |
+
DenseNet-121์ฉ torchvision ์ ์ฒ๋ฆฌ(224 ํ์ดํ๋ผ์ธ)๋ฅผ ๊ตฌ์ฑํฉ๋๋ค.
|
| 143 |
+
"""
|
| 144 |
+
try:
|
| 145 |
+
from torchvision import transforms
|
| 146 |
+
except Exception as e:
|
| 147 |
+
raise ImportError(
|
| 148 |
+
"torchvision DenseNet processor requires `torchvision`. Install: pip install torchvision"
|
| 149 |
+
) from e
|
| 150 |
+
|
| 151 |
+
# These are the standard ImageNet normalization stats used by torchvision weights.
|
| 152 |
+
# ์ด ๊ฐ๋ค์ torchvision weights๊ฐ ์ฌ์ฉํ๋ ํ์ค ImageNet ์ ๊ทํ ํต๊ณ์
๋๋ค.
|
| 153 |
+
mean = (0.485, 0.456, 0.406)
|
| 154 |
+
std = (0.229, 0.224, 0.225)
|
| 155 |
+
|
| 156 |
+
# Training pipeline typically uses RandomResizedCrop and horizontal flip.
|
| 157 |
+
# ํ์ต ํ์ดํ๋ผ์ธ์ ๋ณดํต RandomResizedCrop๊ณผ ์ข์ฐ๋ฐ์ ์ ์ฌ์ฉํฉ๋๋ค.
|
| 158 |
+
if is_training:
|
| 159 |
+
return transforms.Compose(
|
| 160 |
+
[
|
| 161 |
+
transforms.RandomResizedCrop(224),
|
| 162 |
+
transforms.RandomHorizontalFlip(p=0.5),
|
| 163 |
+
transforms.ToTensor(),
|
| 164 |
+
transforms.Normalize(mean=mean, std=std),
|
| 165 |
+
]
|
| 166 |
+
)
|
| 167 |
+
|
| 168 |
+
# Inference pipeline typically uses Resize(256) + CenterCrop(224).
|
| 169 |
+
# ์ถ๋ก ํ์ดํ๋ผ์ธ์ ๋ณดํต Resize(256) + CenterCrop(224)๋ฅผ ์ฌ์ฉํฉ๋๋ค.
|
| 170 |
+
return transforms.Compose(
|
| 171 |
+
[
|
| 172 |
+
transforms.Resize(256),
|
| 173 |
+
transforms.CenterCrop(224),
|
| 174 |
+
transforms.ToTensor(),
|
| 175 |
+
transforms.Normalize(mean=mean, std=std),
|
| 176 |
+
]
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
# ============================================================
|
| 180 |
+
# Serialization
|
| 181 |
+
# ์ง๋ ฌํ
|
| 182 |
+
# ============================================================
|
| 183 |
+
def to_dict(self) -> dict[str, Any]:
|
| 184 |
+
"""
|
| 185 |
+
Return a JSON-serializable dict for preprocessor_config.json.
|
| 186 |
+
preprocessor_config.json์ ๋ค์ด๊ฐ JSON ์ง๋ ฌํ dict๋ฅผ ๋ฐํํฉ๋๋ค.
|
| 187 |
+
|
| 188 |
+
Important: do not leak runtime objects into the serialized dict.
|
| 189 |
+
์ค์: ๋ฐํ์ ๊ฐ์ฒด๊ฐ ์ง๋ ฌํ dict์ ์์ด๋ฉด ์ ๋ฉ๋๋ค.
|
| 190 |
+
"""
|
| 191 |
+
# ImageProcessingMixin.to_dict() adds metadata such as image_processor_type/auto_map.
|
| 192 |
+
# ImageProcessingMixin.to_dict()๋ image_processor_type/auto_map ๊ฐ์ ๋ฉํ๋ฅผ ์ถ๊ฐํฉ๋๋ค.
|
| 193 |
+
d = super().to_dict()
|
| 194 |
+
|
| 195 |
+
# Force minimal stable fields for long-term compatibility.
|
| 196 |
+
# ์ฅ๊ธฐ ํธํ์ ์ํด ์ต์ ์์ ํ๋๋ฅผ ๊ฐ์ ํฉ๋๋ค.
|
| 197 |
+
d["image_processor_type"] = self.__class__.__name__
|
| 198 |
+
d["backbone_name_or_path"] = self.backbone_name_or_path
|
| 199 |
+
d["is_training"] = self.is_training
|
| 200 |
+
|
| 201 |
+
# Remove any runtime-only fields defensively.
|
| 202 |
+
# ๋ฐํ์ ์ ์ฉ ํ๋๋ ๋ณด์์ ์ผ๋ก ์ ๊ฑฐํฉ๋๋ค.
|
| 203 |
+
for key in ["_meta", "_delegate", "_timm_transform", "_torchvision_transform"]:
|
| 204 |
+
d.pop(key, None)
|
| 205 |
+
|
| 206 |
+
return d
|
| 207 |
+
|
| 208 |
+
@classmethod
|
| 209 |
+
def from_dict(cls, image_processor_dict: dict[str, Any], **kwargs):
|
| 210 |
+
"""
|
| 211 |
+
Standard load path used by BaseImageProcessor / AutoImageProcessor.
|
| 212 |
+
BaseImageProcessor / AutoImageProcessor๊ฐ ์ฌ์ฉํ๋ ํ์ค ๋ก๋ ๊ฒฝ๋ก์
๋๋ค.
|
| 213 |
+
"""
|
| 214 |
+
backbone = image_processor_dict.get("backbone_name_or_path", None)
|
| 215 |
+
if backbone is None:
|
| 216 |
+
raise ValueError("preprocessor_config.json missing key: backbone_name_or_path")
|
| 217 |
+
|
| 218 |
+
is_training = bool(image_processor_dict.get("is_training", False))
|
| 219 |
+
return cls(backbone_name_or_path=backbone, is_training=is_training, **kwargs)
|
| 220 |
+
|
| 221 |
+
@classmethod
|
| 222 |
+
def from_pretrained(cls, pretrained_model_name_or_path: str, **kwargs):
|
| 223 |
+
"""
|
| 224 |
+
Fallback path if AutoImageProcessor calls class.from_pretrained directly.
|
| 225 |
+
AutoImageProcessor๊ฐ class.from_pretrained๋ฅผ ์ง์ ํธ์ถํ๋ ๊ฒฝ์ฐ๋ฅผ ๋๋นํ ๊ฒฝ๋ก์
๋๋ค.
|
| 226 |
+
|
| 227 |
+
Strategy:
|
| 228 |
+
์ ๋ต:
|
| 229 |
+
|
| 230 |
+
- Read config.json via AutoConfig and recover backbone_name_or_path.
|
| 231 |
+
AutoConfig๋ก config.json์ ์ฝ๊ณ backbone_name_or_path๋ฅผ ๋ณต๊ตฌํฉ๋๋ค.
|
| 232 |
+
"""
|
| 233 |
+
cfg = AutoConfig.from_pretrained(pretrained_model_name_or_path, **kwargs)
|
| 234 |
+
backbone = getattr(cfg, "backbone_name_or_path", None)
|
| 235 |
+
if backbone is None:
|
| 236 |
+
raise ValueError("Cannot build processor: backbone_name_or_path not found in config.json")
|
| 237 |
+
|
| 238 |
+
# is_training is runtime-only and should default to False for inference/serving.
|
| 239 |
+
# is_training์ ๋ฐํ์ ์ ์ฉ์ด๋ฉฐ ์ถ๋ก /์๋น ๊ธฐ๋ณธ๊ฐ์ False๊ฐ ๋ง์ต๋๋ค.
|
| 240 |
+
return cls(backbone_name_or_path=backbone, is_training=False)
|
| 241 |
+
|
| 242 |
+
# ============================================================
|
| 243 |
+
# Call interface
|
| 244 |
+
# ํธ์ถ ์ธํฐํ์ด์ค
|
| 245 |
+
# ============================================================
|
| 246 |
+
@staticmethod
|
| 247 |
+
def _ensure_list(images: Any) -> list[Any]:
|
| 248 |
+
# Normalize scalar image input to a list for uniform processing.
|
| 249 |
+
# ๋จ์ผ ์
๋ ฅ์ ๋ฆฌ์คํธ๋ก ์ ๊ทํํ์ฌ ๋์ผํ ์ฒ๋ฆฌ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํฉ๋๋ค.
|
| 250 |
+
if isinstance(images, (list, tuple)):
|
| 251 |
+
return list(images)
|
| 252 |
+
return [images]
|
| 253 |
+
|
| 254 |
+
@staticmethod
|
| 255 |
+
def _to_pil_rgb(x: Any):
|
| 256 |
+
# Convert common image inputs into PIL RGB images.
|
| 257 |
+
# ์ผ๋ฐ์ ์ธ ์
๋ ฅ์ PIL RGB ์ด๋ฏธ์ง๋ก ๋ณํํฉ๋๋ค.
|
| 258 |
+
from PIL import Image as PILImage
|
| 259 |
+
|
| 260 |
+
if isinstance(x, PILImage.Image):
|
| 261 |
+
return x.convert("RGB")
|
| 262 |
+
if isinstance(x, np.ndarray) and x.ndim == 3:
|
| 263 |
+
return PILImage.fromarray(x).convert("RGB")
|
| 264 |
+
raise TypeError(f"Unsupported image type: {type(x)}")
|
| 265 |
+
|
| 266 |
+
def __call__(
|
| 267 |
+
self,
|
| 268 |
+
images: Any | list[Any],
|
| 269 |
+
return_tensors: str | TensorType | None = "pt",
|
| 270 |
+
**kwargs,
|
| 271 |
+
) -> dict[str, Any]:
|
| 272 |
+
"""
|
| 273 |
+
Convert images into {"pixel_values": Tensor/ndarray}.
|
| 274 |
+
์ด๋ฏธ์ง๋ฅผ {"pixel_values": Tensor/ndarray}๋ก ๋ณํํฉ๋๋ค.
|
| 275 |
+
"""
|
| 276 |
+
images = self._ensure_list(images)
|
| 277 |
+
|
| 278 |
+
# Rebuild runtime if needed (e.g., right after deserialization).
|
| 279 |
+
# ์ง๋ ฌํ ๋ณต์ ์งํ ๋ฑ ๋ฐํ์์ด ๋น์ด์์ ์ ์์ผ๋ฏ๋ก ์ฌ๊ตฌ์ฑํฉ๋๋ค.
|
| 280 |
+
if (self._delegate is None) and (self._timm_transform is None) and (self._torchvision_transform is None):
|
| 281 |
+
self._build_runtime()
|
| 282 |
+
|
| 283 |
+
# timm path: PIL -> torch.Tensor(C,H,W) normalized float32.
|
| 284 |
+
# timm ๊ฒฝ๋ก: PIL -> torch.Tensor(C,H,W) ์ ๊ทํ float32์
๋๋ค.
|
| 285 |
+
if self._timm_transform is not None:
|
| 286 |
+
pv: list[torch.Tensor] = []
|
| 287 |
+
for im in images:
|
| 288 |
+
pil = self._to_pil_rgb(im)
|
| 289 |
+
t = self._timm_transform(pil)
|
| 290 |
+
if not isinstance(t, torch.Tensor):
|
| 291 |
+
raise RuntimeError("Unexpected timm transform output (expected torch.Tensor).")
|
| 292 |
+
pv.append(t)
|
| 293 |
+
pixel_values = torch.stack(pv, dim=0) # (B,C,H,W)
|
| 294 |
+
return self._format_return(pixel_values, return_tensors)
|
| 295 |
+
|
| 296 |
+
# torchvision path: PIL -> torch.Tensor(C,H,W) normalized float32.
|
| 297 |
+
# torchvision ๊ฒฝ๋ก: PIL -> torch.Tensor(C,H,W) ์ ๊ทํ float32์
๋๋ค.
|
| 298 |
+
if self._torchvision_transform is not None:
|
| 299 |
+
pv: list[torch.Tensor] = []
|
| 300 |
+
for im in images:
|
| 301 |
+
pil = self._to_pil_rgb(im)
|
| 302 |
+
t = self._torchvision_transform(pil)
|
| 303 |
+
if not isinstance(t, torch.Tensor):
|
| 304 |
+
raise RuntimeError("Unexpected torchvision transform output (expected torch.Tensor).")
|
| 305 |
+
pv.append(t)
|
| 306 |
+
pixel_values = torch.stack(pv, dim=0) # (B,C,H,W)
|
| 307 |
+
return self._format_return(pixel_values, return_tensors)
|
| 308 |
+
|
| 309 |
+
# transformers delegate path: rely on official processor behavior.
|
| 310 |
+
# transformers ์์ ๊ฒฝ๋ก: ๊ณต์ processor ๋์์ ๊ทธ๋๋ก ์ฌ์ฉํฉ๋๋ค.
|
| 311 |
+
if self._delegate is None:
|
| 312 |
+
raise RuntimeError("Processor runtime not built: delegate is None and no transforms are available.")
|
| 313 |
+
|
| 314 |
+
return self._delegate(images, return_tensors=return_tensors, **kwargs)
|
| 315 |
+
|
| 316 |
+
@staticmethod
|
| 317 |
+
def _format_return(pixel_values: torch.Tensor, return_tensors: str | TensorType | None) -> dict[str, Any]:
|
| 318 |
+
"""
|
| 319 |
+
Format pixel_values according to return_tensors.
|
| 320 |
+
return_tensors์ ๋ง์ถฐ pixel_values ๋ฐํ ํฌ๋งท์ ๋ง์ถฅ๋๋ค.
|
| 321 |
+
"""
|
| 322 |
+
if return_tensors is None or return_tensors in ("pt", TensorType.PYTORCH):
|
| 323 |
+
return {"pixel_values": pixel_values}
|
| 324 |
+
if return_tensors in ("np", TensorType.NUMPY):
|
| 325 |
+
return {"pixel_values": pixel_values.detach().cpu().numpy()}
|
| 326 |
+
raise ValueError(f"Unsupported return_tensors={return_tensors}. Use 'pt' or 'np'.")
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
# Register this processor for AutoImageProcessor resolution.
|
| 330 |
+
# AutoImageProcessor ํด์์ ์ํด ์ด processor๋ฅผ ๋ฑ๋กํฉ๋๋ค.
|
| 331 |
+
if __name__ != "__main__":
|
| 332 |
+
BackboneMLPHead224ImageProcessor.register_for_auto_class("AutoImageProcessor")
|
models/timm__densenet121.tv_in1k/model.safetensors
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 29293620
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:53c62717fc23ff4a775482d0f9ca8c730a2c04681a9eeefa01864847d713aa9f
|
| 3 |
size 29293620
|
models/timm__densenet121.tv_in1k/preprocessor_config.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"auto_map": {
|
| 3 |
+
"AutoImageProcessor": "ds_proc.BackboneMLPHead224ImageProcessor"
|
| 4 |
+
},
|
| 5 |
+
"backbone_name_or_path": "timm/densenet121.tv_in1k",
|
| 6 |
+
"image_processor_type": "BackboneMLPHead224ImageProcessor",
|
| 7 |
+
"is_training": false
|
| 8 |
+
}
|
models/torchvision__densenet121/config.json
CHANGED
|
@@ -24,10 +24,11 @@
|
|
| 24 |
"num_labels": 3,
|
| 25 |
"transformers_version": "5.1.0",
|
| 26 |
"ds_provenance": {
|
| 27 |
-
"created_at": "
|
| 28 |
"repo_id": "dsaint31/bb_mlp_224",
|
| 29 |
"subdir": "models/torchvision__densenet121",
|
| 30 |
"wrapper_class": "BackboneWithMLPHeadForImageClassification",
|
|
|
|
| 31 |
"backbone_id": "torchvision/densenet121",
|
| 32 |
"pretrained_injected": true,
|
| 33 |
"policy_init_pretrained_forbidden": true,
|
|
|
|
| 24 |
"num_labels": 3,
|
| 25 |
"transformers_version": "5.1.0",
|
| 26 |
"ds_provenance": {
|
| 27 |
+
"created_at": "20260210_111913",
|
| 28 |
"repo_id": "dsaint31/bb_mlp_224",
|
| 29 |
"subdir": "models/torchvision__densenet121",
|
| 30 |
"wrapper_class": "BackboneWithMLPHeadForImageClassification",
|
| 31 |
+
"processor_class": "BackboneMLPHead224ImageProcessor",
|
| 32 |
"backbone_id": "torchvision/densenet121",
|
| 33 |
"pretrained_injected": true,
|
| 34 |
"policy_init_pretrained_forbidden": true,
|
models/torchvision__densenet121/ds_cfg.py
CHANGED
|
@@ -1,84 +1,175 @@
|
|
| 1 |
-
from typing import Literal, Union
|
| 2 |
-
|
| 3 |
-
from transformers import AutoConfig
|
| 4 |
from transformers import PretrainedConfig
|
|
|
|
| 5 |
from ds_meta import BackboneID, BACKBONE_META
|
| 6 |
|
| 7 |
-
|
| 8 |
-
# 3) Config
|
| 9 |
-
# ============================================================
|
| 10 |
class BackboneMLPHeadConfig(PretrainedConfig):
|
| 11 |
"""
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
----------
|
| 16 |
-
- transformers ๋ด๋ถ ๊ฒฝ๋ก์์ "๋ฌด์ธ์ ์์ฑ"์ด ๋ฐ์ํ ์ ์์ผ๋ฏ๋ก,
|
| 17 |
-
backbone_name_or_path=None์ธ ๊ฒฝ์ฐ๋ ๊ฒ์ฆ ์์ด ๊ธฐ๋ณธ๊ฐ๋ง ์ธํ
ํ๊ณ return
|
| 18 |
-
ํ๋ ๊ตฌํ์ด ๋ฐ๋์ ํ์ํจ.
|
| 19 |
-
- ๋จ, ์ค์ ์ฌ์ฉ์์๋ backbone_name_or_path๋ฅผ ๋ฐ๋์ ์ง์ ํด์ ์์ฑํด์ผ ํจ.
|
| 20 |
"""
|
| 21 |
|
| 22 |
-
#
|
|
|
|
| 23 |
model_type = "backbone-mlphead-224-fixed"
|
| 24 |
|
| 25 |
-
# ์์ฑ์์์ ๋ชจ๋ ์ธ์์ ๊ธฐ๋ณธ๊ฐ ๋ถ์ฌ ๋ฐ๋์ ํ์!
|
| 26 |
def __init__(
|
| 27 |
self,
|
| 28 |
backbone_name_or_path: BackboneID | None = None,
|
| 29 |
mlp_head_bottleneck: int = 256,
|
| 30 |
mlp_head_dropout: float = 0.2,
|
| 31 |
-
label2id: dict[str, int]|None = None,
|
| 32 |
-
id2label: dict[int, str]|None = None,
|
| 33 |
**kwargs,
|
| 34 |
):
|
| 35 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
if backbone_name_or_path is None:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
super().__init__(**kwargs)
|
|
|
|
|
|
|
|
|
|
| 38 |
self.backbone_name_or_path = None
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
#
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
return
|
| 44 |
|
| 45 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
if backbone_name_or_path not in BACKBONE_META:
|
| 47 |
raise ValueError(
|
| 48 |
f"Unsupported backbone_name_or_path={backbone_name_or_path}. "
|
| 49 |
f"Allowed: {sorted(BACKBONE_META.keys())}"
|
| 50 |
)
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
#
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
kwargs["num_labels"] = num_labels
|
| 62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
super().__init__(
|
| 64 |
-
label2id=
|
| 65 |
-
id2label=
|
| 66 |
**kwargs,
|
| 67 |
)
|
| 68 |
-
|
| 69 |
-
# 4) ์ค์: ๋ถ๋ชจ ํ๋์๋ num_labels ๋ฐ์(์ ์ฅ/๋ก๋ ์์ )
|
| 70 |
-
self.num_labels = num_labels
|
| 71 |
-
self.label2id = label2id
|
| 72 |
-
self.id2label = id2label
|
| 73 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
self.backbone_name_or_path = backbone_name_or_path
|
| 75 |
-
self.mlp_head_bottleneck
|
| 76 |
-
self.mlp_head_dropout
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
def to_dict(self):
|
|
|
|
|
|
|
| 79 |
output = super().to_dict()
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
return output
|
| 83 |
|
|
|
|
|
|
|
|
|
|
| 84 |
BackboneMLPHeadConfig.register_for_auto_class("AutoConfig")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from transformers import PretrainedConfig
|
| 2 |
+
|
| 3 |
from ds_meta import BackboneID, BACKBONE_META
|
| 4 |
|
| 5 |
+
|
|
|
|
|
|
|
| 6 |
class BackboneMLPHeadConfig(PretrainedConfig):
|
| 7 |
"""
|
| 8 |
+
Configuration for Backbone + MLP Head models.
|
| 9 |
+
|
| 10 |
+
Backbone + MLP Head ๋ชจ๋ธ์ ์ํ ์ค์ ํด๋์ค์
๋๋ค.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
"""
|
| 12 |
|
| 13 |
+
# This string is used by Hugging Face AutoConfig to identify the config class.
|
| 14 |
+
# Hugging Face AutoConfig๊ฐ ์ด config๋ฅผ ์๋ณํ๊ธฐ ์ํด ์ฌ์ฉํ๋ ๊ณ ์ ID์
๋๋ค.
|
| 15 |
model_type = "backbone-mlphead-224-fixed"
|
| 16 |
|
|
|
|
| 17 |
def __init__(
|
| 18 |
self,
|
| 19 |
backbone_name_or_path: BackboneID | None = None,
|
| 20 |
mlp_head_bottleneck: int = 256,
|
| 21 |
mlp_head_dropout: float = 0.2,
|
| 22 |
+
label2id: dict[str, int] | None = None,
|
| 23 |
+
id2label: dict[int, str] | None = None,
|
| 24 |
**kwargs,
|
| 25 |
):
|
| 26 |
+
# ============================================================
|
| 27 |
+
# 0) Guard for argument-less construction
|
| 28 |
+
# 0) ๋ฌด์ธ์ ์์ฑ ๊ฒฝ๋ก ๋ฐฉ์ด
|
| 29 |
+
# ============================================================
|
| 30 |
+
# Transformers may internally construct this config without arguments
|
| 31 |
+
# (e.g., during AutoConfig resolution or Hub loading).
|
| 32 |
+
# Transformers ๋ด๋ถ์์ AutoConfig ๋๋ Hub ๋ก๋ ๊ณผ์ ์ค
|
| 33 |
+
# ์ธ์ ์์ด config๋ฅผ ์์ฑํ๋ ๊ฒฝ๋ก๊ฐ ์ค์ ๋ก ์กด์ฌํฉ๋๋ค.
|
| 34 |
+
#
|
| 35 |
+
# In this case, we must NOT validate or raise errors.
|
| 36 |
+
# ์ด ๊ฒฝ์ฐ ๊ฒ์ฆ์ด๋ ์์ธ๋ฅผ ๋ฐ์์ํค๋ฉด ์ ๋ฉ๋๋ค.
|
| 37 |
+
#
|
| 38 |
+
# The goal here is to provide a minimal, serialization-safe config.
|
| 39 |
+
# ๋ชฉํ๋ ์ต์ํ์ ๊ฐ๋ง ์ฑ์ ์ง๋ ฌํ/์ญ์ง๋ ฌํ๊ฐ ๊นจ์ง์ง ์๊ฒ ํ๋ ๊ฒ์
๋๋ค.
|
| 40 |
if backbone_name_or_path is None:
|
| 41 |
+
# num_labels may be implicitly assumed by downstream code,
|
| 42 |
+
# so we explicitly set a safe default.
|
| 43 |
+
# num_labels๋ downstream ์ฝ๋์์ ์๋ฌต์ ์ผ๋ก ๊ฐ์ ๋๋ฏ๋ก
|
| 44 |
+
# ์์ ํ ๊ธฐ๋ณธ๊ฐ์ ๋ช
์์ ์ผ๋ก ๋ฃ์ด์ค๋๋ค.
|
| 45 |
+
if "num_labels" not in kwargs:
|
| 46 |
+
kwargs["num_labels"] = 0
|
| 47 |
+
|
| 48 |
super().__init__(**kwargs)
|
| 49 |
+
|
| 50 |
+
# Backbone is intentionally unset in this path.
|
| 51 |
+
# ์ด ๊ฒฝ๋ก์์๋ backbone์ ์๋์ ์ผ๋ก ์ค์ ํ์ง ์์ต๋๋ค.
|
| 52 |
self.backbone_name_or_path = None
|
| 53 |
+
|
| 54 |
+
# Store MLP head hyperparameters for completeness.
|
| 55 |
+
# MLP head ๊ด๋ จ ํ์ดํผํ๋ผ๋ฏธํฐ๋ ํํ ์ ์ง๋ฅผ ์ํด ์ ์ฅํฉ๋๋ค.
|
| 56 |
+
self.mlp_head_bottleneck = int(mlp_head_bottleneck)
|
| 57 |
+
self.mlp_head_dropout = float(mlp_head_dropout)
|
| 58 |
+
|
| 59 |
+
# Empty label mappings ensure stable save/load behavior.
|
| 60 |
+
# ๋น label ๋งคํ์ ์ ์ฅ/๋ก๋ ์์ ์ฑ์ ๋ณด์ฅํฉ๋๋ค.
|
| 61 |
+
self.label2id = {}
|
| 62 |
+
self.id2label = {}
|
| 63 |
+
self.num_labels = int(kwargs.get("num_labels", 0))
|
| 64 |
return
|
| 65 |
|
| 66 |
+
# ============================================================
|
| 67 |
+
# 1) Backbone whitelist validation
|
| 68 |
+
# 1) Backbone ํ์ดํธ๋ฆฌ์คํธ ๊ฒ์ฆ
|
| 69 |
+
# ============================================================
|
| 70 |
+
# Only backbones explicitly registered in BACKBONE_META are allowed.
|
| 71 |
+
# BACKBONE_META์ ๋ฑ๋ก๋ backbone๋ง ํ์ฉํฉ๋๋ค.
|
| 72 |
+
#
|
| 73 |
+
# This prevents accidental usage of unsupported or inconsistent backbones.
|
| 74 |
+
# ์ง์๋์ง ์๊ฑฐ๋ ๋ถ์ผ์นํ backbone ์ฌ์ฉ์ ์์ฒ ์ฐจ๋จํฉ๋๋ค.
|
| 75 |
if backbone_name_or_path not in BACKBONE_META:
|
| 76 |
raise ValueError(
|
| 77 |
f"Unsupported backbone_name_or_path={backbone_name_or_path}. "
|
| 78 |
f"Allowed: {sorted(BACKBONE_META.keys())}"
|
| 79 |
)
|
| 80 |
+
|
| 81 |
+
# ============================================================
|
| 82 |
+
# 2) Label mapping normalization
|
| 83 |
+
# 2) ๋ผ๋ฒจ ๋งคํ ์ ๊ทํ
|
| 84 |
+
# ============================================================
|
| 85 |
+
# Both label2id and id2label may be None during pure loading scenarios.
|
| 86 |
+
# ๋จ์ ๋ก๋(from_pretrained) ์ label2id/id2label์ด None์ผ ์ ์์ต๋๋ค.
|
| 87 |
+
#
|
| 88 |
+
# We allow this here to keep Hub loading and verification stable.
|
| 89 |
+
# Hub ๋ก๋ ๋ฐ ๊ฒ์ฆ ์์ ์ฑ์ ์ํด ์ฌ๊ธฐ์๋ ์ด๋ฅผ ํ์ฉํฉ๋๋ค.
|
| 90 |
+
#
|
| 91 |
+
# Fail-fast validation should happen at model or training level instead.
|
| 92 |
+
# ์ค์ ์ฌ์ฉ ๊ฒ์ฆ์ ๋ชจ๋ธ ๋๋ ํ์ต ๋จ๊ณ์์ fail-fast๋ก ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค.
|
| 93 |
+
if label2id is None and id2label is None:
|
| 94 |
+
# Respect num_labels if explicitly provided, otherwise default to 0.
|
| 95 |
+
# num_labels๊ฐ ์ฃผ์ด์ก๋ค๋ฉด ์กด์คํ๊ณ , ์๋๋ฉด 0์ผ๋ก ๋ก๋๋ค.
|
| 96 |
+
num_labels = int(kwargs.get("num_labels", 0))
|
| 97 |
+
label2id_norm: dict[str, int] = {}
|
| 98 |
+
id2label_norm: dict[int, str] = {}
|
| 99 |
+
else:
|
| 100 |
+
# If only one mapping is provided, derive the other.
|
| 101 |
+
# ํ๋๋ง ์ฃผ์ด์ง ๊ฒฝ์ฐ ๋๋จธ์ง๋ฅผ ์๋์ผ๋ก ์์ฑํฉ๋๋ค.
|
| 102 |
+
if id2label is None:
|
| 103 |
+
id2label = {v: k for k, v in label2id.items()}
|
| 104 |
+
if label2id is None:
|
| 105 |
+
label2id = {v: k for k, v in id2label.items()}
|
| 106 |
+
|
| 107 |
+
# Ensure both mappings are consistent in size.
|
| 108 |
+
# ๋ ๋งคํ์ ํฌ๊ธฐ๊ฐ ์ผ์นํ๋์ง ํ์ธํฉ๋๋ค.
|
| 109 |
+
if len(label2id) != len(id2label):
|
| 110 |
+
raise ValueError(
|
| 111 |
+
f"label2id/id2label size mismatch: "
|
| 112 |
+
f"{len(label2id)} vs {len(id2label)}"
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
num_labels = len(id2label)
|
| 116 |
+
label2id_norm = dict(label2id)
|
| 117 |
+
id2label_norm = dict(id2label)
|
| 118 |
+
|
| 119 |
+
# ============================================================
|
| 120 |
+
# 3) num_labels consistency enforcement
|
| 121 |
+
# 3) num_labels ์ผ๊ด์ฑ ๊ฐ์
|
| 122 |
+
# ============================================================
|
| 123 |
+
# If num_labels is provided via kwargs, it must match inferred labels.
|
| 124 |
+
# kwargs๋ก num_labels๊ฐ ๋ค์ด์จ ๊ฒฝ์ฐ ์ถ๋ก ๋ ๊ฐ๊ณผ ๋ฐ๋์ ์ผ์นํด์ผ ํฉ๋๋ค.
|
| 125 |
+
if "num_labels" in kwargs:
|
| 126 |
+
if (label2id is not None or id2label is not None) and int(kwargs["num_labels"]) != num_labels:
|
| 127 |
+
raise ValueError(
|
| 128 |
+
f"kwargs['num_labels']={kwargs['num_labels']} "
|
| 129 |
+
f"!= inferred num_labels={num_labels}"
|
| 130 |
+
)
|
| 131 |
+
else:
|
| 132 |
kwargs["num_labels"] = num_labels
|
| 133 |
|
| 134 |
+
# ============================================================
|
| 135 |
+
# 4) Parent initialization
|
| 136 |
+
# 4) ๋ถ๋ชจ ํด๋์ค ์ด๊ธฐํ
|
| 137 |
+
# ============================================================
|
| 138 |
+
# Initialize PretrainedConfig with normalized label mappings.
|
| 139 |
+
# ์ ๊ทํ๋ ๋ผ๋ฒจ ๋งคํ์ ์ฌ์ฉํ์ฌ PretrainedConfig๋ฅผ ์ด๊ธฐํํฉ๋๋ค.
|
| 140 |
super().__init__(
|
| 141 |
+
label2id=label2id_norm,
|
| 142 |
+
id2label=id2label_norm,
|
| 143 |
**kwargs,
|
| 144 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
+
# ============================================================
|
| 147 |
+
# 5) Explicit attribute assignment for save/load stability
|
| 148 |
+
# 5) ์ ์ฅ/๋ก๋ ์์ ์ฑ์ ์ํ ๋ช
์์ ์์ฑ ๊ณ ์
|
| 149 |
+
# ============================================================
|
| 150 |
+
# Explicitly reassign critical fields to avoid subtle serialization issues.
|
| 151 |
+
# ๋ฏธ๋ฌํ ์ง๋ ฌํ ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ํต์ฌ ํ๋๋ฅผ ๋ช
์์ ์ผ๋ก ๋ค์ ์ค์ ํฉ๋๋ค.
|
| 152 |
self.backbone_name_or_path = backbone_name_or_path
|
| 153 |
+
self.mlp_head_bottleneck = int(mlp_head_bottleneck)
|
| 154 |
+
self.mlp_head_dropout = float(mlp_head_dropout)
|
| 155 |
+
|
| 156 |
+
self.label2id = label2id_norm
|
| 157 |
+
self.id2label = id2label_norm
|
| 158 |
+
self.num_labels = int(kwargs["num_labels"])
|
| 159 |
|
| 160 |
def to_dict(self):
|
| 161 |
+
# Call the parent implementation first.
|
| 162 |
+
# ๋จผ์ ๋ถ๋ชจ ๊ตฌํ์ ํธ์ถํฉ๋๋ค.
|
| 163 |
output = super().to_dict()
|
| 164 |
+
|
| 165 |
+
# Force num_labels to be present and consistent.
|
| 166 |
+
# num_labels๊ฐ ๋ฐ๋์ ์กด์ฌํ๊ณ ์ผ๊ด๋๋๋ก ๊ฐ์ ํฉ๋๋ค.
|
| 167 |
+
output["num_labels"] = int(
|
| 168 |
+
getattr(self, "num_labels", output.get("num_labels", 0))
|
| 169 |
+
)
|
| 170 |
return output
|
| 171 |
|
| 172 |
+
|
| 173 |
+
# Register this config so it can be resolved via AutoConfig.
|
| 174 |
+
# AutoConfig๋ฅผ ํตํด ์ด config๊ฐ ํด์๋ ์ ์๋๋ก ๋ฑ๋กํฉ๋๋ค.
|
| 175 |
BackboneMLPHeadConfig.register_for_auto_class("AutoConfig")
|
models/torchvision__densenet121/ds_model.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
# -*- coding: utf-8 -*-
|
| 3 |
|
| 4 |
-
|
| 5 |
|
| 6 |
-
from
|
| 7 |
-
from typing import Dict, Any, Optional, Tuple, Set, List
|
| 8 |
|
| 9 |
import torch
|
| 10 |
import torch.nn as nn
|
|
@@ -12,146 +11,88 @@ import torch.nn.functional as F
|
|
| 12 |
|
| 13 |
# --- transformers core ---
|
| 14 |
from transformers.modeling_utils import PreTrainedModel
|
| 15 |
-
from transformers import AutoModel, AutoConfig
|
| 16 |
from transformers.modeling_outputs import ImageClassifierOutput
|
| 17 |
|
| 18 |
# --- torchvision ---
|
| 19 |
from torchvision import models as tv_models
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
# - ๊ธฐ์กด ds_cfg.BackboneMLPHeadConfig์ ํ๋๋ช
์ ๋ง์ถ์์ต๋๋ค.
|
| 25 |
-
# - ์ด๋ฏธ ds_cfg.py๊ฐ ์๋ค๋ฉด: ์ด ๋ธ๋ก์ ์ง์ฐ๊ณ "from ds_cfg import BackboneMLPHeadConfig"๋ก ๋ฐ๊ฟ๋ ๋ฉ๋๋ค.
|
| 26 |
-
# ============================================================
|
| 27 |
-
class BackboneMLPHeadConfig(PretrainedConfig):
|
| 28 |
-
model_type = "backbone_mlphead"
|
| 29 |
-
|
| 30 |
-
def __init__(
|
| 31 |
-
self,
|
| 32 |
-
backbone_name_or_path: Optional[str] = None,
|
| 33 |
-
num_labels: int = 2,
|
| 34 |
-
id2label: Optional[Dict[int, str]] = None,
|
| 35 |
-
label2id: Optional[Dict[str, int]] = None,
|
| 36 |
-
mlp_head_bottleneck: int = 256,
|
| 37 |
-
mlp_head_dropout: float = 0.2,
|
| 38 |
-
**kwargs,
|
| 39 |
-
):
|
| 40 |
-
super().__init__(**kwargs)
|
| 41 |
-
self.backbone_name_or_path = backbone_name_or_path
|
| 42 |
-
self.num_labels = num_labels
|
| 43 |
-
self.id2label = id2label or {i: f"LABEL_{i}" for i in range(num_labels)}
|
| 44 |
-
self.label2id = label2id or {v: k for k, v in self.id2label.items()}
|
| 45 |
-
self.mlp_head_bottleneck = mlp_head_bottleneck
|
| 46 |
-
self.mlp_head_dropout = mlp_head_dropout
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
# ============================================================
|
| 50 |
-
# (1) BACKBONE_META
|
| 51 |
-
# - ์ด๋ฏธ ds_meta.py๊ฐ ์๋ค๋ฉด: ์ด ๋ธ๋ก์ ์ง์ฐ๊ณ "from ds_meta import BACKBONE_META"๋ก ๋ฐ๊ฟ๋ ๋ฉ๋๋ค.
|
| 52 |
-
# ============================================================
|
| 53 |
-
BACKBONE_META: Dict[str, Dict[str, Any]] = {
|
| 54 |
-
# transformers
|
| 55 |
-
"google/vit-base-patch16-224": {
|
| 56 |
-
"type": "vit",
|
| 57 |
-
"feat_rule": "cls",
|
| 58 |
-
"feat_dim": 768,
|
| 59 |
-
"has_bn": False,
|
| 60 |
-
"unfreeze": "last_n",
|
| 61 |
-
},
|
| 62 |
-
"microsoft/swin-tiny-patch4-window7-224": {
|
| 63 |
-
"type": "swin",
|
| 64 |
-
"feat_rule": "pool_or_mean",
|
| 65 |
-
"feat_dim": 768,
|
| 66 |
-
"has_bn": False,
|
| 67 |
-
"unfreeze": "last_n",
|
| 68 |
-
},
|
| 69 |
-
"microsoft/resnet-50": {
|
| 70 |
-
"type": "resnet",
|
| 71 |
-
"feat_rule": "pool_or_gap",
|
| 72 |
-
"feat_dim": 2048,
|
| 73 |
-
"has_bn": True,
|
| 74 |
-
"unfreeze": "last_n",
|
| 75 |
-
},
|
| 76 |
-
"google/efficientnet-b0": {
|
| 77 |
-
"type": "efficientnet",
|
| 78 |
-
"feat_rule": "pool_or_gap",
|
| 79 |
-
"feat_dim": 1280,
|
| 80 |
-
"has_bn": True,
|
| 81 |
-
"unfreeze": "last_n",
|
| 82 |
-
},
|
| 83 |
-
|
| 84 |
-
# densenet
|
| 85 |
-
"timm/densenet121.tv_in1k": {
|
| 86 |
-
"type": "timm_densenet",
|
| 87 |
-
"feat_rule": "timm_gap",
|
| 88 |
-
"feat_dim": 1024,
|
| 89 |
-
"has_bn": True,
|
| 90 |
-
"unfreeze": "last_n",
|
| 91 |
-
},
|
| 92 |
-
"torchvision/densenet121": {
|
| 93 |
-
"type": "torchvision_densenet",
|
| 94 |
-
"feat_rule": "torchvision_densenet_gap",
|
| 95 |
-
"feat_dim": 1024,
|
| 96 |
-
"has_bn": True,
|
| 97 |
-
"unfreeze": "last_n",
|
| 98 |
-
},
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
# ============================================================
|
| 103 |
-
# (2) MLP Head
|
| 104 |
-
# - ์ด๋ฏธ mlp_head.py๊ฐ ์๋ค๋ฉด: ์ด ๋ธ๋ก์ ์ง์ฐ๊ณ "from mlp_head import MLPHead"๋ก ๋ฐ๊ฟ๋ ๋ฉ๋๋ค.
|
| 105 |
-
# ============================================================
|
| 106 |
-
class MLPHead(nn.Module):
|
| 107 |
-
def __init__(self, in_dim: int, num_labels: int, bottleneck: int = 256, p: float = 0.2):
|
| 108 |
-
super().__init__()
|
| 109 |
-
self.fc2 = nn.Linear(in_dim, bottleneck)
|
| 110 |
-
self.act2 = nn.GELU()
|
| 111 |
-
self.drop2 = nn.Dropout(p)
|
| 112 |
-
self.fc3 = nn.Linear(bottleneck, num_labels)
|
| 113 |
-
|
| 114 |
-
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 115 |
-
x = self.drop2(self.act2(self.fc2(x)))
|
| 116 |
-
return self.fc3(x)
|
| 117 |
-
|
| 118 |
|
| 119 |
# ============================================================
|
| 120 |
# (3) Model: backbone + MLP head
|
| 121 |
-
#
|
| 122 |
-
# - __init__: backbone ๊ตฌ์กฐ(skeleton)๋ง ์์ฑ. pretrained๋ฅผ ์ ๋ ๋ก๋ํ์ง ์์.
|
| 123 |
-
# - pretrained ์ฃผ์
: model.load_backbone_pretrained_()๋ฅผ "fresh-start์์๋ง" ์ง์ ํธ์ถ.
|
| 124 |
-
# - from_pretrained override ์์: HF ๊ธฐ๋ณธ ๋ก๋๊ฐ checkpoint๋ฅผ ๊ทธ๋๋ก ๋ณต์.
|
| 125 |
# ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
|
|
|
|
|
| 127 |
config_class = BackboneMLPHeadConfig
|
| 128 |
|
| 129 |
def __init__(self, config: BackboneMLPHeadConfig):
|
|
|
|
|
|
|
| 130 |
super().__init__(config)
|
| 131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
if config.backbone_name_or_path is None:
|
| 133 |
-
raise ValueError(
|
|
|
|
|
|
|
|
|
|
| 134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
self._meta = BACKBONE_META[config.backbone_name_or_path]
|
| 136 |
|
| 137 |
-
#
|
|
|
|
| 138 |
self.backbone = self._build_backbone_skeleton(config.backbone_name_or_path)
|
| 139 |
|
| 140 |
-
#
|
|
|
|
| 141 |
self.classifier = MLPHead(
|
| 142 |
in_dim=int(self._meta["feat_dim"]),
|
| 143 |
-
num_labels=config.num_labels,
|
| 144 |
-
bottleneck=config.mlp_head_bottleneck,
|
| 145 |
-
p=config.mlp_head_dropout,
|
| 146 |
)
|
| 147 |
|
| 148 |
-
# HF
|
|
|
|
| 149 |
self.post_init()
|
| 150 |
|
| 151 |
def init_weights(self):
|
| 152 |
"""
|
| 153 |
-
|
| 154 |
-
head๋ง ์ด๊ธฐํ
|
|
|
|
|
|
|
|
|
|
| 155 |
"""
|
| 156 |
if getattr(self, "classifier", None) is not None:
|
| 157 |
self.classifier.apply(self._init_weights)
|
|
@@ -159,8 +100,11 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 159 |
|
| 160 |
# ----------------------------
|
| 161 |
# backbone skeleton builders
|
|
|
|
| 162 |
# ----------------------------
|
| 163 |
def _build_backbone_skeleton(self, backbone_id: str) -> nn.Module:
|
|
|
|
|
|
|
| 164 |
meta = BACKBONE_META[backbone_id]
|
| 165 |
t = meta["type"]
|
| 166 |
|
|
@@ -170,18 +114,24 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 170 |
if t == "torchvision_densenet":
|
| 171 |
return self._build_torchvision_densenet_skeleton(backbone_id)
|
| 172 |
|
| 173 |
-
# transformers:
|
|
|
|
| 174 |
bb_cfg = AutoConfig.from_pretrained(backbone_id)
|
| 175 |
return AutoModel.from_config(bb_cfg)
|
| 176 |
|
| 177 |
@staticmethod
|
| 178 |
def _build_timm_densenet_skeleton(hf_repo_id: str) -> nn.Module:
|
|
|
|
|
|
|
| 179 |
try:
|
| 180 |
import timm
|
| 181 |
except Exception as e:
|
| 182 |
-
raise ImportError(
|
|
|
|
|
|
|
| 183 |
|
| 184 |
-
#
|
|
|
|
| 185 |
return timm.create_model(
|
| 186 |
f"hf_hub:{hf_repo_id}",
|
| 187 |
pretrained=False,
|
|
@@ -190,33 +140,33 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 190 |
|
| 191 |
@staticmethod
|
| 192 |
def _build_torchvision_densenet_skeleton(model_id: str) -> nn.Module:
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
"torchvision
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
arch = mapping[model_id]
|
| 203 |
-
fn = getattr(tv_models, arch)
|
| 204 |
-
|
| 205 |
-
# ๊ตฌ์กฐ๋ง (weights=None)
|
| 206 |
-
m = fn(weights=None).eval()
|
| 207 |
return m
|
| 208 |
|
| 209 |
# ------------------------------------------------------------
|
| 210 |
-
#
|
|
|
|
| 211 |
# ------------------------------------------------------------
|
| 212 |
@torch.no_grad()
|
| 213 |
-
def load_backbone_pretrained_(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
"""
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
model.load_backbone_pretrained_()
|
| 218 |
|
| 219 |
-
|
|
|
|
| 220 |
"""
|
| 221 |
bb = self.config.backbone_name_or_path
|
| 222 |
meta = BACKBONE_META[bb]
|
|
@@ -230,48 +180,58 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 230 |
self._load_torchvision_pretrained_into_skeleton_(bb)
|
| 231 |
return
|
| 232 |
|
| 233 |
-
# transformers pretrained
|
|
|
|
| 234 |
ref = AutoModel.from_pretrained(
|
| 235 |
bb,
|
| 236 |
low_cpu_mem_usage=low_cpu_mem_usage,
|
| 237 |
device_map=device_map,
|
| 238 |
)
|
|
|
|
|
|
|
|
|
|
| 239 |
self.backbone.load_state_dict(ref.state_dict(), strict=False)
|
| 240 |
del ref
|
| 241 |
|
| 242 |
@torch.no_grad()
|
| 243 |
def _load_timm_pretrained_into_skeleton_(self, hf_repo_id: str):
|
|
|
|
|
|
|
| 244 |
import timm
|
|
|
|
|
|
|
|
|
|
| 245 |
ref = timm.create_model(
|
| 246 |
f"hf_hub:{hf_repo_id}",
|
| 247 |
pretrained=True,
|
| 248 |
num_classes=0,
|
| 249 |
).eval()
|
|
|
|
| 250 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 251 |
del ref
|
| 252 |
|
| 253 |
@torch.no_grad()
|
| 254 |
def _load_torchvision_pretrained_into_skeleton_(self, model_id: str):
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
"torchvision
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
arch, weights = mapping[model_id]
|
| 265 |
-
fn = getattr(tv_models, arch)
|
| 266 |
-
ref = fn(weights=weights).eval()
|
| 267 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 268 |
del ref
|
| 269 |
|
| 270 |
# ----------------------------
|
| 271 |
# feature extraction
|
|
|
|
| 272 |
# ----------------------------
|
| 273 |
@staticmethod
|
| 274 |
def _pool_or_gap(outputs) -> torch.Tensor:
|
|
|
|
|
|
|
| 275 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 276 |
x = outputs.pooler_output
|
| 277 |
if x.dim() == 2:
|
|
@@ -280,29 +240,42 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 280 |
return x.flatten(1)
|
| 281 |
raise RuntimeError(f"Unexpected pooler_output shape: {tuple(x.shape)}")
|
| 282 |
|
|
|
|
|
|
|
| 283 |
x = outputs.last_hidden_state
|
| 284 |
if x.dim() == 4:
|
| 285 |
return x.mean(dim=(2, 3))
|
|
|
|
| 286 |
raise RuntimeError(
|
| 287 |
"Expected pooler_output or (B,C,H,W) last_hidden_state for CNN backbones. "
|
| 288 |
f"Got last_hidden_state shape={tuple(x.shape)}"
|
| 289 |
)
|
| 290 |
|
| 291 |
def _extract_features(self, outputs, pixel_values: Optional[torch.Tensor] = None) -> torch.Tensor:
|
|
|
|
|
|
|
| 292 |
rule = self._meta["feat_rule"]
|
| 293 |
|
| 294 |
if rule == "cls":
|
|
|
|
|
|
|
| 295 |
return outputs.last_hidden_state[:, 0, :]
|
| 296 |
|
| 297 |
if rule == "pool_or_mean":
|
|
|
|
|
|
|
| 298 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 299 |
return outputs.pooler_output
|
| 300 |
return outputs.last_hidden_state.mean(dim=1)
|
| 301 |
|
| 302 |
if rule == "pool_or_gap":
|
|
|
|
|
|
|
| 303 |
return self._pool_or_gap(outputs)
|
| 304 |
|
| 305 |
if rule == "timm_gap":
|
|
|
|
|
|
|
| 306 |
if not isinstance(outputs, torch.Tensor):
|
| 307 |
raise TypeError(f"timm_gap expects Tensor features, got {type(outputs)}")
|
| 308 |
if outputs.dim() != 4:
|
|
@@ -310,6 +283,8 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 310 |
return outputs.mean(dim=(2, 3))
|
| 311 |
|
| 312 |
if rule == "torchvision_densenet_gap":
|
|
|
|
|
|
|
| 313 |
if not isinstance(outputs, torch.Tensor):
|
| 314 |
raise TypeError(f"torchvision_densenet_gap expects Tensor, got {type(outputs)}")
|
| 315 |
if outputs.dim() != 4:
|
|
@@ -327,9 +302,13 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 327 |
return_dict=True,
|
| 328 |
**kwargs,
|
| 329 |
):
|
|
|
|
|
|
|
| 330 |
t = self._meta["type"]
|
| 331 |
|
| 332 |
if t == "timm_densenet":
|
|
|
|
|
|
|
| 333 |
if pixel_values is None:
|
| 334 |
raise ValueError("timm DenseNet backbone requires pixel_values.")
|
| 335 |
if pixel_values.dim() != 4:
|
|
@@ -341,6 +320,8 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 341 |
attentions = None
|
| 342 |
|
| 343 |
elif t == "torchvision_densenet":
|
|
|
|
|
|
|
| 344 |
if pixel_values is None:
|
| 345 |
raise ValueError("torchvision DenseNet backbone requires pixel_values.")
|
| 346 |
if pixel_values.dim() != 4:
|
|
@@ -353,6 +334,8 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 353 |
attentions = None
|
| 354 |
|
| 355 |
else:
|
|
|
|
|
|
|
| 356 |
outputs = self.backbone(
|
| 357 |
pixel_values=pixel_values,
|
| 358 |
output_attentions=output_attentions,
|
|
@@ -364,10 +347,14 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 364 |
hidden_states = getattr(outputs, "hidden_states", None)
|
| 365 |
attentions = getattr(outputs, "attentions", None)
|
| 366 |
|
|
|
|
|
|
|
| 367 |
logits = self.classifier(feats)
|
| 368 |
|
| 369 |
loss = None
|
| 370 |
if labels is not None:
|
|
|
|
|
|
|
| 371 |
loss = F.cross_entropy(logits, labels)
|
| 372 |
|
| 373 |
if not return_dict:
|
|
@@ -383,20 +370,27 @@ class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
|
| 383 |
|
| 384 |
|
| 385 |
# ============================================================
|
| 386 |
-
# Freeze / Unfreeze utilities
|
|
|
|
| 387 |
# ============================================================
|
| 388 |
def _set_requires_grad(module: nn.Module, flag: bool):
|
|
|
|
|
|
|
| 389 |
for p in module.parameters():
|
| 390 |
p.requires_grad = flag
|
| 391 |
|
| 392 |
|
| 393 |
def set_bn_eval(module: nn.Module):
|
|
|
|
|
|
|
| 394 |
for m in module.modules():
|
| 395 |
if isinstance(m, (nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d, nn.SyncBatchNorm)):
|
| 396 |
m.eval()
|
| 397 |
|
| 398 |
|
| 399 |
def freeze_backbone(model: BackboneWithMLPHeadForImageClassification, freeze_bn: bool = True):
|
|
|
|
|
|
|
| 400 |
_set_requires_grad(model.backbone, False)
|
| 401 |
_set_requires_grad(model.classifier, True)
|
| 402 |
|
|
@@ -406,6 +400,8 @@ def freeze_backbone(model: BackboneWithMLPHeadForImageClassification, freeze_bn:
|
|
| 406 |
|
| 407 |
|
| 408 |
def finetune_train_mode(model: BackboneWithMLPHeadForImageClassification, keep_bn_eval: bool = True):
|
|
|
|
|
|
|
| 409 |
model.train()
|
| 410 |
meta = BACKBONE_META[model.config.backbone_name_or_path]
|
| 411 |
if keep_bn_eval and meta.get("has_bn", False):
|
|
@@ -413,6 +409,8 @@ def finetune_train_mode(model: BackboneWithMLPHeadForImageClassification, keep_b
|
|
| 413 |
|
| 414 |
|
| 415 |
def trainable_summary(model: nn.Module):
|
|
|
|
|
|
|
| 416 |
total = sum(p.numel() for p in model.parameters())
|
| 417 |
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 418 |
ratio = trainable / total if total > 0 else 0.0
|
|
@@ -425,6 +423,8 @@ def unfreeze_last_stage(
|
|
| 425 |
last_n: int = 2,
|
| 426 |
keep_bn_eval: bool = True,
|
| 427 |
):
|
|
|
|
|
|
|
| 428 |
freeze_backbone(model, freeze_bn=keep_bn_eval)
|
| 429 |
|
| 430 |
n = int(last_n)
|
|
@@ -438,12 +438,16 @@ def unfreeze_last_stage(
|
|
| 438 |
bb_type = meta["type"]
|
| 439 |
|
| 440 |
if bb_type == "vit":
|
|
|
|
|
|
|
| 441 |
blocks = list(model.backbone.encoder.layer)
|
| 442 |
for blk in blocks[-n:]:
|
| 443 |
_set_requires_grad(blk, True)
|
| 444 |
return
|
| 445 |
|
| 446 |
if bb_type == "swin":
|
|
|
|
|
|
|
| 447 |
stages = list(model.backbone.encoder.layers)
|
| 448 |
blocks: List[nn.Module] = []
|
| 449 |
for st in stages:
|
|
@@ -453,6 +457,8 @@ def unfreeze_last_stage(
|
|
| 453 |
return
|
| 454 |
|
| 455 |
if bb_type == "resnet":
|
|
|
|
|
|
|
| 456 |
bb = model.backbone
|
| 457 |
for name in ("layer1", "layer2", "layer3", "layer4"):
|
| 458 |
if not hasattr(bb, name):
|
|
@@ -472,6 +478,8 @@ def unfreeze_last_stage(
|
|
| 472 |
return
|
| 473 |
|
| 474 |
if bb_type == "efficientnet":
|
|
|
|
|
|
|
| 475 |
bb = model.backbone
|
| 476 |
if not hasattr(bb, "features"):
|
| 477 |
raise RuntimeError("Unexpected EfficientNet structure: missing features")
|
|
@@ -488,6 +496,8 @@ def unfreeze_last_stage(
|
|
| 488 |
return
|
| 489 |
|
| 490 |
if bb_type in ("timm_densenet", "torchvision_densenet"):
|
|
|
|
|
|
|
| 491 |
bb = model.backbone
|
| 492 |
if not hasattr(bb, "features"):
|
| 493 |
raise RuntimeError("Unexpected DenseNet: missing features")
|
|
@@ -505,6 +515,8 @@ def unfreeze_last_stage(
|
|
| 505 |
raise RuntimeError(f"Unexpected DenseNet features: missing {name}")
|
| 506 |
|
| 507 |
def _denselayers(db: nn.Module) -> List[nn.Module]:
|
|
|
|
|
|
|
| 508 |
return list(db.children())
|
| 509 |
|
| 510 |
blocks: List[nn.Module] = []
|
|
@@ -526,20 +538,8 @@ def unfreeze_last_stage(
|
|
| 526 |
|
| 527 |
# -------------------------
|
| 528 |
# register
|
|
|
|
|
|
|
|
|
|
|
|
|
| 529 |
BackboneWithMLPHeadForImageClassification.register_for_auto_class("AutoModelForImageClassification")
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
# ==========================================================
|
| 533 |
-
|
| 534 |
-
# # fresh-start
|
| 535 |
-
# cfg = BackboneMLPHeadConfig(backbone_name_or_path="timm/densenet121.tv_in1k", num_labels=3)
|
| 536 |
-
# model = BackboneWithMLPHeadForImageClassification(cfg)
|
| 537 |
-
|
| 538 |
-
# # pretrained๋ฅผ ์ฐ๋ ค๋ฉด ๋ช
์์ ์ผ๋ก ํธ์ถ
|
| 539 |
-
# model.load_backbone_pretrained_()
|
| 540 |
-
|
| 541 |
-
# # ์ดํ ํ์ต
|
| 542 |
-
|
| 543 |
-
# # checkpoint์์ ์์
|
| 544 |
-
# model = BackboneWithMLPHeadForImageClassification.from_pretrained("checkpoints/xxx")
|
| 545 |
-
# # ์ฌ๊ธฐ์๋ load_backbone_pretrained_() ํธ์ถํ๋ฉด ์ ๋ฉ๋๋ค.
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
# -*- coding: utf-8 -*-
|
| 3 |
|
| 4 |
+
# src/ds_model.py
|
| 5 |
|
| 6 |
+
from typing import Optional, List
|
|
|
|
| 7 |
|
| 8 |
import torch
|
| 9 |
import torch.nn as nn
|
|
|
|
| 11 |
|
| 12 |
# --- transformers core ---
|
| 13 |
from transformers.modeling_utils import PreTrainedModel
|
| 14 |
+
from transformers import AutoModel, AutoConfig
|
| 15 |
from transformers.modeling_outputs import ImageClassifierOutput
|
| 16 |
|
| 17 |
# --- torchvision ---
|
| 18 |
from torchvision import models as tv_models
|
| 19 |
|
| 20 |
+
from ds_cfg import BackboneMLPHeadConfig
|
| 21 |
+
from ds_meta import BACKBONE_META
|
| 22 |
+
from mlp_head import MLPHead
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
# ============================================================
|
| 25 |
# (3) Model: backbone + MLP head
|
| 26 |
+
# (3) ๋ชจ๋ธ: backbone + MLP head
|
|
|
|
|
|
|
|
|
|
| 27 |
# ============================================================
|
| 28 |
+
# Design principle: __init__ builds only a skeleton and MUST NOT load pretrained weights.
|
| 29 |
+
# ์ค๊ณ ์์น: __init__์์๋ skeleton๋ง ๋ง๋ค๊ณ pretrained weight ๋ก๋๋ ์ ๋ ํ๋ฉด ์ ๋ฉ๋๋ค.
|
| 30 |
+
#
|
| 31 |
+
# Pretrained injection is allowed ONLY via an explicit call in fresh-start flows.
|
| 32 |
+
# pretrained ์ฃผ์
์ fresh-start ํ๋ฆ์์์ ๋ช
์์ ํธ์ถ๋ก๋ง ํ์ฉํฉ๋๋ค.
|
| 33 |
+
#
|
| 34 |
+
# HF from_pretrained should restore checkpoints as-is without side effects.
|
| 35 |
+
# HF from_pretrained๋ ๋ถ์์ฉ ์์ด ์ฒดํฌํฌ์ธํธ๋ฅผ ๊ทธ๋๋ก ๋ณต์ํด์ผ ํฉ๋๋ค.
|
| 36 |
class BackboneWithMLPHeadForImageClassification(PreTrainedModel):
|
| 37 |
+
# This links the model to its custom config for AutoClass usage.
|
| 38 |
+
# AutoClass ์ฌ์ฉ์ ์ํด ๋ชจ๋ธ๊ณผ ์ปค์คํ
config๋ฅผ ์ฐ๊ฒฐํฉ๋๋ค.
|
| 39 |
config_class = BackboneMLPHeadConfig
|
| 40 |
|
| 41 |
def __init__(self, config: BackboneMLPHeadConfig):
|
| 42 |
+
# PreTrainedModel expects a config object and stores it internally.
|
| 43 |
+
# PreTrainedModel์ config ๊ฐ์ฒด๋ฅผ ๋ฐ์ ๋ด๋ถ์ ์ ์ฅํ๋ ์ ์ ๋ฅผ ๊ฐ์ง๋๋ค.
|
| 44 |
super().__init__(config)
|
| 45 |
|
| 46 |
+
# Fail-fast: the model is not meant to be instantiated without a valid backbone id.
|
| 47 |
+
# fail-fast: ์ ํจํ backbone id ์์ด ๋ชจ๋ธ์ ๋ง๋๋ ์ฌ์ฉ ์๋๋ฆฌ์ค๋ ํ์ฉํ์ง ์์ต๋๋ค.
|
| 48 |
+
#
|
| 49 |
+
# Note: Transformers may create configs with no args, but models are conventionally created with configs.
|
| 50 |
+
# ์ฐธ๊ณ : Transformers๋ config ๋ฌด์ธ์ ์์ฑ์ด ์์ ์ ์์ผ๋, ๋ชจ๋ธ์ ๊ด๋ก์ ์ผ๋ก config๋ฅผ ๋ฐ์ ์์ฑ๋ฉ๋๋ค.
|
| 51 |
if config.backbone_name_or_path is None:
|
| 52 |
+
raise ValueError(
|
| 53 |
+
"config.backbone_name_or_path is None. "
|
| 54 |
+
"Provide a valid backbone id (whitelist key in BACKBONE_META)."
|
| 55 |
+
)
|
| 56 |
|
| 57 |
+
# Fail-fast: training/inference requires a positive number of labels.
|
| 58 |
+
# fail-fast: ํ์ต/์ถ๋ก ์ num_labels๊ฐ ์์์ฌ์ผ ํฉ๋๋ค.
|
| 59 |
+
#
|
| 60 |
+
# Config may exist in a minimal form for internal serialization paths, but the model should not.
|
| 61 |
+
# config๋ ๋ด๋ถ ์ง๋ ฌํ ๊ฒฝ๋ก์์ ์ต์ ํํ๋ก ์กด์ฌํ ์ ์์ผ๋ ๋ชจ๋ธ์ ๊ทธ๋ฌ๋ฉด ์ ๋ฉ๋๋ค.
|
| 62 |
+
if int(getattr(config, "num_labels", 0)) <= 0:
|
| 63 |
+
raise ValueError(
|
| 64 |
+
f"config.num_labels must be > 0, got {getattr(config, 'num_labels', None)}. "
|
| 65 |
+
"Set num_labels (or id2label/label2id) when creating the config."
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
# Meta is a single source of truth for extraction and fine-tuning rules.
|
| 69 |
+
# meta๋ feature ์ถ์ถ ๋ฐ ๋ฏธ์ธ์กฐ์ ๊ท์น์ ๋จ์ผ ๊ธฐ์ค์
๋๋ค.
|
| 70 |
self._meta = BACKBONE_META[config.backbone_name_or_path]
|
| 71 |
|
| 72 |
+
# Backbone skeleton is always created without pretrained weights.
|
| 73 |
+
# backbone skeleton์ ํญ์ pretrained weight ์์ด ์์ฑํฉ๋๋ค.
|
| 74 |
self.backbone = self._build_backbone_skeleton(config.backbone_name_or_path)
|
| 75 |
|
| 76 |
+
# Head shape is driven by meta feat_dim and config.num_labels.
|
| 77 |
+
# head shape์ meta์ feat_dim๊ณผ config.num_labels๋ก ๊ฒฐ์ ๋ฉ๋๋ค.
|
| 78 |
self.classifier = MLPHead(
|
| 79 |
in_dim=int(self._meta["feat_dim"]),
|
| 80 |
+
num_labels=int(config.num_labels),
|
| 81 |
+
bottleneck=int(config.mlp_head_bottleneck),
|
| 82 |
+
p=float(config.mlp_head_dropout),
|
| 83 |
)
|
| 84 |
|
| 85 |
+
# HF initialization hook, but we override init_weights to initialize head-only.
|
| 86 |
+
# HF ์ด๊ธฐํ ํ
์ด์ง๋ง init_weights๋ฅผ overrideํ์ฌ head๋ง ์ด๊ธฐํํฉ๋๋ค.
|
| 87 |
self.post_init()
|
| 88 |
|
| 89 |
def init_weights(self):
|
| 90 |
"""
|
| 91 |
+
Initialize only the head to avoid touching the backbone skeleton.
|
| 92 |
+
backbone skeleton์ ๊ฑด๋๋ฆฌ์ง ์๊ธฐ ์ํด head๋ง ์ด๊ธฐํํฉ๋๋ค.
|
| 93 |
+
|
| 94 |
+
HF's default init may traverse the entire module tree, which is undesirable here.
|
| 95 |
+
HF ๊ธฐ๋ณธ init์ ์ ์ฒด ๋ชจ๋ ํธ๋ฆฌ๋ฅผ ์ํํ ์ ์์ด ์ฌ๊ธฐ์๋ ๋ถ์ ์ ํฉ๋๋ค.
|
| 96 |
"""
|
| 97 |
if getattr(self, "classifier", None) is not None:
|
| 98 |
self.classifier.apply(self._init_weights)
|
|
|
|
| 100 |
|
| 101 |
# ----------------------------
|
| 102 |
# backbone skeleton builders
|
| 103 |
+
# backbone skeleton ์์ฑ๊ธฐ
|
| 104 |
# ----------------------------
|
| 105 |
def _build_backbone_skeleton(self, backbone_id: str) -> nn.Module:
|
| 106 |
+
# Meta decides which loader path to use.
|
| 107 |
+
# meta๊ฐ ์ด๋ค ๋ก๋ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ ์ง ๊ฒฐ์ ํฉ๋๋ค.
|
| 108 |
meta = BACKBONE_META[backbone_id]
|
| 109 |
t = meta["type"]
|
| 110 |
|
|
|
|
| 114 |
if t == "torchvision_densenet":
|
| 115 |
return self._build_torchvision_densenet_skeleton(backbone_id)
|
| 116 |
|
| 117 |
+
# For transformers backbones: build a random-weight skeleton from config only.
|
| 118 |
+
# transformers ๋ฐฑ๋ณธ: config๋ก๋ถํฐ ๋๋ค ์ด๊ธฐํ skeleton๋ง ์์ฑํฉ๋๋ค.
|
| 119 |
bb_cfg = AutoConfig.from_pretrained(backbone_id)
|
| 120 |
return AutoModel.from_config(bb_cfg)
|
| 121 |
|
| 122 |
@staticmethod
|
| 123 |
def _build_timm_densenet_skeleton(hf_repo_id: str) -> nn.Module:
|
| 124 |
+
# timm is an optional dependency and should be imported lazily.
|
| 125 |
+
# timm์ ์ต์
์์กด์ฑ์ด๋ฏ๋ก ์ง์ฐ import ํฉ๋๋ค.
|
| 126 |
try:
|
| 127 |
import timm
|
| 128 |
except Exception as e:
|
| 129 |
+
raise ImportError(
|
| 130 |
+
"DenseNet(timm) backbone requires `timm`. Install: pip install timm"
|
| 131 |
+
) from e
|
| 132 |
|
| 133 |
+
# Build structure only (pretrained=False) and remove classifier head (num_classes=0).
|
| 134 |
+
# ๊ตฌ์กฐ๋ง ์์ฑ(pretrained=False)ํ๊ณ ๋ถ๋ฅ๊ธฐ head๋ ์ ๊ฑฐ(num_classes=0)ํฉ๋๋ค.
|
| 135 |
return timm.create_model(
|
| 136 |
f"hf_hub:{hf_repo_id}",
|
| 137 |
pretrained=False,
|
|
|
|
| 140 |
|
| 141 |
@staticmethod
|
| 142 |
def _build_torchvision_densenet_skeleton(model_id: str) -> nn.Module:
|
| 143 |
+
# This project intentionally supports only torchvision/densenet121 in the 224 whitelist.
|
| 144 |
+
# ์ด ํ๋ก์ ํธ๋ 224 ํ์ดํธ๋ฆฌ์คํธ์์ torchvision/densenet121๋ง ์๋์ ์ผ๋ก ์ง์ํฉ๋๋ค.
|
| 145 |
+
if model_id != "torchvision/densenet121":
|
| 146 |
+
raise ValueError(f"Unsupported torchvision DenseNet id (224 whitelist only): {model_id}")
|
| 147 |
+
|
| 148 |
+
# Build structure only (weights=None) to avoid implicit pretrained loading.
|
| 149 |
+
# implicit pretrained ๋ก๋๋ฅผ ํผํ๊ธฐ ์ํด ๊ตฌ์กฐ๋ง ์์ฑ(weights=None)ํฉ๋๋ค.
|
| 150 |
+
m = tv_models.densenet121(weights=None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
return m
|
| 152 |
|
| 153 |
# ------------------------------------------------------------
|
| 154 |
+
# Pretrained loading is explicit and fresh-start only
|
| 155 |
+
# pretrained ๋ก๋ฉ์ ๋ช
์์ ํธ์ถ์ด๋ฉฐ fresh-start ์ ์ฉ
|
| 156 |
# ------------------------------------------------------------
|
| 157 |
@torch.no_grad()
|
| 158 |
+
def load_backbone_pretrained_(
|
| 159 |
+
self,
|
| 160 |
+
*,
|
| 161 |
+
low_cpu_mem_usage: bool = False,
|
| 162 |
+
device_map=None,
|
| 163 |
+
):
|
| 164 |
"""
|
| 165 |
+
Fresh-start only: inject pretrained backbone weights into the skeleton.
|
| 166 |
+
fresh-start ์ ์ฉ: skeleton backbone์ pretrained ๊ฐ์ค์น๋ฅผ ์ฃผ์
ํฉ๋๋ค.
|
|
|
|
| 167 |
|
| 168 |
+
Do NOT call this after from_pretrained() because it would overwrite checkpoint weights.
|
| 169 |
+
from_pretrained() ์ดํ ํธ์ถํ๋ฉด ์ฒดํฌํฌ์ธํธ ๊ฐ์ค์น๋ฅผ ๋ฎ์ด์ฐ๋ฏ๋ก ์ ๋ ํธ์ถํ๋ฉด ์ ๋ฉ๋๋ค.
|
| 170 |
"""
|
| 171 |
bb = self.config.backbone_name_or_path
|
| 172 |
meta = BACKBONE_META[bb]
|
|
|
|
| 180 |
self._load_torchvision_pretrained_into_skeleton_(bb)
|
| 181 |
return
|
| 182 |
|
| 183 |
+
# For transformers backbones, load a reference pretrained model and copy weights into our skeleton.
|
| 184 |
+
# transformers ๋ฐฑ๋ณธ์ reference pretrained ๋ชจ๋ธ์ ๋ก๋ํ ๋ค skeleton์ ๊ฐ์ค์น๋ฅผ ๋ณต์ฌํฉ๋๋ค.
|
| 185 |
ref = AutoModel.from_pretrained(
|
| 186 |
bb,
|
| 187 |
low_cpu_mem_usage=low_cpu_mem_usage,
|
| 188 |
device_map=device_map,
|
| 189 |
)
|
| 190 |
+
|
| 191 |
+
# strict=False is used to tolerate harmless key differences across minor versions.
|
| 192 |
+
# strict=False๋ ๋ง์ด๋ ๋ฒ์ ์ฐจ์ด๋ก ์ธํ ๋ฌดํดํ ํค ์ฐจ์ด๋ฅผ ํ์ฉํ๊ธฐ ์ํด ์ฌ์ฉํฉ๋๋ค.
|
| 193 |
self.backbone.load_state_dict(ref.state_dict(), strict=False)
|
| 194 |
del ref
|
| 195 |
|
| 196 |
@torch.no_grad()
|
| 197 |
def _load_timm_pretrained_into_skeleton_(self, hf_repo_id: str):
|
| 198 |
+
# timm must be present for timm backbones.
|
| 199 |
+
# timm ๋ฐฑ๋ณธ์๋ timm ์ค์น๊ฐ ํ์ํฉ๋๋ค.
|
| 200 |
import timm
|
| 201 |
+
|
| 202 |
+
# Create a pretrained reference model and copy its weights strictly.
|
| 203 |
+
# pretrained reference ๋ชจ๋ธ์ ๋ง๋ค๊ณ ๊ฐ์ค์น๋ฅผ strictํ๊ฒ ๋ณต์ฌํฉ๋๋ค.
|
| 204 |
ref = timm.create_model(
|
| 205 |
f"hf_hub:{hf_repo_id}",
|
| 206 |
pretrained=True,
|
| 207 |
num_classes=0,
|
| 208 |
).eval()
|
| 209 |
+
|
| 210 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 211 |
del ref
|
| 212 |
|
| 213 |
@torch.no_grad()
|
| 214 |
def _load_torchvision_pretrained_into_skeleton_(self, model_id: str):
|
| 215 |
+
# This project intentionally supports only torchvision/densenet121 in the 224 whitelist.
|
| 216 |
+
# ์ด ํ๋ก์ ํธ๋ 224 ํ์ดํธ๋ฆฌ์คํธ์์ torchvision/densenet121๋ง ์๋์ ์ผ๋ก ์ง์ํฉ๋๋ค.
|
| 217 |
+
if model_id != "torchvision/densenet121":
|
| 218 |
+
raise ValueError(f"Unsupported torchvision DenseNet id (224 whitelist only): {model_id}")
|
| 219 |
+
|
| 220 |
+
# Use torchvision's default pretrained weights for densenet121.
|
| 221 |
+
# torchvision์ densenet121 ๊ธฐ๋ณธ pretrained weights๋ฅผ ์ฌ์ฉํฉ๋๋ค.
|
| 222 |
+
ref = tv_models.densenet121(weights=tv_models.DenseNet121_Weights.DEFAULT).eval()
|
| 223 |
+
|
|
|
|
|
|
|
|
|
|
| 224 |
self.backbone.load_state_dict(ref.state_dict(), strict=True)
|
| 225 |
del ref
|
| 226 |
|
| 227 |
# ----------------------------
|
| 228 |
# feature extraction
|
| 229 |
+
# feature ์ถ์ถ
|
| 230 |
# ----------------------------
|
| 231 |
@staticmethod
|
| 232 |
def _pool_or_gap(outputs) -> torch.Tensor:
|
| 233 |
+
# Some transformers vision CNNs provide pooler_output explicitly.
|
| 234 |
+
# ์ผ๋ถ transformers vision CNN์ pooler_output์ ๋ช
์์ ์ผ๋ก ์ ๊ณตํฉ๋๋ค.
|
| 235 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 236 |
x = outputs.pooler_output
|
| 237 |
if x.dim() == 2:
|
|
|
|
| 240 |
return x.flatten(1)
|
| 241 |
raise RuntimeError(f"Unexpected pooler_output shape: {tuple(x.shape)}")
|
| 242 |
|
| 243 |
+
# Otherwise we expect a CNN-style last_hidden_state=(B,C,H,W) and apply GAP.
|
| 244 |
+
# ๊ทธ๋ ์ง ์์ผ๋ฉด CNN ์คํ์ผ last_hidden_state=(B,C,H,W)๋ฅผ ๊ธฐ๋ํ๊ณ GAP์ ์ ์ฉํฉ๋๋ค.
|
| 245 |
x = outputs.last_hidden_state
|
| 246 |
if x.dim() == 4:
|
| 247 |
return x.mean(dim=(2, 3))
|
| 248 |
+
|
| 249 |
raise RuntimeError(
|
| 250 |
"Expected pooler_output or (B,C,H,W) last_hidden_state for CNN backbones. "
|
| 251 |
f"Got last_hidden_state shape={tuple(x.shape)}"
|
| 252 |
)
|
| 253 |
|
| 254 |
def _extract_features(self, outputs, pixel_values: Optional[torch.Tensor] = None) -> torch.Tensor:
|
| 255 |
+
# Feature rule is defined by BACKBONE_META and must remain stable across saves/loads.
|
| 256 |
+
# feature ๊ท์น์ BACKBONE_META๋ก ์ ์๋๋ฉฐ ์ ์ฅ/๋ก๋ ๊ฐ ์์ ์ ์ผ๋ก ์ ์ง๋์ด์ผ ํฉ๋๋ค.
|
| 257 |
rule = self._meta["feat_rule"]
|
| 258 |
|
| 259 |
if rule == "cls":
|
| 260 |
+
# ViT-style: use CLS token embedding from last_hidden_state.
|
| 261 |
+
# ViT ์คํ์ผ: last_hidden_state์์ CLS ํ ํฐ ์๋ฒ ๋ฉ์ ์ฌ์ฉํฉ๋๋ค.
|
| 262 |
return outputs.last_hidden_state[:, 0, :]
|
| 263 |
|
| 264 |
if rule == "pool_or_mean":
|
| 265 |
+
# Swin-style: prefer pooler_output if present, else mean-pool over tokens.
|
| 266 |
+
# Swin ์คํ์ผ: pooler_output์ด ์์ผ๋ฉด ์ฐ์ ์ฌ์ฉํ๊ณ , ์์ผ๋ฉด ํ ํฐ ํ๊ท ํ๋ง์ ์ฌ์ฉํฉ๋๋ค.
|
| 267 |
if hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
| 268 |
return outputs.pooler_output
|
| 269 |
return outputs.last_hidden_state.mean(dim=1)
|
| 270 |
|
| 271 |
if rule == "pool_or_gap":
|
| 272 |
+
# CNN-style: use pooler_output if present, else GAP over spatial dims.
|
| 273 |
+
# CNN ์คํ์ผ: pooler_output์ด ์์ผ๋ฉด ์ฌ์ฉํ๊ณ , ์์ผ๋ฉด ๊ณต๊ฐ ์ฐจ์ GAP์ ์ฌ์ฉํฉ๋๋ค.
|
| 274 |
return self._pool_or_gap(outputs)
|
| 275 |
|
| 276 |
if rule == "timm_gap":
|
| 277 |
+
# timm forward_features returns a feature map (B,C,H,W) which we GAP to (B,C).
|
| 278 |
+
# timm forward_features๋ (B,C,H,W) feature map์ ๋ฐํํ๋ฉฐ ์ด๋ฅผ GAP์ผ๋ก (B,C)๋ก ๋ง๋ญ๋๋ค.
|
| 279 |
if not isinstance(outputs, torch.Tensor):
|
| 280 |
raise TypeError(f"timm_gap expects Tensor features, got {type(outputs)}")
|
| 281 |
if outputs.dim() != 4:
|
|
|
|
| 283 |
return outputs.mean(dim=(2, 3))
|
| 284 |
|
| 285 |
if rule == "torchvision_densenet_gap":
|
| 286 |
+
# torchvision DenseNet features are feature maps (B,C,H,W) and require GAP.
|
| 287 |
+
# torchvision DenseNet features๋ (B,C,H,W) feature map์ด๋ฉฐ GAP์ด ํ์ํฉ๋๋ค.
|
| 288 |
if not isinstance(outputs, torch.Tensor):
|
| 289 |
raise TypeError(f"torchvision_densenet_gap expects Tensor, got {type(outputs)}")
|
| 290 |
if outputs.dim() != 4:
|
|
|
|
| 302 |
return_dict=True,
|
| 303 |
**kwargs,
|
| 304 |
):
|
| 305 |
+
# Type decides the backbone forward path and output format.
|
| 306 |
+
# type์ด backbone forward ๊ฒฝ๋ก ๋ฐ ์ถ๋ ฅ ํฌ๋งท์ ๊ฒฐ์ ํฉ๋๋ค.
|
| 307 |
t = self._meta["type"]
|
| 308 |
|
| 309 |
if t == "timm_densenet":
|
| 310 |
+
# timm DenseNet consumes pixel_values as a 4D tensor (B,C,H,W).
|
| 311 |
+
# timm DenseNet์ pixel_values๋ฅผ 4D ํ
์ (B,C,H,W)๋ก ๋ฐ์ต๋๋ค.
|
| 312 |
if pixel_values is None:
|
| 313 |
raise ValueError("timm DenseNet backbone requires pixel_values.")
|
| 314 |
if pixel_values.dim() != 4:
|
|
|
|
| 320 |
attentions = None
|
| 321 |
|
| 322 |
elif t == "torchvision_densenet":
|
| 323 |
+
# torchvision DenseNet consumes pixel_values as a 4D tensor (B,C,H,W).
|
| 324 |
+
# torchvision DenseNet์ pixel_values๋ฅผ 4D ํ
์ (B,C,H,W)๋ก ๋ฐ์ต๋๋ค.
|
| 325 |
if pixel_values is None:
|
| 326 |
raise ValueError("torchvision DenseNet backbone requires pixel_values.")
|
| 327 |
if pixel_values.dim() != 4:
|
|
|
|
| 334 |
attentions = None
|
| 335 |
|
| 336 |
else:
|
| 337 |
+
# Transformers vision models are called with pixel_values and return ModelOutput.
|
| 338 |
+
# transformers vision ๋ชจ๋ธ์ pixel_values๋ก ํธ์ถ๋๋ฉฐ ModelOutput์ ๋ฐํํฉ๋๋ค.
|
| 339 |
outputs = self.backbone(
|
| 340 |
pixel_values=pixel_values,
|
| 341 |
output_attentions=output_attentions,
|
|
|
|
| 347 |
hidden_states = getattr(outputs, "hidden_states", None)
|
| 348 |
attentions = getattr(outputs, "attentions", None)
|
| 349 |
|
| 350 |
+
# Classifier consumes (B, feat_dim) and returns logits (B, num_labels).
|
| 351 |
+
# classifier๋ (B, feat_dim)์ ๋ฐ์ logits (B, num_labels)๋ฅผ ๋ฐํํฉ๋๋ค.
|
| 352 |
logits = self.classifier(feats)
|
| 353 |
|
| 354 |
loss = None
|
| 355 |
if labels is not None:
|
| 356 |
+
# Cross entropy expects labels as class indices in [0, num_labels).
|
| 357 |
+
# cross entropy๋ labels๊ฐ [0, num_labels) ๋ฒ์์ class index์ด๊ธธ ๊ธฐ๋ํฉ๋๋ค.
|
| 358 |
loss = F.cross_entropy(logits, labels)
|
| 359 |
|
| 360 |
if not return_dict:
|
|
|
|
| 370 |
|
| 371 |
|
| 372 |
# ============================================================
|
| 373 |
+
# Freeze / Unfreeze utilities
|
| 374 |
+
# Freeze / Unfreeze ์ ํธ๋ฆฌํฐ
|
| 375 |
# ============================================================
|
| 376 |
def _set_requires_grad(module: nn.Module, flag: bool):
|
| 377 |
+
# Toggle requires_grad for all parameters in a module.
|
| 378 |
+
# ๋ชจ๋์ ๋ชจ๋ ํ๋ผ๋ฏธํฐ์ ๋ํด requires_grad๋ฅผ ํ ๊ธํฉ๋๋ค.
|
| 379 |
for p in module.parameters():
|
| 380 |
p.requires_grad = flag
|
| 381 |
|
| 382 |
|
| 383 |
def set_bn_eval(module: nn.Module):
|
| 384 |
+
# Put BatchNorm layers into eval mode to freeze running stats.
|
| 385 |
+
# BatchNorm ๋ ์ด์ด๋ฅผ eval ๋ชจ๋๋ก ๋์ด running stats๋ฅผ ๊ณ ์ ํฉ๋๋ค.
|
| 386 |
for m in module.modules():
|
| 387 |
if isinstance(m, (nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d, nn.SyncBatchNorm)):
|
| 388 |
m.eval()
|
| 389 |
|
| 390 |
|
| 391 |
def freeze_backbone(model: BackboneWithMLPHeadForImageClassification, freeze_bn: bool = True):
|
| 392 |
+
# Stage1: freeze backbone and train only the head.
|
| 393 |
+
# stage1: backbone์ freezeํ๊ณ head๋ง ํ์ตํฉ๋๋ค.
|
| 394 |
_set_requires_grad(model.backbone, False)
|
| 395 |
_set_requires_grad(model.classifier, True)
|
| 396 |
|
|
|
|
| 400 |
|
| 401 |
|
| 402 |
def finetune_train_mode(model: BackboneWithMLPHeadForImageClassification, keep_bn_eval: bool = True):
|
| 403 |
+
# Stage2: train mode, optionally keeping BN layers in eval for stability.
|
| 404 |
+
# stage2: train ๋ชจ๋๋ก ๋๋ ์์ ์ฑ์ ์ํด BN์ eval๋ก ์ ์งํ ์ ์์ต๋๋ค.
|
| 405 |
model.train()
|
| 406 |
meta = BACKBONE_META[model.config.backbone_name_or_path]
|
| 407 |
if keep_bn_eval and meta.get("has_bn", False):
|
|
|
|
| 409 |
|
| 410 |
|
| 411 |
def trainable_summary(model: nn.Module):
|
| 412 |
+
# Print a compact summary of trainable parameters.
|
| 413 |
+
# ํ์ต ๊ฐ๋ฅ ํ๋ผ๋ฏธํฐ ์์ฝ์ ๊ฐ๋จํ ์ถ๋ ฅํฉ๋๋ค.
|
| 414 |
total = sum(p.numel() for p in model.parameters())
|
| 415 |
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 416 |
ratio = trainable / total if total > 0 else 0.0
|
|
|
|
| 423 |
last_n: int = 2,
|
| 424 |
keep_bn_eval: bool = True,
|
| 425 |
):
|
| 426 |
+
# This utility implements BACKBONE_META['unfreeze']=="last_n" across supported backbones.
|
| 427 |
+
# ์ด ์ ํธ์ ์ง์ ๋ฐฑ๋ณธ๋ค์ ๋ํด BACKBONE_META['unfreeze']=="last_n"์ ๊ตฌํํฉ๋๋ค.
|
| 428 |
freeze_backbone(model, freeze_bn=keep_bn_eval)
|
| 429 |
|
| 430 |
n = int(last_n)
|
|
|
|
| 438 |
bb_type = meta["type"]
|
| 439 |
|
| 440 |
if bb_type == "vit":
|
| 441 |
+
# ViT blocks live under backbone.encoder.layer in the transformers implementation.
|
| 442 |
+
# ViT ๋ธ๋ก์ transformers ๊ตฌํ์์ backbone.encoder.layer ์๋์ ์์ต๋๋ค.
|
| 443 |
blocks = list(model.backbone.encoder.layer)
|
| 444 |
for blk in blocks[-n:]:
|
| 445 |
_set_requires_grad(blk, True)
|
| 446 |
return
|
| 447 |
|
| 448 |
if bb_type == "swin":
|
| 449 |
+
# Swin blocks are nested by stages and blocks; we flatten and unfreeze last n blocks.
|
| 450 |
+
# Swin ๋ธ๋ก์ stage์ block์ผ๋ก ์ค์ฒฉ๋์ด ์์ด ํผ์น ํ ๋ง์ง๋ง n๊ฐ๋ฅผ unfreeze ํฉ๋๋ค.
|
| 451 |
stages = list(model.backbone.encoder.layers)
|
| 452 |
blocks: List[nn.Module] = []
|
| 453 |
for st in stages:
|
|
|
|
| 457 |
return
|
| 458 |
|
| 459 |
if bb_type == "resnet":
|
| 460 |
+
# ResNet uses layer1..layer4 stages; we unfreeze at block granularity.
|
| 461 |
+
# ResNet์ layer1..layer4 stage๋ฅผ ์ฌ์ฉํ๋ฉฐ block ๋จ์๋ก unfreeze ํฉ๋๋ค.
|
| 462 |
bb = model.backbone
|
| 463 |
for name in ("layer1", "layer2", "layer3", "layer4"):
|
| 464 |
if not hasattr(bb, name):
|
|
|
|
| 478 |
return
|
| 479 |
|
| 480 |
if bb_type == "efficientnet":
|
| 481 |
+
# EfficientNet in transformers exposes features; we unfreeze from the tail blocks.
|
| 482 |
+
# transformers EfficientNet์ features๋ฅผ ๋
ธ์ถํ๋ฉฐ ๋ค์ชฝ ๋ธ๋ก๋ถํฐ unfreeze ํฉ๋๋ค.
|
| 483 |
bb = model.backbone
|
| 484 |
if not hasattr(bb, "features"):
|
| 485 |
raise RuntimeError("Unexpected EfficientNet structure: missing features")
|
|
|
|
| 496 |
return
|
| 497 |
|
| 498 |
if bb_type in ("timm_densenet", "torchvision_densenet"):
|
| 499 |
+
# DenseNet exposes a .features module with named blocks; we unfreeze last n submodules.
|
| 500 |
+
# DenseNet์ .features ๋ชจ๋์ ๋ธ๋ก๋ค์ด ์ด๋ฆ์ผ๋ก ์กด์ฌํ๋ฉฐ ๋ง์ง๋ง n๊ฐ ์๋ธ๋ชจ๋์ unfreeze ํฉ๋๋ค.
|
| 501 |
bb = model.backbone
|
| 502 |
if not hasattr(bb, "features"):
|
| 503 |
raise RuntimeError("Unexpected DenseNet: missing features")
|
|
|
|
| 515 |
raise RuntimeError(f"Unexpected DenseNet features: missing {name}")
|
| 516 |
|
| 517 |
def _denselayers(db: nn.Module) -> List[nn.Module]:
|
| 518 |
+
# Dense blocks contain multiple DenseLayer children; we return them for fine-grained unfreezing.
|
| 519 |
+
# denseblock์ DenseLayer ์์๋ค์ ๊ฐ์ง๋ฏ๋ก ์ธ๋ฐํ unfreeze๋ฅผ ์ํด ์ด๋ฅผ ๋ฐํํฉ๋๋ค.
|
| 520 |
return list(db.children())
|
| 521 |
|
| 522 |
blocks: List[nn.Module] = []
|
|
|
|
| 538 |
|
| 539 |
# -------------------------
|
| 540 |
# register
|
| 541 |
+
# register
|
| 542 |
+
# -------------------------
|
| 543 |
+
# Register for AutoModelForImageClassification so from_pretrained can resolve this custom class.
|
| 544 |
+
# from_pretrained๊ฐ ์ด ์ปค์คํ
ํด๋์ค๋ฅผ ํด์ํ ์ ์๋๋ก AutoModelForImageClassification์ ๋ฑ๋กํฉ๋๋ค.
|
| 545 |
BackboneWithMLPHeadForImageClassification.register_for_auto_class("AutoModelForImageClassification")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
models/torchvision__densenet121/ds_proc.py
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# src/ds_proc.py
|
| 5 |
+
|
| 6 |
+
# ============================================================
|
| 7 |
+
# (4) ImageProcessor (AutoImageProcessor integration)
|
| 8 |
+
# (4) ImageProcessor (AutoImageProcessor ์ฐ๋)
|
| 9 |
+
# ============================================================
|
| 10 |
+
|
| 11 |
+
from typing import Any
|
| 12 |
+
|
| 13 |
+
import numpy as np
|
| 14 |
+
import torch
|
| 15 |
+
from transformers import AutoImageProcessor, AutoConfig
|
| 16 |
+
from transformers.image_processing_base import ImageProcessingMixin
|
| 17 |
+
from transformers.utils.generic import TensorType
|
| 18 |
+
|
| 19 |
+
from ds_meta import BackboneID, BACKBONE_META
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class BackboneMLPHead224ImageProcessor(ImageProcessingMixin):
|
| 23 |
+
"""
|
| 24 |
+
This processor performs image preprocessing and outputs {"pixel_values": ...}.
|
| 25 |
+
์ด processor๋ ์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ๋ฅผ ์ํํ๊ณ {"pixel_values": ...}๋ฅผ ์ถ๋ ฅํฉ๋๋ค.
|
| 26 |
+
|
| 27 |
+
Key requirements:
|
| 28 |
+
ํต์ฌ ์๊ตฌ์ฌํญ:
|
| 29 |
+
|
| 30 |
+
1) save_pretrained() must produce a JSON-serializable preprocessor_config.json.
|
| 31 |
+
save_pretrained()๋ JSON ์ง๋ ฌํ ๊ฐ๋ฅํ preprocessor_config.json์ ์์ฑํด์ผ ํฉ๋๋ค.
|
| 32 |
+
|
| 33 |
+
2) Runtime-only objects (delegate processor, timm/torchvision transforms) must NOT be serialized.
|
| 34 |
+
๋ฐํ์ ๊ฐ์ฒด(delegate processor, timm/torchvision transform)๋ ์ ๋ ์ง๋ ฌํํ๋ฉด ์ ๋ฉ๋๋ค.
|
| 35 |
+
|
| 36 |
+
3) Runtime objects are rebuilt at init/load time based on backbone meta.
|
| 37 |
+
๋ฐํ์ ๊ฐ์ฒด๋ backbone meta์ ๋ฐ๋ผ init/load ์์ ์ ์ฌ๊ตฌ์ฑ๋ฉ๋๋ค.
|
| 38 |
+
"""
|
| 39 |
+
|
| 40 |
+
# HF vision models conventionally expect "pixel_values" as the primary input key.
|
| 41 |
+
# HF vision ๋ชจ๋ธ์ ๊ด๋ก์ ์ผ๋ก ์
๋ ฅ ํค๋ก "pixel_values"๋ฅผ ๊ธฐ๋ํฉ๋๋ค.
|
| 42 |
+
model_input_names = ["pixel_values"]
|
| 43 |
+
|
| 44 |
+
def __init__(self, backbone_name_or_path: BackboneID, is_training: bool = False, **kwargs):
|
| 45 |
+
# ImageProcessingMixin stores extra kwargs and manages auto_map metadata.
|
| 46 |
+
# ImageProcessingMixin์ ์ถ๊ฐ kwargs๋ฅผ ์ ์ฅํ๊ณ auto_map ๋ฉํ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.
|
| 47 |
+
super().__init__(**kwargs)
|
| 48 |
+
|
| 49 |
+
# Enforce whitelist via BACKBONE_META to keep behavior stable.
|
| 50 |
+
# ๋์ ์์ ์ฑ์ ์ํด BACKBONE_META ๊ธฐ๋ฐ ํ์ดํธ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ ํฉ๋๋ค.
|
| 51 |
+
if backbone_name_or_path not in BACKBONE_META:
|
| 52 |
+
raise ValueError(
|
| 53 |
+
f"Unsupported backbone_name_or_path={backbone_name_or_path}. "
|
| 54 |
+
f"Allowed: {sorted(BACKBONE_META.keys())}"
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
# Serializable fields only: these should appear in preprocessor_config.json.
|
| 58 |
+
# ์ง๋ ฌํ ๊ฐ๋ฅํ ํ๋๋ง: ์ด ๊ฐ๋ค๋ง preprocessor_config.json์ ๋ค์ด๊ฐ์ผ ํฉ๋๋ค.
|
| 59 |
+
self.backbone_name_or_path = backbone_name_or_path
|
| 60 |
+
self.is_training = bool(is_training)
|
| 61 |
+
|
| 62 |
+
# Runtime-only fields: must never be serialized.
|
| 63 |
+
# ๋ฐํ์ ์ ์ฉ ํ๋: ์ ๋ ์ง๋ ฌํ๋๋ฉด ์ ๋ฉ๋๋ค.
|
| 64 |
+
self._meta = None
|
| 65 |
+
self._delegate = None
|
| 66 |
+
self._timm_transform = None
|
| 67 |
+
self._torchvision_transform = None
|
| 68 |
+
|
| 69 |
+
# Build runtime objects according to backbone type.
|
| 70 |
+
# backbone type์ ๋ฐ๋ผ ๋ฐํ์ ๊ฐ์ฒด๋ฅผ ๊ตฌ์ฑํฉ๋๋ค.
|
| 71 |
+
self._build_runtime()
|
| 72 |
+
|
| 73 |
+
# ============================================================
|
| 74 |
+
# Runtime builders
|
| 75 |
+
# ๋ฐํ์ ๋น๋
|
| 76 |
+
# ============================================================
|
| 77 |
+
def _build_runtime(self):
|
| 78 |
+
"""
|
| 79 |
+
Build runtime delegate/transform based on BACKBONE_META["type"].
|
| 80 |
+
BACKBONE_META["type"]์ ๋ฐ๋ผ ๋ฐํ์ delegate/transform์ ๊ตฌ์ฑํฉ๋๋ค.
|
| 81 |
+
"""
|
| 82 |
+
meta = BACKBONE_META[self.backbone_name_or_path]
|
| 83 |
+
self._meta = meta
|
| 84 |
+
|
| 85 |
+
# Always reset runtime fields before rebuilding.
|
| 86 |
+
# ์ฌ๊ตฌ์ฑ ์ ๋ฐํ์ ํ๋๋ ํญ์ ์ด๊ธฐํํฉ๋๋ค.
|
| 87 |
+
self._delegate = None
|
| 88 |
+
self._timm_transform = None
|
| 89 |
+
self._torchvision_transform = None
|
| 90 |
+
|
| 91 |
+
t = meta["type"]
|
| 92 |
+
|
| 93 |
+
if t == "timm_densenet":
|
| 94 |
+
# timm DenseNet uses timm.data transforms for ImageNet-style preprocessing.
|
| 95 |
+
# timm DenseNet์ ImageNet ์ ์ฒ๋ฆฌ๋ฅผ ์ํด timm.data transform์ ์ฌ์ฉํฉ๋๋ค.
|
| 96 |
+
self._timm_transform = self._build_timm_transform(
|
| 97 |
+
backbone_id=self.backbone_name_or_path,
|
| 98 |
+
is_training=self.is_training,
|
| 99 |
+
)
|
| 100 |
+
return
|
| 101 |
+
|
| 102 |
+
if t == "torchvision_densenet":
|
| 103 |
+
# torchvision DenseNet requires torchvision-style preprocessing (resize/crop/tensor/normalize).
|
| 104 |
+
# torchvision DenseNet์ torchvision ์คํ์ผ ์ ์ฒ๋ฆฌ(resize/crop/tensor/normalize)๊ฐ ํ์ํฉ๋๋ค.
|
| 105 |
+
self._torchvision_transform = self._build_torchvision_densenet_transform(
|
| 106 |
+
is_training=self.is_training
|
| 107 |
+
)
|
| 108 |
+
return
|
| 109 |
+
|
| 110 |
+
# Default: transformers backbone delegates to its official AutoImageProcessor.
|
| 111 |
+
# ๊ธฐ๋ณธ: transformers ๋ฐฑ๋ณธ์ ๊ณต์ AutoImageProcessor์ ์์ํฉ๋๋ค.
|
| 112 |
+
self._delegate = AutoImageProcessor.from_pretrained(self.backbone_name_or_path)
|
| 113 |
+
|
| 114 |
+
@staticmethod
|
| 115 |
+
def _build_timm_transform(*, backbone_id: str, is_training: bool):
|
| 116 |
+
"""
|
| 117 |
+
Create timm transform without storing non-serializable objects in config.
|
| 118 |
+
๋น์ง๋ ฌํ ๊ฐ์ฒด๋ฅผ config์ ์ ์ฅํ์ง ์๊ณ timm transform์ ์์ฑํฉ๋๋ค.
|
| 119 |
+
"""
|
| 120 |
+
try:
|
| 121 |
+
import timm
|
| 122 |
+
from timm.data import resolve_model_data_config, create_transform
|
| 123 |
+
except Exception as e:
|
| 124 |
+
raise ImportError(
|
| 125 |
+
"timm backbone processor requires `timm`. Install: pip install timm"
|
| 126 |
+
) from e
|
| 127 |
+
|
| 128 |
+
# We only need model metadata to resolve data config, so pretrained=False is preferred.
|
| 129 |
+
# data config ์ถ์ถ๋ง ํ์ํ๋ฏ๋ก pretrained=False๋ฅผ ์ฐ์ ์ฌ์ฉํฉ๋๋ค.
|
| 130 |
+
m = timm.create_model(f"hf_hub:{backbone_id}", pretrained=False, num_classes=0)
|
| 131 |
+
dc = resolve_model_data_config(m)
|
| 132 |
+
|
| 133 |
+
# create_transform returns a torchvision-like callable that maps PIL -> torch.Tensor(C,H,W).
|
| 134 |
+
# create_transform์ PIL -> torch.Tensor(C,H,W)๋ก ๋งคํํ๋ callable์ ๋ฐํํฉ๋๋ค.
|
| 135 |
+
tfm = create_transform(**dc, is_training=is_training)
|
| 136 |
+
return tfm
|
| 137 |
+
|
| 138 |
+
@staticmethod
|
| 139 |
+
def _build_torchvision_densenet_transform(*, is_training: bool):
|
| 140 |
+
"""
|
| 141 |
+
Build torchvision preprocessing for DenseNet-121 (224 pipeline).
|
| 142 |
+
DenseNet-121์ฉ torchvision ์ ์ฒ๋ฆฌ(224 ํ์ดํ๋ผ์ธ)๋ฅผ ๊ตฌ์ฑํฉ๋๋ค.
|
| 143 |
+
"""
|
| 144 |
+
try:
|
| 145 |
+
from torchvision import transforms
|
| 146 |
+
except Exception as e:
|
| 147 |
+
raise ImportError(
|
| 148 |
+
"torchvision DenseNet processor requires `torchvision`. Install: pip install torchvision"
|
| 149 |
+
) from e
|
| 150 |
+
|
| 151 |
+
# These are the standard ImageNet normalization stats used by torchvision weights.
|
| 152 |
+
# ์ด ๊ฐ๋ค์ torchvision weights๊ฐ ์ฌ์ฉํ๋ ํ์ค ImageNet ์ ๊ทํ ํต๊ณ์
๋๋ค.
|
| 153 |
+
mean = (0.485, 0.456, 0.406)
|
| 154 |
+
std = (0.229, 0.224, 0.225)
|
| 155 |
+
|
| 156 |
+
# Training pipeline typically uses RandomResizedCrop and horizontal flip.
|
| 157 |
+
# ํ์ต ํ์ดํ๋ผ์ธ์ ๋ณดํต RandomResizedCrop๊ณผ ์ข์ฐ๋ฐ์ ์ ์ฌ์ฉํฉ๋๋ค.
|
| 158 |
+
if is_training:
|
| 159 |
+
return transforms.Compose(
|
| 160 |
+
[
|
| 161 |
+
transforms.RandomResizedCrop(224),
|
| 162 |
+
transforms.RandomHorizontalFlip(p=0.5),
|
| 163 |
+
transforms.ToTensor(),
|
| 164 |
+
transforms.Normalize(mean=mean, std=std),
|
| 165 |
+
]
|
| 166 |
+
)
|
| 167 |
+
|
| 168 |
+
# Inference pipeline typically uses Resize(256) + CenterCrop(224).
|
| 169 |
+
# ์ถ๋ก ํ์ดํ๋ผ์ธ์ ๋ณดํต Resize(256) + CenterCrop(224)๋ฅผ ์ฌ์ฉํฉ๋๋ค.
|
| 170 |
+
return transforms.Compose(
|
| 171 |
+
[
|
| 172 |
+
transforms.Resize(256),
|
| 173 |
+
transforms.CenterCrop(224),
|
| 174 |
+
transforms.ToTensor(),
|
| 175 |
+
transforms.Normalize(mean=mean, std=std),
|
| 176 |
+
]
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
# ============================================================
|
| 180 |
+
# Serialization
|
| 181 |
+
# ์ง๋ ฌํ
|
| 182 |
+
# ============================================================
|
| 183 |
+
def to_dict(self) -> dict[str, Any]:
|
| 184 |
+
"""
|
| 185 |
+
Return a JSON-serializable dict for preprocessor_config.json.
|
| 186 |
+
preprocessor_config.json์ ๋ค์ด๊ฐ JSON ์ง๋ ฌํ dict๋ฅผ ๋ฐํํฉ๋๋ค.
|
| 187 |
+
|
| 188 |
+
Important: do not leak runtime objects into the serialized dict.
|
| 189 |
+
์ค์: ๋ฐํ์ ๊ฐ์ฒด๊ฐ ์ง๋ ฌํ dict์ ์์ด๋ฉด ์ ๋ฉ๋๋ค.
|
| 190 |
+
"""
|
| 191 |
+
# ImageProcessingMixin.to_dict() adds metadata such as image_processor_type/auto_map.
|
| 192 |
+
# ImageProcessingMixin.to_dict()๋ image_processor_type/auto_map ๊ฐ์ ๋ฉํ๋ฅผ ์ถ๊ฐํฉ๋๋ค.
|
| 193 |
+
d = super().to_dict()
|
| 194 |
+
|
| 195 |
+
# Force minimal stable fields for long-term compatibility.
|
| 196 |
+
# ์ฅ๊ธฐ ํธํ์ ์ํด ์ต์ ์์ ํ๋๋ฅผ ๊ฐ์ ํฉ๋๋ค.
|
| 197 |
+
d["image_processor_type"] = self.__class__.__name__
|
| 198 |
+
d["backbone_name_or_path"] = self.backbone_name_or_path
|
| 199 |
+
d["is_training"] = self.is_training
|
| 200 |
+
|
| 201 |
+
# Remove any runtime-only fields defensively.
|
| 202 |
+
# ๋ฐํ์ ์ ์ฉ ํ๋๋ ๋ณด์์ ์ผ๋ก ์ ๊ฑฐํฉ๋๋ค.
|
| 203 |
+
for key in ["_meta", "_delegate", "_timm_transform", "_torchvision_transform"]:
|
| 204 |
+
d.pop(key, None)
|
| 205 |
+
|
| 206 |
+
return d
|
| 207 |
+
|
| 208 |
+
@classmethod
|
| 209 |
+
def from_dict(cls, image_processor_dict: dict[str, Any], **kwargs):
|
| 210 |
+
"""
|
| 211 |
+
Standard load path used by BaseImageProcessor / AutoImageProcessor.
|
| 212 |
+
BaseImageProcessor / AutoImageProcessor๊ฐ ์ฌ์ฉํ๋ ํ์ค ๋ก๋ ๊ฒฝ๋ก์
๋๋ค.
|
| 213 |
+
"""
|
| 214 |
+
backbone = image_processor_dict.get("backbone_name_or_path", None)
|
| 215 |
+
if backbone is None:
|
| 216 |
+
raise ValueError("preprocessor_config.json missing key: backbone_name_or_path")
|
| 217 |
+
|
| 218 |
+
is_training = bool(image_processor_dict.get("is_training", False))
|
| 219 |
+
return cls(backbone_name_or_path=backbone, is_training=is_training, **kwargs)
|
| 220 |
+
|
| 221 |
+
@classmethod
|
| 222 |
+
def from_pretrained(cls, pretrained_model_name_or_path: str, **kwargs):
|
| 223 |
+
"""
|
| 224 |
+
Fallback path if AutoImageProcessor calls class.from_pretrained directly.
|
| 225 |
+
AutoImageProcessor๊ฐ class.from_pretrained๋ฅผ ์ง์ ํธ์ถํ๋ ๊ฒฝ์ฐ๋ฅผ ๋๋นํ ๊ฒฝ๋ก์
๋๋ค.
|
| 226 |
+
|
| 227 |
+
Strategy:
|
| 228 |
+
์ ๋ต:
|
| 229 |
+
|
| 230 |
+
- Read config.json via AutoConfig and recover backbone_name_or_path.
|
| 231 |
+
AutoConfig๋ก config.json์ ์ฝ๊ณ backbone_name_or_path๋ฅผ ๋ณต๊ตฌํฉ๋๋ค.
|
| 232 |
+
"""
|
| 233 |
+
cfg = AutoConfig.from_pretrained(pretrained_model_name_or_path, **kwargs)
|
| 234 |
+
backbone = getattr(cfg, "backbone_name_or_path", None)
|
| 235 |
+
if backbone is None:
|
| 236 |
+
raise ValueError("Cannot build processor: backbone_name_or_path not found in config.json")
|
| 237 |
+
|
| 238 |
+
# is_training is runtime-only and should default to False for inference/serving.
|
| 239 |
+
# is_training์ ๋ฐํ์ ์ ์ฉ์ด๋ฉฐ ์ถ๋ก /์๋น ๊ธฐ๋ณธ๊ฐ์ False๊ฐ ๋ง์ต๋๋ค.
|
| 240 |
+
return cls(backbone_name_or_path=backbone, is_training=False)
|
| 241 |
+
|
| 242 |
+
# ============================================================
|
| 243 |
+
# Call interface
|
| 244 |
+
# ํธ์ถ ์ธํฐํ์ด์ค
|
| 245 |
+
# ============================================================
|
| 246 |
+
@staticmethod
|
| 247 |
+
def _ensure_list(images: Any) -> list[Any]:
|
| 248 |
+
# Normalize scalar image input to a list for uniform processing.
|
| 249 |
+
# ๋จ์ผ ์
๋ ฅ์ ๋ฆฌ์คํธ๋ก ์ ๊ทํํ์ฌ ๋์ผํ ์ฒ๋ฆฌ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํฉ๋๋ค.
|
| 250 |
+
if isinstance(images, (list, tuple)):
|
| 251 |
+
return list(images)
|
| 252 |
+
return [images]
|
| 253 |
+
|
| 254 |
+
@staticmethod
|
| 255 |
+
def _to_pil_rgb(x: Any):
|
| 256 |
+
# Convert common image inputs into PIL RGB images.
|
| 257 |
+
# ์ผ๋ฐ์ ์ธ ์
๋ ฅ์ PIL RGB ์ด๋ฏธ์ง๋ก ๋ณํํฉ๋๋ค.
|
| 258 |
+
from PIL import Image as PILImage
|
| 259 |
+
|
| 260 |
+
if isinstance(x, PILImage.Image):
|
| 261 |
+
return x.convert("RGB")
|
| 262 |
+
if isinstance(x, np.ndarray) and x.ndim == 3:
|
| 263 |
+
return PILImage.fromarray(x).convert("RGB")
|
| 264 |
+
raise TypeError(f"Unsupported image type: {type(x)}")
|
| 265 |
+
|
| 266 |
+
def __call__(
|
| 267 |
+
self,
|
| 268 |
+
images: Any | list[Any],
|
| 269 |
+
return_tensors: str | TensorType | None = "pt",
|
| 270 |
+
**kwargs,
|
| 271 |
+
) -> dict[str, Any]:
|
| 272 |
+
"""
|
| 273 |
+
Convert images into {"pixel_values": Tensor/ndarray}.
|
| 274 |
+
์ด๋ฏธ์ง๋ฅผ {"pixel_values": Tensor/ndarray}๋ก ๋ณํํฉ๋๋ค.
|
| 275 |
+
"""
|
| 276 |
+
images = self._ensure_list(images)
|
| 277 |
+
|
| 278 |
+
# Rebuild runtime if needed (e.g., right after deserialization).
|
| 279 |
+
# ์ง๋ ฌํ ๋ณต์ ์งํ ๋ฑ ๋ฐํ์์ด ๋น์ด์์ ์ ์์ผ๋ฏ๋ก ์ฌ๊ตฌ์ฑํฉ๋๋ค.
|
| 280 |
+
if (self._delegate is None) and (self._timm_transform is None) and (self._torchvision_transform is None):
|
| 281 |
+
self._build_runtime()
|
| 282 |
+
|
| 283 |
+
# timm path: PIL -> torch.Tensor(C,H,W) normalized float32.
|
| 284 |
+
# timm ๊ฒฝ๋ก: PIL -> torch.Tensor(C,H,W) ์ ๊ทํ float32์
๋๋ค.
|
| 285 |
+
if self._timm_transform is not None:
|
| 286 |
+
pv: list[torch.Tensor] = []
|
| 287 |
+
for im in images:
|
| 288 |
+
pil = self._to_pil_rgb(im)
|
| 289 |
+
t = self._timm_transform(pil)
|
| 290 |
+
if not isinstance(t, torch.Tensor):
|
| 291 |
+
raise RuntimeError("Unexpected timm transform output (expected torch.Tensor).")
|
| 292 |
+
pv.append(t)
|
| 293 |
+
pixel_values = torch.stack(pv, dim=0) # (B,C,H,W)
|
| 294 |
+
return self._format_return(pixel_values, return_tensors)
|
| 295 |
+
|
| 296 |
+
# torchvision path: PIL -> torch.Tensor(C,H,W) normalized float32.
|
| 297 |
+
# torchvision ๊ฒฝ๋ก: PIL -> torch.Tensor(C,H,W) ์ ๊ทํ float32์
๋๋ค.
|
| 298 |
+
if self._torchvision_transform is not None:
|
| 299 |
+
pv: list[torch.Tensor] = []
|
| 300 |
+
for im in images:
|
| 301 |
+
pil = self._to_pil_rgb(im)
|
| 302 |
+
t = self._torchvision_transform(pil)
|
| 303 |
+
if not isinstance(t, torch.Tensor):
|
| 304 |
+
raise RuntimeError("Unexpected torchvision transform output (expected torch.Tensor).")
|
| 305 |
+
pv.append(t)
|
| 306 |
+
pixel_values = torch.stack(pv, dim=0) # (B,C,H,W)
|
| 307 |
+
return self._format_return(pixel_values, return_tensors)
|
| 308 |
+
|
| 309 |
+
# transformers delegate path: rely on official processor behavior.
|
| 310 |
+
# transformers ์์ ๊ฒฝ๋ก: ๊ณต์ processor ๋์์ ๊ทธ๋๋ก ์ฌ์ฉํฉ๋๋ค.
|
| 311 |
+
if self._delegate is None:
|
| 312 |
+
raise RuntimeError("Processor runtime not built: delegate is None and no transforms are available.")
|
| 313 |
+
|
| 314 |
+
return self._delegate(images, return_tensors=return_tensors, **kwargs)
|
| 315 |
+
|
| 316 |
+
@staticmethod
|
| 317 |
+
def _format_return(pixel_values: torch.Tensor, return_tensors: str | TensorType | None) -> dict[str, Any]:
|
| 318 |
+
"""
|
| 319 |
+
Format pixel_values according to return_tensors.
|
| 320 |
+
return_tensors์ ๋ง์ถฐ pixel_values ๋ฐํ ํฌ๋งท์ ๋ง์ถฅ๋๋ค.
|
| 321 |
+
"""
|
| 322 |
+
if return_tensors is None or return_tensors in ("pt", TensorType.PYTORCH):
|
| 323 |
+
return {"pixel_values": pixel_values}
|
| 324 |
+
if return_tensors in ("np", TensorType.NUMPY):
|
| 325 |
+
return {"pixel_values": pixel_values.detach().cpu().numpy()}
|
| 326 |
+
raise ValueError(f"Unsupported return_tensors={return_tensors}. Use 'pt' or 'np'.")
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
# Register this processor for AutoImageProcessor resolution.
|
| 330 |
+
# AutoImageProcessor ํด์์ ์ํด ์ด processor๋ฅผ ๋ฑ๋กํฉ๋๋ค.
|
| 331 |
+
if __name__ != "__main__":
|
| 332 |
+
BackboneMLPHead224ImageProcessor.register_for_auto_class("AutoImageProcessor")
|
models/torchvision__densenet121/model.safetensors
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 33394052
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e8431398f3461657aea5362b326d351fcdd4c5c009bf126765d0dcd60f94aa72
|
| 3 |
size 33394052
|
models/torchvision__densenet121/preprocessor_config.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"auto_map": {
|
| 3 |
+
"AutoImageProcessor": "ds_proc.BackboneMLPHead224ImageProcessor"
|
| 4 |
+
},
|
| 5 |
+
"backbone_name_or_path": "torchvision/densenet121",
|
| 6 |
+
"image_processor_type": "BackboneMLPHead224ImageProcessor",
|
| 7 |
+
"is_training": false
|
| 8 |
+
}
|