Spaces:
Runtime error
Runtime error
Initial push
Browse files- .gitignore +5 -0
- Dockerfile +15 -0
- README.md +52 -13
- app.py +76 -0
- app/main.py +95 -0
- data/sample_mnist.png +0 -0
- docker-compose.yml +12 -0
- requirements.txt +14 -0
- src/train.py +125 -0
.gitignore
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.pth
|
| 3 |
+
data/MNIST
|
| 4 |
+
models/
|
| 5 |
+
.DS_Store
|
Dockerfile
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dockerfile
|
| 2 |
+
|
| 3 |
+
FROM python:3.10-slim
|
| 4 |
+
|
| 5 |
+
# Install dependencies
|
| 6 |
+
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
|
| 7 |
+
|
| 8 |
+
WORKDIR /app
|
| 9 |
+
|
| 10 |
+
COPY requirements.txt .
|
| 11 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 12 |
+
|
| 13 |
+
COPY . .
|
| 14 |
+
|
| 15 |
+
CMD ["python", "app.py"]
|
README.md
CHANGED
|
@@ -1,13 +1,52 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
--
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🧠 Anomaly Detection as a Service
|
| 2 |
+
|
| 3 |
+
A minimalistic anomaly detection microservice powered by an autoencoder trained on MNIST-style grayscale images.
|
| 4 |
+
This project provides both a backend API and a Gradio web interface to analyze images and identify abnormal patterns.
|
| 5 |
+
|
| 6 |
+
## 🚀 Features
|
| 7 |
+
|
| 8 |
+
- 🧪 **FastAPI** backend for anomaly score prediction
|
| 9 |
+
- 🎛️ **Gradio** frontend with drag-and-drop image testing
|
| 10 |
+
- 🔥 Visual output includes anomaly score, reconstruction, and heatmap
|
| 11 |
+
- ⚙️ Pretrained autoencoder (trained on MNIST digit-style grayscale images)
|
| 12 |
+
- 🧰 Easily extendable to new datasets and model architectures
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
## ⚙️ Setup
|
| 16 |
+
|
| 17 |
+
1. Install dependencies:
|
| 18 |
+
```
|
| 19 |
+
pip install -r requirements.txt
|
| 20 |
+
```
|
| 21 |
+
2. (Optional) Train the model from scratch:
|
| 22 |
+
```
|
| 23 |
+
python src/train.py
|
| 24 |
+
```
|
| 25 |
+
By default, the model will be saved to `models/autoencoder_mnist.pth`.
|
| 26 |
+
|
| 27 |
+
## 🧪 Run the API Server (FastAPI)
|
| 28 |
+
```
|
| 29 |
+
uvicorn app.main:app --reload
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
This starts the API at http://127.0.0.1:8000
|
| 33 |
+
|
| 34 |
+
You can test it using Swagger UI:
|
| 35 |
+
http://127.0.0.1:8000/docs
|
| 36 |
+
|
| 37 |
+
## 🎛️ Run the Gradio Frontend
|
| 38 |
+
```
|
| 39 |
+
python app.py
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
This launches an interactive browser interface for:
|
| 43 |
+
|
| 44 |
+
- Uploading images
|
| 45 |
+
- Viewing anomaly scores
|
| 46 |
+
- Seeing reconstructions and heatmaps side-by-side
|
| 47 |
+
|
| 48 |
+
## 📝 Notes
|
| 49 |
+
- Input images must be grayscale, 28×28 pixels.
|
| 50 |
+
- To adapt to a custom dataset, modify src/train.py accordingly and retrain.
|
| 51 |
+
- The project is meant for educational/demo purposes and not intended for production deployment.
|
| 52 |
+
|
app.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import torch
|
| 3 |
+
from torchvision import transforms as T
|
| 4 |
+
from PIL import Image
|
| 5 |
+
import numpy as np
|
| 6 |
+
import base64
|
| 7 |
+
from io import BytesIO
|
| 8 |
+
import matplotlib.pyplot as plt
|
| 9 |
+
from src.train import load_model, loss_fn
|
| 10 |
+
|
| 11 |
+
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 12 |
+
model = load_model("./models/autoencoder_mnist.pth")
|
| 13 |
+
model.eval()
|
| 14 |
+
|
| 15 |
+
transform = T.Compose([
|
| 16 |
+
T.Grayscale(num_output_channels=1),
|
| 17 |
+
T.Resize((28, 28)),
|
| 18 |
+
T.ToTensor()
|
| 19 |
+
])
|
| 20 |
+
|
| 21 |
+
def predict(image):
|
| 22 |
+
original = image.convert("L")
|
| 23 |
+
tensor = transform(original).unsqueeze(0).to(DEVICE)
|
| 24 |
+
original_resized = original.resize((200, 200), Image.NEAREST)
|
| 25 |
+
|
| 26 |
+
with torch.no_grad():
|
| 27 |
+
decoded = model(tensor)
|
| 28 |
+
score = loss_fn(decoded, tensor).item()
|
| 29 |
+
diff = (decoded - tensor).squeeze().cpu().numpy() ** 2
|
| 30 |
+
reconstructed = decoded.squeeze().cpu().numpy()
|
| 31 |
+
|
| 32 |
+
# Anomaly interpretation
|
| 33 |
+
if score > 0.02:
|
| 34 |
+
verdict = "🚨 Anomaly detected!"
|
| 35 |
+
else:
|
| 36 |
+
verdict = "✅ Everything looks normal."
|
| 37 |
+
|
| 38 |
+
# Create heatmap
|
| 39 |
+
fig, ax = plt.subplots()
|
| 40 |
+
ax.imshow(diff, cmap="hot")
|
| 41 |
+
ax.axis("off")
|
| 42 |
+
buf1 = BytesIO()
|
| 43 |
+
plt.savefig(buf1, format="png", bbox_inches="tight", pad_inches=0)
|
| 44 |
+
plt.close(fig)
|
| 45 |
+
buf1.seek(0)
|
| 46 |
+
heatmap = Image.open(buf1)
|
| 47 |
+
|
| 48 |
+
# Reconstructed image
|
| 49 |
+
fig2, ax2 = plt.subplots()
|
| 50 |
+
ax2.imshow(reconstructed, cmap="gray")
|
| 51 |
+
ax2.axis("off")
|
| 52 |
+
buf2 = BytesIO()
|
| 53 |
+
plt.savefig(buf2, format="png", bbox_inches="tight", pad_inches=0)
|
| 54 |
+
plt.close(fig2)
|
| 55 |
+
buf2.seek(0)
|
| 56 |
+
reconstructed_img = Image.open(buf2)
|
| 57 |
+
|
| 58 |
+
return f"{score:.5f}", verdict, original_resized, reconstructed_img, heatmap
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
iface = gr.Interface(
|
| 62 |
+
fn=predict,
|
| 63 |
+
inputs=gr.Image(type="pil", label="Upload MNIST-style Image"),
|
| 64 |
+
outputs=[
|
| 65 |
+
gr.Textbox(label="Anomaly Score"),
|
| 66 |
+
gr.Textbox(label="Verdict"),
|
| 67 |
+
gr.Image(label="Original", height=200),
|
| 68 |
+
gr.Image(label="Reconstruction", height=200),
|
| 69 |
+
gr.Image(label="Heatmap", height=200),
|
| 70 |
+
],
|
| 71 |
+
allow_flagging="never",
|
| 72 |
+
title="🧠 Anomaly Detection",
|
| 73 |
+
description="Upload a grayscale image (28x28) to get anomaly score."
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
iface.launch(iface.launch(server_name="0.0.0.0", server_port=7860))
|
app/main.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app/main.py
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
from fastapi import FastAPI, UploadFile, HTTPException
|
| 5 |
+
from fastapi.responses import JSONResponse
|
| 6 |
+
from PIL import Image
|
| 7 |
+
import torch
|
| 8 |
+
from torchvision import transforms as T
|
| 9 |
+
from src.train import load_model, loss_fn # Load the model and loss function from src/train.py
|
| 10 |
+
import io
|
| 11 |
+
from io import BytesIO
|
| 12 |
+
|
| 13 |
+
import base64
|
| 14 |
+
import matplotlib.pyplot as plt
|
| 15 |
+
|
| 16 |
+
import logging
|
| 17 |
+
log = logging.getLogger(__name__)
|
| 18 |
+
logging.basicConfig(level=logging.INFO)
|
| 19 |
+
|
| 20 |
+
from huggingface_hub import hf_hub_download
|
| 21 |
+
|
| 22 |
+
app = FastAPI()
|
| 23 |
+
|
| 24 |
+
# Device configuration
|
| 25 |
+
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 26 |
+
|
| 27 |
+
# Load the model once
|
| 28 |
+
MODEL_PATH = hf_hub_download(repo_id="dmtschulz/anomaly-detection-model", filename="autoencoder_mnist.pth")
|
| 29 |
+
|
| 30 |
+
log.info("Model loaded. App is ready.")
|
| 31 |
+
|
| 32 |
+
model = load_model(MODEL_PATH)
|
| 33 |
+
model.eval()
|
| 34 |
+
|
| 35 |
+
# Transformations
|
| 36 |
+
transform = T.Compose([
|
| 37 |
+
T.Grayscale(num_output_channels=1),
|
| 38 |
+
T.Resize((28, 28)),
|
| 39 |
+
T.ToTensor()
|
| 40 |
+
])
|
| 41 |
+
|
| 42 |
+
@app.get("/health")
|
| 43 |
+
def health():
|
| 44 |
+
return {"status": "ok"}
|
| 45 |
+
|
| 46 |
+
@app.post("/predict", summary="Predict anomaly score from image")
|
| 47 |
+
async def predict(file: UploadFile):
|
| 48 |
+
try:
|
| 49 |
+
contents = await file.read()
|
| 50 |
+
image = Image.open(io.BytesIO(contents)).convert("L")
|
| 51 |
+
image = transform(image)
|
| 52 |
+
image = image.unsqueeze(0).to(DEVICE)
|
| 53 |
+
|
| 54 |
+
with torch.no_grad():
|
| 55 |
+
decoded = model(image)
|
| 56 |
+
score = loss_fn(decoded, image).item()
|
| 57 |
+
diff = (decoded - image).squeeze().cpu().numpy() ** 2
|
| 58 |
+
|
| 59 |
+
# Create heatmap image
|
| 60 |
+
fig, ax = plt.subplots()
|
| 61 |
+
ax.imshow(diff, cmap="hot")
|
| 62 |
+
ax.axis("off")
|
| 63 |
+
buf = BytesIO()
|
| 64 |
+
plt.savefig(buf, format="png", bbox_inches="tight", pad_inches=0)
|
| 65 |
+
plt.close(fig)
|
| 66 |
+
buf.seek(0)
|
| 67 |
+
|
| 68 |
+
# Encode to base64
|
| 69 |
+
heatmap_b64 = base64.b64encode(buf.read()).decode("utf-8")
|
| 70 |
+
|
| 71 |
+
# Create reconstructed image
|
| 72 |
+
decoded_img = decoded.squeeze().cpu().numpy()
|
| 73 |
+
|
| 74 |
+
# Visualize the decoded image as png
|
| 75 |
+
fig2, ax2 = plt.subplots()
|
| 76 |
+
ax2.imshow(decoded_img, cmap="gray")
|
| 77 |
+
ax2.axis("off")
|
| 78 |
+
buf2 = BytesIO()
|
| 79 |
+
plt.savefig(buf2, format="png", bbox_inches="tight", pad_inches=0)
|
| 80 |
+
plt.close(fig2)
|
| 81 |
+
buf2.seek(0)
|
| 82 |
+
|
| 83 |
+
# Decode to base64
|
| 84 |
+
decoded_b64 = base64.b64encode(buf2.read()).decode("utf-8")
|
| 85 |
+
|
| 86 |
+
# Return everything as JSON
|
| 87 |
+
return JSONResponse(content={
|
| 88 |
+
"anomaly_score": score,
|
| 89 |
+
"heatmap": heatmap_b64,
|
| 90 |
+
"decoded_image": decoded_b64
|
| 91 |
+
})
|
| 92 |
+
|
| 93 |
+
except Exception as e:
|
| 94 |
+
raise HTTPException(status_code=500, detail=f"Unexpected error: {str(e)}")
|
| 95 |
+
|
data/sample_mnist.png
ADDED
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: "3.8"
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
frontend:
|
| 5 |
+
build: .
|
| 6 |
+
ports:
|
| 7 |
+
- "7860:7860"
|
| 8 |
+
volumes:
|
| 9 |
+
- .:/app
|
| 10 |
+
environment:
|
| 11 |
+
- PYTHONUNBUFFERED=1
|
| 12 |
+
restart: unless-stopped
|
requirements.txt
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch>=2.0.0
|
| 2 |
+
fastapi
|
| 3 |
+
gradio
|
| 4 |
+
numpy
|
| 5 |
+
pandas
|
| 6 |
+
requests
|
| 7 |
+
uvicorn
|
| 8 |
+
pillow
|
| 9 |
+
matplotlib
|
| 10 |
+
plotly
|
| 11 |
+
scikit-learn
|
| 12 |
+
tqdm
|
| 13 |
+
python-multipart
|
| 14 |
+
torchvision
|
src/train.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# src/train.py
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import torch
|
| 5 |
+
import numpy as np
|
| 6 |
+
from tqdm import tqdm
|
| 7 |
+
from torch import nn, optim
|
| 8 |
+
from torch.utils.data import DataLoader, Subset
|
| 9 |
+
from torchvision import transforms as T
|
| 10 |
+
from torchvision.datasets import MNIST
|
| 11 |
+
from torchvision.utils import save_image
|
| 12 |
+
|
| 13 |
+
import logging
|
| 14 |
+
|
| 15 |
+
logging.basicConfig(level=logging.INFO)
|
| 16 |
+
log = logging.getLogger(__name__)
|
| 17 |
+
|
| 18 |
+
# Device configuration
|
| 19 |
+
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 20 |
+
log.info("Using device: %s", DEVICE)
|
| 21 |
+
|
| 22 |
+
# Constants
|
| 23 |
+
DATA_ROOT = "./data"
|
| 24 |
+
MODEL_DIR = "./models"
|
| 25 |
+
|
| 26 |
+
os.makedirs(DATA_ROOT, exist_ok=True)
|
| 27 |
+
os.makedirs(MODEL_DIR, exist_ok=True)
|
| 28 |
+
|
| 29 |
+
# Autoencoder Model
|
| 30 |
+
class Autoencoder(nn.Module):
|
| 31 |
+
def __init__(self, h: int = 32):
|
| 32 |
+
super().__init__()
|
| 33 |
+
self.encoder = nn.Sequential(
|
| 34 |
+
nn.Flatten(),
|
| 35 |
+
nn.Linear(784, 12000),
|
| 36 |
+
nn.Dropout(0.2),
|
| 37 |
+
nn.LeakyReLU(),
|
| 38 |
+
nn.Linear(12000, h),
|
| 39 |
+
nn.LeakyReLU(),
|
| 40 |
+
)
|
| 41 |
+
self.decoder = nn.Sequential(
|
| 42 |
+
nn.Linear(h, 12000),
|
| 43 |
+
nn.Dropout(0.2),
|
| 44 |
+
nn.LeakyReLU(),
|
| 45 |
+
nn.Linear(12000, 784),
|
| 46 |
+
nn.Sigmoid(),
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
def forward(self, x: torch.Tensor):
|
| 50 |
+
x = x.view(x.size(0), -1) # (N, 784)
|
| 51 |
+
encoded = self.encoder(x)
|
| 52 |
+
decoded = self.decoder(encoded)
|
| 53 |
+
decoded = decoded.view(-1, 1, 28, 28)
|
| 54 |
+
return decoded
|
| 55 |
+
|
| 56 |
+
# Loss function
|
| 57 |
+
loss_fn = nn.MSELoss()
|
| 58 |
+
|
| 59 |
+
# Train function
|
| 60 |
+
def train(loader, h=32, epochs=5, save_path=None):
|
| 61 |
+
model = Autoencoder(h).to(DEVICE)
|
| 62 |
+
optimizer = optim.Adam(model.parameters(), lr=0.001)
|
| 63 |
+
|
| 64 |
+
for epoch in range(epochs):
|
| 65 |
+
log.info("Epoch %d", epoch + 1)
|
| 66 |
+
pbar = tqdm(loader)
|
| 67 |
+
for batch, _ in pbar:
|
| 68 |
+
batch = batch.to(DEVICE)
|
| 69 |
+
optimizer.zero_grad()
|
| 70 |
+
prediction = model(batch)
|
| 71 |
+
loss = loss_fn(prediction, batch)
|
| 72 |
+
loss.backward()
|
| 73 |
+
optimizer.step()
|
| 74 |
+
pbar.set_description(f"Loss: {loss.item():.4f}")
|
| 75 |
+
|
| 76 |
+
if save_path:
|
| 77 |
+
save_model(model, save_path)
|
| 78 |
+
|
| 79 |
+
return model
|
| 80 |
+
|
| 81 |
+
# Save model
|
| 82 |
+
def save_model(model, path):
|
| 83 |
+
torch.save(model.state_dict(), path)
|
| 84 |
+
log.info("Model saved to %s", path)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
# Load model
|
| 88 |
+
def load_model(path, h=32):
|
| 89 |
+
model = Autoencoder(h).to(DEVICE)
|
| 90 |
+
model.load_state_dict(torch.load(path, map_location=DEVICE))
|
| 91 |
+
model.to(DEVICE)
|
| 92 |
+
model.eval()
|
| 93 |
+
log.info("Model loaded from %s", path)
|
| 94 |
+
return model
|
| 95 |
+
|
| 96 |
+
# Get DataLoaders
|
| 97 |
+
def get_data_loaders(batch_size=128, anomaly_class_threshold=None):
|
| 98 |
+
transform = T.ToTensor()
|
| 99 |
+
train_dataset = MNIST(DATA_ROOT, train=True, download=True, transform=transform)
|
| 100 |
+
test_dataset = MNIST(DATA_ROOT, train=False, download=True, transform=transform)
|
| 101 |
+
|
| 102 |
+
if anomaly_class_threshold is not None:
|
| 103 |
+
indices = np.where(train_dataset.targets < anomaly_class_threshold)[0]
|
| 104 |
+
train_dataset = Subset(train_dataset, indices)
|
| 105 |
+
|
| 106 |
+
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
|
| 107 |
+
test_loader = DataLoader(test_dataset, batch_size=batch_size)
|
| 108 |
+
|
| 109 |
+
img, _ = test_dataset[6]
|
| 110 |
+
save_image(img, "data/sample_mnist.png")
|
| 111 |
+
|
| 112 |
+
return train_loader, test_loader
|
| 113 |
+
|
| 114 |
+
# Predict Anomaly Score
|
| 115 |
+
def predict_anomaly_score(model, x):
|
| 116 |
+
model.eval()
|
| 117 |
+
with torch.no_grad():
|
| 118 |
+
reconstruction = model(x.to(DEVICE))
|
| 119 |
+
score = torch.mean((x - reconstruction.cpu()) ** 2, dim=(1, 2, 3))
|
| 120 |
+
return score.cpu().numpy()
|
| 121 |
+
|
| 122 |
+
# Main for getting data and testing the model
|
| 123 |
+
if __name__ == "__main__":
|
| 124 |
+
train_loader, test_loader = get_data_loaders()
|
| 125 |
+
model = train(train_loader, h=32, epochs=5, save_path=f"{MODEL_DIR}/autoencoder_mnist.pth")
|