congrads 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
congrads/core.py DELETED
@@ -1,225 +0,0 @@
1
- import logging
2
- from torch import Tensor, float32, no_grad, norm, tensor
3
- from torch.optim import Optimizer
4
- from torch.nn import Module
5
- from torch.utils.data import DataLoader
6
- from time import time
7
-
8
- from .metrics import MetricManager
9
- from .constraints import Constraint
10
- from .descriptor import Descriptor
11
-
12
-
13
- class CongradsCore:
14
-
15
- def __init__(
16
- self,
17
- descriptor: Descriptor,
18
- constraints: list[Constraint],
19
- loaders: tuple[DataLoader, DataLoader, DataLoader],
20
- network: Module,
21
- criterion: callable,
22
- optimizer: Optimizer,
23
- metric_manager: MetricManager,
24
- device,
25
- ):
26
-
27
- # Init parent class
28
- super().__init__()
29
-
30
- # Init object variables
31
- self.descriptor = descriptor
32
- self.constraints = constraints
33
- self.train_loader = loaders[0]
34
- self.valid_loader = loaders[1]
35
- self.test_loader = loaders[2]
36
- self.network = network
37
- self.criterion = criterion
38
- self.optimizer = optimizer
39
- self.metric_manager = metric_manager
40
- self.device = device
41
-
42
- # Perform checks
43
- if len(self.descriptor.variable_layers) == 0:
44
- logging.warning(
45
- "The descriptor object has no variable layers. The constraint guided loss adjustment is therefore not used. Is this the intended behaviour?"
46
- )
47
-
48
- # Initialize constraint metrics
49
- metric_manager.register("Loss/train")
50
- metric_manager.register("Loss/valid")
51
- metric_manager.register("CSR/train")
52
- metric_manager.register("CSR/valid")
53
-
54
- for constraint in self.constraints:
55
- metric_manager.register(f"{constraint.name}/train")
56
- metric_manager.register(f"{constraint.name}/valid")
57
-
58
- def fit(self, max_epochs: int = 100):
59
- # Loop over epochs
60
- for epoch in range(max_epochs):
61
-
62
- # Log start time
63
- start_time = time()
64
-
65
- # Training
66
- for batch in self.train_loader:
67
-
68
- # Set model in training mode
69
- self.network.train()
70
-
71
- # Get input-output pairs from batch
72
- inputs, outputs = batch
73
-
74
- # Transfer to GPU
75
- inputs, outputs = inputs.to(self.device), outputs.to(self.device)
76
-
77
- # Log preparation time
78
- prepare_time = start_time - time()
79
-
80
- # Model computations
81
- prediction = self.network(inputs)
82
-
83
- # Calculate loss
84
- loss = self.criterion(prediction["output"], outputs)
85
- self.metric_manager.accumulate("Loss/train", loss.unsqueeze(0))
86
-
87
- # Adjust loss based on constraints
88
- combined_loss = self.train_step(prediction, loss)
89
-
90
- # Backpropx
91
- self.optimizer.zero_grad()
92
- combined_loss.backward(
93
- retain_graph=False, inputs=list(self.network.parameters())
94
- )
95
- self.optimizer.step()
96
-
97
- # Validation
98
- with no_grad():
99
- for batch in self.valid_loader:
100
-
101
- # Set model in evaluation mode
102
- self.network.eval()
103
-
104
- # Get input-output pairs from batch
105
- inputs, outputs = batch
106
-
107
- # Transfer to GPU
108
- inputs, outputs = inputs.to(self.device), outputs.to(self.device)
109
-
110
- # Model computations
111
- prediction = self.network(inputs)
112
-
113
- # Calculate loss
114
- loss = self.criterion(prediction["output"], outputs)
115
- self.metric_manager.accumulate("Loss/valid", loss.unsqueeze(0))
116
-
117
- # Validate constraints
118
- self.valid_step(prediction, loss)
119
-
120
- # TODO with valid loader, checkpoint model with best performance
121
-
122
- # Save metrics
123
- self.metric_manager.record(epoch)
124
- self.metric_manager.reset()
125
-
126
- # Log compute and preparation time
127
- process_time = start_time - time() - prepare_time
128
- print(
129
- "Compute efficiency: {:.2f}, epoch: {}/{}:".format(
130
- process_time / (process_time + prepare_time), epoch, max_epochs
131
- )
132
- )
133
- start_time = time()
134
-
135
- def train_step(
136
- self,
137
- prediction: dict[str, Tensor],
138
- loss: Tensor,
139
- ):
140
-
141
- # Init scalar tensor for loss
142
- total_rescale_loss = tensor(0, dtype=float32, device=self.device)
143
- loss_grads = {}
144
-
145
- # Precalculate loss gradients for each variable layer
146
- with no_grad():
147
- for layer in self.descriptor.variable_layers:
148
- self.optimizer.zero_grad()
149
- loss.backward(retain_graph=True, inputs=prediction[layer])
150
- loss_grads[layer] = prediction[layer].grad
151
-
152
- # For each constraint, TODO split into real and validation only constraints
153
- for constraint in self.constraints:
154
-
155
- # Check if constraints are satisfied and calculate directions
156
- with no_grad():
157
- constraint_checks = constraint.check_constraint(prediction)
158
- constraint_directions = constraint.calculate_direction(prediction)
159
-
160
- # Only do direction calculations for variable layers affecting constraint
161
- for layer in constraint.layers & self.descriptor.variable_layers:
162
-
163
- with no_grad():
164
- # Multiply direction modifiers with constraint result
165
- constraint_result = (
166
- constraint_checks.unsqueeze(1).type(float32)
167
- * constraint_directions[layer]
168
- )
169
-
170
- # Multiply result with rescale factor of constraint
171
- constraint_result *= constraint.rescale_factor
172
-
173
- # Calculate loss gradient norm
174
- norm_loss_grad = norm(loss_grads[layer], dim=1, p=2, keepdim=True)
175
-
176
- # Calculate rescale loss
177
- rescale_loss = (
178
- prediction[layer]
179
- * constraint_result
180
- * norm_loss_grad.detach().clone()
181
- ).mean()
182
-
183
- # Store rescale loss for this reference space
184
- total_rescale_loss += rescale_loss
185
-
186
- # Log constraint satisfaction ratio
187
- self.metric_manager.accumulate(
188
- f"{constraint.name}/train",
189
- (~constraint_checks).type(float32),
190
- )
191
- self.metric_manager.accumulate(
192
- "CSR/train",
193
- (~constraint_checks).type(float32),
194
- )
195
-
196
- # Return combined loss
197
- return loss + total_rescale_loss
198
-
199
- def valid_step(
200
- self,
201
- prediction: dict[str, Tensor],
202
- loss: Tensor,
203
- ):
204
-
205
- # Compute rescale loss without tracking gradients
206
- with no_grad():
207
-
208
- # For each constraint in this reference space, calculate directions
209
- for constraint in self.constraints:
210
-
211
- # Check if constraints are satisfied for
212
- constraint_checks = constraint.check_constraint(prediction)
213
-
214
- # Log constraint satisfaction ratio
215
- self.metric_manager.accumulate(
216
- f"{constraint.name}/valid",
217
- (~constraint_checks).type(float32),
218
- )
219
- self.metric_manager.accumulate(
220
- "CSR/valid",
221
- (~constraint_checks).type(float32),
222
- )
223
-
224
- # Return loss
225
- return loss
congrads/datasets.py DELETED
@@ -1,195 +0,0 @@
1
- import os
2
- from urllib.error import URLError
3
- import numpy as np
4
- from pathlib import Path
5
- from typing import Callable, Union
6
- import pandas as pd
7
- from torch.utils.data import Dataset
8
- import torch
9
-
10
- from torchvision.datasets.utils import check_integrity, download_and_extract_archive
11
-
12
-
13
- class BiasCorrection(Dataset):
14
-
15
- mirrors = [
16
- "https://archive.ics.uci.edu/static/public/514/",
17
- ]
18
-
19
- resources = [
20
- (
21
- "bias+correction+of+numerical+prediction+model+temperature+forecast.zip",
22
- "3deee56d461a2686887c4ae38fe3ccf3",
23
- ),
24
- ]
25
-
26
- def __init__(
27
- self,
28
- root: Union[str, Path],
29
- transform: Callable,
30
- download: bool = False,
31
- ) -> None:
32
-
33
- super().__init__()
34
- self.root = root
35
- self.transform = transform
36
-
37
- if download:
38
- self.download()
39
-
40
- if not self._check_exists():
41
- raise RuntimeError(
42
- "Dataset not found. You can use download=True to download it"
43
- )
44
-
45
- self.data_input, self.data_output = self._load_data()
46
-
47
- def _load_data(self):
48
-
49
- data: pd.DataFrame = pd.read_csv(
50
- os.path.join(self.data_folder, "Bias_correction_ucl.csv")
51
- ).pipe(self.transform)
52
-
53
- data_input = data["Input"].to_numpy(dtype=np.float32)
54
- data_output = data["Output"].to_numpy(dtype=np.float32)
55
-
56
- return data_input, data_output
57
-
58
- def __len__(self):
59
-
60
- return self.data_input.shape[0]
61
-
62
- def __getitem__(self, idx):
63
-
64
- example = self.data_input[idx, :]
65
- target = self.data_output[idx, :]
66
- example = torch.tensor(example)
67
- target = torch.tensor(target)
68
- return example, target
69
-
70
- @property
71
- def data_folder(self) -> str:
72
-
73
- return os.path.join(self.root, self.__class__.__name__)
74
-
75
- def _check_exists(self) -> bool:
76
- return all(
77
- check_integrity(os.path.join(self.data_folder, file_path), checksum)
78
- for file_path, checksum in self.resources
79
- )
80
-
81
- def download(self) -> None:
82
- if self._check_exists():
83
- return
84
-
85
- os.makedirs(self.data_folder, exist_ok=True)
86
-
87
- # download files
88
- for filename, md5 in self.resources:
89
- errors = []
90
- for mirror in self.mirrors:
91
- url = f"{mirror}{filename}"
92
- try:
93
- download_and_extract_archive(
94
- url, download_root=self.data_folder, filename=filename, md5=md5
95
- )
96
- except URLError as e:
97
- errors.append(e)
98
- continue
99
- break
100
- else:
101
- s = f"Error downloading {filename}:\n"
102
- for mirror, err in zip(self.mirrors, errors):
103
- s += f"Tried {mirror}, got:\n{str(err)}\n"
104
- raise RuntimeError(s)
105
-
106
-
107
- class FiniteIncome(Dataset):
108
-
109
- mirrors = [
110
- "https://www.kaggle.com/api/v1/datasets/download/grosvenpaul/",
111
- ]
112
-
113
- resources = [
114
- (
115
- "family-income-and-expenditure",
116
- "7d74bc7facc3d7c07c4df1c1c6ac563e",
117
- ),
118
- ]
119
-
120
- def __init__(
121
- self,
122
- root: Union[str, Path],
123
- transform: Callable,
124
- download: bool = False,
125
- ) -> None:
126
- super().__init__()
127
- self.root = root
128
- self.transform = transform
129
-
130
- if download:
131
- self.download()
132
-
133
- if not self._check_exists():
134
- raise RuntimeError(
135
- "Dataset not found. You can use download=True to download it."
136
- )
137
-
138
- self.data_input, self.data_output = self._load_data()
139
-
140
- def _load_data(self):
141
-
142
- data: pd.DataFrame = pd.read_csv(
143
- os.path.join(self.data_folder, "Family Income and Expenditure.csv")
144
- ).pipe(self.transform)
145
-
146
- data_input = data["Input"].to_numpy(dtype=np.float32)
147
- data_output = data["Output"].to_numpy(dtype=np.float32)
148
-
149
- return data_input, data_output
150
-
151
- def __len__(self):
152
- return self.data_input.shape[0]
153
-
154
- def __getitem__(self, idx):
155
- example = self.data_input[idx, :]
156
- target = self.data_output[idx, :]
157
- example = torch.tensor(example)
158
- target = torch.tensor(target)
159
- return example, target
160
-
161
- @property
162
- def data_folder(self) -> str:
163
- return os.path.join(self.root, self.__class__.__name__)
164
-
165
- def _check_exists(self) -> bool:
166
- return all(
167
- check_integrity(os.path.join(self.data_folder, file_path), checksum)
168
- for file_path, checksum in self.resources
169
- )
170
-
171
- def download(self) -> None:
172
-
173
- if self._check_exists():
174
- return
175
-
176
- os.makedirs(self.data_folder, exist_ok=True)
177
-
178
- # download files
179
- for filename, md5 in self.resources:
180
- errors = []
181
- for mirror in self.mirrors:
182
- url = f"{mirror}{filename}"
183
- try:
184
- download_and_extract_archive(
185
- url, download_root=self.data_folder, filename=filename, md5=md5
186
- )
187
- except URLError as e:
188
- errors.append(e)
189
- continue
190
- break
191
- else:
192
- s = f"Error downloading {filename}:\n"
193
- for mirror, err in zip(self.mirrors, errors):
194
- s += f"Tried {mirror}, got:\n{str(err)}\n"
195
- raise RuntimeError(s)
congrads/networks.py DELETED
@@ -1,90 +0,0 @@
1
- from torch.nn import Linear, Sequential, ReLU, Module
2
-
3
-
4
- class MLPNetwork(Module):
5
- """
6
- A multi-layer perceptron (MLP) neural network model consisting of
7
- an input layer, multiple hidden layers, and an output layer.
8
-
9
- This class constructs an MLP with configurable hyperparameters such as the
10
- number of input features, output features, number of hidden layers, and
11
- the dimensionality of hidden layers. It provides methods for both
12
- building the model and performing a forward pass through the network.
13
-
14
- Attributes:
15
- n_inputs (int): The number of input features.
16
- n_outputs (int): The number of output features.
17
- n_hidden_layers (int): The number of hidden layers in the network.
18
- hidden_dim (int): The dimensionality of the hidden layers.
19
- input (nn.Module): The input layer (linear transformation followed by ReLU).
20
- hidden (nn.Module): The sequential hidden layers (each consisting of
21
- a linear transformation followed by ReLU).
22
- out (nn.Module): The output layer (linear transformation).
23
- """
24
-
25
- def __init__(
26
- self,
27
- n_inputs,
28
- n_outputs,
29
- n_hidden_layers=3,
30
- hidden_dim=35,
31
- ):
32
- """
33
- Initializes the MLP network with the given hyperparameters.
34
-
35
- Args:
36
- n_inputs (int, optional): The number of input features. Defaults to 25.
37
- n_outputs (int, optional): The number of output features. Defaults to 2.
38
- n_hidden_layers (int, optional): The number of hidden layers. Defaults to 2.
39
- hidden_dim (int, optional): The dimensionality of the hidden layers. Defaults to 35.
40
- """
41
- super().__init__()
42
-
43
- # Init object variables
44
- self.n_inputs = n_inputs
45
- self.n_outputs = n_outputs
46
- self.n_hidden_layers = n_hidden_layers
47
- self.hidden_dim = hidden_dim
48
-
49
- # Set up the components of our model
50
- self.input = Linear(self.n_inputs, self.hidden_dim)
51
- self.hidden = Sequential(
52
- *(
53
- self.linear(self.hidden_dim, self.hidden_dim)
54
- for _ in range(n_hidden_layers)
55
- )
56
- )
57
- self.out = Linear(self.hidden_dim, self.n_outputs)
58
-
59
- def forward(self, X):
60
- """
61
- Performs a forward pass through the network.
62
-
63
- Args:
64
- X (Tensor): The input tensor to be passed through the network.
65
-
66
- Returns:
67
- dict: A dictionary containing the 'input' (original input) and
68
- 'output' (predicted output) of the network.
69
- """
70
- output = self.out(self.hidden(self.input(X)))
71
-
72
- return {"input": X, "output": output}
73
-
74
- @staticmethod
75
- def linear(in_features, out_features):
76
- """
77
- Creates a basic linear block with a linear transformation followed
78
- by a ReLU activation function.
79
-
80
- Args:
81
- in_features (int): The number of input features.
82
- out_features (int): The number of output features.
83
-
84
- Returns:
85
- nn.Module: A sequential module consisting of a Linear layer and ReLU activation.
86
- """
87
- return Sequential(
88
- Linear(in_features, out_features),
89
- ReLU(),
90
- )
@@ -1,26 +0,0 @@
1
- Copyright 2024 DTAI - KU Leuven
2
-
3
- Redistribution and use in source and binary forms, with or without modification,
4
- are permitted provided that the following conditions are met:
5
-
6
- 1. Redistributions of source code must retain the above copyright notice,
7
- this list of conditions and the following disclaimer.
8
-
9
- 2. Redistributions in binary form must reproduce the above copyright notice,
10
- this list of conditions and the following disclaimer in the documentation
11
- and/or other materials provided with the distribution.
12
-
13
- 3. Neither the name of the copyright holder nor the names of its
14
- contributors may be used to endorse or promote products derived from
15
- this software without specific prior written permission.
16
-
17
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
18
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
- ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21
- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.