Spaces:
Sleeping
Sleeping
Upload 13 files
Browse files- .gitattributes +6 -0
- app.py +169 -0
- data/cifar-10-batches-py/batches.meta +0 -0
- data/cifar-10-batches-py/data_batch_1 +3 -0
- data/cifar-10-batches-py/data_batch_2 +3 -0
- data/cifar-10-batches-py/data_batch_3 +3 -0
- data/cifar-10-batches-py/data_batch_4 +3 -0
- data/cifar-10-batches-py/data_batch_5 +3 -0
- data/cifar-10-batches-py/readme.html +1 -0
- data/cifar-10-batches-py/test_batch +3 -0
- data/cifar-10-python.tar.gz +3 -0
- model.pth +3 -0
- requirements.txt +6 -0
- train.py +79 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,9 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
data/cifar-10-batches-py/data_batch_1 filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
data/cifar-10-batches-py/data_batch_2 filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
data/cifar-10-batches-py/data_batch_3 filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
data/cifar-10-batches-py/data_batch_4 filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
data/cifar-10-batches-py/data_batch_5 filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
data/cifar-10-batches-py/test_batch filter=lfs diff=lfs merge=lfs -text
|
app.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
import torchvision.transforms as transforms
|
| 5 |
+
from PIL import Image
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
import matplotlib.pyplot as plt
|
| 8 |
+
import numpy as np
|
| 9 |
+
import os
|
| 10 |
+
|
| 11 |
+
# ----------------- CLASS LABELS -----------------
|
| 12 |
+
CLASSES = [
|
| 13 |
+
"airplane", "automobile", "bird", "cat", "deer",
|
| 14 |
+
"dog", "frog", "horse", "ship", "truck"
|
| 15 |
+
]
|
| 16 |
+
|
| 17 |
+
# ----------------- CNN MODEL (same as training) -----------------
|
| 18 |
+
class CNN(nn.Module):
|
| 19 |
+
def __init__(self):
|
| 20 |
+
super().__init__()
|
| 21 |
+
self.conv_layer = nn.Sequential(
|
| 22 |
+
nn.Conv2d(3, 32, kernel_size=3, padding=1),
|
| 23 |
+
nn.ReLU(),
|
| 24 |
+
nn.MaxPool2d(2, 2),
|
| 25 |
+
|
| 26 |
+
nn.Conv2d(32, 64, kernel_size=3, padding=1),
|
| 27 |
+
nn.ReLU(),
|
| 28 |
+
nn.MaxPool2d(2, 2)
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
self.fc_layer = nn.Sequential(
|
| 32 |
+
nn.Linear(64 * 8 * 8, 256),
|
| 33 |
+
nn.ReLU(),
|
| 34 |
+
nn.Linear(256, 10)
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
def forward(self, x):
|
| 38 |
+
x = self.conv_layer(x)
|
| 39 |
+
x = x.view(x.size(0), -1)
|
| 40 |
+
x = self.fc_layer(x)
|
| 41 |
+
return x
|
| 42 |
+
|
| 43 |
+
# ----------------- LOAD TRAINED MODEL -----------------
|
| 44 |
+
model = CNN()
|
| 45 |
+
import os
|
| 46 |
+
MODEL_PATH = os.path.join(os.getcwd(), "model.pth")
|
| 47 |
+
model.load_state_dict(torch.load(MODEL_PATH, map_location=torch.device('cpu')))
|
| 48 |
+
|
| 49 |
+
model.eval()
|
| 50 |
+
|
| 51 |
+
# ----------------- IMAGE TRANSFORMS -----------------
|
| 52 |
+
transform = transforms.Compose([
|
| 53 |
+
transforms.Resize((32, 32)),
|
| 54 |
+
transforms.ToTensor(),
|
| 55 |
+
transforms.Normalize((0.5,), (0.5,))
|
| 56 |
+
])
|
| 57 |
+
# ----------------- GRAD CAM UTILS -----------------
|
| 58 |
+
|
| 59 |
+
# ----------------- CORRECT GRAD-CAM IMPLEMENTATION -----------------
|
| 60 |
+
|
| 61 |
+
# Store activations + gradients
|
| 62 |
+
activations = None
|
| 63 |
+
gradients = None
|
| 64 |
+
|
| 65 |
+
# Save forward activations
|
| 66 |
+
def save_activation(module, input, output):
|
| 67 |
+
global activations
|
| 68 |
+
activations = output
|
| 69 |
+
|
| 70 |
+
# Save backward gradients
|
| 71 |
+
def save_gradient(module, grad_input, grad_output):
|
| 72 |
+
global gradients
|
| 73 |
+
gradients = grad_output[0]
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def generate_gradcam(model, image_tensor):
|
| 77 |
+
global activations, gradients
|
| 78 |
+
|
| 79 |
+
# Last conv layer
|
| 80 |
+
last_conv_layer = model.conv_layer[3] # Conv2d(32 → 64)
|
| 81 |
+
|
| 82 |
+
# Hook for forward activations
|
| 83 |
+
forward_handle = last_conv_layer.register_forward_hook(save_activation)
|
| 84 |
+
|
| 85 |
+
# Hook for backward gradients
|
| 86 |
+
backward_handle = last_conv_layer.register_backward_hook(save_gradient)
|
| 87 |
+
|
| 88 |
+
# Forward pass
|
| 89 |
+
output = model(image_tensor)
|
| 90 |
+
pred_class = output.argmax(dim=1)
|
| 91 |
+
|
| 92 |
+
# Backward pass
|
| 93 |
+
model.zero_grad()
|
| 94 |
+
output[0, pred_class].backward()
|
| 95 |
+
|
| 96 |
+
# Remove hooks
|
| 97 |
+
forward_handle.remove()
|
| 98 |
+
backward_handle.remove()
|
| 99 |
+
|
| 100 |
+
# Process gradients + activations
|
| 101 |
+
pooled_grads = torch.mean(gradients, dim=[0, 2, 3])
|
| 102 |
+
activation_maps = activations[0]
|
| 103 |
+
|
| 104 |
+
# Weight channels
|
| 105 |
+
for i in range(len(pooled_grads)):
|
| 106 |
+
activation_maps[i, :, :] *= pooled_grads[i]
|
| 107 |
+
|
| 108 |
+
heatmap = torch.mean(activation_maps, dim=0).detach().cpu().numpy()
|
| 109 |
+
|
| 110 |
+
# Normalize
|
| 111 |
+
heatmap = np.maximum(heatmap, 0)
|
| 112 |
+
heatmap = heatmap / np.max(heatmap)
|
| 113 |
+
|
| 114 |
+
return heatmap
|
| 115 |
+
|
| 116 |
+
def overlay_heatmap(img, heatmap):
|
| 117 |
+
heatmap = np.uint8(255 * heatmap)
|
| 118 |
+
heatmap = Image.fromarray(heatmap).resize(img.size, Image.BILINEAR)
|
| 119 |
+
heatmap = np.array(heatmap)
|
| 120 |
+
|
| 121 |
+
# Colorize heatmap
|
| 122 |
+
heatmap_color = plt.cm.jet(heatmap)[:, :, :3] * 255
|
| 123 |
+
heatmap_color = heatmap_color.astype(np.uint8)
|
| 124 |
+
|
| 125 |
+
# Overlay with original
|
| 126 |
+
img_np = np.array(img)
|
| 127 |
+
superimposed = (0.6 * heatmap_color + 0.4 * img_np).astype(np.uint8)
|
| 128 |
+
|
| 129 |
+
return Image.fromarray(superimposed)
|
| 130 |
+
|
| 131 |
+
# ----------------- STREAMLIT UI -----------------
|
| 132 |
+
st.title("🖼️ CIFAR-10 Image Classifier (CNN)")
|
| 133 |
+
|
| 134 |
+
uploaded_image = st.file_uploader("Upload an image", type=["jpg", "jpeg", "png"])
|
| 135 |
+
|
| 136 |
+
if uploaded_image:
|
| 137 |
+
image = Image.open(uploaded_image).convert("RGB")
|
| 138 |
+
st.image(image, caption="Uploaded Image", width=250)
|
| 139 |
+
|
| 140 |
+
img_tensor = transform(image).unsqueeze(0)
|
| 141 |
+
|
| 142 |
+
with torch.no_grad():
|
| 143 |
+
outputs = model(img_tensor)
|
| 144 |
+
|
| 145 |
+
# Apply softmax to get probabilities
|
| 146 |
+
probs = F.softmax(outputs, dim=1)[0]
|
| 147 |
+
|
| 148 |
+
# Get top-3 predictions
|
| 149 |
+
top3_prob, top3_idx = torch.topk(probs, 3)
|
| 150 |
+
|
| 151 |
+
st.write("### 🔍 Top Predictions:")
|
| 152 |
+
for i in range(3):
|
| 153 |
+
st.write(f"**{CLASSES[top3_idx[i]]}: {top3_prob[i].item()*100:.2f}%**")
|
| 154 |
+
|
| 155 |
+
# ----------------- BAR CHART -----------------
|
| 156 |
+
st.write("### 📊 Probability Distribution")
|
| 157 |
+
|
| 158 |
+
fig, ax = plt.subplots()
|
| 159 |
+
ax.bar(CLASSES, probs.tolist())
|
| 160 |
+
plt.xticks(rotation=45)
|
| 161 |
+
st.pyplot(fig)
|
| 162 |
+
# ----------------- GENERATE GRAD-CAM -----------------
|
| 163 |
+
heatmap = generate_gradcam(model, img_tensor)
|
| 164 |
+
cam_image = overlay_heatmap(image, heatmap)
|
| 165 |
+
|
| 166 |
+
st.write("### 🔥 Grad-CAM Heatmap")
|
| 167 |
+
st.image(cam_image, caption="Where the model is looking", use_column_width=True)
|
| 168 |
+
|
| 169 |
+
|
data/cifar-10-batches-py/batches.meta
ADDED
|
Binary file (158 Bytes). View file
|
|
|
data/cifar-10-batches-py/data_batch_1
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:54636561a3ce25bd3e19253c6b0d8538147b0ae398331ac4a2d86c6d987368cd
|
| 3 |
+
size 31035704
|
data/cifar-10-batches-py/data_batch_2
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:766b2cef9fbc745cf056b3152224f7cf77163b330ea9a15f9392beb8b89bc5a8
|
| 3 |
+
size 31035320
|
data/cifar-10-batches-py/data_batch_3
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0f00d98ebfb30b3ec0ad19f9756dc2630b89003e10525f5e148445e82aa6a1f9
|
| 3 |
+
size 31035999
|
data/cifar-10-batches-py/data_batch_4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3f7bb240661948b8f4d53e36ec720d8306f5668bd0071dcb4e6c947f78e9682b
|
| 3 |
+
size 31035696
|
data/cifar-10-batches-py/data_batch_5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d91802434d8376bbaeeadf58a737e3a1b12ac839077e931237e0dcd43adcb154
|
| 3 |
+
size 31035623
|
data/cifar-10-batches-py/readme.html
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
<meta HTTP-EQUIV="REFRESH" content="0; url=http://www.cs.toronto.edu/~kriz/cifar.html">
|
data/cifar-10-batches-py/test_batch
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f53d8d457504f7cff4ea9e021afcf0e0ad8e24a91f3fc42091b8adef61157831
|
| 3 |
+
size 31035526
|
data/cifar-10-python.tar.gz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6d958be074577803d12ecdefd02955f39262c83c16fe9348329d7fe0b5c001ce
|
| 3 |
+
size 170498071
|
model.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:20372faa89ed838d94fddbb2c548b87504e87c8f663542656585bb11b1367736
|
| 3 |
+
size 4286056
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit
|
| 2 |
+
torch
|
| 3 |
+
torchvision
|
| 4 |
+
pillow
|
| 5 |
+
matplotlib
|
| 6 |
+
numpy
|
train.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import torch.optim as optim
|
| 4 |
+
import torchvision
|
| 5 |
+
import torchvision.transforms as transforms
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
# ----------------- TRANSFORMS -----------------
|
| 9 |
+
transform = transforms.Compose([
|
| 10 |
+
transforms.ToTensor(),
|
| 11 |
+
transforms.Normalize((0.5,), (0.5,))
|
| 12 |
+
])
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
# ----------------- LOAD DATASET -----------------
|
| 16 |
+
train_set = torchvision.datasets.CIFAR10(
|
| 17 |
+
root="./data", train=True, download=True, transform=transform
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
test_set = torchvision.datasets.CIFAR10(
|
| 21 |
+
root="./data", train=False, download=True, transform=transform
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
train_loader = torch.utils.data.DataLoader(train_set, batch_size=64, shuffle=True)
|
| 25 |
+
test_loader = torch.utils.data.DataLoader(test_set, batch_size=64, shuffle=False)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
# ----------------- BUILD CNN MODEL -----------------
|
| 29 |
+
class CNN(nn.Module):
|
| 30 |
+
def __init__(self):
|
| 31 |
+
super().__init__()
|
| 32 |
+
self.conv_layer = nn.Sequential(
|
| 33 |
+
nn.Conv2d(3, 32, kernel_size=3, padding=1),
|
| 34 |
+
nn.ReLU(),
|
| 35 |
+
nn.MaxPool2d(2, 2),
|
| 36 |
+
|
| 37 |
+
nn.Conv2d(32, 64, kernel_size=3, padding=1),
|
| 38 |
+
nn.ReLU(),
|
| 39 |
+
nn.MaxPool2d(2, 2)
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
self.fc_layer = nn.Sequential(
|
| 43 |
+
nn.Linear(64 * 8 * 8, 256),
|
| 44 |
+
nn.ReLU(),
|
| 45 |
+
nn.Linear(256, 10)
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
def forward(self, x):
|
| 49 |
+
x = self.conv_layer(x)
|
| 50 |
+
x = x.view(x.size(0), -1)
|
| 51 |
+
x = self.fc_layer(x)
|
| 52 |
+
return x
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
model = CNN()
|
| 56 |
+
|
| 57 |
+
criterion = nn.CrossEntropyLoss()
|
| 58 |
+
optimizer = optim.Adam(model.parameters(), lr=0.001)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
# ----------------- TRAIN LOOP -----------------
|
| 62 |
+
for epoch in range(5):
|
| 63 |
+
running_loss = 0.0
|
| 64 |
+
for images, labels in train_loader:
|
| 65 |
+
optimizer.zero_grad()
|
| 66 |
+
|
| 67 |
+
outputs = model(images)
|
| 68 |
+
loss = criterion(outputs, labels)
|
| 69 |
+
loss.backward()
|
| 70 |
+
optimizer.step()
|
| 71 |
+
|
| 72 |
+
running_loss += loss.item()
|
| 73 |
+
|
| 74 |
+
print(f"Epoch {epoch+1}, Loss: {running_loss/len(train_loader)}")
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
# ----------------- SAVE MODEL -----------------
|
| 78 |
+
torch.save(model.state_dict(), "model.pth")
|
| 79 |
+
print("Model saved as model.pth")
|