Repository: aeon-toolkit/aeon
Pull request #1122 (2024-01-31 14:32:08)

[ENH] Some fixes network module
- Normally code coverage should be 100% for the networks module
-  Fixed bugs when customizing some of the networks
- Added a `plot_network` functionality, its dependent on `pydot` so i added a catching error for it, i would be happy to add it as a soft dependency as well, any comments are welcome

aeon-actions-bot[bot]: 
## Thank you for contributing to `aeon`

I have added the following labels to this PR based on the title: **[ $\\color{#FEF1BE}{\\textsf{enhancement}}$ ]**.
I would have added the following labels to this PR based on the changes made: **[ $\\color{#379E11}{\\textsf{networks}}$ ]**, however some package labels are already present.

The [Checks](https://github.com/aeon-toolkit/aeon/pull/1122/checks) tab will show the status of our automated tests. You can click on individual test runs in the tab or "Details" in the panel below to see more information if there is a failure.

If our `pre-commit` code quality check fails, any trivial fixes will automatically be pushed to your PR unless it is a draft.

Don't hesitate to ask questions on the `aeon` [Slack](
https://join.slack.com/t/aeon-toolkit/shared_invite/zt-22vwvut29-HDpCu~7VBUozyfL_8j3dLA) channel if you have any.
    
TonyBagnall: thanks Ali, looks good. Two things
1. I think pydot should be a soft dependency
2. Would it make sense to the plotting functionality to the visualisation module as a function, then just pass the network? You can then remove the need for the soft dep in the base class, as its not really needed to build the network
hadifawaz1999: i would put it as in the visluazation model yes, thought about it at first, makes more sense once someone else proposed it as well :) thanks @TonyBagnall 


Patch:
diff --git a/aeon/classification/deep_learning/_fcn.py b/aeon/classification/deep_learning/_fcn.py
index 865469d9..e9696628 100644
--- a/aeon/classification/deep_learning/_fcn.py
+++ b/aeon/classification/deep_learning/_fcn.py
@@ -313,7 +313,9 @@ class FCNClassifier(BaseDeepClassifier):
             "n_epochs": 10,
             "batch_size": 4,
             "use_bias": False,
-            "n_layers": 2,
+            "n_layers": 1,
+            "n_filters": 4,
+            "kernel_size": 3,
             "padding": "valid",
             "strides": 2,
         }
diff --git a/aeon/classification/deep_learning/_resnet.py b/aeon/classification/deep_learning/_resnet.py
index 028d0d46..f40c2081 100644
--- a/aeon/classification/deep_learning/_resnet.py
+++ b/aeon/classification/deep_learning/_resnet.py
@@ -325,7 +325,9 @@ class ResNetClassifier(BaseDeepClassifier):
             "n_epochs": 10,
             "batch_size": 4,
             "n_residual_blocks": 1,
+            "n_filters": 5,
             "n_conv_per_residual_block": 1,
+            "kernel_size": 3,
         }
 
         test_params = [param]
diff --git a/aeon/clustering/deep_learning/_ae_fcn.py b/aeon/clustering/deep_learning/_ae_fcn.py
index 6e3a5711..65c05ec2 100644
--- a/aeon/clustering/deep_learning/_ae_fcn.py
+++ b/aeon/clustering/deep_learning/_ae_fcn.py
@@ -336,6 +336,8 @@ class AEFCNClusterer(BaseDeepClusterer):
             "batch_size": 4,
             "use_bias": False,
             "n_layers": 1,
+            "n_filters": 5,
+            "kernel_size": 3,
             "padding": "same",
             "strides": 1,
             "clustering_params": {
diff --git a/aeon/datasets/dataset_collections.py b/aeon/datasets/dataset_collections.py
index 99c4ff77..fc834780 100644
--- a/aeon/datasets/dataset_collections.py
+++ b/aeon/datasets/dataset_collections.py
@@ -44,7 +44,7 @@ MODULE = os.path.join(os.path.dirname(aeon.__file__), "datasets")
 
 
 def get_available_tser_datasets(name="tser_soton", return_list=True):
-    """List available tser data.
+    """List available tser data as specified by lists.
 
     Parameters
     ----------
@@ -69,16 +69,14 @@ def get_available_tser_datasets(name="tser_soton", return_list=True):
             return sorted(list(tser_monash))
         else:
             return tser_monash
-    return []
+    return name in tser_soton
 
 
 def get_available_tsf_datasets(name=None):
     """List available tsf data."""
     if name is None:  # List them all
         return sorted(list(tsf_all))
-    if name in tsf_all:
-        return True
-    return False
+    return name in tsf_all
 
 
 def get_available_tsc_datasets(name=None):
@@ -99,9 +97,7 @@ def get_available_tsc_datasets(name=None):
     if name is None:  # List them all
         merged_set = univariate.union(multivariate)
         return sorted(list(merged_set))
-    if name in univariate or name in multivariate:
-        return True
-    return False
+    return name in univariate or name in multivariate
 
 
 def get_downloaded_tsc_tsr_datasets(extract_path=None):
diff --git a/aeon/networks/_ae_fcn.py b/aeon/networks/_ae_fcn.py
index d5672b33..61149287 100644
--- a/aeon/networks/_ae_fcn.py
+++ b/aeon/networks/_ae_fcn.py
@@ -109,36 +109,43 @@ class AEFCNNetwork(BaseDeepNetwork):
         self._kernel_size_ = [8, 5, 3] if self.kernel_size is None else self.kernel_size
 
         if isinstance(self._n_filters_, list):
+            assert len(self._n_filters_) == self.n_layers
             self._n_filters = self._n_filters_
         else:
             self._n_filters = [self._n_filters_] * self.n_layers
 
         if isinstance(self._kernel_size_, list):
+            assert len(self._kernel_size_) == self.n_layers
             self._kernel_size = self._kernel_size_
         else:
             self._kernel_size = [self._kernel_size_] * self.n_layers
 
         if isinstance(self.dilation_rate, list):
+            assert len(self.dilation_rate) == self.n_layers
             self._dilation_rate = self.dilation_rate
         else:
             self._dilation_rate = [self.dilation_rate] * self.n_layers
 
         if isinstance(self.strides, list):
+            assert len(self.strides) == self.n_layers
             self._strides = self.strides
         else:
             self._strides = [self.strides] * self.n_layers
 
         if isinstance(self.padding, list):
+            assert len(self.padding) == self.n_layers
             self._padding = self.padding
         else:
             self._padding = [self.padding] * self.n_layers
 
         if isinstance(self.activation, list):
+            assert len(self.activation) == self.n_layers
             self._activation = self.activation
         else:
             self._activation = [self.activation] * self.n_layers
 
         if isinstance(self.use_bias, list):
+            assert len(self.use_bias) == self.n_layers
             self._use_bias = self.use_bias
         else:
             self._use_bias = [self.use_bias] * self.n_layers
diff --git a/aeon/networks/_cnn.py b/aeon/networks/_cnn.py
index e8c1589c..ec157460 100644
--- a/aeon/networks/_cnn.py
+++ b/aeon/networks/_cnn.py
@@ -102,79 +102,54 @@ class CNNNetwork(BaseDeepNetwork):
         self._n_filters_ = [6, 12] if self.n_filters is None else self.n_filters
 
         if isinstance(self.kernel_size, list):
+            assert len(self.kernel_size) == self.n_layers
             self._kernel_size = self.kernel_size
         else:
             self._kernel_size = [self.kernel_size] * self.n_layers
 
         if isinstance(self._n_filters_, list):
+            assert len(self._n_filters_) == self.n_layers
             self._n_filters = self._n_filters_
         else:
             self._n_filters = [self._n_filters_] * self.n_layers
 
         if isinstance(self.avg_pool_size, list):
+            assert len(self.avg_pool_size) == self.n_layers
             self._avg_pool_size = self.avg_pool_size
         else:
             self._avg_pool_size = [self.avg_pool_size] * self.n_layers
 
         if isinstance(self.activation, list):
+            assert len(self.activation) == self.n_layers
             self._activation = self.activation
         else:
             self._activation = [self.activation] * self.n_layers
 
         if isinstance(self.padding, list):
+            assert len(self.padding) == self.n_layers
             self._padding = self.padding
         else:
             self._padding = [self.padding] * self.n_layers
 
         if isinstance(self.strides, list):
+            assert len(self.strides) == self.n_layers
             self._strides = self.strides
         else:
             self._strides = [self.strides] * self.n_layers
 
         if isinstance(self.dilation_rate, list):
+            assert len(self.dilation_rate) == self.n_layers
             self._dilation_rate = self.dilation_rate
         else:
             self._dilation_rate = [self.dilation_rate] * self.n_layers
 
         if isinstance(self.use_bias, list):
+            assert len(self.use_bias) == self.n_layers
             self._use_bias = self.use_bias
         else:
             self._use_bias = [self.use_bias] * self.n_layers
 
         input_layer = tf.keras.layers.Input(input_shape)
-        # sort this out, why hard coded to 60? [hadifawaz1999 answer:
-        # It was proposed like that in the original paper, check code in
-        # https://github.com/hfawaz/dl-4-tsc/blob/3ee62e16e118e4f5cfa86d01661846dfa75febfa/classifiers/cnn.py#L30]
-
-        """
-
-        When the UCR archive was published in 2015, it
-        contained 85 datasets, each dataset had
-        different characteristics such as the time series length.
-
-        The smallest dataset (refering to time series length)
-        was the ItalyPowerDemand dataset
-        with a length of 24,
-        the second smallest one was SyntheticControl with a length of 60.
-
-        The authors in [1], added the padding condition on the
-        length 60 because it was the second smallest
-        dataset (refering to time series length), SyntheticControl.
-
-        Padding helps in the case of short time series because
-        the samples would contain a small amount
-        of information, which leads to a loss of information
-        when applying convolutions without padding.
-        This is due to the fact to negleting the side
-        parts of the time series.
-
-        Note that when the UCR archive had new datasets,
-        128 in 2018, more datasets of small length were added.
-        For this reason, we keep the conditioning on 60
-        as length of input time series to add the padding in
-        the convolution.
-
-        """
 
         if input_shape[0] < 60:
             self._padding = ["same"] * self.n_layers
diff --git a/aeon/networks/_fcn.py b/aeon/networks/_fcn.py
index 40766d3e..e54db1ce 100644
--- a/aeon/networks/_fcn.py
+++ b/aeon/networks/_fcn.py
@@ -91,36 +91,43 @@ class FCNNetwork(BaseDeepNetwork):
         self._kernel_size_ = [8, 5, 3] if self.kernel_size is None else self.kernel_size
 
         if isinstance(self._n_filters_, list):
+            assert len(self._n_filters_) == self.n_layers
             self._n_filters = self._n_filters_
         else:
             self._n_filters = [self._n_filters_] * self.n_layers
 
         if isinstance(self._kernel_size_, list):
+            assert len(self._kernel_size_) == self.n_layers
             self._kernel_size = self._kernel_size_
         else:
             self._kernel_size = [self._kernel_size_] * self.n_layers
 
         if isinstance(self.dilation_rate, list):
+            assert len(self.dilation_rate) == self.n_layers
             self._dilation_rate = self.dilation_rate
         else:
             self._dilation_rate = [self.dilation_rate] * self.n_layers
 
         if isinstance(self.strides, list):
+            assert len(self.strides) == self.n_layers
             self._strides = self.strides
         else:
             self._strides = [self.strides] * self.n_layers
 
         if isinstance(self.padding, list):
+            assert len(self.padding) == self.n_layers
             self._padding = self.padding
         else:
             self._padding = [self.padding] * self.n_layers
 
         if isinstance(self.activation, list):
+            assert len(self.activation) == self.n_layers
             self._activation = self.activation
         else:
             self._activation = [self.activation] * self.n_layers
 
         if isinstance(self.use_bias, list):
+            assert len(self.use_bias) == self.n_layers
             self._use_bias = self.use_bias
         else:
             self._use_bias = [self.use_bias] * self.n_layers
diff --git a/aeon/networks/_inception.py b/aeon/networks/_inception.py
index ff01c696..161a59e7 100644
--- a/aeon/networks/_inception.py
+++ b/aeon/networks/_inception.py
@@ -310,6 +310,7 @@ class InceptionNetwork(BaseDeepNetwork):
             input_inception = input_tensor
 
         kernel_size_s = [kernel_size // (2**i) for i in range(nb_conv_per_layer)]
+        self.kernel_size_s = kernel_size_s
 
         conv_list = []
 
@@ -385,51 +386,61 @@ class InceptionNetwork(BaseDeepNetwork):
         import tensorflow as tf
 
         if isinstance(self.nb_filters, list):
+            assert len(self.nb_filters) == self.depth
             self._nb_filters = self.nb_filters
         else:
             self._nb_filters = [self.nb_filters] * self.depth
 
         if isinstance(self.kernel_size, list):
+            assert len(self.kernel_size) == self.depth
             self._kernel_size = self.kernel_size
         else:
             self._kernel_size = [self.kernel_size] * self.depth
 
         if isinstance(self.nb_conv_per_layer, list):
+            assert len(self.nb_conv_per_layer) == self.depth
             self._nb_conv_per_layer = self.nb_conv_per_layer
         else:
             self._nb_conv_per_layer = [self.nb_conv_per_layer] * self.depth
 
         if isinstance(self.strides, list):
+            assert len(self.strides) == self.depth
             self._strides = self.strides
         else:
             self._strides = [self.strides] * self.depth
 
         if isinstance(self.dilation_rate, list):
+            assert len(self.dilation_rate) == self.depth
             self._dilation_rate = self.dilation_rate
         else:
             self._dilation_rate = [self.dilation_rate] * self.depth
 
         if isinstance(self.padding, list):
+            assert len(self.padding) == self.depth
             self._padding = self.padding
         else:
             self._padding = [self.padding] * self.depth
 
         if isinstance(self.activation, list):
+            assert len(self.activation) == self.depth
             self._activation = self.activation
         else:
             self._activation = [self.activation] * self.depth
 
         if isinstance(self.use_max_pooling, list):
+            assert len(self.use_max_pooling) == self.depth
             self._use_max_pooling = self.use_max_pooling
         else:
             self._use_max_pooling = [self.use_max_pooling] * self.depth
 
         if isinstance(self.max_pool_size, list):
+            assert len(self.max_pool_size) == self.depth
             self._max_pool_size = self.max_pool_size
         else:
             self._max_pool_size = [self.max_pool_size] * self.depth
 
         if isinstance(self.use_bias, list):
+            assert len(self.use_bias) == self.depth
             self._use_bias = self.use_bias
         else:
             self._use_bias = [self.use_bias] * self.depth
diff --git a/aeon/networks/_resnet.py b/aeon/networks/_resnet.py
index dbd04991..6f389ded 100644
--- a/aeon/networks/_resnet.py
+++ b/aeon/networks/_resnet.py
@@ -129,36 +129,43 @@ class ResNetNetwork(BaseDeepNetwork):
         self._kernel_size_ = [8, 5, 3] if self.kernel_size is None else self.kernel_size
 
         if isinstance(self._n_filters_, list):
+            assert len(self._n_filters_) == self.n_residual_blocks
             self._n_filters = self._n_filters_
         else:
             self._n_filters = [self._n_filters_] * self.n_residual_blocks
 
         if isinstance(self._kernel_size_, list):
+            assert len(self._kernel_size_) == self.n_conv_per_residual_block
             self._kernel_size = self._kernel_size_
         else:
             self._kernel_size = [self._kernel_size_] * self.n_conv_per_residual_block
 
         if isinstance(self.strides, list):
+            assert len(self.strides) == self.n_conv_per_residual_block
             self._strides = self.strides
         else:
             self._strides = [self.strides] * self.n_conv_per_residual_block
 
         if isinstance(self.dilation_rate, list):
+            assert len(self.dilation_rate) == self.n_conv_per_residual_block
             self._dilation_rate = self.dilation_rate
         else:
             self._dilation_rate = [self.dilation_rate] * self.n_conv_per_residual_block
 
         if isinstance(self.padding, list):
+            assert len(self.padding) == self.n_conv_per_residual_block
             self._padding = self.padding
         else:
             self._padding = [self.padding] * self.n_conv_per_residual_block
 
         if isinstance(self.activation, list):
+            assert len(self.activation) == self.n_conv_per_residual_block
             self._activation = self.activation
         else:
             self._activation = [self.activation] * self.n_conv_per_residual_block
 
         if isinstance(self.use_bias, list):
+            assert len(self.use_bias) == self.n_conv_per_residual_block
             self._use_bias = self.use_bias
         else:
             self._use_bias = [self.use_bias] * self.n_conv_per_residual_block
diff --git a/aeon/networks/base.py b/aeon/networks/base.py
index d3218750..aee196b1 100644
--- a/aeon/networks/base.py
+++ b/aeon/networks/base.py
@@ -1,6 +1,6 @@
 """Abstract base class for deep learning networks."""
 
-__author__ = ["Withington", "TonyBagnall"]
+__author__ = ["hadifawaz1999", "Withington", "TonyBagnall"]
 
 from abc import ABC, abstractmethod
 
diff --git a/aeon/regression/deep_learning/_fcn.py b/aeon/regression/deep_learning/_fcn.py
index 9b458426..8d2fc54e 100644
--- a/aeon/regression/deep_learning/_fcn.py
+++ b/aeon/regression/deep_learning/_fcn.py
@@ -315,7 +315,9 @@ class FCNRegressor(BaseDeepRegressor):
             "n_epochs": 10,
             "batch_size": 4,
             "use_bias": False,
-            "n_layers": 2,
+            "n_layers": 1,
+            "n_filters": 5,
+            "kernel_size": 3,
             "padding": "valid",
             "strides": 2,
         }
diff --git a/aeon/regression/deep_learning/_resnet.py b/aeon/regression/deep_learning/_resnet.py
index 5655b009..6a481933 100644
--- a/aeon/regression/deep_learning/_resnet.py
+++ b/aeon/regression/deep_learning/_resnet.py
@@ -336,7 +336,9 @@ class ResNetRegressor(BaseDeepRegressor):
             "n_epochs": 10,
             "batch_size": 4,
             "n_residual_blocks": 1,
+            "n_filters": 5,
             "n_conv_per_residual_block": 1,
+            "kernel_size": 3,
         }
 
         return [param]
diff --git a/aeon/transformations/base.py b/aeon/transformations/base.py
index 7d995740..67f65682 100644
--- a/aeon/transformations/base.py
+++ b/aeon/transformations/base.py
@@ -1042,7 +1042,7 @@ class BaseTransformer(BaseEstimator):
                 store=_converter_store_X,
                 store_behaviour="freeze",
             )
-        elif output_scitype == "Primitives":
+        elif output_scitype == "Tabular" or output_scitype == "Primitives":
             # we ensure the output is pd_DataFrame_Table
             # & ensure the returned index is sensible
             # for return index, we need to deal with last level, constant 0
diff --git a/aeon/transformations/collection/_collection_wrapper.py b/aeon/transformations/collection/_collection_wrapper.py
index a6a5e682..382a9f38 100644
--- a/aeon/transformations/collection/_collection_wrapper.py
+++ b/aeon/transformations/collection/_collection_wrapper.py
@@ -36,8 +36,11 @@ class CollectionToSeriesWrapper(BaseTransformer):
     >>> from aeon.datasets import load_airline
     >>> y = load_airline()
     >>> wrap = CollectionToSeriesWrapper(Catch22())
-    >>> wrap.fit_transform(y).shape
-    (1, 22)
+    >>> wrap.fit_transform(y)
+               0           1     2         3   ...        18        19        20    21
+    0  155.800003  181.700012  49.0  0.541667  ...  0.282051  0.769231  0.166667  11.0
+    <BLANKLINE>
+    [1 rows x 22 columns]
     """
 
     _tags = {
@@ -47,7 +50,7 @@ class CollectionToSeriesWrapper(BaseTransformer):
     def __init__(
         self,
         transformer,
-        _output_convert=None,
+        _output_convert="auto",
     ):
         self.transformer = transformer
         self.transformer_ = _clone_estimator(self.transformer)
@@ -90,4 +93,4 @@ class CollectionToSeriesWrapper(BaseTransformer):
         """
         from aeon.transformations.collection.convolution_based import Rocket
 
-        return {"transformer": Rocket(num_kernels=50, random_state=42)}
+        return {"transformer": Rocket(num_kernels=50)}
diff --git a/aeon/visualisation/estimator/deep_learning/__init__.py b/aeon/visualisation/estimator/deep_learning/__init__.py
new file mode 100644
index 00000000..6b5cb525
--- /dev/null
+++ b/aeon/visualisation/estimator/deep_learning/__init__.py
@@ -0,0 +1,5 @@
+"""Deep learning visualization module."""
+
+__all__ = ["plot_network"]
+
+from aeon.visualisation.estimator.deep_learning._network_plot import plot_network
diff --git a/aeon/visualisation/estimator/deep_learning/_network_plot.py b/aeon/visualisation/estimator/deep_learning/_network_plot.py
new file mode 100644
index 00000000..cae3faf1
--- /dev/null
+++ b/aeon/visualisation/estimator/deep_learning/_network_plot.py
@@ -0,0 +1,91 @@
+__all__ = ["plot_network"]
+__author__ = ["hadifawaz1999"]
+
+from aeon.utils.validation._dependencies import _check_soft_dependencies
+
+
+def plot_network(
+    input_shape,
+    network,
+    file_name=None,
+    show_shapes=True,
+    show_layer_names=False,
+    show_layer_activations=True,
+    dpi=96,
+):
+    """Plot the network with its input-output shapes and activations.
+
+    The network is plotted regardless of the output task.
+    Its important to note that to be able to use this function,
+    the pydot package should be installed in the environment as well
+    as graphviz on the system.
+    The function also works when the network is an auto-encoder based
+    model, and the function will split the output files into 2 files,
+    the first for the encoder and the second for the decoder.
+
+    Parameters
+    ----------
+    input_shape : tuple
+                  The shape of the data fed into the input layer.
+    network : an element from the network module
+              Example: FCNNetwork().
+    file_name : str default = None ("model.pdf")
+                File name of the plot image, without the extension.
+    show_shapes : bool, default = True
+                  Whether to display shape information.
+    show_layer_names : bool, default = False
+                       Whether to display layer names.
+    show_layer_activations : bool, default = True
+                             Display layer activations
+                             (only for layers that have an activation property).
+    dpi : int, default = 96
+          Dots per inch.
+
+    Returns
+    -------
+    None
+    """
+    _check_soft_dependencies(["tensorflow", "pydot"])
+    import tensorflow as tf
+
+    input_layer, output_layer = network.build_network(input_shape=input_shape)
+
+    if not isinstance(input_layer, tf.keras.models.Model):
+        network = tf.keras.models.Model(inputs=input_layer, outputs=output_layer)
+
+        file_name_ = "model" if file_name is None else file_name
+
+        try:
+            tf.keras.utils.plot_model(
+                model=network,
+                to_file=file_name_ + ".pdf",
+                show_shapes=show_shapes,
+                show_layer_names=show_layer_names,
+                show_layer_activations=show_layer_activations,
+                dpi=dpi,
+            )
+        except ImportError:
+            raise ImportError("Either graphviz or pydot should be installed.")
+
+    else:
+        file_name_ = "model" if file_name is None else file_name
+
+        try:
+            tf.keras.utils.plot_model(
+                model=input_layer,
+                to_file=file_name_ + "_encoder.pdf",
+                show_shapes=show_shapes,
+                show_layer_names=show_layer_names,
+                show_layer_activations=show_layer_activations,
+                dpi=dpi,
+            )
+            tf.keras.utils.plot_model(
+                model=output_layer,
+                to_file=file_name_ + "_decoder.pdf",
+                show_shapes=show_shapes,
+                show_layer_names=show_layer_names,
+                show_layer_activations=show_layer_activations,
+                dpi=dpi,
+            )
+        except ImportError:
+            raise ImportError("Either graphviz or pydot should be installed.")
diff --git a/pyproject.toml b/pyproject.toml
index 6a356ffb..8bf29e06 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -78,6 +78,7 @@ all_extras = [
     "xarray",
     "mlflow<2.4.0", # see https://github.com/mlflow/mlflow/issues/8629
     "imbalanced-learn",
+    "pydot==2.0.0",
 ]
 dl = [
     "keras-self-attention",


Test patch:
diff --git a/aeon/datasets/tests/test_dataset_collections.py b/aeon/datasets/tests/test_dataset_collections.py
index f5409224..611f2e78 100644
--- a/aeon/datasets/tests/test_dataset_collections.py
+++ b/aeon/datasets/tests/test_dataset_collections.py
@@ -13,7 +13,7 @@ from aeon.testing.test_config import PR_TESTING
 
 @pytest.mark.skipif(
     PR_TESTING,
-    reason="Only run on overnights because of intermittent fail for read/write",
+    reason="Only run on overnights because of intermittent fail for read/write.",
 )
 def test_downloaded_tsf_datasets():
     """Test recovering downloaded data sets.
@@ -29,7 +29,7 @@ def test_downloaded_tsf_datasets():
 
 @pytest.mark.skipif(
     PR_TESTING,
-    reason="Only run on overnights because of intermittent fail for read/write",
+    reason="Only run on overnights because of intermittent fail for read/write.",
 )
 def test_list_available_tsc_datasets():
     """Test recovering lists of available data sets."""
@@ -43,12 +43,12 @@ def test_list_available_tsc_datasets():
 
 @pytest.mark.skipif(
     PR_TESTING,
-    reason="Only run on overnights because of intermittent fail for read/write",
+    reason="Only run on overnights because of intermittent fail for read/write.",
 )
 def test_list_available_tser_datasets():
     """Test recovering lists of available data sets."""
     res = get_available_tser_datasets()
-    assert len(res) == 18  # PGDalia excluded because of unequal length within case
+    assert len(res) == 63
     res = get_available_tser_datasets("FOO")
     assert not res
     res = get_available_tser_datasets("Covid3Month")
@@ -57,7 +57,7 @@ def test_list_available_tser_datasets():
 
 @pytest.mark.skipif(
     PR_TESTING,
-    reason="Only run on overnights because of intermittent fail for read/write",
+    reason="Only run on overnights because of intermittent fail for read/write.",
 )
 def test_list_available_tsf_datasets():
     """Test recovering lists of available data sets."""
diff --git a/aeon/networks/tests/test_all_networks.py b/aeon/networks/tests/test_all_networks.py
new file mode 100644
index 00000000..a250fbc4
--- /dev/null
+++ b/aeon/networks/tests/test_all_networks.py
@@ -0,0 +1,32 @@
+import inspect
+
+import pytest
+
+from aeon import networks
+from aeon.utils.validation._dependencies import _check_soft_dependencies
+
+__author__ = ["hadifawaz1999"]
+
+
+@pytest.mark.skipif(
+    not _check_soft_dependencies(["tensorflow", "tensorflow_addons"], severity="none"),
+    reason="skip test if required soft dependency not available",
+)
+def test_all_networks_functionality():
+    network_classes = [
+        member[1] for member in inspect.getmembers(networks, inspect.isclass)
+    ]
+    input_shape = (100, 2)
+
+    for i in range(len(network_classes)):
+        if "BaseDeepNetwork" in str(network_classes[i]) or "AEFCNNetwork" in str(
+            network_classes[i]
+        ):
+            continue
+
+        my_network = network_classes[i]()
+
+        input_layer, output_layer = my_network.build_network(input_shape=input_shape)
+
+        assert input_layer is not None
+        assert output_layer is not None
diff --git a/aeon/networks/tests/test_network_base.py b/aeon/networks/tests/test_network_base.py
index 263dd820..a046cf07 100644
--- a/aeon/networks/tests/test_network_base.py
+++ b/aeon/networks/tests/test_network_base.py
@@ -22,7 +22,7 @@ class DummyDeepNetwork(BaseDeepNetwork):
 
 
 @pytest.mark.skipif(
-    not _check_soft_dependencies("tensorflow", severity="none"),
+    not _check_soft_dependencies(["tensorflow", "pydot"], severity="none"),
     reason="skip test if required soft dependency not available",
 )
 def test_dummy_deep_network():
diff --git a/aeon/visualisation/estimator/deep_learning/tests/__init__.py b/aeon/visualisation/estimator/deep_learning/tests/__init__.py
new file mode 100644
index 00000000..1fa771e5
--- /dev/null
+++ b/aeon/visualisation/estimator/deep_learning/tests/__init__.py
@@ -0,0 +1,1 @@
+"""Plotting for deep learners tests."""