From a7cf0333e7e3e34c79e842edb04a58bb62080113 Mon Sep 17 00:00:00 2001 From: Johanmkr Date: Fri, 31 Jan 2025 15:25:34 +0100 Subject: [PATCH 01/11] Had to add conda-forge as channel for ti work for me --- environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/environment.yml b/environment.yml index f1df2f5..466c8a4 100644 --- a/environment.yml +++ b/environment.yml @@ -1,6 +1,7 @@ name: cc-exam channels: - defaults + - conda-forge dependencies: - python=3.12 - myst-parser From e3b103f61b773bcf9c0933bbcc9adba158b949d4 Mon Sep 17 00:00:00 2001 From: Johanmkr Date: Fri, 31 Jan 2025 15:52:21 +0100 Subject: [PATCH 02/11] Many packages needed to be added to the environmentsfile --- environment.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/environment.yml b/environment.yml index 466c8a4..d715e8e 100644 --- a/environment.yml +++ b/environment.yml @@ -1,6 +1,7 @@ name: cc-exam channels: - defaults + - pytorch - conda-forge dependencies: - python=3.12 @@ -19,5 +20,7 @@ dependencies: - pytest - ruff - scalene + - pytorch + - torchvision prefix: /opt/miniconda3/envs/cc-exam From 6c2301f2e169d693f50933be7c3c6d233e97bcde Mon Sep 17 00:00:00 2001 From: Johanmkr Date: Mon, 3 Feb 2025 08:07:57 +0100 Subject: [PATCH 03/11] Added skeleton of johan_model --- utils/models/johan_model.py | 56 +++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 utils/models/johan_model.py diff --git a/utils/models/johan_model.py b/utils/models/johan_model.py new file mode 100644 index 0000000..a21765e --- /dev/null +++ b/utils/models/johan_model.py @@ -0,0 +1,56 @@ +import pytest +import torch +import torch.nn as nn + +""" +Multi-layer perceptron model for image classification. +""" + +# class NeuronLayer(nn.Module): +# def __init__(self, in_features, out_features): +# super().__init__() + +# self.fc = nn.Linear(in_features, out_features) +# self.relu = nn.ReLU() + +# def forward(self, x): +# x = self.fc(x) +# x = self.relu(x) +# return x + +class JohanModel(nn.Module): + """Small MLP model for image classification. + + Parameters + ---------- + in_features : int + Numer of input features. + num_classes : int + Number of classes in the dataset. + + """ + def __init__(self, in_features, num_classes): + super().__init__() + + self.fc1 = nn.Linear(in_features, 77) + self.fc2 = nn.Linear(77, 77) + self.fc3 = nn.Linear(77, 77) + self.fc4 = nn.Linear(77, num_classes) + self.softmax = nn.Softmax(dim=1) + self.relu = nn.ReLU() + + def forward(self, x): + for layer in [self.fc1, self.fc2, self.fc3, self.fc4]: + x = layer(x) + x = self.relu(x) + x = self.softmax(x) + return x + + + +#TODO +# Add your tests here + + +if __name__=="__main__": + pass # Add your tests here \ No newline at end of file From 7ffce00637352f9254da49a5916ed7f78a31ba15 Mon Sep 17 00:00:00 2001 From: Johanmkr Date: Mon, 3 Feb 2025 08:13:06 +0100 Subject: [PATCH 04/11] Added skeleton of precision metric to metrics --- utils/metrics/precision.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 utils/metrics/precision.py diff --git a/utils/metrics/precision.py b/utils/metrics/precision.py new file mode 100644 index 0000000..5d9604b --- /dev/null +++ b/utils/metrics/precision.py @@ -0,0 +1,12 @@ +import torch +import torch.nn as nn + + +class Precision(nn.Module): + def __init__(self, num_classes): + super().__init__() + + self.num_classes = num_classes + + def forward(self, y_true, y_pred): + pass \ No newline at end of file From c45325113fda33a09cb16ade290b6466f35b9a57 Mon Sep 17 00:00:00 2001 From: Johanmkr Date: Mon, 3 Feb 2025 08:15:53 +0100 Subject: [PATCH 05/11] added precision to load_metrics.py --- utils/load_metric.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/load_metric.py b/utils/load_metric.py index f166c25..f2a70f3 100644 --- a/utils/load_metric.py +++ b/utils/load_metric.py @@ -4,7 +4,7 @@ import torch.nn as nn from .metrics import EntropyPrediction - +from .metrics import precision class MetricWrapper(nn.Module): def __init__(self, *metrics): @@ -39,7 +39,7 @@ def _get_metric(self, key): case "recall": raise NotImplementedError("Recall score not implemented yet") case "precision": - raise NotImplementedError("Precision score not implemented yet") + return precision() case "accuracy": raise NotImplementedError("Accuracy score not implemented yet") case _: From 2a050cf8fcb6de242aedfca4ee10882658a7327e Mon Sep 17 00:00:00 2001 From: Johanmkr Date: Tue, 4 Feb 2025 10:21:13 +0100 Subject: [PATCH 06/11] added precision metric --- utils/metrics/precision.py | 95 +++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/utils/metrics/precision.py b/utils/metrics/precision.py index 5d9604b..be3f91b 100644 --- a/utils/metrics/precision.py +++ b/utils/metrics/precision.py @@ -1,12 +1,105 @@ import torch import torch.nn as nn +USE_MEAN = True + +# Precision = TP / (TP + FP) + class Precision(nn.Module): + """Metric module for precision. Can calculate precision both as a mean of precisions or as brute function of true positives and false positives. This is for now controller with the USE_MEAN macro. + + Parameters + ---------- + num_classes : int + Number of classes in the dataset. + """ + def __init__(self, num_classes): super().__init__() self.num_classes = num_classes def forward(self, y_true, y_pred): - pass \ No newline at end of file + """Calculates the precision score given number of classes and the true and predicted labels. + + Parameters + ---------- + y_true : torch.tensor + true labels + y_pred : torch.tensor + predicted labels + + Returns + ------- + torch.tensor + precision score + """ + # One-hot encode the target tensor + true_oh = torch.zeros(y_true.size(0), self.num_classes).scatter_( + 1, y_true.unsqueeze(1), 1 + ) + pred_oh = torch.zeros(y_pred.size(0), self.num_classes).scatter_( + 1, y_pred.unsqueeze(1), 1 + ) + + if USE_MEAN: + tp = torch.sum(true_oh * pred_oh, 0) + fp = torch.sum(~true_oh.bool() * pred_oh, 0) + + else: + tp = torch.sum(true_oh * pred_oh) + fp = torch.sum(~true_oh[pred_oh.bool()].bool()) + + return torch.nanmean(tp / (tp + fp)) + + +def test_precision_case1(): + true_precision = 25.0 / 36 if USE_MEAN else 7.0 / 10 + + true1 = torch.tensor([0, 1, 2, 1, 0, 2, 1, 0, 2, 1]) + pred1 = torch.tensor([0, 2, 1, 1, 0, 2, 0, 0, 2, 1]) + P = Precision(3) + precision1 = P(true1, pred1) + assert precision1.allclose(torch.tensor(true_precision), atol=1e-5), ( + f"Precision Score: {precision1.item()}" + ) + + +def test_precision_case2(): + true_precision = 8.0 / 15 if USE_MEAN else 6.0 / 15 + + true2 = torch.tensor([0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4]) + pred2 = torch.tensor([0, 0, 4, 3, 4, 0, 4, 4, 2, 3, 4, 1, 2, 4, 0]) + P = Precision(5) + precision2 = P(true2, pred2) + assert precision2.allclose(torch.tensor(true_precision), atol=1e-5), ( + f"Precision Score: {precision2.item()}" + ) + + +def test_precision_case3(): + true_precision = 3.0 / 4 if USE_MEAN else 4.0 / 5 + + true3 = torch.tensor([0, 0, 0, 1, 0]) + pred3 = torch.tensor([1, 0, 0, 1, 0]) + P = Precision(2) + precision3 = P(true3, pred3) + assert precision3.allclose(torch.tensor(true_precision), atol=1e-5), ( + f"Precision Score: {precision3.item()}" + ) + + +def test_for_zero_denominator(): + true_precision = 0.0 + true4 = torch.tensor([1, 1, 1, 1, 1]) + pred4 = torch.tensor([0, 0, 0, 0, 0]) + P = Precision(2) + precision4 = P(true4, pred4) + assert precision4.allclose(torch.tensor(true_precision), atol=1e-5), ( + f"Precision Score: {precision4.item()}" + ) + + +if __name__ == "__main__": + pass From 95313d7e12374d95362b0d81cf658e1adc4a071a Mon Sep 17 00:00:00 2001 From: Johanmkr Date: Tue, 4 Feb 2025 10:23:35 +0100 Subject: [PATCH 07/11] Ran ruff and isort on johan_model.py --- utils/models/johan_model.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/utils/models/johan_model.py b/utils/models/johan_model.py index a21765e..11e81d6 100644 --- a/utils/models/johan_model.py +++ b/utils/models/johan_model.py @@ -1,5 +1,3 @@ -import pytest -import torch import torch.nn as nn """ @@ -9,15 +7,16 @@ # class NeuronLayer(nn.Module): # def __init__(self, in_features, out_features): # super().__init__() - + # self.fc = nn.Linear(in_features, out_features) # self.relu = nn.ReLU() - + # def forward(self, x): # x = self.fc(x) # x = self.relu(x) # return x - + + class JohanModel(nn.Module): """Small MLP model for image classification. @@ -27,30 +26,30 @@ class JohanModel(nn.Module): Numer of input features. num_classes : int Number of classes in the dataset. - + """ + def __init__(self, in_features, num_classes): super().__init__() - + self.fc1 = nn.Linear(in_features, 77) self.fc2 = nn.Linear(77, 77) self.fc3 = nn.Linear(77, 77) self.fc4 = nn.Linear(77, num_classes) self.softmax = nn.Softmax(dim=1) self.relu = nn.ReLU() - + def forward(self, x): for layer in [self.fc1, self.fc2, self.fc3, self.fc4]: x = layer(x) x = self.relu(x) x = self.softmax(x) return x - - - -#TODO + + +# TODO # Add your tests here -if __name__=="__main__": - pass # Add your tests here \ No newline at end of file +if __name__ == "__main__": + pass # Add your tests here From 1f09383b80776ff9822d455c5c143128efa5f721 Mon Sep 17 00:00:00 2001 From: Johanmkr Date: Tue, 4 Feb 2025 10:31:15 +0100 Subject: [PATCH 08/11] Updated environment.yml --- environment.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/environment.yml b/environment.yml index d715e8e..ad0d13b 100644 --- a/environment.yml +++ b/environment.yml @@ -1,8 +1,6 @@ name: cc-exam channels: - defaults - - pytorch - - conda-forge dependencies: - python=3.12 - myst-parser @@ -20,7 +18,8 @@ dependencies: - pytest - ruff - scalene - - pytorch - - torchvision + pip: + - torch + - torchvision prefix: /opt/miniconda3/envs/cc-exam From 3af0a71b6a36bfea99261f3e2673136dfc7b7377 Mon Sep 17 00:00:00 2001 From: Johanmkr Date: Tue, 4 Feb 2025 10:38:58 +0100 Subject: [PATCH 09/11] ruff & isort on load_metric.py --- utils/load_metric.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/load_metric.py b/utils/load_metric.py index f2a70f3..65a585d 100644 --- a/utils/load_metric.py +++ b/utils/load_metric.py @@ -3,8 +3,8 @@ import numpy as np import torch.nn as nn -from .metrics import EntropyPrediction -from .metrics import precision +from .metrics import EntropyPrediction, precision + class MetricWrapper(nn.Module): def __init__(self, *metrics): From 86444080452d80b58565703596a674e2de7f610f Mon Sep 17 00:00:00 2001 From: Johanmkr Date: Tue, 4 Feb 2025 13:07:43 +0100 Subject: [PATCH 10/11] updated model according to #31 --- utils/models/johan_model.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/utils/models/johan_model.py b/utils/models/johan_model.py index 11e81d6..55c6251 100644 --- a/utils/models/johan_model.py +++ b/utils/models/johan_model.py @@ -29,10 +29,17 @@ class JohanModel(nn.Module): """ - def __init__(self, in_features, num_classes): + def __init__(self, image_shape, num_classes): super().__init__() - self.fc1 = nn.Linear(in_features, 77) + # Extract features from image shape + self.in_channels = image_shape[0] + self.height = image_shape[1] + self.width = image_shape[2] + self.num_classes = num_classes + self.in_features = self.in_channels * self.height * self.width + + self.fc1 = nn.Linear(self.in_features, 77) self.fc2 = nn.Linear(77, 77) self.fc3 = nn.Linear(77, 77) self.fc4 = nn.Linear(77, num_classes) From b1a3627c1578afe766d3d128bb5aed893d745258 Mon Sep 17 00:00:00 2001 From: Johanmkr Date: Tue, 4 Feb 2025 13:12:28 +0100 Subject: [PATCH 11/11] ruffed load_metric.py --- utils/load_metric.py | 1 - 1 file changed, 1 deletion(-) diff --git a/utils/load_metric.py b/utils/load_metric.py index 7455fe6..9c942d1 100644 --- a/utils/load_metric.py +++ b/utils/load_metric.py @@ -6,7 +6,6 @@ from .metrics import EntropyPrediction, F1Score, precision - class MetricWrapper(nn.Module): def __init__(self, *metrics): super().__init__()