surrogates_interface 4.1.0__tar.gz → 4.1.2__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,16 +1,15 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: surrogates_interface
3
- Version: 4.1.0
3
+ Version: 4.1.2
4
4
  Summary: Interface for surrogate models.
5
5
  Home-page: https://gitlab.windenergy.dtu.dk/surrogate-models/surrogate-models-interface
6
6
  License: MIT
7
7
  Author: DTU Wind and Energy Systems
8
8
  Maintainer: Riccardo Riva
9
9
  Maintainer-email: ricriv@dtu.dk
10
- Requires-Python: >=3.9,<3.12
10
+ Requires-Python: >=3.10,<3.12
11
11
  Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.9
14
13
  Classifier: Programming Language :: Python :: 3.10
15
14
  Classifier: Programming Language :: Python :: 3.11
16
15
  Provides-Extra: code-style
@@ -20,24 +19,24 @@ Provides-Extra: smt
20
19
  Provides-Extra: test
21
20
  Provides-Extra: tf
22
21
  Provides-Extra: torch
23
- Requires-Dist: black ; extra == "code-style"
24
- Requires-Dist: codespell ; extra == "code-style"
25
- Requires-Dist: flake8 ; extra == "code-style"
26
- Requires-Dist: h5py
27
- Requires-Dist: isort ; extra == "code-style"
28
- Requires-Dist: matplotlib ; extra == "test" or extra == "smt"
22
+ Requires-Dist: black (>=24.0.0,<25.0.0) ; extra == "code-style"
23
+ Requires-Dist: codespell (>=2.0.0,<3.0.0) ; extra == "code-style"
24
+ Requires-Dist: flake8 (>=7.0.0,<8.0.0) ; extra == "code-style"
25
+ Requires-Dist: h5py (>=3.10.0,<4.0.0)
26
+ Requires-Dist: isort (>=5.0.0,<6.0.0) ; extra == "code-style"
27
+ Requires-Dist: keras (>=3.0.0,<4.0.0) ; extra == "tf" or extra == "torch"
28
+ Requires-Dist: matplotlib (>=3.8.0,<4.0.0) ; extra == "test" or extra == "smt"
29
29
  Requires-Dist: numpy (>=1.10.0)
30
- Requires-Dist: numpydoc ; extra == "docs"
30
+ Requires-Dist: numpydoc (>=1.8.0,<2.0.0) ; extra == "docs"
31
31
  Requires-Dist: openmdao (>=3.25,<4.0) ; extra == "docs" or extra == "test" or extra == "om"
32
32
  Requires-Dist: pydata-sphinx-theme ; extra == "docs"
33
- Requires-Dist: pytest ; extra == "test"
34
- Requires-Dist: pytest-cov ; extra == "test"
35
- Requires-Dist: pytest-testmon ; extra == "test"
36
- Requires-Dist: scikit-learn ; extra == "test" or extra == "smt" or extra == "tf"
33
+ Requires-Dist: pytest (>=8.0.0,<9.0.0) ; extra == "test"
34
+ Requires-Dist: pytest-cov (>=6.0.0,<7.0.0) ; extra == "test"
35
+ Requires-Dist: scikit-learn (>=1.4.0,<2.0.0) ; extra == "test" or extra == "smt" or extra == "tf" or extra == "torch"
37
36
  Requires-Dist: smt (>=2.0.1,<3.0.0) ; extra == "test" or extra == "smt"
38
- Requires-Dist: sphinx ; extra == "docs"
37
+ Requires-Dist: sphinx (>=8.0.0,<9.0.0) ; extra == "docs"
39
38
  Requires-Dist: sphinx-copybutton ; extra == "docs"
40
- Requires-Dist: sphinx-sitemap ; extra == "docs"
39
+ Requires-Dist: sphinx-sitemap (>=2.0.0,<3.0.0) ; extra == "docs"
41
40
  Requires-Dist: sphinx_design ; extra == "docs"
42
41
  Requires-Dist: tensorflow (>=2.10.0,<3.0.0) ; extra == "test" or extra == "tf"
43
42
  Requires-Dist: tensorflow-intel (>=2.10.0,<3.0.0) ; (sys_platform == "win32") and (extra == "test" or extra == "tf")
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "surrogates_interface"
3
- version = "4.1.0"
3
+ version = "4.1.2"
4
4
  description = "Interface for surrogate models."
5
5
  authors = ["DTU Wind and Energy Systems"]
6
6
  maintainers = [
@@ -16,25 +16,24 @@ generate-setup-file = false
16
16
 
17
17
  [tool.poetry.dependencies]
18
18
 
19
- python = ">=3.9,<3.12"
20
- numpy = ">=1.10.0"
21
- h5py = "*"
22
- black = {version="*", optional=true}
23
- flake8 = {version="*", optional=true}
24
- isort = {version="*", optional=true}
25
- codespell = {version="*", optional=true}
26
- pytest = {version="*", optional=true}
27
- pytest-cov = {version="*", optional=true}
28
- pytest-testmon = {version="*", optional=true}
19
+ python = ">=3.10,<3.12"
20
+ numpy = {version=">=1.10.0"}
21
+ h5py = {version="^3.10.0"}
22
+ black = {version="^24.0.0", optional=true}
23
+ flake8 = {version="^7.0.0", optional=true}
24
+ isort = {version="^5.0.0", optional=true}
25
+ codespell = {version="^2.0.0", optional=true}
26
+ pytest = {version="^8.0.0", optional=true}
27
+ pytest-cov = {version="^6.0.0", optional=true}
29
28
  tomli = {version="^2.0.1", python="<3.11", optional=true}
30
- sphinx = {version="*", optional=true}
29
+ sphinx = {version="^8.0.0", optional=true}
31
30
  pydata-sphinx-theme = {version="*", optional=true}
32
31
  sphinx-copybutton = {version="*", optional=true}
33
- sphinx-sitemap = {version="*", optional=true}
32
+ sphinx-sitemap = {version="^2.0.0", optional=true}
34
33
  sphinx_design = {version="*", optional=true}
35
- numpydoc = {version="*", optional=true}
36
- matplotlib = {version="*", optional=true}
37
- scikit-learn = {version="*", optional=true}
34
+ numpydoc = {version="^1.8.0", optional=true}
35
+ matplotlib = {version="^3.8.0", optional=true}
36
+ scikit-learn = {version="^1.4.0", optional=true}
38
37
  # The following configuration for TensorFlow seems to work on Windows and linux, as tested at
39
38
  # https://gitlab.windenergy.dtu.dk/surrogate-models/test-poetry-and-tensorflow
40
39
  tensorflow = {version = "^2.10.0", optional=true }
@@ -43,6 +42,7 @@ tensorflow-io-gcs-filesystem = [
43
42
  {version = "< 0.32.0", markers = "platform_system == 'Windows'", python=">=3.9,<3.12", optional=true}
44
43
  ]
45
44
  tensorflow-intel = {version="^2.10.0", platform = "win32", optional=true}
45
+ keras = {version="^3.0.0", optional=true}
46
46
  smt = {version="^2.0.1", optional=true}
47
47
  openmdao = {version="^3.25", optional=true}
48
48
  torch = { version = "^2.0.0", optional=true }
@@ -67,7 +67,6 @@ code_style = ["black",
67
67
  test = ["matplotlib", # Needed by SMT.
68
68
  "pytest",
69
69
  "pytest-cov",
70
- "pytest-testmon",
71
70
  "tomli",
72
71
  "scikit-learn",
73
72
  "smt",
@@ -87,11 +86,15 @@ tf = ["scikit-learn",
87
86
  "tensorflow",
88
87
  "tensorflow-io-gcs-filesystem",
89
88
  "tensorflow-intel",
89
+ "keras",
90
90
  ]
91
91
 
92
92
  om = ["openmdao"]
93
93
 
94
- torch = ["torch"]
94
+ torch = ["torch",
95
+ "scikit-learn",
96
+ "keras",
97
+ ]
95
98
 
96
99
 
97
100
 
@@ -90,8 +90,8 @@ class SurrogateModelCompFD(om.ExplicitComponent):
90
90
  self.options.declare(
91
91
  "input_type",
92
92
  desc="""Type of the input.
93
- - If `InputOutputType.JOINED` there will be only 1 input named `input_name`.
94
- - If `InputOutputType.SPLIT` there will be 1 input per input channel. Their names will be taken from `model.input_names`.
93
+ - If `InputOutputType.JOINED` there will be only 1 input named `options["input_name"]`.
94
+ - If `InputOutputType.SPLIT` there will be 1 input per channel. Their names will be taken from `model.input_names`.
95
95
  """,
96
96
  check_valid=self._check_in_out_type,
97
97
  )
@@ -99,8 +99,8 @@ class SurrogateModelCompFD(om.ExplicitComponent):
99
99
  self.options.declare(
100
100
  "output_type",
101
101
  desc="""Type of the output.
102
- - If `InputOutputType.JOINED` there will be only 1 output named `output_name`.
103
- - If `InputOutputType.SPLIT` there will be 1 input per output channel. Their names will be taken from `model.output_names`.
102
+ - If `InputOutputType.JOINED` there will be only 1 output named `options["output_name"]`.
103
+ - If `InputOutputType.SPLIT` there will be 1 output per channel. Their names will be taken from `model.output_names`.
104
104
  """,
105
105
  check_valid=self._check_in_out_type,
106
106
  )
@@ -150,13 +150,13 @@ class SurrogateModelCompFD(om.ExplicitComponent):
150
150
  ----------
151
151
  inputs : dict
152
152
  Inputs.
153
- - If `input_type is InputOutputType.JOINED` we expect one input named `input_name` of shape `(n_points, n_inputs)`.
153
+ - If `input_type is InputOutputType.JOINED` we expect one input named `options["input_name"]` of shape `(n_points, n_inputs)`.
154
154
  - If `input_type is InputOutputType.SPLIT` we expect one input per feature of shape `(n_points,)`.
155
- The inputs will be stacked according to `input_names`.
155
+ The inputs will be stacked according to `model.input_names`.
156
156
  outputs : dict
157
157
  Outputs.
158
- - If `output_type is InputOutputType.JOINED` we expect one output named `output_name` of shape `(n_points, n_outputs)`.
159
- The outputs will be stacked according to `output_names`.
158
+ - If `output_type is InputOutputType.JOINED` we expect one output named `options["output_name"]` of shape `(n_points, n_outputs)`.
159
+ The outputs will be stacked according to `model.output_names`.
160
160
  - If `output_type is InputOutputType.SPLIT` we expect one output per feature of shape `(n_points,)`.
161
161
 
162
162
  Returns
@@ -240,9 +240,9 @@ class SurrogateModelComp(SurrogateModelCompFD):
240
240
  ----------
241
241
  inputs : dict
242
242
  Inputs.
243
- - If `input_type is InputOutputType.JOINED` we expect one input named `input_name` of shape `(n_points, n_inputs)`.
243
+ - If `input_type is InputOutputType.JOINED` we expect one input named `options["input_name"]` of shape `(n_points, n_inputs)`.
244
244
  - If `input_type is InputOutputType.SPLIT` we expect one input per feature of shape `(n_points,)`.
245
- The inputs will be stacked according to `input_names`.
245
+ The inputs will be stacked according to `model.input_names`.
246
246
  partials : dict
247
247
  Jacobian.
248
248
  - If `output_type is InputOutputType.JOINED` we expect one Jacobian of shape `(n_points, n_outputs)`.
@@ -364,6 +364,10 @@ class SurrogateModel:
364
364
  .. math::
365
365
  \hat{y} = g_1^{-1} \circ g_2^{-1} \circ \cdots \circ g_m^{-1} \circ \hat{\tilde{y}}
366
366
 
367
+ and by substitution we find
368
+
369
+ .. math::
370
+ \hat{y} = g_1^{-1}(g_2^{-1}(g_m^{-1}(s(f_n(f_2(f_1(x)))))))
367
371
 
368
372
  Parameters
369
373
  ----------
@@ -419,9 +423,26 @@ class SurrogateModel:
419
423
  raise NotImplementedError()
420
424
 
421
425
  def predict_output_and_jacobian(self, x, *args, **kwargs):
422
- """
426
+ r"""
423
427
  Predict the complete model output and Jacobian, including all input and output transformations.
424
428
 
429
+ Let's recall that the predicted output is given by
430
+
431
+ .. math::
432
+ \hat{y} = g_1^{-1}(g_2^{-1}(g_m^{-1}(s(f_n(f_2(f_1(x)))))))
433
+
434
+ By indicating with :math:`J_h(z)` the Jacobian of :math:`h` evaluated at :math:`z`, we obtain
435
+ the Jacobian of the predicted output by applying the chain rule
436
+
437
+ .. math::
438
+ J = &J_{g_1^{-1}}(g_2^{-1}(g_m^{-1}(s(f_n(f_2(f_1(x))))))) \\
439
+ &J_{g_2^{-1}}(g_m^{-1}(s(f_n(f_2(f_1(x)))))) \\
440
+ &J_{g_m^{-1}}(s(f_n(f_2(f_1(x))))) \\
441
+ &J_{s}(f_n(f_2(f_1(x)))) \\
442
+ &J_{f_n}(f_2(f_1(x)))
443
+ J_{f_2}(f_1(x))
444
+ J_{f_1}(x)
445
+
425
446
  Parameters
426
447
  ----------
427
448
  x : array-like of shape (n_points, n_inputs)
@@ -1030,48 +1051,51 @@ class SurrogateModelFamily:
1030
1051
 
1031
1052
 
1032
1053
  class PyTorchModel(SurrogateModel):
1033
- import torch
1034
- import torch._dynamo # fmt: skip
1035
- torch._dynamo.config.suppress_errors = True
1036
- import torch.func as torch_func # fmt: skip
1054
+ """
1055
+ Class that stores and evaluates an existing PyTorch model.
1056
+ """
1037
1057
 
1038
- # from torch.func import jacfwd, jacrev, jvp, vjp, vmap # fmt: skip
1058
+ def __init__(self, *args, dtype=None, max_batch=50_000, **kwargs):
1059
+ import torch
1060
+ import torch._dynamo
1039
1061
 
1040
- def __init__(self, *args, dtype=torch.float16, max_batch=50_000, **kwargs):
1062
+ torch._dynamo.config.suppress_errors = True
1041
1063
  super().__init__(*args, **kwargs)
1042
1064
  self.model.eval()
1043
- self.device = self.torch.device(
1044
- "cuda" if self.torch.cuda.is_available() else "cpu"
1045
- )
1065
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
1046
1066
  self.model.to(self.device)
1047
- self.dtype = dtype
1048
- self.model.to(dtype)
1067
+ self.dtype = torch.float16 if dtype is None else dtype
1068
+ self.model.to(self.dtype)
1049
1069
  self.max_batch = max_batch
1050
1070
 
1051
1071
  def __to_numpy(self, _x):
1072
+ import torch
1073
+
1052
1074
  try:
1053
1075
  import cupy as cp
1054
1076
  except ImportError: # pragma: no cover
1055
1077
  cp = np
1056
- lalib = (
1057
- cp if self.device == self.torch.device("cuda") else np
1058
- ) # pragma: no cover
1078
+ lalib = cp if self.device == torch.device("cuda") else np # pragma: no cover
1059
1079
  return lalib.asarray(_x).astype(lalib.float32)
1060
1080
 
1061
1081
  def __to_tensor(self, _x):
1082
+ import torch
1083
+
1062
1084
  if len(_x.shape) < 2:
1063
1085
  _x = _x.reshape(1, -1)
1064
- return self.torch.as_tensor(_x, device=self.device, dtype=self.dtype)
1086
+ return torch.as_tensor(_x, device=self.device, dtype=self.dtype)
1065
1087
 
1066
1088
  def __record_in_dtype(self, _x): # pragma: no cover
1067
1089
  # TODO: could be used to retain the input/output dtype the same
1068
1090
  self.in_dtype = _x.dtype
1069
1091
 
1070
1092
  def _predict_model_output(self, x):
1093
+ import torch
1094
+
1071
1095
  x = self.__to_tensor(x)
1072
1096
 
1073
- with self.torch.no_grad():
1074
- output = self.torch.zeros(
1097
+ with torch.no_grad():
1098
+ output = torch.zeros(
1075
1099
  (x.shape[0], self.n_outputs),
1076
1100
  device=self.device,
1077
1101
  dtype=self.dtype,
@@ -1100,21 +1124,28 @@ class PyTorchModel(SurrogateModel):
1100
1124
  return self.__to_numpy(y)
1101
1125
 
1102
1126
  def _save_model(self, filepath):
1103
- self.torch.save(self.model.state_dict(), filepath)
1127
+ import torch
1128
+
1129
+ torch.save(self.model.state_dict(), filepath)
1104
1130
 
1105
1131
  @staticmethod
1106
1132
  def _load_model(filepath, model_cls):
1107
- import torch # fmt: skip
1133
+ import torch
1134
+
1108
1135
  model_cls.load_state_dict(torch.load(filepath, weights_only=True))
1109
1136
  return model_cls
1110
1137
 
1111
1138
  def _predict_model_jvp(self, x, dx):
1139
+ import torch.func as torch_func
1140
+
1112
1141
  if not hasattr(self, "batched_jvp_func"):
1113
1142
  def g(_x): # fmt: skip
1114
1143
  return self.model(_x)
1115
- def single_jvp(_x, _v): # fmt: skip
1116
- return self.torch_func.jvp(g, (_x,), (_v,))
1117
- self.batched_jvp_func = self.torch_func.vmap(single_jvp)
1144
+
1145
+ def single_jvp(_x, _v):
1146
+ return torch_func.jvp(g, (_x,), (_v,))
1147
+
1148
+ self.batched_jvp_func = torch_func.vmap(single_jvp)
1118
1149
 
1119
1150
  x = self.__to_tensor(x)
1120
1151
  dx = self.__to_tensor(dx)
@@ -1122,13 +1153,17 @@ class PyTorchModel(SurrogateModel):
1122
1153
  return self.__to_numpy(output.detach()), self.__to_numpy(jvp_res.detach())
1123
1154
 
1124
1155
  def _predict_model_vjp(self, x, dy):
1156
+ import torch.func as torch_func
1157
+
1125
1158
  if not hasattr(self, "batched_vjp_func"):
1126
1159
  def g(_x): # fmt: skip
1127
1160
  return self.model(_x)
1128
- def single_vjp(_x, _v): # fmt: skip
1129
- out, f = self.torch_func.vjp(g, _x)
1161
+
1162
+ def single_vjp(_x, _v):
1163
+ out, f = torch_func.vjp(g, _x)
1130
1164
  return out, f(_v)
1131
- self.batched_vjp_func = self.torch_func.vmap(single_vjp)
1165
+
1166
+ self.batched_vjp_func = torch_func.vmap(single_vjp)
1132
1167
 
1133
1168
  x = self.__to_tensor(x)
1134
1169
  dy = self.__to_tensor(dy)
@@ -1136,49 +1171,50 @@ class PyTorchModel(SurrogateModel):
1136
1171
  return self.__to_numpy(output.detach()), self.__to_numpy(vjp_res[0].detach())
1137
1172
 
1138
1173
  def _predict_model_output_and_jacobian_fwd(self, x):
1174
+ import torch.func as torch_func
1175
+
1139
1176
  if not hasattr(self, "y_jac_fwd_func"):
1140
1177
  def g(_x): # fmt: skip
1141
1178
  result = self.model(_x)
1142
1179
  return (result, result)
1143
- self.y_jac_fwd_func = self.torch_func.vmap(
1144
- self.torch_func.jacfwd(g, has_aux=True)
1145
- )
1180
+ self.y_jac_fwd_func = torch_func.vmap(torch_func.jacfwd(g, has_aux=True))
1146
1181
 
1147
1182
  x = self.__to_tensor(x)
1148
1183
  jac, output = self.y_jac_fwd_func(x)
1149
1184
  return self.__to_numpy(output.detach()), self.__to_numpy(jac.detach())
1150
1185
 
1151
1186
  def _predict_model_output_and_jacobian_rev(self, x):
1187
+ import torch.func as torch_func
1188
+
1152
1189
  if not hasattr(self, "y_jac_rev_func"):
1153
1190
  def g(_x): # fmt: skip
1154
1191
  result = self.model(_x)
1155
1192
  return (result, result)
1156
- self.y_jac_rev_func = self.torch_func.vmap(
1157
- self.torch_func.jacrev(g, has_aux=True)
1158
- )
1193
+ self.y_jac_rev_func = torch_func.vmap(torch_func.jacrev(g, has_aux=True))
1159
1194
 
1160
1195
  x = self.__to_tensor(x)
1161
1196
  jac, output = self.y_jac_rev_func(x)
1162
1197
  return self.__to_numpy(output.detach()), self.__to_numpy(jac.detach())
1163
1198
 
1164
1199
  def _predict_model_jacobian_sbatch(self, x): # pragma: no cover
1165
- """Not used for now, but left as a reference for future if GPU memory issues arise."""
1200
+ # Not used for now, but left as a reference for future if GPU memory issues arise.
1201
+ import torch
1202
+ import torch.func as torch_func
1203
+
1166
1204
  if not hasattr(self, "y_jac_func"):
1167
1205
  def g(_x): # fmt: skip
1168
1206
  result = self.model(_x)
1169
1207
  return (result, result)
1170
- self.y_jac_func = self.torch_func.vmap(
1171
- self.torch_func.jacrev(g, has_aux=True)
1172
- )
1208
+ self.y_jac_func = torch_func.vmap(torch_func.jacrev(g, has_aux=True))
1173
1209
 
1174
1210
  x = self.__to_tensor(x)
1175
1211
  batch_size = x.shape[0]
1176
- y = self.torch.zeros(
1212
+ y = torch.zeros(
1177
1213
  (batch_size, self.n_outputs),
1178
1214
  device=self.device,
1179
1215
  dtype=self.dtype,
1180
1216
  )
1181
- jac = self.torch.zeros(
1217
+ jac = torch.zeros(
1182
1218
  (batch_size, self.n_outputs, self.n_inputs),
1183
1219
  device=self.device,
1184
1220
  dtype=self.dtype,
@@ -125,7 +125,7 @@ class GainOffsetScaler(Transformer):
125
125
  x : array-like of shape (n_points, n_features)
126
126
  Input data that will be transformed.
127
127
  inplace : bool
128
- `True` if the transformation must happen inplace. Same behavior of the `copy` attribute in scikit-learn.
128
+ `True` if the transformation must happen inplace, which means that no additional memory is allocated.
129
129
 
130
130
  Returns
131
131
  -------
@@ -147,7 +147,7 @@ class GainOffsetScaler(Transformer):
147
147
  x : array-like of shape (n_points, n_features)
148
148
  Input data that will be transformed.
149
149
  inplace : bool
150
- True if the transformation must happen inplace. Same behavior of the `copy` attribute in scikit-learn.
150
+ `True` if the transformation must happen inplace, which means that no additional memory is allocated.
151
151
 
152
152
  Returns
153
153
  -------
@@ -173,6 +173,7 @@ class GainOffsetScaler(Transformer):
173
173
  -------
174
174
  y : array-like of shape (n_points, n_features)
175
175
  Gradient of the direct transformation evaluated on the input data.
176
+ Since all features are independent, the gradient is stored in compact form.
176
177
  Warning: a read-only view is returned.
177
178
  """
178
179
  y = np.broadcast_to(self.gain_, x.shape)
@@ -190,6 +191,7 @@ class GainOffsetScaler(Transformer):
190
191
  -------
191
192
  y : array-like of shape (n_points, n_features)
192
193
  Gradient of the inverse transformation evaluated on the input data.
194
+ Since all features are independent, the gradient is stored in compact form.
193
195
  Warning: a read-only view is returned.
194
196
  """
195
197
  y = np.broadcast_to(self.inv_gain_, x.shape)
@@ -344,7 +346,7 @@ class PCA(Transformer):
344
346
  x : ndarray of shape (n_points, n_features)
345
347
  Input data that will be transformed.
346
348
  inplace : bool, optional
347
- Must be `False`. Although it would be possible to to update the first `n_components` columns of `x`,
349
+ Must be `False`. Although it would be possible to update the first `n_components` columns of `x`,
348
350
  we choose to not do it, so that `predict_output()` can be supported more easily.
349
351
 
350
352
  Returns
@@ -527,7 +529,7 @@ class PowerTransformerYeoJohnson(Transformer):
527
529
  x : array-like of shape (n_points, n_features)
528
530
  Input data that will be transformed.
529
531
  inplace : bool
530
- `True` if the transformation must happen inplace. Same behavior of the `copy` attribute in scikit-learn.
532
+ `True` if the transformation must happen inplace, which means that no additional memory is allocated.
531
533
 
532
534
  Returns
533
535
  -------
@@ -627,7 +629,7 @@ class PowerTransformerYeoJohnson(Transformer):
627
629
  x : array-like of shape (n_points, n_features)
628
630
  Input data that will be transformed.
629
631
  inplace : bool
630
- True if the transformation must happen inplace. Same behavior of the `copy` attribute in scikit-learn.
632
+ `True` if the transformation must happen inplace, which means that no additional memory is allocated.
631
633
 
632
634
  Returns
633
635
  -------
@@ -726,6 +728,7 @@ class PowerTransformerYeoJohnson(Transformer):
726
728
  -------
727
729
  y : array-like of shape (n_points, n_features)
728
730
  Gradient of the transformation evaluated on the input data.
731
+ Since all features are independent, the gradient is stored in compact form.
729
732
  """
730
733
  y = np.zeros_like(x)
731
734
 
@@ -780,6 +783,7 @@ class PowerTransformerYeoJohnson(Transformer):
780
783
  -------
781
784
  y : array-like of shape (n_points, n_features)
782
785
  Gradient of the inverse transformation evaluated on the input data.
786
+ Since all features are independent, the gradient is stored in compact form.
783
787
  """
784
788
  y = np.zeros_like(x)
785
789
 
@@ -817,39 +821,50 @@ class PowerTransformerYeoJohnson(Transformer):
817
821
 
818
822
 
819
823
  class StandardScalerTorch(StandardScaler):
820
- import torch
821
824
 
822
825
  def __init__(self, scale, mean):
826
+ import torch
827
+
823
828
  super().__init__(scale, mean)
824
- device = self.torch.device("cuda" if self.torch.cuda.is_available() else "cpu")
829
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
825
830
  self.device = device
826
831
  def __np2torch(v): # fmt: skip
827
- return self.torch.as_tensor(v).to(device)
832
+ return torch.as_tensor(v).to(device)
828
833
  self.gain_ = __np2torch(self.gain_)
829
834
  self.inv_gain_ = __np2torch(self.inv_gain_)
830
835
  self.offset_ = __np2torch(self.offset_)
831
836
 
832
837
  def transform(self, x, inplace=True):
838
+ import torch
839
+
833
840
  if inplace:
834
841
  raise ValueError("No inplace...")
835
- x = self.torch.as_tensor(x, device=self.device, dtype=self.torch.float32)
842
+ x = torch.as_tensor(x, device=self.device, dtype=torch.float32)
836
843
  return super().transform(x, inplace)
837
844
 
838
845
  def inverse_transform(self, x, inplace=True):
846
+ import torch
847
+
839
848
  if inplace:
840
849
  raise ValueError("No inplace...")
841
- x = self.torch.as_tensor(x, device=self.device, dtype=self.torch.float32)
850
+ x = torch.as_tensor(x, device=self.device, dtype=torch.float32)
842
851
  return super().inverse_transform(x, inplace)
843
852
 
844
853
  def transform_gradient(self, x):
845
- return self.torch.broadcast_to(self.gain_, x.shape).numpy()
854
+ import torch
855
+
856
+ return torch.broadcast_to(self.gain_, x.shape).numpy()
846
857
 
847
858
  def inverse_transform_gradient(self, x):
848
- return self.torch.broadcast_to(self.inv_gain_, x.shape).numpy()
859
+ import torch
860
+
861
+ return torch.broadcast_to(self.inv_gain_, x.shape).numpy()
849
862
 
850
863
  def __eq__(self, other):
864
+ import torch
865
+
851
866
  if isinstance(other, self.__class__):
852
- test_gain = self.torch.equal(self.gain_, other.gain_)
853
- test_offset = self.torch.equal(self.offset_, other.offset_)
867
+ test_gain = torch.equal(self.gain_, other.gain_)
868
+ test_offset = torch.equal(self.offset_, other.offset_)
854
869
  return test_gain and test_offset
855
870
  return False