commit ccad4e85a938b391b58aa06d74a1f48f3d684ccd Author: Jens Ahrensfeld Date: Fri Dec 19 10:11:58 2025 +0100 Initial commit diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/cnn_image_classification.iml b/.idea/cnn_image_classification.iml new file mode 100644 index 0000000..3270377 --- /dev/null +++ b/.idea/cnn_image_classification.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..6e3af74 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..a914d00 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a2adac4 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Tutorial +## Python CNN image classification from with jupyter, torch, torchvision and pillow +from https://youtu.be/CtzfbUwrYGI?si=09iwSO4S5DtaAl4G + +# Notes +## Register venv at jupyter lab +pip3 install ipykernel +python3 -m ipykernel install --user --name=.venv + diff --git a/item1.jpg b/item1.jpg new file mode 100755 index 0000000..5f6a966 Binary files /dev/null and b/item1.jpg differ diff --git a/item2.jpg b/item2.jpg new file mode 100755 index 0000000..71f307f Binary files /dev/null and b/item2.jpg differ diff --git a/item3.jpg b/item3.jpg new file mode 100755 index 0000000..bb232aa Binary files /dev/null and b/item3.jpg differ diff --git a/nn_image_classification.ipynb b/nn_image_classification.ipynb new file mode 100644 index 0000000..bc83125 --- /dev/null +++ b/nn_image_classification.ipynb @@ -0,0 +1,349 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 17, + "id": "2e9496e9-ec07-445f-b29d-fc44aabb377d", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from PIL import Image\n", + "import torch\n", + "import torch.nn as nn\n", + "import torch.nn.functional as F\n", + "import torch.optim as optim\n", + "import torchvision\n", + "import torchvision.transforms as transforms" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "9b17757b-76c5-4562-a2be-e3a50201c9ee", + "metadata": {}, + "outputs": [], + "source": [ + "transform = transforms.Compose([transforms.ToTensor(), import numpy as np\n", + "from PIL import Image\n", + "import torch\n", + "import torch.nn as nn\n", + "import torch.nn.functional as F\n", + "import torch.optim as opim\n", + "import torchvision\n", + "import torchvision.transform as transform\n", + "\n", + "]) " + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "5b898116-e9a8-429d-8491-67db5a65d358", + "metadata": {}, + "outputs": [], + "source": [ + "train_data = torchvision.datasets.CIFAR10(root='./data', train=True, transform=transform, download=True)\n", + "test_data = torchvision.datasets.CIFAR10(root='./data', train=False, transform=transform, download=True)\n", + "\n", + "train_loader = torch.utils.data.DataLoader(train_data, batch_size=32, shuffle=True, num_workers=2)\n", + "test_loader = torch.utils.data.DataLoader(test_data, batch_size=32, shuffle=True, num_workers=2)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "49712fae-dfe6-4b6f-92ea-2e97a965c15c", + "metadata": {}, + "outputs": [], + "source": [ + "image, label = train_data[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "d783e1a5-3410-4992-88a6-230dd2ec85dd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([3, 32, 32])" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "image.size()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "8ef21399-f2f2-46f4-8268-f60f36bbb533", + "metadata": {}, + "outputs": [], + "source": [ + "class_names = ['plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "192ff676-c4b8-4611-9338-ec9b35a502d5", + "metadata": {}, + "outputs": [], + "source": [ + "class NeuralNet(nn.Module):\n", + " def __init__(self):\n", + " super().__init__()\n", + " self.conv1 = nn.Conv2d(3, 12, 5) # (12, 28, 28)\n", + " self.pool = nn.MaxPool2d(2, 2) # (12, 14, 14)\n", + " self.conv2 = nn.Conv2d(12, 24, 5) # (24, 10, 10) -> (24, 5, 5) -> Flatten (25 * 5 * 5)\n", + " self.fc1 = nn.Linear(24 * 5 * 5, 120)\n", + " self.fc2 = nn.Linear(120, 84)\n", + " self.fc3 = nn.Linear(84, 10)\n", + "\n", + " def forward(self, x):\n", + " x = self.pool(F.relu(self.conv1(x)))\n", + " x = self.pool(F.relu(self.conv2(x)))\n", + " x = torch.flatten(x, 1)\n", + " x = F.relu(self.fc1(x))\n", + " x = F.relu(self.fc2(x))\n", + " x = self.fc3(x)\n", + " return x" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "f48dd819-9196-4aa5-92c4-ddcc644c6aa9", + "metadata": {}, + "outputs": [], + "source": [ + "net = NeuralNet()\n", + "loss_function = nn.CrossEntropyLoss()\n", + "optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "3a91ce0c-e71d-4bfb-b945-47edfc8ca8d6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Training epoch 0...\n", + "Loss: 2.1758\n", + "Training epoch 1...\n", + "Loss: 1.7324\n", + "Training epoch 2...\n", + "Loss: 1.5096\n", + "Training epoch 3...\n", + "Loss: 1.3996\n", + "Training epoch 4...\n", + "Loss: 1.3065\n", + "Training epoch 5...\n", + "Loss: 1.2286\n", + "Training epoch 6...\n", + "Loss: 1.1649\n", + "Training epoch 7...\n", + "Loss: 1.1101\n", + "Training epoch 8...\n", + "Loss: 1.0568\n", + "Training epoch 9...\n", + "Loss: 1.0140\n", + "Training epoch 10...\n", + "Loss: 0.9719\n", + "Training epoch 11...\n", + "Loss: 0.9321\n", + "Training epoch 12...\n", + "Loss: 0.8969\n", + "Training epoch 13...\n", + "Loss: 0.8649\n", + "Training epoch 14...\n", + "Loss: 0.8326\n", + "Training epoch 15...\n", + "Loss: 0.8014\n", + "Training epoch 16...\n", + "Loss: 0.7764\n", + "Training epoch 17...\n", + "Loss: 0.7449\n", + "Training epoch 18...\n", + "Loss: 0.7199\n", + "Training epoch 19...\n", + "Loss: 0.6991\n", + "Training epoch 20...\n", + "Loss: 0.6700\n", + "Training epoch 21...\n", + "Loss: 0.6438\n", + "Training epoch 22...\n", + "Loss: 0.6243\n", + "Training epoch 23...\n", + "Loss: 0.6051\n", + "Training epoch 24...\n", + "Loss: 0.5802\n", + "Training epoch 25...\n", + "Loss: 0.5609\n", + "Training epoch 26...\n", + "Loss: 0.5381\n", + "Training epoch 27...\n", + "Loss: 0.5162\n", + "Training epoch 28...\n", + "Loss: 0.4949\n", + "Training epoch 29...\n", + "Loss: 0.4752\n" + ] + } + ], + "source": [ + "for epoch in range(30):\n", + " print(f'Training epoch {epoch}...')\n", + "\n", + " running_loss = 0.0\n", + "\n", + " for i, data in enumerate(train_loader):\n", + " inputs, labels = data\n", + " optimizer.zero_grad()\n", + " outputs = net(inputs)\n", + " loss = loss_function(outputs, labels)\n", + " loss.backward()\n", + " optimizer.step()\n", + " \n", + " running_loss += loss.item()\n", + "\n", + " print(f'Loss: {running_loss / len(train_loader):0.4f}')" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "aa5b2497-90a7-44a9-9985-427b920945eb", + "metadata": {}, + "outputs": [], + "source": [ + "torch.save(net.state_dict(), 'trained_net.pth')" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "ff62419f-ead5-4244-9a5e-a21d568bdca8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "net = NeuralNet()\n", + "net.load_state_dict(torch.load('trained_net.pth'))" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "eb1d4385-d4da-454f-9d8c-d844aa7b24df", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Accuracy: 68.23%\n" + ] + } + ], + "source": [ + "correct = 0\n", + "total = 0\n", + "net.eval()\n", + "with torch.no_grad():\n", + " for data in test_loader:\n", + " images, labels = data\n", + " outputs = net(images)\n", + " _, predicted = torch.max(outputs, 1)\n", + " total += labels.size(0)\n", + " correct += (predicted == labels).sum().item()\n", + "\n", + "accuracy = 100 * correct / total\n", + "print(f'Accuracy: {accuracy}%')" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "1a4f098f-b1ea-46b7-a7d6-769f80292f5f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Prediction: cat\n", + "Prediction: deer\n", + "Prediction: plane\n" + ] + } + ], + "source": [ + "new_transform = transforms.Compose([\n", + " transforms.Resize((32, 32)), \n", + " transforms.ToTensor(),\n", + " transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))\n", + "])\n", + "\n", + "def load_image(image_path):\n", + " image = Image.open(image_path)\n", + " image = new_transform(image)\n", + " image = image.unsqueeze(0)\n", + " return image\n", + "\n", + "image_paths = ['item1.jpg', 'item2.jpg', 'item3.jpg']\n", + "images = [load_image(img) for img in image_paths]\n", + "\n", + "net.eval()\n", + "with torch.no_grad():\n", + " for image in images:\n", + " output = net(image)\n", + " _, predicted = torch.max(output, 1)\n", + " print(f'Prediction: {class_names[predicted.item()]}')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..07eace4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +requires = [ + "setuptools>=61.0" +] +build-backend = "setuptools.build_meta" + +[project] +name = "pyCnnImageClassifier" +description = "Python CNN image classification from with jupyter, torch, torchvision and pillow" +dynamic = ["version"] +requires-python = ">=3.12" +authors = [ + { name = "Jens Ahrensfeld" } +] +dependencies = [ + "pillow", + "torch", + "torchvision", + "jupyterlab" +] +readme = "README.md"