mlpp-lib 0.5.2__tar.gz → 0.7.0__tar.gz
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.
- {mlpp_lib-0.5.2 → mlpp_lib-0.7.0}/PKG-INFO +4 -4
- {mlpp_lib-0.5.2 → mlpp_lib-0.7.0}/README.md +31 -24
- {mlpp_lib-0.5.2 → mlpp_lib-0.7.0}/mlpp_lib/losses.py +98 -0
- {mlpp_lib-0.5.2 → mlpp_lib-0.7.0}/mlpp_lib/models.py +1 -1
- {mlpp_lib-0.5.2 → mlpp_lib-0.7.0}/mlpp_lib/probabilistic_layers.py +7 -7
- {mlpp_lib-0.5.2 → mlpp_lib-0.7.0}/pyproject.toml +7 -6
- {mlpp_lib-0.5.2 → mlpp_lib-0.7.0}/LICENSE +0 -0
- {mlpp_lib-0.5.2 → mlpp_lib-0.7.0}/mlpp_lib/__init__.py +0 -0
- {mlpp_lib-0.5.2 → mlpp_lib-0.7.0}/mlpp_lib/callbacks.py +0 -0
- {mlpp_lib-0.5.2 → mlpp_lib-0.7.0}/mlpp_lib/datasets.py +0 -0
- {mlpp_lib-0.5.2 → mlpp_lib-0.7.0}/mlpp_lib/decorators.py +0 -0
- {mlpp_lib-0.5.2 → mlpp_lib-0.7.0}/mlpp_lib/ensemble.py +0 -0
- {mlpp_lib-0.5.2 → mlpp_lib-0.7.0}/mlpp_lib/metrics.py +0 -0
- {mlpp_lib-0.5.2 → mlpp_lib-0.7.0}/mlpp_lib/model_selection.py +0 -0
- {mlpp_lib-0.5.2 → mlpp_lib-0.7.0}/mlpp_lib/physical_layers.py +0 -0
- {mlpp_lib-0.5.2 → mlpp_lib-0.7.0}/mlpp_lib/standardizers.py +0 -0
- {mlpp_lib-0.5.2 → mlpp_lib-0.7.0}/mlpp_lib/train.py +0 -0
- {mlpp_lib-0.5.2 → mlpp_lib-0.7.0}/mlpp_lib/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mlpp-lib
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Collection of methods for ML-based postprocessing of weather forecasts.
|
|
5
5
|
Author: Daniele Nerini
|
|
6
6
|
Author-email: daniele.nerini@meteoswiss.ch
|
|
@@ -17,8 +17,8 @@ Requires-Dist: numpy (>=1.22.1,<2.0.0)
|
|
|
17
17
|
Requires-Dist: pandas (>=1.4.1,<2.0.0)
|
|
18
18
|
Requires-Dist: properscoring (>=0.1,<0.2)
|
|
19
19
|
Requires-Dist: scipy (>=1.8.0,<2.0.0)
|
|
20
|
-
Requires-Dist: tensorflow (>=2.
|
|
21
|
-
Requires-Dist: tensorflow-cpu (>=2.
|
|
22
|
-
Requires-Dist: tensorflow-probability (>=0.
|
|
20
|
+
Requires-Dist: tensorflow (>=2.12.1,<2.13.0) ; extra == "tensorflow" or extra == "extended"
|
|
21
|
+
Requires-Dist: tensorflow-cpu (>=2.12.1,<2.13.0) ; extra == "tensorflow-cpu"
|
|
22
|
+
Requires-Dist: tensorflow-probability (>=0.20.0,<0.21.0)
|
|
23
23
|
Requires-Dist: xarray (>=2022.3.0,<2023.0.0)
|
|
24
24
|
Requires-Dist: zarr (>=2.14.1,<3.0.0) ; extra == "extended"
|
|
@@ -20,15 +20,22 @@ import pandas as pd
|
|
|
20
20
|
from mlpp_lib.datasets import DataModule, DataSplitter
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
2024-03-12 11:01:48.532698: I tensorflow/tsl/cuda/cudart_stub.cc:28] Could not find cuda drivers on your machine, GPU will not be used.
|
|
24
|
+
2024-03-12 11:01:48.594233: I tensorflow/tsl/cuda/cudart_stub.cc:28] Could not find cuda drivers on your machine, GPU will not be used.
|
|
25
|
+
2024-03-12 11:01:48.595154: I tensorflow/core/platform/cpu_feature_guard.cc:182] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.
|
|
26
|
+
To enable the following instructions: AVX2 AVX512F FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
2024-03-12 11:01:49.442240: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT
|
|
30
|
+
|
|
31
|
+
|
|
25
32
|
|
|
26
33
|
```python
|
|
27
34
|
LEADTIMES = np.arange(24)
|
|
28
35
|
REFTIMES = pd.date_range("2018-01-01", "2018-03-31", freq="24H")
|
|
29
36
|
STATIONS = [chr(i) * 3 for i in range(ord("A"), ord("Z"))]
|
|
30
37
|
SHAPE = (len(REFTIMES), len(LEADTIMES), len(STATIONS))
|
|
31
|
-
DIMS = ["forecast_reference_time", "
|
|
38
|
+
DIMS = ["forecast_reference_time", "lead_time", "station"]
|
|
32
39
|
|
|
33
40
|
def features_dataset() -> xr.Dataset:
|
|
34
41
|
rng = np.random.default_rng(1)
|
|
@@ -44,7 +51,7 @@ def features_dataset() -> xr.Dataset:
|
|
|
44
51
|
},
|
|
45
52
|
coords={
|
|
46
53
|
"forecast_reference_time": REFTIMES,
|
|
47
|
-
"
|
|
54
|
+
"lead_time": LEADTIMES,
|
|
48
55
|
"station": STATIONS,
|
|
49
56
|
},
|
|
50
57
|
)
|
|
@@ -63,7 +70,7 @@ def targets_dataset() -> xr.Dataset:
|
|
|
63
70
|
{"obs:y1": (DIMS, Y[..., 0]), "obs:y2": (DIMS, Y[..., 1])},
|
|
64
71
|
coords={
|
|
65
72
|
"forecast_reference_time": REFTIMES,
|
|
66
|
-
"
|
|
73
|
+
"lead_time": LEADTIMES,
|
|
67
74
|
"station": STATIONS,
|
|
68
75
|
},
|
|
69
76
|
)
|
|
@@ -72,9 +79,7 @@ def targets_dataset() -> xr.Dataset:
|
|
|
72
79
|
|
|
73
80
|
|
|
74
81
|
```
|
|
75
|
-
</details>
|
|
76
82
|
|
|
77
|
-
## Raw datasets
|
|
78
83
|
MLPP expects xarray objects that look like this:
|
|
79
84
|
|
|
80
85
|
|
|
@@ -85,16 +90,17 @@ print(features)
|
|
|
85
90
|
```
|
|
86
91
|
|
|
87
92
|
<xarray.Dataset>
|
|
88
|
-
Dimensions: (forecast_reference_time: 90,
|
|
93
|
+
Dimensions: (forecast_reference_time: 90, lead_time: 24,
|
|
94
|
+
station: 25)
|
|
89
95
|
Coordinates:
|
|
90
96
|
* forecast_reference_time (forecast_reference_time) datetime64[ns] 2018-01...
|
|
91
|
-
*
|
|
97
|
+
* lead_time (lead_time) int64 0 1 2 3 4 5 ... 18 19 20 21 22 23
|
|
92
98
|
* station (station) <U3 'AAA' 'BBB' 'CCC' ... 'XXX' 'YYY'
|
|
93
99
|
Data variables:
|
|
94
|
-
coe:x1 (forecast_reference_time,
|
|
95
|
-
coe:x2 (forecast_reference_time,
|
|
96
|
-
obs:x3 (forecast_reference_time,
|
|
97
|
-
dem:x4 (forecast_reference_time,
|
|
100
|
+
coe:x1 (forecast_reference_time, lead_time, station) float64 ...
|
|
101
|
+
coe:x2 (forecast_reference_time, lead_time, station) float64 ...
|
|
102
|
+
obs:x3 (forecast_reference_time, lead_time, station) float64 ...
|
|
103
|
+
dem:x4 (forecast_reference_time, lead_time, station) float64 ...
|
|
98
104
|
|
|
99
105
|
|
|
100
106
|
|
|
@@ -104,14 +110,15 @@ print(targets)
|
|
|
104
110
|
```
|
|
105
111
|
|
|
106
112
|
<xarray.Dataset>
|
|
107
|
-
Dimensions: (forecast_reference_time: 90,
|
|
113
|
+
Dimensions: (forecast_reference_time: 90, lead_time: 24,
|
|
114
|
+
station: 25)
|
|
108
115
|
Coordinates:
|
|
109
116
|
* forecast_reference_time (forecast_reference_time) datetime64[ns] 2018-01...
|
|
110
|
-
*
|
|
117
|
+
* lead_time (lead_time) int64 0 1 2 3 4 5 ... 18 19 20 21 22 23
|
|
111
118
|
* station (station) <U3 'AAA' 'BBB' 'CCC' ... 'XXX' 'YYY'
|
|
112
119
|
Data variables:
|
|
113
|
-
obs:y1 (forecast_reference_time,
|
|
114
|
-
obs:y2 (forecast_reference_time,
|
|
120
|
+
obs:y1 (forecast_reference_time, lead_time, station) float64 ...
|
|
121
|
+
obs:y2 (forecast_reference_time, lead_time, station) float64 ...
|
|
115
122
|
|
|
116
123
|
|
|
117
124
|
## Preparing data
|
|
@@ -133,7 +140,7 @@ splitter = DataSplitter(
|
|
|
133
140
|
|
|
134
141
|
datamodule = DataModule(
|
|
135
142
|
features, targets[["obs:y1"]],
|
|
136
|
-
batch_dims=["forecast_reference_time","
|
|
143
|
+
batch_dims=["forecast_reference_time", "lead_time", "station"],
|
|
137
144
|
splitter=splitter
|
|
138
145
|
)
|
|
139
146
|
|
|
@@ -168,9 +175,10 @@ history = model.fit(
|
|
|
168
175
|
```
|
|
169
176
|
|
|
170
177
|
Epoch 1/2
|
|
171
|
-
689/689 [==============================] - 2s 2ms/step - loss: 0.
|
|
178
|
+
689/689 [==============================] - 2s 2ms/step - loss: 0.5633 - val_loss: 0.5721
|
|
179
|
+
|
|
172
180
|
Epoch 2/2
|
|
173
|
-
689/689 [==============================] - 1s 2ms/step - loss: 0.
|
|
181
|
+
689/689 [==============================] - 1s 2ms/step - loss: 0.5607 - val_loss: 0.5695
|
|
174
182
|
|
|
175
183
|
|
|
176
184
|
## Predictions
|
|
@@ -185,12 +193,11 @@ print(test_pred_ensemble)
|
|
|
185
193
|
|
|
186
194
|
<xarray.Dataset>
|
|
187
195
|
Dimensions: (realization: 21, forecast_reference_time: 18,
|
|
188
|
-
|
|
196
|
+
lead_time: 24, station: 5)
|
|
189
197
|
Coordinates:
|
|
190
198
|
* forecast_reference_time (forecast_reference_time) datetime64[ns] 2018-03...
|
|
191
|
-
*
|
|
199
|
+
* lead_time (lead_time) int64 0 1 2 3 4 5 ... 18 19 20 21 22 23
|
|
192
200
|
* station (station) <U3 'AAA' 'EEE' 'JJJ' 'PPP' 'RRR'
|
|
193
201
|
* realization (realization) int64 0 1 2 3 4 5 ... 16 17 18 19 20
|
|
194
202
|
Data variables:
|
|
195
|
-
obs:y1 (realization, forecast_reference_time,
|
|
196
|
-
|
|
203
|
+
obs:y1 (realization, forecast_reference_time, lead_time, station) float64 ...
|
|
@@ -576,3 +576,101 @@ class MultivariateLoss(tf.keras.losses.Loss):
|
|
|
576
576
|
y_pred.shape = (*y_pred.batch_shape, *y_pred.event_shape)
|
|
577
577
|
|
|
578
578
|
return y_true, y_pred
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
class BinaryClassifierLoss(tf.keras.losses.Loss):
|
|
582
|
+
"""
|
|
583
|
+
Compute binary classification loss from continuous predictions based on a threshold.
|
|
584
|
+
|
|
585
|
+
Parameters
|
|
586
|
+
----------
|
|
587
|
+
threshold: float
|
|
588
|
+
loss_type: {"binary_crossentropy", "focal"}
|
|
589
|
+
The type of loss to be used.
|
|
590
|
+
n_samples: int, optional
|
|
591
|
+
**kwargs:
|
|
592
|
+
(Optional) Additional keyword arguments to be passed to the parent `Loss` class.
|
|
593
|
+
|
|
594
|
+
"""
|
|
595
|
+
|
|
596
|
+
def __init__(
|
|
597
|
+
self,
|
|
598
|
+
threshold: float,
|
|
599
|
+
loss_type: Literal["binary_crossentropy", "focal"] = "binary_crossentropy",
|
|
600
|
+
n_samples: int = 1000,
|
|
601
|
+
**kwargs,
|
|
602
|
+
) -> None:
|
|
603
|
+
super(BinaryClassifierLoss, self).__init__(**kwargs)
|
|
604
|
+
|
|
605
|
+
self.threshold = float(threshold)
|
|
606
|
+
self.n_samples = int(n_samples)
|
|
607
|
+
if self.n_samples < 2:
|
|
608
|
+
raise ValueError("n_samples must be > 1")
|
|
609
|
+
self.loss_type = loss_type
|
|
610
|
+
|
|
611
|
+
def get_config(self) -> dict:
|
|
612
|
+
custom_config = {
|
|
613
|
+
"threshold": self.threshold,
|
|
614
|
+
"loss_type": self.loss_type,
|
|
615
|
+
"n_samples": self.n_samples,
|
|
616
|
+
}
|
|
617
|
+
config = super().get_config()
|
|
618
|
+
config.update(custom_config)
|
|
619
|
+
return config
|
|
620
|
+
|
|
621
|
+
def call(
|
|
622
|
+
self,
|
|
623
|
+
y_true: Union[tf.Tensor, np.ndarray],
|
|
624
|
+
y_pred: Union[tf.Tensor, np.ndarray, tfp.distributions.Distribution],
|
|
625
|
+
) -> tf.Tensor:
|
|
626
|
+
"""
|
|
627
|
+
Compute the loss.
|
|
628
|
+
|
|
629
|
+
Parameters
|
|
630
|
+
----------
|
|
631
|
+
y_true: array-like
|
|
632
|
+
Values representing the ground truth.
|
|
633
|
+
y_pred: array_like or tfp.Distribution
|
|
634
|
+
Predicted values or distributions.
|
|
635
|
+
"""
|
|
636
|
+
threshold = tf.constant(self.threshold, dtype=y_true.dtype)
|
|
637
|
+
n_samples = self.n_samples
|
|
638
|
+
y_true = tf.debugging.check_numerics(y_true, "Target values")
|
|
639
|
+
|
|
640
|
+
if isinstance(y_pred, tfp.distributions.Distribution):
|
|
641
|
+
y_pred_samples = y_pred.sample(n_samples)
|
|
642
|
+
else:
|
|
643
|
+
y_pred_samples = y_pred
|
|
644
|
+
|
|
645
|
+
y_pred_samples = tf.debugging.check_numerics(y_pred_samples, "Predicted values")
|
|
646
|
+
|
|
647
|
+
y_true_bool = tf.cast(y_true > threshold, dtype=y_true.dtype)
|
|
648
|
+
y_pred_bool = tf.cast(y_pred_samples > threshold, dtype=y_true.dtype)
|
|
649
|
+
y_pred_prob = tf.reduce_mean(y_pred_bool, axis=0)
|
|
650
|
+
|
|
651
|
+
loss = tf.keras.losses.binary_crossentropy(y_true_bool, y_pred_prob, axis=1)
|
|
652
|
+
if self.loss_type == "focal":
|
|
653
|
+
loss = tf.pow(1 - tf.exp(-loss), 2)
|
|
654
|
+
|
|
655
|
+
return loss
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
class CombinedLoss(tf.keras.losses.Loss):
|
|
659
|
+
def __init__(self, losses):
|
|
660
|
+
# Local import to avoid circular dependency with mlpp_lib.utils
|
|
661
|
+
from mlpp_lib.utils import get_loss
|
|
662
|
+
|
|
663
|
+
super(CombinedLoss, self).__init__()
|
|
664
|
+
self.losses = []
|
|
665
|
+
self.weights = []
|
|
666
|
+
|
|
667
|
+
# Initialize losses based on the input config dictionaries
|
|
668
|
+
for loss_config in losses:
|
|
669
|
+
self.weights.append(loss_config.get("weight", 1.0))
|
|
670
|
+
self.losses.append(get_loss(loss_config))
|
|
671
|
+
|
|
672
|
+
def call(self, y_true, y_pred):
|
|
673
|
+
total_loss = 0
|
|
674
|
+
for loss, weight in zip(self.losses, self.weights):
|
|
675
|
+
total_loss += weight * loss(y_true, y_pred)
|
|
676
|
+
return total_loss
|
|
@@ -26,7 +26,7 @@ else:
|
|
|
26
26
|
_LOGGER = logging.getLogger(__name__)
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
@tf.keras.
|
|
29
|
+
@tf.keras.saving.register_keras_serializable()
|
|
30
30
|
class MonteCarloDropout(Dropout):
|
|
31
31
|
def call(self, inputs):
|
|
32
32
|
return super().call(inputs, training=True)
|
|
@@ -22,7 +22,7 @@ from tensorflow_probability.python.layers import (
|
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
@tf.keras.
|
|
25
|
+
@tf.keras.saving.register_keras_serializable()
|
|
26
26
|
class IndependentBeta(tfpl.DistributionLambda):
|
|
27
27
|
"""An independent 4-parameter Beta Keras layer."""
|
|
28
28
|
|
|
@@ -135,7 +135,7 @@ class IndependentBeta(tfpl.DistributionLambda):
|
|
|
135
135
|
return super(IndependentBeta, self).output[0]
|
|
136
136
|
|
|
137
137
|
|
|
138
|
-
@tf.keras.
|
|
138
|
+
@tf.keras.saving.register_keras_serializable()
|
|
139
139
|
class IndependentGamma(tfpl.DistributionLambda):
|
|
140
140
|
"""An independent gamma Keras layer."""
|
|
141
141
|
|
|
@@ -245,7 +245,7 @@ class IndependentGamma(tfpl.DistributionLambda):
|
|
|
245
245
|
return super(IndependentGamma, self).output[0]
|
|
246
246
|
|
|
247
247
|
|
|
248
|
-
@tf.keras.
|
|
248
|
+
@tf.keras.saving.register_keras_serializable()
|
|
249
249
|
class IndependentLogNormal(tfpl.DistributionLambda):
|
|
250
250
|
"""An independent LogNormal Keras layer."""
|
|
251
251
|
|
|
@@ -353,7 +353,7 @@ class IndependentLogNormal(tfpl.DistributionLambda):
|
|
|
353
353
|
return super(IndependentLogNormal, self).output[0]
|
|
354
354
|
|
|
355
355
|
|
|
356
|
-
@tf.keras.
|
|
356
|
+
@tf.keras.saving.register_keras_serializable()
|
|
357
357
|
class IndependentTruncatedNormal(tfpl.DistributionLambda):
|
|
358
358
|
"""An independent TruncatedNormal Keras layer."""
|
|
359
359
|
|
|
@@ -463,7 +463,7 @@ class IndependentTruncatedNormal(tfpl.DistributionLambda):
|
|
|
463
463
|
return super(IndependentTruncatedNormal, self).output[0]
|
|
464
464
|
|
|
465
465
|
|
|
466
|
-
@tf.keras.
|
|
466
|
+
@tf.keras.saving.register_keras_serializable()
|
|
467
467
|
class IndependentWeibull(tfpl.DistributionLambda):
|
|
468
468
|
"""An independent Weibull Keras layer."""
|
|
469
469
|
|
|
@@ -574,7 +574,7 @@ class IndependentWeibull(tfpl.DistributionLambda):
|
|
|
574
574
|
return super(IndependentWeibull, self).output[0]
|
|
575
575
|
|
|
576
576
|
|
|
577
|
-
@tf.keras.
|
|
577
|
+
@tf.keras.saving.register_keras_serializable()
|
|
578
578
|
class MultivariateNormalDiag(tfpl.DistributionLambda):
|
|
579
579
|
"""A `d`-variate normal Keras layer from `2* d` params,
|
|
580
580
|
with a diagonal scale matrix.
|
|
@@ -670,7 +670,7 @@ class MultivariateNormalDiag(tfpl.DistributionLambda):
|
|
|
670
670
|
return super(MultivariateNormalDiag, self).output[0]
|
|
671
671
|
|
|
672
672
|
|
|
673
|
-
@tf.keras.
|
|
673
|
+
@tf.keras.saving.register_keras_serializable()
|
|
674
674
|
class MultivariateNormalTriL(tfpl.MultivariateNormalTriL):
|
|
675
675
|
def __init__(
|
|
676
676
|
self,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "mlpp-lib"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.7.0"
|
|
4
4
|
description = "Collection of methods for ML-based postprocessing of weather forecasts."
|
|
5
5
|
authors = ["Daniele Nerini <daniele.nerini@meteoswiss.ch>"]
|
|
6
6
|
include = ["LICENSE", "README.md"]
|
|
@@ -8,9 +8,9 @@ include = ["LICENSE", "README.md"]
|
|
|
8
8
|
[tool.poetry.dependencies]
|
|
9
9
|
python = ">=3.9, <3.11"
|
|
10
10
|
numpy = "^1.22.1"
|
|
11
|
-
tensorflow = {version = "~2.
|
|
12
|
-
tensorflow-cpu = {version = "~2.
|
|
13
|
-
tensorflow-probability = "~0.
|
|
11
|
+
tensorflow = {version = "~2.12.1", optional = true}
|
|
12
|
+
tensorflow-cpu = {version = "~2.12.1", optional = true}
|
|
13
|
+
tensorflow-probability = "~0.20.0"
|
|
14
14
|
pandas = "^1.4.1"
|
|
15
15
|
scipy = "^1.8.0"
|
|
16
16
|
xarray = "^2022.3.0"
|
|
@@ -25,10 +25,11 @@ tensorflow-cpu = ["tensorflow-cpu"]
|
|
|
25
25
|
extended = ["zarr", "numba", "tensorflow"]
|
|
26
26
|
|
|
27
27
|
[tool.poetry.group.dev.dependencies]
|
|
28
|
-
tensorflow = "~2.
|
|
28
|
+
tensorflow = "~2.12.1"
|
|
29
29
|
pytest = "^7"
|
|
30
30
|
black = "^22.1.0"
|
|
31
|
-
mlflow = "
|
|
31
|
+
mlflow = "^2.8.1"
|
|
32
|
+
jupyter = "^1.0.0"
|
|
32
33
|
|
|
33
34
|
[build-system]
|
|
34
35
|
requires = ["poetry-core>=1.0.8"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|