bowdbeg commited on
Commit
b7ef094
·
1 Parent(s): 6646ae1

refactor to arranged code

Browse files
Files changed (1) hide show
  1. matching_series.py +132 -141
matching_series.py CHANGED
@@ -164,11 +164,14 @@ class matching_series(evaluate.Metric):
164
  return_coverages = True
165
  predictions = np.array(predictions).astype(dtype)
166
  references = np.array(references).astype(dtype)
 
167
  if instance_normalization:
168
  predictions = (predictions - predictions.mean(axis=1, keepdims=True)) / predictions.std(
169
  axis=1, keepdims=True
170
  )
171
  references = (references - references.mean(axis=1, keepdims=True)) / references.std(axis=1, keepdims=True)
 
 
172
  if predictions.shape[1:] != references.shape[1:]:
173
  raise ValueError(
174
  "The number of features in the predictions and references should be the same. predictions: {}, references: {}".format(
@@ -177,7 +180,110 @@ class matching_series(evaluate.Metric):
177
  )
178
 
179
  # at first, convert the inputs to numpy arrays
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  # distance between predictions and references for all example combinations for each features
182
  # shape: (num_generation, num_reference, num_features)
183
  if batch_size is not None:
@@ -194,7 +300,7 @@ class matching_series(evaluate.Metric):
194
  ]
195
  with concurrent.futures.ProcessPoolExecutor(max_workers=num_process) as executor:
196
  results = executor.map(
197
- self._compute_metric,
198
  *zip(*args),
199
  )
200
  for (i, j), d in zip(idxs, results):
@@ -205,7 +311,7 @@ class matching_series(evaluate.Metric):
205
  # iterate over the predictions and references in batches
206
  for i in range(0, len(predictions) + batch_size, batch_size):
207
  for j in range(0, len(references) + batch_size, batch_size):
208
- d = self._compute_metric(
209
  predictions[i : i + batch_size, None],
210
  references[None, j : j + batch_size],
211
  metric=metric,
@@ -213,24 +319,34 @@ class matching_series(evaluate.Metric):
213
  )
214
  distance[i : i + batch_size, j : j + batch_size] = d
215
  else:
216
- distance = self._compute_metric(predictions[:, None], references[None, :], metric=metric, axis=-2)
 
217
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  index_distance = distance.diagonal(axis1=0, axis2=1).mean().item()
219
 
220
  # matching scores
221
- distance_mean = distance.mean(axis=-1)
222
  # best match for each generated time series
223
  # shape: (num_generation,)
224
- best_match = np.argmin(distance_mean, axis=-1)
225
-
226
- # matching distance
227
- # shape: (num_generation,)
228
- precision_distance = distance_mean[np.arange(len(best_match)), best_match].mean().item()
229
 
230
  # best match for each reference time series
231
  # shape: (num_reference,)
232
- best_match_inv = np.argmin(distance_mean, axis=0)
233
- recall_distance = distance_mean[best_match_inv, np.arange(len(best_match_inv))].mean().item()
234
 
235
  f1_distance = 2 / (1 / (precision_distance + eps) + 1 / (recall_distance + eps))
236
  mean_distance = (precision_distance + recall_distance) / 2
@@ -240,144 +356,19 @@ class matching_series(evaluate.Metric):
240
  matching_precision = np.unique(best_match_inv).size / len(best_match)
241
  matching_f1 = 2 / (1 / (matching_precision + eps) + 1 / (matching_recall + eps))
242
 
243
- # take matching for each feature and compute metrics for them
244
- precision_distance_features = []
245
- recall_distance_features = []
246
- f1_distance_features = []
247
- mean_distance_features = []
248
- matching_precision_features = []
249
- matching_recall_features = []
250
- matching_f1_features = []
251
- index_distance_features = []
252
- coverages_features = []
253
- cuc_features = []
254
- for f in range(predictions.shape[-1]):
255
- distance_f = distance[:, :, f]
256
- index_distance_f = (distance_f.diagonal(axis1=0, axis2=1).mean()).item()
257
- best_match_f = np.argmin(distance_f, axis=-1)
258
- precision_distance_f = (distance_f[np.arange(len(best_match_f)), best_match_f].mean()).item()
259
- best_match_inv_f = np.argmin(distance_f, axis=0)
260
- recall_distance_f = (distance_f[best_match_inv_f, np.arange(len(best_match_inv_f))].mean()).item()
261
- f1_distance_f = 2 / (1 / (precision_distance_f + eps) + 1 / (recall_distance_f + eps))
262
- mean_distance_f = (precision_distance_f + recall_distance_f) / 2
263
- precision_distance_features.append(precision_distance_f)
264
- recall_distance_features.append(recall_distance_f)
265
- f1_distance_features.append(f1_distance_f)
266
- index_distance_features.append(index_distance_f)
267
- mean_distance_features.append(mean_distance_f)
268
-
269
- matching_recall_f = np.unique(best_match_f).size / len(best_match_f)
270
- matching_precision_f = np.unique(best_match_inv_f).size / len(best_match_inv_f)
271
- matching_f1_f = 2 / (1 / (matching_precision_f + eps) + 1 / (matching_recall_f + eps))
272
- matching_precision_features.append(matching_precision_f)
273
- matching_recall_features.append(matching_recall_f)
274
- matching_f1_features.append(matching_f1_f)
275
-
276
- coverages_f, cuc_f = self.compute_cuc(best_match_f, len(references), cuc_n_calculation, cuc_n_samples)
277
- coverages_features.append(coverages_f)
278
- cuc_features.append(cuc_f)
279
-
280
- macro_precision_distance = statistics.mean(precision_distance_features)
281
- macro_recall_distance = statistics.mean(recall_distance_features)
282
- macro_f1_distance = statistics.mean(f1_distance_features)
283
- macro_mean_distance = statistics.mean(mean_distance_features)
284
- macro_index_distance = statistics.mean(index_distance_features)
285
-
286
- macro_matching_precision = statistics.mean(matching_precision_features)
287
- macro_matching_recall = statistics.mean(matching_recall_features)
288
- macro_matching_f1 = statistics.mean(matching_f1_features)
289
-
290
  # cuc
291
- coverages, cuc = self.compute_cuc(best_match, len(references), cuc_n_calculation, cuc_n_samples)
292
-
293
- macro_cuc = statistics.mean(cuc_features)
294
- macro_coverages = [statistics.mean(c) for c in zip(*coverages_features)]
295
- out = {
296
  "precision_distance": precision_distance,
297
  "f1_distance": f1_distance,
298
  "recall_distance": recall_distance,
299
  "mean_distance": mean_distance,
300
  "index_distance": index_distance,
301
- "macro_precision_distance": macro_precision_distance,
302
- "macro_recall_distance": macro_recall_distance,
303
- "macro_f1_distance": macro_f1_distance,
304
- "macro_mean_distance": macro_mean_distance,
305
- "macro_index_distance": macro_index_distance,
306
  "matching_precision": matching_precision,
307
  "matching_recall": matching_recall,
308
  "matching_f1": matching_f1,
309
- "macro_matching_precision": macro_matching_precision,
310
- "macro_matching_recall": macro_matching_recall,
311
- "macro_matching_f1": macro_matching_f1,
312
  "cuc": cuc,
313
- "macro_cuc": macro_cuc,
 
 
314
  }
315
- if return_distance:
316
- out["distance"] = distance
317
- if return_matching:
318
- out["match"] = best_match
319
- out["match_inv"] = best_match_inv
320
- if return_each_features:
321
- if return_distance:
322
- out["distance_features"] = distance_mean
323
- out.update(
324
- {
325
- "precision_distance_features": precision_distance_features,
326
- "f1_distance_features": f1_distance_features,
327
- "recall_distance_features": recall_distance_features,
328
- "index_distance_features": index_distance_features,
329
- "matching_precision_features": matching_precision_features,
330
- "matching_recall_features": matching_recall_features,
331
- "matching_f1_features": matching_f1_features,
332
- "cuc_features": cuc_features,
333
- "coverages_features": coverages_features,
334
- }
335
- )
336
- if return_coverages:
337
- out["coverages"] = coverages
338
- out["macro_coverages"] = macro_coverages
339
- return out
340
-
341
- def compute_cuc(
342
- self,
343
- match: np.ndarray,
344
- n_reference: int,
345
- n_calculation: int,
346
- n_samples: Union[List[int], str],
347
- ):
348
- """
349
- Compute Coverage Under Curve
350
- Args:
351
- match: best match for each generated time series
352
- n_reference: number of reference time series
353
- n_calculation: number of Coverage Under Curve calculate times
354
- n_samples: number of samples to use for Coverage Under Curve calculation. If "auto", it uses the number of samples of the predictions.
355
- Returns:
356
- """
357
- n_generaiton = len(match)
358
- if n_samples == "auto":
359
- exp = int(math.log2(n_generaiton))
360
- n_samples = [int(2**i) for i in range(exp)]
361
- n_samples.append(n_generaiton)
362
- assert isinstance(n_samples, list) and all(isinstance(n, int) for n in n_samples)
363
-
364
- coverages = []
365
- for n_sample in n_samples:
366
- coverage = 0
367
- for _ in range(n_calculation):
368
- sample = np.random.choice(match, size=n_sample, replace=False) # type: ignore
369
- coverage += len(np.unique(sample)) / n_reference
370
- coverages.append(coverage / n_calculation)
371
- cuc = (np.trapz(coverages, n_samples) / len(n_samples) / max(n_samples)).item()
372
- return coverages, cuc
373
-
374
- @staticmethod
375
- def _compute_metric(x, y, metric: str = "mse", axis: int = -1):
376
- if metric.lower() == "mse":
377
- return np.mean((x - y) ** 2, axis=axis)
378
- elif metric.lower() == "mae":
379
- return np.mean(np.abs(x - y), axis=axis)
380
- elif metric.lower() == "rmse":
381
- return np.sqrt(np.mean((x - y) ** 2, axis=axis))
382
- else:
383
- raise ValueError("Unknown metric: {}".format(metric))
 
164
  return_coverages = True
165
  predictions = np.array(predictions).astype(dtype)
166
  references = np.array(references).astype(dtype)
167
+
168
  if instance_normalization:
169
  predictions = (predictions - predictions.mean(axis=1, keepdims=True)) / predictions.std(
170
  axis=1, keepdims=True
171
  )
172
  references = (references - references.mean(axis=1, keepdims=True)) / references.std(axis=1, keepdims=True)
173
+
174
+ assert isinstance(predictions, np.ndarray) and isinstance(references, np.ndarray)
175
  if predictions.shape[1:] != references.shape[1:]:
176
  raise ValueError(
177
  "The number of features in the predictions and references should be the same. predictions: {}, references: {}".format(
 
180
  )
181
 
182
  # at first, convert the inputs to numpy arrays
183
+ distance = self.compute_distance(
184
+ predictions=predictions,
185
+ references=references,
186
+ metric=metric,
187
+ batch_size=batch_size,
188
+ num_process=num_process,
189
+ dtype=dtype,
190
+ )
191
+
192
+ metrics = self._compute_metrics(
193
+ distance=distance.mean(axis=-1),
194
+ eps=eps,
195
+ cuc_n_calculation=cuc_n_calculation,
196
+ cuc_n_samples=cuc_n_samples,
197
+ )
198
+ metrics_feature = [
199
+ self._compute_metrics(distance[:, :, f], eps, cuc_n_calculation, cuc_n_samples)
200
+ for f in range(predictions.shape[-1])
201
+ ]
202
+ macro_metrics = {
203
+ "macro_" + k: statistics.mean([m[k] for m in metrics_feature]) # type: ignore
204
+ for k in metrics_feature[0].keys()
205
+ if isinstance(metrics_feature[0][k], (int, float))
206
+ }
207
+
208
+ out = {}
209
+ out.update({k: v for k, v in metrics.items() if isinstance(v, (int, float))})
210
+ out.update(macro_metrics)
211
+
212
+ if return_distance:
213
+ out["distance"] = distance
214
+ if return_matching:
215
+ out.update({k: v for k, v in metrics.items() if "match" in k})
216
+ if return_coverages:
217
+ out["coverages"] = metrics["coverages"]
218
+ if return_each_features:
219
+ out.update(
220
+ {
221
+ k + "_features": [m[k] for m in metrics_feature]
222
+ for k in metrics_feature[0].keys()
223
+ if isinstance(metrics_feature[0][k], (int, float))
224
+ }
225
+ )
226
+ if return_coverages:
227
+ out.update(
228
+ {
229
+ "coverages_features": [m["coverages"] for m in metrics_feature],
230
+ }
231
+ )
232
+ return out
233
+
234
+ def compute_cuc(
235
+ self,
236
+ match: np.ndarray,
237
+ n_reference: int,
238
+ n_calculation: int,
239
+ n_samples: Union[List[int], str],
240
+ ):
241
+ """
242
+ Compute Coverage Under Curve
243
+ Args:
244
+ match: best match for each generated time series
245
+ n_reference: number of reference time series
246
+ n_calculation: number of Coverage Under Curve calculate times
247
+ n_samples: number of samples to use for Coverage Under Curve calculation. If "auto", it uses the number of samples of the predictions.
248
+ Returns:
249
+ """
250
+ n_generaiton = len(match)
251
+ if n_samples == "auto":
252
+ exp = int(math.log2(n_generaiton))
253
+ n_samples = [int(2**i) for i in range(exp)]
254
+ n_samples.append(n_generaiton)
255
+ assert isinstance(n_samples, list) and all(isinstance(n, int) for n in n_samples)
256
 
257
+ coverages = []
258
+ for n_sample in n_samples:
259
+ coverage = 0
260
+ for _ in range(n_calculation):
261
+ sample = np.random.choice(match, size=n_sample, replace=False) # type: ignore
262
+ coverage += len(np.unique(sample)) / n_reference
263
+ coverages.append(coverage / n_calculation)
264
+ cuc = (np.trapz(coverages, n_samples) / len(n_samples) / max(n_samples)).item()
265
+ return coverages, cuc
266
+
267
+ @staticmethod
268
+ def _compute_distance(x, y, metric: str = "mse", axis: int = -1):
269
+ if metric.lower() == "mse":
270
+ return np.mean((x - y) ** 2, axis=axis)
271
+ elif metric.lower() == "mae":
272
+ return np.mean(np.abs(x - y), axis=axis)
273
+ elif metric.lower() == "rmse":
274
+ return np.sqrt(np.mean((x - y) ** 2, axis=axis))
275
+ else:
276
+ raise ValueError("Unknown metric: {}".format(metric))
277
+
278
+ def compute_distance(
279
+ self,
280
+ predictions: np.ndarray,
281
+ references: np.ndarray,
282
+ metric: str,
283
+ batch_size: Optional[int] = None,
284
+ num_process: int = 1,
285
+ dtype=np.float32,
286
+ ):
287
  # distance between predictions and references for all example combinations for each features
288
  # shape: (num_generation, num_reference, num_features)
289
  if batch_size is not None:
 
300
  ]
301
  with concurrent.futures.ProcessPoolExecutor(max_workers=num_process) as executor:
302
  results = executor.map(
303
+ self._compute_distance,
304
  *zip(*args),
305
  )
306
  for (i, j), d in zip(idxs, results):
 
311
  # iterate over the predictions and references in batches
312
  for i in range(0, len(predictions) + batch_size, batch_size):
313
  for j in range(0, len(references) + batch_size, batch_size):
314
+ d = self._compute_distance(
315
  predictions[i : i + batch_size, None],
316
  references[None, j : j + batch_size],
317
  metric=metric,
 
319
  )
320
  distance[i : i + batch_size, j : j + batch_size] = d
321
  else:
322
+ distance = self._compute_distance(predictions[:, None], references[None, :], metric=metric, axis=-2)
323
+ return distance
324
 
325
+ def _compute_metrics(
326
+ self,
327
+ distance: np.ndarray,
328
+ eps: float = 1e-10,
329
+ cuc_n_calculation: int = 3,
330
+ cuc_n_samples: Union[List[int], str] = "auto",
331
+ ) -> dict[str, float | list[float]]:
332
+ """
333
+ Compute metrics from the distance matrix
334
+ Args:
335
+ distance: distance matrix. shape: (num_generation, num_reference)
336
+ Returns:
337
+ """
338
  index_distance = distance.diagonal(axis1=0, axis2=1).mean().item()
339
 
340
  # matching scores
 
341
  # best match for each generated time series
342
  # shape: (num_generation,)
343
+ best_match = np.argmin(distance, axis=-1)
344
+ precision_distance = distance[np.arange(len(best_match)), best_match].mean().item()
 
 
 
345
 
346
  # best match for each reference time series
347
  # shape: (num_reference,)
348
+ best_match_inv = np.argmin(distance, axis=0)
349
+ recall_distance = distance[best_match_inv, np.arange(len(best_match_inv))].mean().item()
350
 
351
  f1_distance = 2 / (1 / (precision_distance + eps) + 1 / (recall_distance + eps))
352
  mean_distance = (precision_distance + recall_distance) / 2
 
356
  matching_precision = np.unique(best_match_inv).size / len(best_match)
357
  matching_f1 = 2 / (1 / (matching_precision + eps) + 1 / (matching_recall + eps))
358
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
  # cuc
360
+ coverages, cuc = self.compute_cuc(best_match, len(best_match_inv), cuc_n_calculation, cuc_n_samples)
361
+ return {
 
 
 
362
  "precision_distance": precision_distance,
363
  "f1_distance": f1_distance,
364
  "recall_distance": recall_distance,
365
  "mean_distance": mean_distance,
366
  "index_distance": index_distance,
 
 
 
 
 
367
  "matching_precision": matching_precision,
368
  "matching_recall": matching_recall,
369
  "matching_f1": matching_f1,
 
 
 
370
  "cuc": cuc,
371
+ "coverages": coverages,
372
+ "match": best_match,
373
+ "match_inv": best_match_inv,
374
  }