microcalibrate 0.1.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.
@@ -0,0 +1 @@
1
+ from .calibration import Calibration
@@ -0,0 +1,27 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+
4
+
5
+ class Calibration:
6
+ def __init__(
7
+ self,
8
+ data: pd.DataFrame,
9
+ weights: np.ndarray,
10
+ targets: np.ndarray,
11
+ ):
12
+ self.data = data
13
+ self.weights = weights
14
+ self.targets = targets
15
+
16
+ def calibrate(self):
17
+ from .reweight import reweight
18
+
19
+ new_weights, subsample = reweight(
20
+ original_weights=self.weights,
21
+ loss_matrix=self.data,
22
+ targets_array=self.targets,
23
+ epochs=32,
24
+ )
25
+
26
+ self.data = self.data.loc[subsample]
27
+ self.weights = new_weights
@@ -0,0 +1,109 @@
1
+ import numpy as np
2
+ import torch
3
+ from tqdm import tqdm
4
+
5
+ # Add device variable to use gpu (incl mps) if available
6
+ device = torch.device(
7
+ "cuda"
8
+ if torch.cuda.is_available()
9
+ else "mps" if torch.backends.mps.is_available() else "cpu"
10
+ )
11
+
12
+
13
+ def reweight(
14
+ original_weights,
15
+ loss_matrix,
16
+ targets_array,
17
+ dropout_rate=0.1,
18
+ epochs=2_000,
19
+ noise_level=10.0,
20
+ subsample_every=50,
21
+ learning_rate=1e-3,
22
+ ):
23
+ target_names = np.array(loss_matrix.columns)
24
+ loss_matrix = torch.tensor(
25
+ loss_matrix.values, dtype=torch.float32, device=device
26
+ )
27
+ original_indices = np.arange(loss_matrix.shape[0])
28
+
29
+ targets_array = torch.tensor(
30
+ targets_array, dtype=torch.float32, device=device
31
+ )
32
+ random_noise = np.random.random(original_weights.shape) * noise_level
33
+ weights = torch.tensor(
34
+ np.log(original_weights + random_noise),
35
+ requires_grad=True,
36
+ dtype=torch.float32,
37
+ device=device,
38
+ )
39
+
40
+ def loss(weights):
41
+ estimate = weights @ loss_matrix
42
+ rel_error = (
43
+ ((estimate - targets_array) + 1) / (targets_array + 1)
44
+ ) ** 2
45
+ return rel_error.mean()
46
+
47
+ def dropout_weights(weights, p):
48
+ if p == 0:
49
+ return weights
50
+ total_weight = weights.sum()
51
+ mask = torch.rand_like(weights) < p
52
+ masked_weights = weights.clone()
53
+ masked_weights[mask] = 0
54
+ masked_weights = masked_weights / masked_weights.sum() * total_weight
55
+ return masked_weights
56
+
57
+ optimizer = torch.optim.Adam([weights], lr=learning_rate)
58
+
59
+ iterator = tqdm(range(epochs), desc="Reweighting progress", unit="epoch")
60
+
61
+ for i in iterator:
62
+ optimizer.zero_grad()
63
+ running_loss = None
64
+ for j in range(2):
65
+ weights_ = dropout_weights(weights, dropout_rate)
66
+ l = loss(torch.exp(weights_))
67
+ if running_loss is None:
68
+ running_loss = l
69
+ else:
70
+ running_loss += l
71
+ l = running_loss / 2
72
+
73
+ if i % 10 == 0:
74
+ iterator.set_postfix(
75
+ {
76
+ "loss": l.item(),
77
+ "count_observations": loss_matrix.shape[0],
78
+ "weights_mean": torch.exp(weights).mean().item(),
79
+ "weights_std": torch.exp(weights).std().item(),
80
+ "weights_min": torch.exp(weights).min().item(),
81
+ }
82
+ )
83
+
84
+ l.backward()
85
+ optimizer.step()
86
+
87
+ if subsample_every > 0 and i % subsample_every == 0 and i > 0:
88
+ weight_values = np.exp(weights.detach().cpu().numpy())
89
+
90
+ k = 100
91
+ # indices = indices of weights with values < 1
92
+ indices = np.where(weight_values >= k)[0]
93
+ loss_matrix = loss_matrix[indices, :]
94
+ weights = weights[indices]
95
+
96
+ loss_matrix = torch.tensor(
97
+ loss_matrix.detach().cpu(), dtype=torch.float32, device=device
98
+ )
99
+ weights = torch.tensor(
100
+ weights.detach().cpu(),
101
+ dtype=torch.float32,
102
+ device=device,
103
+ requires_grad=True,
104
+ )
105
+
106
+ original_indices = original_indices[indices]
107
+ optimizer = torch.optim.Adam([weights], lr=learning_rate)
108
+
109
+ return torch.exp(weights).detach().cpu().numpy(), original_indices
@@ -0,0 +1,58 @@
1
+ import numpy as np
2
+ import torch
3
+ import pandas as pd
4
+
5
+ device = torch.device(
6
+ "cuda"
7
+ if torch.cuda.is_available()
8
+ else "mps" if torch.backends.mps.is_available() else "cpu"
9
+ )
10
+
11
+
12
+ def analyse_targets(
13
+ weights,
14
+ loss_matrix,
15
+ targets_array,
16
+ ):
17
+ target_names = np.array(loss_matrix.columns)
18
+ loss_matrix = torch.tensor(
19
+ loss_matrix.values, dtype=torch.float32, device=device
20
+ )
21
+ targets_array = torch.tensor(
22
+ targets_array, dtype=torch.float32, device=device
23
+ )
24
+ weights = torch.tensor(
25
+ np.log(weights), requires_grad=True, dtype=torch.float32, device=device
26
+ )
27
+
28
+ # Compute estimates
29
+ estimate = torch.exp(weights) @ loss_matrix
30
+
31
+ # Compute relative errors for each target
32
+ rel_errors = ((estimate - targets_array) / targets_array) ** 2
33
+
34
+ # Use jacobian to compute gradients for all targets at once
35
+ gradients = torch.autograd.functional.jacobian(
36
+ func=lambda w: (
37
+ (torch.exp(w) @ loss_matrix - targets_array) / targets_array
38
+ )
39
+ ** 2,
40
+ inputs=weights,
41
+ )
42
+
43
+ # gradients is now [n_targets, n_weights]
44
+ gradients_numpy = gradients.cpu().numpy()
45
+ gradients_df = pd.DataFrame(gradients_numpy, index=target_names)
46
+
47
+ # Calculate correlation matrix
48
+ target_correlation = gradients_df.T.corr().mean()
49
+
50
+ df = pd.DataFrame(
51
+ {
52
+ "target": target_names,
53
+ "average_correlation": target_correlation,
54
+ "relative_error": rel_errors.detach().cpu().numpy() ** 0.5,
55
+ }
56
+ )
57
+
58
+ return df
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: microcalibrate
3
+ Version: 0.1.0
4
+ Summary: Add your description here
5
+ Author-email: Nikhil Woodruff <nikhil.woodruff@outlook.com>
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: torch>=2.7.0
8
+ Provides-Extra: dev
9
+ Requires-Dist: black>=23.0.0; extra == 'dev'
10
+ Requires-Dist: build<2.0.0,>=1.0.0; extra == 'dev'
11
+ Requires-Dist: flake8<7.0.0,>=6.0.0; extra == 'dev'
12
+ Requires-Dist: isort<6.0.0,>=5.9.0; extra == 'dev'
13
+ Requires-Dist: linecheck<0.2.0,>=0.1.0; extra == 'dev'
14
+ Requires-Dist: mypy<2.0.0,>=1.0.0; extra == 'dev'
15
+ Requires-Dist: pytest-cov<7.0.0,>=6.0.0; extra == 'dev'
16
+ Requires-Dist: pytest<9.0.0,>=8.0.0; extra == 'dev'
@@ -0,0 +1,8 @@
1
+ microcalibrate/__init__.py,sha256=NP_5n7HBFIF1U5goXpLo3loPbmAtWudUzK_tRYaNYf0,37
2
+ microcalibrate/calibration.py,sha256=hOMwkzvIX8V-SoF_uGt9vJTqPDoiBYgT_WjNiqGtnp8,613
3
+ microcalibrate/reweight.py,sha256=QlK21Wy3GC5h-iSLs1ap_wQTTlALq_XpG9_7ku0LY9U,3380
4
+ microcalibrate/target_correlation.py,sha256=NxhnzCMyjUMEUEUBUdazMEVwZF3W3SGpxEt32BKBSok,1558
5
+ microcalibrate-0.1.0.dist-info/METADATA,sha256=lzIqNK4fvn-gBwRwo4CNB2K4kfIZlQvcZuY1RzZQ2Fc,637
6
+ microcalibrate-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
+ microcalibrate-0.1.0.dist-info/entry_points.txt,sha256=rA66BJoIufeupyLVJq266BJ-jqy7YlQKovKTwY0Z__A,71
8
+ microcalibrate-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ policyengine-calibrate = policyengine_calibrate:main