vikenkd commited on
Commit
106788a
·
1 Parent(s): 2a2559c

[feat]: update model weight and deployment

Browse files
.gitignore CHANGED
@@ -209,6 +209,7 @@ __marimo__/
209
 
210
  # local
211
  datasets/*
212
- checkpoints/*
213
  dc_env/*
214
  note.md
 
 
209
 
210
  # local
211
  datasets/*
212
+ checkpoints/ckpt_23_10_2025_1/*
213
  dc_env/*
214
  note.md
215
+
deployment/gradio/main.py CHANGED
@@ -1,30 +1,39 @@
1
  import gradio as gr
2
-
3
-
4
- def greet(name):
5
- return f"Hello {name}!"
6
-
7
- def classify_image(image):
8
- return "cat" # Placeholder for actual image classification logic
9
-
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  with gr.Blocks() as demo:
12
- gr.Markdown("# Cat vs Dog Classifier")
 
13
  with gr.Row():
14
  with gr.Column():
15
- image_input = gr.Image(shape=(224, 224))
16
- classify_button = gr.Button("Classify")
 
 
 
17
  with gr.Column():
18
- output_text = gr.Textbox(label="Prediction")
19
-
20
- classify_button.click(fn=classify_image, inputs=image_input, outputs=output_text)
21
 
22
- # Image input and output example
23
- demo = gr.Interface(
24
- fn=greet,
25
- inputs=gr.inputs.Image(shape=(224, 224)),
26
- outputs="text"
27
- )
28
 
29
  demo.launch(debug=True)
30
- demo.launch(share=True)
 
1
  import gradio as gr
2
+ from src.infer import inference_pipeline
3
+
4
+ model_path = "checkpoints/ckpt_23_10_2025/best_cat_dog_classifier_model_20251019_122336.pth"
5
+
6
+ def classify_image(
7
+ image_path: str
8
+ ) -> str:
9
+ """
10
+ Classify the input image as cat or dog.
11
+ """
12
+ if image_path is None:
13
+ return "Please upload an image."
14
+ try:
15
+ prediction = inference_pipeline(
16
+ image_path=image_path,
17
+ model_path=model_path
18
+ )
19
+ return f"Prediction: {prediction.capitalize()}"
20
+ except Exception as e:
21
+ return f"Error: {str(e)}"
22
 
23
  with gr.Blocks() as demo:
24
+ gr.Markdown("# 🐶🐱 Cat vs Dog Classifier")
25
+
26
  with gr.Row():
27
  with gr.Column():
28
+ image_input = gr.Image(
29
+ type="filepath",
30
+ label="Input"
31
+ )
32
+ classify_button = gr.Button("🔍 Classify")
33
  with gr.Column():
34
+ output_text = gr.Textbox(label="🧠 Prediction", placeholder="Result will appear here")
 
 
35
 
36
+ classify_button.click(fn=classify_image, inputs=[image_input], outputs=[output_text])
 
 
 
 
 
37
 
38
  demo.launch(debug=True)
39
+
src/config.py CHANGED
@@ -35,6 +35,10 @@ class CatDogClassifierConfigs(BaseModel):
35
  default=2,
36
  description="Number of output classes (cat and dog)"
37
  )
 
 
 
 
38
 
39
  class CatDogDatasetConfigsInput(BaseModel):
40
  data_path: str = Field(
 
35
  default=2,
36
  description="Number of output classes (cat and dog)"
37
  )
38
+ use_amp: bool = Field(
39
+ default=False,
40
+ description="Whether to use Automatic Mixed Precision (AMP) for training"
41
+ )
42
 
43
  class CatDogDatasetConfigsInput(BaseModel):
44
  data_path: str = Field(
src/data_preprocessing.py CHANGED
@@ -22,6 +22,7 @@ class DataPreprocessor:
22
  transform = transforms.Compose([
23
  transforms.Resize((self.image_size, self.image_size)),
24
  transforms.RandomHorizontalFlip(p=self.horizontal_flip_prob),
 
25
  transforms.ToTensor(),
26
  transforms.Normalize(mean=self.mean, std=self.std),
27
  ])
 
22
  transform = transforms.Compose([
23
  transforms.Resize((self.image_size, self.image_size)),
24
  transforms.RandomHorizontalFlip(p=self.horizontal_flip_prob),
25
+ transforms.RandomRotation(degrees=15),
26
  transforms.ToTensor(),
27
  transforms.Normalize(mean=self.mean, std=self.std),
28
  ])
src/infer.py CHANGED
@@ -3,7 +3,8 @@ from src.model import CatDogClassifier
3
  from src.config import CatDogClassifierConfigs
4
 
5
  def inference_pipeline(
6
- image_path: str = "datasets/single_prediction/cat_or_dog_1.jpg"
 
7
  ):
8
 
9
  # Initialize model
@@ -15,11 +16,12 @@ def inference_pipeline(
15
  kernel_size=3,
16
  stride=2,
17
  padding=1,
18
- num_layers=3
 
19
  )
20
  # Load state_dict
21
  model = CatDogClassifier(configs=model_configs)
22
- model.load_state_dict(torch.load("cat_dog_classifier.pth", map_location="cpu"))
23
  y_pred = model.predict(
24
  model=model,
25
  image_path=image_path
@@ -31,5 +33,5 @@ def inference_pipeline(
31
 
32
 
33
  if __name__ == "__main__":
34
- y_pred = inference_pipeline("datasets/single_prediction/cat_or_dog_1.jpg")
35
  print(y_pred)
 
3
  from src.config import CatDogClassifierConfigs
4
 
5
  def inference_pipeline(
6
+ image_path: str = "datasets/single_prediction/cat_or_dog_1.jpg",
7
+ model_path: str = "checkpoints/ckpt_23_10_2025/best_cat_dog_classifier_model_20251019_122336.pth"
8
  ):
9
 
10
  # Initialize model
 
16
  kernel_size=3,
17
  stride=2,
18
  padding=1,
19
+ num_layers=3,
20
+ use_amp=False
21
  )
22
  # Load state_dict
23
  model = CatDogClassifier(configs=model_configs)
24
+ model.load_state_dict(torch.load(model_path, map_location="cpu"))
25
  y_pred = model.predict(
26
  model=model,
27
  image_path=image_path
 
33
 
34
 
35
  if __name__ == "__main__":
36
+ y_pred = inference_pipeline("D:\\Desktop\\stores\\Application\\GoldenOwl\\technical_test\\test_image_2.jpg")
37
  print(y_pred)
src/model.py CHANGED
@@ -1,8 +1,11 @@
1
- from tqdm.auto import tqdm
2
- from matplotlib import transforms
3
  import torch
4
  import torchvision
5
  import torch.nn as nn
 
 
 
6
  from .config import CatDogClassifierConfigs
7
 
8
  class CatDogClassifier(nn.Module):
@@ -16,6 +19,8 @@ class CatDogClassifier(nn.Module):
16
  self.learning_rate = configs.learning_rate
17
  self.num_classes = configs.num_classes
18
  self.input_channels = configs.input_channels
 
 
19
 
20
  # Initialize the model architecture
21
  self._build_model()
@@ -37,28 +42,43 @@ class CatDogClassifier(nn.Module):
37
  self.conv_layer_2 = nn.Sequential(
38
  nn.Conv2d(
39
  in_channels=64,
40
- out_channels=512,
41
  kernel_size=self.kernel_size,
42
  padding=self.padding
43
  ),
 
44
  nn.ReLU(),
45
- nn.BatchNorm2d(num_features=512),
46
  nn.MaxPool2d(kernel_size=2)
47
  )
48
  self.conv_layer_3 = nn.Sequential(
49
  nn.Conv2d(
50
- in_channels=512,
51
- out_channels=512,
52
  kernel_size=self.kernel_size,
53
  padding=self.padding
54
  ),
 
55
  nn.ReLU(),
56
- nn.BatchNorm2d(num_features=512),
57
  nn.MaxPool2d(kernel_size=2)
58
  )
 
 
 
 
 
 
 
 
 
 
 
59
  self.classifier = nn.Sequential(
60
  nn.Flatten(),
61
- nn.Linear(in_features=512*3*3, out_features=self.num_classes)
 
 
 
 
62
  )
63
 
64
 
@@ -66,9 +86,7 @@ class CatDogClassifier(nn.Module):
66
  x = self.conv_layer_1(x)
67
  x = self.conv_layer_2(x)
68
  x = self.conv_layer_3(x)
69
- x = self.conv_layer_3(x)
70
- x = self.conv_layer_3(x)
71
- x = self.conv_layer_3(x)
72
  x = self.classifier(x)
73
  return x
74
 
@@ -80,9 +98,14 @@ class CatDogClassifier(nn.Module):
80
  num_epochs: int,
81
  loss_fn: nn.Module,
82
  optimizer: torch.optim.Optimizer,
 
83
  ):
84
- # Placeholder for training logic
 
 
85
  print("Training the model with provided data")
 
 
86
  # Implement training loop here
87
  results = {
88
  "train_loss": [],
@@ -98,13 +121,26 @@ class CatDogClassifier(nn.Module):
98
  dataloader=train_dataloader,
99
  loss_fn=loss_fn,
100
  optimizer=optimizer,
 
 
 
101
  )
102
  test_loss, test_acc = self._test_step(
103
  model=model,
104
  dataloader=test_dataloader,
105
- loss_fn=loss_fn,
106
  )
107
 
 
 
 
 
 
 
 
 
 
 
108
  results["train_loss"].append(train_loss)
109
  results["train_acc"].append(train_acc)
110
  results["test_loss"].append(test_loss)
@@ -115,7 +151,7 @@ class CatDogClassifier(nn.Module):
115
  f"Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f} | "
116
  f"Test Loss: {test_loss:.4f}, Test Acc: {test_acc:.4f}"
117
  )
118
-
119
  return results
120
 
121
  def _train_step(
@@ -124,33 +160,43 @@ class CatDogClassifier(nn.Module):
124
  dataloader: torch.utils.data.DataLoader,
125
  loss_fn: nn.Module,
126
  optimizer: torch.optim.Optimizer,
 
 
 
127
  ):
128
  # Define model in training mode
129
  model.train()
130
 
131
- train_loss, train_acc = 0, 0
132
 
133
  # Loop through each batch
134
- for batch_idx, (data, target) in enumerate(dataloader):
 
135
  data, target = data.to(self.configs.device), target.to(self.configs.device)
136
 
137
  # print(f"Batch {batch_idx+1}: data shape {data.shape}, target shape {target.shape}")
138
  # Forward pass
139
- y_pred = model(data)
140
- # Calculate and accumulate loss
141
- loss = loss_fn(y_pred, target)
 
 
 
142
  train_loss += loss.item()
143
  # Backward pass
144
  optimizer.zero_grad()
145
- loss.backward()
146
- optimizer.step()
147
- # Calculate and accumulate accuracy metric across all batches
148
- y_pred_labels = torch.argmax(torch.softmax(y_pred, dim=1), dim=1)
149
- train_acc += (y_pred_labels == target).sum().item()/data.size(0)
 
 
 
150
 
151
  # Adjust loss and accuracy to get average loss and accuracy based on number of batches
152
  train_loss /= len(dataloader)
153
- train_acc /= len(dataloader)
154
 
155
  return train_loss, train_acc
156
 
@@ -162,8 +208,8 @@ class CatDogClassifier(nn.Module):
162
  ):
163
  # Define model in evaluation
164
  model.eval()
165
- test_loss, test_acc = 0, 0
166
- with torch.no_grad():
167
  for batch_idx, (data, target) in enumerate(dataloader):
168
  data, target = data.to(self.configs.device), target.to(self.configs.device)
169
  # Forward pass
@@ -171,12 +217,13 @@ class CatDogClassifier(nn.Module):
171
  # Calculate and accumulate loss
172
  loss = loss_fn(y_pred, target)
173
  test_loss += loss.item()
174
- # Calculate and accumulate accuracy metric across all batches
175
- y_pred_labels = torch.argmax(torch.softmax(y_pred, dim=1), dim=1)
176
- test_acc += (y_pred_labels == target).sum().item()/data.size(0)
 
177
  # Adjust loss and accuracy to get average loss and accuracy based on number of batches
178
  test_loss /= len(dataloader)
179
- test_acc /= len(dataloader)
180
 
181
  return test_loss, test_acc
182
 
@@ -188,21 +235,21 @@ class CatDogClassifier(nn.Module):
188
  ) -> str:
189
  # Load and preprocess the image converting it to a tensor
190
  # and normalizing the pixel values between 0 and 1
191
- image_tensor = torchvision.io.read_image(str(image_path)).type(torch.float32) / 255.0
192
- image_tensor_transformed = transforms.Compose([
193
- transforms.Resize((256, 256)),
194
- transforms.CenterCrop((224, 224)),
195
- transforms.ToTensor(),
196
- ])(image_tensor)
197
-
 
 
 
 
198
  model.eval()
199
- with torch.no_grad():
200
- image_tensor_transformed = image_tensor_transformed.unsqueeze(0) # Add batch dimension
201
- image_tensor_pred = model(image_tensor_transformed).to(self.configs.device)
202
- predicted_label = torch.argmax(torch.softmax(image_tensor_pred, dim=1), dim=1).item()
203
-
204
- if predicted_label == 0:
205
- return "cat"
206
- else:
207
- return "dog"
208
 
 
1
+
2
+ from datetime import datetime
3
  import torch
4
  import torchvision
5
  import torch.nn as nn
6
+
7
+ from tqdm.auto import tqdm
8
+ from torchvision import transforms
9
  from .config import CatDogClassifierConfigs
10
 
11
  class CatDogClassifier(nn.Module):
 
19
  self.learning_rate = configs.learning_rate
20
  self.num_classes = configs.num_classes
21
  self.input_channels = configs.input_channels
22
+ self.device = configs.device
23
+ self.use_amp = configs.use_amp
24
 
25
  # Initialize the model architecture
26
  self._build_model()
 
42
  self.conv_layer_2 = nn.Sequential(
43
  nn.Conv2d(
44
  in_channels=64,
45
+ out_channels=128,
46
  kernel_size=self.kernel_size,
47
  padding=self.padding
48
  ),
49
+ nn.BatchNorm2d(num_features=128),
50
  nn.ReLU(),
 
51
  nn.MaxPool2d(kernel_size=2)
52
  )
53
  self.conv_layer_3 = nn.Sequential(
54
  nn.Conv2d(
55
+ in_channels=128,
56
+ out_channels=256,
57
  kernel_size=self.kernel_size,
58
  padding=self.padding
59
  ),
60
+ nn.BatchNorm2d(num_features=256),
61
  nn.ReLU(),
 
62
  nn.MaxPool2d(kernel_size=2)
63
  )
64
+ self.conv_layer_4 = nn.Sequential(
65
+ nn.Conv2d(
66
+ in_channels=256,
67
+ out_channels=512,
68
+ kernel_size=self.kernel_size,
69
+ padding=self.padding
70
+ ),
71
+ nn.BatchNorm2d(num_features=512),
72
+ nn.ReLU(),
73
+ nn.AdaptiveAvgPool2d((1, 1))
74
+ )
75
  self.classifier = nn.Sequential(
76
  nn.Flatten(),
77
+ nn.Dropout(p=0.5),
78
+ nn.Linear(in_features=512, out_features=256),
79
+ nn.ReLU(),
80
+ nn.Dropout(p=0.3),
81
+ nn.Linear(in_features=256, out_features=self.num_classes)
82
  )
83
 
84
 
 
86
  x = self.conv_layer_1(x)
87
  x = self.conv_layer_2(x)
88
  x = self.conv_layer_3(x)
89
+ x = self.conv_layer_4(x)
 
 
90
  x = self.classifier(x)
91
  return x
92
 
 
98
  num_epochs: int,
99
  loss_fn: nn.Module,
100
  optimizer: torch.optim.Optimizer,
101
+ scheduler: torch.optim.lr_scheduler._LRScheduler = None,
102
  ):
103
+ # Initialize the loss function and optimizer
104
+ scaler = torch.amp.GradScaler(device=self.configs.device, enabled=self.configs.use_amp)
105
+
106
  print("Training the model with provided data")
107
+ best_acc = 0.0
108
+
109
  # Implement training loop here
110
  results = {
111
  "train_loss": [],
 
121
  dataloader=train_dataloader,
122
  loss_fn=loss_fn,
123
  optimizer=optimizer,
124
+ epoch=epoch,
125
+ num_epochs=num_epochs,
126
+ scaler=scaler
127
  )
128
  test_loss, test_acc = self._test_step(
129
  model=model,
130
  dataloader=test_dataloader,
131
+ loss_fn=loss_fn
132
  )
133
 
134
+ # ----- Scheduler update -----
135
+ if scheduler:
136
+ scheduler.step()
137
+
138
+ # ----- Save best model -----
139
+ if test_acc > best_acc:
140
+ best_acc = test_acc
141
+ torch.save(model.state_dict(), f"best_cat_dog_classifier_model_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pth")
142
+
143
+
144
  results["train_loss"].append(train_loss)
145
  results["train_acc"].append(train_acc)
146
  results["test_loss"].append(test_loss)
 
151
  f"Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f} | "
152
  f"Test Loss: {test_loss:.4f}, Test Acc: {test_acc:.4f}"
153
  )
154
+ print(f"\n✅ Training complete! Best Test Accuracy: {best_acc:.4f}")
155
  return results
156
 
157
  def _train_step(
 
160
  dataloader: torch.utils.data.DataLoader,
161
  loss_fn: nn.Module,
162
  optimizer: torch.optim.Optimizer,
163
+ epoch: int,
164
+ num_epochs: int,
165
+ scaler: torch.amp.GradScaler,
166
  ):
167
  # Define model in training mode
168
  model.train()
169
 
170
+ train_loss, train_acc, correct, total_train_examples = 0, 0, 0, 0
171
 
172
  # Loop through each batch
173
+ pbar = tqdm(enumerate(dataloader), desc=f"Epoch [{epoch+1}/{num_epochs}]")
174
+ for batch_idx, (data, target) in pbar:
175
  data, target = data.to(self.configs.device), target.to(self.configs.device)
176
 
177
  # print(f"Batch {batch_idx+1}: data shape {data.shape}, target shape {target.shape}")
178
  # Forward pass
179
+ # y_pred = model(data)
180
+ with torch.amp.autocast(device_type=self.configs.device, enabled=self.configs.use_amp):
181
+ y_pred = model(data)
182
+ # Calculate and accumulate loss
183
+ loss = loss_fn(y_pred, target)
184
+
185
  train_loss += loss.item()
186
  # Backward pass
187
  optimizer.zero_grad()
188
+ scaler.scale(loss).backward()
189
+ scaler.step(optimizer)
190
+ scaler.update()
191
+
192
+ # Calculate and accumulate accuracy metric
193
+ y_pred_labels = torch.argmax(y_pred, dim=1)
194
+ correct += (y_pred_labels == target).sum().item()
195
+ total_train_examples += target.size(0)
196
 
197
  # Adjust loss and accuracy to get average loss and accuracy based on number of batches
198
  train_loss /= len(dataloader)
199
+ train_acc = correct / total_train_examples
200
 
201
  return train_loss, train_acc
202
 
 
208
  ):
209
  # Define model in evaluation
210
  model.eval()
211
+ test_loss, test_acc, correct, total_test_examples = 0, 0, 0, 0
212
+ with torch.inference_mode():
213
  for batch_idx, (data, target) in enumerate(dataloader):
214
  data, target = data.to(self.configs.device), target.to(self.configs.device)
215
  # Forward pass
 
217
  # Calculate and accumulate loss
218
  loss = loss_fn(y_pred, target)
219
  test_loss += loss.item()
220
+ # Calculate and accumulate accuracy metric
221
+ y_pred_labels = torch.argmax(y_pred, dim=1)
222
+ correct += (y_pred_labels == target).sum().item()
223
+ total_test_examples += target.size(0)
224
  # Adjust loss and accuracy to get average loss and accuracy based on number of batches
225
  test_loss /= len(dataloader)
226
+ test_acc = correct / total_test_examples
227
 
228
  return test_loss, test_acc
229
 
 
235
  ) -> str:
236
  # Load and preprocess the image converting it to a tensor
237
  # and normalizing the pixel values between 0 and 1
238
+ image_tensor = torchvision.io.read_image(str(image_path)).float() / 255.0
239
+ transform = transforms.Compose([
240
+ transforms.Resize((224, 224)),
241
+ transforms.Normalize(
242
+ mean=[0.485, 0.456, 0.406],
243
+ std=[0.229, 0.224, 0.225]
244
+ ),
245
+ ])
246
+ image_tensor_transformed = transform(image_tensor).unsqueeze(0).to(self.configs.device)
247
+ # Set model to evaluation mode and make prediction
248
+ model = model.to(self.configs.device)
249
  model.eval()
250
+ with torch.inference_mode():
251
+ image_tensor_pred = model(image_tensor_transformed)
252
+ predicted_label = torch.argmax(image_tensor_pred, dim=1).item()
253
+
254
+ return "cat" if predicted_label == 0 else "dog"
 
 
 
 
255
 
src/train.py CHANGED
@@ -1,8 +1,8 @@
1
  import os
2
  import torch
3
  import torch.nn as nn
 
4
 
5
- from src import model
6
  from src.config import (
7
  CatDogDatasetConfigsInput,
8
  CatDogClassifierConfigs,
@@ -50,6 +50,7 @@ def train_pipeline():
50
  preprocessor = DataPreprocessor(data_preprocessing_configs)
51
  train_dataloader, test_dataloader = preprocessor.create_dataloader()
52
  device = "cuda" if torch.cuda.is_available() else "cpu"
 
53
 
54
  # Model training
55
  model_configs = CatDogClassifierConfigs(
@@ -60,7 +61,8 @@ def train_pipeline():
60
  kernel_size=3,
61
  stride=2,
62
  padding=1,
63
- num_layers=3
 
64
  )
65
 
66
  model = CatDogClassifier(model_configs)
@@ -81,7 +83,7 @@ def train_pipeline():
81
  model=model,
82
  train_dataloader=train_dataloader,
83
  test_dataloader=test_dataloader,
84
- num_epochs=30,
85
  loss_fn=loss_fn,
86
  optimizer=optimizer
87
  )
@@ -89,7 +91,7 @@ def train_pipeline():
89
  print(f"Training completed in {end_time - start_time} seconds.")
90
 
91
  # Save the trained model
92
- torch.save(model.state_dict(), "cat_dog_classifier.pth")
93
  print("Model saved to cat_dog_classifier.pth")
94
 
95
 
 
1
  import os
2
  import torch
3
  import torch.nn as nn
4
+ from datetime import datetime
5
 
 
6
  from src.config import (
7
  CatDogDatasetConfigsInput,
8
  CatDogClassifierConfigs,
 
50
  preprocessor = DataPreprocessor(data_preprocessing_configs)
51
  train_dataloader, test_dataloader = preprocessor.create_dataloader()
52
  device = "cuda" if torch.cuda.is_available() else "cpu"
53
+ print(f"Using device: {device}")
54
 
55
  # Model training
56
  model_configs = CatDogClassifierConfigs(
 
61
  kernel_size=3,
62
  stride=2,
63
  padding=1,
64
+ num_layers=3,
65
+ use_amp=True
66
  )
67
 
68
  model = CatDogClassifier(model_configs)
 
83
  model=model,
84
  train_dataloader=train_dataloader,
85
  test_dataloader=test_dataloader,
86
+ num_epochs=20,
87
  loss_fn=loss_fn,
88
  optimizer=optimizer
89
  )
 
91
  print(f"Training completed in {end_time - start_time} seconds.")
92
 
93
  # Save the trained model
94
+ torch.save(model.state_dict(), f"cat_dog_classifier_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pth")
95
  print("Model saved to cat_dog_classifier.pth")
96
 
97