SebasLopez-ai commited on
Commit
5d7a83f
·
1 Parent(s): 394cef0

Transfer Learning Update: Synchronized models and added comparative documentation

Browse files
models/transfer_learning.keras DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:5516031beafea75d838dc61097ee0bd08eef101f2e9c661495f512ab15cac7e5
3
- size 19458191
 
 
 
 
notebooks_knowledge&presentation/jupyter notebooks/Transfer_Learning_Colab_Models.ipynb ADDED
@@ -0,0 +1,483 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "aeb59df1",
6
+ "metadata": {},
7
+ "source": [
8
+ "# \ud83e\udde0 Transfer Learning in Deep Learning (MobileNetV2 & ResNet50)\n",
9
+ "\n",
10
+ "In this notebook we will apply **Transfer Learning** to classify the CIFAR-10 dataset. We build on the concepts covered in `EXTRA Transfer Learning II.ipynb`.\n",
11
+ "\n",
12
+ "## \ud83d\udccc What is Transfer Learning?\n",
13
+ "Transfer Learning consists of taking a model that has already been pre-trained on a massive dataset and adapting it for our own problem.\n",
14
+ "\n",
15
+ "The process is divided into two main phases:\n",
16
+ "\n",
17
+ "1. **Feature Extraction:**\n",
18
+ " - We import the pre-trained base model without the final classification layer (`include_top=False`).\n",
19
+ " - We **freeze** the base model weights (`trainable = False`) so they are not modified.\n",
20
+ " - We add our own classification \"head\" (Dense layers) at the end.\n",
21
+ " - We train **only** our new classification head. The base model acts solely as a generic \"feature extractor\".\n",
22
+ "\n",
23
+ "2. **Fine-Tuning:**\n",
24
+ " - Once our classification head has learned initial patterns, we **unfreeze** some or all of the top layers of the base model.\n",
25
+ " - We retrain using a **very small Learning Rate**.\n",
26
+ " - This allows the model to subtly adapt its prior knowledge (weights) to the specific characteristics of our images.\n"
27
+ ]
28
+ },
29
+ {
30
+ "cell_type": "code",
31
+ "execution_count": null,
32
+ "id": "88788953",
33
+ "metadata": {},
34
+ "outputs": [],
35
+ "source": [
36
+ "# 1. Setup and imports for training (Google Colab)\n",
37
+ "import os\n",
38
+ "import json\n",
39
+ "import numpy as np\n",
40
+ "import matplotlib.pyplot as plt\n",
41
+ "\n",
42
+ "import tensorflow as tf\n",
43
+ "from tensorflow import keras\n",
44
+ "from keras.datasets import cifar10\n",
45
+ "from keras.utils import to_categorical\n",
46
+ "from keras.layers import Dense, GlobalAveragePooling2D, Input, Dropout\n",
47
+ "from keras.models import Sequential, Model\n",
48
+ "from keras.callbacks import EarlyStopping\n",
49
+ "from keras.optimizers import Adam\n",
50
+ "\n",
51
+ "# Configuration\n",
52
+ "BATCH_SIZE = 64\n",
53
+ "CLASS_NAMES = ['airplane', 'automobile', 'bird', 'cat', 'deer','dog', 'frog', 'horse', 'ship', 'truck']\n"
54
+ ]
55
+ },
56
+ {
57
+ "cell_type": "code",
58
+ "execution_count": null,
59
+ "id": "8f1bf95a",
60
+ "metadata": {},
61
+ "outputs": [],
62
+ "source": [
63
+ "# 2. Load and prepare data\n",
64
+ "(x_train_raw, y_train_raw), (x_test_raw, y_test_raw) = cifar10.load_data()\n",
65
+ "\n",
66
+ "# One-hot encode labels\n",
67
+ "y_train = to_categorical(y_train_raw, 10)\n",
68
+ "y_test = to_categorical(y_test_raw, 10)\n",
69
+ "\n",
70
+ "# Split training set for validation\n",
71
+ "from sklearn.model_selection import train_test_split\n",
72
+ "x_train_split, x_val, y_train_split, y_val = train_test_split(\n",
73
+ " x_train_raw, y_train, test_size=0.2, random_state=42\n",
74
+ ")\n",
75
+ "\n",
76
+ "# Resize for Transfer Learning models: 32x32 -> 96x96 (bilinear interpolation)\n",
77
+ "# Pre-trained models need larger images to extract features correctly.\n",
78
+ "# We pre-resize once here to avoid doing it on every batch during training.\n",
79
+ "x_train_split_96 = tf.image.resize(x_train_split, [96, 96]).numpy()\n",
80
+ "x_val_96 = tf.image.resize(x_val, [96, 96]).numpy()\n",
81
+ "\n",
82
+ "print(f\"X_train_split shape (original): {x_train_split.shape}\")\n",
83
+ "print(f\"X_train_split_96 shape (resized): {x_train_split_96.shape}\")\n",
84
+ "print(f\"X_val shape: {x_val.shape}\")\n",
85
+ "print(f\"X_val_96 shape (resized): {x_val_96.shape}\")\n",
86
+ "print(\"Data ready for training.\")\n"
87
+ ]
88
+ },
89
+ {
90
+ "cell_type": "markdown",
91
+ "id": "fafb0d92",
92
+ "metadata": {},
93
+ "source": [
94
+ "---\n",
95
+ "## \ud83d\ude80 Model 1: MobileNetV2"
96
+ ]
97
+ },
98
+ {
99
+ "cell_type": "code",
100
+ "execution_count": null,
101
+ "id": "15187f8d",
102
+ "metadata": {},
103
+ "outputs": [],
104
+ "source": [
105
+ "# MobileNetV2 - PHASE 1: Feature Extraction\n",
106
+ "from keras.applications import MobileNetV2\n",
107
+ "from keras.applications.mobilenet_v2 import preprocess_input\n",
108
+ "\n",
109
+ "# Preprocess the RESIZED images using the model-specific function\n",
110
+ "x_train_mb = preprocess_input(x_train_split_96.astype('float32'))\n",
111
+ "x_val_mb = preprocess_input(x_val_96.astype('float32'))\n",
112
+ "\n",
113
+ "# Load base model with 96x96 input and FREEZE it\n",
114
+ "base_model_mb = MobileNetV2(input_shape=(96, 96, 3), include_top=False, weights='imagenet')\n",
115
+ "base_model_mb.trainable = False\n",
116
+ "\n",
117
+ "# Build our architecture\n",
118
+ "model_mb = Sequential([\n",
119
+ " base_model_mb,\n",
120
+ " GlobalAveragePooling2D(),\n",
121
+ " Dropout(0.2), # To prevent overfitting\n",
122
+ " Dense(10, activation='softmax')\n",
123
+ "])\n",
124
+ "\n",
125
+ "model_mb.compile(optimizer=Adam(learning_rate=0.001),\n",
126
+ " loss='categorical_crossentropy',\n",
127
+ " metrics=['accuracy'])\n",
128
+ "\n",
129
+ "print(\"Starting Phase 1: Feature Extraction...\")\n",
130
+ "history_mb_fe = model_mb.fit(x_train_mb, y_train_split, epochs=5, validation_data=(x_val_mb, y_val), batch_size=BATCH_SIZE)\n"
131
+ ]
132
+ },
133
+ {
134
+ "cell_type": "code",
135
+ "execution_count": null,
136
+ "id": "17584d82",
137
+ "metadata": {},
138
+ "outputs": [],
139
+ "source": [
140
+ "# MobileNetV2 - PHASE 2: Fine-Tuning\n",
141
+ "\n",
142
+ "# Unfreeze the base model\n",
143
+ "base_model_mb.trainable = True\n",
144
+ "\n",
145
+ "# Recompile with a MUCH SMALLER Learning Rate (1e-5 or 1e-4) to avoid destroying weights\n",
146
+ "model_mb.compile(optimizer=Adam(learning_rate=1e-5),\n",
147
+ " loss='categorical_crossentropy',\n",
148
+ " metrics=['accuracy'])\n",
149
+ "\n",
150
+ "print(\"Starting Phase 2: Fine-Tuning...\")\n",
151
+ "early_stop = EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True)\n",
152
+ "\n",
153
+ "history_mb_ft = model_mb.fit(x_train_mb, y_train_split, epochs=10, \n",
154
+ " validation_data=(x_val_mb, y_val), \n",
155
+ " batch_size=BATCH_SIZE, callbacks=[early_stop])\n",
156
+ "\n",
157
+ "# Combine training histories to plot everything together\n",
158
+ "acc_mbV2 = history_mb_fe.history['accuracy'] + history_mb_ft.history['accuracy']\n",
159
+ "val_acc_mbV2 = history_mb_fe.history['val_accuracy'] + history_mb_ft.history['val_accuracy']\n",
160
+ "loss_mbV2 = history_mb_fe.history['loss'] + history_mb_ft.history['loss']\n",
161
+ "val_loss_mbV2 = history_mb_fe.history['val_loss'] + history_mb_ft.history['val_loss']\n",
162
+ "\n",
163
+ "# Save training plot\n",
164
+ "fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n",
165
+ "axes[0].plot(acc_mbV2, label='Train Accuracy')\n",
166
+ "axes[0].plot(val_acc_mbV2, label='Validation Accuracy')\n",
167
+ "axes[0].axvline(len(history_mb_fe.history['accuracy'])-1, color='r', linestyle='--', label='Start Fine Tuning')\n",
168
+ "axes[0].set_title('MobileNetV2 Accuracy')\n",
169
+ "axes[0].set_xlabel('Epochs')\n",
170
+ "axes[0].set_ylabel('Accuracy')\n",
171
+ "axes[0].legend()\n",
172
+ "\n",
173
+ "axes[1].plot(loss_mbV2, label='Train Loss')\n",
174
+ "axes[1].plot(val_loss_mbV2, label='Validation Loss')\n",
175
+ "axes[1].axvline(len(history_mb_fe.history['loss'])-1, color='r', linestyle='--', label='Start Fine Tuning')\n",
176
+ "axes[1].set_title('MobileNetV2 Loss')\n",
177
+ "axes[1].set_xlabel('Epochs')\n",
178
+ "axes[1].set_ylabel('Loss')\n",
179
+ "axes[1].legend()\n",
180
+ "\n",
181
+ "# Save plot locally (On Colab it will be in the environment files)\n",
182
+ "plt.tight_layout()\n",
183
+ "plt.savefig('mobilenetv2_history.png')\n",
184
+ "print(\"\u2705 History saved as mobilenetv2_history.png\")\n",
185
+ "plt.show()\n",
186
+ "\n",
187
+ "# Save the trained model\n",
188
+ "model_mb.save('mobilenetv2_tl.keras')\n",
189
+ "print(\"\u2705 MobileNetV2 model saved as mobilenetv2_tl.keras\")\n"
190
+ ]
191
+ },
192
+ {
193
+ "cell_type": "markdown",
194
+ "id": "84ad9b75",
195
+ "metadata": {},
196
+ "source": [
197
+ "---\n",
198
+ "## \ud83d\ude80 Model 2: ResNet50"
199
+ ]
200
+ },
201
+ {
202
+ "cell_type": "code",
203
+ "execution_count": null,
204
+ "id": "1e8efedc",
205
+ "metadata": {},
206
+ "outputs": [],
207
+ "source": [
208
+ "# ResNet50 - PHASE 1: Feature Extraction\n",
209
+ "from keras.applications import ResNet50\n",
210
+ "from keras.applications.resnet50 import preprocess_input as preprocess_input_rn\n",
211
+ "\n",
212
+ "# Preprocess the RESIZED images using the ResNet50-specific function\n",
213
+ "x_train_rn = preprocess_input_rn(x_train_split_96.astype('float32'))\n",
214
+ "x_val_rn = preprocess_input_rn(x_val_96.astype('float32'))\n",
215
+ "\n",
216
+ "# Load base model with 96x96 input and FREEZE it\n",
217
+ "base_model_rn = ResNet50(input_shape=(96, 96, 3), include_top=False, weights='imagenet')\n",
218
+ "base_model_rn.trainable = False\n",
219
+ "\n",
220
+ "# Build our architecture\n",
221
+ "model_rn = Sequential([\n",
222
+ " base_model_rn,\n",
223
+ " GlobalAveragePooling2D(),\n",
224
+ " Dropout(0.2), # Added to prevent overfitting\n",
225
+ " Dense(10, activation='softmax')\n",
226
+ "])\n",
227
+ "\n",
228
+ "model_rn.compile(optimizer=Adam(learning_rate=0.001),\n",
229
+ " loss='categorical_crossentropy',\n",
230
+ " metrics=['accuracy'])\n",
231
+ "\n",
232
+ "print(\"Starting Phase 1: Feature Extraction for ResNet50...\")\n",
233
+ "history_rn_fe = model_rn.fit(x_train_rn, y_train_split, epochs=5, validation_data=(x_val_rn, y_val), batch_size=BATCH_SIZE)\n"
234
+ ]
235
+ },
236
+ {
237
+ "cell_type": "code",
238
+ "execution_count": null,
239
+ "id": "eca746e6",
240
+ "metadata": {},
241
+ "outputs": [],
242
+ "source": [
243
+ "# ResNet50 - PHASE 2: Fine-Tuning\n",
244
+ "\n",
245
+ "# Unfreeze the base model\n",
246
+ "base_model_rn.trainable = True\n",
247
+ "\n",
248
+ "# Recompile with a MUCH SMALLER Learning Rate\n",
249
+ "model_rn.compile(optimizer=Adam(learning_rate=1e-5),\n",
250
+ " loss='categorical_crossentropy',\n",
251
+ " metrics=['accuracy'])\n",
252
+ "\n",
253
+ "print(\"Starting Phase 2: Fine-Tuning for ResNet50...\")\n",
254
+ "early_stop_rn = EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True)\n",
255
+ "\n",
256
+ "history_rn_ft = model_rn.fit(x_train_rn, y_train_split, epochs=10, \n",
257
+ " validation_data=(x_val_rn, y_val), \n",
258
+ " batch_size=BATCH_SIZE, callbacks=[early_stop_rn])\n",
259
+ "\n",
260
+ "# Combine training histories\n",
261
+ "acc_rn50 = history_rn_fe.history['accuracy'] + history_rn_ft.history['accuracy']\n",
262
+ "val_acc_rn50 = history_rn_fe.history['val_accuracy'] + history_rn_ft.history['val_accuracy']\n",
263
+ "loss_rn50 = history_rn_fe.history['loss'] + history_rn_ft.history['loss']\n",
264
+ "val_loss_rn50 = history_rn_fe.history['val_loss'] + history_rn_ft.history['val_loss']\n",
265
+ "\n",
266
+ "# Save training plot\n",
267
+ "fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n",
268
+ "axes[0].plot(acc_rn50, label='Train Accuracy')\n",
269
+ "axes[0].plot(val_acc_rn50, label='Validation Accuracy')\n",
270
+ "axes[0].axvline(len(history_rn_fe.history['accuracy'])-1, color='r', linestyle='--', label='Start Fine Tuning')\n",
271
+ "axes[0].set_title('ResNet50 Accuracy')\n",
272
+ "axes[0].set_xlabel('Epochs')\n",
273
+ "axes[0].set_ylabel('Accuracy')\n",
274
+ "axes[0].legend()\n",
275
+ "\n",
276
+ "axes[1].plot(loss_rn50, label='Train Loss')\n",
277
+ "axes[1].plot(val_loss_rn50, label='Validation Loss')\n",
278
+ "axes[1].axvline(len(history_rn_fe.history['loss'])-1, color='r', linestyle='--', label='Start Fine Tuning')\n",
279
+ "axes[1].set_title('ResNet50 Loss')\n",
280
+ "axes[1].set_xlabel('Epochs')\n",
281
+ "axes[1].set_ylabel('Loss')\n",
282
+ "axes[1].legend()\n",
283
+ "\n",
284
+ "plt.tight_layout()\n",
285
+ "plt.savefig('resnet50_history.png')\n",
286
+ "print(\"\u2705 History saved as resnet50_history.png\")\n",
287
+ "plt.show()\n",
288
+ "\n",
289
+ "# Save the trained model\n",
290
+ "model_rn.save('resnet50_tl.keras')\n",
291
+ "print(\"\u2705 ResNet50 model saved as resnet50_tl.keras\")\n"
292
+ ]
293
+ },
294
+ {
295
+ "cell_type": "markdown",
296
+ "id": "37b2be11",
297
+ "metadata": {},
298
+ "source": [
299
+ "---\n",
300
+ "## \ud83d\udcca Local Evaluation and Model Comparison\n",
301
+ "\n",
302
+ "**IMPORTANT NOTE:** Run the cells below **after** training on Google Colab and downloading the models `mobilenetv2_tl.keras` and `resnet50_tl.keras` to your local machine, placing them inside the `models` folder (`/Users/sebastianlopez/Desktop/it-studies/ironhack/week_7/day_2/models`).\n",
303
+ "\n",
304
+ "In this section we will:\n",
305
+ "1. Load the Custom CNN and compute its metrics on the Test dataset.\n",
306
+ "2. Load MobileNetV2 and ResNet50, preprocessing Test images according to each model's requirements.\n",
307
+ "3. Evaluate and compute `accuracy`, `precision`, `recall` and `f1-score`.\n",
308
+ "4. Export the new models' metrics as `.json`.\n",
309
+ "5. Plot and save comparisons (`model_comparison_mobilenetv2_cnn.png` and `model_comparison_resnet50_cnn.png`).\n"
310
+ ]
311
+ },
312
+ {
313
+ "cell_type": "code",
314
+ "execution_count": null,
315
+ "id": "5b0eb49d",
316
+ "metadata": {},
317
+ "outputs": [],
318
+ "source": [
319
+ "# 3. Local Evaluation and JSON Export\n",
320
+ "import os\n",
321
+ "import json\n",
322
+ "import numpy as np\n",
323
+ "import matplotlib.pyplot as plt\n",
324
+ "import tensorflow as tf\n",
325
+ "from sklearn.metrics import classification_report, accuracy_score, precision_score, recall_score, f1_score\n",
326
+ "\n",
327
+ "from tensorflow import keras\n",
328
+ "from keras.datasets import cifar10\n",
329
+ "from keras.utils import to_categorical\n",
330
+ "from keras.applications.mobilenet_v2 import preprocess_input as mb_prep\n",
331
+ "from keras.applications.resnet50 import preprocess_input as rn_prep\n",
332
+ "\n",
333
+ "# Load test data\n",
334
+ "(_, _), (x_test_raw, y_test_raw) = cifar10.load_data()\n",
335
+ "y_test = to_categorical(y_test_raw, 10)\n",
336
+ "\n",
337
+ "# Preprocessing for the test set (same as during training)\n",
338
+ "x_test_custom = x_test_raw.astype('float32') / 255.0\n",
339
+ "\n",
340
+ "# Resize test images to 96x96 for Transfer Learning models\n",
341
+ "# (same resize applied to training data)\n",
342
+ "x_test_96 = tf.image.resize(x_test_raw, [96, 96]).numpy()\n",
343
+ "x_test_mb = mb_prep(x_test_96.astype('float32'))\n",
344
+ "x_test_rn = rn_prep(x_test_96.astype('float32'))\n",
345
+ "\n",
346
+ "# Paths - Make sure the files exist locally\n",
347
+ "MODEL_DIR = '/Users/sebastianlopez/Desktop/it-studies/ironhack/week_7/day_2/models'\n",
348
+ "OUTPUT_DIR = '/Users/sebastianlopez/Desktop/it-studies/ironhack/week_7/day_2/outputs'\n",
349
+ "os.makedirs(OUTPUT_DIR, exist_ok=True)\n",
350
+ "\n",
351
+ "def evaluate_and_save(model_path, x_test_prep, model_name, file_suffix):\n",
352
+ " if not os.path.exists(model_path):\n",
353
+ " print(f\"\u274c Model not found at: {model_path}\")\n",
354
+ " return None\n",
355
+ " \n",
356
+ " print(f\"\\nLoading and evaluating {model_name}...\")\n",
357
+ " model = keras.models.load_model(model_path)\n",
358
+ " loss, accuracy = model.evaluate(x_test_prep, y_test, verbose=0)\n",
359
+ " \n",
360
+ " y_pred_proba = model.predict(x_test_prep, verbose=0)\n",
361
+ " y_pred = np.argmax(y_pred_proba, axis=1)\n",
362
+ " y_true = np.argmax(y_test, axis=1)\n",
363
+ " \n",
364
+ " prec = precision_score(y_true, y_pred, average='weighted', zero_division=0)\n",
365
+ " rec = recall_score(y_true, y_pred, average='weighted', zero_division=0)\n",
366
+ " f1 = f1_score(y_true, y_pred, average='weighted', zero_division=0)\n",
367
+ " \n",
368
+ " print(f\" Test Loss: {loss:.4f}\")\n",
369
+ " print(f\" Test Accuracy: {accuracy:.4f}\")\n",
370
+ " print(f\" Precision: {prec:.4f}\")\n",
371
+ " print(f\" Recall: {rec:.4f}\")\n",
372
+ " print(f\" F1-Score: {f1:.4f}\")\n",
373
+ " \n",
374
+ " metrics = {\n",
375
+ " \"model\": model_name,\n",
376
+ " \"loss\": float(loss),\n",
377
+ " \"accuracy\": float(accuracy),\n",
378
+ " \"precision\": float(prec),\n",
379
+ " \"recall\": float(rec),\n",
380
+ " \"f1_score\": float(f1)\n",
381
+ " }\n",
382
+ " \n",
383
+ " # Export JSON\n",
384
+ " json_path = os.path.join(OUTPUT_DIR, f\"{file_suffix}_metrics.json\")\n",
385
+ " with open(json_path, 'w') as f:\n",
386
+ " json.dump(metrics, f, indent=2)\n",
387
+ " print(f\"\u2705 Metrics exported to {json_path}\")\n",
388
+ " \n",
389
+ " return metrics\n",
390
+ "\n",
391
+ "# Load metrics for all 3 models\n",
392
+ "custom_cnn_path = os.path.join(MODEL_DIR, 'custom_cnn.keras')\n",
393
+ "mb_path = os.path.join(MODEL_DIR, 'mobilenetv2_tl.keras')\n",
394
+ "rn_path = os.path.join(MODEL_DIR, 'resnet50_tl.keras')\n",
395
+ "\n",
396
+ "metrics_custom = evaluate_and_save(custom_cnn_path, x_test_custom, 'Custom CNN', 'custom_cnn')\n",
397
+ "metrics_mb = evaluate_and_save(mb_path, x_test_mb, 'MobileNetV2', 'mobilenetv2_tl')\n",
398
+ "metrics_rn = evaluate_and_save(rn_path, x_test_rn, 'ResNet50', 'resnet50_tl')\n"
399
+ ]
400
+ },
401
+ {
402
+ "cell_type": "code",
403
+ "execution_count": null,
404
+ "id": "ea5d7c75",
405
+ "metadata": {},
406
+ "outputs": [],
407
+ "source": [
408
+ "# 4. Comparison Chart: Custom CNN vs MobileNetV2\n",
409
+ "def plot_comparison(m1, m2, save_filename, title):\n",
410
+ " if m1 is None or m2 is None:\n",
411
+ " print(f\"Missing data to plot {title}\")\n",
412
+ " return\n",
413
+ " \n",
414
+ " labels = ['Accuracy', 'Precision', 'Recall', 'F1-Score']\n",
415
+ " values_m1 = [m1['accuracy'], m1['precision'], m1['recall'], m1['f1_score']]\n",
416
+ " values_m2 = [m2['accuracy'], m2['precision'], m2['recall'], m2['f1_score']]\n",
417
+ " \n",
418
+ " x = np.arange(len(labels))\n",
419
+ " width = 0.35\n",
420
+ " \n",
421
+ " fig, ax = plt.subplots(figsize=(8, 5))\n",
422
+ " rects1 = ax.bar(x - width/2, values_m1, width, label=m1['model'], color='skyblue')\n",
423
+ " rects2 = ax.bar(x + width/2, values_m2, width, label=m2['model'], color='salmon')\n",
424
+ " \n",
425
+ " ax.set_ylabel('Scores')\n",
426
+ " ax.set_title(title, fontweight='bold', fontsize=14)\n",
427
+ " ax.set_xticks(x)\n",
428
+ " ax.set_xticklabels(labels)\n",
429
+ " ax.legend(loc='lower right')\n",
430
+ " ax.set_ylim(0, 1.1)\n",
431
+ " \n",
432
+ " # Add numeric values on top of bars\n",
433
+ " for rects in [rects1, rects2]:\n",
434
+ " for rect in rects:\n",
435
+ " height = rect.get_height()\n",
436
+ " ax.annotate(f'{height:.3f}',\n",
437
+ " xy=(rect.get_x() + rect.get_width() / 2, height),\n",
438
+ " xytext=(0, 3), \n",
439
+ " textcoords=\"offset points\",\n",
440
+ " ha='center', va='bottom', fontsize=9)\n",
441
+ " \n",
442
+ " # Add box with Loss info\n",
443
+ " textstr = f\"Test Loss {m1['model']}: {m1['loss']:.4f}\\nTest Loss {m2['model']}: {m2['loss']:.4f}\"\n",
444
+ " props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)\n",
445
+ " ax.text(1.05, 0.5, textstr, transform=ax.transAxes, fontsize=10, verticalalignment='center', bbox=props)\n",
446
+ " \n",
447
+ " # Save locally\n",
448
+ " save_path = os.path.join(OUTPUT_DIR, save_filename)\n",
449
+ " plt.savefig(save_path, bbox_inches='tight')\n",
450
+ " print(f\"\u2705 Comparison saved at: {save_path}\")\n",
451
+ " plt.show()\n",
452
+ "\n",
453
+ "# Run and plot the first comparison\n",
454
+ "plot_comparison(metrics_custom, metrics_mb, 'model_comparison_mobilenetv2_cnn.png', 'Comparison: Custom CNN vs MobileNetV2')\n"
455
+ ]
456
+ },
457
+ {
458
+ "cell_type": "code",
459
+ "execution_count": null,
460
+ "id": "b28030be",
461
+ "metadata": {},
462
+ "outputs": [],
463
+ "source": [
464
+ "# 5. Comparison Chart: Custom CNN vs ResNet50\n",
465
+ "# Run and plot the second comparison\n",
466
+ "plot_comparison(metrics_custom, metrics_rn, 'model_comparison_resnet50_cnn.png', 'Comparison: Custom CNN vs ResNet50')\n"
467
+ ]
468
+ }
469
+ ],
470
+ "metadata": {
471
+ "kernelspec": {
472
+ "display_name": "Python 3",
473
+ "language": "python",
474
+ "name": "python3"
475
+ },
476
+ "language_info": {
477
+ "name": "python",
478
+ "version": "3.10.0"
479
+ }
480
+ },
481
+ "nbformat": 4,
482
+ "nbformat_minor": 5
483
+ }
notebooks_knowledge&presentation/src folder.md ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ In software development and data science, creating a src (short for source) folder is a widespread best practice for organizing a project.
2
+
3
+ Looking at your current CIFAR-10 image classification project, here is why you and most developers put core Python files (like
4
+
5
+ data_loader.py
6
+ ,
7
+
8
+ model_builder.py
9
+ , etc.) into a src/ directory:
10
+
11
+ 1. Separation of Concerns
12
+ A real-world project has many different types of files:
13
+
14
+ Documentation:
15
+
16
+ README.md
17
+ ,
18
+
19
+ REPORT.md
20
+ Environment settings:
21
+
22
+ requirements.txt
23
+ ,
24
+
25
+ .gitignore
26
+ Exploration & Presentation: Jupyter Notebooks (in your notebooks_knowledge&presentation/ folder)
27
+ User Interface/App: Streamlit or Flask code (in your app/ folder)
28
+ The Core Logic: The actual python scripts that do the heavy lifting.
29
+ By placing the core logic inside the src/ folder, you perfectly separate the "engine" of your project from the documentation, the UI, and the configuration.
30
+
31
+ 2. Modularity and Reusability (Easy Importing)
32
+ By having a src/ folder (with an init.py file inside), Python treats it as a module. This means you can easily reuse the exact same code in different places without copying and pasting.
33
+
34
+ For example, whether you are experimenting in a Jupyter Notebook or running your web interface in
35
+
36
+ app/app.py
37
+ , you can effortlessly use your data loader like this:
38
+
39
+ python
40
+ from src.data_loader import load_data
41
+ from src.model_builder import build_model
42
+ This is much cleaner than defining standard functions repeatedly in different notebooks.
43
+
44
+ 3. A Clean Root Directory
45
+ Without a src/ folder, your root project directory would be flooded with python files:
46
+
47
+ train.py
48
+ ,
49
+
50
+ evaluate.py
51
+ ,
52
+
53
+ data_loader.py
54
+ , app.py, mixed alongside
55
+
56
+ requirements.txt
57
+ and .git. By storing them in src/, your root directory stays clean and easy to read for any other developer (or yourself in the future) who lands on your project's GitHub page.
58
+
59
+ 4. Preventing Import Conflicts (The src Layout)
60
+ In standard Python packaging, using a src layout forces you to test your code exactly how it will be imported by others. It prevents accidental import errors that can happen when your application code is sitting right next to your top-level scripts.
61
+
62
+ Summary:
63
+
64
+ You create src/ to house the core "engine" of your machine learning workflow (loading data, building models, training, evaluating), allowing you to easily import those functions into both your Jupyter Notebooks and your app.py while keeping your workspace organized!
65
+