epyt-flow 0.10.0__py3-none-any.whl → 0.12.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.
- epyt_flow/VERSION +1 -1
- epyt_flow/data/benchmarks/gecco_water_quality.py +2 -2
- epyt_flow/data/benchmarks/leakdb.py +40 -5
- epyt_flow/data/benchmarks/water_usage.py +4 -3
- epyt_flow/data/networks.py +27 -14
- epyt_flow/gym/__init__.py +0 -3
- epyt_flow/gym/scenario_control_env.py +11 -13
- epyt_flow/rest_api/scenario/control_handlers.py +118 -0
- epyt_flow/rest_api/scenario/event_handlers.py +114 -1
- epyt_flow/rest_api/scenario/handlers.py +33 -0
- epyt_flow/rest_api/server.py +14 -2
- epyt_flow/serialization.py +1 -0
- epyt_flow/simulation/__init__.py +0 -1
- epyt_flow/simulation/backend/__init__.py +1 -0
- epyt_flow/simulation/backend/my_epyt.py +1056 -0
- epyt_flow/simulation/events/actuator_events.py +7 -1
- epyt_flow/simulation/events/quality_events.py +3 -1
- epyt_flow/simulation/scada/scada_data.py +716 -5
- epyt_flow/simulation/scenario_config.py +1 -40
- epyt_flow/simulation/scenario_simulator.py +645 -119
- epyt_flow/simulation/sensor_config.py +18 -2
- epyt_flow/topology.py +24 -7
- epyt_flow/uncertainty/model_uncertainty.py +80 -62
- epyt_flow/uncertainty/sensor_noise.py +15 -4
- epyt_flow/uncertainty/uncertainties.py +71 -18
- epyt_flow/uncertainty/utils.py +40 -13
- epyt_flow/utils.py +45 -1
- epyt_flow/visualization/__init__.py +2 -0
- epyt_flow/visualization/scenario_visualizer.py +1240 -0
- epyt_flow/visualization/visualization_utils.py +738 -0
- {epyt_flow-0.10.0.dist-info → epyt_flow-0.12.0.dist-info}/METADATA +15 -4
- {epyt_flow-0.10.0.dist-info → epyt_flow-0.12.0.dist-info}/RECORD +35 -36
- {epyt_flow-0.10.0.dist-info → epyt_flow-0.12.0.dist-info}/WHEEL +1 -1
- epyt_flow/gym/control_gyms.py +0 -47
- epyt_flow/metrics.py +0 -466
- epyt_flow/models/__init__.py +0 -2
- epyt_flow/models/event_detector.py +0 -31
- epyt_flow/models/sensor_interpolation_detector.py +0 -118
- epyt_flow/simulation/scada/advanced_control.py +0 -138
- epyt_flow/simulation/scenario_visualizer.py +0 -1307
- {epyt_flow-0.10.0.dist-info → epyt_flow-0.12.0.dist-info/licenses}/LICENSE +0 -0
- {epyt_flow-0.10.0.dist-info → epyt_flow-0.12.0.dist-info}/top_level.txt +0 -0
epyt_flow/metrics.py
DELETED
|
@@ -1,466 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This module provides different metrics for evaluation.
|
|
3
|
-
"""
|
|
4
|
-
import numpy as np
|
|
5
|
-
from sklearn.metrics import roc_auc_score as skelarn_roc_auc_score, f1_score as skelarn_f1_scpre, \
|
|
6
|
-
mean_absolute_error, root_mean_squared_error, r2_score as sklearn_r2_score
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def r2_score(y_pred: np.ndarray, y: np.ndarray) -> float:
|
|
10
|
-
"""
|
|
11
|
-
Computes the R^2 score (also called the coefficient of determination).
|
|
12
|
-
|
|
13
|
-
Parameters
|
|
14
|
-
----------
|
|
15
|
-
y_pred : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
16
|
-
Predicted outputs.
|
|
17
|
-
y : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
18
|
-
Ground truth outputs.
|
|
19
|
-
|
|
20
|
-
Returns
|
|
21
|
-
-------
|
|
22
|
-
`float`
|
|
23
|
-
R^2 score.
|
|
24
|
-
"""
|
|
25
|
-
return sklearn_r2_score(y, y_pred)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def running_r2_score(y_pred: np.ndarray, y: np.ndarray) -> list[float]:
|
|
29
|
-
"""
|
|
30
|
-
Computes and returns the running R^2 score -- i.e. the R^2 score for every point in time.
|
|
31
|
-
|
|
32
|
-
Parameters
|
|
33
|
-
----------
|
|
34
|
-
y_pred : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
35
|
-
Predicted outputs.
|
|
36
|
-
y : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
37
|
-
Ground truth outputs.
|
|
38
|
-
|
|
39
|
-
Returns
|
|
40
|
-
-------
|
|
41
|
-
`list[float]`
|
|
42
|
-
The running R^2 score.
|
|
43
|
-
"""
|
|
44
|
-
r = []
|
|
45
|
-
|
|
46
|
-
for t in range(2, len(y_pred)):
|
|
47
|
-
r.append(r2_score(y_pred[:t], y[:t]))
|
|
48
|
-
|
|
49
|
-
return r
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def mean_squared_error(y_pred: np.ndarray, y: np.ndarray) -> float:
|
|
53
|
-
"""
|
|
54
|
-
Computes the Mean Squared Error (MSE).
|
|
55
|
-
|
|
56
|
-
Parameters
|
|
57
|
-
----------
|
|
58
|
-
y_pred : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
59
|
-
Predicted outputs.
|
|
60
|
-
y : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
61
|
-
Ground truth outputs.
|
|
62
|
-
|
|
63
|
-
Returns
|
|
64
|
-
-------
|
|
65
|
-
`float`
|
|
66
|
-
MSE.
|
|
67
|
-
"""
|
|
68
|
-
return root_mean_squared_error(y, y_pred)**2
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def running_mse(y_pred: np.ndarray, y: np.ndarray) -> list[float]:
|
|
72
|
-
"""
|
|
73
|
-
Computes the running Mean Squared Error (MSE) -- i.e. the MSE for every point in time.
|
|
74
|
-
|
|
75
|
-
Parameters
|
|
76
|
-
----------
|
|
77
|
-
y_pred : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
78
|
-
Predicted outputs.
|
|
79
|
-
y : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
80
|
-
Ground truth outputs.
|
|
81
|
-
|
|
82
|
-
Returns
|
|
83
|
-
-------
|
|
84
|
-
`float`
|
|
85
|
-
Running MSE.
|
|
86
|
-
"""
|
|
87
|
-
if not isinstance(y_pred, np.ndarray):
|
|
88
|
-
raise TypeError("'y_pred' must be an instance of 'numpy.ndarray' " +
|
|
89
|
-
f"but not of '{type(y_pred)}'")
|
|
90
|
-
if not isinstance(y, np.ndarray):
|
|
91
|
-
raise TypeError("'y' must be an instance of 'numpy.ndarray' " +
|
|
92
|
-
f"but not of '{type(y)}'")
|
|
93
|
-
if y_pred.shape != y.shape:
|
|
94
|
-
raise ValueError(f"Shape mismatch: {y_pred.shape} vs. {y.shape}")
|
|
95
|
-
if len(y_pred.shape) != 1:
|
|
96
|
-
raise ValueError("'y_pred' must be a 1d array")
|
|
97
|
-
if len(y.shape) != 1:
|
|
98
|
-
raise ValueError("'y' must be a 1d array")
|
|
99
|
-
|
|
100
|
-
e_sq = np.square(y - y_pred)
|
|
101
|
-
r_mse = list(esq for esq in e_sq)
|
|
102
|
-
|
|
103
|
-
for i in range(1, len(y)):
|
|
104
|
-
r_mse[i] = float((i * r_mse[i - 1]) / (i + 1)) + (r_mse[i] / (i + 1))
|
|
105
|
-
|
|
106
|
-
return r_mse
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def mape(y_pred: np.ndarray, y: np.ndarray, epsilon: float = .05) -> float:
|
|
110
|
-
"""
|
|
111
|
-
Computes the Mean Absolute Percentage Error (MAPE).
|
|
112
|
-
|
|
113
|
-
Parameters
|
|
114
|
-
----------
|
|
115
|
-
y_pred : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
116
|
-
Predicted outputs.
|
|
117
|
-
y : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
118
|
-
Ground truth outputs.
|
|
119
|
-
epsilon : `float`, optional
|
|
120
|
-
Small number added to predictions and ground truth to avoid division-by-zero.
|
|
121
|
-
|
|
122
|
-
The default is 0.05
|
|
123
|
-
|
|
124
|
-
Returns
|
|
125
|
-
-------
|
|
126
|
-
`float`
|
|
127
|
-
MAPE score.
|
|
128
|
-
"""
|
|
129
|
-
if not isinstance(y_pred, np.ndarray):
|
|
130
|
-
raise TypeError("'y_pred' must be an instance of 'numpy.ndarray' " +
|
|
131
|
-
f"but not of '{type(y_pred)}'")
|
|
132
|
-
if not isinstance(y, np.ndarray):
|
|
133
|
-
raise TypeError("'y' must be an instance of 'numpy.ndarray' " +
|
|
134
|
-
f"but not of '{type(y)}'")
|
|
135
|
-
if not isinstance(epsilon, float):
|
|
136
|
-
raise TypeError("'epsilon' must be an instance of 'float' " +
|
|
137
|
-
f"but not of '{type(epsilon)}'")
|
|
138
|
-
if y_pred.shape != y.shape:
|
|
139
|
-
raise ValueError(f"Shape mismatch: {y_pred.shape} vs. {y.shape}")
|
|
140
|
-
if len(y_pred.shape) != 1:
|
|
141
|
-
raise ValueError("'y_pred' must be a 1d array")
|
|
142
|
-
if len(y.shape) != 1:
|
|
143
|
-
raise ValueError("'y' must be a 1d array")
|
|
144
|
-
|
|
145
|
-
y_ = y + epsilon
|
|
146
|
-
y_pred_ = y_pred + epsilon
|
|
147
|
-
return np.mean(np.abs((y_ - y_pred_) / y_))
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def smape(y_pred: np.ndarray, y: np.ndarray, epsilon: float = .05) -> float:
|
|
151
|
-
"""
|
|
152
|
-
Computes the Symmetric Mean Absolute Percentage Error (SMAPE).
|
|
153
|
-
|
|
154
|
-
Parameters
|
|
155
|
-
----------
|
|
156
|
-
y_pred : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
157
|
-
Predicted outputs.
|
|
158
|
-
y : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
159
|
-
Ground truth outputs.
|
|
160
|
-
epsilon : `float`, optional
|
|
161
|
-
Small number added to predictions and ground truth to avoid division-by-zero.
|
|
162
|
-
|
|
163
|
-
The default is 0.05
|
|
164
|
-
|
|
165
|
-
Returns
|
|
166
|
-
-------
|
|
167
|
-
`float`
|
|
168
|
-
SMAPE score.
|
|
169
|
-
"""
|
|
170
|
-
if not isinstance(y_pred, np.ndarray):
|
|
171
|
-
raise TypeError("'y_pred' must be an instance of 'numpy.ndarray' " +
|
|
172
|
-
f"but not of '{type(y_pred)}'")
|
|
173
|
-
if not isinstance(y, np.ndarray):
|
|
174
|
-
raise TypeError("'y' must be an instance of 'numpy.ndarray' " +
|
|
175
|
-
f"but not of '{type(y)}'")
|
|
176
|
-
if not isinstance(epsilon, float):
|
|
177
|
-
raise TypeError("'epsilon' must be an instance of 'float' " +
|
|
178
|
-
f"but not of '{type(epsilon)}'")
|
|
179
|
-
if y_pred.shape != y.shape:
|
|
180
|
-
raise ValueError(f"Shape mismatch: {y_pred.shape} vs. {y.shape}")
|
|
181
|
-
if len(y_pred.shape) != 1:
|
|
182
|
-
raise ValueError("'y_pred' must be a 1d array")
|
|
183
|
-
if len(y.shape) != 1:
|
|
184
|
-
raise ValueError("'y' must be a 1d array")
|
|
185
|
-
|
|
186
|
-
y_ = y + epsilon
|
|
187
|
-
y_pred_ = y_pred + epsilon
|
|
188
|
-
return 2. * np.mean(np.abs(y_ - y_pred_) / (np.abs(y_) + np.abs(y_pred_)))
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def mase(y_pred: np.ndarray, y: np.ndarray, epsilon: float = .05) -> float:
|
|
192
|
-
"""
|
|
193
|
-
Computes the Mean Absolute Scaled Error (MASE).
|
|
194
|
-
|
|
195
|
-
Parameters
|
|
196
|
-
----------
|
|
197
|
-
y_pred : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
198
|
-
Predicted outputs.
|
|
199
|
-
y : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
200
|
-
Ground truth outputs.
|
|
201
|
-
epsilon : `float`, optional
|
|
202
|
-
Small number added to predictions and ground truth to avoid division-by-zero.
|
|
203
|
-
|
|
204
|
-
The default is 0.05
|
|
205
|
-
|
|
206
|
-
Returns
|
|
207
|
-
-------
|
|
208
|
-
`float`
|
|
209
|
-
MASE score.
|
|
210
|
-
"""
|
|
211
|
-
if not isinstance(y_pred, np.ndarray):
|
|
212
|
-
raise TypeError("'y_pred' must be an instance of 'numpy.ndarray' " +
|
|
213
|
-
f"but not of '{type(y_pred)}'")
|
|
214
|
-
if not isinstance(y, np.ndarray):
|
|
215
|
-
raise TypeError("'y' must be an instance of 'numpy.ndarray' " +
|
|
216
|
-
f"but not of '{type(y)}'")
|
|
217
|
-
if not isinstance(epsilon, float):
|
|
218
|
-
raise TypeError("'epsilon' must be an instance of 'float' " +
|
|
219
|
-
f"but not of '{type(epsilon)}'")
|
|
220
|
-
if y_pred.shape != y.shape:
|
|
221
|
-
raise ValueError(f"Shape mismatch: {y_pred.shape} vs. {y.shape}")
|
|
222
|
-
if len(y_pred.shape) != 1:
|
|
223
|
-
raise ValueError("'y_pred' must be a 1d array")
|
|
224
|
-
if len(y.shape) != 1:
|
|
225
|
-
raise ValueError("'y' must be a 1d array")
|
|
226
|
-
|
|
227
|
-
try:
|
|
228
|
-
y_ = y + epsilon
|
|
229
|
-
y_pred_ = y_pred + epsilon
|
|
230
|
-
|
|
231
|
-
mae = mean_absolute_error(y_, y_pred_)
|
|
232
|
-
naive_error = np.mean(np.abs(y_[1:] - y_pred_[:-1]))
|
|
233
|
-
return mae / naive_error
|
|
234
|
-
except Exception:
|
|
235
|
-
return None
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
def f1_micro_score(y_pred: np.ndarray, y: np.ndarray) -> float:
|
|
239
|
-
"""
|
|
240
|
-
Computes the F1 score using for a multi-class classification by
|
|
241
|
-
counting the total true positives, false negatives and false positives.
|
|
242
|
-
|
|
243
|
-
Parameters
|
|
244
|
-
----------
|
|
245
|
-
y_pred : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
246
|
-
Predicted labels.
|
|
247
|
-
y : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
248
|
-
Ground truth labels.
|
|
249
|
-
|
|
250
|
-
Returns
|
|
251
|
-
-------
|
|
252
|
-
`float`
|
|
253
|
-
F1 score.
|
|
254
|
-
"""
|
|
255
|
-
if not isinstance(y_pred, np.ndarray):
|
|
256
|
-
raise TypeError("'y_pred' must be an instance of 'numpy.ndarray' " +
|
|
257
|
-
f"but not of '{type(y_pred)}'")
|
|
258
|
-
if not isinstance(y, np.ndarray):
|
|
259
|
-
raise TypeError("'y' must be an instance of 'numpy.ndarray' " +
|
|
260
|
-
f"but not of '{type(y)}'")
|
|
261
|
-
if y_pred.shape != y.shape:
|
|
262
|
-
raise ValueError(f"Shape mismatch: {y_pred.shape} vs. {y.shape}")
|
|
263
|
-
|
|
264
|
-
return skelarn_f1_scpre(y, y_pred, average="micro")
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
def roc_auc_score(y_pred: np.ndarray, y: np.ndarray) -> float:
|
|
268
|
-
"""
|
|
269
|
-
Computes the Area Under the Curve (AUC) of a classification.
|
|
270
|
-
|
|
271
|
-
Parameters
|
|
272
|
-
----------
|
|
273
|
-
y_pred : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
274
|
-
Predicted labels.
|
|
275
|
-
y : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
276
|
-
Ground truth labels.
|
|
277
|
-
|
|
278
|
-
Returns
|
|
279
|
-
-------
|
|
280
|
-
`float`
|
|
281
|
-
ROC AUC score.
|
|
282
|
-
"""
|
|
283
|
-
if not isinstance(y_pred, np.ndarray):
|
|
284
|
-
raise TypeError("'y_pred' must be an instance of 'numpy.ndarray' " +
|
|
285
|
-
f"but not of '{type(y_pred)}'")
|
|
286
|
-
if not isinstance(y, np.ndarray):
|
|
287
|
-
raise TypeError("'y' must be an instance of 'numpy.ndarray' " +
|
|
288
|
-
f"but not of '{type(y)}'")
|
|
289
|
-
if y_pred.shape != y.shape:
|
|
290
|
-
raise ValueError(f"Shape mismatch: {y_pred.shape} vs. {y.shape}")
|
|
291
|
-
|
|
292
|
-
return skelarn_roc_auc_score(y, y_pred)
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
def true_positive_rate(y_pred: np.ndarray, y: np.ndarray) -> float:
|
|
296
|
-
"""
|
|
297
|
-
Computes the true positive rate (also called sensitivity).
|
|
298
|
-
|
|
299
|
-
Parameters
|
|
300
|
-
----------
|
|
301
|
-
y_pred : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
302
|
-
Predicted labels.
|
|
303
|
-
y : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
304
|
-
Ground truth labels.
|
|
305
|
-
|
|
306
|
-
Returns
|
|
307
|
-
-------
|
|
308
|
-
`float`
|
|
309
|
-
True positive rate.
|
|
310
|
-
"""
|
|
311
|
-
if not isinstance(y_pred, np.ndarray):
|
|
312
|
-
raise TypeError("'y_pred' must be an instance of 'numpy.ndarray' " +
|
|
313
|
-
f"but not of '{type(y_pred)}'")
|
|
314
|
-
if not isinstance(y, np.ndarray):
|
|
315
|
-
raise TypeError("'y' must be an instance of 'numpy.ndarray' " +
|
|
316
|
-
f"but not of '{type(y)}'")
|
|
317
|
-
if y_pred.shape != y.shape:
|
|
318
|
-
raise ValueError(f"Shape mismatch: {y_pred.shape} vs. {y.shape}")
|
|
319
|
-
if len(y_pred.shape) != 1:
|
|
320
|
-
raise ValueError("'y_pred' must be a 1d array")
|
|
321
|
-
if len(y.shape) != 1:
|
|
322
|
-
raise ValueError("'y' must be a 1d array")
|
|
323
|
-
if set(np.unique(y_pred)) != set([0, 1]):
|
|
324
|
-
raise ValueError("Labels must be either '0' or '1'")
|
|
325
|
-
|
|
326
|
-
tp = np.sum((y == 1) & (y_pred == 1))
|
|
327
|
-
fn = np.sum((y == 1) & (y_pred == 0))
|
|
328
|
-
|
|
329
|
-
return tp / (tp + fn)
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
def true_negative_rate(y_pred: np.ndarray, y: np.ndarray) -> float:
|
|
333
|
-
"""
|
|
334
|
-
Computes the true negative rate (also called specificity).
|
|
335
|
-
|
|
336
|
-
Parameters
|
|
337
|
-
----------
|
|
338
|
-
y_pred : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
339
|
-
Predicted labels.
|
|
340
|
-
y : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
341
|
-
Ground truth labels.
|
|
342
|
-
|
|
343
|
-
Returns
|
|
344
|
-
-------
|
|
345
|
-
`float`
|
|
346
|
-
True negative rate.
|
|
347
|
-
"""
|
|
348
|
-
if not isinstance(y_pred, np.ndarray):
|
|
349
|
-
raise TypeError("'y_pred' must be an instance of 'numpy.ndarray' " +
|
|
350
|
-
f"but not of '{type(y_pred)}'")
|
|
351
|
-
if not isinstance(y, np.ndarray):
|
|
352
|
-
raise TypeError("'y' must be an instance of 'numpy.ndarray' " +
|
|
353
|
-
f"but not of '{type(y)}'")
|
|
354
|
-
if y_pred.shape != y.shape:
|
|
355
|
-
raise ValueError(f"Shape mismatch: {y_pred.shape} vs. {y.shape}")
|
|
356
|
-
if len(y_pred.shape) > 1:
|
|
357
|
-
raise ValueError("'y_pred' must be a 1d array")
|
|
358
|
-
if len(y.shape) > 1:
|
|
359
|
-
raise ValueError("'y' must be a 1d array")
|
|
360
|
-
if set(np.unique(y_pred)) != set([0, 1]):
|
|
361
|
-
raise ValueError("Labels must be either '0' or '1'")
|
|
362
|
-
|
|
363
|
-
tn = np.sum((y == 0) & (y_pred == 0))
|
|
364
|
-
fp = np.sum((y == 0) & (y_pred == 1))
|
|
365
|
-
|
|
366
|
-
return tn / (tn + fp)
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
def precision_score(y_pred: np.ndarray, y: np.ndarray) -> float:
|
|
370
|
-
"""
|
|
371
|
-
Computes the precision of a classification.
|
|
372
|
-
|
|
373
|
-
Parameters
|
|
374
|
-
----------
|
|
375
|
-
y_pred : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
376
|
-
Predicted labels.
|
|
377
|
-
y : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
378
|
-
Ground truth labels.
|
|
379
|
-
|
|
380
|
-
Returns
|
|
381
|
-
-------
|
|
382
|
-
`float`
|
|
383
|
-
Precision score.
|
|
384
|
-
"""
|
|
385
|
-
if not isinstance(y_pred, np.ndarray):
|
|
386
|
-
raise TypeError("'y_pred' must be an instance of 'numpy.ndarray' " +
|
|
387
|
-
f"but not of '{type(y_pred)}'")
|
|
388
|
-
if not isinstance(y, np.ndarray):
|
|
389
|
-
raise TypeError("'y' must be an instance of 'numpy.ndarray' " +
|
|
390
|
-
f"but not of '{type(y)}'")
|
|
391
|
-
if y_pred.shape != y.shape:
|
|
392
|
-
raise ValueError(f"Shape mismatch: {y_pred.shape} vs. {y.shape}")
|
|
393
|
-
if set(np.unique(y_pred)) != set([0, 1]):
|
|
394
|
-
raise ValueError("Labels must be either '0' or '1'")
|
|
395
|
-
|
|
396
|
-
tp = np.sum([np.all((y[i] == 1) & (y_pred[i] == 1)) for i in range(len(y))])
|
|
397
|
-
fp = np.sum([np.any((y[i] == 0) & (y_pred[i] == 1)) for i in range(len(y))])
|
|
398
|
-
|
|
399
|
-
return tp / (tp + fp)
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
def accuracy_score(y_pred: np.ndarray, y: np.ndarray) -> float:
|
|
403
|
-
"""
|
|
404
|
-
Computes the accuracy of a classification.
|
|
405
|
-
|
|
406
|
-
Parameters
|
|
407
|
-
----------
|
|
408
|
-
y_pred : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
409
|
-
Predicted labels.
|
|
410
|
-
y : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
411
|
-
Ground truth labels.
|
|
412
|
-
|
|
413
|
-
Returns
|
|
414
|
-
-------
|
|
415
|
-
`float`
|
|
416
|
-
Accuracy score.
|
|
417
|
-
"""
|
|
418
|
-
if not isinstance(y_pred, np.ndarray):
|
|
419
|
-
raise TypeError("'y_pred' must be an instance of 'numpy.ndarray' " +
|
|
420
|
-
f"but not of '{type(y_pred)}'")
|
|
421
|
-
if not isinstance(y, np.ndarray):
|
|
422
|
-
raise TypeError("'y' must be an instance of 'numpy.ndarray' " +
|
|
423
|
-
f"but not of '{type(y)}'")
|
|
424
|
-
if y_pred.shape != y.shape:
|
|
425
|
-
raise ValueError(f"Shape mismatch: {y_pred.shape} vs. {y.shape}")
|
|
426
|
-
|
|
427
|
-
tp = np.sum([np.all(y[i] == y_pred[i]) for i in range(len(y))])
|
|
428
|
-
return tp / len(y)
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
def f1_score(y_pred: np.ndarray, y: np.ndarray) -> float:
|
|
432
|
-
"""
|
|
433
|
-
Computes the F1-score for a binary classification.
|
|
434
|
-
|
|
435
|
-
Parameters
|
|
436
|
-
----------
|
|
437
|
-
y_pred : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
438
|
-
Predicted labels.
|
|
439
|
-
y : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
440
|
-
Ground truth labels.
|
|
441
|
-
|
|
442
|
-
Returns
|
|
443
|
-
-------
|
|
444
|
-
`float`
|
|
445
|
-
F1-score.
|
|
446
|
-
"""
|
|
447
|
-
if not isinstance(y_pred, np.ndarray):
|
|
448
|
-
raise TypeError("'y_pred' must be an instance of 'numpy.ndarray' " +
|
|
449
|
-
f"but not of '{type(y_pred)}'")
|
|
450
|
-
if not isinstance(y, np.ndarray):
|
|
451
|
-
raise TypeError("'y' must be an instance of 'numpy.ndarray' " +
|
|
452
|
-
f"but not of '{type(y)}'")
|
|
453
|
-
if y_pred.shape != y.shape:
|
|
454
|
-
raise ValueError(f"Shape mismatch: {y_pred.shape} vs. {y.shape}")
|
|
455
|
-
if len(y_pred.shape) != 1:
|
|
456
|
-
raise ValueError("'y_pred' must be a 1d array")
|
|
457
|
-
if len(y.shape) != 1:
|
|
458
|
-
raise ValueError("'y' must be a 1d array")
|
|
459
|
-
if set(np.unique(y_pred)) != set([0, 1]):
|
|
460
|
-
raise ValueError("Labels must be either '0' or '1'")
|
|
461
|
-
|
|
462
|
-
tp = np.sum((y == 1) & (y_pred == 1))
|
|
463
|
-
fp = np.sum((y == 0) & (y_pred == 1))
|
|
464
|
-
fn = np.sum((y == 1) & (y_pred == 0))
|
|
465
|
-
|
|
466
|
-
return (2. * tp) / (2. * tp + fp + fn)
|
epyt_flow/models/__init__.py
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Module provides a base class for event detectors.
|
|
3
|
-
"""
|
|
4
|
-
from abc import abstractmethod, ABC
|
|
5
|
-
|
|
6
|
-
from ..simulation.scada import ScadaData
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class EventDetector(ABC):
|
|
10
|
-
"""
|
|
11
|
-
Base class for event detectors.
|
|
12
|
-
"""
|
|
13
|
-
def __init__(self, **kwds):
|
|
14
|
-
super().__init__(**kwds)
|
|
15
|
-
|
|
16
|
-
@abstractmethod
|
|
17
|
-
def apply(self, scada_data: ScadaData) -> list[int]:
|
|
18
|
-
"""
|
|
19
|
-
Applies this detector to given SCADA data and returns suspicious time points.
|
|
20
|
-
|
|
21
|
-
Parameters
|
|
22
|
-
----------
|
|
23
|
-
scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`
|
|
24
|
-
SCADA data in which to look for events (i.e. anomalies).
|
|
25
|
-
|
|
26
|
-
Returns
|
|
27
|
-
-------
|
|
28
|
-
`list[int]`
|
|
29
|
-
List of suspicious time points.
|
|
30
|
-
"""
|
|
31
|
-
raise NotImplementedError()
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Module provides a simple residual-based event detector that performs sensor interpolation.
|
|
3
|
-
"""
|
|
4
|
-
from typing import Any, Union
|
|
5
|
-
from copy import deepcopy
|
|
6
|
-
import numpy as np
|
|
7
|
-
from sklearn.linear_model import LinearRegression
|
|
8
|
-
|
|
9
|
-
from .event_detector import EventDetector
|
|
10
|
-
from ..simulation.scada import ScadaData
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class SensorInterpolationDetector(EventDetector):
|
|
14
|
-
"""
|
|
15
|
-
Class implementing a residual-based event detector based on sensor interpolation.
|
|
16
|
-
|
|
17
|
-
Parameters
|
|
18
|
-
----------
|
|
19
|
-
regressor_type : `Any`, optional
|
|
20
|
-
Regressor class that will be used for the sensor interpolation.
|
|
21
|
-
Must implement the usual `fit` and `predict` functions.
|
|
22
|
-
|
|
23
|
-
The default is `sklearn.linear_model.LinearRegression <https://scikit-learn.org/dev/modules/generated/sklearn.linear_model.LinearRegression.html>`_
|
|
24
|
-
"""
|
|
25
|
-
def __init__(self, regressor_type: Any = LinearRegression, **kwds):
|
|
26
|
-
self.__regressor_type = regressor_type
|
|
27
|
-
self.__regressors = []
|
|
28
|
-
|
|
29
|
-
super().__init__(**kwds)
|
|
30
|
-
|
|
31
|
-
@property
|
|
32
|
-
def regressor_type(self) -> Any:
|
|
33
|
-
"""
|
|
34
|
-
Gets the class used for building the regressors in the sensor interpolation.
|
|
35
|
-
|
|
36
|
-
Returns
|
|
37
|
-
-------
|
|
38
|
-
`Any`
|
|
39
|
-
Regressor class.
|
|
40
|
-
"""
|
|
41
|
-
return self.__regressor_type
|
|
42
|
-
|
|
43
|
-
@property
|
|
44
|
-
def regressors(self) -> list[Any]:
|
|
45
|
-
"""
|
|
46
|
-
Gets the fitted sensor interpolation regressors.
|
|
47
|
-
|
|
48
|
-
Returns
|
|
49
|
-
-------
|
|
50
|
-
`list[Any]`
|
|
51
|
-
Fitted regressors.
|
|
52
|
-
"""
|
|
53
|
-
return deepcopy(self.__regressors)
|
|
54
|
-
|
|
55
|
-
def __eq__(self, other) -> bool:
|
|
56
|
-
return self.__regressor_type == other.regressor_type and \
|
|
57
|
-
all(self.__regressors == other.regressors)
|
|
58
|
-
|
|
59
|
-
def fit(self, scada_data: Union[ScadaData, np.ndarray]) -> None:
|
|
60
|
-
"""
|
|
61
|
-
Fit detector to given SCADA data -- assuming the given data represents
|
|
62
|
-
the normal operating state.
|
|
63
|
-
|
|
64
|
-
Parameters
|
|
65
|
-
----------
|
|
66
|
-
scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` or `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
67
|
-
SCADA data to fit this detector.
|
|
68
|
-
"""
|
|
69
|
-
if isinstance(scada_data, ScadaData):
|
|
70
|
-
data = scada_data.get_data()
|
|
71
|
-
else:
|
|
72
|
-
data = scada_data
|
|
73
|
-
|
|
74
|
-
self.__regressors = []
|
|
75
|
-
for output_idx in range(data.shape[1]):
|
|
76
|
-
input_idx = list(range(data.shape[1]))
|
|
77
|
-
input_idx.remove(output_idx)
|
|
78
|
-
|
|
79
|
-
X = data[:, input_idx]
|
|
80
|
-
y = data[:, output_idx]
|
|
81
|
-
|
|
82
|
-
model = self.__regressor_type()
|
|
83
|
-
model.fit(X, y)
|
|
84
|
-
|
|
85
|
-
y_pred = model.predict(X)
|
|
86
|
-
threshold = 1.2 * np.max(np.abs(y_pred - y))
|
|
87
|
-
|
|
88
|
-
self.__regressors.append((input_idx, output_idx, model, threshold))
|
|
89
|
-
|
|
90
|
-
def apply(self, scada_data: Union[ScadaData, np.ndarray]) -> list[int]:
|
|
91
|
-
"""
|
|
92
|
-
Applies this detector to given SCADA data and returns suspicious time points.
|
|
93
|
-
|
|
94
|
-
Parameters
|
|
95
|
-
----------
|
|
96
|
-
scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` or `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
97
|
-
SCADA data in which to look for events/anomalies.
|
|
98
|
-
|
|
99
|
-
Returns
|
|
100
|
-
-------
|
|
101
|
-
`list[int]`
|
|
102
|
-
List of suspicious time points.
|
|
103
|
-
"""
|
|
104
|
-
suspicious_time_points = []
|
|
105
|
-
|
|
106
|
-
if isinstance(scada_data, ScadaData):
|
|
107
|
-
X = scada_data.get_data()
|
|
108
|
-
else:
|
|
109
|
-
X = scada_data
|
|
110
|
-
|
|
111
|
-
for input_idx, output_idx, model, threshold in self.__regressors:
|
|
112
|
-
y_pred = model.predict(X[:, input_idx])
|
|
113
|
-
y = X[:, output_idx]
|
|
114
|
-
|
|
115
|
-
suspicious_time_points += list(np.argwhere(np.abs(y_pred - y) > threshold).
|
|
116
|
-
flatten())
|
|
117
|
-
|
|
118
|
-
return list(set(suspicious_time_points))
|