Image Classification
Keras
LiteRT
TF-Keras
Safetensors
English
efficientnetv2-s
efficientnetv2
fgic
transfer-learning
gem-pooling
focal-loss
swa
grad-cam
calibration
temperature-scaling
computer-vision
tensorflow.js
Eval Results (legacy)
Instructions to use 0xgr3y/Arch-Building-Image-Classification with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Keras
How to use 0xgr3y/Arch-Building-Image-Classification with Keras:
# Available backend options are: "jax", "torch", "tensorflow". import os os.environ["KERAS_BACKEND"] = "jax" import keras model = keras.saving.load_model("hf://0xgr3y/Arch-Building-Image-Classification") - Notebooks
- Google Colab
- Kaggle
Update Readme: v6 final (EfficientNetV2-S, 97.77% acc, T-scaling ECE 0.0053)
Browse files
README.md
CHANGED
|
@@ -4,11 +4,14 @@ pipeline_tag: image-classification
|
|
| 4 |
tags:
|
| 5 |
- tensorflow
|
| 6 |
- keras
|
|
|
|
|
|
|
|
|
|
| 7 |
- image-classification
|
| 8 |
-
-
|
| 9 |
- architecture
|
| 10 |
- building
|
| 11 |
-
-
|
| 12 |
- transfer-learning
|
| 13 |
- gem-pooling
|
| 14 |
- focal-loss
|
|
@@ -17,12 +20,13 @@ tags:
|
|
| 17 |
- grad-cam
|
| 18 |
- calibration
|
| 19 |
- roc-auc
|
|
|
|
| 20 |
library_name: keras
|
| 21 |
language: en
|
| 22 |
datasets:
|
| 23 |
- 0xgr3y/arch-building-dataset
|
| 24 |
widget:
|
| 25 |
-
- src:
|
| 26 |
example_title: Bridge Classification
|
| 27 |
model-index:
|
| 28 |
- name: Architectural Building Image Classifier
|
|
@@ -36,46 +40,58 @@ model-index:
|
|
| 36 |
split: test
|
| 37 |
metrics:
|
| 38 |
- type: accuracy
|
| 39 |
-
value: 0.
|
| 40 |
name: Test Accuracy
|
| 41 |
- type: accuracy
|
| 42 |
-
value: 0.
|
| 43 |
name: Validation Accuracy (SWA)
|
| 44 |
- type: accuracy
|
| 45 |
-
value: 0.
|
| 46 |
name: TTA Accuracy
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
---
|
| 48 |
|
| 49 |
-
 of world architectural buildings using CNN transfer learning with
|
| 56 |
|
| 57 |
<table>
|
| 58 |
-
<tr><td><strong>Architecture</strong></td><td>
|
| 59 |
<tr><td><strong>Task</strong></td><td>Fine-Grained Image Classification (FGIC)</td></tr>
|
| 60 |
-
<tr><td><strong>Test Accuracy</strong></td><td>
|
| 61 |
-
<tr><td><strong>Classes</strong></td><td>8 (
|
| 62 |
<tr><td><strong>Input Size</strong></td><td>320 Γ 320 pixels</td></tr>
|
| 63 |
-
<tr><td><strong>Parameters</strong></td><td>
|
| 64 |
<tr><td><strong>Framework</strong></td><td>TensorFlow / Keras 3</td></tr>
|
| 65 |
<tr><td><strong>License</strong></td><td><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></td></tr>
|
| 66 |
</table>
|
| 67 |
|
| 68 |
## Model Description
|
| 69 |
|
| 70 |
-
A fine-grained image classification model for world architectural buildings. Built on
|
| 71 |
|
| 72 |
**Key architectural contributions:**
|
| 73 |
|
| 74 |
- **GeM Pooling** (Radenovic et al., CVPR 2018) β replaces global average pooling with a learnable power parameter (p=3.0) that emphasizes high-activation features, yielding stronger discriminative representations for FGIC tasks
|
| 75 |
- **Focal Loss** (Lin et al., ICCV 2017, gamma=2.0) β down-weights well-classified examples to focus gradient updates on hard-to-classify building pairs
|
| 76 |
-
- **DiscriminativeAdamW** β extends AdamW with per-
|
| 77 |
- **SWA with BN re-estimation** (Izmailov et al., UAI 2018) β 10-epoch post-training weight averaging with constant LR 1e-4, followed by 100-step batch normalization statistics re-estimation
|
| 78 |
-
- **Grad-CAM** (Selvaraju et al., ICCV 2017) β gradient-weighted class activation mapping for explainability, targeting *
|
| 79 |
- **ECE Calibration** (Guo et al., ICML 2017) β Expected Calibration Error with 15-bin reliability diagram to assess prediction confidence reliability
|
| 80 |
|
| 81 |
## Architecture
|
|
@@ -83,9 +99,9 @@ A fine-grained image classification model for world architectural buildings. Bui
|
|
| 83 |
```
|
| 84 |
Input (320, 320, 3)
|
| 85 |
β
|
| 86 |
-
|
| 87 |
β
|
| 88 |
-
Conv2D(256, 3Γ3, ReLU, padding=same) β 2,
|
| 89 |
BatchNormalization β 1,024 params
|
| 90 |
MaxPooling2D(2Γ2) β 0 params
|
| 91 |
β
|
|
@@ -102,8 +118,8 @@ Output (8 classes)
|
|
| 102 |
|
| 103 |
| Component | Output Shape | Parameters |
|
| 104 |
|-----------|-------------|------------|
|
| 105 |
-
|
|
| 106 |
-
| Conv2D 256 3Γ3 | (None, 10, 10, 256) | 2,
|
| 107 |
| BatchNormalization | (None, 10, 10, 256) | 1,024 |
|
| 108 |
| MaxPooling2D 2Γ2 | (None, 5, 5, 256) | 0 |
|
| 109 |
| GeM Pooling p=3.0 | (None, 256) | 1 |
|
|
@@ -111,10 +127,10 @@ Output (8 classes)
|
|
| 111 |
| BatchNormalization | (None, 256) | 1,024 |
|
| 112 |
| Dropout 0.4 | (None, 256) | 0 |
|
| 113 |
| Dense 8 Softmax | (None, 8) | 2,056 |
|
| 114 |
-
| **Total** | | **
|
| 115 |
-
| Trainable (Phase 1) | | **
|
| 116 |
-
| Trainable (Phase 2) | | **
|
| 117 |
-
| Non-trainable (Phase 1) | | **
|
| 118 |
|
| 119 |
## Performance
|
| 120 |
|
|
@@ -122,32 +138,32 @@ Output (8 classes)
|
|
| 122 |
|
| 123 |
| Metric | Value |
|
| 124 |
|--------|-------|
|
| 125 |
-
| Test Accuracy |
|
| 126 |
-
| Validation Accuracy (SWA) |
|
| 127 |
-
| Test-Time Augmentation |
|
| 128 |
-
| Test Loss | 0.
|
| 129 |
-
| Overfitting Gap (Train β Test) |
|
| 130 |
-
| Macro Avg Precision | 0.
|
| 131 |
-
| Macro Avg Recall | 0.
|
| 132 |
-
| Macro Avg F1-Score | 0.
|
| 133 |
-
| Top-2 Accuracy |
|
| 134 |
-
| Top-3 Accuracy | 99.
|
| 135 |
-
| Macro ROC-AUC (OvR) | 0.
|
| 136 |
-
| ECE (15 bins) | 0.
|
| 137 |
|
| 138 |
### Per-Class Results
|
| 139 |
|
| 140 |
-
| Class | Precision | Recall | F1-Score | Support |
|
| 141 |
-
|-------|-----------|--------|----------|---------|
|
| 142 |
-
|
|
| 143 |
-
|
|
| 144 |
-
|
|
| 145 |
-
|
|
| 146 |
-
|
|
| 147 |
-
|
|
| 148 |
-
|
|
| 149 |
-
|
|
| 150 |
-
| **Macro Avg** | **0.
|
| 151 |
|
| 152 |
### Model Selection
|
| 153 |
|
|
@@ -155,26 +171,26 @@ Four candidate models were evaluated on the validation set:
|
|
| 155 |
|
| 156 |
| Checkpoint | Val Accuracy | Val Loss | Description |
|
| 157 |
|------------|-------------|----------|-------------|
|
| 158 |
-
| `
|
| 159 |
-
| `
|
| 160 |
-
| `
|
| 161 |
-
| **`
|
| 162 |
|
| 163 |
### SWA Progression
|
| 164 |
|
| 165 |
| SWA Epoch | Val Accuracy | Val Loss |
|
| 166 |
|-----------|-------------|----------|
|
| 167 |
-
| 1 |
|
| 168 |
-
| 2 |
|
| 169 |
-
| 3 |
|
| 170 |
-
| 4 |
|
| 171 |
-
| 5 |
|
| 172 |
-
| 6 |
|
| 173 |
-
| 7 |
|
| 174 |
-
| 8 |
|
| 175 |
-
| 9 |
|
| 176 |
-
| 10 |
|
| 177 |
-
| **SWA + BN (final)** | **
|
| 178 |
|
| 179 |

|
| 180 |
|
|
@@ -194,14 +210,14 @@ Four candidate models were evaluated on the validation set:
|
|
| 194 |
|
| 195 |
## Training Details
|
| 196 |
|
| 197 |
-
###
|
| 198 |
|
| 199 |
-
Two-phase
|
| 200 |
|
| 201 |
| Phase | Description | Backbone | Optimizer | LR | Max Epochs | Actual Epochs | CutMix+Mixup | FocalLoss LS |
|
| 202 |
|-------|-------------|----------|-----------|-----|-----------|---------------|---------------|-------------|
|
| 203 |
| **Phase 1** β Feature Extraction | Train custom head only | Frozen (all) | AdamW (wd=2e-5) | 0.001 + CosineDecay + Warmup 3ep | 25 | 1 | Yes (50/50 alternation) | 0.1 |
|
| 204 |
-
| **Phase 2** β Selective Fine-Tuning | Load
|
| 205 |
|
| 206 |
> ΒΉ Phase 1 stops when `val_accuracy β₯ 85%` threshold (myCallback).
|
| 207 |
|
|
@@ -215,13 +231,13 @@ Two-phase strategy train with SWA post-processing:
|
|
| 215 |
| Learning Rate | 0.001 | 3Γ10β»β΄ |
|
| 216 |
| LR Schedule | WarmupCosineDecay (warmup=3) | WarmupCosineDecay (warmup=5) |
|
| 217 |
| Weight Decay | 2Γ10β»β΅ | 2Γ10β»β΅ |
|
| 218 |
-
| LR Multiplier (
|
| 219 |
-
| LR Multiplier (
|
| 220 |
| Loss | FocalLoss (gamma=2.0, LS=0.1) | FocalLoss (gamma=2.0, LS=0.05) |
|
| 221 |
| Batch Size | 32 | 32 |
|
| 222 |
| Early Stopping Patience | 7 | 12 |
|
| 223 |
| myCallback Threshold | val_acc β₯ 0.85 | val_acc β₯ 0.92 |
|
| 224 |
-
| EMA Decay | 0.999 | 0.999 |
|
| 225 |
| SWA Epochs | β | 10 (post-training) |
|
| 226 |
| SWA LR | β | 1Γ10β»β΄ (constant) |
|
| 227 |
| BN Re-estimation Steps | β | 100 |
|
|
@@ -233,25 +249,26 @@ Two-phase strategy train with SWA post-processing:
|
|
| 233 |
|
| 234 |
| Technique | Implementation | Reference |
|
| 235 |
|-----------|---------------|-----------|
|
| 236 |
-
| Transfer Learning |
|
| 237 |
-
| Selective Fine-Tuning | Unfreeze
|
| 238 |
-
| Discriminative LR |
|
| 239 |
| CutMix + Mixup | Alternation per batch (50/50), Phase 1 only | Yun et al., ICCV 2019; Zhang et al., ICLR 2018 |
|
| 240 |
| Focal Loss | gamma=2.0, down-weights easy examples | Lin et al., ICCV 2017 |
|
| 241 |
| Label Smoothing | 0.1 (Phase 1) β 0.05 (Phase 2) | Szegedy et al., CVPR 2016 |
|
| 242 |
| GeM Pooling | p=3.0 learnable, replaces GAP | Radenovic et al., CVPR 2018 |
|
| 243 |
| Dropout | 0.4 after Dense(256)+BN | Srivastava et al., JMLR 2014 |
|
| 244 |
| Batch Normalization | After Conv2D and Dense; frozen during fine-tuning | Ioffe & Szegedy, arXiv 2015 |
|
| 245 |
-
| EMA | Shadow weights, decay=0.999 | Tarvainen & Valpola, NeurIPS 2017 |
|
| 246 |
| SWA | 10-epoch post-training, constant LR 1e-4 | Izmailov et al., UAI 2018 |
|
| 247 |
-
| Data Augmentation | Rotation Β±15Β°, shift Β±10%, zoom Β±20%, brightness 0.75β1.15, horizontal flip | Perez & Wang, arXiv 2017 |
|
|
|
|
| 248 |
| Test-Time Augmentation | 6 augmentation variants, averaged | Shanmugam et al., ICML 2020 |
|
| 249 |
| WarmupCosineDecay | Linear warmup + cosine annealing | Loshchilov & Hutter, ICLR 2017 (SGDR) |
|
| 250 |
| Early Stopping | Patience 7 (Phase 1) / 12 (Phase 2) | Prechelt, Neural Networks 1998 |
|
| 251 |
|
| 252 |
### Dataset
|
| 253 |
|
| 254 |
-
See
|
| 255 |
|
| 256 |
| Split | Images | Percentage |
|
| 257 |
|-------|--------|------------|
|
|
@@ -261,7 +278,7 @@ See more data studio curation [World Architectural Buildings Dataset for Multi
|
|
| 261 |
|
| 262 |
### Data Preprocessing
|
| 263 |
|
| 264 |
-
- **Normalization:** `preprocess_input` from `tf.keras.applications.
|
| 265 |
- **Input resolution:** 320Γ320 (higher than ImageNet default 224Γ224 to capture fine-grained architectural details β textures, ornaments, facade patterns)
|
| 266 |
- **Augmentation:** Applied to training set only; validation and test sets use clean preprocessing
|
| 267 |
- **Split method:** `splitfolders.ratio` from `dataset/`, seed=42
|
|
@@ -270,19 +287,25 @@ See more data studio curation [World Architectural Buildings Dataset for Multi
|
|
| 270 |
|
| 271 |
| File | Description |
|
| 272 |
|------|-------------|
|
| 273 |
-
| `
|
| 274 |
-
| `
|
| 275 |
-
| `
|
| 276 |
-
| `
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
| `tflite/label.txt` | Class label names for TF-Lite |
|
| 278 |
-
| `tfjs_model/` | TensorFlow.js model (
|
| 279 |
| `config.json` | Model configuration and evaluation metrics |
|
| 280 |
| `label_mapping.json` | Class name β ID mapping with training config and architecture info |
|
| 281 |
| `preprocessor_config.json` | Input preprocessing specification (320Γ320) |
|
| 282 |
| `confusion_pairs.json` | Auto-detected confusion pairs from confusion matrix (threshold >5%) |
|
| 283 |
| `class_confidence_stats.json` | Per-class mean/std/p5/p95 confidence distribution |
|
| 284 |
| `model_benchmark.json` | Model parameters, sizes, speed, Top-K, AUC, ECE, TTA metrics |
|
| 285 |
-
| `calibration_data.json` | ECE, bin accuracies/confidences, per-class AUC for
|
|
|
|
| 286 |
| `results/training_curves.png` | Training/validation accuracy and loss curves |
|
| 287 |
| `results/confusion_matrix.png` | Confusion matrix on test set |
|
| 288 |
| `results/per_class_accuracy.png` | Per-class accuracy bar chart |
|
|
@@ -300,127 +323,41 @@ See more data studio curation [World Architectural Buildings Dataset for Multi
|
|
| 300 |
|
| 301 |
### Gradio Space
|
| 302 |
|
| 303 |
-
Try the live
|
| 304 |
|
| 305 |
-
### Python β
|
| 306 |
|
| 307 |
-
``
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
from PIL import Image
|
| 313 |
-
import numpy as np
|
| 314 |
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
class GeMPooling(Layer):
|
| 318 |
-
def __init__(self, p=3.0, eps=1e-6, **kwargs):
|
| 319 |
-
super().__init__(**kwargs)
|
| 320 |
-
self.p_init = p
|
| 321 |
-
self.eps = eps
|
| 322 |
-
def build(self, input_shape):
|
| 323 |
-
self.p = self.add_weight(name="gem_p", shape=(), dtype=tf.float32,
|
| 324 |
-
initializer=tf.keras.initializers.Constant(self.p_init), trainable=True)
|
| 325 |
-
super().build(input_shape)
|
| 326 |
-
def call(self, x):
|
| 327 |
-
x = tf.maximum(x, self.eps)
|
| 328 |
-
x = tf.pow(x, self.p)
|
| 329 |
-
x = tf.reduce_mean(x, axis=[1, 2], keepdims=False)
|
| 330 |
-
return tf.pow(x, 1.0 / self.p)
|
| 331 |
-
def get_config(self):
|
| 332 |
-
return {**super().get_config(), "p": self.p_init, "eps": self.eps}
|
| 333 |
-
|
| 334 |
-
class FocalLoss(tf.keras.losses.Loss):
|
| 335 |
-
def __init__(self, gamma=2.0, alpha=None, label_smoothing=0.0, **kwargs):
|
| 336 |
-
super().__init__(**kwargs)
|
| 337 |
-
self.gamma = gamma
|
| 338 |
-
self.alpha = alpha
|
| 339 |
-
self.label_smoothing = label_smoothing
|
| 340 |
-
def call(self, y_true, y_pred):
|
| 341 |
-
y_pred = tf.clip_by_value(y_pred, 1e-7, 1.0 - 1e-7)
|
| 342 |
-
if self.label_smoothing > 0:
|
| 343 |
-
y_true = y_true * (1.0 - self.label_smoothing) + \
|
| 344 |
-
(self.label_smoothing / tf.cast(tf.shape(y_true)[-1], tf.float32))
|
| 345 |
-
ce = -y_true * tf.math.log(y_pred)
|
| 346 |
-
weight = tf.pow(1.0 - y_pred, self.gamma)
|
| 347 |
-
fl = weight * ce
|
| 348 |
-
if self.alpha is not None:
|
| 349 |
-
alpha_t = y_true * self.alpha
|
| 350 |
-
fl = alpha_t * fl
|
| 351 |
-
return tf.reduce_mean(tf.reduce_sum(fl, axis=-1))
|
| 352 |
-
def get_config(self):
|
| 353 |
-
return {**super().get_config(), "gamma": self.gamma,
|
| 354 |
-
"alpha": self.alpha, "label_smoothing": self.label_smoothing}
|
| 355 |
-
|
| 356 |
-
class DiscriminativeAdamW(tf.keras.optimizers.AdamW):
|
| 357 |
-
def __init__(self, lr_multipliers=None, backbone_layer_idx=0, **kwargs):
|
| 358 |
-
super().__init__(**kwargs)
|
| 359 |
-
self.lr_multipliers = lr_multipliers or {}
|
| 360 |
-
self.backbone_layer_idx = backbone_layer_idx
|
| 361 |
-
self._var_mult_cache = {}
|
| 362 |
-
def _build_var_cache(self, model):
|
| 363 |
-
self._var_mult_cache = {}
|
| 364 |
-
base_model = model.layers[self.backbone_layer_idx]
|
| 365 |
-
for layer in base_model.layers:
|
| 366 |
-
mult = 1.0
|
| 367 |
-
for pattern, m in self.lr_multipliers.items():
|
| 368 |
-
if pattern in layer.name:
|
| 369 |
-
mult = m
|
| 370 |
-
break
|
| 371 |
-
for var in layer.trainable_variables:
|
| 372 |
-
self._var_mult_cache[id(var)] = mult
|
| 373 |
-
def _get_multiplier(self, var):
|
| 374 |
-
return self._var_mult_cache.get(id(var), 1.0)
|
| 375 |
-
def apply_gradients(self, grads_and_vars, **kwargs):
|
| 376 |
-
scaled_gv = []
|
| 377 |
-
for grad, var in grads_and_vars:
|
| 378 |
-
if grad is not None:
|
| 379 |
-
mult = self._get_multiplier(var)
|
| 380 |
-
if mult != 1.0:
|
| 381 |
-
grad = grad * tf.cast(mult, grad.dtype)
|
| 382 |
-
scaled_gv.append((grad, var))
|
| 383 |
-
else:
|
| 384 |
-
scaled_gv.append((grad, var))
|
| 385 |
-
return super().apply_gradients(scaled_gv, **kwargs)
|
| 386 |
-
def get_config(self):
|
| 387 |
-
return {**super().get_config(), "lr_multipliers": self.lr_multipliers,
|
| 388 |
-
"backbone_layer_idx": self.backbone_layer_idx}
|
| 389 |
-
|
| 390 |
-
# =====================---Load Model---==========================
|
| 391 |
-
|
| 392 |
-
LABELS_PATH = hf_hub_download("0xgr3y/Arch-Building-Image-Classification", "label_mapping.json")
|
| 393 |
-
import json
|
| 394 |
-
with open(LABELS_PATH) as f:
|
| 395 |
-
LABELS = json.load(f)["labels"]
|
| 396 |
-
# ["barn","bridge","castle","mosque","skyscraper","stadium","temple","windmill"]
|
| 397 |
|
| 398 |
-
|
| 399 |
-
"GeMPooling": GeMPooling,
|
| 400 |
-
"FocalLoss": FocalLoss,
|
| 401 |
-
"DiscriminativeAdamW": DiscriminativeAdamW,
|
| 402 |
-
}
|
| 403 |
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
# =======================---Inference---==========================
|
| 408 |
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
preds = model.predict(arr, verbose=0)[0]
|
| 412 |
-
print(f"Predicted: {LABELS[np.argmax(preds)]} ({np.max(preds)*100:.1f}%)")
|
| 413 |
-
```
|
| 414 |
|
| 415 |
-
#
|
|
|
|
| 416 |
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
|
|
|
|
|
|
| 422 |
```
|
| 423 |
|
|
|
|
|
|
|
| 424 |
```python
|
| 425 |
import numpy as np
|
| 426 |
import tensorflow as tf
|
|
@@ -428,19 +365,26 @@ from huggingface_hub import hf_hub_download
|
|
| 428 |
from PIL import Image
|
| 429 |
import json
|
| 430 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
labels_path = hf_hub_download("0xgr3y/Arch-Building-Image-Classification", "label_mapping.json")
|
|
|
|
| 432 |
with open(labels_path) as f:
|
| 433 |
LABELS = json.load(f)["labels"]
|
| 434 |
|
| 435 |
-
interpreter = tf.lite.Interpreter(model_path=
|
| 436 |
interpreter.allocate_tensors()
|
| 437 |
-
|
| 438 |
input_details = interpreter.get_input_details()
|
| 439 |
output_details = interpreter.get_output_details()
|
| 440 |
|
| 441 |
img = Image.open("skyscraper_00000.jpg").convert("RGB").resize((320, 320))
|
| 442 |
-
arr = np.expand_dims(
|
| 443 |
-
|
| 444 |
|
| 445 |
interpreter.set_tensor(input_details[0]["index"], arr)
|
| 446 |
interpreter.invoke()
|
|
@@ -451,39 +395,135 @@ for i in top3_idx:
|
|
| 451 |
print(f" {LABELS[i]}: {preds[i]*100:.1f}%")
|
| 452 |
```
|
| 453 |
|
| 454 |
-
### Python β
|
| 455 |
-
|
| 456 |
-
Download the model first:
|
| 457 |
-
```python
|
| 458 |
-
from huggingface_hub import snapshot_download
|
| 459 |
-
snapshot_download("0xgr3y/Arch-Building-Image-Classification", allow_patterns=["saved_model/*"], local_dir=".")
|
| 460 |
-
```
|
| 461 |
-
|
| 462 |
-
Requires the custom layer definitions from the Keras section above.
|
| 463 |
|
| 464 |
```python
|
|
|
|
| 465 |
import tensorflow as tf
|
| 466 |
-
import numpy as np
|
| 467 |
from huggingface_hub import hf_hub_download
|
|
|
|
|
|
|
|
|
|
|
|
|
| 468 |
from PIL import Image
|
|
|
|
| 469 |
import json
|
| 470 |
|
|
|
|
| 471 |
labels_path = hf_hub_download("0xgr3y/Arch-Building-Image-Classification", "label_mapping.json")
|
| 472 |
-
with open(labels_path) as f:
|
| 473 |
-
LABELS = json.load(f)["labels"]
|
| 474 |
|
| 475 |
-
|
| 476 |
-
"DiscriminativeAdamW": DiscriminativeAdamW}
|
| 477 |
|
| 478 |
-
|
|
|
|
| 479 |
|
| 480 |
img = Image.open("skyscraper_00000.jpg").convert("RGB").resize((320, 320))
|
| 481 |
-
arr = np.expand_dims(
|
| 482 |
-
np.array(img, dtype=np.float32)), axis=0)
|
| 483 |
preds = model.predict(arr, verbose=0)[0]
|
| 484 |
print(f"Predicted: {LABELS[np.argmax(preds)]} ({np.max(preds)*100:.1f}%)")
|
| 485 |
```
|
| 486 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 487 |
## Intended Use
|
| 488 |
|
| 489 |
- Architectural style classification from building photographs
|
|
@@ -496,6 +536,7 @@ print(f"Predicted: {LABELS[np.argmax(preds)]} ({np.max(preds)*100:.1f}%)")
|
|
| 496 |
- Trained on Pexels stock photography β performance may differ on user-generated or field photographs
|
| 497 |
- Limited to 8 architectural classes (barn, bridge, castle, mosque, skyscraper, stadium, temple, windmill)
|
| 498 |
- Confusion pair analysis found **0 significant pairs** (threshold >5%) β all 8 classes are well-distinguished by the model; see `confusion_pairs.json` for details
|
|
|
|
| 499 |
- Inference confidence can be low on atypical examples
|
| 500 |
|
| 501 |

|
|
@@ -512,11 +553,11 @@ print(f"Predicted: {LABELS[np.argmax(preds)]} ({np.max(preds)*100:.1f}%)")
|
|
| 512 |
|
| 513 |
- **Gradio Space (Live):** [arch-building-classifier Space](https://huggingface.co/spaces/0xgr3y/arch-building-classifier)
|
| 514 |
- **Dataset Studio:** [0xgr3y/arch-building-dataset](https://huggingface.co/datasets/0xgr3y/arch-building-dataset)
|
| 515 |
-
- **GitHub Repo:** [arcxteam/
|
| 516 |
|
| 517 |
## References
|
| 518 |
|
| 519 |
-
1.
|
| 520 |
2. Radenovic, F., Tolias, G., & Chum, O. (2018). Fine-Tuning CNN Image Retrieval with No Human Annotation. *IEEE TPAMI*. [arXiv:1711.02512](https://arxiv.org/abs/1711.02512)
|
| 521 |
3. Lin, T.-Y., Goyal, P., Girshick, R., He, K., & Dollar, P. (2017). Focal Loss for Dense Object Detection. *ICCV 2017*. [arXiv:1708.02002](https://arxiv.org/abs/1708.02002)
|
| 522 |
4. Izmailov, P., Podoprikhin, D., Garipov, T., Vetrov, D., & Wilson, A. G. (2018). Averaging Weights Leads to Wider Optima and Better Generalization. *UAI 2018*. [arXiv:1803.05407](https://arxiv.org/abs/1803.05407)
|
|
@@ -544,7 +585,7 @@ print(f"Predicted: {LABELS[np.argmax(preds)]} ({np.max(preds)*100:.1f}%)")
|
|
| 544 |
```bibtex
|
| 545 |
@misc{saugani2026_arch_building,
|
| 546 |
title={Fine-Grained Image Classification of World Architecture:
|
| 547 |
-
|
| 548 |
author={Saugani},
|
| 549 |
year={2026},
|
| 550 |
publisher={Hugging Face},
|
|
|
|
| 4 |
tags:
|
| 5 |
- tensorflow
|
| 6 |
- keras
|
| 7 |
+
- safetensors
|
| 8 |
+
- tflite
|
| 9 |
+
- tensorflow.js
|
| 10 |
- image-classification
|
| 11 |
+
- efficientnetv2
|
| 12 |
- architecture
|
| 13 |
- building
|
| 14 |
+
- fgic
|
| 15 |
- transfer-learning
|
| 16 |
- gem-pooling
|
| 17 |
- focal-loss
|
|
|
|
| 20 |
- grad-cam
|
| 21 |
- calibration
|
| 22 |
- roc-auc
|
| 23 |
+
- temperature-scaling
|
| 24 |
library_name: keras
|
| 25 |
language: en
|
| 26 |
datasets:
|
| 27 |
- 0xgr3y/arch-building-dataset
|
| 28 |
widget:
|
| 29 |
+
- src: results/greyscope-labs-architecture-classification-efficientnetv2.jpg
|
| 30 |
example_title: Bridge Classification
|
| 31 |
model-index:
|
| 32 |
- name: Architectural Building Image Classifier
|
|
|
|
| 40 |
split: test
|
| 41 |
metrics:
|
| 42 |
- type: accuracy
|
| 43 |
+
value: 0.9777
|
| 44 |
name: Test Accuracy
|
| 45 |
- type: accuracy
|
| 46 |
+
value: 0.9836
|
| 47 |
name: Validation Accuracy (SWA)
|
| 48 |
- type: accuracy
|
| 49 |
+
value: 0.9799
|
| 50 |
name: TTA Accuracy
|
| 51 |
+
- type: f1
|
| 52 |
+
value: 0.9777
|
| 53 |
+
name: Macro F1
|
| 54 |
+
- type: precision
|
| 55 |
+
value: 0.9777
|
| 56 |
+
name: Macro Precision
|
| 57 |
+
- type: recall
|
| 58 |
+
value: 0.9777
|
| 59 |
+
name: Macro Recall
|
| 60 |
+
- type: roc_auc
|
| 61 |
+
value: 0.9985
|
| 62 |
+
name: Macro ROC-AUC (OvR)
|
| 63 |
---
|
| 64 |
|
| 65 |
+

|
| 66 |
|
| 67 |
+
# Fine-Grained Image Classification of World Architecture: An EfficientNetV2-S Transfer Learning Approach with Layered Regularization
|
| 68 |
|
| 69 |
### Architectural Building Image Classifier
|
| 70 |
|
| 71 |
+
Fine-Grained Image Classification (FGIC) of world architectural buildings using CNN transfer learning with EfficientNetV2-S, enhanced with GeM Pooling, Focal Loss, Discriminative AdamW (LR), Stochastic Weight Averaging (SWA), Grad-CAM explainability, and calibration analysis.
|
| 72 |
|
| 73 |
<table>
|
| 74 |
+
<tr><td><strong>Architecture</strong></td><td>EfficientNetV2-S + GeM Pooling + Focal Loss + SWA</td></tr>
|
| 75 |
<tr><td><strong>Task</strong></td><td>Fine-Grained Image Classification (FGIC)</td></tr>
|
| 76 |
+
<tr><td><strong>Test Accuracy</strong></td><td>97.77%</td></tr>
|
| 77 |
+
<tr><td><strong>Classes</strong></td><td>8 (barn, bridge, castle, mosque, skyscraper, stadium, temple, windmill)</td></tr>
|
| 78 |
<tr><td><strong>Input Size</strong></td><td>320 Γ 320 pixels</td></tr>
|
| 79 |
+
<tr><td><strong>Parameters</strong></td><td>23,350,633</td></tr>
|
| 80 |
<tr><td><strong>Framework</strong></td><td>TensorFlow / Keras 3</td></tr>
|
| 81 |
<tr><td><strong>License</strong></td><td><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></td></tr>
|
| 82 |
</table>
|
| 83 |
|
| 84 |
## Model Description
|
| 85 |
|
| 86 |
+
A fine-grained image classification model for world architectural buildings. Built on EfficientNetV2-S pretrained on ImageNet, enhanced with GeM Pooling (learnable generalized mean pooling), Focal Loss, Discriminative AdamW and Stochastic Weight Averaging (SWA). Extended with Grad-CAM explainability visualization, ROC-AUC evaluation, ECE calibration analysis, and t-SNE embedding visualization.
|
| 87 |
|
| 88 |
**Key architectural contributions:**
|
| 89 |
|
| 90 |
- **GeM Pooling** (Radenovic et al., CVPR 2018) β replaces global average pooling with a learnable power parameter (p=3.0) that emphasizes high-activation features, yielding stronger discriminative representations for FGIC tasks
|
| 91 |
- **Focal Loss** (Lin et al., ICCV 2017, gamma=2.0) β down-weights well-classified examples to focus gradient updates on hard-to-classify building pairs
|
| 92 |
+
- **DiscriminativeAdamW LR** β extends AdamW with per-variable LR scaling on block6 (Γ0.1) via update_step override, combined with selective fine-tuning (block6+top_conv unfrozen, BN frozen). LR scaling produces truly discriminative updates β block6 variables receive 10Γ smaller learning rate than head variables
|
| 93 |
- **SWA with BN re-estimation** (Izmailov et al., UAI 2018) β 10-epoch post-training weight averaging with constant LR 1e-4, followed by 100-step batch normalization statistics re-estimation
|
| 94 |
+
- **Grad-CAM** (Selvaraju et al., ICCV 2017) β gradient-weighted class activation mapping for explainability, targeting *top_conv* (last Conv2D layer)
|
| 95 |
- **ECE Calibration** (Guo et al., ICML 2017) β Expected Calibration Error with 15-bin reliability diagram to assess prediction confidence reliability
|
| 96 |
|
| 97 |
## Architecture
|
|
|
|
| 99 |
```
|
| 100 |
Input (320, 320, 3)
|
| 101 |
β
|
| 102 |
+
EfficientNetV2-S (ImageNet pretrained, 513 layers, 20.33M params)
|
| 103 |
β
|
| 104 |
+
Conv2D(256, 3Γ3, ReLU, padding=same) β 2,949,376 params
|
| 105 |
BatchNormalization β 1,024 params
|
| 106 |
MaxPooling2D(2Γ2) β 0 params
|
| 107 |
β
|
|
|
|
| 118 |
|
| 119 |
| Component | Output Shape | Parameters |
|
| 120 |
|-----------|-------------|------------|
|
| 121 |
+
| EfficientNetV2-S (Functional) | (None, 10, 10, 1280) | 20,331,360 |
|
| 122 |
+
| Conv2D 256 3Γ3 | (None, 10, 10, 256) | 2,949,376 |
|
| 123 |
| BatchNormalization | (None, 10, 10, 256) | 1,024 |
|
| 124 |
| MaxPooling2D 2Γ2 | (None, 5, 5, 256) | 0 |
|
| 125 |
| GeM Pooling p=3.0 | (None, 256) | 1 |
|
|
|
|
| 127 |
| BatchNormalization | (None, 256) | 1,024 |
|
| 128 |
| Dropout 0.4 | (None, 256) | 0 |
|
| 129 |
| Dense 8 Softmax | (None, 8) | 2,056 |
|
| 130 |
+
| **Total** | | **23,350,633** |
|
| 131 |
+
| Trainable (Phase 1) | | **3,018,249** (11.51 MB) |
|
| 132 |
+
| Trainable (Phase 2) | | **17,810,225** (67.94 MB) |
|
| 133 |
+
| Non-trainable (Phase 1) | | **20,332,384** (77.56 MB) |
|
| 134 |
|
| 135 |
## Performance
|
| 136 |
|
|
|
|
| 138 |
|
| 139 |
| Metric | Value |
|
| 140 |
|--------|-------|
|
| 141 |
+
| Test Accuracy | 97.77% |
|
| 142 |
+
| Validation Accuracy (SWA) | 98.36% |
|
| 143 |
+
| Test-Time Augmentation | 97.99% |
|
| 144 |
+
| Test Loss | 0.4262 |
|
| 145 |
+
| Overfitting Gap (Train β Test) | 2.11% |
|
| 146 |
+
| Macro Avg Precision | 0.9777 |
|
| 147 |
+
| Macro Avg Recall | 0.9777 |
|
| 148 |
+
| Macro Avg F1-Score | 0.9777 |
|
| 149 |
+
| Top-2 Accuracy | 99.26% |
|
| 150 |
+
| Top-3 Accuracy | 99.70% |
|
| 151 |
+
| Macro ROC-AUC (OvR) | 0.9985 |
|
| 152 |
+
| ECE (15 bins) | 0.1204 (pre-T-scaling; post-T-scaling: 0.0053, T=0.54) |
|
| 153 |
|
| 154 |
### Per-Class Results
|
| 155 |
|
| 156 |
+
| Class | Precision | Recall | F1-Score | AUC (OvR) | Support |
|
| 157 |
+
|-------|-----------|--------|----------|-----------|---------|
|
| 158 |
+
| barn | 0.9760 | 0.9702 | 0.9731 | 0.9950 | 168 |
|
| 159 |
+
| bridge | 0.9591 | 0.9762 | 0.9676 | 0.9983 | 168 |
|
| 160 |
+
| castle | 0.9763 | 0.9821 | 0.9792 | 0.9996 | 168 |
|
| 161 |
+
| mosque | 0.9763 | 0.9821 | 0.9792 | 0.9987 | 168 |
|
| 162 |
+
| skyscraper | 0.9940 | 0.9940 | 0.9940 | 0.9999 | 168 |
|
| 163 |
+
| stadium | 0.9820 | 0.9762 | 0.9791 | 0.9999 | 168 |
|
| 164 |
+
| temple | 0.9816 | 0.9524 | 0.9668 | 0.9976 | 168 |
|
| 165 |
+
| windmill | 0.9765 | 0.9881 | 0.9822 | 0.9987 | 168 |
|
| 166 |
+
| **Macro Avg** | **0.9777** | **0.9777** | **0.9777** | **0.9985** | **1,344** |
|
| 167 |
|
| 168 |
### Model Selection
|
| 169 |
|
|
|
|
| 171 |
|
| 172 |
| Checkpoint | Val Accuracy | Val Loss | Description |
|
| 173 |
|------------|-------------|----------|-------------|
|
| 174 |
+
| `head_training.keras` | 92.34% | 1.0109 | Phase 1 checkpoint (backbone frozen) |
|
| 175 |
+
| `fine_tuning.keras` | 96.28% | 0.5655 | Phase 2 checkpoint (block6+top_conv unfrozen) |
|
| 176 |
+
| `fine_tuning_ema.keras` | 93.53% | 0.6007 | Phase 2 EMA (per-step Polyak averaging) |
|
| 177 |
+
| **`fine_tuning_swa.keras`** | **98.36%** | **0.4109** | **SWA averaged weights β SELECTED** |
|
| 178 |
|
| 179 |
### SWA Progression
|
| 180 |
|
| 181 |
| SWA Epoch | Val Accuracy | Val Loss |
|
| 182 |
|-----------|-------------|----------|
|
| 183 |
+
| 1 | 95.76% | 0.5831 |
|
| 184 |
+
| 2 | 97.62% | 0.5116 |
|
| 185 |
+
| 3 | 97.69% | 0.4748 |
|
| 186 |
+
| 4 | 96.95% | 0.4390 |
|
| 187 |
+
| 5 | 97.47% | 0.4490 |
|
| 188 |
+
| 6 | 97.84% | 0.4416 |
|
| 189 |
+
| 7 | 98.14% | 0.4055 |
|
| 190 |
+
| 8 | 97.32% | 0.4359 |
|
| 191 |
+
| 9 | 97.02% | 0.4519 |
|
| 192 |
+
| 10 | 97.54% | 0.4226 |
|
| 193 |
+
| **SWA + BN (final)** | **98.36%** | **0.4109** |
|
| 194 |
|
| 195 |

|
| 196 |
|
|
|
|
| 210 |
|
| 211 |
## Training Details
|
| 212 |
|
| 213 |
+
### Training Strategy
|
| 214 |
|
| 215 |
+
Two-phase progressive training with SWA post-processing:
|
| 216 |
|
| 217 |
| Phase | Description | Backbone | Optimizer | LR | Max Epochs | Actual Epochs | CutMix+Mixup | FocalLoss LS |
|
| 218 |
|-------|-------------|----------|-----------|-----|-----------|---------------|---------------|-------------|
|
| 219 |
| **Phase 1** β Feature Extraction | Train custom head only | Frozen (all) | AdamW (wd=2e-5) | 0.001 + CosineDecay + Warmup 3ep | 25 | 1 | Yes (50/50 alternation) | 0.1 |
|
| 220 |
+
| **Phase 2** β Selective Fine-Tuning | Load head_training β fine-tune | block6 + top_conv unfrozen (BN frozen) | DiscriminativeAdamW (block6=0.1Γ) | 3e-4 + CosineDecay + Warmup 5ep | 50 | 1 + 10 SWA | No | 0.05 |
|
| 221 |
|
| 222 |
> ΒΉ Phase 1 stops when `val_accuracy β₯ 85%` threshold (myCallback).
|
| 223 |
|
|
|
|
| 231 |
| Learning Rate | 0.001 | 3Γ10β»β΄ |
|
| 232 |
| LR Schedule | WarmupCosineDecay (warmup=3) | WarmupCosineDecay (warmup=5) |
|
| 233 |
| Weight Decay | 2Γ10β»β΅ | 2Γ10β»β΅ |
|
| 234 |
+
| LR Multiplier (block6) | β | 0.1Γ (LR scaling via update_step, truly discriminative) |
|
| 235 |
+
| LR Multiplier (top_conv+head) | β | 1.0Γ |
|
| 236 |
| Loss | FocalLoss (gamma=2.0, LS=0.1) | FocalLoss (gamma=2.0, LS=0.05) |
|
| 237 |
| Batch Size | 32 | 32 |
|
| 238 |
| Early Stopping Patience | 7 | 12 |
|
| 239 |
| myCallback Threshold | val_acc β₯ 0.85 | val_acc β₯ 0.92 |
|
| 240 |
+
| EMA Decay (per-step) | 0.999 | 0.999 |
|
| 241 |
| SWA Epochs | β | 10 (post-training) |
|
| 242 |
| SWA LR | β | 1Γ10β»β΄ (constant) |
|
| 243 |
| BN Re-estimation Steps | β | 100 |
|
|
|
|
| 249 |
|
| 250 |
| Technique | Implementation | Reference |
|
| 251 |
|-----------|---------------|-----------|
|
| 252 |
+
| Transfer Learning | EfficientNetV2-S backbone frozen in Phase 1 | Yosinski et al., NeurIPS 2014 |
|
| 253 |
+
| Selective Fine-Tuning | Unfreeze block6+top_conv only, BN stays frozen | Howard & Ruder, ACL 2018 |
|
| 254 |
+
| Discriminative LR Scaling | block6 LRΓ0.1 via update_step (truly discriminative β 10Γ smaller updates for pretrained features) | Howard & Ruder, ACL 2018 |
|
| 255 |
| CutMix + Mixup | Alternation per batch (50/50), Phase 1 only | Yun et al., ICCV 2019; Zhang et al., ICLR 2018 |
|
| 256 |
| Focal Loss | gamma=2.0, down-weights easy examples | Lin et al., ICCV 2017 |
|
| 257 |
| Label Smoothing | 0.1 (Phase 1) β 0.05 (Phase 2) | Szegedy et al., CVPR 2016 |
|
| 258 |
| GeM Pooling | p=3.0 learnable, replaces GAP | Radenovic et al., CVPR 2018 |
|
| 259 |
| Dropout | 0.4 after Dense(256)+BN | Srivastava et al., JMLR 2014 |
|
| 260 |
| Batch Normalization | After Conv2D and Dense; frozen during fine-tuning | Ioffe & Szegedy, arXiv 2015 |
|
| 261 |
+
| EMA (per-step) | Shadow weights, decay=0.999, Polyak averaging | Tarvainen & Valpola, NeurIPS 2017 |
|
| 262 |
| SWA | 10-epoch post-training, constant LR 1e-4 | Izmailov et al., UAI 2018 |
|
| 263 |
+
| Data Augmentation | Rotation Β±15Β°, shift Β±10%, shear Β±0.1 rad, zoom Β±20%, brightness 0.75β1.15, channel shift Β±10.0, horizontal flip | Perez & Wang, arXiv 2017 |
|
| 264 |
+
| Random Erasing | p=0.5, area [0.02β0.15], aspect [0.3β3.3], applied pre-normalization | Zhong et al., AAAI 2020 |
|
| 265 |
| Test-Time Augmentation | 6 augmentation variants, averaged | Shanmugam et al., ICML 2020 |
|
| 266 |
| WarmupCosineDecay | Linear warmup + cosine annealing | Loshchilov & Hutter, ICLR 2017 (SGDR) |
|
| 267 |
| Early Stopping | Patience 7 (Phase 1) / 12 (Phase 2) | Prechelt, Neural Networks 1998 |
|
| 268 |
|
| 269 |
### Dataset
|
| 270 |
|
| 271 |
+
See the dataset curation page for [World Architectural Buildings Dataset for MultiβClass Image Classification](https://huggingface.co/datasets/0xgr3y/arch-building-dataset) β 13,440 images (8 classes Γ 1,680, balanced) sourced from Pexels with perceptual (pHash) and exact (SHA256) deduplication.
|
| 272 |
|
| 273 |
| Split | Images | Percentage |
|
| 274 |
|-------|--------|------------|
|
|
|
|
| 278 |
|
| 279 |
### Data Preprocessing
|
| 280 |
|
| 281 |
+
- **Normalization:** `preprocess_input` from `tf.keras.applications.efficientnet_v2` (ImageNet distribution)
|
| 282 |
- **Input resolution:** 320Γ320 (higher than ImageNet default 224Γ224 to capture fine-grained architectural details β textures, ornaments, facade patterns)
|
| 283 |
- **Augmentation:** Applied to training set only; validation and test sets use clean preprocessing
|
| 284 |
- **Split method:** `splitfolders.ratio` from `dataset/`, seed=42
|
|
|
|
| 287 |
|
| 288 |
| File | Description |
|
| 289 |
|------|-------------|
|
| 290 |
+
| `fine_tuning_swa.keras` | Best model β SWA averaged weights (226.75 MB) |
|
| 291 |
+
| `fine_tuning_swa.weights.h5` | Best model weights β clean, no ProtectAI flag (158.31 MB) |
|
| 292 |
+
| `fine_tuning_swa.safetensors` | Best model weights β HF standard, cross-framework (157.10 MB) |
|
| 293 |
+
| `build_model.py` | Architecture reconstruction + CLI inference (20.7 KB) |
|
| 294 |
+
| `head_training.keras` | Phase 1 checkpoint |
|
| 295 |
+
| `fine_tuning.keras` | Phase 2 checkpoint |
|
| 296 |
+
| `fine_tuning_ema.keras` | Phase 2 EMA checkpoint |
|
| 297 |
+
| `saved_model/` | TensorFlow SavedModel format (183.29 MB, for TF Serving) |
|
| 298 |
+
| `tflite/model.tflite` | TensorFlow Lite model (88.36 MB, mobile/embedded) |
|
| 299 |
| `tflite/label.txt` | Class label names for TF-Lite |
|
| 300 |
+
| `tfjs_model/` | TensorFlow.js model (89.54 MB, 23 shards + model.json) |
|
| 301 |
| `config.json` | Model configuration and evaluation metrics |
|
| 302 |
| `label_mapping.json` | Class name β ID mapping with training config and architecture info |
|
| 303 |
| `preprocessor_config.json` | Input preprocessing specification (320Γ320) |
|
| 304 |
| `confusion_pairs.json` | Auto-detected confusion pairs from confusion matrix (threshold >5%) |
|
| 305 |
| `class_confidence_stats.json` | Per-class mean/std/p5/p95 confidence distribution |
|
| 306 |
| `model_benchmark.json` | Model parameters, sizes, speed, Top-K, AUC, ECE, TTA metrics |
|
| 307 |
+
| `calibration_data.json` | ECE, bin accuracies/confidences, per-class AUC, Temperature Scaling (T_opt) for calibrated inference |
|
| 308 |
+
| `temperature_config.json` | Temperature Scaling parameters (T, ECE before/after, method) β Guo et al., ICML 2017 |
|
| 309 |
| `results/training_curves.png` | Training/validation accuracy and loss curves |
|
| 310 |
| `results/confusion_matrix.png` | Confusion matrix on test set |
|
| 311 |
| `results/per_class_accuracy.png` | Per-class accuracy bar chart |
|
|
|
|
| 323 |
|
| 324 |
### Gradio Space
|
| 325 |
|
| 326 |
+
Try the live building classifier: [Architecture Building Image Classifier with Space](https://huggingface.co/spaces/0xgr3y/arch-building-classifier)
|
| 327 |
|
| 328 |
+
### Python β build_model.py (recommended)
|
| 329 |
|
| 330 |
+
`build_model.py` is a standalone module that provides:
|
| 331 |
+
- **Custom class definitions** (`GeMPooling`, `FocalLoss`, `DiscriminativeAdamW`) with `@register_keras_serializable` β importing the module registers all custom classes globally, so `load_model()` works without explicit `custom_objects`.
|
| 332 |
+
- **`ArchBuildingClassifier`** β high-level wrapper class with `build()`, `from_weights()`, `from_keras()`, `predict()`, `predict_batch()` methods.
|
| 333 |
+
- **`CUSTOM_OBJECTS`** dict β fallback for explicit `custom_objects=` in `load_model()`.
|
| 334 |
+
- **`build_model()`** β backward-compatible function that returns a raw `tf.keras.Model`.
|
|
|
|
|
|
|
| 335 |
|
| 336 |
+
Upload `build_model.py` to the same directory as your script or add it to `PYTHONPATH`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
|
| 338 |
+
> **Note:** Filenames below use `fine_tuning_swa` as an example. The actual best checkpoint filename depends on training results β check the repo for the actual `.keras`, `.weights.h5`, and `.safetensors` filenames.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
|
| 340 |
+
```python
|
| 341 |
+
from build_model import ArchBuildingClassifier
|
| 342 |
+
from huggingface_hub import hf_hub_download
|
|
|
|
| 343 |
|
| 344 |
+
# Download weights (clean format)
|
| 345 |
+
weights_path = hf_hub_download("0xgr3y/Arch-Building-Image-Classification", "fine_tuning_swa.weights.h5")
|
|
|
|
|
|
|
|
|
|
| 346 |
|
| 347 |
+
# Load model: architecture + weights
|
| 348 |
+
clf = ArchBuildingClassifier.from_weights(weights_path)
|
| 349 |
|
| 350 |
+
# Inference
|
| 351 |
+
from PIL import Image
|
| 352 |
+
import numpy as np
|
| 353 |
+
label, confidence, top3 = clf.predict(Image.open("skyscraper_00000.jpg"))
|
| 354 |
+
print(f"Predicted: {label} ({confidence:.1%})")
|
| 355 |
+
for cls, prob in top3:
|
| 356 |
+
print(f" {cls}: {prob:.1%}")
|
| 357 |
```
|
| 358 |
|
| 359 |
+
### Python β TF-Lite (fastest inference)
|
| 360 |
+
|
| 361 |
```python
|
| 362 |
import numpy as np
|
| 363 |
import tensorflow as tf
|
|
|
|
| 365 |
from PIL import Image
|
| 366 |
import json
|
| 367 |
|
| 368 |
+
try:
|
| 369 |
+
from tensorflow.keras.applications.efficientnet_v2 import preprocess_input
|
| 370 |
+
except (ImportError, ModuleNotFoundError):
|
| 371 |
+
from tensorflow.keras.applications.efficientnet import preprocess_input
|
| 372 |
+
|
| 373 |
+
# Download
|
| 374 |
+
model_path = hf_hub_download("0xgr3y/Arch-Building-Image-Classification", "tflite/model.tflite")
|
| 375 |
labels_path = hf_hub_download("0xgr3y/Arch-Building-Image-Classification", "label_mapping.json")
|
| 376 |
+
|
| 377 |
with open(labels_path) as f:
|
| 378 |
LABELS = json.load(f)["labels"]
|
| 379 |
|
| 380 |
+
interpreter = tf.lite.Interpreter(model_path=model_path)
|
| 381 |
interpreter.allocate_tensors()
|
|
|
|
| 382 |
input_details = interpreter.get_input_details()
|
| 383 |
output_details = interpreter.get_output_details()
|
| 384 |
|
| 385 |
img = Image.open("skyscraper_00000.jpg").convert("RGB").resize((320, 320))
|
| 386 |
+
arr = np.expand_dims(preprocess_input(
|
| 387 |
+
np.array(img, dtype=np.float32)), axis=0)
|
| 388 |
|
| 389 |
interpreter.set_tensor(input_details[0]["index"], arr)
|
| 390 |
interpreter.invoke()
|
|
|
|
| 395 |
print(f" {LABELS[i]}: {preds[i]*100:.1f}%")
|
| 396 |
```
|
| 397 |
|
| 398 |
+
### Python β Keras (convenient)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
|
| 400 |
```python
|
| 401 |
+
import build_model # registers custom classes via @register_keras_serializable
|
| 402 |
import tensorflow as tf
|
|
|
|
| 403 |
from huggingface_hub import hf_hub_download
|
| 404 |
+
try:
|
| 405 |
+
from tensorflow.keras.applications.efficientnet_v2 import preprocess_input
|
| 406 |
+
except (ImportError, ModuleNotFoundError):
|
| 407 |
+
from tensorflow.keras.applications.efficientnet import preprocess_input
|
| 408 |
from PIL import Image
|
| 409 |
+
import numpy as np
|
| 410 |
import json
|
| 411 |
|
| 412 |
+
model_path = hf_hub_download("0xgr3y/Arch-Building-Image-Classification", "fine_tuning_swa.keras")
|
| 413 |
labels_path = hf_hub_download("0xgr3y/Arch-Building-Image-Classification", "label_mapping.json")
|
|
|
|
|
|
|
| 414 |
|
| 415 |
+
model = tf.keras.models.load_model(model_path, compile=False) # custom_objects not needed
|
|
|
|
| 416 |
|
| 417 |
+
with open(labels_path) as f:
|
| 418 |
+
LABELS = json.load(f)["labels"]
|
| 419 |
|
| 420 |
img = Image.open("skyscraper_00000.jpg").convert("RGB").resize((320, 320))
|
| 421 |
+
arr = np.expand_dims(preprocess_input(np.array(img, dtype=np.float32)), axis=0)
|
|
|
|
| 422 |
preds = model.predict(arr, verbose=0)[0]
|
| 423 |
print(f"Predicted: {LABELS[np.argmax(preds)]} ({np.max(preds)*100:.1f}%)")
|
| 424 |
```
|
| 425 |
|
| 426 |
+
### Python β SavedModel (TF Serving)
|
| 427 |
+
|
| 428 |
+
```python
|
| 429 |
+
from huggingface_hub import snapshot_download
|
| 430 |
+
import tensorflow as tf
|
| 431 |
+
import numpy as np
|
| 432 |
+
from PIL import Image
|
| 433 |
+
|
| 434 |
+
try:
|
| 435 |
+
from tensorflow.keras.applications.efficientnet_v2 import preprocess_input
|
| 436 |
+
except (ImportError, ModuleNotFoundError):
|
| 437 |
+
from tensorflow.keras.applications.efficientnet import preprocess_input
|
| 438 |
+
|
| 439 |
+
snapshot_download("0xgr3y/Arch-Building-Image-Classification", allow_patterns=["saved_model/*"], local_dir=".")
|
| 440 |
+
|
| 441 |
+
# Load SavedModel (created via model.export() β inference-only, no custom_objects needed)
|
| 442 |
+
loaded = tf.saved_model.load("saved_model")
|
| 443 |
+
|
| 444 |
+
img = Image.open("skyscraper_00000.jpg").convert("RGB").resize((320, 320))
|
| 445 |
+
arr = tf.constant(np.expand_dims(preprocess_input(np.array(img, dtype=np.float32)), axis=0))
|
| 446 |
+
preds = loaded(arr).numpy()[0]
|
| 447 |
+
|
| 448 |
+
top3_idx = np.argsort(preds)[::-1][:3]
|
| 449 |
+
for i in top3_idx:
|
| 450 |
+
print(f" Class {i}: {preds[i]*100:.1f}%")
|
| 451 |
+
```
|
| 452 |
+
|
| 453 |
+
### Python β safetensors (HF standard, cross-framework)
|
| 454 |
+
|
| 455 |
+
> **Note:** safetensors stores raw weight tensors without architecture metadata. To load, reconstruct the architecture with `build_model.py` first, then map tensors manually. For most use cases, `.weights.h5` (via `ArchBuildingClassifier.from_weights()`) is simpler and equally clean.
|
| 456 |
+
|
| 457 |
+
```python
|
| 458 |
+
from safetensors.numpy import load_file
|
| 459 |
+
from build_model import ArchBuildingClassifier
|
| 460 |
+
from PIL import Image
|
| 461 |
+
|
| 462 |
+
# Reconstruct architecture
|
| 463 |
+
clf = ArchBuildingClassifier.build()
|
| 464 |
+
|
| 465 |
+
# Load safetensors tensors
|
| 466 |
+
tensors = load_file("fine_tuning_swa.safetensors")
|
| 467 |
+
|
| 468 |
+
# Map tensors to model weights (iterate layers, not .variables β Keras 3 compatible)
|
| 469 |
+
for layer in clf.keras_model.layers:
|
| 470 |
+
for w in layer.weights:
|
| 471 |
+
name = w.name.replace(':', '_').replace('/', '_')
|
| 472 |
+
if name in tensors:
|
| 473 |
+
w.assign(tensors[name])
|
| 474 |
+
|
| 475 |
+
# Inference
|
| 476 |
+
label, confidence, top3 = clf.predict(Image.open("skyscraper_00000.jpg"))
|
| 477 |
+
```
|
| 478 |
+
|
| 479 |
+
## Inference Verification
|
| 480 |
+
|
| 481 |
+
Keras vs TFLite consistency was verified on 8 random test samples (1 per class):
|
| 482 |
+
|
| 483 |
+
| Metric | Result |
|
| 484 |
+
|--------|--------|
|
| 485 |
+
| Keras correct | 7/8 (88%) |
|
| 486 |
+
| TFLite correct | 7/8 (88%) |
|
| 487 |
+
| Keras vs TFLite match | **8/8 (100%)** β identical predictions |
|
| 488 |
+
| Keras inference speed | 358.0 ms |
|
| 489 |
+
| TFLite inference speed | 170.0 ms |
|
| 490 |
+
|
| 491 |
+
> The 1 misclassification (castleβbarn, 65% confidence) is consistent with the 97.77% test accuracy. The 8/8 match confirms TFLite conversion preserves model behavior exactly.
|
| 492 |
+
|
| 493 |
+
## Security Notice (PAIT-KERAS-301)
|
| 494 |
+
|
| 495 |
+
The `.keras` files in this repository are flagged **"Unsafe"** by [Protect AI Guardian](https://protectai.com/insights/models/0xgr3y/Arch-Building-Image-Classification) (threat: PAIT-KERAS-301). This is a **structural false positive**, not a malware detection:
|
| 496 |
+
|
| 497 |
+
- **What the scanner checks:** String-matching of `class_name` fields in the Keras v3 config against a whitelist of built-in Keras layers.
|
| 498 |
+
- **Why flagged:** The model contains a custom layer (`GeMPooling`) β a non-standard class name triggers the flag.
|
| 499 |
+
- **What it does NOT check:** The scanner does not analyze the Python code of the custom class, does not look for `eval()`/`exec()`/`os.system()`, and does not detect actual malware.
|
| 500 |
+
- **Other scanners:** VirusTotal β
, JFrog β
, HF Picklescan β
β all clean. Only Protect AI flags this file.
|
| 501 |
+
|
| 502 |
+
**The custom classes are safe and open source:**
|
| 503 |
+
- `GeMPooling` β Generalized Mean Pooling (Radenovic et al., CVPR 2018). Pure tensor ops: `tf.pow`, `tf.reduce_mean`, `tf.maximum`.
|
| 504 |
+
- `FocalLoss` β Focal Loss (Lin et al., ICCV 2017). Pure tensor ops.
|
| 505 |
+
- `DiscriminativeAdamW` β AdamW subclass with gradient scaling. No file I/O, no network calls, no arbitrary code.
|
| 506 |
+
|
| 507 |
+
Full source code for all custom classes is available in [`build_model.py`](https://huggingface.co/0xgr3y/Arch-Building-Image-Classification/blob/main/build_model.py) and the training notebook for public audit.
|
| 508 |
+
|
| 509 |
+
## Multi-Format Deployment Guide
|
| 510 |
+
|
| 511 |
+
This model is provided in multiple formats to suit different deployment scenarios. Formats marked β
are **not flagged** by Protect AI (no custom class serialization).
|
| 512 |
+
|
| 513 |
+
| Format | File | Size | Protect AI | Inference Speed | Best For |
|
| 514 |
+
|--------|------|------|------------|-----------------|----------|
|
| 515 |
+
| **TF-Lite** β
| `tflite/model.tflite` | ~88 MB | β
Safe | **170.0 ms** (fastest) | Mobile, edge, embedded, HF Space |
|
| 516 |
+
| **SavedModel** β
| `saved_model/` | ~183 MB | β
Safe | β | TensorFlow Serving, cloud backend |
|
| 517 |
+
| **TFJS** β
| `tfjs_model/` | ~90 MB | β
Safe | β | Browser, Node.js (no backend) |
|
| 518 |
+
| **Weights H5** β
| `fine_tuning_swa.weights.h5` | ~158 MB | β
Safe | β | Programmatic load via `build_model.py` |
|
| 519 |
+
| **safetensors** β
| `fine_tuning_swa.safetensors` | ~157 MB | β
Safe | β | HF standard, cross-framework |
|
| 520 |
+
| **Build Script** β
| `build_model.py` | ~21 KB | β
Safe | β | Architecture reconstruction + `load_weights()` |
|
| 521 |
+
| **Keras** β οΈ | `fine_tuning_swa.keras` | ~227 MB | β οΈ Flagged | 358.0 ms | Developer reference, fine-tuning |
|
| 522 |
+
|
| 523 |
+
### Load Examples
|
| 524 |
+
|
| 525 |
+
See **Usage** section above for complete load + inference examples for each format.
|
| 526 |
+
|
| 527 |
## Intended Use
|
| 528 |
|
| 529 |
- Architectural style classification from building photographs
|
|
|
|
| 536 |
- Trained on Pexels stock photography β performance may differ on user-generated or field photographs
|
| 537 |
- Limited to 8 architectural classes (barn, bridge, castle, mosque, skyscraper, stadium, temple, windmill)
|
| 538 |
- Confusion pair analysis found **0 significant pairs** (threshold >5%) β all 8 classes are well-distinguished by the model; see `confusion_pairs.json` for details
|
| 539 |
+
- Barn and windmill share 3 cross-class duplicates (0.02% of dataset) β left as-is due to negligible impact
|
| 540 |
- Inference confidence can be low on atypical examples
|
| 541 |
|
| 542 |

|
|
|
|
| 553 |
|
| 554 |
- **Gradio Space (Live):** [arch-building-classifier Space](https://huggingface.co/spaces/0xgr3y/arch-building-classifier)
|
| 555 |
- **Dataset Studio:** [0xgr3y/arch-building-dataset](https://huggingface.co/datasets/0xgr3y/arch-building-dataset)
|
| 556 |
+
- **GitHub Repo:** [arcxteam/building-architectural-image-classifier](https://github.com/arcxteam/building-architectural-image-classifier)
|
| 557 |
|
| 558 |
## References
|
| 559 |
|
| 560 |
+
1. Tan, M., & Le, Q. V. (2021). EfficientNetV2: Smaller Models and Faster Training. *ICML 2021*. [arXiv:2104.00298](https://arxiv.org/abs/2104.00298)
|
| 561 |
2. Radenovic, F., Tolias, G., & Chum, O. (2018). Fine-Tuning CNN Image Retrieval with No Human Annotation. *IEEE TPAMI*. [arXiv:1711.02512](https://arxiv.org/abs/1711.02512)
|
| 562 |
3. Lin, T.-Y., Goyal, P., Girshick, R., He, K., & Dollar, P. (2017). Focal Loss for Dense Object Detection. *ICCV 2017*. [arXiv:1708.02002](https://arxiv.org/abs/1708.02002)
|
| 563 |
4. Izmailov, P., Podoprikhin, D., Garipov, T., Vetrov, D., & Wilson, A. G. (2018). Averaging Weights Leads to Wider Optima and Better Generalization. *UAI 2018*. [arXiv:1803.05407](https://arxiv.org/abs/1803.05407)
|
|
|
|
| 585 |
```bibtex
|
| 586 |
@misc{saugani2026_arch_building,
|
| 587 |
title={Fine-Grained Image Classification of World Architecture:
|
| 588 |
+
An EfficientNetV2-S Transfer Learning Approach with Layered Regularization},
|
| 589 |
author={Saugani},
|
| 590 |
year={2026},
|
| 591 |
publisher={Hugging Face},
|