File size: 4,898 Bytes
5960711
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import streamlit as st
import torch
import torch.nn as nn
import torchvision.transforms as transforms
from PIL import Image
import torch.nn.functional as F
import matplotlib.pyplot as plt
import numpy as np
import os

# ----------------- CLASS LABELS -----------------
CLASSES = [
    "airplane", "automobile", "bird", "cat", "deer",
    "dog", "frog", "horse", "ship", "truck"
]

# ----------------- CNN MODEL (same as training) -----------------
class CNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv_layer = nn.Sequential(
            nn.Conv2d(3, 32, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2, 2),

            nn.Conv2d(32, 64, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2, 2)
        )

        self.fc_layer = nn.Sequential(
            nn.Linear(64 * 8 * 8, 256),
            nn.ReLU(),
            nn.Linear(256, 10)
        )

    def forward(self, x):
        x = self.conv_layer(x)
        x = x.view(x.size(0), -1)
        x = self.fc_layer(x)
        return x

# ----------------- LOAD TRAINED MODEL -----------------
model = CNN()
import os
MODEL_PATH = os.path.join(os.getcwd(), "model.pth")
model.load_state_dict(torch.load(MODEL_PATH, map_location=torch.device('cpu')))

model.eval()

# ----------------- IMAGE TRANSFORMS -----------------
transform = transforms.Compose([
    transforms.Resize((32, 32)),
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])
# ----------------- GRAD CAM UTILS -----------------

# ----------------- CORRECT GRAD-CAM IMPLEMENTATION -----------------

# Store activations + gradients
activations = None
gradients = None

# Save forward activations
def save_activation(module, input, output):
    global activations
    activations = output

# Save backward gradients
def save_gradient(module, grad_input, grad_output):
    global gradients
    gradients = grad_output[0]


def generate_gradcam(model, image_tensor):
    global activations, gradients

    # Last conv layer
    last_conv_layer = model.conv_layer[3]  # Conv2d(32 → 64)

    # Hook for forward activations
    forward_handle = last_conv_layer.register_forward_hook(save_activation)

    # Hook for backward gradients
    backward_handle = last_conv_layer.register_backward_hook(save_gradient)

    # Forward pass
    output = model(image_tensor)
    pred_class = output.argmax(dim=1)

    # Backward pass
    model.zero_grad()
    output[0, pred_class].backward()

    # Remove hooks
    forward_handle.remove()
    backward_handle.remove()

    # Process gradients + activations
    pooled_grads = torch.mean(gradients, dim=[0, 2, 3])
    activation_maps = activations[0]

    # Weight channels
    for i in range(len(pooled_grads)):
        activation_maps[i, :, :] *= pooled_grads[i]

    heatmap = torch.mean(activation_maps, dim=0).detach().cpu().numpy()

    # Normalize
    heatmap = np.maximum(heatmap, 0)
    heatmap = heatmap / np.max(heatmap)

    return heatmap

def overlay_heatmap(img, heatmap):
    heatmap = np.uint8(255 * heatmap)
    heatmap = Image.fromarray(heatmap).resize(img.size, Image.BILINEAR)
    heatmap = np.array(heatmap)

    # Colorize heatmap
    heatmap_color = plt.cm.jet(heatmap)[:, :, :3] * 255
    heatmap_color = heatmap_color.astype(np.uint8)

    # Overlay with original
    img_np = np.array(img)
    superimposed = (0.6 * heatmap_color + 0.4 * img_np).astype(np.uint8)

    return Image.fromarray(superimposed)

# ----------------- STREAMLIT UI -----------------
st.title("🖼️ CIFAR-10 Image Classifier (CNN)")

uploaded_image = st.file_uploader("Upload an image", type=["jpg", "jpeg", "png"])

if uploaded_image:
    image = Image.open(uploaded_image).convert("RGB")
    st.image(image, caption="Uploaded Image", width=250)

    img_tensor = transform(image).unsqueeze(0)

    with torch.no_grad():
        outputs = model(img_tensor)

    # Apply softmax to get probabilities
    probs = F.softmax(outputs, dim=1)[0]

    # Get top-3 predictions
    top3_prob, top3_idx = torch.topk(probs, 3)

    st.write("### 🔍 Top Predictions:")
    for i in range(3):
        st.write(f"**{CLASSES[top3_idx[i]]}: {top3_prob[i].item()*100:.2f}%**")

    # ----------------- BAR CHART -----------------
    st.write("### 📊 Probability Distribution")

    fig, ax = plt.subplots()
    ax.bar(CLASSES, probs.tolist())
    plt.xticks(rotation=45)
    st.pyplot(fig)
    # ----------------- GENERATE GRAD-CAM -----------------
    heatmap = generate_gradcam(model, img_tensor)
    cam_image = overlay_heatmap(image, heatmap)

    st.write("### 🔥 Grad-CAM Heatmap")
    st.image(cam_image, caption="Where the model is looking", use_column_width=True)