Skip to content

Commit

Permalink
[ENH] Add AEDCNNClusterer (#1911)
Browse files Browse the repository at this point in the history
* Add DCNN

* remove triplet loss and move utils.py to utils/networks

* Add docstring and minor changes dcnn network

* minor fixes

* Update DCNNEncoderNetwork

* add activation kwarg

* minor

* minor

* minor fixes

* update class name

* minor

* minor

* Add temporal_latent_space kwarg

* minor

* minor

* Add test for DCNNNetwork

* minor

* refactor test

* add AEDCNN Network

* Add tag

* bug fix

* bug fix

* bug fixes and add tests

* add pytest.skipif

* update base

* Update _ae_dcnn.py

* pre-commit

* minor

* minor

* use flatten instead of GMP

* minor fix

* typo fix

* Replace Conv1D with Conv1DTranspose in the decoder

* Add AEDCNNClusterer

* add to __init__

* typo fix

* bug fix

* num => n

* fix clusterer

* fix tests

* make symmetric only network

* fix tests

* fix clusterer

* Fix bugs

* Add estimator kwarg

* Add notebook

* Add handling on None in kernel_size

* Add padding kwargs

* Fix network

* minor fixes

* Warn if dilation rate > 1

* dilation-rate issues

* Automatic `pre-commit` fixes

* fix tests

* fix

* Delete examples/clustering/deep_clustering.ipynb

* Add user warning

* Add metrics kwarg to clusterer

* remove return_X_y

* Update _ae_dcnn.py

* Update _ae_dcnn.py

* Update _ae_dcnn.py

* minor

---------

Co-authored-by: aadya940 <[email protected]>
  • Loading branch information
aadya940 and aadya940 authored Nov 12, 2024
1 parent 217572b commit 5adee43
Show file tree
Hide file tree
Showing 2 changed files with 352 additions and 0 deletions.
2 changes: 2 additions & 0 deletions aeon/clustering/deep_learning/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
"BaseDeepClusterer",
"AEFCNClusterer",
"AEResNetClusterer",
"AEDCNNClusterer",
"AEDRNNClusterer",
"AEAttentionBiGRUClusterer",
"AEBiGRUClusterer",
]
from aeon.clustering.deep_learning._ae_abgru import AEAttentionBiGRUClusterer
from aeon.clustering.deep_learning._ae_bgru import AEBiGRUClusterer
from aeon.clustering.deep_learning._ae_dcnn import AEDCNNClusterer
from aeon.clustering.deep_learning._ae_drnn import AEDRNNClusterer
from aeon.clustering.deep_learning._ae_fcn import AEFCNClusterer
from aeon.clustering.deep_learning._ae_resnet import AEResNetClusterer
Expand Down
350 changes: 350 additions & 0 deletions aeon/clustering/deep_learning/_ae_dcnn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,350 @@
"""Deep Learning Auto-Encoder using DCNN Network."""

__all__ = ["AEDCNNClusterer"]

import gc
import os
import time
from copy import deepcopy

from sklearn.utils import check_random_state

from aeon.clustering import DummyClusterer
from aeon.clustering.deep_learning.base import BaseDeepClusterer
from aeon.networks import AEDCNNNetwork


class AEDCNNClusterer(BaseDeepClusterer):
"""Auto-Encoder based Dilated Convolutional Networks (DCNN), as described in [1]_.
Parameters
----------
n_clusters : int, default=None
Number of clusters for the deep learnign model.
clustering_algorithm : str, default="deprecated"
Use 'estimator' parameter instead.
clustering_params : dict, default=None
Use 'estimator' parameter instead.
estimator : aeon clusterer, default=None
An aeon estimator to be built using the transformed data.
Defaults to aeon TimeSeriesKMeans() with euclidean distance
and mean averaging method and n_clusters set to 2.
latent_space_dim : int, default=128
Dimension of the latent space of the auto-encoder.
temporal_latent_space : bool, default = False
Flag to choose whether the latent space is an MTS or Euclidean space.
n_layers : int, default = 3
Number of convolution layers in the encoder.
n_filters : int or list of int, default = None
Number of filters used in convolution layers in the encoder.
kernel_size : int or list of int, default = 3
Size of convolution kernel in the encoder.
dilation_rate : int or list of int, default = 1
The dilation rate for convolution in the encoder.
`dilation_rate` greater than `1` is not supported on
`Conv1DTranspose` for some devices/OS.
activation : str or list of str, default = "relu"
Activation used after the convolution in the encoder.
padding_encoder : str or list of str, default = "causal"
Keras compatible Padding string for the encoder. Defaults to a list
of "causal" paddings.
padding_decoder : str or list of str, default = "same"
Keras compatible Padding string for the decoder. Defaults to a list
of "same" paddings.
use_bias : bool or list of bool, default = True
Whether or not ot use bias in convolution.
n_epochs : int, default = 2000
The number of epochs to train the model.
batch_size : int, default = 16
The number of samples per gradient update.
use_mini_batch_size : bool, default = True,
Whether or not to use the mini batch size formula.
random_state : int, RandomState instance or None, default=None
If `int`, random_state is the seed used by the random number generator;
If `RandomState` instance, random_state is the random number generator;
If `None`, the random number generator is the `RandomState` instance used
by `np.random`.
Seeded random number generation can only be guaranteed on CPU processing,
GPU processing will be non-deterministic.
verbose : boolean, default = False
Whether to output extra information.
loss : string, default="mean_squared_error"
Fit parameter for the keras model.
metrics : List[str], default=["mean_squared_error"]
Metrics to evaluate the performance of the deep learning network.
optimizer : keras.optimizers object, default = Adam(lr=0.01)
Specify the optimizer and the learning rate to be used.
file_path : str, default = "./"
File path to save best model.
save_best_model : bool, default = False
Whether or not to save the best model, if the
modelcheckpoint callback is used by default,
this condition, if True, will prevent the
automatic deletion of the best saved model from
file and the user can choose the file name.
save_last_model : bool, default = False
Whether or not to save the last model, last
epoch trained, using the base class method
save_last_model_to_file.
best_file_name : str, default = "best_model"
The name of the file of the best model, if
save_best_model is set to False, this parameter
is discarded.
last_file_name : str, default = "last_model"
The name of the file of the last model, if
save_last_model is set to False, this parameter
is discarded.
callbacks : keras.callbacks, default = None
List of keras callbacks.
Examples
--------
>>> from aeon.clustering.deep_learning import AEDCNNClusterer
>>> from aeon.datasets import load_unit_test
>>> from aeon.clustering import DummyClusterer
>>> X_train, y_train = load_unit_test(split="train")
>>> X_test, y_test = load_unit_test(split="test")
>>> _clst = DummyClusterer(n_clusters=2)
>>> aedcnn=AEDCNNClusterer(estimator=_clst, n_epochs=20,
... batch_size=4) # doctest: +SKIP
>>> aedcnn.fit(X_train) # doctest: +SKIP
AEDCNNClusterer(...)
"""

def __init__(
self,
n_clusters=None,
estimator=None,
clustering_algorithm="deprecated",
clustering_params=None,
latent_space_dim=128,
temporal_latent_space=False,
n_layers=3,
n_filters=None,
kernel_size=3,
dilation_rate=1,
activation="relu",
padding_encoder="same",
padding_decoder="same",
n_epochs=2000,
batch_size=32,
use_mini_batch_size=False,
random_state=None,
verbose=False,
loss="mse",
metrics=None,
optimizer="Adam",
file_path="./",
save_best_model=False,
save_last_model=False,
best_file_name="best_model",
last_file_name="last_file",
callbacks=None,
):
self.latent_space_dim = latent_space_dim
self.temporal_latent_space = temporal_latent_space
self.n_layers = n_layers
self.n_filters = n_filters
self.kernel_size = kernel_size
self.activation = activation
self.padding_encoder = padding_encoder
self.padding_decoder = padding_decoder
self.dilation_rate = dilation_rate
self.optimizer = optimizer
self.loss = loss
self.metrics = metrics
self.verbose = verbose
self.use_mini_batch_size = use_mini_batch_size
self.callbacks = callbacks
self.file_path = file_path
self.n_epochs = n_epochs
self.save_best_model = save_best_model
self.save_last_model = save_last_model
self.best_file_name = best_file_name
self.random_state = random_state

super().__init__(
n_clusters=n_clusters,
clustering_params=clustering_params,
clustering_algorithm=clustering_algorithm,
estimator=estimator,
batch_size=batch_size,
last_file_name=last_file_name,
)

self._network = AEDCNNNetwork(
latent_space_dim=self.latent_space_dim,
temporal_latent_space=self.temporal_latent_space,
n_layers=self.n_layers,
n_filters=self.n_filters,
kernel_size=self.kernel_size,
dilation_rate=self.dilation_rate,
activation=self.activation,
padding_encoder=self.padding_encoder,
padding_decoder=self.padding_decoder,
)

def build_model(self, input_shape, **kwargs):
"""Construct a compiled, un-trained, keras model that is ready for training.
In aeon, time series are stored in numpy arrays of shape
(n_channels,n_timepoints). Keras/tensorflow assume
data is in shape (n_timepoints,n_channels). This method also assumes
(n_timepoints,n_channels). Transpose should happen in fit.
Parameters
----------
input_shape : tuple
The shape of the data fed into the input layer, should be
(n_timepoints,n_channels).
Returns
-------
output : a compiled Keras Model.
"""
import numpy as np
import tensorflow as tf

rng = check_random_state(self.random_state)
self.random_state_ = rng.randint(0, np.iinfo(np.int32).max)
tf.keras.utils.set_random_seed(self.random_state_)
encoder, decoder = self._network.build_network(input_shape, **kwargs)

input_layer = tf.keras.layers.Input(input_shape, name="input layer")
encoder_output = encoder(input_layer)
decoder_output = decoder(encoder_output)
output_layer = tf.keras.layers.Reshape(
target_shape=input_shape, name="outputlayer"
)(decoder_output)

model = tf.keras.models.Model(inputs=input_layer, outputs=output_layer)

self.optimizer_ = (
tf.keras.optimizers.Adam() if self.optimizer is None else self.optimizer
)

if self.metrics is None:
self._metrics = ["mean_squared_error"]
elif isinstance(self.metrics, list):
self._metrics = self.metrics
elif isinstance(self.metrics, str):
self._metrics = [self.metrics]
else:
raise ValueError("Metrics should be a list, string, or None.")

model.compile(optimizer=self.optimizer_, loss=self.loss, metrics=self._metrics)

return model

def _fit(self, X):
"""Fit the classifier on the training set (X, y).
Parameters
----------
X : np.ndarray of shape = (n_cases (n), n_channels (d), n_timepoints (m))
The training input samples.
Returns
-------
self : object
"""
import tensorflow as tf

# Transpose to conform to Keras input style.
X = X.transpose(0, 2, 1)

self.input_shape = X.shape[1:]
self.training_model_ = self.build_model(self.input_shape)

if self.verbose:
self.training_model_.summary()

if self.use_mini_batch_size:
mini_batch_size = min(self.batch_size, X.shape[0] // 10)
else:
mini_batch_size = self.batch_size

self.file_name_ = (
self.best_file_name if self.save_best_model else str(time.time_ns())
)

if self.callbacks is None:
self.callbacks_ = [
tf.keras.callbacks.ReduceLROnPlateau(
monitor="loss", factor=0.5, patience=50, min_lr=0.0001
),
tf.keras.callbacks.ModelCheckpoint(
filepath=self.file_path + self.file_name_ + ".keras",
monitor="loss",
save_best_only=True,
),
]
else:
self.callbacks_ = self._get_model_checkpoint_callback(
callbacks=self.callbacks,
file_path=self.file_path,
file_name=self.file_name_,
)

self.history = self.training_model_.fit(
X,
X,
batch_size=mini_batch_size,
epochs=self.n_epochs,
verbose=self.verbose,
callbacks=self.callbacks_,
)

try:
self.model_ = tf.keras.models.load_model(
self.file_path + self.file_name_ + ".keras", compile=False
)
if not self.save_best_model:
os.remove(self.file_path + self.file_name_ + ".keras")
except FileNotFoundError:
self.model_ = deepcopy(self.training_model_)

self._fit_clustering(X=X)

gc.collect()

return self

def _score(self, X, y=None):
# Transpose to conform to Keras input style.
X = X.transpose(0, 2, 1)
latent_space = self.model_.layers[1].predict(X)
return self._estimator.score(latent_space)

@classmethod
def _get_test_params(cls, parameter_set="default"):
"""Return testing parameter settings for the estimator.
Parameters
----------
parameter_set : str, default="default"
Name of the set of test parameters to return, for use in tests. If no
special parameters are defined for a value, will return `"default"` set.
For classifiers, a "default" set of parameters should be provided for
general testing, and a "results_comparison" set for comparing against
previously recorded results if the general set does not produce suitable
probabilities to compare against.
Returns
-------
params : dict or list of dict, default={}
Parameters to create testing instances of the class.
Each dict are parameters to construct an "interesting" test instance, i.e.,
`MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
`create_test_instance` uses the first (or only) dictionary in `params`.
"""
param1 = {
"estimator": DummyClusterer(n_clusters=2),
"n_epochs": 1,
"batch_size": 4,
"n_layers": 1,
"n_filters": 1,
"kernel_size": None,
}

return [param1]

0 comments on commit 5adee43

Please sign in to comment.