Skip to content

Commit e3a7311

Browse files
committed
tony's comments
1 parent 1ea2e26 commit e3a7311

File tree

3 files changed

+100
-47
lines changed

3 files changed

+100
-47
lines changed

aeon/transformations/collection/self_supervised/_trilite.py

Lines changed: 71 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,18 @@
88
import sys
99
import time
1010
from copy import deepcopy
11+
from typing import TYPE_CHECKING
1112

1213
import numpy as np
1314
from sklearn.utils import check_random_state
1415

16+
from aeon.networks import BaseDeepLearningNetwork
1517
from aeon.transformations.collection import BaseCollectionTransformer
18+
from aeon.utils.self_supervised.general import z_normalization
19+
20+
if TYPE_CHECKING:
21+
from tensorflow.keras.callbacks import Callback
22+
from tensorflow.keras.optimizers import Optimizer
1623

1724

1825
class TRILITE(BaseCollectionTransformer):
@@ -32,39 +39,39 @@ class TRILITE(BaseCollectionTransformer):
3239
3340
Parameters
3441
----------
35-
alpha: float, default = 1e-2
42+
alpha : float, default = 1e-2
3643
The value that controls the space of the triplet loss,
3744
the smaller the value the more difficult the problem
3845
becomes, the higher the value the more easy the problem
3946
becomes, a balance should be found.
40-
weight_ref_min: float, default = 0.6
47+
weight_ref_min : float, default = 0.6
4148
The weight of the reference series used for the triplet
4249
generation.
43-
percentage_mask_length: int, default = 0.2
50+
percentage_mask_length : int, default = 0.2
4451
The percentage of time series length to calculate
4552
the length of the masking used for the triplet
4653
generation. Default is 20%.
47-
use_mixing_up: bool, default = True
54+
use_mixing_up : bool, default = True
4855
Wether or not to use mixing up during the triplet
4956
generation phase.
50-
use_masking: bool, default = True
57+
use_masking : bool, default = True
5158
Whether or not to use masking during the triplet
5259
generation phase.
53-
znormalize_pos_neg: bool, default = True
54-
Whether or not to znormalize (mean 0 and std 1)
60+
z_normalize_pos_neg : bool, default = True
61+
Whether or not to z_normalize (mean 0 and std 1)
5562
pos and neg samples after generating the triplet.
56-
backbone_network: aeon Network, default = None
63+
backbone_network : aeon Network, default = None
5764
The backbone network used for the SSL model,
5865
it can be any network from the aeon.networks
5966
module on condition for it's structure to be
6067
configured as "encoder", see _config attribute.
6168
For TRILITE, the default network used is
6269
FCNNetwork.
63-
latent_space_dim: int, default = 128
70+
latent_space_dim : int, default = 128
6471
The size of the latent space, applied using a
6572
fully connected layer at the end of the network's
6673
output.
67-
latent_space_activation: str, default = "linear"
74+
latent_space_activation : str, default = "linear"
6875
The activation to control the range of values
6976
of the latent space.
7077
random_state : int, RandomState instance or None, default=None
@@ -151,36 +158,36 @@ class TRILITE(BaseCollectionTransformer):
151158

152159
def __init__(
153160
self,
154-
alpha=1e-2,
155-
weight_ref_min=0.6,
156-
percentage_mask_length=0.2,
157-
use_mixing_up=True,
158-
use_masking=True,
159-
znormalize_pos_neg=True,
160-
backbone_network=None,
161-
latent_space_dim=128,
162-
latent_space_activation="linear",
163-
random_state=None,
164-
verbose=False,
165-
optimizer=None,
166-
file_path="./",
167-
save_best_model=False,
168-
save_last_model=False,
169-
save_init_model=False,
170-
best_file_name="best_model",
171-
last_file_name="last_model",
172-
init_file_name="init_model",
173-
callbacks=None,
174-
batch_size=64,
175-
use_mini_batch_size=False,
176-
n_epochs=2000,
161+
alpha: float = 1e-2,
162+
weight_ref_min: float = 0.6,
163+
percentage_mask_length: float = 0.2,
164+
use_mixing_up: bool = True,
165+
use_masking: bool = True,
166+
z_normalize_pos_neg: bool = True,
167+
backbone_network: BaseDeepLearningNetwork = None,
168+
latent_space_dim: int = 128,
169+
latent_space_activation: str = "linear",
170+
random_state: int | np.random.RandomState | None = None,
171+
verbose: bool = False,
172+
optimizer: Optimizer | None = None,
173+
file_path: str = "./",
174+
save_best_model: bool = False,
175+
save_last_model: bool = False,
176+
save_init_model: bool = False,
177+
best_file_name: str = "best_model",
178+
last_file_name: str = "last_model",
179+
init_file_name: str = "init_model",
180+
callbacks: Callback | list[Callback] | None = None,
181+
batch_size: int = 64,
182+
use_mini_batch_size: bool = False,
183+
n_epochs: int = 2000,
177184
):
178185
self.alpha = alpha
179186
self.weight_ref_min = weight_ref_min
180187
self.percentage_mask_length = percentage_mask_length
181188
self.use_mixing_up = use_mixing_up
182189
self.use_masking = use_masking
183-
self.znormalize_pos_neg = znormalize_pos_neg
190+
self.z_normalize_pos_neg = z_normalize_pos_neg
184191
self.backbone_network = backbone_network
185192
self.latent_space_dim = latent_space_dim
186193
self.latent_space_activation = latent_space_activation
@@ -201,7 +208,7 @@ def __init__(
201208

202209
super().__init__()
203210

204-
def _fit(self, X, y=None):
211+
def _fit(self, X: np.ndarray, y=None):
205212
"""Fit the SSL model on X, y is ignored.
206213
207214
Parameters
@@ -216,7 +223,7 @@ def _fit(self, X, y=None):
216223
"""
217224
import tensorflow as tf
218225

219-
from aeon.networks import BaseDeepLearningNetwork, FCNNetwork
226+
from aeon.networks import FCNNetwork
220227

221228
if isinstance(self.backbone_network, BaseDeepLearningNetwork):
222229
self._backbone_network = deepcopy(self.backbone_network)
@@ -374,7 +381,7 @@ def build_model(self, input_shape):
374381
375382
Parameters
376383
----------
377-
input_shape : tuple
384+
input_shape : tuple[int, int]
378385
The shape of the data fed into the input layer, should be (m, d).
379386
380387
Returns
@@ -453,32 +460,41 @@ def _triplet_generation(self, X):
453460
n_channels = int(X.shape[-1])
454461
length_TS = int(X.shape[1])
455462

463+
# define mask length
456464
self.mask_length = int(length_TS * self.percentage_mask_length)
457465

466+
# define weight for each sample in the mixing up
458467
w_ref = np.random.choice(
459468
np.linspace(start=self.weight_ref_min, stop=1, num=1000), size=1
460469
)
461470
w_ts = (1 - w_ref) / 2
462471

472+
# define your ref as random permutation of X
463473
ref = np.random.permutation(X[:])
464474

465475
n = int(ref.shape[0])
466476

477+
# define positive and negative sample arrays
467478
_pos = np.zeros(shape=ref.shape)
468479
_neg = np.zeros(shape=ref.shape)
469480

470481
all_indices = np.arange(start=0, stop=n)
471482

472483
for i_ref in range(n):
484+
# remove the sample ref from the random choice of pos-neg
473485
all_indices_without_ref = np.delete(arr=all_indices, obj=i_ref)
486+
487+
# choose a random sample used for the negative generation
474488
index_neg = int(np.random.choice(all_indices_without_ref, size=1))
475489

476490
_ref = ref[i_ref].copy()
477491

492+
# remove the index_neg from choices
478493
all_indices_without_ref_and_not_ref = np.delete(
479494
arr=all_indices, obj=[i_ref, index_neg]
480495
)
481496

497+
# choose samples used for the mixing up
482498
index_ts1_pos = int(
483499
np.random.choice(all_indices_without_ref_and_not_ref, size=1)
484500
)
@@ -504,9 +520,12 @@ def _triplet_generation(self, X):
504520
# MixingUp
505521

506522
if self.use_mixing_up and self.use_masking:
523+
# mix up the selected series with ref to obtain pos
507524
_pos[i_ref] = w_ref * _ref + w_ts * _ts1_pos + w_ts * _ts2_pos
525+
# mix up the selected series with neg ref to obtain neg
508526
_neg[i_ref] = w_ref * _not_ref + w_ts * _ts1_neg + w_ts * _ts2_neg
509527

528+
# apply masking
510529
_pos[i_ref], _neg[i_ref] = self._apply_masking(
511530
pos=_pos[i_ref],
512531
neg=_neg[i_ref],
@@ -516,10 +535,13 @@ def _triplet_generation(self, X):
516535
)
517536

518537
elif self.use_mixing_up and not self.use_masking:
538+
# mix up the selected series with ref to obtain pos
519539
_pos[i_ref] = w_ref * _ref + w_ts * _ts1_pos + w_ts * _ts2_pos
540+
# mix up the selected series with neg ref to obtain neg
520541
_neg[i_ref] = w_ref * _not_ref + w_ts * _ts1_neg + w_ts * _ts2_neg
521542

522543
elif self.use_masking and not self.use_mixing_up:
544+
# apply masking
523545
_pos[i_ref], _neg[i_ref] = self._apply_masking(
524546
pos=_pos[i_ref],
525547
neg=_neg[i_ref],
@@ -534,29 +556,38 @@ def _triplet_generation(self, X):
534556
"should be chosen to generate",
535557
"the triplets.",
536558
)
537-
if self.znormalize_pos_neg:
538-
_pos_normalized = self._znormalization(_pos)
539-
_neg_normalized = self._znormalization(_neg)
559+
if self.z_normalize_pos_neg:
560+
# z_normalize pos and neg
561+
_pos_normalized = z_normalization(_pos)
562+
_neg_normalized = z_normalization(_neg)
540563

541564
return ref, _pos_normalized, _neg_normalized
542565
else:
543566
return ref, _pos, _neg
544567

545568
def _apply_masking(self, pos, neg, n_channels, length_TS, mask_length):
546569
"""Apply masking phase on pos and neg."""
570+
# select a random start for the mask
547571
start_mask = int(np.random.randint(low=0, high=length_TS - mask_length, size=1))
548572
stop_mask = start_mask + mask_length
549573

574+
# define noise on replacement on the left side of the mask
550575
noise_pos_left = np.random.random(size=(start_mask, n_channels))
576+
# normalize noise
551577
noise_pos_left /= 5
552578
noise_pos_left -= 0.1
579+
580+
# define noise on replacement on the left side of the mask
553581
noise_pos_right = np.random.random(size=(length_TS - stop_mask, n_channels))
582+
# normalize noise
554583
noise_pos_right /= 5
555584
noise_pos_right -= 0.1
556585

586+
# replace left and right side of the mask by normalized noise
557587
pos[0:start_mask, :] = noise_pos_left
558588
pos[stop_mask:length_TS, :] = noise_pos_right
559589

590+
# repeat the same procedure for the negative sample
560591
noise_neg_left = np.random.random(size=(start_mask, n_channels))
561592
noise_neg_left /= 5
562593
noise_neg_left -= 0.1
@@ -569,13 +600,6 @@ def _apply_masking(self, pos, neg, n_channels, length_TS, mask_length):
569600

570601
return pos, neg
571602

572-
def _znormalization(self, X):
573-
stds = np.std(X, axis=1, keepdims=True)
574-
if len(stds[stds == 0.0]) > 0:
575-
stds[stds == 0.0] = 1.0
576-
return (X - X.mean(axis=1, keepdims=True)) / stds
577-
return (X - X.mean(axis=1, keepdims=True)) / (X.std(axis=1, keepdims=True))
578-
579603
def save_last_model_to_file(self, file_path="./"):
580604
"""Save the last epoch of the trained deep learning model.
581605
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Utils for self_supervised."""

aeon/utils/self_supervised/general.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""General utils for self_supervised."""
2+
3+
__all__ = ["z_normalization"]
4+
5+
import numpy as np
6+
7+
8+
def z_normalization(self, X, axis=1):
9+
"""Z-Normalize collection of time series.
10+
11+
Parameters
12+
----------
13+
X : np.ndarray
14+
The input collection of time series of shape
15+
(n_cases, n_channels, n_timepoints).
16+
axis : int, default = 1
17+
The axis of time, on which z-normalization
18+
is performed.
19+
20+
Returns
21+
-------
22+
Normalized version of X.
23+
"""
24+
stds = np.std(X, axis=1, keepdims=True)
25+
if len(stds[stds == 0.0]) > 0:
26+
stds[stds == 0.0] = 1.0
27+
return (X - X.mean(axis=1, keepdims=True)) / stds
28+
return (X - X.mean(axis=1, keepdims=True)) / (X.std(axis=1, keepdims=True))

0 commit comments

Comments
 (0)