Spaces:
Sleeping
Sleeping
Upload 12 files
Browse files- .gitignore +3 -0
- app.py +75 -0
- classes.txt +4 -0
- dataset.py +27 -0
- dog_emotion.ipynb +1 -0
- dog_emotion_model.pth +3 -0
- model.py +17 -0
- readme.md +28 -0
- requirements.txt +9 -0
- test.py +22 -0
- train.py +66 -0
- utils.py +63 -0
.gitignore
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
data
|
| 2 |
+
archive.zip
|
| 3 |
+
main.ipynb
|
app.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
from torchvision import models
|
| 4 |
+
import gradio as gr
|
| 5 |
+
from typing import Tuple, Dict
|
| 6 |
+
from timeit import default_timer as timer
|
| 7 |
+
|
| 8 |
+
class DogEmotionResNet(nn.Module):
|
| 9 |
+
def __init__(self, num_classes, weights=None):
|
| 10 |
+
super().__init__()
|
| 11 |
+
|
| 12 |
+
self.resnet = models.resnet50(weights=weights)
|
| 13 |
+
for param in self.resnet.parameters():
|
| 14 |
+
param.requires_grad = False
|
| 15 |
+
|
| 16 |
+
in_features = self.resnet.fc.in_features
|
| 17 |
+
self.resnet.fc = nn.Linear(in_features, num_classes)
|
| 18 |
+
|
| 19 |
+
def forward(self, x):
|
| 20 |
+
return self.resnet(x)
|
| 21 |
+
|
| 22 |
+
def load_model(weights_path: str, num_classes: int) -> DogEmotionResNet:
|
| 23 |
+
resnet_weights = models.ResNet50_Weights.DEFAULT
|
| 24 |
+
model = DogEmotionResNet(num_classes=num_classes, weights=resnet_weights)
|
| 25 |
+
model.load_state_dict(torch.load(weights_path))
|
| 26 |
+
return model
|
| 27 |
+
|
| 28 |
+
def load_class_names(file_path: str) -> list:
|
| 29 |
+
with open(file_path, "r") as f:
|
| 30 |
+
class_names = [emotion.strip() for emotion in f.readlines()]
|
| 31 |
+
return class_names
|
| 32 |
+
|
| 33 |
+
def predict(img) -> Tuple[Dict, float]:
|
| 34 |
+
"""Transforms and performs a prediction on img and returns prediction and time taken."""
|
| 35 |
+
start_time = timer()
|
| 36 |
+
|
| 37 |
+
img = resnet_transform(img).unsqueeze(0)
|
| 38 |
+
|
| 39 |
+
model.eval()
|
| 40 |
+
with torch.inference_mode():
|
| 41 |
+
pred_probs = torch.softmax(model(img), dim=1)
|
| 42 |
+
|
| 43 |
+
pred_labels_and_probs = {class_names[i]: float(pred_probs[0][i]) for i in range(len(class_names))}
|
| 44 |
+
|
| 45 |
+
pred_time = round(timer() - start_time, 5)
|
| 46 |
+
return pred_labels_and_probs, pred_time
|
| 47 |
+
|
| 48 |
+
# Load model and class names
|
| 49 |
+
weights_path = 'dog_emotion_model.pth'
|
| 50 |
+
class_names_file = "classes.txt"
|
| 51 |
+
|
| 52 |
+
model = load_model(weights_path, num_classes=4)
|
| 53 |
+
class_names = load_class_names(class_names_file)
|
| 54 |
+
resnet_weights = models.ResNet50_Weights.DEFAULT
|
| 55 |
+
resnet_transform = resnet_weights.transforms()
|
| 56 |
+
|
| 57 |
+
# Gradio Interface
|
| 58 |
+
title = "Dog Emotion Classifier 🐶🎭"
|
| 59 |
+
description = "This app classifies the emotion of a dog in an image into one of four categories: happy, sad, angry, or relaxed."
|
| 60 |
+
article = ""
|
| 61 |
+
|
| 62 |
+
demo = gr.Interface(
|
| 63 |
+
fn=predict,
|
| 64 |
+
inputs=gr.Image(type="pil"),
|
| 65 |
+
outputs=[
|
| 66 |
+
gr.Label(num_top_classes=5, label="Predictions"),
|
| 67 |
+
gr.Number(label="Prediction time (s)"),
|
| 68 |
+
],
|
| 69 |
+
title=title,
|
| 70 |
+
description=description,
|
| 71 |
+
article=article,
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
# Launch the app!
|
| 75 |
+
demo.launch()
|
classes.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
angry
|
| 2 |
+
happy
|
| 3 |
+
relaxed
|
| 4 |
+
sad
|
dataset.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from torchvision import datasets, transforms, models
|
| 3 |
+
|
| 4 |
+
def get_datasets(data_dir):
|
| 5 |
+
resnet_weights = models.ResNet50_Weights.DEFAULT
|
| 6 |
+
|
| 7 |
+
data_transforms = {
|
| 8 |
+
'train': transforms.Compose([
|
| 9 |
+
transforms.Resize((224, 224)),
|
| 10 |
+
transforms.ToTensor(),
|
| 11 |
+
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
|
| 12 |
+
]),
|
| 13 |
+
'test': transforms.Compose([
|
| 14 |
+
transforms.Resize((224, 224)),
|
| 15 |
+
transforms.ToTensor(),
|
| 16 |
+
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
|
| 17 |
+
])
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
image_dataset = datasets.ImageFolder(data_dir, transform=resnet_weights.transforms())
|
| 21 |
+
|
| 22 |
+
train_size = int(0.9 * len(image_dataset))
|
| 23 |
+
test_size = len(image_dataset) - train_size
|
| 24 |
+
|
| 25 |
+
train_dataset, test_dataset = torch.utils.data.random_split(image_dataset, [train_size, test_size], generator=torch.Generator().manual_seed(42))
|
| 26 |
+
|
| 27 |
+
return train_dataset, test_dataset, image_dataset.classes
|
dog_emotion.ipynb
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"cells":[{"cell_type":"code","execution_count":2,"metadata":{"execution":{"iopub.execute_input":"2024-07-13T14:16:59.816891Z","iopub.status.busy":"2024-07-13T14:16:59.815968Z","iopub.status.idle":"2024-07-13T14:16:59.821084Z","shell.execute_reply":"2024-07-13T14:16:59.820084Z","shell.execute_reply.started":"2024-07-13T14:16:59.816858Z"},"trusted":true},"outputs":[],"source":["import pandas as pd\n","import numpy as np\n","import matplotlib.pyplot as plt\n","import os"]},{"cell_type":"code","execution_count":3,"metadata":{"execution":{"iopub.execute_input":"2024-07-13T14:16:59.837306Z","iopub.status.busy":"2024-07-13T14:16:59.836599Z","iopub.status.idle":"2024-07-13T14:16:59.844964Z","shell.execute_reply":"2024-07-13T14:16:59.843982Z","shell.execute_reply.started":"2024-07-13T14:16:59.837259Z"},"trusted":true},"outputs":[{"data":{"text/plain":["'2.3.0+cu121'"]},"execution_count":3,"metadata":{},"output_type":"execute_result"}],"source":["import torch\n","from torch import nn, optim\n","from torch.utils.data import DataLoader, Dataset\n","from torchvision import datasets, transforms, models\n","torch.__version__"]},{"cell_type":"code","execution_count":4,"metadata":{"execution":{"iopub.execute_input":"2024-07-13T14:16:59.849583Z","iopub.status.busy":"2024-07-13T14:16:59.849298Z","iopub.status.idle":"2024-07-13T14:16:59.855674Z","shell.execute_reply":"2024-07-13T14:16:59.854751Z","shell.execute_reply.started":"2024-07-13T14:16:59.849561Z"},"trusted":true},"outputs":[{"data":{"text/plain":["'cuda'"]},"execution_count":4,"metadata":{},"output_type":"execute_result"}],"source":["device='cuda' if torch.cuda.is_available() else 'cpu'\n","device"]},{"cell_type":"code","execution_count":9,"metadata":{"execution":{"iopub.execute_input":"2024-07-13T14:16:59.866616Z","iopub.status.busy":"2024-07-13T14:16:59.866153Z","iopub.status.idle":"2024-07-13T14:16:59.889656Z","shell.execute_reply":"2024-07-13T14:16:59.888806Z","shell.execute_reply.started":"2024-07-13T14:16:59.866593Z"},"trusted":true},"outputs":[],"source":["# dir = '/kaggle/input/dog-emotion/Dog Emotion/'\n","dir='data/'"]},{"cell_type":"code","execution_count":6,"metadata":{"execution":{"iopub.execute_input":"2024-07-13T14:16:59.919603Z","iopub.status.busy":"2024-07-13T14:16:59.919302Z","iopub.status.idle":"2024-07-13T14:16:59.928354Z","shell.execute_reply":"2024-07-13T14:16:59.927503Z","shell.execute_reply.started":"2024-07-13T14:16:59.919580Z"},"trusted":true},"outputs":[],"source":["resnet_weights=models.ResNet50_Weights.DEFAULT"]},{"cell_type":"code","execution_count":10,"metadata":{"execution":{"iopub.execute_input":"2024-07-13T14:16:59.930583Z","iopub.status.busy":"2024-07-13T14:16:59.930150Z","iopub.status.idle":"2024-07-13T14:16:59.938564Z","shell.execute_reply":"2024-07-13T14:16:59.937780Z","shell.execute_reply.started":"2024-07-13T14:16:59.930552Z"},"trusted":true},"outputs":[],"source":["train_transforms = transforms.Compose([\n"," transforms.Resize((224, 224)),\n"," transforms.ToTensor(),\n"," transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n","])\n","\n","test_transforms = transforms.Compose([\n"," transforms.Resize((224, 224)),\n"," transforms.ToTensor(),\n"," transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n","])\n"]},{"cell_type":"code","execution_count":11,"metadata":{"execution":{"iopub.execute_input":"2024-07-13T14:16:59.941623Z","iopub.status.busy":"2024-07-13T14:16:59.941285Z","iopub.status.idle":"2024-07-13T14:17:01.610438Z","shell.execute_reply":"2024-07-13T14:17:01.609614Z","shell.execute_reply.started":"2024-07-13T14:16:59.941593Z"},"trusted":true},"outputs":[],"source":["# train_dataset = DogEmotionDataset(\n","# images=X_train.values,\n","# labels=y_train.values,\n","# transforms=train_transforms,\n","# classes=classes\n","# )\n","\n","# test_dataset = DogEmotionDataset(\n","# images=X_test.values,\n","# labels=y_test.values,\n","# transforms=test_transforms,\n","# classes=classes\n","# )\n","\n","dogdataset = datasets.ImageFolder(dir, transform=resnet_weights.transforms())\n","\n","train_size = int(0.9 * len(dogdataset))\n","test_size = len(dogdataset) - train_size\n","\n","train_dataset, test_dataset = torch.utils.data.random_split(dogdataset, [train_size, test_size],generator=torch.Generator().manual_seed(42))"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2024-07-13T14:17:01.612559Z","iopub.status.busy":"2024-07-13T14:17:01.612244Z","iopub.status.idle":"2024-07-13T14:17:01.617559Z","shell.execute_reply":"2024-07-13T14:17:01.616669Z","shell.execute_reply.started":"2024-07-13T14:17:01.612534Z"},"trusted":true},"outputs":[],"source":["train_loader = DataLoader(train_dataset, batch_size=128, shuffle=True)\n","test_loader = DataLoader(test_dataset, batch_size=128, shuffle=False)"]},{"cell_type":"code","execution_count":14,"metadata":{},"outputs":[],"source":["with open('classes.txt', 'w') as f:\n"," for item in dogdataset.classes:\n"," f.write(\"%s\\n\" % item)"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2024-07-13T14:17:01.629372Z","iopub.status.busy":"2024-07-13T14:17:01.629027Z","iopub.status.idle":"2024-07-13T14:17:02.324715Z","shell.execute_reply":"2024-07-13T14:17:02.323774Z","shell.execute_reply.started":"2024-07-13T14:17:01.629341Z"},"trusted":true},"outputs":[],"source":["imgs, labels =next(iter(train_loader))\n","imgs.shape"]},{"cell_type":"code","execution_count":1,"metadata":{"execution":{"iopub.execute_input":"2024-07-13T14:17:02.327344Z","iopub.status.busy":"2024-07-13T14:17:02.327026Z","iopub.status.idle":"2024-07-13T14:17:02.333456Z","shell.execute_reply":"2024-07-13T14:17:02.332447Z","shell.execute_reply.started":"2024-07-13T14:17:02.327314Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["Writing model.py\n"]}],"source":["class DogEmotionResNet(nn.Module):\n"," def __init__(self, num_classes, weights=None):\n"," super().__init__()\n"," \n"," self.resnet = models.resnet50(weights=weights)\n"," for param in self.resnet.parameters():\n"," param.requires_grad = False\n"," \n"," in_features = self.resnet.fc.in_features\n"," self.resnet.fc = nn.Linear(in_features, num_classes)\n"," \n"," def forward(self, x):\n"," return self.resnet(x)"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2024-07-13T14:17:02.334806Z","iopub.status.busy":"2024-07-13T14:17:02.334546Z","iopub.status.idle":"2024-07-13T14:17:02.588742Z","shell.execute_reply":"2024-07-13T14:17:02.587715Z","shell.execute_reply.started":"2024-07-13T14:17:02.334783Z"},"trusted":true},"outputs":[],"source":["torch.manual_seed(42)\n","torch.cuda.manual_seed(42)\n","\n","classes = dogdataset.classes\n","\n","model = DogEmotionResNet(num_classes= len(classes), weights=resnet_weights)\n","model.to(device)\n","\n","# test_output = model(imgs.to(device))"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2024-07-13T14:17:02.590369Z","iopub.status.busy":"2024-07-13T14:17:02.590030Z","iopub.status.idle":"2024-07-13T14:17:02.602762Z","shell.execute_reply":"2024-07-13T14:17:02.601655Z","shell.execute_reply.started":"2024-07-13T14:17:02.590341Z"},"trusted":true},"outputs":[],"source":["def train(model, train_loader, criterion, optimizer, device):\n"," model.train()\n"," running_loss = 0.0\n"," correct_predictions = 0\n"," total_predictions = 0\n"," \n"," for inputs, labels in train_loader:\n"," inputs, labels = inputs.to(device), labels.to(device)\n"," \n"," optimizer.zero_grad()\n"," \n"," outputs = model(inputs)\n"," loss = criterion(outputs, labels)\n"," loss.backward()\n"," optimizer.step()\n"," \n"," running_loss += loss.item()\n"," predicted_labels = outputs.argmax(dim=1)\n"," correct_predictions += (predicted_labels == labels).sum().item()\n"," total_predictions += labels.size(0)\n"," \n"," train_loss = running_loss / len(train_loader)\n"," train_accuracy = correct_predictions / total_predictions\n"," \n"," return train_loss, train_accuracy\n","\n","def validate(model, test_loader, criterion, device):\n"," model.eval()\n"," running_loss = 0.0\n"," correct_predictions = 0\n"," total_predictions = 0\n"," \n"," with torch.inference_mode():\n"," for inputs, labels in test_loader:\n"," inputs, labels = inputs.to(device), labels.to(device)\n"," \n"," outputs = model(inputs)\n"," loss = criterion(outputs, labels)\n"," \n"," running_loss += loss.item()\n"," predicted_labels = outputs.argmax(dim=1)\n"," correct_predictions += (predicted_labels == labels).sum().item()\n"," total_predictions += labels.size(0)\n"," \n"," test_loss = running_loss / len(test_loader)\n"," test_accuracy = correct_predictions / total_predictions\n"," \n"," return test_loss, test_accuracy\n"," "]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2024-07-13T14:17:02.604726Z","iopub.status.busy":"2024-07-13T14:17:02.604323Z","iopub.status.idle":"2024-07-13T14:20:47.515463Z","shell.execute_reply":"2024-07-13T14:20:47.514558Z","shell.execute_reply.started":"2024-07-13T14:17:02.604682Z"},"trusted":true},"outputs":[],"source":["from tqdm.auto import tqdm\n","\n","EPOCHS = 30\n","criterion = nn.CrossEntropyLoss()\n","optimizer = optim.Adam(model.parameters(), lr=0.002)\n","\n","train_losses = []\n","train_accuracies = []\n","test_losses = []\n","test_accuracies = []\n","\n","for epoch in tqdm(range(EPOCHS)):\n"," train_loss, train_accuracy = train(model, train_loader, criterion, optimizer, device)\n"," test_loss, test_accuracy = validate(model, test_loader, criterion, device)\n"," \n"," train_losses.append(train_loss)\n"," train_accuracies.append(train_accuracy)\n"," test_losses.append(test_loss)\n"," test_accuracies.append(test_accuracy)\n"," \n"," print(f'Epoch: {epoch+1}/{EPOCHS}, Train Loss: {train_loss:.4f}, Train Accuracy: {train_accuracy:.4f}, Test Loss: {test_loss:.4f}, Test Accuracy: {test_accuracy:.4f}')\n"," "]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2024-07-13T14:22:05.329623Z","iopub.status.busy":"2024-07-13T14:22:05.328737Z","iopub.status.idle":"2024-07-13T14:22:05.554562Z","shell.execute_reply":"2024-07-13T14:22:05.553621Z","shell.execute_reply.started":"2024-07-13T14:22:05.329587Z"},"trusted":true},"outputs":[],"source":["plt.figure(figsize=(12, 6))\n","plt.subplot(1, 2, 1)\n","plt.plot(train_losses, label='train loss')\n","plt.plot(test_losses, label='test loss')\n","plt.xlabel('Epochs')\n","plt.ylabel('Loss')\n","plt.legend()"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2024-07-13T14:22:05.557018Z","iopub.status.busy":"2024-07-13T14:22:05.556561Z","iopub.status.idle":"2024-07-13T14:22:05.748228Z","shell.execute_reply":"2024-07-13T14:22:05.747325Z","shell.execute_reply.started":"2024-07-13T14:22:05.556984Z"},"trusted":true},"outputs":[],"source":["plt.subplot(1, 2, 2)\n","plt.plot(train_accuracies, label='train accuracy')\n","plt.plot(test_accuracies, label='test accuracy')\n","plt.xlabel('Epochs')\n","plt.ylabel('Accuracy')\n","plt.legend()\n","plt.show()"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2024-07-13T14:20:47.969342Z","iopub.status.busy":"2024-07-13T14:20:47.968739Z","iopub.status.idle":"2024-07-13T14:20:48.069413Z","shell.execute_reply":"2024-07-13T14:20:48.068345Z","shell.execute_reply.started":"2024-07-13T14:20:47.969309Z"},"trusted":true},"outputs":[],"source":["torch.save(model.state_dict(), 'dog_emotion_model.pth')"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2024-07-13T14:20:48.071053Z","iopub.status.busy":"2024-07-13T14:20:48.070747Z","iopub.status.idle":"2024-07-13T14:20:48.343977Z","shell.execute_reply":"2024-07-13T14:20:48.343058Z","shell.execute_reply.started":"2024-07-13T14:20:48.071027Z"},"trusted":true},"outputs":[],"source":["model = DogEmotionResNet(num_classes=len(classes))\n","model.load_state_dict(torch.load('dog_emotion_model.pth'))\n","model.to(device)"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2024-07-13T14:22:26.567754Z","iopub.status.busy":"2024-07-13T14:22:26.566902Z","iopub.status.idle":"2024-07-13T14:22:30.940835Z","shell.execute_reply":"2024-07-13T14:22:30.940029Z","shell.execute_reply.started":"2024-07-13T14:22:26.567714Z"},"trusted":true},"outputs":[],"source":["def predict(model, test_loader, device):\n"," model.eval()\n"," predictions = []\n"," \n"," with torch.inference_mode():\n"," for inputs, labels in test_loader:\n"," inputs = inputs.to(device)\n"," outputs = model(inputs)\n"," predicted_labels = outputs.argmax(dim=1)\n"," \n"," predictions.extend(predicted_labels.cpu().numpy())\n"," \n"," return predictions\n","\n","predictions = predict(model, test_loader, device)"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2024-07-13T14:22:30.942858Z","iopub.status.busy":"2024-07-13T14:22:30.942507Z","iopub.status.idle":"2024-07-13T14:22:30.949446Z","shell.execute_reply":"2024-07-13T14:22:30.948521Z","shell.execute_reply.started":"2024-07-13T14:22:30.942825Z"},"trusted":true},"outputs":[],"source":["predictions[:10]"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2024-07-13T14:22:33.724471Z","iopub.status.busy":"2024-07-13T14:22:33.723796Z","iopub.status.idle":"2024-07-13T14:22:37.403463Z","shell.execute_reply":"2024-07-13T14:22:37.402489Z","shell.execute_reply.started":"2024-07-13T14:22:33.724441Z"},"trusted":true},"outputs":[],"source":["from sklearn.metrics import classification_report\n","\n","print(classification_report([i[1] for i in test_dataset], predictions))\n"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":[]}],"metadata":{"kaggle":{"accelerator":"gpu","dataSources":[{"datasetId":2882322,"sourceId":4969612,"sourceType":"datasetVersion"}],"dockerImageVersionId":30747,"isGpuEnabled":true,"isInternetEnabled":true,"language":"python","sourceType":"notebook"},"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.9.13"}},"nbformat":4,"nbformat_minor":4}
|
dog_emotion_model.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:80a013e6847088db2d44c3dfbc9ac5391e5932f612e30a8b330662c302d8e9ea
|
| 3 |
+
size 94389082
|
model.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from torch import nn
|
| 3 |
+
from torchvision import models
|
| 4 |
+
|
| 5 |
+
class DogEmotionResNet(nn.Module):
|
| 6 |
+
def __init__(self, num_classes, weights=None):
|
| 7 |
+
super().__init__()
|
| 8 |
+
|
| 9 |
+
self.resnet = models.resnet50(weights=weights)
|
| 10 |
+
for param in self.resnet.parameters():
|
| 11 |
+
param.requires_grad = False
|
| 12 |
+
|
| 13 |
+
in_features = self.resnet.fc.in_features
|
| 14 |
+
self.resnet.fc = nn.Linear(in_features, num_classes)
|
| 15 |
+
|
| 16 |
+
def forward(self, x):
|
| 17 |
+
return self.resnet(x)
|
readme.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dog Emotion Classifier 🐶🎭
|
| 2 |
+
|
| 3 |
+
This project uses PyTorch for model development and Gradio for the user interface to classify the emotions of dogs from images. The classifier identifies emotions as happy, sad, angry, or relaxed.
|
| 4 |
+
|
| 5 |
+
## Features
|
| 6 |
+
|
| 7 |
+
- **Model Architecture:** Utilizes a pre-trained ResNet50 model.
|
| 8 |
+
- **User Interface:** Interactive web interface powered by Gradio.
|
| 9 |
+
- **Training and Evaluation:** Scripts for training and evaluating the model on custom datasets.
|
| 10 |
+
|
| 11 |
+
## Dataset
|
| 12 |
+
|
| 13 |
+
- The model was trained on the https://www.kaggle.com/datasets/danielshanbalico/dog-emotion dataset.
|
| 14 |
+
|
| 15 |
+
## Installation
|
| 16 |
+
|
| 17 |
+
### Prerequisites
|
| 18 |
+
|
| 19 |
+
- Python 3.7 or higher
|
| 20 |
+
- PyTorch
|
| 21 |
+
- Gradio
|
| 22 |
+
- Other dependencies listed in `requirements.txt`
|
| 23 |
+
|
| 24 |
+
### Clone the Repository
|
| 25 |
+
|
| 26 |
+
```bash
|
| 27 |
+
git clone https://github.com/Yuval728/dog-emotion-classifier.git
|
| 28 |
+
cd dog-emotion-classifier
|
requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
numpy
|
| 2 |
+
torch
|
| 3 |
+
torchvision
|
| 4 |
+
Pillow
|
| 5 |
+
gradio
|
| 6 |
+
pandas
|
| 7 |
+
scikit-learn
|
| 8 |
+
matplotlib
|
| 9 |
+
tqdm
|
test.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from torch.utils.data import DataLoader
|
| 3 |
+
from sklearn.metrics import classification_report
|
| 4 |
+
|
| 5 |
+
from dataset import get_datasets
|
| 6 |
+
from model import DogEmotionResNet
|
| 7 |
+
from utils import predict
|
| 8 |
+
|
| 9 |
+
device = 'cuda' if torch.cuda.is_available() else 'cpu'
|
| 10 |
+
data_dir = 'data/'
|
| 11 |
+
|
| 12 |
+
_, test_dataset, classes = get_datasets(data_dir)
|
| 13 |
+
|
| 14 |
+
test_loader = DataLoader(test_dataset, batch_size=128, shuffle=False)
|
| 15 |
+
|
| 16 |
+
model = DogEmotionResNet(num_classes=len(classes))
|
| 17 |
+
model.load_state_dict(torch.load('dog_emotion_model.pth'))
|
| 18 |
+
model.to(device)
|
| 19 |
+
|
| 20 |
+
predictions = predict(model, test_loader, device)
|
| 21 |
+
|
| 22 |
+
print(classification_report([label for _, label in test_dataset], predictions))
|
train.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from torch import nn, optim
|
| 3 |
+
from torch.utils.data import DataLoader
|
| 4 |
+
from tqdm.auto import tqdm
|
| 5 |
+
import matplotlib.pyplot as plt
|
| 6 |
+
from torchvision import models
|
| 7 |
+
from dataset import get_datasets
|
| 8 |
+
from model import DogEmotionResNet
|
| 9 |
+
from utils import train, validate
|
| 10 |
+
|
| 11 |
+
device = 'cuda' if torch.cuda.is_available() else 'cpu'
|
| 12 |
+
data_dir = 'data/'
|
| 13 |
+
|
| 14 |
+
train_dataset, test_dataset, classes = get_datasets(data_dir)
|
| 15 |
+
|
| 16 |
+
train_loader = DataLoader(train_dataset, batch_size=128, shuffle=True)
|
| 17 |
+
test_loader = DataLoader(test_dataset, batch_size=128, shuffle=False)
|
| 18 |
+
|
| 19 |
+
with open('classes.txt', 'w') as f:
|
| 20 |
+
for item in classes:
|
| 21 |
+
f.write("%s\n" % item)
|
| 22 |
+
|
| 23 |
+
torch.manual_seed(42)
|
| 24 |
+
torch.cuda.manual_seed(42)
|
| 25 |
+
|
| 26 |
+
resnet_weights = models.ResNet50_Weights.DEFAULT
|
| 27 |
+
model = DogEmotionResNet(num_classes=len(classes), weights=resnet_weights)
|
| 28 |
+
model.to(device)
|
| 29 |
+
|
| 30 |
+
EPOCHS = 30
|
| 31 |
+
criterion = nn.CrossEntropyLoss()
|
| 32 |
+
optimizer = optim.Adam(model.parameters(), lr=0.002)
|
| 33 |
+
|
| 34 |
+
train_losses = []
|
| 35 |
+
train_accuracies = []
|
| 36 |
+
test_losses = []
|
| 37 |
+
test_accuracies = []
|
| 38 |
+
|
| 39 |
+
for epoch in tqdm(range(EPOCHS)):
|
| 40 |
+
train_loss, train_accuracy = train(model, train_loader, criterion, optimizer, device)
|
| 41 |
+
test_loss, test_accuracy = validate(model, test_loader, criterion, device)
|
| 42 |
+
|
| 43 |
+
train_losses.append(train_loss)
|
| 44 |
+
train_accuracies.append(train_accuracy)
|
| 45 |
+
test_losses.append(test_loss)
|
| 46 |
+
test_accuracies.append(test_accuracy)
|
| 47 |
+
|
| 48 |
+
print(f'Epoch: {epoch+1}/{EPOCHS}, Train Loss: {train_loss:.4f}, Train Accuracy: {train_accuracy:.4f}, Test Loss: {test_loss:.4f}, Test Accuracy: {test_accuracy:.4f}')
|
| 49 |
+
|
| 50 |
+
plt.figure(figsize=(12, 6))
|
| 51 |
+
plt.subplot(1, 2, 1)
|
| 52 |
+
plt.plot(train_losses, label='train loss')
|
| 53 |
+
plt.plot(test_losses, label='test loss')
|
| 54 |
+
plt.xlabel('Epochs')
|
| 55 |
+
plt.ylabel('Loss')
|
| 56 |
+
plt.legend()
|
| 57 |
+
|
| 58 |
+
plt.subplot(1, 2, 2)
|
| 59 |
+
plt.plot(train_accuracies, label='train accuracy')
|
| 60 |
+
plt.plot(test_accuracies, label='test accuracy')
|
| 61 |
+
plt.xlabel('Epochs')
|
| 62 |
+
plt.ylabel('Accuracy')
|
| 63 |
+
plt.legend()
|
| 64 |
+
plt.show()
|
| 65 |
+
|
| 66 |
+
torch.save(model.state_dict(), 'dog_emotion_model.pth')
|
utils.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
|
| 3 |
+
def train(model, train_loader, criterion, optimizer, device):
|
| 4 |
+
model.train()
|
| 5 |
+
running_loss = 0.0
|
| 6 |
+
correct_predictions = 0
|
| 7 |
+
total_predictions = 0
|
| 8 |
+
|
| 9 |
+
for inputs, labels in train_loader:
|
| 10 |
+
inputs, labels = inputs.to(device), labels.to(device)
|
| 11 |
+
|
| 12 |
+
optimizer.zero_grad()
|
| 13 |
+
|
| 14 |
+
outputs = model(inputs)
|
| 15 |
+
loss = criterion(outputs, labels)
|
| 16 |
+
loss.backward()
|
| 17 |
+
optimizer.step()
|
| 18 |
+
|
| 19 |
+
running_loss += loss.item()
|
| 20 |
+
predicted_labels = outputs.argmax(dim=1)
|
| 21 |
+
correct_predictions += (predicted_labels == labels).sum().item()
|
| 22 |
+
total_predictions += labels.size(0)
|
| 23 |
+
|
| 24 |
+
train_loss = running_loss / len(train_loader)
|
| 25 |
+
train_accuracy = correct_predictions / total_predictions
|
| 26 |
+
|
| 27 |
+
return train_loss, train_accuracy
|
| 28 |
+
|
| 29 |
+
def validate(model, test_loader, criterion, device):
|
| 30 |
+
model.eval()
|
| 31 |
+
running_loss = 0.0
|
| 32 |
+
correct_predictions = 0
|
| 33 |
+
total_predictions = 0
|
| 34 |
+
|
| 35 |
+
with torch.inference_mode():
|
| 36 |
+
for inputs, labels in test_loader:
|
| 37 |
+
inputs, labels = inputs.to(device), labels.to(device)
|
| 38 |
+
|
| 39 |
+
outputs = model(inputs)
|
| 40 |
+
loss = criterion(outputs, labels)
|
| 41 |
+
|
| 42 |
+
running_loss += loss.item()
|
| 43 |
+
predicted_labels = outputs.argmax(dim=1)
|
| 44 |
+
correct_predictions += (predicted_labels == labels).sum().item()
|
| 45 |
+
total_predictions += labels.size(0)
|
| 46 |
+
|
| 47 |
+
test_loss = running_loss / len(test_loader)
|
| 48 |
+
test_accuracy = correct_predictions / total_predictions
|
| 49 |
+
|
| 50 |
+
return test_loss, test_accuracy
|
| 51 |
+
|
| 52 |
+
def predict(model, test_loader, device):
|
| 53 |
+
model.eval()
|
| 54 |
+
predictions = []
|
| 55 |
+
|
| 56 |
+
with torch.inference_mode():
|
| 57 |
+
for inputs, _ in test_loader:
|
| 58 |
+
inputs = inputs.to(device)
|
| 59 |
+
outputs = model(inputs)
|
| 60 |
+
predicted_labels = outputs.argmax(dim=1)
|
| 61 |
+
predictions.extend(predicted_labels.cpu().numpy())
|
| 62 |
+
|
| 63 |
+
return predictions
|