Created
June 24, 2026 22:03
-
-
Save dnkm/247d5e3d567e660e50b5f1ed400b3943 to your computer and use it in GitHub Desktop.
AI powered Air Quality Prediction System
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "cells": [ | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "view-in-github", | |
| "colab_type": "text" | |
| }, | |
| "source": [ | |
| "<a href=\"https://colab.research.google.com/gist/dnkm/247d5e3d567e660e50b5f1ed400b3943/ai-powered-air-quality-prediction-system.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# **AI Powered Air Quality Prediction System**" | |
| ], | |
| "metadata": { | |
| "id": "6N7XTwulh_xI" | |
| } | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "Ifzv2FxT2XnK" | |
| }, | |
| "source": [ | |
| "**Joowon Kang**" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "**Instructions to load data**\n", | |
| "\n", | |
| "1. Download the dataset zip file\n", | |
| "2. Upload the zip file onto your Google Drive\n", | |
| "3. Run the code below!" | |
| ], | |
| "metadata": { | |
| "id": "o-mdb7MAirb6" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "pq4N5WKSy4Ex" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "import os\n", | |
| "import cv2\n", | |
| "import math\n", | |
| "import torch\n", | |
| "import numpy as np\n", | |
| "import pandas as pd\n", | |
| "import torch.nn as nn\n", | |
| "import matplotlib.pyplot as plt" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "mwhH3T-eswTH" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "from PIL import Image\n", | |
| "from google.colab import drive\n", | |
| "from torchvision import models\n", | |
| "from torchvision import transforms\n", | |
| "from collections import OrderedDict\n", | |
| "from torch.utils.data import Dataset, DataLoader" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/", | |
| "height": 293 | |
| }, | |
| "id": "4C5jzcnqtNMI", | |
| "outputId": "3f14fec9-b64f-4cf5-8377-8a8525acc9b5" | |
| }, | |
| "outputs": [ | |
| { | |
| "output_type": "error", | |
| "ename": "MessageError", | |
| "evalue": "Error: credential propagation was unsuccessful", | |
| "traceback": [ | |
| "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", | |
| "\u001b[0;31mMessageError\u001b[0m Traceback (most recent call last)", | |
| "\u001b[0;32m/tmp/ipykernel_12805/2985678062.py\u001b[0m in \u001b[0;36m<cell line: 0>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mdrive\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmount\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'/content/drive'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", | |
| "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/colab/drive.py\u001b[0m in \u001b[0;36mmount\u001b[0;34m(mountpoint, force_remount, timeout_ms, readonly)\u001b[0m\n\u001b[1;32m 95\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mmount\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmountpoint\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mforce_remount\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtimeout_ms\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m120000\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mreadonly\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 96\u001b[0m \u001b[0;34m\"\"\"Mount your Google Drive at the specified mountpoint path.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 97\u001b[0;31m return _mount(\n\u001b[0m\u001b[1;32m 98\u001b[0m \u001b[0mmountpoint\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 99\u001b[0m \u001b[0mforce_remount\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mforce_remount\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", | |
| "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/colab/drive.py\u001b[0m in \u001b[0;36m_mount\u001b[0;34m(mountpoint, force_remount, timeout_ms, ephemeral, readonly)\u001b[0m\n\u001b[1;32m 132\u001b[0m )\n\u001b[1;32m 133\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mephemeral\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 134\u001b[0;31m _message.blocking_request(\n\u001b[0m\u001b[1;32m 135\u001b[0m \u001b[0;34m'request_auth'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 136\u001b[0m \u001b[0mrequest\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m{\u001b[0m\u001b[0;34m'authType'\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'dfs_ephemeral'\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", | |
| "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/colab/_message.py\u001b[0m in \u001b[0;36mblocking_request\u001b[0;34m(request_type, request, timeout_sec, parent)\u001b[0m\n\u001b[1;32m 174\u001b[0m \u001b[0mrequest_type\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrequest\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mparent\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mparent\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mexpect_reply\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 175\u001b[0m )\n\u001b[0;32m--> 176\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mread_reply_from_input\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrequest_id\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtimeout_sec\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", | |
| "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/google/colab/_message.py\u001b[0m in \u001b[0;36mread_reply_from_input\u001b[0;34m(message_id, timeout_sec)\u001b[0m\n\u001b[1;32m 101\u001b[0m ):\n\u001b[1;32m 102\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;34m'error'\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mreply\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 103\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mMessageError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mreply\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'error'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 104\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mreply\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'data'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 105\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", | |
| "\u001b[0;31mMessageError\u001b[0m: Error: credential propagation was unsuccessful" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "drive.mount('/content/drive')" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "XWx9QylCPpVe" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "!unzip -q /content/drive/MyDrive/Dataset.zip -d /content/DatasetFolder" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "kHTn3t5GtWUo" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "IMAGE_DIR = \"/content/DatasetFolder/Dataset/Air_project\"\n", | |
| "SHEET_PATH = \"/content/DatasetFolder/Dataset/Data_Recording_Sheet.xlsx\"" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "Sljxb5N17VZh" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "sheet = pd.read_excel(SHEET_PATH)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "**Splitting Data into Train, Test, and Overfitting Sets**" | |
| ], | |
| "metadata": { | |
| "id": "NkSBxjHyng9n" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "2TlSWItnMNnJ" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "# import pandas as pd\n", | |
| "# from sklearn.model_selection import train_test_split\n", | |
| "# import os\n", | |
| "\n", | |
| "# # Define the file path and desired ratios\n", | |
| "# # file_path = SHEET_PATH\n", | |
| "# train_ratio = 0.8\n", | |
| "# test_ratio = 0.1\n", | |
| "# validation_ratio = 0.1\n", | |
| "# seed = 42 # for reproducibility\n", | |
| "\n", | |
| "# # 1. Read the Excel file\n", | |
| "# df = pd.read_excel(SHEET_PATH)\n", | |
| "\n", | |
| "# sampled_df = df.sample(n=200, random_state=None)\n", | |
| "\n", | |
| "# output_dir = '/content/drive/MyDrive/data_splits'\n", | |
| "# os.makedirs(output_dir, exist_ok=True)\n", | |
| "# sampled_df.to_excel(os.path.join(output_dir, 'overfit_set.xlsx'), index=False)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "pBeGN-j7yWGY" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "# import pandas as pd\n", | |
| "# from sklearn.model_selection import train_test_split\n", | |
| "# import os\n", | |
| "\n", | |
| "# # Define the file path and desired ratios\n", | |
| "# # file_path = SHEET_PATH\n", | |
| "# train_ratio = 0.8\n", | |
| "# test_ratio = 0.1\n", | |
| "# validation_ratio = 0.1\n", | |
| "# seed = 42 # for reproducibility\n", | |
| "\n", | |
| "# # 1. Read the Excel file\n", | |
| "# df = pd.read_excel(SHEET_PATH)\n", | |
| "\n", | |
| "# # 2. Split into main (train+val) and test sets (e.g., 90% main, 10% test)\n", | |
| "# # The test_size for the first split is the final desired test ratio\n", | |
| "# df_main, df_test = train_test_split(df, test_size=test_ratio, random_state=seed)\n", | |
| "\n", | |
| "# # 3. Split the main set into train and validation sets\n", | |
| "# # The validation set size must be adjusted relative to the remaining main set\n", | |
| "# # New validation ratio = actual_validation_ratio / (actual_train_ratio + actual_validation_ratio)\n", | |
| "# val_ratio_adjusted = validation_ratio / (train_ratio + validation_ratio)\n", | |
| "\n", | |
| "# df_train, df_val = train_test_split(df_main, test_size=val_ratio_adjusted, random_state=seed)\n", | |
| "\n", | |
| "# # 4. Save the splits to new Excel files\n", | |
| "# output_dir = '/content/drive/MyDrive/data_splits'\n", | |
| "# os.makedirs(output_dir, exist_ok=True)\n", | |
| "\n", | |
| "# df_train.to_excel(os.path.join(output_dir, 'train_set.xlsx'), index=False)\n", | |
| "# df_val.to_excel(os.path.join(output_dir, 'val_set.xlsx'), index=False)\n", | |
| "# df_test.to_excel(os.path.join(output_dir, 'test_set.xlsx'), index=False)\n", | |
| "\n", | |
| "# print(f\"\\nData successfully split and saved in the '{output_dir}' directory.\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "**Converting Air Quality Values into Air Quality Labels using US EPA Offical Conversion Tool: https://1drv.ms/w/c/ba0921185736d6f8/EfW2J4_I9KFBjxfDGaXzo5EBdlLlNNhN_tXeTBXPUpAMGQ?e=lEedJ7**" | |
| ], | |
| "metadata": { | |
| "id": "57cdO_R-nuDj" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "eybYimTtyrKu" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "label_columns = [\"PM2.5 µg/m³\", \"PM10 µg/m³\", \"CO ppm\", \" NO₂ ppm\", \" SO₂ ppm\", \"O₃ ppm\"]\n", | |
| "air_measures = {row[\"Name\"]: row[label_columns].tolist() for _, row in sheet.iterrows()}" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "rxTbkta68hfK" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "BREAKPOINTS = {\n", | |
| " \"PM25\": [ (0.0, 9.0, 0, 50),\n", | |
| " (9.1, 35.4, 51, 100),\n", | |
| " (35.5, 55.4, 101, 150),\n", | |
| " (55.5, 125.4, 151, 200),\n", | |
| " (125.5, 225.4, 201, 300),\n", | |
| " (225.5, math.inf, 301, math.inf), ],\n", | |
| " \"PM10\": [ (0, 54, 0, 50),\n", | |
| " (55, 154, 51, 100),\n", | |
| " (155, 254, 101, 150),\n", | |
| " (255, 354, 151, 200),\n", | |
| " (355, 424, 201, 300),\n", | |
| " (425, math.inf, 301, math.inf), ],\n", | |
| " \"O3\": [ (0.000, 0.054, 0, 50),\n", | |
| " (0.055, 0.070, 51, 100),\n", | |
| " (0.071, 0.085, 101, 150),\n", | |
| " (0.086, 0.105, 151, 200),\n", | |
| " (0.106, 0.200, 201, 300),\n", | |
| " (0.201, math.inf, 301, math.inf)],\n", | |
| " \"CO\": [ (0.0, 4.4, 0, 50),\n", | |
| " (4.5, 9.4, 51, 100),\n", | |
| " (9.5, 12.4, 101, 150),\n", | |
| " (12.5, 15.4, 151, 200),\n", | |
| " (15.5, 30.4, 201, 300),\n", | |
| " (30.5, math.inf, 301, math.inf), ],\n", | |
| " \"SO2\": [ (0, 35, 0, 50),\n", | |
| " (36, 75, 51, 100),\n", | |
| " (76, 185, 101, 150),\n", | |
| " (186, 304, 151, 200),\n", | |
| " (305, 604, 201, 300),\n", | |
| " (605, math.inf, 301, math.inf), ],\n", | |
| " \"NO2\": [ (0, 53, 0, 50),\n", | |
| " (54, 100, 51, 100),\n", | |
| " (101, 360, 101, 150),\n", | |
| " (361, 649, 151, 200),\n", | |
| " (650, 1249, 201, 300),\n", | |
| " (1250, math.inf, 301, math.inf), ], }" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "**PyTorch Dataloader for Dataset**" | |
| ], | |
| "metadata": { | |
| "id": "oWHtW5QMo46u" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "YWMbCYrhy5iW" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "class AirQualityDataset(Dataset):\n", | |
| " def __init__(self, folder, split, transform=None):\n", | |
| " file_path = f'/content/DatasetFolder/Dataset/data_splits/{split}_set.xlsx'\n", | |
| " sheet = pd.read_excel(file_path)\n", | |
| " self.folder = folder\n", | |
| " self.transform = transform\n", | |
| " self.label_columns = [\"PM2.5 µg/m³\", \"PM10 µg/m³\", \"CO ppm\",\" NO₂ ppm\", \" SO₂ ppm\", \"O₃ ppm\"]\n", | |
| " self.data = []\n", | |
| "\n", | |
| " for _, row in sheet.iterrows():\n", | |
| " name = row[\"Name\"]\n", | |
| " img_path = os.path.join(folder, f\"{name}.jpg\")\n", | |
| " if os.path.exists(img_path):\n", | |
| " measures = row[self.label_columns].tolist()\n", | |
| " self.data.append((name, measures))\n", | |
| "\n", | |
| " print(f\"Loaded {len(self.data)} valid samples\")\n", | |
| " def __len__(self):\n", | |
| " return len(self.data)\n", | |
| "\n", | |
| " def __getitem__(self, idx):\n", | |
| " name, air_measure = self.data[idx]\n", | |
| " img_path = os.path.join(self.folder, f\"{name}.jpg\")\n", | |
| " img = Image.open(img_path).convert(\"RGB\")\n", | |
| " if self.transform:\n", | |
| " img = self.transform(img)\n", | |
| "\n", | |
| " label = self.convertAQI(air_measure)\n", | |
| " return img, label\n", | |
| "\n", | |
| " def convertAQI (self, air_measure):\n", | |
| "\n", | |
| " def truncate(x, n):\n", | |
| " factor = 10 ** n\n", | |
| " return math.trunc(x * factor) / factor\n", | |
| "\n", | |
| " def AQIcategory(aqi):\n", | |
| " if aqi <= 50: return 0\n", | |
| " if aqi <= 100: return 1\n", | |
| " return 2\n", | |
| "\n", | |
| " PM25 = truncate(air_measure[0], 1)\n", | |
| " PM10 = round(air_measure[1])\n", | |
| " CO = truncate(air_measure[2], 1)\n", | |
| " NO2 = round(air_measure[3])\n", | |
| " SO2 = round(air_measure[4])\n", | |
| " O3 = truncate(air_measure[5], 3)\n", | |
| "\n", | |
| " AQI = []\n", | |
| " pollutants = {\"PM25\": PM25, \"PM10\": PM10, \"CO\": CO, \"NO2\": NO2, \"SO2\": SO2, \"O3\": O3}\n", | |
| "\n", | |
| " for key, value in pollutants.items():\n", | |
| " AQIvalue = self.pollutantAQI(key, value)\n", | |
| " AQI.append(AQIvalue)\n", | |
| "\n", | |
| " imageAQI = round(max(AQI))\n", | |
| " return AQIcategory(imageAQI)\n", | |
| "\n", | |
| " def computeAQI (self, cp, bplo, bphi, ilo, ihi):\n", | |
| " return((((ihi - ilo)/(bphi - bplo))*(cp - bplo)) + ilo)\n", | |
| "\n", | |
| " def pollutantAQI(self, pollutant, cp):\n", | |
| " for bplo, bphi, ilo, ihi in BREAKPOINTS[pollutant]:\n", | |
| " if bplo <= cp <= bphi:\n", | |
| " results = self.computeAQI(cp, bplo, bphi, ilo, ihi)\n", | |
| " break\n", | |
| " return results" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "Bqja7ztGy7pb" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "#Transforms for Training and Testing Splits\n", | |
| "train_transform = transforms.Compose([\n", | |
| " transforms.RandomHorizontalFlip(p=0.5),\n", | |
| " transforms.Resize((224, 224)),\n", | |
| " transforms.ToTensor(),\n", | |
| " transforms.Normalize([0.485, 0.456, 0.406],\n", | |
| " [0.229, 0.224, 0.225])\n", | |
| "])\n", | |
| "\n", | |
| "test_transform = transforms.Compose([\n", | |
| " transforms.Resize((224, 224)),\n", | |
| " transforms.ToTensor(),\n", | |
| " transforms.Normalize([0.485, 0.456, 0.406],\n", | |
| " [0.229, 0.224, 0.225])\n", | |
| "])" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "-QtR3wUH0nR1" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "#Full Training and Testing Splits\n", | |
| "train_dataset = AirQualityDataset(IMAGE_DIR, 'train', train_transform)\n", | |
| "train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)\n", | |
| "\n", | |
| "test_dataset = AirQualityDataset(IMAGE_DIR, 'test', test_transform)\n", | |
| "test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "rBZLL1h1M3mh" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "#Overfitting Training and Testing Splits\n", | |
| "overfit_train_dataset = AirQualityDataset(IMAGE_DIR, 'overfit', train_transform)\n", | |
| "overfit_train_loader = DataLoader(overfit_train_dataset, batch_size=32, shuffle=True)\n", | |
| "\n", | |
| "overfit_test_dataset = AirQualityDataset(IMAGE_DIR, 'overfit', test_transform)\n", | |
| "overfit_test_loader = DataLoader(overfit_test_dataset, batch_size=32, shuffle=False)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "yPnlpLSZBtai" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "class VGG19Classifier(nn.Module):\n", | |
| " def __init__(self, num_classes=3, freeze_backbone=True, hidden_dim=512):\n", | |
| " super(VGG19Classifier, self).__init__()\n", | |
| "\n", | |
| " # Load pretrained VGG19\n", | |
| " vgg19 = models.vgg19(pretrained=True)\n", | |
| "\n", | |
| " if freeze_backbone:\n", | |
| " for param in vgg19.features.parameters():\n", | |
| " param.requires_grad = False\n", | |
| "\n", | |
| " self.features = vgg19.features\n", | |
| " self.avgpool = vgg19.avgpool\n", | |
| " self.num_features = 25088\n", | |
| "\n", | |
| " self.classifier = nn.Sequential(OrderedDict([\n", | |
| " ('fc1', nn.Linear(self.num_features, hidden_dim)),\n", | |
| " ('relu', nn.ReLU()),\n", | |
| " ('dropout1', nn.Dropout(0.5)),\n", | |
| " ('fc2', nn.Linear(hidden_dim, num_classes))\n", | |
| " ]))\n", | |
| "\n", | |
| " def forward(self, x):\n", | |
| " x = self.features(x)\n", | |
| " x = self.avgpool(x)\n", | |
| " x = torch.flatten(x, 1)\n", | |
| " x = self.classifier(x)\n", | |
| " return x" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "kVonLOQxvKWA" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "import torch\n", | |
| "import torch.nn as nn\n", | |
| "from torchvision import models\n", | |
| "from collections import OrderedDict\n", | |
| "\n", | |
| "\n", | |
| "class EfficientNetB0Classifier(nn.Module):\n", | |
| " def __init__(self, num_classes=4, freeze_backbone=True, hidden_dim=512):\n", | |
| " super().__init__()\n", | |
| "\n", | |
| " # Load pretrained EfficientNet-B0\n", | |
| " effnet = models.efficientnet_b0(pretrained=True)\n", | |
| "\n", | |
| " if freeze_backbone:\n", | |
| " for param in effnet.features.parameters():\n", | |
| " param.requires_grad = False\n", | |
| "\n", | |
| " self.features = effnet.features\n", | |
| " self.avgpool = effnet.avgpool\n", | |
| "\n", | |
| " self.num_features = effnet.classifier[1].in_features\n", | |
| "\n", | |
| " # Custom classifier head\n", | |
| " self.classifier = nn.Sequential(OrderedDict([\n", | |
| " (\"fc1\", nn.Linear(self.num_features, hidden_dim)),\n", | |
| " (\"bn1\", nn.BatchNorm1d(hidden_dim)),\n", | |
| " (\"relu\", nn.ReLU(inplace=True)),\n", | |
| " (\"dropout\", nn.Dropout(0.5)),\n", | |
| " (\"fc2\", nn.Linear(hidden_dim, num_classes)),\n", | |
| " ]))\n", | |
| "\n", | |
| "\n", | |
| " def forward(self, x):\n", | |
| " x = self.features(x)\n", | |
| " x = self.avgpool(x)\n", | |
| " x = torch.flatten(x, 1)\n", | |
| " x = self.classifier(x)\n", | |
| " return x\n" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "import torch\n", | |
| "import torch.nn as nn\n", | |
| "import torch.nn.functional as F\n", | |
| "\n", | |
| "\n", | |
| "class ConvBlock(nn.Module):\n", | |
| " def __init__(self, in_ch, out_ch, stride=1):\n", | |
| " super().__init__()\n", | |
| " self.block = nn.Sequential(\n", | |
| " nn.Conv2d(in_ch, out_ch, kernel_size=3, stride=stride, padding=1, bias=False),\n", | |
| " nn.BatchNorm2d(out_ch),\n", | |
| " nn.ReLU(inplace=True),\n", | |
| " )\n", | |
| "\n", | |
| " def forward(self, x):\n", | |
| " return self.block(x)\n", | |
| "\n", | |
| "\n", | |
| "class AirQualityNet(nn.Module):\n", | |
| " def __init__(self, num_classes=3, dropout=0.5):\n", | |
| " super().__init__()\n", | |
| "\n", | |
| " # -------- Backbone --------\n", | |
| " self.stage1 = nn.Sequential(\n", | |
| " ConvBlock(3, 32),\n", | |
| " ConvBlock(32, 32),\n", | |
| " nn.MaxPool2d(2),\n", | |
| " )\n", | |
| "\n", | |
| " self.stage2 = nn.Sequential(\n", | |
| " ConvBlock(32, 64),\n", | |
| " ConvBlock(64, 64),\n", | |
| " nn.MaxPool2d(2),\n", | |
| " )\n", | |
| "\n", | |
| " self.stage3 = nn.Sequential(\n", | |
| " ConvBlock(64, 128),\n", | |
| " ConvBlock(128, 128),\n", | |
| " nn.MaxPool2d(2),\n", | |
| " )\n", | |
| "\n", | |
| " self.stage4 = nn.Sequential(\n", | |
| " ConvBlock(128, 256),\n", | |
| " nn.MaxPool2d(2),\n", | |
| " )\n", | |
| "\n", | |
| " # -------- Head --------\n", | |
| " self.global_pool = nn.AdaptiveAvgPool2d(1)\n", | |
| "\n", | |
| " self.classifier = nn.Sequential(\n", | |
| " nn.Linear(256, 128),\n", | |
| " nn.ReLU(inplace=True),\n", | |
| " nn.Dropout(dropout),\n", | |
| " nn.Linear(128, num_classes),\n", | |
| " )\n", | |
| "\n", | |
| " def forward(self, x):\n", | |
| " x = self.stage1(x)\n", | |
| " x = self.stage2(x)\n", | |
| " x = self.stage3(x)\n", | |
| " x = self.stage4(x)\n", | |
| "\n", | |
| " x = self.global_pool(x)\n", | |
| " x = torch.flatten(x, 1)\n", | |
| " x = self.classifier(x)\n", | |
| " return x\n" | |
| ], | |
| "metadata": { | |
| "id": "3HPR5098uf6h" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class ResNet101Classifier(nn.Module):\n", | |
| "\n", | |
| " def __init__(self, num_classes=4, freeze_backbone=True, hidden_dim=512):\n", | |
| " super(ResNet101Classifier,self).__init__()\n", | |
| " resnet101 = models.resnet101(pretrained=True)\n", | |
| "\n", | |
| " for param in resnet101.parameters():\n", | |
| " param.requires_grad = False\n", | |
| "\n", | |
| " self.features = nn.Sequential(*list(resnet101.children())[:-1])\n", | |
| " self.num_features = 2048\n", | |
| " self.classifier = nn.Sequential(OrderedDict([ ('fc1',nn.Linear(self.num_features, hidden_dim)), ('relu', nn.ReLU()), ('fc2', nn.Linear(hidden_dim, num_classes)) ]))\n", | |
| "\n", | |
| " def forward(self, x):\n", | |
| " x = self.features(x)\n", | |
| " x = torch.flatten(x, 1)\n", | |
| " x = self.classifier(x)\n", | |
| " return x" | |
| ], | |
| "metadata": { | |
| "id": "npaDg3VHuwMa" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "MvmZvEDDvHAz" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "model = EfficientNetB0Classifier().cuda()" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "HdKCIFYMvxhD" | |
| }, | |
| "source": [ | |
| "**Loops**" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "CcrXoU_nwOQY" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "model = EfficientNetB0Classifier(num_classes=3).cuda()\n", | |
| "criterion = nn.CrossEntropyLoss()\n", | |
| "optimizer = torch.optim.Adam(model.classifier.parameters(), lr=1e-4)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "MK7GEHCbBTsW" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "loss = nn.CrossEntropyLoss()\n", | |
| "input = torch.randn(3, 5, requires_grad=True)\n", | |
| "target = torch.empty(3, dtype=torch.long).random_(5)\n", | |
| "output = loss(input, target)\n", | |
| "output.backward()" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "P7OTnRFTwbC-" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "def training_loop(loader, epoch, criterion, model, optimizer, device=\"cuda\"):\n", | |
| " model.train()\n", | |
| "\n", | |
| " total_loss = 0.0\n", | |
| " correct = 0\n", | |
| " total = 0\n", | |
| "\n", | |
| " for step, (images, labels) in enumerate(loader):\n", | |
| " images = images.to(device)\n", | |
| " labels = labels.to(device)\n", | |
| "\n", | |
| " optimizer.zero_grad()\n", | |
| "\n", | |
| " outputs = model(images)\n", | |
| " loss = criterion(outputs, labels)\n", | |
| "\n", | |
| " loss.backward()\n", | |
| " optimizer.step()\n", | |
| "\n", | |
| " batch_size = labels.size(0)\n", | |
| " total_loss += loss.item() * batch_size\n", | |
| " total += batch_size\n", | |
| "\n", | |
| " preds = outputs.argmax(dim=1)\n", | |
| " correct += (preds == labels).sum().item()\n", | |
| "\n", | |
| " if step % 5 == 0:\n", | |
| " print(\n", | |
| " f\"Epoch: {epoch}, Step: {step}, \"\n", | |
| " f\"Loss: {total_loss/total:.4f}, \"\n", | |
| " f\"Accuracy: {(correct/total)*100:.4f}\"\n", | |
| " )\n", | |
| "\n", | |
| " return total_loss / total, correct / total" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "5paTe4DDwb4y" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "def validation_loop(loader, epoch, criterion, model, device=\"cuda\"):\n", | |
| " model.eval()\n", | |
| " total_loss = 0\n", | |
| " correct = 0\n", | |
| " total = 0\n", | |
| "\n", | |
| " with torch.no_grad():\n", | |
| " for step, (images, labels) in enumerate(loader):\n", | |
| " images = images.to(device)\n", | |
| " labels = labels.to(device)\n", | |
| "\n", | |
| " outputs = model(images)\n", | |
| " loss = criterion(outputs, labels)\n", | |
| "\n", | |
| " batch_size = labels.size(0)\n", | |
| " total_loss += loss.item() * batch_size\n", | |
| " total += batch_size\n", | |
| "\n", | |
| " preds = outputs.argmax(dim=1)\n", | |
| " correct += (preds == labels).sum().item()\n", | |
| "\n", | |
| " if step % 5 == 0:\n", | |
| " print(\n", | |
| " f\"Epoch: {epoch}, Step: {step}, \"\n", | |
| " f\"Loss: {total_loss/total:.4f}, \"\n", | |
| " f\"Accuracy: {(correct/total)*100:.4f}\"\n", | |
| " )\n", | |
| "\n", | |
| " return total_loss / total, correct / total\n" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "hDJeTGZ4DJVR" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "epochs = 100\n", | |
| "best_acc = 0.0\n", | |
| "\n", | |
| "checkpoint_path = \"/content/drive/MyDrive/checkpoint.pth\"\n", | |
| "best_model_path = \"/content/drive/MyDrive/best_model.pth\"\n", | |
| "\n", | |
| "start_epoch = 0\n", | |
| "\n", | |
| "train_losses = []\n", | |
| "train_accs = []\n", | |
| "test_losses = []\n", | |
| "test_accs = []\n", | |
| "\n", | |
| "for epoch in range(start_epoch, epochs):\n", | |
| " train_loss, train_acc = training_loop(overfit_train_loader, epoch, criterion, model, optimizer)\n", | |
| " train_losses.append(train_loss)\n", | |
| " train_accs.append(train_acc)\n", | |
| "\n", | |
| " print(f\"Train Epoch: {epoch}, train accuracy: {100*train_acc:.4f}, train loss: {train_loss:.4f}\")\n", | |
| "\n", | |
| " test_loss, test_acc = validation_loop(overfit_test_loader, epoch, criterion, model)\n", | |
| " test_losses.append(test_loss)\n", | |
| " test_accs.append(test_acc)\n", | |
| "\n", | |
| " print(f\"Test Epoch: {epoch}, test accuracy: {100*test_acc:.4f}, test loss: {test_loss:.4f}\")\n", | |
| "\n", | |
| " if test_acc > best_acc:\n", | |
| " best_acc = test_acc\n", | |
| " torch.save(model.state_dict(), best_model_path)\n", | |
| "\n", | |
| " torch.save({\n", | |
| " \"epoch\": epoch,\n", | |
| " \"model\": model.state_dict(),\n", | |
| " \"optimizer\": optimizer.state_dict(),\n", | |
| " \"best_acc\": best_acc\n", | |
| " }, checkpoint_path)\n", | |
| "\n", | |
| "plt.figure(figsize=(12,5))\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(\"Epoch\")\n", | |
| "plt.ylabel(\"Loss\")\n", | |
| "plt.title(\"Loss over Epochs\")\n", | |
| "plt.legend()\n", | |
| "\n", | |
| "plt.subplot(1,2,2)\n", | |
| "plt.plot([t*100 for t in train_accs], label='Train Accuracy')\n", | |
| "plt.plot([t*100 for t in test_accs], label='Test Accuracy')\n", | |
| "plt.xlabel(\"Epoch\")\n", | |
| "plt.ylabel(\"Accuracy (%)\")\n", | |
| "plt.title(\"Accuracy over Epochs\")\n", | |
| "plt.legend()\n", | |
| "plt.show()" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "hKF2PM2wx8NN" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "model.load_state_dict(torch.load(\"/content/drive/MyDrive/best_model.pth\"))\n", | |
| "\n", | |
| "final_loss, final_acc = validation_loop(test_loader, 1, criterion, model)\n", | |
| "print(f\"Final Test Loss: {final_loss:.4f}\")\n", | |
| "print(f\"Final Test Accuracy: {final_acc:.4f}\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "id": "gKHNTsCV_8Jo" | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay\n", | |
| "\n", | |
| "y_true = []\n", | |
| "y_pred = []\n", | |
| "\n", | |
| "test_loader = DataLoader(overfit_test_dataset, batch_size=1, shuffle=False)\n", | |
| "\n", | |
| "model.eval()\n", | |
| "\n", | |
| "with torch.no_grad():\n", | |
| " for image, label in overfit_test_loader:\n", | |
| " image = image.cuda()\n", | |
| "\n", | |
| " outputs = model(image)\n", | |
| " preds = outputs.argmax(dim=1)\n", | |
| "\n", | |
| " y_true.append(label.cpu().numpy())\n", | |
| " y_pred.append(preds.cpu().numpy())\n", | |
| "\n", | |
| "numpy_true = np.array(y_true)\n", | |
| "numpy_pred = np.array(y_pred)\n", | |
| "\n", | |
| "cm = confusion_matrix(numpy_true, numpy_pred, normalize = \"true\")\n", | |
| "disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels = np.array([0, 1, 2]))\n", | |
| "\n", | |
| "disp.plot()\n", | |
| "plt.show()" | |
| ] | |
| } | |
| ], | |
| "metadata": { | |
| "accelerator": "GPU", | |
| "colab": { | |
| "gpuType": "T4", | |
| "provenance": [], | |
| "include_colab_link": true | |
| }, | |
| "kernelspec": { | |
| "display_name": "Python 3", | |
| "name": "python3" | |
| }, | |
| "language_info": { | |
| "name": "python" | |
| } | |
| }, | |
| "nbformat": 4, | |
| "nbformat_minor": 0 | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment