From 371db3a5e7f70df37c77b6b723c8cfd936486f48 Mon Sep 17 00:00:00 2001 From: Aninda Goswamy <39881731+anindabitm@users.noreply.github.com> Date: Thu, 1 Aug 2024 16:38:58 +0530 Subject: [PATCH 1/4] Clay embeddings on Sentinel-1 data Added notebook to demonstrate use of Clay embeddings on Sentinel-1 imagery for earthquake detection --- docs/tutorials/Earthquake-preds-Clay.ipynb | 407 +++++++++++++++++++++ 1 file changed, 407 insertions(+) create mode 100644 docs/tutorials/Earthquake-preds-Clay.ipynb diff --git a/docs/tutorials/Earthquake-preds-Clay.ipynb b/docs/tutorials/Earthquake-preds-Clay.ipynb new file mode 100644 index 00000000..6bf841e5 --- /dev/null +++ b/docs/tutorials/Earthquake-preds-Clay.ipynb @@ -0,0 +1,407 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "84c1ddaf-3149-4461-a0bc-91a4b68d99a2", + "metadata": {}, + "source": [ + "**Using Clay embeddings to detect Earthquake**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5bc8c2d1-f2e1-49c9-911b-a4b1fb5efed5", + "metadata": {}, + "outputs": [], + "source": [ + "#git clone https://github.com/Clay-foundation/model #Need to run this for the first time" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52d10e8f-ebfb-4b17-8d84-c57af1188f53", + "metadata": {}, + "outputs": [], + "source": [ + "# pip install torchgeo[all]\n", + "# pip install git+https://github.com/microsoft/torchgeo.git #Use this to be able to access the QuakeSet dataset" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "89f8bc4b-73ca-47e3-aa07-f444a871ca56", + "metadata": {}, + "outputs": [], + "source": [ + "#importing the required modules\n", + "\n", + "import torchgeo\n", + "import torchgeo.datasets as datasets\n", + "import sys\n", + "import torch\n", + "import numpy as np\n", + "from sklearn.ensemble import RandomForestClassifier\n", + "from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay\n", + "from sklearn.model_selection import train_test_split\n", + "import matplotlib.pyplot as plt\n", + "\n", + "\n", + "sys.path.append(\"model/\")\n", + "\n", + "from src.model import ClayMAEModule" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d87a1df1-9e67-40af-9b76-147e7c3b6a38", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'0.6.0.dev0'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "torchgeo.__version__ #check torchgeo version" + ] + }, + { + "cell_type": "markdown", + "id": "f7f5ae92-1978-40a1-adcd-56a087f1f67a", + "metadata": {}, + "source": [ + "**Downloading the QuakeSet dataset**\n", + "\n", + "@misc{cambrin2024quakesetdatasetlowresourcemodels,\n", + " title={QuakeSet: A Dataset and Low-Resource Models to Monitor Earthquakes through Sentinel-1}, \n", + " author={Daniele Rege Cambrin and Paolo Garza},\n", + " year={2024},\n", + " eprint={2403.18116},\n", + " archivePrefix={arXiv},\n", + " primaryClass={cs.CV},\n", + " url={https://arxiv.org/abs/2403.18116}, \n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "57c7327c-e454-4ca6-bb50-fe67272ca3fd", + "metadata": {}, + "outputs": [], + "source": [ + "#We download the QuakeSet dataset available in torchgeo.\n", + "#We will work with the \"train\" split of the data only to show the workflow\n", + "\n", + "train_ds = datasets.QuakeSet(split=\"train\",download=False) #Change download to True to download first time" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "3e60a646-d412-4212-a3a1-e0bf7edc363e", + "metadata": {}, + "outputs": [], + "source": [ + "#val_ds = datasets.QuakeSet(split=\"val\",download=False) #Can download and use other splits such \"val\",\"test\"" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "c2ebdc0c-d4d8-4ba2-b97d-babb0d0d4d70", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([4, 512, 512])" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#Checking the Sentinel-1 imagery size. \n", + "\n", + "#Each sample consists of pre and post event data with two channels - vv and vh of Sentinel-1\n", + "\n", + "train_ds[0][\"image\"].shape" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "df682dd4-7eee-48a7-a42b-3c45cc2a7460", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/zeus/miniconda3/envs/cloudspace/lib/python3.10/site-packages/torch/nn/modules/transformer.py:286: UserWarning: enable_nested_tensor is True, but self.use_nested_tensor is False because encoder_layer.self_attn.batch_first was not True(use batch_first for better inference performance)\n", + " warnings.warn(f\"enable_nested_tensor is True, but self.use_nested_tensor is False because {why_not_sparsity_fast_path}\")\n" + ] + } + ], + "source": [ + "#Download Clay-1 model\n", + "\n", + "device = torch.device(\"cuda\") if torch.cuda.is_available() else torch.device(\"cpu\")\n", + "ckpt = \"https://clay-model-ckpt.s3.amazonaws.com/v0.5.7/mae_v0.5.7_epoch-13_val-loss-0.3098.ckpt\"\n", + "torch.set_default_device(device)\n", + "\n", + "model = ClayMAEModule.load_from_checkpoint(\n", + " ckpt, metadata_path=\"model/configs/metadata.yaml\", shuffle=False, mask_ratio=0\n", + ")\n", + "model.eval()\n", + "\n", + "model = model.to(device)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "b115e660-fb27-4340-a8e5-09d29c330d1b", + "metadata": {}, + "outputs": [], + "source": [ + "#Dataset class to use for loading the data\n", + "\n", + "class EarthQuakeDataset:\n", + " def __init__(self,ds):\n", + " self.ds = ds\n", + " \n", + " def __len__(self):\n", + " return len(self.ds)\n", + "\n", + " def __getitem__(self,idx):\n", + " pre_image = self.ds[idx][\"image\"][:2,:,:] #First two images are Sentinel-1 images (vv & vh band) of pre-event \n", + " post_image = self.ds[idx][\"image\"][2:,:,:] #Last two images are Sentinel-1 images (vv & vh band) of post-event\n", + " label = self.ds[idx][\"label\"]\n", + "\n", + " sample = {\n", + " \"pixels1\": pre_image, # 2 x 512 x 512\n", + " \"pixels2\": post_image, # 2 x 512 x 512\n", + " \"time\": torch.zeros(4), # Placeholder for time information\n", + " \"latlon\": torch.zeros(4), # Placeholder for latlon information\n", + " \"label\":label\n", + " }\n", + "\n", + " \n", + " return sample\n", + "\n", + "\n", + "#Construct training dataset object\n", + "train_dataset = EarthQuakeDataset(train_ds)\n", + "\n", + "#validation_dataset = EarthQuakeDataset(val_ds)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bbc4fd0a-0483-4dfd-a82e-a0da1a1530de", + "metadata": {}, + "outputs": [], + "source": [ + "#Dataloaders from dataset\n", + "train_dl = torch.utils.data.DataLoader(train_dataset,batch_size=32,shuffle=True,generator=torch.Generator(device=device))\n", + "#val_dl = torch.utils.data.DataLoader(validation_dataset,batch_size=32,shuffle=False,generator=torch.Generator(device=device))" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "6ac0b263-f334-41ee-88fd-cab4195cb767", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d8f69b8f67fe4f95bfb66471e6f38e88", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + " 0%| | 0/284 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#Train a RandomForestClassifier on the embeddings\n", + "\n", + "X_train, X_test, y_train, y_test = train_test_split(X, y,random_state=42) #Split the data into two parts - one for training and other for validation \n", + "\n", + "clf = RandomForestClassifier() #Instantiate a RandomForestClassifier\n", + "clf.fit(X_train, y_train) #Train on embeddings\n", + "predictions = clf.predict(X_test) #Generate predictions\n", + "\n", + "#Confusion matrix\n", + "cm = confusion_matrix(y_test, predictions, labels=clf.classes_)\n", + "disp = ConfusionMatrixDisplay(confusion_matrix=cm,display_labels=clf.classes_)\n", + "disp.plot()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "f0f6fb57-3a2b-49eb-bfe5-14c37dedbdd0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "F1 score: 0.9088050314465408\n" + ] + } + ], + "source": [ + "#check F1-score which is the metric for the competition which uses this data - \"ECML-PKDD 2024 - SMAC: Seismic Monitoring and Analysis Challenge\" https://www.codabench.org/competitions/2222/#/pages-tab had F1-score upward of 0.908 with training Deep Learning Models\n", + "\n", + "from sklearn.metrics import f1_score\n", + "\n", + "score = f1_score(y_test,predictions)\n", + "\n", + "print(\"F1 score: \",score)\n", + "\n" + ] + } + ], + "metadata": { + "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.10.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 824b6a15e5400b6f40d6e021282b4c316416b095 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 05:26:52 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/tutorials/Earthquake-preds-Clay.ipynb | 114 ++++++++++++--------- 1 file changed, 64 insertions(+), 50 deletions(-) diff --git a/docs/tutorials/Earthquake-preds-Clay.ipynb b/docs/tutorials/Earthquake-preds-Clay.ipynb index 6bf841e5..88226ad3 100644 --- a/docs/tutorials/Earthquake-preds-Clay.ipynb +++ b/docs/tutorials/Earthquake-preds-Clay.ipynb @@ -15,7 +15,7 @@ "metadata": {}, "outputs": [], "source": [ - "#git clone https://github.com/Clay-foundation/model #Need to run this for the first time" + "# git clone https://github.com/Clay-foundation/model #Need to run this for the first time" ] }, { @@ -36,7 +36,7 @@ "metadata": {}, "outputs": [], "source": [ - "#importing the required modules\n", + "# importing the required modules\n", "\n", "import torchgeo\n", "import torchgeo.datasets as datasets\n", @@ -72,7 +72,7 @@ } ], "source": [ - "torchgeo.__version__ #check torchgeo version" + "torchgeo.__version__ # check torchgeo version" ] }, { @@ -100,10 +100,12 @@ "metadata": {}, "outputs": [], "source": [ - "#We download the QuakeSet dataset available in torchgeo.\n", - "#We will work with the \"train\" split of the data only to show the workflow\n", + "# We download the QuakeSet dataset available in torchgeo.\n", + "# We will work with the \"train\" split of the data only to show the workflow\n", "\n", - "train_ds = datasets.QuakeSet(split=\"train\",download=False) #Change download to True to download first time" + "train_ds = datasets.QuakeSet(\n", + " split=\"train\", download=False\n", + ") # Change download to True to download first time" ] }, { @@ -113,7 +115,7 @@ "metadata": {}, "outputs": [], "source": [ - "#val_ds = datasets.QuakeSet(split=\"val\",download=False) #Can download and use other splits such \"val\",\"test\"" + "# val_ds = datasets.QuakeSet(split=\"val\",download=False) #Can download and use other splits such \"val\",\"test\"" ] }, { @@ -134,9 +136,9 @@ } ], "source": [ - "#Checking the Sentinel-1 imagery size. \n", + "# Checking the Sentinel-1 imagery size.\n", "\n", - "#Each sample consists of pre and post event data with two channels - vv and vh of Sentinel-1\n", + "# Each sample consists of pre and post event data with two channels - vv and vh of Sentinel-1\n", "\n", "train_ds[0][\"image\"].shape" ] @@ -157,7 +159,7 @@ } ], "source": [ - "#Download Clay-1 model\n", + "# Download Clay-1 model\n", "\n", "device = torch.device(\"cuda\") if torch.cuda.is_available() else torch.device(\"cpu\")\n", "ckpt = \"https://clay-model-ckpt.s3.amazonaws.com/v0.5.7/mae_v0.5.7_epoch-13_val-loss-0.3098.ckpt\"\n", @@ -178,36 +180,40 @@ "metadata": {}, "outputs": [], "source": [ - "#Dataset class to use for loading the data\n", + "# Dataset class to use for loading the data\n", + "\n", "\n", "class EarthQuakeDataset:\n", - " def __init__(self,ds):\n", + " def __init__(self, ds):\n", " self.ds = ds\n", - " \n", + "\n", " def __len__(self):\n", " return len(self.ds)\n", "\n", - " def __getitem__(self,idx):\n", - " pre_image = self.ds[idx][\"image\"][:2,:,:] #First two images are Sentinel-1 images (vv & vh band) of pre-event \n", - " post_image = self.ds[idx][\"image\"][2:,:,:] #Last two images are Sentinel-1 images (vv & vh band) of post-event\n", + " def __getitem__(self, idx):\n", + " pre_image = self.ds[idx][\"image\"][\n", + " :2, :, :\n", + " ] # First two images are Sentinel-1 images (vv & vh band) of pre-event\n", + " post_image = self.ds[idx][\"image\"][\n", + " 2:, :, :\n", + " ] # Last two images are Sentinel-1 images (vv & vh band) of post-event\n", " label = self.ds[idx][\"label\"]\n", "\n", " sample = {\n", " \"pixels1\": pre_image, # 2 x 512 x 512\n", - " \"pixels2\": post_image, # 2 x 512 x 512\n", + " \"pixels2\": post_image, # 2 x 512 x 512\n", " \"time\": torch.zeros(4), # Placeholder for time information\n", " \"latlon\": torch.zeros(4), # Placeholder for latlon information\n", - " \"label\":label\n", + " \"label\": label,\n", " }\n", "\n", - " \n", " return sample\n", "\n", "\n", - "#Construct training dataset object\n", + "# Construct training dataset object\n", "train_dataset = EarthQuakeDataset(train_ds)\n", "\n", - "#validation_dataset = EarthQuakeDataset(val_ds)" + "# validation_dataset = EarthQuakeDataset(val_ds)" ] }, { @@ -217,9 +223,11 @@ "metadata": {}, "outputs": [], "source": [ - "#Dataloaders from dataset\n", - "train_dl = torch.utils.data.DataLoader(train_dataset,batch_size=32,shuffle=True,generator=torch.Generator(device=device))\n", - "#val_dl = torch.utils.data.DataLoader(validation_dataset,batch_size=32,shuffle=False,generator=torch.Generator(device=device))" + "# Dataloaders from dataset\n", + "train_dl = torch.utils.data.DataLoader(\n", + " train_dataset, batch_size=32, shuffle=True, generator=torch.Generator(device=device)\n", + ")\n", + "# val_dl = torch.utils.data.DataLoader(validation_dataset,batch_size=32,shuffle=False,generator=torch.Generator(device=device))" ] }, { @@ -244,31 +252,30 @@ } ], "source": [ - "#Generate embeddings for the data\n", + "# Generate embeddings for the data\n", "\n", "from tqdm.auto import tqdm\n", "\n", - "gsd = torch.tensor(10, device=device) #Ground sampling distance for Sentinel-1\n", - "waves = torch.tensor([3.5,4.0], device=device) #wavelengths for Sentinel-1\n", + "gsd = torch.tensor(10, device=device) # Ground sampling distance for Sentinel-1\n", + "waves = torch.tensor([3.5, 4.0], device=device) # wavelengths for Sentinel-1\n", "\n", "embeddings1 = []\n", "embeddings2 = []\n", "target = []\n", "\n", - "for bid,batch in enumerate(tqdm(train_dl)):\n", - " \n", + "for bid, batch in enumerate(tqdm(train_dl)):\n", " datacube1 = {\n", " \"pixels\": batch[\"pixels1\"].to(device),\n", - " \"time\":batch[\"time\"].to(device),\n", - " \"latlon\":batch[\"latlon\"].to(device),\n", + " \"time\": batch[\"time\"].to(device),\n", + " \"latlon\": batch[\"latlon\"].to(device),\n", " \"gsd\": gsd,\n", " \"waves\": waves,\n", " }\n", "\n", " datacube2 = {\n", " \"pixels\": batch[\"pixels2\"].to(device),\n", - " \"time\":batch[\"time\"].to(device),\n", - " \"latlon\":batch[\"latlon\"].to(device),\n", + " \"time\": batch[\"time\"].to(device),\n", + " \"latlon\": batch[\"latlon\"].to(device),\n", " \"gsd\": gsd,\n", " \"waves\": waves,\n", " }\n", @@ -282,7 +289,7 @@ "\n", " embeddings1.append(emb1)\n", " embeddings2.append(emb2)\n", - " target.append(batch[\"label\"].cpu().numpy())\n" + " target.append(batch[\"label\"].cpu().numpy())" ] }, { @@ -292,10 +299,13 @@ "metadata": {}, "outputs": [], "source": [ - "#Saving embeddings and ground truth (label) data\n", + "# Saving embeddings and ground truth (label) data\n", "\n", - "np.save(\"train_emb.npy\",np.concatenate((np.concatenate(embeddings1),np.concatenate(embeddings2)),axis=1))\n", - "np.save(\"train_label.npy\",np.concatenate(target))" + "np.save(\n", + " \"train_emb.npy\",\n", + " np.concatenate((np.concatenate(embeddings1), np.concatenate(embeddings2)), axis=1),\n", + ")\n", + "np.save(\"train_label.npy\", np.concatenate(target))" ] }, { @@ -316,12 +326,15 @@ } ], "source": [ - "#Load the saved embeddings and ground truth (labels)\n", + "# Load the saved embeddings and ground truth (labels)\n", "\n", "X = np.load(\"train_emb.npy\")\n", "y = np.load(\"train_label.npy\")\n", "\n", - "X.shape,y.shape #Check dimensions. The embeddings have a size of 768. Since we have pre-event and post-event image and we concatenate the embeddings so the dimension is 768 x 2 " + "(\n", + " X.shape,\n", + " y.shape,\n", + ") # Check dimensions. The embeddings have a size of 768. Since we have pre-event and post-event image and we concatenate the embeddings so the dimension is 768 x 2" ] }, { @@ -342,17 +355,19 @@ } ], "source": [ - "#Train a RandomForestClassifier on the embeddings\n", + "# Train a RandomForestClassifier on the embeddings\n", "\n", - "X_train, X_test, y_train, y_test = train_test_split(X, y,random_state=42) #Split the data into two parts - one for training and other for validation \n", + "X_train, X_test, y_train, y_test = train_test_split(\n", + " X, y, random_state=42\n", + ") # Split the data into two parts - one for training and other for validation\n", "\n", - "clf = RandomForestClassifier() #Instantiate a RandomForestClassifier\n", - "clf.fit(X_train, y_train) #Train on embeddings\n", - "predictions = clf.predict(X_test) #Generate predictions\n", + "clf = RandomForestClassifier() # Instantiate a RandomForestClassifier\n", + "clf.fit(X_train, y_train) # Train on embeddings\n", + "predictions = clf.predict(X_test) # Generate predictions\n", "\n", - "#Confusion matrix\n", + "# Confusion matrix\n", "cm = confusion_matrix(y_test, predictions, labels=clf.classes_)\n", - "disp = ConfusionMatrixDisplay(confusion_matrix=cm,display_labels=clf.classes_)\n", + "disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=clf.classes_)\n", "disp.plot()\n", "plt.show()" ] @@ -372,14 +387,13 @@ } ], "source": [ - "#check F1-score which is the metric for the competition which uses this data - \"ECML-PKDD 2024 - SMAC: Seismic Monitoring and Analysis Challenge\" https://www.codabench.org/competitions/2222/#/pages-tab had F1-score upward of 0.908 with training Deep Learning Models\n", + "# check F1-score which is the metric for the competition which uses this data - \"ECML-PKDD 2024 - SMAC: Seismic Monitoring and Analysis Challenge\" https://www.codabench.org/competitions/2222/#/pages-tab had F1-score upward of 0.908 with training Deep Learning Models\n", "\n", "from sklearn.metrics import f1_score\n", "\n", - "score = f1_score(y_test,predictions)\n", + "score = f1_score(y_test, predictions)\n", "\n", - "print(\"F1 score: \",score)\n", - "\n" + "print(\"F1 score: \", score)" ] } ], From d7a439cc4b3ef7746b755c52ab1601da52df9c3e Mon Sep 17 00:00:00 2001 From: Aninda Goswamy <39881731+anindabitm@users.noreply.github.com> Date: Wed, 7 Aug 2024 13:46:44 +0530 Subject: [PATCH 3/4] Made changes to the Earthquake prediction Updated notebook on Earthquake prediction --- docs/tutorials/Earthquake-preds-Clay.ipynb | 631 ++++++++++++++++----- 1 file changed, 490 insertions(+), 141 deletions(-) diff --git a/docs/tutorials/Earthquake-preds-Clay.ipynb b/docs/tutorials/Earthquake-preds-Clay.ipynb index 88226ad3..0756ace5 100644 --- a/docs/tutorials/Earthquake-preds-Clay.ipynb +++ b/docs/tutorials/Earthquake-preds-Clay.ipynb @@ -1,26 +1,140 @@ { "cells": [ + { + "cell_type": "markdown", + "id": "0e2c4b4b-063f-4b9f-8a6e-df8d48ba4b3a", + "metadata": {}, + "source": [ + "**Problem Statement**" + ] + }, + { + "cell_type": "markdown", + "id": "26154498-17c4-4aff-94a4-f69fdfabbdde", + "metadata": {}, + "source": [ + "Earthquake monitoring and damage estimation is essential to ensure that necessary aid reaches the affected areas in atimely manner. However, the coverage of earthquake monitoring network is limited and inadequate for remote areas. Possible solution to this problem can be use of satellite imagery to estimate occurence of earthquake and also get a sense of the extent of damage.\n", + "\n", + "This notebook presents a workflow to use the embeddings generated by Clay foundation model on Sentinel-1 data to predict whether earthquake has occurred or not. The code below provides a walkthrough of this binary classification problem (detecting earthquake) on Sentinel-1 imagery which is free and available through the Copernicus Sentinel program.\n", + "\n", + "For more information on Sentinel-1 bands, please see https://developers.google.com/earth-engine/datasets/catalog/COPERNICUS_S1_GRD" + ] + }, + { + "cell_type": "markdown", + "id": "5575d9af-8917-49bb-844b-64356060d72e", + "metadata": {}, + "source": [ + "**About the dataset**" + ] + }, + { + "cell_type": "markdown", + "id": "77210df1-d1e8-4142-833c-0639052c0e1c", + "metadata": {}, + "source": [ + "*QuakeSet: A Dataset and Low-Resource Models to Monitor Earthquakes through Sentinel-1.*\n", + "\n", + "Citation:\n", + "@misc{cambrin2024quakesetdatasetlowresourcemodels,\n", + " title={QuakeSet: A Dataset and Low-Resource Models to Monitor Earthquakes through Sentinel-1}, \n", + " author={Daniele Rege Cambrin and Paolo Garza},\n", + " year={2024},\n", + " eprint={2403.18116},\n", + " archivePrefix={arXiv},\n", + " primaryClass={cs.CV},\n", + " url={https://arxiv.org/abs/2403.18116}, \n", + "}\n", + "\n", + "Huggingface link: https://huggingface.co/datasets/DarthReca/quakeset\n", + "\n", + "Github link: https://github.com/DarthReca/quakeset/\n", + "\n", + "torchgeo link: https://torchgeo.readthedocs.io/en/latest/_modules/torchgeo/datasets/quakeset.html#QuakeSet\n", + "\n", + "**Dataset Structure**\n", + "\n", + "The dataset is divided into three folds with equal distribution of magnitudes and balanced in positive and negative examples.\n", + "\n", + "Dataset features:\n", + "\n", + "-Sentinel-1 SAR imagery\n", + "\n", + "-before/pre/post imagery of areas affected by earthquakes\n", + "\n", + "-2 SAR bands (VV/VH)\n", + "\n", + "-3,327 pairs of pre and post images with 5 m per pixel resolution (512x512 px)\n", + "\n", + "-2 classification labels (unaffected / affected by earthquake) (**used in this notebook for binary classification**)\n", + "\n", + "-pre/post image pairs represent earthquake affected areas\n", + "\n", + "-before/pre image pairs represent hard negative unaffected areas\n", + "\n", + "-earthquake magnitudes for each sample\n", + "\n", + "Dataset format:\n", + "\n", + "single hdf5 dataset containing images, magnitudes, hypercenters, and splits\n", + "\n", + "Dataset classes:\n", + "\n", + "-unaffected area\n", + "\n", + "-earthquake affected area\n" + ] + }, + { + "cell_type": "markdown", + "id": "bf424db3-765c-4f13-953e-4bf180061696", + "metadata": {}, + "source": [ + "**Approaches to the problem**" + ] + }, { "cell_type": "markdown", "id": "84c1ddaf-3149-4461-a0bc-91a4b68d99a2", "metadata": {}, "source": [ - "**Using Clay embeddings to detect Earthquake**" + "Each sample consist of 2 images (vv and vh band) of pre-event and 2 images (vv and vh band) of post event and the image size is 512 x 512. The ground truth is 1 or 0 indicating whether earthquake has occurred or not. The binary classification problem can be solved using multiple approaches such as:\n", + "\n", + "1. Using traditional machine learning \n", + "The images can be flattened such that each data point has dimension 512 x 512 x 4 with a label of 1 or 0. Then the data can be fed into a traditional ML model such as SVM or RF classifier directly or after the inputs has undergone dimensionality reduction through PCA / t-SNE etc.\n", + "\n", + "2. Using deep learning \n", + "Deep learning algorithms such as CNN models can be trained on imagery treating the input to have 4 channels with labels being 1 or 0. Binary cross entropy loss functions can be used to optimize the CNN network\n", + "\n", + "3. Using embeddings of pre-trained models \n", + "Foundation models have revolutionalized the deep learning space wherein these models are pre-trained on large amount of data and are able to learn a lot of domain knowledge from the data itself. Recently, the application of these models have especially been visible in the NLP space (think LLMs and chatgpt). Clay is a foundation model for earth observation and the embeddings it generates on satellite data (of different platforms) can be used for multiple downstream tasks such as classification, regression etc. since the embeddings contain rich information about the imagery in a latent space. \n", + "\n", + "Our approach in this notebook is to use the Clay pre-trained model to generate embeddings on the Sentinel-1 imagery (of the Quakeset dataset) and use those embeddings as inputs to random forest classifier for binary classification. Our beleief is that since these embeddings already contain lots of information about the imagery, they should serve as useful features which can be used to do classification.\n", + "\n", + "This approach is also computationally cheaper and faster since high end GPUs are not needed to train classifier models on embeddings and these models can be trained in few minutes. On the other hand, training large deep learning models require extensive hardware and long training times spanning days and sometime months." + ] + }, + { + "cell_type": "markdown", + "id": "16dd744e-0f96-47d8-9766-49771e01e71b", + "metadata": {}, + "source": [ + "**Using Clay embeddings to detect Earthquake - Code walkthrough**" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "5bc8c2d1-f2e1-49c9-911b-a4b1fb5efed5", "metadata": {}, "outputs": [], "source": [ - "# git clone https://github.com/Clay-foundation/model #Need to run this for the first time" + "#git clone https://github.com/Clay-foundation/model #Need to run this for the first time" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "52d10e8f-ebfb-4b17-8d84-c57af1188f53", "metadata": {}, "outputs": [], @@ -31,12 +145,12 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 3, "id": "89f8bc4b-73ca-47e3-aa07-f444a871ca56", "metadata": {}, "outputs": [], "source": [ - "# importing the required modules\n", + "#importing the required modules\n", "\n", "import torchgeo\n", "import torchgeo.datasets as datasets\n", @@ -44,10 +158,12 @@ "import torch\n", "import numpy as np\n", "from sklearn.ensemble import RandomForestClassifier\n", + "import xgboost as xgb\n", "from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay\n", "from sklearn.model_selection import train_test_split\n", "import matplotlib.pyplot as plt\n", - "\n", + "from tqdm.auto import tqdm\n", + "from sklearn.metrics import f1_score\n", "\n", "sys.path.append(\"model/\")\n", "\n", @@ -56,7 +172,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "d87a1df1-9e67-40af-9b76-147e7c3b6a38", "metadata": {}, "outputs": [ @@ -66,13 +182,13 @@ "'0.6.0.dev0'" ] }, - "execution_count": 3, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "torchgeo.__version__ # check torchgeo version" + "torchgeo.__version__ #check torchgeo version" ] }, { @@ -80,47 +196,41 @@ "id": "f7f5ae92-1978-40a1-adcd-56a087f1f67a", "metadata": {}, "source": [ - "**Downloading the QuakeSet dataset**\n", - "\n", - "@misc{cambrin2024quakesetdatasetlowresourcemodels,\n", - " title={QuakeSet: A Dataset and Low-Resource Models to Monitor Earthquakes through Sentinel-1}, \n", - " author={Daniele Rege Cambrin and Paolo Garza},\n", - " year={2024},\n", - " eprint={2403.18116},\n", - " archivePrefix={arXiv},\n", - " primaryClass={cs.CV},\n", - " url={https://arxiv.org/abs/2403.18116}, \n", - "}" + "**Downloading the QuakeSet dataset**" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 12, "id": "57c7327c-e454-4ca6-bb50-fe67272ca3fd", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Downloading https://cdn-lfs-us-1.huggingface.co/repos/67/91/67919aae2184524f91dca1003493d36f4fc4799f55277f03c820fc4c90423eaa/11527e6a21c425b787d0952443e434f53d90a22ee16b75d20e17d54c2b091a78?response-content-disposition=inline%3B+filename*%3DUTF-8%27%27earthquakes.h5%3B+filename%3D%22earthquakes.h5%22%3B&Expires=1723272724&Policy=eyJTdGF0ZW1lbnQiOlt7IkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTcyMzI3MjcyNH19LCJSZXNvdXJjZSI6Imh0dHBzOi8vY2RuLWxmcy11cy0xLmh1Z2dpbmdmYWNlLmNvL3JlcG9zLzY3LzkxLzY3OTE5YWFlMjE4NDUyNGY5MWRjYTEwMDM0OTNkMzZmNGZjNDc5OWY1NTI3N2YwM2M4MjBmYzRjOTA0MjNlYWEvMTE1MjdlNmEyMWM0MjViNzg3ZDA5NTI0NDNlNDM0ZjUzZDkwYTIyZWUxNmI3NWQyMGUxN2Q1NGMyYjA5MWE3OD9yZXNwb25zZS1jb250ZW50LWRpc3Bvc2l0aW9uPSoifV19&Signature=AAAWpgowEaqyI7lPQs6QTjMYZq03OgAjaKuaW5fLO5gGfj5rbLd566zV5%7EiImjr0ejHSQi1-VIaimbTHd1AG%7E7SBOBqNgLmsbWOYjjTndDN5kWTEBlYggCiBa7msD57x-3ZENB%7ETdMtRx%7EmVdURvgTiWVxOfT3uisCo78EtEL8AycpuU9or6zX0ay17VFc5uPb7wWmU0kpMsG-yF62jo13DqGMScwEFy-OgntW9oPyfOd0NbCo18y7rjtSp1uSbGachkkPzzQcp-0vqX7Z31UjvFHJoom9R38FzcnbJOW0xk%7Ez2YQpcP6ZOwYXSyYZj1CM-7eTI3UPyAia4uMvPyHw__&Key-Pair-Id=K24J24Z295AEI9 to data/earthquakes.h5\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 9666460547/9666460547 [03:50<00:00, 41959334.33it/s]\n" + ] + } + ], "source": [ - "# We download the QuakeSet dataset available in torchgeo.\n", - "# We will work with the \"train\" split of the data only to show the workflow\n", + "#We download the QuakeSet dataset available in torchgeo.\n", "\n", - "train_ds = datasets.QuakeSet(\n", - " split=\"train\", download=False\n", - ") # Change download to True to download first time" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "3e60a646-d412-4212-a3a1-e0bf7edc363e", - "metadata": {}, - "outputs": [], - "source": [ - "# val_ds = datasets.QuakeSet(split=\"val\",download=False) #Can download and use other splits such \"val\",\"test\"" + "train_ds = datasets.QuakeSet(split=\"train\",download=True) #Change download to True to download first time\n", + "val_ds = datasets.QuakeSet(split=\"val\",download=True) \n", + "test_ds = datasets.QuakeSet(split=\"test\",download=True) " ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 13, "id": "c2ebdc0c-d4d8-4ba2-b97d-babb0d0d4d70", "metadata": {}, "outputs": [ @@ -130,25 +240,47 @@ "torch.Size([4, 512, 512])" ] }, - "execution_count": 6, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "# Checking the Sentinel-1 imagery size.\n", + "#Checking the Sentinel-1 imagery size. \n", "\n", - "# Each sample consists of pre and post event data with two channels - vv and vh of Sentinel-1\n", + "#Each sample consists of pre and post event data with two channels - vv and vh of Sentinel-1\n", "\n", "train_ds[0][\"image\"].shape" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 14, "id": "df682dd4-7eee-48a7-a42b-3c45cc2a7460", "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Downloading: \"https://clay-model-ckpt.s3.amazonaws.com/v0.5.7/mae_v0.5.7_epoch-13_val-loss-0.3098.ckpt\" to /teamspace/studios/this_studio/.cache/torch/hub/checkpoints/mae_v0.5.7_epoch-13_val-loss-0.3098.ckpt\n", + "100%|██████████| 1.61G/1.61G [01:49<00:00, 15.7MB/s]\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "78f781a4d10b4a05bffc926451cd3d4b", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "model.safetensors: 0%| | 0.00/343M [00:00" ] @@ -355,26 +538,105 @@ } ], "source": [ - "# Train a RandomForestClassifier on the embeddings\n", + "#Train a RandomForestClassifier on the embeddings\n", "\n", - "X_train, X_test, y_train, y_test = train_test_split(\n", - " X, y, random_state=42\n", - ") # Split the data into two parts - one for training and other for validation\n", + "clf = xgb.XGBClassifier() #Instantiate a RandomForestClassifier\n", + "clf.fit(train_fea, train_label) #Train on embeddings\n", "\n", - "clf = RandomForestClassifier() # Instantiate a RandomForestClassifier\n", - "clf.fit(X_train, y_train) # Train on embeddings\n", - "predictions = clf.predict(X_test) # Generate predictions\n", + "train_predictions = clf.predict(train_fea) #Generate predictions on training set\n", "\n", - "# Confusion matrix\n", - "cm = confusion_matrix(y_test, predictions, labels=clf.classes_)\n", - "disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=clf.classes_)\n", + "#Confusion matrix\n", + "cm = confusion_matrix(train_label, train_predictions, labels=clf.classes_)\n", + "disp = ConfusionMatrixDisplay(confusion_matrix=cm,display_labels=clf.classes_)\n", "disp.plot()\n", "plt.show()" ] }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 31, + "id": "0555d94e-bece-4338-9830-edf94241a7b6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "F1 score on training data: 0.9996207811907472\n" + ] + } + ], + "source": [ + "#check F1-score which is the metric for the competition which uses this data - \"ECML-PKDD 2024 - SMAC: Seismic Monitoring and Analysis Challenge\" https://www.codabench.org/competitions/2222/#/pages-tab had F1-score upward of 0.908 with training Deep Learning Models\n", + "trn_score = f1_score(train_label,train_predictions)\n", + "\n", + "print(\"F1 score on training data: \",trn_score)" + ] + }, + { + "cell_type": "markdown", + "id": "43c06fda-d86f-4ff4-8bce-b9885bc7dc90", + "metadata": {}, + "source": [ + "**Inspect the quality of predictions on Validation set**" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "5e883428-e797-44d4-9fee-e71a8d9af5f6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "((550, 1536), (550,))" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#Load the saved embeddings and ground truth (labels)\n", + "\n", + "val_fea= np.load(\"val_emb.npy\")\n", + "val_label = np.load(\"val_label.npy\")\n", + "\n", + "val_fea.shape,val_label.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "68c55870-43fd-452e-876e-b1c03e873fa2", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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AwDgApvN5pTsG+qGG25w/NynTx/17dtX3bp100cffaQrr7zS7fP/L8r4AABzMDywNaKuXbuqbdu2ys7OliRFR0fr6NGjTn2qq6tVVFR0ynn+UyHZAwBMoalX47vqyJEj+vHHH9W+fXtJUmJioo4dO6bdu3c7+nz44Yey2+1KSEhw6dz1KuO//fbb9T7htdde61IAAAD4o9LSUscoXZJycnKUmZmpiIgIRUREaN68eRo9erSio6N14MAB3XvvverevbuGDRsmSerVq5eGDx+uiRMnatmyZaqqqtKUKVN0ww03uLQSX6pnsh85cmS9TmaxWFRTU+NSAAAANJkmfL79rl27NHjwYMfn6dOnS5KSk5O1dOlSff7553rppZd07NgxxcTEaOjQoVqwYIHTgr+VK1dqypQpuvLKKxUQEKDRo0dryZIlLsdSr2Rvt9tdPjEAAGeTpn7r3aBBg2ScZvn/e++9d8ZzREREaNWqVS5d92TcmrMvLy93OwAAAJrEWb5ArzG5nOxramq0YMECdejQQa1atdLBgwclSbNmzdILL7zg8QABAIB7XE72Dz/8sNLT0/XII48oKCjI0X7eeefp+eef92hwAAB4jsUDm29yOdmvWLFCf//73zV27FgFBgY62uPj4/X11197NDgAADyGMn79fffdd+revXuddrvdrqqqKo8EBQAAPMflZN+7d29t27atTvvrr7+u888/3yNBAQDgcSYe2bv8bPzZs2crOTlZ3333nex2u958801lZWVpxYoVWr9+fWPECACA+/7nzXUNPt5HuTyyv+6667Ru3Tp98MEHatmypWbPnq19+/Zp3bp1uuqqqxojRgAA4IYGvfXusssu08aNGz0dCwAAjcZTr7j1RQ1+xe2uXbu0b98+SbXz+AMGDPBYUAAAeJy78+5mSvZHjhzRmDFj9Mknnyg8PFySdOzYMf3+97/X6tWr1bFjR0/HCAAA3ODynP0tt9yiqqoq7du3T0VFRSoqKtK+fftkt9t1yy23NEaMAAC478QCPXc2H+XyyH7Lli3avn27evTo4Wjr0aOHnn76aV122WUeDQ4AAE+xGLWbO8f7KpeTfWxs7EkfnlNTU+Py+3UBAGgyJp6zd7mMv3jxYk2dOlW7du1ytO3atUt33nmnHn30UY8GBwAA3FevkX3r1q1lsfw6V1FWVqaEhAQ1a1Z7eHV1tZo1a6abb75ZI0eObJRAAQBwi4kfqlOvZP/kk082chgAADQyE5fx65Xsk5OTGzsOAADQSBr8UB1JKi8vV2VlpVObzWZzKyAAABqFiUf2Li/QKysr05QpUxQZGamWLVuqdevWThsAAGclE7/1zuVkf++99+rDDz/U0qVLZbVa9fzzz2vevHmKiYnRihUrGiNGAADgBpfL+OvWrdOKFSs0aNAgjR8/Xpdddpm6d++uzp07a+XKlRo7dmxjxAkAgHtMvBrf5ZF9UVGRunbtKql2fr6oqEiSdOmll2rr1q2ejQ4AAA858QQ9dzZf5XKy79q1q3JyciRJPXv21KuvviqpdsR/4sU4AADg7OFysh8/frw+++wzSdJ9992ntLQ0BQcHKyUlRffcc4/HAwQAwCNMvEDP5Tn7lJQUx89DhgzR119/rd27d6t79+7q27evR4MDAADuc+s+e0nq3LmzOnfu7IlYAABoNBa5+dY7j0XS9OqV7JcsWVLvE06bNq3BwQAAAM+rV7J/4okn6nUyi8XilWQ/796b1ax5cJNfF2gKW/L+7u0QgEZTctyu1uc20cVMfOtdvZL9idX3AAD4LB6XCwAA/JXbC/QAAPAJJh7Zk+wBAKbg7lPwTPUEPQAAcGZbt27ViBEjFBMTI4vForVr1zr2VVVVaebMmerTp49atmypmJgY/eUvf1FeXp7TObp06SKLxeK0LVq0yOVYSPYAAHNo4ifolZWVKT4+XmlpaXX2/fzzz9qzZ49mzZqlPXv26M0331RWVpauvfbaOn3nz5+v/Px8xzZ16lTXAlEDy/jbtm3T3/72Nx04cECvv/66OnTooH/84x+Ki4vTpZde2pBTAgDQuJp4zj4pKUlJSUkn3RcWFqaNGzc6tT3zzDO66KKLdPjwYXXq1MnRHhoaqujoaJfD/V8uj+zfeOMNDRs2TCEhIfr0009VUVEhSSouLtbChQvdCgYAgLNdSUmJ03YiD7qruLhYFoulzkvlFi1apDZt2uj888/X4sWLVV1d7fK5XU72Dz30kJYtW6bnnntOzZs3d7Rfcskl2rNnj8sBAADQFDz1itvY2FiFhYU5ttTUVLdjKy8v18yZMzVmzBjZbDZH+7Rp07R69Wpt3rxZt956qxYuXKh7773X5fO7XMbPysrSwIED67SHhYXp2LFjLgcAAECT8NAT9HJzc50SstVqdSusqqoqXX/99TIMQ0uXLnXaN336dMfPffv2VVBQkG699Valpqa6dF2XR/bR0dHKzs6u0/7xxx+ra9eurp4OAICm4aEFejabzWlzJ9mfSPSHDh3Sxo0bnf6IOJmEhARVV1fr22+/dek6Lif7iRMn6s4779TOnTtlsViUl5enlStXasaMGbr99ttdPR0AAKZ0ItHv379fH3zwgdq0aXPGYzIzMxUQEKDIyEiXruVyGf++++6T3W7XlVdeqZ9//lkDBw6U1WrVjBkzGnQ7AAAATaGpH6pTWlrqVAnPyclRZmamIiIi1L59e/3hD3/Qnj17tH79etXU1KigoECSFBERoaCgIGVkZGjnzp0aPHiwQkNDlZGRoZSUFN14441q3bq1S7G4nOwtFov++te/6p577lF2drZKS0vVu3dvtWrVytVTAQDQdJr41rtdu3Zp8ODBjs8n5t+Tk5M1d+5cvf3225Kkfv36OR23efNmDRo0SFarVatXr9bcuXNVUVGhuLg4paSkOM3j11eDH5cbFBSk3r17N/RwAAD82qBBg2QYp/4L4XT7JKl///7asWOHR2JxOdkPHjxYFsupVzN++OGHbgUEAECjcLOMb6oX4fy23FBVVaXMzEzt3btXycnJnooLAADP4q139ffEE0+ctH3u3LkqLS11OyAAAOBZHnsRzo033qgXX3zRU6cDAMCzmvhFOGcTj73PPiMjQ8HBwZ46HQAAHmXm99m7nOxHjRrl9NkwDOXn52vXrl2aNWuWxwIDAACe4XKyDwsLc/ocEBCgHj16aP78+Ro6dKjHAgMAAJ7hUrKvqanR+PHj1adPH5ef3gMAgFeZeDW+Swv0AgMDNXToUN5uBwDwOZ56xa0vcnk1/nnnnaeDBw82RiwAAKARuJzsH3roIc2YMUPr169Xfn6+SkpKnDYAAM5aJrztTnJhzn7+/Pm6++67dfXVV0uSrr32WqfH5hqGIYvFopqaGs9HCQCAu0w8Z1/vZD9v3jzddttt2rx5c2PGAwAAPKzeyf7E23kuv/zyRgsGAIDGwkN16ul0b7sDAOCsRhm/fs4999wzJvyioiK3AgIAAJ7lUrKfN29enSfoAQDgCyjj19MNN9ygyMjIxooFAIDGY+Iyfr3vs2e+HgAA3+TyanwAAHySiUf29U72dru9MeMAAKBRMWcPAIC/M/HI3uVn4wMAAN/CyB4AYA4mHtmT7AEApmDmOXvK+AAA+DlG9gAAc6CMDwCAf6OMDwAA/BYjewCAOVDGBwDAz5k42VPGBwDAzzGyBwCYguWXzZ3jfRXJHgBgDpTxAQDwbyduvXNnc8XWrVs1YsQIxcTEyGKxaO3atU77DcPQ7Nmz1b59e4WEhGjIkCHav3+/U5+ioiKNHTtWNptN4eHhmjBhgkpLS13+7iR7AAAaQVlZmeLj45WWlnbS/Y888oiWLFmiZcuWaefOnWrZsqWGDRum8vJyR5+xY8fqyy+/1MaNG7V+/Xpt3bpVkyZNcjkWyvgAAHPwUBm/pKTEqdlqtcpqtdbpnpSUpKSkpJOfyjD05JNP6sEHH9R1110nSVqxYoWioqK0du1a3XDDDdq3b582bNig//znP7rgggskSU8//bSuvvpqPfroo4qJial36IzsAQDmYbix/SI2NlZhYWGOLTU11eUwcnJyVFBQoCFDhjjawsLClJCQoIyMDElSRkaGwsPDHYlekoYMGaKAgADt3LnTpesxsgcAwAW5ubmy2WyOzycb1Z9JQUGBJCkqKsqpPSoqyrGvoKBAkZGRTvubNWumiIgIR5/6ItkDAEzBU8/Gt9lsTsneF1DGBwCYgzslfHfn+38jOjpaklRYWOjUXlhY6NgXHR2to0ePOu2vrq5WUVGRo099kewBAGhicXFxio6O1qZNmxxtJSUl2rlzpxITEyVJiYmJOnbsmHbv3u3o8+GHH8putyshIcGl61HGBwCYQlO/4ra0tFTZ2dmOzzk5OcrMzFRERIQ6deqku+66Sw899JDOOeccxcXFadasWYqJidHIkSMlSb169dLw4cM1ceJELVu2TFVVVZoyZYpuuOEGl1biSyR7AIBZNPET9Hbt2qXBgwc7Pk+fPl2SlJycrPT0dN17770qKyvTpEmTdOzYMV166aXasGGDgoODHcesXLlSU6ZM0ZVXXqmAgACNHj1aS5YscTl0kj0AAI1g0KBBMoxT/4VgsVg0f/58zZ8//5R9IiIitGrVKrdjIdkDAEyhqcv4ZxOSPQDAHEz8IhySPQDAHEyc7Ln1DgAAP8fIHgBgCszZAwDg7yjjAwAAf8XIHgBgChbDkOU0973X53hfRbIHAJgDZXwAAOCvGNkDAEyB1fgAAPg7yvgAAMBfMbIHAJgCZXwAAPydicv4JHsAgCmYeWTPnD0AAH6OkT0AwBwo4wMA4P98uRTvDsr4AAD4OUb2AABzMIzazZ3jfRTJHgBgCqzGBwAAfouRPQDAHFiNDwCAf7PYazd3jvdVlPEBAPBzjOxRx+qF/1T7tqV12tds7q0n/3mJnrx7vc7vke+0760tPfX4ysuaKkSg3lY/HalP3g1XbrZVQcF29b7gZ034a55iu1c4+jx1b0d9ui1UPxY2V0gLu3pdUKYJf81Tp3Mq6pyvpChQt1/VQz/kB+mNfV+oVVhNU34duIMyPvCrWxeOVGDAr7/VcR1+0uMp7+qj3XGOtnVbe+rFtwc4PpdX8quEs9PnGa00YtwPOrffz6qpltIXtdcDY7rpuS1fK7hFbV32nL7/1RWjflK7DlU6/lOgXn4sWg+M6aaXdn6lwEDn8z1+dyfF9SrXD/lBXvg2cAer8b1k69atGjFihGJiYmSxWLR27VpvhoNfFJeGqKikhWNL7HNYR47alPlNe0ef8spmTn1+LucfPpydFq46qKF/KlKXHuXq9rty3f3kYR39Lkj7Pw9x9Ln6xh/V5+IyRcdW6py+/1XyzHx9nxekwlzn3+t1L7VRWUmg/nDb0ab+GvCEE/fZu7P5KK8m+7KyMsXHxystLc2bYeA0mgXW6KqL9+tfn5wryeJovyohW289vkLL57yuif/3b1mDqr0XJOCCspLaoXpo+MnL7+U/B+j9VyIU3alC7WKqHO2HvrFq1RPRuuepQ7Kw2gk+xqu116SkJCUlJdW7f0VFhSoqfp1DKykpaYyw8D8u6/etWoVU6l/bz3W0bfp3NxX82Eo/FrdU1w5FunX0v9Upqlizll3lxUiBM7PbpWVzOuh3F5aqS89yp33r0tvo+YdiVP5zoDp2K1fq6gNqHlQ7kqussCj1ji66ZVaeIjtWKf+w1Rvhw01mLuP71ERramqq5s2b5+0wTOXqS7P0772x+rG4paNt3bZejp8PfhehH4tD9OTd7yqmXYnyvrd5I0ygXp55oKMOfR2ix9bur7PvilE/qf/A4yo62lyvL43Uw7d20RNv7VdQsKHlqe3VqXu5rhz9kxeihseYeIGeTxWj7r//fhUXFzu23Nxcb4fk16IijmtArzyt/7jHafvty4mUJHVoV9wUYQEN8swDHbRzo02PvJ7tVJ4/oaXNrg5dK9Xn4jI9+Ny3ys226pN/hUmSMj8O1bb14UqKjVdSbLzuu76bJOmP552nFYujm/R7AA3hU8nearXKZrM5bWg8SZd8o2PHg7Xji06n7dc99kdJ0o/FLZoiLMAlhlGb6LdvCNMjr2UrulNlvY6RYVFVZe0/kbOez9HSD7K0dGPtdtejtQONx9bs17Xjf2jM8OFBJ8r47myu6NKliywWS51t8uTJkqRBgwbV2Xfbbbc1wjf3sTI+mo7FYijp999ow/ZzVWP/9W/CmHYlGnJRtnZ8EauSsmB17VikKddnKPObaB38ro0XIwZO7pkHOmrzmtaau/ygQlrZVXS09p+9lqE1soYYyj8UpC1vh2vA5ccVFlGt7/Ob69VnohQUYtdFV9auC4rp4vwHQnFR7Tk6nVPBffa+pInfevef//xHNTW//n7s3btXV111lf74xz862iZOnKj58+c7Prdo0TiDJpI9TmpAr+8U3aZU735yrlN7VXWABvT6Tn+4cq+CrdX6vqiltu6J04p3zvdSpMDprX+prSTpntHnOLXf/cRhDf1TkYKsdu3d2Uprnmun0uJAhbetVp+LS/XEW/sV3pa7TNBw7dq1c/q8aNEidevWTZdffrmjrUWLFoqObvypIK8m+9LSUmVnZzs+5+TkKDMzUxEREerU6fSlYzSuXV911OWTJtZp//6nVrrz0RFeiAhomPfyMk+7v010tR56+aBL54z/fekZz4uzj6dW4//2TjCr1Sqr9fR3aFRWVurll1/W9OnTZbH8ehvzypUr9fLLLys6OlojRozQrFmzGmV079Vkv2vXLg0ePNjxefr06ZKk5ORkpaeneykqAIBf8tBq/NjYWKfmOXPmaO7cuac9dO3atTp27JjGjRvnaPvzn/+szp07KyYmRp9//rlmzpyprKwsvfnmm24EeXJeTfaDBg2S4cNPJAIAmE9ubq7TAvEzjeol6YUXXlBSUpJiYmIcbZMmTXL83KdPH7Vv315XXnmlDhw4oG7dunk0ZubsAQCm4Kkyvqt3gx06dEgffPDBGUfsCQkJkqTs7GySPQAADWI3ajd3jm+A5cuXKzIyUtdcc81p+2VmZkqS2rdvf9p+DUGyBwCYgxeeoGe327V8+XIlJyerWbNfU+6BAwe0atUqXX311WrTpo0+//xzpaSkaODAgerbt68bQZ4cyR4AgEbywQcf6PDhw7r55pud2oOCgvTBBx/oySefVFlZmWJjYzV69Gg9+OCDjRIHyR4AYAoWuTln34Bjhg4detKF6LGxsdqyZUvDg3ERyR4AYA5N/AS9s4lPPRsfAAC4jpE9AMAUeJ89AAD+jvfZAwAAf8XIHgBgChbDkMWNRXbuHOttJHsAgDnYf9ncOd5HUcYHAMDPMbIHAJgCZXwAAPydiVfjk+wBAObAE/QAAIC/YmQPADAFnqAHAIC/o4wPAAD8FSN7AIApWOy1mzvH+yqSPQDAHCjjAwAAf8XIHgBgDjxUBwAA/2bmx+VSxgcAwM8xsgcAmIOJF+iR7AEA5mDIvXfS+26uJ9kDAMyBOXsAAOC3GNkDAMzBkJtz9h6LpMmR7AEA5mDiBXqU8QEA8HOM7AEA5mCXZHHzeB9FsgcAmAKr8QEAgN9iZA8AMAcTL9Aj2QMAzMHEyZ4yPgAAjWDu3LmyWCxOW8+ePR37y8vLNXnyZLVp00atWrXS6NGjVVhY2CixkOwBAOZwYmTvzuai3/3ud8rPz3dsH3/8sWNfSkqK1q1bp9dee01btmxRXl6eRo0a5clv7EAZHwBgDl649a5Zs2aKjo6u015cXKwXXnhBq1at0hVXXCFJWr58uXr16qUdO3bo4osvdiPQuhjZAwBM4cStd+5sklRSUuK0VVRUnPKa+/fvV0xMjLp27aqxY8fq8OHDkqTdu3erqqpKQ4YMcfTt2bOnOnXqpIyMDI9/d5I9AAAuiI2NVVhYmGNLTU09ab+EhASlp6drw4YNWrp0qXJycnTZZZfp+PHjKigoUFBQkMLDw52OiYqKUkFBgcdjpowPADAHD63Gz83Nlc1mczRbrdaTdk9KSnL83LdvXyUkJKhz58569dVXFRIS0vA4GoCRPQDAHOyG+5skm83mtJ0q2f9WeHi4zj33XGVnZys6OlqVlZU6duyYU5/CwsKTzvG7i2QPAEATKC0t1YEDB9S+fXsNGDBAzZs316ZNmxz7s7KydPjwYSUmJnr82pTxAQDm0MQP1ZkxY4ZGjBihzp07Ky8vT3PmzFFgYKDGjBmjsLAwTZgwQdOnT1dERIRsNpumTp2qxMREj6/El0j2AADTcDPZy7Vjjxw5ojFjxujHH39Uu3btdOmll2rHjh1q166dJOmJJ55QQECARo8erYqKCg0bNkzPPvusG/GdGskeAIBGsHr16tPuDw4OVlpamtLS0ho9FpI9AMAcTPxsfJI9AMAc7IZcLcXXPd43sRofAAA/x8geAGAOhr12c+d4H0WyBwCYA3P2AAD4OebsAQCAv2JkDwAwB8r4AAD4OUNuJnuPRdLkKOMDAODnGNkDAMyBMj4AAH7Obpfkxr3ydt+9z54yPgAAfo6RPQDAHCjjAwDg50yc7CnjAwDg5xjZAwDMwcSPyyXZAwBMwTDsMtx4c507x3obyR4AYA6G4d7onDl7AABwtmJkDwAwB8PNOXsfHtmT7AEA5mC3SxY35t19eM6eMj4AAH6OkT0AwBwo4wMA4N8Mu12GG2V8X771jjI+AAB+jpE9AMAcKOMDAODn7IZkMWeyp4wPAICfY2QPADAHw5Dkzn32vjuyJ9kDAEzBsBsy3CjjGyR7AADOcoZd7o3sufUOAACcpRjZAwBMgTI+AAD+zsRlfJ9O9if+yqquLvdyJEDjKTnuu//AAGdSUlr7+90Uo+ZqVbn1TJ1qVXkumCZmMXy4LnHkyBHFxsZ6OwwAgJtyc3PVsWPHRjl3eXm54uLiVFBQ4Pa5oqOjlZOTo+DgYA9E1nR8Otnb7Xbl5eUpNDRUFovF2+GYQklJiWJjY5WbmyubzebtcACP4ve76RmGoePHjysmJkYBAY23Zry8vFyVlZVunycoKMjnEr3k42X8gICARvtLEKdns9n4xxB+i9/vphUWFtbo1wgODvbJJO0p3HoHAICfI9kDAODnSPZwidVq1Zw5c2S1Wr0dCuBx/H7DX/n0Aj0AAHBmjOwBAPBzJHsAAPwcyR4AAD9HsgcAwM+R7FFvaWlp6tKli4KDg5WQkKB///vf3g4J8IitW7dqxIgRiomJkcVi0dq1a70dEuBRJHvUyyuvvKLp06drzpw52rNnj+Lj4zVs2DAdPXrU26EBbisrK1N8fLzS0tK8HQrQKLj1DvWSkJCgCy+8UM8884yk2vcSxMbGaurUqbrvvvu8HB3gORaLRWvWrNHIkSO9HQrgMYzscUaVlZXavXu3hgwZ4mgLCAjQkCFDlJGR4cXIAAD1QbLHGf3www+qqalRVFSUU3tUVJRHXhkJAGhcJHsAAPwcyR5n1LZtWwUGBqqwsNCpvbCwUNHR0V6KCgBQXyR7nFFQUJAGDBigTZs2Odrsdrs2bdqkxMREL0YGAKiPZt4OAL5h+vTpSk5O1gUXXKCLLrpITz75pMrKyjR+/Hhvhwa4rbS0VNnZ2Y7POTk5yszMVEREhDp16uTFyADP4NY71NszzzyjxYsXq6CgQP369dOSJUuUkJDg7bAAt3300UcaPHhwnfbk5GSlp6c3fUCAh5HsAQDwc8zZAwDg50j2AAD4OZI9AAB+jmQPAICfI9kDAODnSPYAAPg5kj0AAH6OZA8AgJ8j2QNuGjdunEaOHOn4PGjQIN11111NHsdHH30ki8WiY8eOnbKPxWLR2rVr633OuXPnql+/fm7F9e2338pisSgzM9Ot8wBoOJI9/NK4ceNksVhksVgUFBSk7t27a/78+aqurm70a7/55ptasGBBvfrWJ0EDgLt4EQ781vDhw7V8+XJVVFTo3Xff1eTJk9W8eXPdf//9dfpWVlYqKCjII9eNiIjwyHkAwFMY2cNvWa1WRUdHq3Pnzrr99ts1ZMgQvf3225J+Lb0//PDDiomJUY8ePSRJubm5uv766xUeHq6IiAhdd911+vbbbx3nrKmp0fTp0xUeHq42bdro3nvv1W9fL/HbMn5FRYVmzpyp2NhYWa1Wde/eXS+88IK+/fZbx8tXWrduLYvFonHjxkmqfYVwamqq4uLiFBISovj4eL3++utO13n33Xd17rnnKiQkRIMHD3aKs75mzpypc889Vy1atFDXrl01a9YsVVVV1en3t7/9TbGxsWrRooWuv/56FRcXO+1//vnn1atXLwUHB6tnz5569tlnXY4FQOMh2cM0QkJCVFlZ6fi8adMmZWVlaePGjVq/fr2qqqo0bNgwhYaGatu2bfrkk0/UqlUrDR8+3HHcY489pvT0dL344ov6+OOPVVRUpDVr1pz2un/5y1/0z3/+U0uWLNG+ffv0t7/9Ta1atVJsbKzeeOMNSVJWVpby8/P11FNPSZJSU1O1YsUKLVu2TF9++aVSUlJ04403asuWLZJq/ygZNWqURowYoczMTN1yyy267777XP7vJDQ0VOnp6frqq6/01FNP6bnnntMTTzzh1Cc7O1uvvvqq1q1bpw0bNujTTz/VHXfc4di/cuVKzZ49Ww8//LD27dunhQsXatasWXrppZdcjgdAIzEAP5ScnGxcd911hmEYht1uNzZu3GhYrVZjxowZjv1RUVFGRUWF45h//OMfRo8ePQy73e5oq6ioMEJCQoz33nvPMAzDaN++vfHII4849ldVVRkdO3Z0XMswDOPyyy837rzzTsMwDCMrK8uQZGzcuPGkcW7evNmQZPz000+OtvLycqNFixbG9u3bnfpOmDDBGDNmjGEYhnH//fcbvXv3dto/c+bMOuf6LUnGmjVrTrl/8eLFxoABAxyf58yZYwQGBhpHjhxxtP3rX/8yAgICjPz8fMMwDKNbt27GqlWrnM6zYMECIzEx0TAMw8jJyTEkGZ9++ukprwugcTFnD7+1fv16tWrVSlVVVbLb7frzn/+suXPnOvb36dPHaZ7+s88+U3Z2tkJDQ53OU15ergMHDqi4uFj5+flKSEhw7GvWrJkuuOCCOqX8EzIzMxUYGKjLL7+83nFnZ2fr559/1lVXXeXUXllZqfPPP1+StG/fPqc4JCkxMbHe1zjhlVde0ZIlS3TgwAGVlpaqurpaNpvNqU+nTp3UoUMHp+vY7XZlZWUpNDRUBw4c0IQJEzRx4kRHn+rqaoWFhbkcD4DGQbKH3xo8eLCWLl2qoKAgxcTEqFkz51/3li1bOn0uLS3VgAEDtHLlyjrnateuXYNiCAkJcfmY0tJSSdI777zjlGSl2nUInpKRkaGxY8dq3rx5GjZsmMLCwrR69Wo99thjLsf63HPP1fnjIzAw0GOxAnAPyR5+q2XLlurevXu9+/fv31+vvPKKIiMj64xuT2jfvr127typgQMHSqodwe7evVv9+/c/af8+ffrIbrdry5YtGjJkSJ39JyoLNTU1jrbevXvLarXq8OHDp6wI9OrVy7HY8IQdO3ac+Uv+j+3bt6tz587661//6mg7dOhQnX6HDx9WXl6eYmJiHNcJCAhQjx49FBUVpZiYGB08eFBjx4516foAmg4L9IBfjB07Vm3bttV1112nbdu2KScnRx999JGmTZumI0eOSJLuvPNOLVq0SGvXrtXXX3+tO+6447T3yHfp0kXJycm6+eabtXbtWsc5X331VUlS586dZbFYtH79en3//fcqLS1VaGioZsyYoZSUFL300ks6cOCA9uzZo6efftqx6O22227T/v37dc899ygrK0urVq1Senq6S9/3nHPO0eHDh7V69WodOHBAS5YsOeliw+DgYCUnJ+uzzz7Ttm3bNG3aNF1//fWKjo6WJM2bN0+pqalasmSJvvnmG33xxRdavny5Hn/8cZfiAdB4SPbAL1q0aKGtW7eqU6dOGjVqlHr16qUJEyaovLzcMdK/++67ddNNNyk5OVmJiYkKDQ3V//3f/532vEuXLtUf/vAH3XHHHerZs6cmTpyosrIySVKHDh00b9483XfffYqKitKUKVMkSQsWLNCsWbOUmpqqXr16afjw4XrnnXcUFxcnqXYe/Y033tDatWsVHx+vZcuWaeHChS5932uvvVYpKSmaMmWK+vXrp+3bt2vWrFl1+nXv3l2jRo3S1VdfraFDh6pv375Ot9bdcsstev7557V8+XL16dNHl19+udLT0x2xAvA+i3GqlUUAAMAvMLIHAMDPkewBAPBzJHsAAPwcyR4AAD9HsgcAwM+R7AEA8HMkewAA/BzJHgAAP0eyBwDAz5HsAQDwcyR7AAD83P8DRXki9hww4KcAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "val_predictions = clf.predict(val_fea) #Generate predictions on validation set\n", + "\n", + "#Confusion matrix\n", + "cm = confusion_matrix(val_label, val_predictions, labels=clf.classes_)\n", + "disp = ConfusionMatrixDisplay(confusion_matrix=cm,display_labels=clf.classes_)\n", + "disp.plot()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 34, "id": "f0f6fb57-3a2b-49eb-bfe5-14c37dedbdd0", "metadata": {}, "outputs": [ @@ -382,19 +644,106 @@ "name": "stdout", "output_type": "stream", "text": [ - "F1 score: 0.9088050314465408\n" + "F1 score on validation data: 0.8224956063268892\n" ] } ], "source": [ - "# check F1-score which is the metric for the competition which uses this data - \"ECML-PKDD 2024 - SMAC: Seismic Monitoring and Analysis Challenge\" https://www.codabench.org/competitions/2222/#/pages-tab had F1-score upward of 0.908 with training Deep Learning Models\n", + "#check F1-score which is the metric for the competition which uses this data - \"ECML-PKDD 2024 - SMAC: Seismic Monitoring and Analysis Challenge\" https://www.codabench.org/competitions/2222/#/pages-tab had F1-score upward of 0.908 with training Deep Learning Models\n", "\n", - "from sklearn.metrics import f1_score\n", + "val_score = f1_score(val_label,val_predictions)\n", "\n", - "score = f1_score(y_test, predictions)\n", + "print(\"F1 score on validation data: \",val_score)\n" + ] + }, + { + "cell_type": "markdown", + "id": "28597f36-3ae5-498e-88ea-23fd1b6ede1d", + "metadata": {}, + "source": [ + "**Inspect the quality of predictions on Test set**" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "0390b46d-d161-4d85-8b3b-9a739a81c698", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "((511, 1536), (511,))" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#Load the saved embeddings and ground truth (labels)\n", + "\n", + "test_fea= np.load(\"test_emb.npy\")\n", + "test_label = np.load(\"test_label.npy\")\n", "\n", - "print(\"F1 score: \", score)" + "test_fea.shape,test_label.shape" ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "8cb261a8-417e-457d-bd89-ecd4a79e7b05", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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UVHKy8vzUlQAgOoi2QMA4OdI9rigpk2bKjAwUPn5+U778/PzFR0d7aWoAADVRbLHBQUFBalPnz7asGGDY5/dbteGDRuUkJDgxcgAANVRz9sBwDdMnTpVSUlJuuyyy3TFFVfomWeeUXFxscaOHevt0AC3FRUVKTs72/E5JydHmZmZioiIUKtWrbwYGeAZ3HqHanvuuee0YMEC5eXlqWfPnlq0aJHi4+O9HRbgto8++kj9+vWrsj8pKUnp6em1HxDgYSR7AAD8HHP2AAD4OZI9AAB+jmQPAICfI9kDAODnSPYAAPg5kj0AAH6OZA8AgJ8j2QMA4OdI9oCbxowZo2HDhjk+9+3bV/fdd1+tx/HRRx/JYrHo+PHj5xxjsVi0atWqal8zJSVFPXv2dCuu/fv3y2KxKDMz063rALh4JHv4pTFjxshischisSgoKEgdOnTQ3LlzVVFRUePf/c4772jevHnVGludBA0A7uJFOPBbgwYN0tKlS1VaWqr3339fEydOVP369TVjxowqY8vKyhQUFOSR742IiPDIdQDAU6js4besVquio6PVunVr3X333erfv7/ee+89Sb+03h977DHFxMSoU6dOkqRDhw7p1ltvVXh4uCIiIjR06FDt37/fcc3KykpNnTpV4eHhatKkiR588EH99vUSv23jl5aWavr06YqNjZXValWHDh308ssva//+/Y6XrzRu3FgWi0VjxoyRdPoVwqmpqWrbtq1CQkIUFxent956y+l73n//fXXs2FEhISHq16+fU5zVNX36dHXs2FENGjRQu3btNHPmTJWXl1cZ97e//U2xsbFq0KCBbr31Vp04ccLp+EsvvaQuXbooODhYnTt31vPPP+9yLABqDskephESEqKysjLH5w0bNigrK0vr1q3TmjVrVF5eroEDByo0NFRbtmzRxx9/rEaNGmnQoEGO85566imlp6dryZIl2rp1qwoKCrRy5crzfu+f//xn/fOf/9SiRYu0Z88e/e1vf1OjRo0UGxurt99+W5KUlZWlI0eO6K9//askKTU1Va+++qpeeOEFff3110pOTtbo0aO1adMmSad/KRk+fLiGDBmizMxM3XHHHXrooYdc/m8SGhqq9PR0ffPNN/rrX/+qF198UQsXLnQak52drTfeeEOrV6/W2rVr9dlnn+mee+5xHF+2bJlmzZqlxx57THv27NHjjz+umTNn6pVXXnE5HgA1xAD8UFJSkjF06FDDMAzDbrcb69atM6xWqzFt2jTH8aioKKO0tNRxzj/+8Q+jU6dOht1ud+wrLS01QkJCjA8++MAwDMNo3ry58cQTTziOl5eXGy1btnR8l2EYxnXXXWfce++9hmEYRlZWliHJWLdu3Vnj/PDDDw1Jxk8//eTYV1JSYjRo0MDYtm2b09hx48YZI0eONAzDMGbMmGF07drV6fj06dOrXOu3JBkrV6485/EFCxYYffr0cXyePXu2ERgYaBw+fNix79///rcREBBgHDlyxDAMw2jfvr2xfPlyp+vMmzfPSEhIMAzDMHJycgxJxmeffXbO7wVQs5izh99as2aNGjVqpPLyctntdv3xj39USkqK43j37t2d5uk///xzZWdnKzQ01Ok6JSUl2rdvn06cOKEjR44oPj7ecaxevXq67LLLqrTyz8jMzFRgYKCuu+66asednZ2tU6dO6cYbb3TaX1ZWpl69ekmS9uzZ4xSHJCUkJFT7O854/fXXtWjRIu3bt09FRUWqqKiQzWZzGtOqVSu1aNHC6XvsdruysrIUGhqqffv2ady4cRo/frxjTEVFhcLCwlyOB0DNINnDb/Xr10+LFy9WUFCQYmJiVK+e81/3hg0bOn0uKipSnz59tGzZsirXatas2UXFEBIS4vI5RUVFkqR//etfTklWOr0OwVMyMjI0atQozZkzRwMHDlRYWJhWrFihp556yuVYX3zxxSq/fAQGBnosVgDuIdnDbzVs2FAdOnSo9vjevXvr9ddfV2RkZJXq9ozmzZtrx44duvbaayWdrmB37dql3r17n3V89+7dZbfbtWnTJvXv37/K8TOdhcrKSse+rl27ymq16uDBg+fsCHTp0sWx2PCM7du3X/iH/JVt27apdevWeuSRRxz7Dhw4UGXcwYMHlZubq5iYGMf3BAQEqFOnToqKilJMTIy+++47jRo1yqXvB1B7WKAH/M+oUaPUtGlTDR06VFu2bFFOTo4++ugjTZkyRYcPH5Yk3XvvvZo/f75WrVqlb7/9Vvfcc89575Fv06aNkpKSdPvtt2vVqlWOa77xxhuSpNatW8tisWjNmjU6duyYioqKFBoaqmnTpik5OVmvvPKK9u3bp927d+vZZ591LHq76667tHfvXj3wwAPKysrS8uXLlZ6e7tLPe8kll+jgwYNasWKF9u3bp0WLFp11sWFwcLCSkpL0+eefa8uWLZoyZYpuvfVWRUdHS5LmzJmj1NRULVq0SP/973/15ZdfaunSpXr66addigdAzSHZA//ToEEDbd68Wa1atdLw4cPVpUsXjRs3TiUlJY5K//7779ef/vQnJSUlKSEhQaGhobrlllvOe93Fixfrd7/7ne655x517txZ48ePV3FxsSSpRYsWmjNnjh566CFFRUVp0qRJkqR58+Zp5syZSk1NVZcuXTRo0CD961//Utu2bSWdnkd/++23tWrVKsXFxemFF17Q448/7tLPe/PNNys5OVmTJk1Sz549tW3bNs2cObPKuA4dOmj48OEaPHiwBgwYoB49ejjdWnfHHXfopZde0tKlS9W9e3ddd911Sk9Pd8QKwPssxrlWFgEAAL9AZQ8AgJ8j2QMA4OdI9gAA+DmSPQAAfo5kDwCAnyPZAwDg50j2AAD4OZI9AAB+jmQPAICfI9kDAODnSPYAAPi5/x/u5h/6qO6GWAAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "test_predictions = clf.predict(test_fea) #Generate predictions on test set\n", + "\n", + "#Confusion matrix\n", + "cm = confusion_matrix(test_label, test_predictions, labels=clf.classes_)\n", + "disp = ConfusionMatrixDisplay(confusion_matrix=cm,display_labels=clf.classes_)\n", + "disp.plot()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "302ebd4b-0025-411f-955e-2f4420d3c11f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "F1 score on validation data: 0.8550458715596331\n" + ] + } + ], + "source": [ + "test_score = f1_score(test_label,test_predictions)\n", + "\n", + "print(\"F1 score on validation data: \",test_score)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "07dea230-2fd2-4844-9e1e-d9dc18171010", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { From 0b498e260be7e0efbd9c2b9e19a8976abe79cf29 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2024 08:17:56 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/tutorials/Earthquake-preds-Clay.ipynb | 179 ++++++++++++--------- 1 file changed, 101 insertions(+), 78 deletions(-) diff --git a/docs/tutorials/Earthquake-preds-Clay.ipynb b/docs/tutorials/Earthquake-preds-Clay.ipynb index 0756ace5..74caaedb 100644 --- a/docs/tutorials/Earthquake-preds-Clay.ipynb +++ b/docs/tutorials/Earthquake-preds-Clay.ipynb @@ -129,7 +129,7 @@ "metadata": {}, "outputs": [], "source": [ - "#git clone https://github.com/Clay-foundation/model #Need to run this for the first time" + "# git clone https://github.com/Clay-foundation/model #Need to run this for the first time" ] }, { @@ -150,7 +150,7 @@ "metadata": {}, "outputs": [], "source": [ - "#importing the required modules\n", + "# importing the required modules\n", "\n", "import torchgeo\n", "import torchgeo.datasets as datasets\n", @@ -188,7 +188,7 @@ } ], "source": [ - "torchgeo.__version__ #check torchgeo version" + "torchgeo.__version__ # check torchgeo version" ] }, { @@ -221,11 +221,13 @@ } ], "source": [ - "#We download the QuakeSet dataset available in torchgeo.\n", + "# We download the QuakeSet dataset available in torchgeo.\n", "\n", - "train_ds = datasets.QuakeSet(split=\"train\",download=True) #Change download to True to download first time\n", - "val_ds = datasets.QuakeSet(split=\"val\",download=True) \n", - "test_ds = datasets.QuakeSet(split=\"test\",download=True) " + "train_ds = datasets.QuakeSet(\n", + " split=\"train\", download=True\n", + ") # Change download to True to download first time\n", + "val_ds = datasets.QuakeSet(split=\"val\", download=True)\n", + "test_ds = datasets.QuakeSet(split=\"test\", download=True)" ] }, { @@ -246,9 +248,9 @@ } ], "source": [ - "#Checking the Sentinel-1 imagery size. \n", + "# Checking the Sentinel-1 imagery size.\n", "\n", - "#Each sample consists of pre and post event data with two channels - vv and vh of Sentinel-1\n", + "# Each sample consists of pre and post event data with two channels - vv and vh of Sentinel-1\n", "\n", "train_ds[0][\"image\"].shape" ] @@ -291,7 +293,7 @@ } ], "source": [ - "#Download Clay-1 model\n", + "# Download Clay-1 model\n", "\n", "device = torch.device(\"cuda\") if torch.cuda.is_available() else torch.device(\"cpu\")\n", "ckpt = \"https://clay-model-ckpt.s3.amazonaws.com/v0.5.7/mae_v0.5.7_epoch-13_val-loss-0.3098.ckpt\"\n", @@ -312,33 +314,37 @@ "metadata": {}, "outputs": [], "source": [ - "#Dataset class to use for loading the data\n", + "# Dataset class to use for loading the data\n", + "\n", "\n", "class EarthQuakeDataset:\n", - " def __init__(self,ds):\n", + " def __init__(self, ds):\n", " self.ds = ds\n", - " \n", + "\n", " def __len__(self):\n", " return len(self.ds)\n", "\n", - " def __getitem__(self,idx):\n", - " pre_image = self.ds[idx][\"image\"][:2,:,:] #First two images are Sentinel-1 images (vv & vh band) of pre-event \n", - " post_image = self.ds[idx][\"image\"][2:,:,:] #Last two images are Sentinel-1 images (vv & vh band) of post-event\n", + " def __getitem__(self, idx):\n", + " pre_image = self.ds[idx][\"image\"][\n", + " :2, :, :\n", + " ] # First two images are Sentinel-1 images (vv & vh band) of pre-event\n", + " post_image = self.ds[idx][\"image\"][\n", + " 2:, :, :\n", + " ] # Last two images are Sentinel-1 images (vv & vh band) of post-event\n", " label = self.ds[idx][\"label\"]\n", "\n", " sample = {\n", " \"pixels1\": pre_image, # 2 x 512 x 512\n", - " \"pixels2\": post_image, # 2 x 512 x 512\n", + " \"pixels2\": post_image, # 2 x 512 x 512\n", " \"time\": torch.zeros(4), # Placeholder for time information\n", " \"latlon\": torch.zeros(4), # Placeholder for latlon information\n", - " \"label\":label\n", + " \"label\": label,\n", " }\n", "\n", - " \n", " return sample\n", "\n", "\n", - "#Construct training/validaton/test dataset object\n", + "# Construct training/validaton/test dataset object\n", "train_dataset = EarthQuakeDataset(train_ds)\n", "validation_dataset = EarthQuakeDataset(val_ds)\n", "test_dataset = EarthQuakeDataset(test_ds)" @@ -351,12 +357,21 @@ "metadata": {}, "outputs": [], "source": [ - "#Dataloaders from dataset\n", + "# Dataloaders from dataset\n", "BS = 8\n", "\n", - "train_dl = torch.utils.data.DataLoader(train_dataset,batch_size=BS,shuffle=True,generator=torch.Generator(device=device))\n", - "val_dl = torch.utils.data.DataLoader(validation_dataset,batch_size=BS,shuffle=False,generator=torch.Generator(device=device))\n", - "test_dl = torch.utils.data.DataLoader(test_dataset,batch_size=BS,shuffle=False,generator=torch.Generator(device=device))" + "train_dl = torch.utils.data.DataLoader(\n", + " train_dataset, batch_size=BS, shuffle=True, generator=torch.Generator(device=device)\n", + ")\n", + "val_dl = torch.utils.data.DataLoader(\n", + " validation_dataset,\n", + " batch_size=BS,\n", + " shuffle=False,\n", + " generator=torch.Generator(device=device),\n", + ")\n", + "test_dl = torch.utils.data.DataLoader(\n", + " test_dataset, batch_size=BS, shuffle=False, generator=torch.Generator(device=device)\n", + ")" ] }, { @@ -366,49 +381,57 @@ "metadata": {}, "outputs": [], "source": [ - "#Generate embeddings for the data\n", + "# Generate embeddings for the data\n", + "\n", + "\n", + "def generate_and_save_embeddings(dl, split=\"train\"):\n", + " gsd = torch.tensor(10, device=device) # Ground sampling distance for Sentinel-1\n", + " waves = torch.tensor([3.5, 4.0], device=device) # wavelengths for Sentinel-1\n", "\n", - "def generate_and_save_embeddings(dl,split=\"train\"):\n", - " \n", - " gsd = torch.tensor(10, device=device) #Ground sampling distance for Sentinel-1\n", - " waves = torch.tensor([3.5,4.0], device=device) #wavelengths for Sentinel-1\n", - " \n", " embeddings1 = []\n", " embeddings2 = []\n", " target = []\n", - " \n", - " for bid,batch in enumerate(tqdm(dl)):\n", - " \n", + "\n", + " for bid, batch in enumerate(tqdm(dl)):\n", " datacube1 = {\n", " \"pixels\": batch[\"pixels1\"].to(device),\n", - " \"time\":batch[\"time\"].to(device),\n", - " \"latlon\":batch[\"latlon\"].to(device),\n", + " \"time\": batch[\"time\"].to(device),\n", + " \"latlon\": batch[\"latlon\"].to(device),\n", " \"gsd\": gsd,\n", " \"waves\": waves,\n", " }\n", - " \n", + "\n", " datacube2 = {\n", " \"pixels\": batch[\"pixels2\"].to(device),\n", - " \"time\":batch[\"time\"].to(device),\n", - " \"latlon\":batch[\"latlon\"].to(device),\n", + " \"time\": batch[\"time\"].to(device),\n", + " \"latlon\": batch[\"latlon\"].to(device),\n", " \"gsd\": gsd,\n", " \"waves\": waves,\n", " }\n", - " \n", + "\n", " with torch.no_grad():\n", - " unmsk_patch1, unmsk_idx1, msk_idx1, msk_matrix1 = model.model.encoder(datacube1)\n", - " unmsk_patch2, unmsk_idx2, msk_idx2, msk_matrix2 = model.model.encoder(datacube2)\n", - " \n", + " unmsk_patch1, unmsk_idx1, msk_idx1, msk_matrix1 = model.model.encoder(\n", + " datacube1\n", + " )\n", + " unmsk_patch2, unmsk_idx2, msk_idx2, msk_matrix2 = model.model.encoder(\n", + " datacube2\n", + " )\n", + "\n", " emb1 = unmsk_patch1[:, 0, :].cpu().numpy()\n", " emb2 = unmsk_patch2[:, 0, :].cpu().numpy()\n", - " \n", + "\n", " embeddings1.append(emb1)\n", " embeddings2.append(emb2)\n", " target.append(batch[\"label\"].cpu().numpy())\n", "\n", - " #Saving embeddings and ground truth (label) data\n", - " np.save(f\"{split}_emb.npy\",np.concatenate((np.concatenate(embeddings1),np.concatenate(embeddings2)),axis=1))\n", - " np.save(f\"{split}_label.npy\",np.concatenate(target))\n" + " # Saving embeddings and ground truth (label) data\n", + " np.save(\n", + " f\"{split}_emb.npy\",\n", + " np.concatenate(\n", + " (np.concatenate(embeddings1), np.concatenate(embeddings2)), axis=1\n", + " ),\n", + " )\n", + " np.save(f\"{split}_label.npy\", np.concatenate(target))" ] }, { @@ -433,7 +456,7 @@ } ], "source": [ - "generate_and_save_embeddings(train_dl,\"train\")" + "generate_and_save_embeddings(train_dl, \"train\")" ] }, { @@ -466,7 +489,7 @@ } ], "source": [ - "generate_and_save_embeddings(val_dl,\"val\")" + "generate_and_save_embeddings(val_dl, \"val\")" ] }, { @@ -491,7 +514,7 @@ } ], "source": [ - "generate_and_save_embeddings(test_dl,\"test\")" + "generate_and_save_embeddings(test_dl, \"test\")" ] }, { @@ -512,12 +535,12 @@ } ], "source": [ - "#Load the saved embeddings and ground truth (labels)\n", + "# Load the saved embeddings and ground truth (labels)\n", "\n", - "train_fea= np.load(\"train_emb.npy\")\n", + "train_fea = np.load(\"train_emb.npy\")\n", "train_label = np.load(\"train_label.npy\")\n", "\n", - "train_fea.shape,train_label.shape" + "train_fea.shape, train_label.shape" ] }, { @@ -538,16 +561,16 @@ } ], "source": [ - "#Train a RandomForestClassifier on the embeddings\n", + "# Train a RandomForestClassifier on the embeddings\n", "\n", - "clf = xgb.XGBClassifier() #Instantiate a RandomForestClassifier\n", - "clf.fit(train_fea, train_label) #Train on embeddings\n", + "clf = xgb.XGBClassifier() # Instantiate a RandomForestClassifier\n", + "clf.fit(train_fea, train_label) # Train on embeddings\n", "\n", - "train_predictions = clf.predict(train_fea) #Generate predictions on training set\n", + "train_predictions = clf.predict(train_fea) # Generate predictions on training set\n", "\n", - "#Confusion matrix\n", + "# Confusion matrix\n", "cm = confusion_matrix(train_label, train_predictions, labels=clf.classes_)\n", - "disp = ConfusionMatrixDisplay(confusion_matrix=cm,display_labels=clf.classes_)\n", + "disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=clf.classes_)\n", "disp.plot()\n", "plt.show()" ] @@ -567,10 +590,10 @@ } ], "source": [ - "#check F1-score which is the metric for the competition which uses this data - \"ECML-PKDD 2024 - SMAC: Seismic Monitoring and Analysis Challenge\" https://www.codabench.org/competitions/2222/#/pages-tab had F1-score upward of 0.908 with training Deep Learning Models\n", - "trn_score = f1_score(train_label,train_predictions)\n", + "# check F1-score which is the metric for the competition which uses this data - \"ECML-PKDD 2024 - SMAC: Seismic Monitoring and Analysis Challenge\" https://www.codabench.org/competitions/2222/#/pages-tab had F1-score upward of 0.908 with training Deep Learning Models\n", + "trn_score = f1_score(train_label, train_predictions)\n", "\n", - "print(\"F1 score on training data: \",trn_score)" + "print(\"F1 score on training data: \", trn_score)" ] }, { @@ -599,12 +622,12 @@ } ], "source": [ - "#Load the saved embeddings and ground truth (labels)\n", + "# Load the saved embeddings and ground truth (labels)\n", "\n", - "val_fea= np.load(\"val_emb.npy\")\n", + "val_fea = np.load(\"val_emb.npy\")\n", "val_label = np.load(\"val_label.npy\")\n", "\n", - "val_fea.shape,val_label.shape" + "val_fea.shape, val_label.shape" ] }, { @@ -625,11 +648,11 @@ } ], "source": [ - "val_predictions = clf.predict(val_fea) #Generate predictions on validation set\n", + "val_predictions = clf.predict(val_fea) # Generate predictions on validation set\n", "\n", - "#Confusion matrix\n", + "# Confusion matrix\n", "cm = confusion_matrix(val_label, val_predictions, labels=clf.classes_)\n", - "disp = ConfusionMatrixDisplay(confusion_matrix=cm,display_labels=clf.classes_)\n", + "disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=clf.classes_)\n", "disp.plot()\n", "plt.show()" ] @@ -649,11 +672,11 @@ } ], "source": [ - "#check F1-score which is the metric for the competition which uses this data - \"ECML-PKDD 2024 - SMAC: Seismic Monitoring and Analysis Challenge\" https://www.codabench.org/competitions/2222/#/pages-tab had F1-score upward of 0.908 with training Deep Learning Models\n", + "# check F1-score which is the metric for the competition which uses this data - \"ECML-PKDD 2024 - SMAC: Seismic Monitoring and Analysis Challenge\" https://www.codabench.org/competitions/2222/#/pages-tab had F1-score upward of 0.908 with training Deep Learning Models\n", "\n", - "val_score = f1_score(val_label,val_predictions)\n", + "val_score = f1_score(val_label, val_predictions)\n", "\n", - "print(\"F1 score on validation data: \",val_score)\n" + "print(\"F1 score on validation data: \", val_score)" ] }, { @@ -682,12 +705,12 @@ } ], "source": [ - "#Load the saved embeddings and ground truth (labels)\n", + "# Load the saved embeddings and ground truth (labels)\n", "\n", - "test_fea= np.load(\"test_emb.npy\")\n", + "test_fea = np.load(\"test_emb.npy\")\n", "test_label = np.load(\"test_label.npy\")\n", "\n", - "test_fea.shape,test_label.shape" + "test_fea.shape, test_label.shape" ] }, { @@ -708,11 +731,11 @@ } ], "source": [ - "test_predictions = clf.predict(test_fea) #Generate predictions on test set\n", + "test_predictions = clf.predict(test_fea) # Generate predictions on test set\n", "\n", - "#Confusion matrix\n", + "# Confusion matrix\n", "cm = confusion_matrix(test_label, test_predictions, labels=clf.classes_)\n", - "disp = ConfusionMatrixDisplay(confusion_matrix=cm,display_labels=clf.classes_)\n", + "disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=clf.classes_)\n", "disp.plot()\n", "plt.show()" ] @@ -732,9 +755,9 @@ } ], "source": [ - "test_score = f1_score(test_label,test_predictions)\n", + "test_score = f1_score(test_label, test_predictions)\n", "\n", - "print(\"F1 score on validation data: \",test_score)\n" + "print(\"F1 score on validation data: \", test_score)" ] }, {