Update README.md
Browse files
README.md
CHANGED
|
@@ -1,6 +1,4 @@
|
|
| 1 |
---
|
| 2 |
-
license: mit
|
| 3 |
-
pipeline_tag: image-classification
|
| 4 |
tags:
|
| 5 |
- cloud
|
| 6 |
- weather
|
|
@@ -8,7 +6,10 @@ tags:
|
|
| 8 |
- cloud types
|
| 9 |
- wmo
|
| 10 |
- cloud image classification
|
|
|
|
|
|
|
| 11 |
---
|
|
|
|
| 12 |
# Genera - Cloud Image Classification Model
|
| 13 |
|
| 14 |
**Version:** 1.0.0
|
|
@@ -72,18 +73,22 @@ The model was trained on the **UGCI (Ultimate Ground-level Cloud Image) dataset*
|
|
| 72 |
|
| 73 |
## You can install necessary packages using pip:
|
| 74 |
|
| 75 |
-
|
| 76 |
|
| 77 |
## Loading the Model
|
| 78 |
|
| 79 |
The model is saved in the Keras native format (.keras). You will need to provide the definitions of the custom layers (RepVGGBlock and NECALayer) when loading.
|
| 80 |
|
| 81 |
-
|
| 82 |
-
|
|
|
|
| 83 |
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
| 87 |
def __init__(self, in_channels, out_channels, kernel_size=3, stride=1,
|
| 88 |
groups=1, deploy=False, use_se=False, **kwargs):
|
| 89 |
super(RepVGGBlock, self).__init__(**kwargs)
|
|
@@ -95,7 +100,7 @@ class RepVGGBlock(layers.Layer):
|
|
| 95 |
self._deploy_mode_internal = deploy
|
| 96 |
self.config_use_se = use_se # Placeholder, not used in this version of RepVGGBlock
|
| 97 |
self.actual_in_channels = None
|
| 98 |
-
|
| 99 |
self.rbr_dense_conv = layers.Conv2D(
|
| 100 |
filters=self.config_out_channels, kernel_size=self.config_kernel_size,
|
| 101 |
strides=self.config_strides_val, padding='same',
|
|
@@ -201,10 +206,11 @@ class RepVGGBlock(layers.Layer):
|
|
| 201 |
return config
|
| 202 |
@classmethod
|
| 203 |
def from_config(cls, config): return cls(**config)
|
| 204 |
-
|
|
|
|
|
|
|
| 205 |
|
| 206 |
-
|
| 207 |
-
class NECALayer(layers.Layer):
|
| 208 |
def __init__(self, channels, gamma=2, b=1, **kwargs):
|
| 209 |
super(NECALayer, self).__init__(**kwargs)
|
| 210 |
self.channels = channels
|
|
@@ -220,7 +226,7 @@ class NECALayer(layers.Layer):
|
|
| 220 |
self.gap = layers.GlobalAveragePooling2D(keepdims=True)
|
| 221 |
self.conv1d = layers.Conv1D(filters=1, kernel_size=kernel_size_for_conv1d, padding='same', use_bias=False, name=self.name + '_eca_conv1d')
|
| 222 |
self.sigmoid = layers.Activation('sigmoid')
|
| 223 |
-
|
| 224 |
def call(self, inputs):
|
| 225 |
if self.channels != inputs.shape[-1]: raise ValueError(f"Input channels {inputs.shape[-1]} != layer channels {self.channels} for {self.name}")
|
| 226 |
x = self.gap(inputs)
|
|
@@ -238,36 +244,40 @@ class NECALayer(layers.Layer):
|
|
| 238 |
return config
|
| 239 |
@classmethod
|
| 240 |
def from_config(cls, config): return cls(**config)
|
| 241 |
-
|
| 242 |
-
|
| 243 |
|
| 244 |
-
import tensorflow as tf
|
| 245 |
-
from tensorflow import keras
|
| 246 |
|
| 247 |
-
|
| 248 |
-
LABEL_MAPPING_FILE = 'path/to/your/label_mapping.json' # Replace with actual path
|
| 249 |
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
|
| 255 |
# Load label mapping
|
| 256 |
-
import json
|
| 257 |
-
with open(LABEL_MAPPING_FILE, 'r') as f:
|
| 258 |
-
|
| 259 |
-
int_to_label = {int(k): v for k, v in label_map_data['int_to_label'].items()}
|
| 260 |
|
| 261 |
**Making Predictions**
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
def preprocess_image_for_prediction(image_path_or_pil_image, target_size=(299, 299)):
|
| 266 |
-
if isinstance(image_path_or_pil_image, str):
|
| 267 |
-
img = Image.open(image_path_or_pil_image)
|
| 268 |
-
else: # Assuming PIL image
|
| 269 |
-
img = image_path_or_pil_image
|
| 270 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
img = img.convert('RGB') # Ensure 3 channels
|
| 272 |
img = img.resize(target_size)
|
| 273 |
img_array = np.array(img, dtype=np.float32)
|
|
@@ -275,24 +285,24 @@ def preprocess_image_for_prediction(image_path_or_pil_image, target_size=(299, 2
|
|
| 275 |
img_array = np.expand_dims(img_array, axis=0) # Add batch dimension
|
| 276 |
return img_array
|
| 277 |
|
| 278 |
-
# Example prediction:
|
| 279 |
-
image_path = 'path/to/your/cloud_image.jpg' # Replace with your image path
|
| 280 |
-
input_tensor = preprocess_image_for_prediction(image_path)
|
| 281 |
-
predictions = loaded_model.predict(input_tensor)
|
| 282 |
-
predicted_probabilities = predictions[0]
|
| 283 |
-
|
| 284 |
-
# Get top prediction
|
| 285 |
-
predicted_class_index = np.argmax(predicted_probabilities)
|
| 286 |
-
predicted_class_name = int_to_label.get(predicted_class_index, "Unknown Class")
|
| 287 |
-
confidence = predicted_probabilities[predicted_class_index]
|
| 288 |
-
|
| 289 |
-
print(f"Predicted Cloud Type: {predicted_class_name}")
|
| 290 |
-
print(f"Confidence: {confidence*100:.2f}%")
|
| 291 |
-
|
| 292 |
-
# Display all class probabilities (optional)
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
|
| 297 |
## 4. Training Procedure
|
| 298 |
Dataset: UGCI
|
|
@@ -347,6 +357,8 @@ Random Contrast adjustments (factor 0.3)
|
|
| 347 |
|
| 348 |
Framework: TensorFlow/Keras
|
| 349 |
|
|
|
|
|
|
|
| 350 |
Optimizer: AdamW (learning_rate=1e-4, weight_decay=5e-5)
|
| 351 |
|
| 352 |
Loss Function: Sparse Categorical Crossentropy
|
|
@@ -361,7 +373,7 @@ EarlyStopping (monitoring val_loss, patience 20, restore_best_weights=True)
|
|
| 361 |
|
| 362 |
ReduceLROnPlateau (monitoring val_loss, patience 10)
|
| 363 |
|
| 364 |
-
Epochs: Trained for
|
| 365 |
|
| 366 |
Batch Size: 32
|
| 367 |
|
|
@@ -468,4 +480,4 @@ This project, including the model weights and source code, is licensed under the
|
|
| 468 |
|
| 469 |
## 11. Acknowledgements
|
| 470 |
|
| 471 |
-
This work was inspired by the methodologies presented in "Improved RepVGG ground-based cloud image classification with attention convolution" by Shi et al. (2024).
|
|
|
|
| 1 |
---
|
|
|
|
|
|
|
| 2 |
tags:
|
| 3 |
- cloud
|
| 4 |
- weather
|
|
|
|
| 6 |
- cloud types
|
| 7 |
- wmo
|
| 8 |
- cloud image classification
|
| 9 |
+
license: mit
|
| 10 |
+
pipeline_tag: image-classification
|
| 11 |
---
|
| 12 |
+
|
| 13 |
# Genera - Cloud Image Classification Model
|
| 14 |
|
| 15 |
**Version:** 1.0.0
|
|
|
|
| 73 |
|
| 74 |
## You can install necessary packages using pip:
|
| 75 |
|
| 76 |
+
pip install tensorflow numpy Pillow
|
| 77 |
|
| 78 |
## Loading the Model
|
| 79 |
|
| 80 |
The model is saved in the Keras native format (.keras). You will need to provide the definitions of the custom layers (RepVGGBlock and NECALayer) when loading.
|
| 81 |
|
| 82 |
+
**IMPORTANT: You must have the RepVGGBlock and NECALayer class definitions available in your Python environment before running this.**
|
| 83 |
+
|
| 84 |
+
**--- CUSTOM LAYER DEFINITIONS ---**
|
| 85 |
|
| 86 |
+
|
| 87 |
+
**--- RepVGGBlock Class Definition ---**
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class RepVGGBlock(layers.Layer):
|
| 92 |
def __init__(self, in_channels, out_channels, kernel_size=3, stride=1,
|
| 93 |
groups=1, deploy=False, use_se=False, **kwargs):
|
| 94 |
super(RepVGGBlock, self).__init__(**kwargs)
|
|
|
|
| 100 |
self._deploy_mode_internal = deploy
|
| 101 |
self.config_use_se = use_se # Placeholder, not used in this version of RepVGGBlock
|
| 102 |
self.actual_in_channels = None
|
| 103 |
+
|
| 104 |
self.rbr_dense_conv = layers.Conv2D(
|
| 105 |
filters=self.config_out_channels, kernel_size=self.config_kernel_size,
|
| 106 |
strides=self.config_strides_val, padding='same',
|
|
|
|
| 206 |
return config
|
| 207 |
@classmethod
|
| 208 |
def from_config(cls, config): return cls(**config)
|
| 209 |
+
**--- End of RepVGGBlock ---**
|
| 210 |
+
|
| 211 |
+
**--- NECALayer Class Definition ---**
|
| 212 |
|
| 213 |
+
class NECALayer(layers.Layer):
|
|
|
|
| 214 |
def __init__(self, channels, gamma=2, b=1, **kwargs):
|
| 215 |
super(NECALayer, self).__init__(**kwargs)
|
| 216 |
self.channels = channels
|
|
|
|
| 226 |
self.gap = layers.GlobalAveragePooling2D(keepdims=True)
|
| 227 |
self.conv1d = layers.Conv1D(filters=1, kernel_size=kernel_size_for_conv1d, padding='same', use_bias=False, name=self.name + '_eca_conv1d')
|
| 228 |
self.sigmoid = layers.Activation('sigmoid')
|
| 229 |
+
|
| 230 |
def call(self, inputs):
|
| 231 |
if self.channels != inputs.shape[-1]: raise ValueError(f"Input channels {inputs.shape[-1]} != layer channels {self.channels} for {self.name}")
|
| 232 |
x = self.gap(inputs)
|
|
|
|
| 244 |
return config
|
| 245 |
@classmethod
|
| 246 |
def from_config(cls, config): return cls(**config)
|
| 247 |
+
|
| 248 |
+
**--- End of NECALayer ---**
|
| 249 |
|
|
|
|
|
|
|
| 250 |
|
| 251 |
+
**--- END OF CUSTOM LAYER DEFINITIONS ---**
|
|
|
|
| 252 |
|
| 253 |
+
import tensorflow as tf
|
| 254 |
+
from tensorflow import keras
|
| 255 |
+
|
| 256 |
+
MODEL_FILE = 'path/to/your/repvgg_neca_deploy_final.keras' # Replace with actual path
|
| 257 |
+
LABEL_MAPPING_FILE = 'path/to/your/label_mapping.json' # Replace with actual path
|
| 258 |
+
|
| 259 |
+
custom_objects = {'RepVGGBlock': RepVGGBlock, 'NECALayer': NECALayer}
|
| 260 |
+
loaded_model = tf.keras.models.load_model(MODEL_FILE, custom_objects=custom_objects, compile=False)
|
| 261 |
+
print("Model loaded successfully!")
|
| 262 |
+
loaded_model.summary() # Optional: to see the loaded architecture
|
| 263 |
|
| 264 |
# Load label mapping
|
| 265 |
+
import json
|
| 266 |
+
with open(LABEL_MAPPING_FILE, 'r') as f:
|
| 267 |
+
label_map_data = json.load(f)
|
| 268 |
+
int_to_label = {int(k): v for k, v in label_map_data['int_to_label'].items()}
|
| 269 |
|
| 270 |
**Making Predictions**
|
| 271 |
+
|
| 272 |
+
from PIL import Image
|
| 273 |
+
import numpy as np
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
|
| 275 |
+
def preprocess_image_for_prediction(image_path_or_pil_image, target_size=(299, 299)):
|
| 276 |
+
if isinstance(image_path_or_pil_image, str):
|
| 277 |
+
img = Image.open(image_path_or_pil_image)
|
| 278 |
+
else: # Assuming PIL image
|
| 279 |
+
img = image_path_or_pil_image
|
| 280 |
+
|
| 281 |
img = img.convert('RGB') # Ensure 3 channels
|
| 282 |
img = img.resize(target_size)
|
| 283 |
img_array = np.array(img, dtype=np.float32)
|
|
|
|
| 285 |
img_array = np.expand_dims(img_array, axis=0) # Add batch dimension
|
| 286 |
return img_array
|
| 287 |
|
| 288 |
+
# Example prediction:
|
| 289 |
+
image_path = 'path/to/your/cloud_image.jpg' # Replace with your image path
|
| 290 |
+
input_tensor = preprocess_image_for_prediction(image_path)
|
| 291 |
+
predictions = loaded_model.predict(input_tensor)
|
| 292 |
+
predicted_probabilities = predictions[0]
|
| 293 |
+
|
| 294 |
+
# Get top prediction
|
| 295 |
+
predicted_class_index = np.argmax(predicted_probabilities)
|
| 296 |
+
predicted_class_name = int_to_label.get(predicted_class_index, "Unknown Class")
|
| 297 |
+
confidence = predicted_probabilities[predicted_class_index]
|
| 298 |
+
|
| 299 |
+
print(f"Predicted Cloud Type: {predicted_class_name}")
|
| 300 |
+
print(f"Confidence: {confidence*100:.2f}%")
|
| 301 |
+
|
| 302 |
+
# Display all class probabilities (optional)
|
| 303 |
+
for i, prob in enumerate(predicted_probabilities):
|
| 304 |
+
class_name = int_to_label.get(i, f"Class_{i}")
|
| 305 |
+
print(f"- {class_name}: {prob*100:.2f}%")
|
| 306 |
|
| 307 |
## 4. Training Procedure
|
| 308 |
Dataset: UGCI
|
|
|
|
| 357 |
|
| 358 |
Framework: TensorFlow/Keras
|
| 359 |
|
| 360 |
+
Compute - Nvidia A100 GPU (Google Colab)
|
| 361 |
+
|
| 362 |
Optimizer: AdamW (learning_rate=1e-4, weight_decay=5e-5)
|
| 363 |
|
| 364 |
Loss Function: Sparse Categorical Crossentropy
|
|
|
|
| 373 |
|
| 374 |
ReduceLROnPlateau (monitoring val_loss, patience 10)
|
| 375 |
|
| 376 |
+
Epochs: Trained for 200 epochs (~7 hours) (EarlyStopping intervened). The best model was restored from Epoch 171 of the final run.
|
| 377 |
|
| 378 |
Batch Size: 32
|
| 379 |
|
|
|
|
| 480 |
|
| 481 |
## 11. Acknowledgements
|
| 482 |
|
| 483 |
+
This work was inspired by the methodologies presented in "Improved RepVGG ground-based cloud image classification with attention convolution" by Shi et al. (2024).
|