1717from absl import flags
1818from PIL import Image
1919from transformers import AutoImageProcessor
20- from transformers import SwinModel
20+ from transformers import SwinForImageClassification
2121
2222import keras_hub
23- from keras_hub .src .models .swin_transformer .swin_transformer_backbone import (
24- SwinTransformerBackbone ,
25- )
26- from keras_hub .src .models .swin_transformer .swin_transformer_image_classifier_preprocessor import ( # noqa: E501
27- SwinTransformerImageClassifierPreprocessor ,
28- )
29- from keras_hub .src .models .swin_transformer .swin_transformer_image_converter import ( # noqa: E501
30- SwinTransformerImageConverter ,
23+ from keras_hub .src .models .swin_transformer .swin_transformer_image_classifier import ( # noqa: E501
24+ SwinTransformerImageClassifier ,
3125)
26+ from keras_hub .src .utils .transformers import convert_swin_transformer
3227
3328FLAGS = flags .FLAGS
3429
5550 f"Must be one of { ',' .join (PRESET_MAP .keys ())} " ,
5651 required = True ,
5752)
53+
54+
55+ class StateDictLoader :
56+ """Minimal loader adapter to port weights from a HF PyTorch state_dict."""
57+
58+ def __init__ (self , state_dict ):
59+ self .state_dict = state_dict
60+ self .prefix = None
61+
62+ def get_tensor (self , hf_weight_key ):
63+ if hf_weight_key in self .state_dict :
64+ return self .state_dict [hf_weight_key ]
65+
66+ if self .prefix is not None :
67+ full_key = self .prefix + hf_weight_key
68+ if full_key in self .state_dict :
69+ return self .state_dict [full_key ]
70+
71+ for full_key in self .state_dict :
72+ if full_key .endswith (hf_weight_key ) and full_key != hf_weight_key :
73+ self .prefix = full_key [: - len (hf_weight_key )]
74+ return self .state_dict [full_key ]
75+
76+ raise KeyError (f"Missing key in HF state_dict: { hf_weight_key } " )
77+
78+ def port_weight (self , keras_variable , hf_weight_key , hook_fn = None ):
79+ hf_tensor = self .get_tensor (hf_weight_key )
80+ if hook_fn :
81+ hf_tensor = hook_fn (hf_tensor , list (keras_variable .shape ))
82+ keras_variable .assign (hf_tensor )
83+
84+
5885flags .DEFINE_string (
5986 "upload_uri" ,
6087 None ,
6390)
6491
6592
66- def convert_image_converter (hf_image_processor ):
67- """Build a SwinTransformerImageConverter from a HuggingFace processor."""
68- config = hf_image_processor .to_dict ()
69- # crop_size is the actual model input resolution after resize+crop.
70- image_size = (
71- config ["crop_size" ]["height" ],
72- config ["crop_size" ]["width" ],
73- )
74- std = config ["image_std" ]
75- mean = config ["image_mean" ]
76- rescale_factor = config ["rescale_factor" ]
77- scale = [rescale_factor / s for s in std ]
78- offset = [- m / s for m , s in zip (mean , std )]
79- return SwinTransformerImageConverter (
80- image_size = image_size ,
81- scale = scale ,
82- offset = offset ,
83- antialias = True ,
84- interpolation = "bicubic" ,
85- crop_to_aspect_ratio = False ,
86- )
87-
88-
89- def validate_output (
90- keras_backbone , keras_image_converter , hf_model , hf_processor
91- ):
92- """Validate converted model outputs match HuggingFace."""
93- # Compare number of parameters between Keras and HF backbone.
94- keras_params = keras_backbone .count_params ()
95- hf_params = sum (p .numel () for p in hf_model .parameters ())
93+ def validate_output (keras_model , keras_image_converter , hf_model , hf_processor ):
94+ """Validate converted classifier outputs match HuggingFace."""
95+ # Compare number of parameters between Keras and HF classifier.
96+ keras_params = keras_model .count_params ()
97+ hf_params = hf_model .num_parameters ()
9698 print (f"🔶 Keras model params: { keras_params :,} " )
9799 print (f"🔶 HF model params: { hf_params :,} " )
98100 assert keras_params == hf_params , (
@@ -116,22 +118,24 @@ def validate_output(
116118 )
117119 )
118120 with torch .no_grad ():
119- hf_features = (
120- hf_model (** hf_inputs ).last_hidden_state .detach ().cpu ().numpy ()
121- )
121+ hf_logits = hf_model (** hf_inputs ).logits .detach ().cpu ().numpy ()
122122
123- keras_features = keras .ops .convert_to_numpy (
124- keras_backbone (keras_preprocessed , training = False )
123+ keras_logits = keras .ops .convert_to_numpy (
124+ keras_model (keras_preprocessed , training = False )
125125 )
126-
127- print ("🔶 Keras output (first token, first 10 dims):" )
128- print (f" { keras_features [0 , 0 , :10 ]} " )
129- print ("🔶 HF output (first token, first 10 dims):" )
130- print (f" { hf_features [0 , 0 , :10 ]} " )
131-
132- modeling_diff = np .mean (np .abs (keras_features - hf_features ))
133- max_diff = np .max (np .abs (keras_features - hf_features ))
134- relative_error = modeling_diff / np .mean (np .abs (hf_features ))
126+ keras_label = np .argmax (keras_logits [0 ])
127+ hf_label = np .argmax (hf_logits [0 ])
128+
129+ print ("🔶 Keras output (first 10 logits):" )
130+ print (f" { keras_logits [0 , :10 ]} " )
131+ print ("🔶 HF output (first 10 logits):" )
132+ print (f" { hf_logits [0 , :10 ]} " )
133+ print (f"🔶 Keras label: { keras_label } " )
134+ print (f"🔶 HF label: { hf_label } " )
135+
136+ modeling_diff = np .mean (np .abs (keras_logits - hf_logits ))
137+ max_diff = np .max (np .abs (keras_logits - hf_logits ))
138+ relative_error = modeling_diff / np .mean (np .abs (hf_logits ))
135139 print (f"🔶 Modeling difference (mean): { modeling_diff :.6f} " )
136140 print (f"🔶 Modeling difference (max): { max_diff :.6f} " )
137141 print (f"🔶 Relative error: { relative_error * 100 :.4f} %" )
@@ -169,33 +173,47 @@ def main(_):
169173 print (f"🏃 Converting { preset } " )
170174
171175 # Load HuggingFace model and processor.
172- hf_model = SwinModel .from_pretrained (hf_preset )
173- hf_processor = AutoImageProcessor .from_pretrained (
174- hf_preset ,
175- do_center_crop = False ,
176- )
177- # Align resize target with crop_size so both steps land on the same size.
178- hf_processor .size = hf_processor .crop_size
176+ hf_model = SwinForImageClassification .from_pretrained (hf_preset )
177+ hf_processor = AutoImageProcessor .from_pretrained (hf_preset )
179178 hf_model .eval ()
180179
181- # Load backbone via on-the-fly conversion (uses convert_swin_transformer.py
182- # under the hood).
183- keras_backbone = SwinTransformerBackbone .from_preset (f"hf://{ hf_preset } " )
184- print ("✅ KerasHub backbone loaded via on-the-fly conversion." )
185- print (f" Parameters: { keras_backbone .count_params ():,} " )
186-
187- keras_image_converter = convert_image_converter (hf_processor )
188- keras_preprocessor = SwinTransformerImageClassifierPreprocessor (
189- image_converter = keras_image_converter
180+ # Load preset architecture/config only and port HF weights manually.
181+ # This avoids requiring model.safetensors for presets that only publish
182+ # pytorch_model.bin.
183+ keras_model = SwinTransformerImageClassifier .from_preset (
184+ f"hf://{ hf_preset } " ,
185+ load_weights = False ,
186+ num_classes = len (hf_model .config .id2label ),
190187 )
191-
192- validate_output (
193- keras_backbone , keras_image_converter , hf_model , hf_processor
188+ hf_state_dict = {
189+ key : value .detach ().cpu ().numpy ()
190+ for key , value in hf_model .state_dict ().items ()
191+ }
192+ loader = StateDictLoader (hf_state_dict )
193+ hf_config = hf_model .config .to_dict ()
194+ convert_swin_transformer .convert_weights (
195+ keras_model .backbone ,
196+ loader ,
197+ hf_config ,
194198 )
199+ convert_swin_transformer .convert_head (
200+ keras_model ,
201+ loader ,
202+ hf_config ,
203+ )
204+ print ("✅ KerasHub classifier loaded via on-the-fly conversion." )
205+ print (f" Parameters: { keras_model .count_params ():,} " )
206+
207+ keras_preprocessor = keras_model .preprocessor
208+ keras_image_converter = keras_preprocessor .image_converter
209+ # Keep resize target aligned with converter image size.
210+ image_height , image_width = keras_image_converter .image_size
211+ hf_processor .size = {"height" : image_height , "width" : image_width }
212+
213+ validate_output (keras_model , keras_image_converter , hf_model , hf_processor )
195214 print ("✅ Output validated." )
196215
197- keras_backbone .save_to_preset (f"./{ preset } " )
198- keras_preprocessor .save_to_preset (f"./{ preset } " )
216+ keras_model .save_to_preset (f"./{ preset } " )
199217 print (f"🏁 Preset saved to ./{ preset } ." )
200218
201219 upload_uri = FLAGS .upload_uri
0 commit comments