-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
115 lines (97 loc) Β· 4.11 KB
/
Copy pathapp.py
File metadata and controls
115 lines (97 loc) Β· 4.11 KB
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
from fastapi import FastAPI, File, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse
import torch
import torch.nn as nn
from torchvision import transforms
from PIL import Image
import io
import uvicorn
# ββ Model Definition (must match training) ββββββββββββββββββββββββββββββββββ
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.conv_layers = 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),
nn.Conv2d(64, 128, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(2, 2),
)
self.fc_layers = nn.Sequential(
nn.Linear(4 * 4 * 128, 256), nn.ReLU(),
nn.Linear(256, 10),
)
def forward(self, x):
x = self.conv_layers(x)
x = x.view(x.size(0), -1)
x = self.fc_layers(x)
return x
# ββ Constants ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
CLASSES = ["airplane", "automobile", "bird", "cat", "deer",
"dog", "frog", "horse", "ship", "truck"]
CLASS_EMOJIS = {
"airplane": "βοΈ", "automobile": "π", "bird": "π¦", "cat": "π±",
"deer": "π¦", "dog": "πΆ", "frog": "πΈ", "horse": "π΄",
"ship": "π’", "truck": "π"
}
TRANSFORM = transforms.Compose([
transforms.Resize((32, 32)),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
])
# ββ App Setup ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
app = FastAPI(title="CIFAR-10 CNN Classifier")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# Load model at startup
device = torch.device("cpu")
model = CNN().to(device)
try:
model.load_state_dict(torch.load("cnn_cifar10.pth", map_location=device))
print("β
Model loaded from cnn_cifar10.pth")
except FileNotFoundError:
print("β οΈ cnn_cifar10.pth not found!")
model.eval()
# ββ Read index.html once at startup βββββββββββββββββββββββββββββββββββββββββ
with open("index.html", "r", encoding="utf-8") as f:
_RAW_HTML = f.read()
# Patch the API URL so it works on any host (HF Spaces URL changes per user)
_HTML = _RAW_HTML.replace(
'const API = "http://localhost:8000"',
'const API = window.location.origin'
)
# ββ Routes βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@app.get("/")
def root():
return HTMLResponse(content=_HTML)
@app.post("/predict")
async def predict(file: UploadFile = File(...)):
img_bytes = await file.read()
img = Image.open(io.BytesIO(img_bytes)).convert("RGB")
tensor = TRANSFORM(img).unsqueeze(0).to(device)
with torch.no_grad():
logits = model(tensor)
probs = torch.softmax(logits, dim=1)[0]
top_idx = torch.argmax(probs).item()
confidence = probs[top_idx].item()
top3 = torch.topk(probs, 3)
top3_preds = [
{
"label": CLASSES[i],
"emoji": CLASS_EMOJIS[CLASSES[i]],
"confidence": round(probs[i].item() * 100, 2),
}
for i in top3.indices.tolist()
]
return {
"prediction": CLASSES[top_idx],
"emoji": CLASS_EMOJIS[CLASSES[top_idx]],
"confidence": round(confidence * 100, 2),
"top3": top3_preds,
"all_probs": {c: round(probs[i].item() * 100, 2) for i, c in enumerate(CLASSES)},
}
if __name__ == "__main__":
uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=False)