Odeyale Kehinde Musiliudeen commited on
Commit
868dae8
·
1 Parent(s): 2fa8270

Deploy gradio Animal Classifier

Browse files
Files changed (5) hide show
  1. .gitignore +18 -0
  2. Animal_Classification_with_PyTorch.ipynb +0 -557
  3. README.md +0 -0
  4. app.py +684 -49
  5. requirements.txt +13 -4
.gitignore ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+
3
+ *.pyc
4
+
5
+ .env
6
+
7
+ venv/
8
+ .venv/
9
+
10
+ .ipynb_checkpoints
11
+
12
+ .idea/
13
+
14
+ .vscode/
15
+
16
+ logs/
17
+
18
+ .DS_Store
Animal_Classification_with_PyTorch.ipynb DELETED
@@ -1,557 +0,0 @@
1
- {
2
- "nbformat": 4,
3
- "nbformat_minor": 0,
4
- "metadata": {
5
- "colab": {
6
- "provenance": [],
7
- "gpuType": "T4"
8
- },
9
- "kernelspec": {
10
- "name": "python3",
11
- "display_name": "Python 3"
12
- },
13
- "language_info": {
14
- "name": "python"
15
- },
16
- "accelerator": "GPU"
17
- },
18
- "cells": [
19
- {
20
- "cell_type": "code",
21
- "execution_count": null,
22
- "metadata": {
23
- "id": "RjttzVZ579G3"
24
- },
25
- "outputs": [],
26
- "source": []
27
- },
28
- {
29
- "cell_type": "markdown",
30
- "source": [
31
- "## Animal Classification with PyTorch"
32
- ],
33
- "metadata": {
34
- "id": "eDAUjwYyzlX8"
35
- }
36
- },
37
- {
38
- "cell_type": "code",
39
- "source": [
40
- "# Imports\n",
41
- "import torch\n",
42
- "import torch.nn as nn\n",
43
- "import torch.optim as optim\n",
44
- "from torch.utils.data import DataLoader, random_split\n",
45
- "from torchvision import datasets, transforms, models"
46
- ],
47
- "metadata": {
48
- "id": "k82-c-BITr7H"
49
- },
50
- "execution_count": 2,
51
- "outputs": []
52
- },
53
- {
54
- "cell_type": "code",
55
- "source": [
56
- "!unzip -q \"/content/dataset.zip\" -d /content/dataset\n",
57
- "data_dir = '/content/dataset'"
58
- ],
59
- "metadata": {
60
- "id": "x57UatR7pizC"
61
- },
62
- "execution_count": 4,
63
- "outputs": []
64
- },
65
- {
66
- "cell_type": "code",
67
- "source": [
68
- "!ls /content/"
69
- ],
70
- "metadata": {
71
- "colab": {
72
- "base_uri": "https://localhost:8080/"
73
- },
74
- "id": "aNXIeY_9NJfr",
75
- "outputId": "876269cc-719e-4709-b34d-cd38aad2eeea"
76
- },
77
- "execution_count": 5,
78
- "outputs": [
79
- {
80
- "output_type": "stream",
81
- "name": "stdout",
82
- "text": [
83
- "dataset dataset.zip drive sample_data\n"
84
- ]
85
- }
86
- ]
87
- },
88
- {
89
- "cell_type": "code",
90
- "source": [
91
- "\n",
92
- "from google.colab import drive\n",
93
- "drive.mount('/content/drive')"
94
- ],
95
- "metadata": {
96
- "colab": {
97
- "base_uri": "https://localhost:8080/"
98
- },
99
- "id": "Cd2bJUcW27jF",
100
- "outputId": "e8b3a9cc-2e47-4ee0-9a6e-fd86fd77c591"
101
- },
102
- "execution_count": 2,
103
- "outputs": [
104
- {
105
- "output_type": "stream",
106
- "name": "stdout",
107
- "text": [
108
- "Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount(\"/content/drive\", force_remount=True).\n"
109
- ]
110
- }
111
- ]
112
- },
113
- {
114
- "cell_type": "code",
115
- "source": [
116
- "# Transforms\n",
117
- "# Training transforms include augmentation to reduce overfitting\n",
118
- "train_transform = transforms.Compose([\n",
119
- " transforms.Resize((224, 224)), # ResNet expects 224x224 input\n",
120
- " transforms.RandomHorizontalFlip(),\n",
121
- " transforms.RandomRotation(15),\n",
122
- " transforms.ColorJitter(brightness=0.2, contrast=0.2),\n",
123
- " transforms.ToTensor(),\n",
124
- " transforms.Normalize(mean=[0.485, 0.456, 0.406], # ImageNet stats —\n",
125
- " std=[0.229, 0.224, 0.225]) # required since we use pretrained weights\n",
126
- "])\n",
127
- "\n",
128
- "# Validation/test transforms: no augmentation, just resize + normalize\n",
129
- "val_transform = transforms.Compose([\n",
130
- " transforms.Resize((224, 224)),\n",
131
- " transforms.ToTensor(),\n",
132
- " transforms.Normalize(mean=[0.485, 0.456, 0.406],\n",
133
- " std=[0.229, 0.224, 0.225])\n",
134
- "])"
135
- ],
136
- "metadata": {
137
- "id": "9I_mKjfyo3H0"
138
- },
139
- "execution_count": 6,
140
- "outputs": []
141
- },
142
- {
143
- "cell_type": "code",
144
- "source": [
145
- "\n"
146
- ],
147
- "metadata": {
148
- "id": "HbI3T56qo_q3"
149
- },
150
- "execution_count": 6,
151
- "outputs": []
152
- },
153
- {
154
- "cell_type": "code",
155
- "source": [
156
- "!ls /content/dataset/dataset"
157
- ],
158
- "metadata": {
159
- "colab": {
160
- "base_uri": "https://localhost:8080/"
161
- },
162
- "id": "0ywm31h2Tvt4",
163
- "outputId": "5a43b8f5-82a3-4767-85bd-37cbaac35421"
164
- },
165
- "execution_count": 7,
166
- "outputs": [
167
- {
168
- "output_type": "stream",
169
- "name": "stdout",
170
- "text": [
171
- "Butterfly Cat\tChicken Cow Dog Elephant Horse Sheep Spider Squirrel\n"
172
- ]
173
- }
174
- ]
175
- },
176
- {
177
- "cell_type": "code",
178
- "source": [
179
- "# Load dataset and split\n",
180
- "data_dir = '/content/dataset/dataset' # path to your folder\n",
181
- "\n",
182
- "full_dataset = datasets.ImageFolder(root=data_dir, transform=train_transform)\n",
183
- "class_names = full_dataset.classes\n",
184
- "num_classes = len(class_names)\n",
185
- "print(f'Classes: {class_names}')\n",
186
- "\n",
187
- "# Split into train/val (80/20)\n",
188
- "train_size = int(0.8 * len(full_dataset))\n",
189
- "val_size = len(full_dataset) - train_size\n",
190
- "train_dataset, val_dataset = random_split(full_dataset, [train_size, val_size])\n",
191
- "\n",
192
- "# Val set should use val_transform, not train_transform (no augmentation)\n",
193
- "val_dataset.dataset.transform = val_transform\n",
194
- "\n",
195
- "train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=2)\n",
196
- "val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=2)"
197
- ],
198
- "metadata": {
199
- "colab": {
200
- "base_uri": "https://localhost:8080/"
201
- },
202
- "id": "AL0BnBNopGDM",
203
- "outputId": "177857e2-f824-463e-d88e-167cfe03b220"
204
- },
205
- "execution_count": 8,
206
- "outputs": [
207
- {
208
- "output_type": "stream",
209
- "name": "stdout",
210
- "text": [
211
- "Classes: ['Butterfly', 'Cat', 'Chicken', 'Cow', 'Dog', 'Elephant', 'Horse', 'Sheep', 'Spider', 'Squirrel']\n"
212
- ]
213
- }
214
- ]
215
- },
216
- {
217
- "cell_type": "code",
218
- "source": [
219
- "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
220
- "print(device)\n",
221
- "print(torch.cuda.is_available())"
222
- ],
223
- "metadata": {
224
- "colab": {
225
- "base_uri": "https://localhost:8080/"
226
- },
227
- "id": "gmf4mQ0mLMio",
228
- "outputId": "72498418-0301-4059-c16b-b81693340c6c"
229
- },
230
- "execution_count": 9,
231
- "outputs": [
232
- {
233
- "output_type": "stream",
234
- "name": "stdout",
235
- "text": [
236
- "cuda\n",
237
- "True\n"
238
- ]
239
- }
240
- ]
241
- },
242
- {
243
- "cell_type": "code",
244
- "source": [
245
- "# Model — transfer learning with ResNet18\n",
246
- "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
247
- "\n",
248
- "model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1)\n",
249
- "\n",
250
- "for param in model.parameters():\n",
251
- " param.requires_grad = False\n",
252
- "\n",
253
- "model.fc = nn.Linear(model.fc.in_features, num_classes)\n",
254
- "model = model.to(device)\n",
255
- "\n",
256
- "criterion = nn.CrossEntropyLoss()\n",
257
- "optimizer = optim.Adam(model.fc.parameters(), lr=1e-3)"
258
- ],
259
- "metadata": {
260
- "colab": {
261
- "base_uri": "https://localhost:8080/"
262
- },
263
- "id": "eiOeXlV1pJA5",
264
- "outputId": "e50f56f5-ad82-4d6d-904f-1fbf35b526e3"
265
- },
266
- "execution_count": 10,
267
- "outputs": [
268
- {
269
- "output_type": "stream",
270
- "name": "stdout",
271
- "text": [
272
- "Downloading: \"https://download.pytorch.org/models/resnet18-f37072fd.pth\" to /root/.cache/torch/hub/checkpoints/resnet18-f37072fd.pth\n"
273
- ]
274
- },
275
- {
276
- "output_type": "stream",
277
- "name": "stderr",
278
- "text": [
279
- "100%|██████████| 44.7M/44.7M [00:00<00:00, 177MB/s]\n"
280
- ]
281
- }
282
- ]
283
- },
284
- {
285
- "cell_type": "code",
286
- "source": [
287
- "# Train\n",
288
- "def train_model(model, train_loader, val_loader, criterion, optimizer, epochs=10):\n",
289
- " for epoch in range(epochs):\n",
290
- " # --- Training phase ---\n",
291
- " model.train()\n",
292
- " running_loss, correct, total = 0.0, 0, 0\n",
293
- "\n",
294
- " for images, labels in train_loader:\n",
295
- " images, labels = images.to(device), labels.to(device)\n",
296
- "\n",
297
- " optimizer.zero_grad()\n",
298
- " outputs = model(images)\n",
299
- " loss = criterion(outputs, labels)\n",
300
- " loss.backward()\n",
301
- " optimizer.step()\n",
302
- "\n",
303
- " running_loss += loss.item() * images.size(0)\n",
304
- " _, predicted = torch.max(outputs, 1)\n",
305
- " correct += (predicted == labels).sum().item()\n",
306
- " total += labels.size(0)\n",
307
- "\n",
308
- " train_loss = running_loss / total\n",
309
- " train_acc = correct / total\n",
310
- "\n",
311
- " # --- Validation phase ---\n",
312
- " model.eval()\n",
313
- " val_loss, val_correct, val_total = 0.0, 0, 0\n",
314
- " with torch.no_grad():\n",
315
- " for images, labels in val_loader:\n",
316
- " images, labels = images.to(device), labels.to(device)\n",
317
- " outputs = model(images)\n",
318
- " loss = criterion(outputs, labels)\n",
319
- "\n",
320
- " val_loss += loss.item() * images.size(0)\n",
321
- " _, predicted = torch.max(outputs, 1)\n",
322
- " val_correct += (predicted == labels).sum().item()\n",
323
- " val_total += labels.size(0)\n",
324
- "\n",
325
- " val_loss /= val_total\n",
326
- " val_acc = val_correct / val_total\n",
327
- "\n",
328
- " print(f'Epoch {epoch+1}/{epochs} | '\n",
329
- " f'Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f} | '\n",
330
- " f'Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.4f}')\n",
331
- "\n",
332
- "train_model(model, train_loader, val_loader, criterion, optimizer, epochs=10)"
333
- ],
334
- "metadata": {
335
- "colab": {
336
- "base_uri": "https://localhost:8080/"
337
- },
338
- "id": "Q6QnhlhlpkdC",
339
- "outputId": "803f995a-e22b-4100-f912-959de7b73858"
340
- },
341
- "execution_count": 11,
342
- "outputs": [
343
- {
344
- "output_type": "stream",
345
- "name": "stdout",
346
- "text": [
347
- "Epoch 1/10 | Train Loss: 0.5573, Train Acc: 0.8568 | Val Loss: 0.2354, Val Acc: 0.9325\n",
348
- "Epoch 2/10 | Train Loss: 0.2344, Train Acc: 0.9328 | Val Loss: 0.1828, Val Acc: 0.9461\n",
349
- "Epoch 3/10 | Train Loss: 0.1938, Train Acc: 0.9445 | Val Loss: 0.1665, Val Acc: 0.9493\n",
350
- "Epoch 4/10 | Train Loss: 0.1738, Train Acc: 0.9476 | Val Loss: 0.1645, Val Acc: 0.9488\n",
351
- "Epoch 5/10 | Train Loss: 0.1591, Train Acc: 0.9517 | Val Loss: 0.1663, Val Acc: 0.9525\n",
352
- "Epoch 6/10 | Train Loss: 0.1557, Train Acc: 0.9527 | Val Loss: 0.1569, Val Acc: 0.9525\n",
353
- "Epoch 7/10 | Train Loss: 0.1455, Train Acc: 0.9546 | Val Loss: 0.1581, Val Acc: 0.9516\n",
354
- "Epoch 8/10 | Train Loss: 0.1315, Train Acc: 0.9585 | Val Loss: 0.1573, Val Acc: 0.9520\n",
355
- "Epoch 9/10 | Train Loss: 0.1307, Train Acc: 0.9581 | Val Loss: 0.1571, Val Acc: 0.9498\n",
356
- "Epoch 10/10 | Train Loss: 0.1277, Train Acc: 0.9579 | Val Loss: 0.1575, Val Acc: 0.9529\n"
357
- ]
358
- }
359
- ]
360
- },
361
- {
362
- "cell_type": "code",
363
- "source": [
364
- "# unfreeze layer4 and fine-tune\n",
365
- "# Unfreeze the last convolutional block so it can adapt to your specific classes\n",
366
- "for param in model.layer4.parameters():\n",
367
- " param.requires_grad = True\n",
368
- "\n",
369
- "# Two learning rates: small for the pretrained layer4 (don't destroy its features),\n",
370
- "# normal for the head which still needs to keep adjusting\n",
371
- "optimizer = optim.Adam([\n",
372
- " {'params': model.fc.parameters(), 'lr': 1e-3},\n",
373
- " {'params': model.layer4.parameters(), 'lr': 1e-5}\n",
374
- "])\n",
375
- "\n",
376
- "train_model(model, train_loader, val_loader, criterion, optimizer, epochs=10)"
377
- ],
378
- "metadata": {
379
- "colab": {
380
- "base_uri": "https://localhost:8080/"
381
- },
382
- "id": "erBDHDJLqeDa",
383
- "outputId": "0d0813ee-e83f-40a8-bedb-481c3e309c43"
384
- },
385
- "execution_count": 12,
386
- "outputs": [
387
- {
388
- "output_type": "stream",
389
- "name": "stdout",
390
- "text": [
391
- "Epoch 1/10 | Train Loss: 0.1284, Train Acc: 0.9574 | Val Loss: 0.1514, Val Acc: 0.9552\n",
392
- "Epoch 2/10 | Train Loss: 0.0813, Train Acc: 0.9737 | Val Loss: 0.1519, Val Acc: 0.9556\n",
393
- "Epoch 3/10 | Train Loss: 0.0533, Train Acc: 0.9838 | Val Loss: 0.1491, Val Acc: 0.9552\n",
394
- "Epoch 4/10 | Train Loss: 0.0362, Train Acc: 0.9915 | Val Loss: 0.1455, Val Acc: 0.9593\n",
395
- "Epoch 5/10 | Train Loss: 0.0286, Train Acc: 0.9918 | Val Loss: 0.1477, Val Acc: 0.9547\n",
396
- "Epoch 6/10 | Train Loss: 0.0170, Train Acc: 0.9972 | Val Loss: 0.1481, Val Acc: 0.9593\n",
397
- "Epoch 7/10 | Train Loss: 0.0151, Train Acc: 0.9971 | Val Loss: 0.1449, Val Acc: 0.9602\n",
398
- "Epoch 8/10 | Train Loss: 0.0133, Train Acc: 0.9973 | Val Loss: 0.1628, Val Acc: 0.9570\n",
399
- "Epoch 9/10 | Train Loss: 0.0160, Train Acc: 0.9955 | Val Loss: 0.1581, Val Acc: 0.9593\n",
400
- "Epoch 10/10 | Train Loss: 0.0081, Train Acc: 0.9986 | Val Loss: 0.1612, Val Acc: 0.9602\n"
401
- ]
402
- }
403
- ]
404
- },
405
- {
406
- "cell_type": "code",
407
- "source": [
408
- "# Confusion matrix — check if horse/elephant confusion actually improved\n",
409
- "from sklearn.metrics import confusion_matrix, classification_report\n",
410
- "import numpy as np\n",
411
- "\n",
412
- "model.eval()\n",
413
- "all_preds, all_labels = [], []\n",
414
- "\n",
415
- "with torch.no_grad():\n",
416
- " for images, labels in val_loader:\n",
417
- " images = images.to(device)\n",
418
- " outputs = model(images)\n",
419
- " _, predicted = torch.max(outputs, 1)\n",
420
- " all_preds.extend(predicted.cpu().numpy())\n",
421
- " all_labels.extend(labels.numpy())\n",
422
- "\n",
423
- "cm = confusion_matrix(all_labels, all_preds)\n",
424
- "print(classification_report(all_labels, all_preds, target_names=class_names))"
425
- ],
426
- "metadata": {
427
- "colab": {
428
- "base_uri": "https://localhost:8080/"
429
- },
430
- "id": "TTm0B6vdqxkC",
431
- "outputId": "7b6a74d0-8c11-4421-d0b4-fc721fc7764e"
432
- },
433
- "execution_count": 13,
434
- "outputs": [
435
- {
436
- "output_type": "stream",
437
- "name": "stdout",
438
- "text": [
439
- " precision recall f1-score support\n",
440
- "\n",
441
- " Butterfly 0.94 0.97 0.95 199\n",
442
- " Cat 0.97 0.99 0.98 272\n",
443
- " Chicken 1.00 0.96 0.98 200\n",
444
- " Cow 0.89 0.94 0.91 193\n",
445
- " Dog 0.95 0.98 0.97 345\n",
446
- " Elephant 0.97 0.95 0.96 203\n",
447
- " Horse 0.98 0.92 0.94 215\n",
448
- " Sheep 0.95 0.94 0.94 188\n",
449
- " Spider 0.97 0.95 0.96 196\n",
450
- " Squirrel 0.99 0.97 0.98 198\n",
451
- "\n",
452
- " accuracy 0.96 2209\n",
453
- " macro avg 0.96 0.96 0.96 2209\n",
454
- "weighted avg 0.96 0.96 0.96 2209\n",
455
- "\n"
456
- ]
457
- }
458
- ]
459
- },
460
- {
461
- "cell_type": "code",
462
- "source": [
463
- "# Save the model\n",
464
- "torch.save(model.state_dict(), 'animal_classifier_resnet18.pth')"
465
- ],
466
- "metadata": {
467
- "id": "HMCICAPqpr-p"
468
- },
469
- "execution_count": 14,
470
- "outputs": []
471
- },
472
- {
473
- "cell_type": "code",
474
- "source": [
475
- "#Inference on a new image\n",
476
- "from PIL import Image\n",
477
- "\n",
478
- "def predict_image(image_path, model, transform, class_names):\n",
479
- " model.eval()\n",
480
- " image = Image.open(image_path).convert('RGB')\n",
481
- " image_tensor = transform(image).unsqueeze(0).to(device) # add batch dimension\n",
482
- "\n",
483
- " with torch.no_grad():\n",
484
- " output = model(image_tensor)\n",
485
- " _, predicted = torch.max(output, 1)\n",
486
- "\n",
487
- " return class_names[predicted.item()]\n",
488
- "\n",
489
- "# Provide a valid path to an image from your dataset\n",
490
- "# For example, using one of the butterfly images that was unzipped earlier\n",
491
- "image_to_predict_path = '/content/dataset/dataset/Butterfly/OIP--gEvtip9-FSfOy_MWMXSxQHaJ6.jpeg'\n",
492
- "result = predict_image(image_to_predict_path, model, val_transform, class_names)\n",
493
- "print(f'Predicted class: {result}')"
494
- ],
495
- "metadata": {
496
- "colab": {
497
- "base_uri": "https://localhost:8080/"
498
- },
499
- "id": "kpUYVRnKpx-r",
500
- "outputId": "65eb83b2-fb5c-49a5-86ab-1130e27a01f7"
501
- },
502
- "execution_count": 15,
503
- "outputs": [
504
- {
505
- "output_type": "stream",
506
- "name": "stdout",
507
- "text": [
508
- "Predicted class: Butterfly\n"
509
- ]
510
- }
511
- ]
512
- },
513
- {
514
- "cell_type": "code",
515
- "metadata": {
516
- "colab": {
517
- "base_uri": "https://localhost:8080/"
518
- },
519
- "id": "a5959856",
520
- "outputId": "498cf7ca-7c37-4016-cc17-54b57d5b3d7b"
521
- },
522
- "source": [
523
- "import os\n",
524
- "\n",
525
- "directory_to_check = '/content/dataset/dataset/Butterfly/'\n",
526
- "if os.path.isdir(directory_to_check):\n",
527
- " print(f'The directory \"{directory_to_check}\" exists.')\n",
528
- " # Optionally, list some files in the directory to confirm it's not empty\n",
529
- " print('Files in the directory (first 5):')\n",
530
- " print(os.listdir(directory_to_check)[:5])\n",
531
- "else:\n",
532
- " print(f'The directory \"{directory_to_check}\" does NOT exist.')"
533
- ],
534
- "execution_count": 23,
535
- "outputs": [
536
- {
537
- "output_type": "stream",
538
- "name": "stdout",
539
- "text": [
540
- "The directory \"/content/dataset/dataset/Butterfly/\" exists.\n",
541
- "Files in the directory (first 5):\n",
542
- "['OIP-qugefs9Tnt250B8cygtBJQAAAA.jpeg', 'OIP-WTFhQqD88fBJqFbfVlHSEwHaFa.jpeg', 'OIP-Y5_dpYEmm7Gar2Dyn_EZcAHaHa.jpeg', 'OIP-rME83hQvAoryna1yrFbUDQHaJ5.jpeg', 'OIP-zdZAIubVm0NYpKeBu4zW5QHaFa.jpeg']\n"
543
- ]
544
- }
545
- ]
546
- },
547
- {
548
- "cell_type": "code",
549
- "source": [],
550
- "metadata": {
551
- "id": "m3FUixF6p3oQ"
552
- },
553
- "execution_count": null,
554
- "outputs": []
555
- }
556
- ]
557
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
Binary files a/README.md and b/README.md differ
 
app.py CHANGED
@@ -1,75 +1,710 @@
1
- import streamlit as st
2
- import torch
3
- import torch.nn as nn
4
- from torchvision import models, transforms
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  from PIL import Image
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
- # --- Config ---
9
- CLASS_NAMES = ['Butterfly', 'Cat', 'Chicken','Cow', 'Dog', 'Elephant', 'Horse', 'Sheep', 'Spider','Squirrel'] # replace with your actual class_names, in the same order
10
- MODEL_PATH = 'animal_classifier_resnet18.pth'
11
- CONFIDENCE_THRESHOLD = 0.70
12
- # --- Load model (cached so it only loads once, not on every interaction) ---
13
- @st.cache_resource
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  def load_model():
15
- model = models.resnet18(weights=None) # architecture only, no pretrained weights needed here
16
- model.fc = nn.Linear(model.fc.in_features, len(CLASS_NAMES))
17
- model.load_state_dict(torch.load(MODEL_PATH, map_location=torch.device('cpu')))
 
 
 
 
 
 
 
 
 
 
 
 
18
  model.eval()
 
19
  return model
20
 
 
21
  model = load_model()
22
 
23
- # --- Same transform used for validation/inference during training ---
 
 
 
24
  transform = transforms.Compose([
25
- transforms.Resize((224, 224)),
 
 
26
  transforms.ToTensor(),
27
- transforms.Normalize(mean=[0.485, 0.456, 0.406],
28
- std=[0.229, 0.224, 0.225])
 
 
 
 
 
 
 
29
  ])
30
 
31
- # --- UI ---
32
- st.title("Animal Image Classifier ")
 
33
 
34
- st.write("This app uses a pre-trained ResNet18 model to classify images of animals. ")
35
- st.write("The model was trained on a dataset of 10 different animals, and it can predict which animal is in the uploaded image.")
36
 
37
- st.write("Upload an image and the model will predict which animal it is.")
38
 
39
- st.caption(
40
- f"⚠️ This model can only recognize: **{', '.join(CLASS_NAMES)}**. "
41
- f"Uploading other animals or objects may produce incorrect or misleading results."
42
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
- uploaded_file = st.file_uploader("Choose an image...", type=["jpg", "jpeg", "png"])
45
 
46
- if uploaded_file is not None:
47
- image = Image.open(uploaded_file).convert('RGB')
48
- st.image(image, caption='Uploaded Image', width='stretch')
49
 
50
- image_tensor = transform(image).unsqueeze(0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
  with torch.no_grad():
53
- output = model(image_tensor)
54
- probabilities = torch.softmax(output, dim=1)[0]
55
- predicted_idx = torch.argmax(probabilities).item()
56
- confidence = probabilities[predicted_idx].item()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
  if confidence < CONFIDENCE_THRESHOLD:
59
- st.subheader("Prediction: Not confident enough to classify")
60
- st.write(
61
- f"The model's best guess was **{CLASS_NAMES[predicted_idx]}** "
62
- f"at only {confidence * 100:.2f}% confidence, which is below the "
63
- f"{CONFIDENCE_THRESHOLD * 100:.0f}% threshold. This usually means the "
64
- f"uploaded image isn't one of the animals this model was trained on."
65
- )
 
 
 
 
 
 
 
 
 
 
 
 
66
  else:
67
- st.subheader(f"Prediction: **{CLASS_NAMES[predicted_idx]}**")
68
- st.write(f"Confidence: {confidence * 100:.2f}%")
69
 
70
- # Show all class probabilities regardless, for transparency
71
- st.write("All class probabilities:")
72
- for i, class_name in enumerate(CLASS_NAMES):
73
- st.write(f"- {class_name}: {probabilities[i].item() * 100:.2f}%")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
- st.write("Developed by: Odeyale Kehinde Musiliudeen(Alphatech)")
 
 
 
 
1
+ ##############################################################
2
+ # Animal Image Classification using ResNet18
3
+ # Developed by: Odeyale Kehinde Musiliudeen
4
+ ##############################################################
5
+
6
+ import os
7
+ import time
8
+ from datetime import datetime
9
+
10
+ import torch
11
+ import torch.nn as nn
12
+
13
+ import pandas as pd
14
+
15
+ import plotly.express as px
16
+
17
+ import gradio as gr
18
+
19
  from PIL import Image
20
 
21
+ from torchvision import models, transforms
22
+
23
+ from reportlab.lib import colors
24
+ from reportlab.lib.styles import getSampleStyleSheet
25
+
26
+ from reportlab.platypus import (
27
+ SimpleDocTemplate,
28
+ Paragraph,
29
+ Table,
30
+ TableStyle,
31
+ Spacer
32
+ )
33
+
34
+ ##############################################################
35
+ # Configuration
36
+ ##############################################################
37
 
38
+ CLASS_NAMES = [
39
+ "Butterfly",
40
+ "Cat",
41
+ "Chicken",
42
+ "Cow",
43
+ "Dog",
44
+ "Elephant",
45
+ "Horse",
46
+ "Sheep",
47
+ "Spider",
48
+ "Squirrel"
49
+ ]
50
+
51
+ MODEL_PATH = "animal_classifier_resnet18.pth"
52
+
53
+ CONFIDENCE_THRESHOLD = 0.70
54
+
55
+ MODEL_NAME = "ResNet18"
56
+
57
+ MODEL_SIZE = "42 MB"
58
+
59
+ ##############################################################
60
+ # Create folders automatically
61
+ ##############################################################
62
+
63
+ os.makedirs("reports", exist_ok=True)
64
+
65
+ ##############################################################
66
+ # Load Model
67
+ ##############################################################
68
+
69
+ @torch.no_grad()
70
  def load_model():
71
+
72
+ model = models.resnet18(weights=None)
73
+
74
+ model.fc = nn.Linear(
75
+ model.fc.in_features,
76
+ len(CLASS_NAMES)
77
+ )
78
+
79
+ state = torch.load(
80
+ MODEL_PATH,
81
+ map_location="cpu"
82
+ )
83
+
84
+ model.load_state_dict(state)
85
+
86
  model.eval()
87
+
88
  return model
89
 
90
+
91
  model = load_model()
92
 
93
+ ##############################################################
94
+ # Image Preprocessing
95
+ ##############################################################
96
+
97
  transform = transforms.Compose([
98
+
99
+ transforms.Resize((224,224)),
100
+
101
  transforms.ToTensor(),
102
+
103
+ transforms.Normalize(
104
+
105
+ mean=[0.485,0.456,0.406],
106
+
107
+ std=[0.229,0.224,0.225]
108
+
109
+ )
110
+
111
  ])
112
 
113
+ ##############################################################
114
+ # CSV Report
115
+ ##############################################################
116
 
117
+ def create_csv(top5_df):
 
118
 
119
+ filename = os.path.join(
120
 
121
+ "reports",
122
+
123
+ f"prediction_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
124
+
125
+ )
126
+
127
+ top5_df.to_csv(filename,index=False)
128
+
129
+ return filename
130
+
131
+ ##############################################################
132
+ # PDF Report
133
+ ##############################################################
134
+
135
+ def create_pdf(
136
+
137
+ prediction,
138
+
139
+ confidence,
140
+
141
+ inference_time,
142
+
143
+ top5_df
144
+
145
+ ):
146
+
147
+ filename = os.path.join(
148
+
149
+ "reports",
150
+
151
+ f"prediction_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
152
+
153
+ )
154
+
155
+ doc = SimpleDocTemplate(filename)
156
+
157
+ styles = getSampleStyleSheet()
158
+
159
+ elements = []
160
+
161
+ elements.append(
162
+
163
+ Paragraph(
164
+
165
+ "Animal Image Classification Report",
166
+
167
+ styles["Title"]
168
+
169
+ )
170
+
171
+ )
172
+
173
+ elements.append(Spacer(1,15))
174
+
175
+ elements.append(
176
+
177
+ Paragraph(
178
+
179
+ f"<b>Date:</b> {datetime.now()}",
180
+
181
+ styles["Normal"]
182
+
183
+ )
184
+
185
+ )
186
+
187
+ elements.append(
188
+
189
+ Paragraph(
190
+
191
+ f"<b>Prediction:</b> {prediction}",
192
+
193
+ styles["Normal"]
194
+
195
+ )
196
 
197
+ )
198
 
199
+ elements.append(
 
 
200
 
201
+ Paragraph(
202
+
203
+ f"<b>Confidence:</b> {confidence}",
204
+
205
+ styles["Normal"]
206
+
207
+ )
208
+
209
+ )
210
+
211
+ elements.append(
212
+
213
+ Paragraph(
214
+
215
+ f"<b>Inference Time:</b> {inference_time}",
216
+
217
+ styles["Normal"]
218
+
219
+ )
220
+
221
+ )
222
+
223
+ elements.append(Spacer(1,15))
224
+
225
+ table_data = [list(top5_df.columns)]
226
+
227
+ table_data.extend(
228
+
229
+ top5_df.values.tolist()
230
+
231
+ )
232
+
233
+ table = Table(table_data)
234
+
235
+ table.setStyle(
236
+
237
+ TableStyle([
238
+
239
+ ("BACKGROUND",(0,0),(-1,0),colors.darkgreen),
240
+
241
+ ("TEXTCOLOR",(0,0),(-1,0),colors.white),
242
+
243
+ ("GRID",(0,0),(-1,-1),1,colors.black),
244
+
245
+ ("BACKGROUND",(0,1),(-1,-1),colors.beige),
246
+
247
+ ("ALIGN",(0,0),(-1,-1),"CENTER")
248
+
249
+ ])
250
+
251
+ )
252
+
253
+ elements.append(table)
254
+
255
+ doc.build(elements)
256
+
257
+ return filename
258
+
259
+ ##############################################################
260
+ # Prediction Function
261
+ ##############################################################
262
+
263
+ def predict(image):
264
+
265
+ if image is None:
266
+
267
+ return (
268
+
269
+ None,
270
+
271
+ "Please upload an image.",
272
+
273
+ "",
274
+
275
+ "",
276
+
277
+ "",
278
+
279
+ pd.DataFrame(),
280
+
281
+ None,
282
+
283
+ None,
284
+
285
+ None
286
+
287
+ )
288
+
289
+ start = time.time()
290
+
291
+ image = image.convert("RGB")
292
+
293
+ tensor = transform(image).unsqueeze(0)
294
 
295
  with torch.no_grad():
296
+
297
+ output = model(tensor)
298
+
299
+ probabilities = torch.softmax(
300
+
301
+ output,
302
+
303
+ dim=1
304
+
305
+ )[0]
306
+
307
+ inference_time = round(
308
+
309
+ time.time()-start,
310
+
311
+ 3
312
+
313
+ )
314
+
315
+ confidence,pred = torch.max(
316
+
317
+ probabilities,
318
+
319
+ 0
320
+
321
+ )
322
+
323
+ confidence = confidence.item()
324
+
325
+ predicted_class = CLASS_NAMES[pred.item()]
326
+
327
+ if confidence >= 0.90:
328
+
329
+ status = "🟢 High Confidence"
330
+
331
+ elif confidence >= 0.75:
332
+
333
+ status = "🟡 Medium Confidence"
334
+
335
+ else:
336
+
337
+ status = "🔴 Low Confidence"
338
 
339
  if confidence < CONFIDENCE_THRESHOLD:
340
+
341
+ prediction_html = f"""
342
+
343
+ <h2 style='color:red;'>
344
+
345
+ Not confident enough
346
+
347
+ </h2>
348
+
349
+ <p>
350
+
351
+ Best Guess:
352
+
353
+ <b>{predicted_class}</b>
354
+
355
+ </p>
356
+
357
+ """
358
+
359
  else:
 
 
360
 
361
+ prediction_html = f"""
362
+
363
+ <h1 style='color:green;'>
364
+
365
+ {predicted_class}
366
+
367
+ </h1>
368
+
369
+ """
370
+
371
+ ##########################################################
372
+ # Top-5 Predictions
373
+ ##########################################################
374
+
375
+ top5 = torch.topk(
376
+
377
+ probabilities,
378
+
379
+ 5
380
+
381
+ )
382
+
383
+ top5_df = pd.DataFrame({
384
+
385
+ "Rank":
386
+
387
+ range(1,6),
388
+
389
+ "Animal":[
390
+
391
+ CLASS_NAMES[i]
392
+
393
+ for i in top5.indices.tolist()
394
+
395
+ ],
396
+
397
+ "Probability (%)":[
398
+
399
+ round(
400
+
401
+ float(v)*100,
402
+
403
+ 2
404
+
405
+ )
406
+
407
+ for v in top5.values.tolist()
408
+
409
+ ]
410
+
411
+ })
412
+
413
+ ##########################################################
414
+ # Plotly Confidence Chart
415
+ ##########################################################
416
+
417
+ fig = px.bar(
418
+
419
+ top5_df,
420
+
421
+ x="Probability (%)",
422
+
423
+ y="Animal",
424
+
425
+ orientation="h",
426
+
427
+ text="Probability (%)",
428
+
429
+ color="Probability (%)",
430
+
431
+ color_continuous_scale="Viridis"
432
+
433
+ )
434
+
435
+ fig.update_layout(
436
+
437
+ height=420,
438
+
439
+ template="plotly_white",
440
+
441
+ coloraxis_showscale=False,
442
+
443
+ title="Top-5 Prediction Confidence"
444
+
445
+ )
446
+
447
+ fig.update_traces(
448
+
449
+ texttemplate="%{text:.2f}%",
450
+
451
+ textposition="outside"
452
+
453
+ )
454
+
455
+ ##########################################################
456
+ # Model Information
457
+ ##########################################################
458
+
459
+ model_information = f"""
460
+
461
+ Architecture : {MODEL_NAME}
462
+
463
+ Framework : PyTorch
464
+
465
+ Classes : {len(CLASS_NAMES)}
466
+
467
+ Input Size : 224 × 224
468
+
469
+ Model Size : {MODEL_SIZE}
470
+
471
+ Confidence Threshold : {CONFIDENCE_THRESHOLD}
472
+
473
+ Prediction Status : {status}
474
+
475
+ """
476
+
477
+ ##########################################################
478
+ # Reports
479
+ ##########################################################
480
+
481
+ csv_file = create_csv(top5_df)
482
+
483
+ pdf_file = create_pdf(
484
+
485
+ predicted_class,
486
+
487
+ f"{confidence*100:.2f}%",
488
+
489
+ f"{inference_time:.3f} sec",
490
+
491
+ top5_df
492
+
493
+ )
494
+
495
+ ##########################################################
496
+ # Return Outputs
497
+ ##########################################################
498
+
499
+ return (
500
+
501
+ image,
502
+
503
+ prediction_html,
504
+
505
+ f"{confidence*100:.2f}%",
506
+
507
+ f"{inference_time:.3f} sec",
508
+
509
+ model_information,
510
+
511
+ top5_df,
512
+
513
+ fig,
514
+
515
+ csv_file,
516
+
517
+ pdf_file
518
+
519
+ )
520
+
521
+
522
+ ##############################################################
523
+ # User Interface
524
+ ##############################################################
525
+
526
+ with gr.Blocks(
527
+ title="Animal Image Classification"
528
+ ) as demo:
529
+
530
+ gr.Markdown(
531
+ """
532
+ # 🐾 Animal Image Classification using Deep Learning
533
+
534
+ This application uses a **ResNet18** convolutional neural network trained to classify **10 animal species**.
535
+
536
+ ### Supported Classes
537
+
538
+ Butterfly • Cat • Chicken • Cow • Dog • Elephant • Horse • Sheep • Spider • Squirrel
539
+ """
540
+ )
541
+
542
+ ##########################################################
543
+ # Main Layout
544
+ ##########################################################
545
+
546
+ with gr.Row():
547
+
548
+ ######################################################
549
+ # Left Panel
550
+ ######################################################
551
+
552
+ with gr.Column(scale=1):
553
+
554
+ input_image = gr.Image(
555
+ type="pil",
556
+ label="Upload Animal Image"
557
+ )
558
+
559
+ with gr.Row():
560
+
561
+ predict_btn = gr.Button(
562
+ "🔍 Predict",
563
+ variant="primary"
564
+ )
565
+
566
+ clear_btn = gr.ClearButton(
567
+ components=[input_image]
568
+ )
569
+
570
+ ######################################################
571
+ # Right Panel
572
+ ######################################################
573
+
574
+ with gr.Column(scale=1):
575
+
576
+ output_image = gr.Image(
577
+ label="Uploaded Image"
578
+ )
579
+
580
+ prediction = gr.HTML(
581
+ label="Prediction"
582
+ )
583
+
584
+ confidence = gr.Textbox(
585
+ label="Confidence"
586
+ )
587
+
588
+ inference_time = gr.Textbox(
589
+ label="Inference Time"
590
+ )
591
+
592
+ ##########################################################
593
+ # Top-5 Prediction + Confidence Chart
594
+ ##########################################################
595
+
596
+ gr.Markdown("## 📊 Prediction Analysis")
597
+
598
+ with gr.Row():
599
+
600
+ with gr.Column():
601
+
602
+ top5_table = gr.Dataframe(
603
+ headers=[
604
+ "Rank",
605
+ "Animal",
606
+ "Probability (%)"
607
+ ],
608
+ interactive=False,
609
+ label="Top-5 Predictions"
610
+ )
611
+
612
+ with gr.Column():
613
+
614
+ confidence_chart = gr.Plot(
615
+ label="Confidence Distribution"
616
+ )
617
+
618
+ ##########################################################
619
+ # Model Information
620
+ ##########################################################
621
+
622
+ with gr.Accordion(
623
+ "ℹ️ Model Information",
624
+ open=False
625
+ ):
626
+
627
+ info = gr.Textbox(
628
+ lines=8,
629
+ interactive=False,
630
+ label="Model Details"
631
+ )
632
+
633
+ ##########################################################
634
+ # Download Reports
635
+ ##########################################################
636
+
637
+ gr.Markdown("## 📥 Download Reports")
638
+
639
+ with gr.Row():
640
+
641
+ csv_download = gr.File(
642
+ label="CSV Report"
643
+ )
644
+
645
+ pdf_download = gr.File(
646
+ label="PDF Report"
647
+ )
648
+
649
+ ##########################################################
650
+ # Footer
651
+ ##########################################################
652
+
653
+ gr.Markdown(
654
+ """
655
+ ---
656
+
657
+ ### Developed by
658
+
659
+ **Odeyale Kehinde Musiliudeen**
660
+
661
+ Alphatech AI Research Laboratory
662
+
663
+ Powered by **PyTorch + Gradio**
664
+ """
665
+ )
666
+
667
+ ##########################################################
668
+ # Prediction Event
669
+ ##########################################################
670
+
671
+ predict_btn.click(
672
+
673
+ fn=predict,
674
+
675
+ inputs=input_image,
676
+
677
+ outputs=[
678
+
679
+ output_image,
680
+
681
+ prediction,
682
+
683
+ confidence,
684
+
685
+ inference_time,
686
+
687
+ info,
688
+
689
+ top5_table,
690
+
691
+ confidence_chart,
692
+
693
+ csv_download,
694
+
695
+ pdf_download
696
+
697
+ ]
698
+
699
+ )
700
+
701
+ ##############################################################
702
+ # Launch Application
703
+ ##############################################################
704
+
705
+ if __name__ == "__main__":
706
 
707
+ demo.launch(
708
+ debug=True,
709
+ show_error=True
710
+ )
requirements.txt CHANGED
@@ -1,4 +1,13 @@
1
- streamlit
2
- torch
3
- torchvision
4
- Pillow
 
 
 
 
 
 
 
 
 
 
1
+ gradio==5.38.2
2
+
3
+ torch>=2.3
4
+
5
+ torchvision>=0.18
6
+
7
+ pillow
8
+
9
+ pandas
10
+
11
+ plotly
12
+
13
+ reportlab