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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mlpp-lib
3
- Version: 0.5.2
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.9.3,<2.10.0) ; extra == "tensorflow" or extra == "extended"
21
- Requires-Dist: tensorflow-cpu (>=2.9.3,<2.10.0) ; extra == "tensorflow-cpu"
22
- Requires-Dist: tensorflow-probability (>=0.17.0,<0.18.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
- <details>
24
- <summary> click here to visualize </summary>
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", "t", "station"]
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
- "t": LEADTIMES,
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
- "t": LEADTIMES,
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, t: 24, station: 25)
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
- * t (t) int64 0 1 2 3 4 5 6 7 ... 17 18 19 20 21 22 23
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, t, station) float64 0....
95
- coe:x2 (forecast_reference_time, t, station) float64 0....
96
- obs:x3 (forecast_reference_time, t, station) float64 0....
97
- dem:x4 (forecast_reference_time, t, station) float64 -1...
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, t: 24, station: 25)
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
- * t (t) int64 0 1 2 3 4 5 6 7 ... 17 18 19 20 21 22 23
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, t, station) float64 0....
114
- obs:y2 (forecast_reference_time, t, station) float64 0....
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","t","station"],
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.5629 - val_loss: 0.5716
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.5601 - val_loss: 0.5709
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
- t: 24, station: 5)
196
+ lead_time: 24, station: 5)
189
197
  Coordinates:
190
198
  * forecast_reference_time (forecast_reference_time) datetime64[ns] 2018-03...
191
- * t (t) int64 0 1 2 3 4 5 6 7 ... 17 18 19 20 21 22 23
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, t, station) float64 ...
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.utils.register_keras_serializable()
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.utils.register_keras_serializable()
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.utils.register_keras_serializable()
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.utils.register_keras_serializable()
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.utils.register_keras_serializable()
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.utils.register_keras_serializable()
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.utils.register_keras_serializable()
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.utils.register_keras_serializable()
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.5.2"
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.9.3", optional = true}
12
- tensorflow-cpu = {version = "~2.9.3", optional = true}
13
- tensorflow-probability = "~0.17.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.9.3"
28
+ tensorflow = "~2.12.1"
29
29
  pytest = "^7"
30
30
  black = "^22.1.0"
31
- mlflow = "<2"
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