diff --git a/.github/utilities/run_examples.sh b/.github/utilities/run_examples.sh index 3eeac963a3..fd7376c05b 100755 --- a/.github/utilities/run_examples.sh +++ b/.github/utilities/run_examples.sh @@ -9,6 +9,7 @@ excluded=() if [ "$1" = true ]; then excluded+=( "examples/datasets/load_data_from_web.ipynb" + "examples/benchmarking/published_results.ipynb" "examples/benchmarking/reference_results.ipynb" "examples/benchmarking/bakeoff_results.ipynb" "examples/benchmarking/regression.ipynb" @@ -21,6 +22,7 @@ if [ "$1" = true ]; then "examples/classification/interval_based.ipynb" "examples/classification/shapelet_based.ipynb" "examples/classification/convolution_based.ipynb" + "examples/similarity_search/code_speed.ipynb" ) fi diff --git a/.github/workflows/periodic_tests.yml b/.github/workflows/periodic_tests.yml index 8c5eeca564..ac81989b27 100644 --- a/.github/workflows/periodic_tests.yml +++ b/.github/workflows/periodic_tests.yml @@ -83,6 +83,31 @@ jobs: # Save cache with the current date (ENV set in numba_cache action) key: numba-run-notebook-examples-${{ runner.os }}-3.10-${{ env.CURRENT_DATE }} + test-core-imports: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install aeon and dependencies + uses: nick-fields/retry@v3 + with: + timeout_minutes: 30 + max_attempts: 3 + command: python -m pip install . + + - name: Show dependencies + run: python -m pip list + + - name: Run import test + run: python aeon/testing/tests/test_core_imports.py + test-no-soft-deps: runs-on: ubuntu-22.04 diff --git a/.github/workflows/pr_core_dep_import.yml b/.github/workflows/pr_core_dep_import.yml new file mode 100644 index 0000000000..19fb56e294 --- /dev/null +++ b/.github/workflows/pr_core_dep_import.yml @@ -0,0 +1,43 @@ +name: PR module imports + +on: + push: + branches: + - main + pull_request: + branches: + - main + paths: + - "aeon/**" + - ".github/workflows/**" + - "pyproject.toml" + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + test-core-imports: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install aeon and dependencies + uses: nick-fields/retry@v3 + with: + timeout_minutes: 30 + max_attempts: 3 + command: python -m pip install . + + - name: Show dependencies + run: python -m pip list + + - name: Run import test + run: python aeon/testing/tests/test_core_imports.py diff --git a/.github/workflows/pr_pytest.yml b/.github/workflows/pr_pytest.yml index 0a5ed4c673..abbb0f596f 100644 --- a/.github/workflows/pr_pytest.yml +++ b/.github/workflows/pr_pytest.yml @@ -48,7 +48,7 @@ jobs: run: python -m pip list - name: Run tests - run: python -m pytest -n logical -k 'not TestAll' + run: python -m pytest -n logical pytest: runs-on: ${{ matrix.os }} diff --git a/.readthedocs.yml b/.readthedocs.yml index c031c0d25b..1b016c167c 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -11,7 +11,7 @@ python: - docs build: - os: ubuntu-20.04 + os: ubuntu-22.04 tools: python: "3.10" diff --git a/README.md b/README.md index f51a79291a..0763b2f5de 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ The following modules are still considered experimental, and the [deprecation po does not apply: - `anomaly_detection` +- `forecasting` - `segmentation` - `similarity_search` - `visualisation` diff --git a/aeon/base/tests/test_base_collection.py b/aeon/base/tests/test_base_collection.py index 97e2232c66..fff1f75f38 100644 --- a/aeon/base/tests/test_base_collection.py +++ b/aeon/base/tests/test_base_collection.py @@ -14,7 +14,7 @@ UNEQUAL_LENGTH_MULTIVARIATE_CLASSIFICATION, UNEQUAL_LENGTH_UNIVARIATE_CLASSIFICATION, ) -from aeon.utils import COLLECTIONS_DATA_TYPES +from aeon.utils.data_types import COLLECTIONS_DATA_TYPES from aeon.utils.validation import get_type diff --git a/aeon/classification/distance_based/_proximity_tree.py b/aeon/classification/distance_based/_proximity_tree.py index e3db90864d..9af2edfe84 100644 --- a/aeon/classification/distance_based/_proximity_tree.py +++ b/aeon/classification/distance_based/_proximity_tree.py @@ -234,7 +234,7 @@ def _get_best_splitter(self, X, y): dist = distance( X[j], splitter[0][labels[k]], - metric=measure, + measure=measure, **splitter[1][measure], ) if dist < min_dist: @@ -320,7 +320,7 @@ def _build_tree(self, X, y, depth, node_id, parent_target_value=None): dist = distance( X[i], splitter[0][labels[j]], - metric=measure, + measure=measure, **splitter[1][measure], ) if dist < min_dist: @@ -404,7 +404,7 @@ def _classify(self, treenode, x): dist = distance( x, treenode.splitter[0][branches[i]], - metric=measure, + measure=measure, **treenode.splitter[1][measure], ) if dist < min_dist: diff --git a/aeon/classification/distance_based/_time_series_neighbors.py b/aeon/classification/distance_based/_time_series_neighbors.py index a24459d182..efb7473e65 100644 --- a/aeon/classification/distance_based/_time_series_neighbors.py +++ b/aeon/classification/distance_based/_time_series_neighbors.py @@ -111,7 +111,7 @@ def _fit(self, X, y): y : array-like, shape = (n_cases) The class labels. """ - self.metric_ = get_distance_function(metric=self.distance) + self.metric_ = get_distance_function(measure=self.distance) self.X_ = X self.classes_, self.y_ = np.unique(y, return_inverse=True) return self diff --git a/aeon/classification/tests/test_base.py b/aeon/classification/tests/test_base.py index e59baa1bf4..49782cab85 100644 --- a/aeon/classification/tests/test_base.py +++ b/aeon/classification/tests/test_base.py @@ -15,7 +15,7 @@ EQUAL_LENGTH_UNIVARIATE_CLASSIFICATION, UNEQUAL_LENGTH_UNIVARIATE_CLASSIFICATION, ) -from aeon.utils import COLLECTIONS_DATA_TYPES +from aeon.utils.data_types import COLLECTIONS_DATA_TYPES __maintainer__ = [] diff --git a/aeon/clustering/_clara.py b/aeon/clustering/_clara.py index 66da2e9920..edc334f6c9 100644 --- a/aeon/clustering/_clara.py +++ b/aeon/clustering/_clara.py @@ -42,7 +42,7 @@ class TimeSeriesCLARA(BaseClusterer): If a np.ndarray provided it must be of shape (n_clusters,) and contain the indexes of the time series to use as centroids. distance : str or Callable, default='msm' - Distance metric to compute similarity between time series. A list of valid + Distance measure to compute similarity between time series. A list of valid strings for metrics can be found in the documentation for :func:`aeon.distances.get_distance_function`. If a callable is passed it must be a function that takes two 2d numpy arrays as input and returns a float. @@ -73,7 +73,7 @@ class TimeSeriesCLARA(BaseClusterer): If `None`, the random number generator is the `RandomState` instance used by `np.random`. distance_params : dict, default=None - Dictionary containing kwargs for the distance metric being used. + Dictionary containing kwargs for the distance measure being used. Attributes ---------- @@ -189,7 +189,7 @@ def _fit(self, X: np.ndarray, y=None): curr_centers = pam.cluster_centers_ if isinstance(pam.distance, str): pairwise_matrix = pairwise_distance( - X, curr_centers, metric=self.distance, **pam._distance_params + X, curr_centers, measure=self.distance, **pam._distance_params ) else: pairwise_matrix = pairwise_distance( diff --git a/aeon/clustering/_clarans.py b/aeon/clustering/_clarans.py index 71ca1a9284..c13d177dfa 100644 --- a/aeon/clustering/_clarans.py +++ b/aeon/clustering/_clarans.py @@ -43,8 +43,8 @@ class TimeSeriesCLARANS(TimeSeriesKMedoids): If a np.ndarray provided it must be of shape (n_clusters,) and contain the indexes of the time series to use as centroids. distance : str or Callable, default='msm' - Distance metric to compute similarity between time series. A list of valid - strings for metrics can be found in the documentation for + Distance measure to compute similarity between time series. A list of valid + strings for measures can be found in the documentation for :func:`aeon.distances.get_distance_function`. If a callable is passed it must be a function that takes two 2d numpy arrays as input and returns a float. max_neighbours : int, default=None, @@ -62,7 +62,7 @@ class TimeSeriesCLARANS(TimeSeriesKMedoids): random_state : int or np.random.RandomState instance or None, default=None Determines random number generation for centroid initialization. distance_params : dict, default=None - Dictionary containing kwargs for the distance metric being used. + Dictionary containing kwargs for the distance measure being used. Attributes ---------- diff --git a/aeon/clustering/_elastic_som.py b/aeon/clustering/_elastic_som.py index 36a8769b13..e4edec42ab 100644 --- a/aeon/clustering/_elastic_som.py +++ b/aeon/clustering/_elastic_som.py @@ -44,8 +44,8 @@ class ElasticSOM(BaseClusterer): n_clusters : int, default=8 The number of clusters to form as well as the number of centroids to generate. distance : str or Callable, default='dtw' - Distance metric to compute similarity between time series. A list of valid - strings for metrics can be found in the documentation for + Distance measure to compute similarity between time series. A list of valid + strings for measures can be found in the documentation for :func:`aeon.distances.get_distance_function`. If a callable is passed it must be a function that takes two 2d numpy arrays as input and returns a float. init : str or np.ndarray, default='random' @@ -224,7 +224,7 @@ def _find_bmu(self, x, weights): pairwise_matrix = pairwise_distance( x, weights, - metric=self.distance, + measure=self.distance, **self._distance_params, ) return pairwise_matrix.argmin(axis=1) @@ -366,7 +366,7 @@ def _kmeans_plus_plus_center_initializer(self, X: np.ndarray): for _ in range(1, self.n_clusters): pw_dist = pairwise_distance( - X, X[indexes], metric=self.distance, **self._distance_params + X, X[indexes], measure=self.distance, **self._distance_params ) min_distances = pw_dist.min(axis=1) probabilities = min_distances / min_distances.sum() diff --git a/aeon/clustering/_k_means.py b/aeon/clustering/_k_means.py index e68d09e1b8..7c1e9a07d2 100644 --- a/aeon/clustering/_k_means.py +++ b/aeon/clustering/_k_means.py @@ -54,8 +54,8 @@ class TimeSeriesKMeans(BaseClusterer): n_timepoints) and contains the time series to use as centroids. distance : str or Callable, default='msm' - Distance metric to compute similarity between time series. A list of valid - strings for metrics can be found in the documentation for + Distance measure to compute similarity between time series. A list of valid + strings for measures can be found in the documentation for :func:`aeon.distances.get_distance_function`. If a callable is passed it must be a function that takes two 2d numpy arrays as input and returns a float. n_init : int, default=10 @@ -236,7 +236,7 @@ def _fit_one_init(self, X: np.ndarray) -> tuple: prev_labels = None for i in range(self.max_iter): curr_pw = pairwise_distance( - X, cluster_centres, metric=self.distance, **self._distance_params + X, cluster_centres, measure=self.distance, **self._distance_params ) curr_labels = curr_pw.argmin(axis=1) curr_inertia = curr_pw.min(axis=1).sum() @@ -273,13 +273,13 @@ def _fit_one_init(self, X: np.ndarray) -> tuple: def _predict(self, X: np.ndarray, y=None) -> np.ndarray: if isinstance(self.distance, str): pairwise_matrix = pairwise_distance( - X, self.cluster_centers_, metric=self.distance, **self._distance_params + X, self.cluster_centers_, measure=self.distance, **self._distance_params ) else: pairwise_matrix = pairwise_distance( X, self.cluster_centers_, - metric=self.distance, + measure=self.distance, **self._distance_params, ) return pairwise_matrix.argmin(axis=1) @@ -346,7 +346,7 @@ def _kmeans_plus_plus_center_initializer(self, X: np.ndarray): for _ in range(1, self.n_clusters): pw_dist = pairwise_distance( - X, X[indexes], metric=self.distance, **self._distance_params + X, X[indexes], measure=self.distance, **self._distance_params ) min_distances = pw_dist.min(axis=1) probabilities = min_distances / min_distances.sum() @@ -381,7 +381,7 @@ def _handle_empty_cluster( index_furthest_from_centre = curr_pw.min(axis=1).argmax() cluster_centres[current_empty_cluster_index] = X[index_furthest_from_centre] curr_pw = pairwise_distance( - X, cluster_centres, metric=self.distance, **self._distance_params + X, cluster_centres, measure=self.distance, **self._distance_params ) curr_labels = curr_pw.argmin(axis=1) curr_inertia = curr_pw.min(axis=1).sum() diff --git a/aeon/clustering/_k_medoids.py b/aeon/clustering/_k_medoids.py index a54220cec2..6a00f2a46e 100644 --- a/aeon/clustering/_k_medoids.py +++ b/aeon/clustering/_k_medoids.py @@ -56,8 +56,8 @@ class TimeSeriesKMedoids(BaseClusterer): If a np.ndarray provided it must be of shape (n_clusters,) and contain the indexes of the time series to use as centroids. distance : str or Callable, default='msm' - Distance metric to compute similarity between time series. A list of valid - strings for metrics can be found in the documentation for + Distance measure to compute similarity between time series. A list of valid + strings for measures can be found in the documentation for :func:`aeon.distances.get_distance_function`. If a callable is passed it must be a function that takes two 2d numpy arrays as input and returns a float. method : str, default='pam' @@ -88,7 +88,7 @@ class TimeSeriesKMedoids(BaseClusterer): If `None`, the random number generator is the `RandomState` instance used by `np.random`. distance_params: dict, default=None - Dictionary containing kwargs for the distance metric being used. + Dictionary containing kwargs for the distance measure being used. Attributes ---------- @@ -211,7 +211,7 @@ def _fit(self, X: np.ndarray, y=None): def _predict(self, X: np.ndarray, y=None) -> np.ndarray: if isinstance(self.distance, str): pairwise_matrix = pairwise_distance( - X, self.cluster_centers_, metric=self.distance, **self._distance_params + X, self.cluster_centers_, measure=self.distance, **self._distance_params ) else: pairwise_matrix = pairwise_distance( @@ -456,7 +456,7 @@ def _check_params(self, X: np.ndarray) -> None: f"n_clusters ({self.n_clusters}) cannot be larger than " f"n_cases ({X.shape[0]})" ) - self._distance_callable = get_distance_function(metric=self.distance) + self._distance_callable = get_distance_function(measure=self.distance) self._distance_cache = np.full((X.shape[0], X.shape[0]), np.inf) if self.method == "alternate": @@ -486,7 +486,7 @@ def _kmedoids_plus_plus_center_initializer(self, X: np.ndarray): for _ in range(1, self.n_clusters): pw_dist = pairwise_distance( - X, X[indexes], metric=self.distance, **self._distance_params + X, X[indexes], measure=self.distance, **self._distance_params ) min_distances = pw_dist.min(axis=1) probabilities = min_distances / min_distances.sum() diff --git a/aeon/clustering/averaging/_ba_petitjean.py b/aeon/clustering/averaging/_ba_petitjean.py index 3d456bfa69..6b3765f77e 100644 --- a/aeon/clustering/averaging/_ba_petitjean.py +++ b/aeon/clustering/averaging/_ba_petitjean.py @@ -55,7 +55,7 @@ def petitjean_barycenter_average( random_state: int or None, default=None Random state to use for the barycenter averaging. **kwargs - Keyword arguments to pass to the distance metric. + Keyword arguments to pass to the distance measure. Returns ------- diff --git a/aeon/clustering/averaging/_ba_subgradient.py b/aeon/clustering/averaging/_ba_subgradient.py index 12410ba6d9..369ad6bded 100644 --- a/aeon/clustering/averaging/_ba_subgradient.py +++ b/aeon/clustering/averaging/_ba_subgradient.py @@ -70,7 +70,7 @@ def subgradient_barycenter_average( random_state: int or None, default=None Random state to use for the barycenter averaging. **kwargs - Keyword arguments to pass to the distance metric. + Keyword arguments to pass to the distance measure. Returns ------- diff --git a/aeon/clustering/averaging/_ba_utils.py b/aeon/clustering/averaging/_ba_utils.py index 4b449fb0a8..496f1f6e74 100644 --- a/aeon/clustering/averaging/_ba_utils.py +++ b/aeon/clustering/averaging/_ba_utils.py @@ -31,7 +31,7 @@ def _medoids( return X if precomputed_pairwise_distance is None: - precomputed_pairwise_distance = pairwise_distance(X, metric=distance, **kwargs) + precomputed_pairwise_distance = pairwise_distance(X, measure=distance, **kwargs) x_size = X.shape[0] distance_matrix = np.zeros((x_size, x_size)) @@ -155,6 +155,6 @@ def _get_alignment_path( elif distance == "adtw": return adtw_alignment_path(ts, center, window=window, warp_penalty=warp_penalty) else: - # When numba version > 0.57 add more informative error with what metric + # When numba version > 0.57 add more informative error with what measure # was passed. raise ValueError("Distance parameter invalid") diff --git a/aeon/clustering/averaging/_barycenter_averaging.py b/aeon/clustering/averaging/_barycenter_averaging.py index 78f2583c59..48397f8f84 100644 --- a/aeon/clustering/averaging/_barycenter_averaging.py +++ b/aeon/clustering/averaging/_barycenter_averaging.py @@ -84,7 +84,7 @@ def elastic_barycenter_average( random_state: int or None, default=None Random state to use for the barycenter averaging. **kwargs - Keyword arguments to pass to the distance metric. + Keyword arguments to pass to the distance measure. Returns ------- diff --git a/aeon/distances/_distance.py b/aeon/distances/_distance.py index 6b3c9d91a3..0004884184 100644 --- a/aeon/distances/_distance.py +++ b/aeon/distances/_distance.py @@ -118,7 +118,7 @@ class DistanceKwargs(TypedDict, total=False): def distance( x: np.ndarray, y: np.ndarray, - metric: Union[str, DistanceFunction], + measure: Union[str, DistanceFunction], **kwargs: Unpack[DistanceKwargs], ) -> float: """Compute the distance between two time series. @@ -131,13 +131,13 @@ def distance( y : np.ndarray Second time series, either univariate, shape ``(n_timepoints,)``, or multivariate, shape ``(n_channels, n_timepoints)``. - metric : str or Callable - The distance metric to use. - A list of valid distance metrics can be found in the documentation for + measure : str or Callable + The distance measure to use. + A list of valid distance measures can be found in the documentation for :func:`aeon.distances.get_distance_function` or by calling the function :func:`aeon.distances.get_distance_function_names`. kwargs : Any - Arguments for metric. Refer to each metrics documentation for a list of + Arguments for measure. Refer to each measure documentation for a list of possible arguments. Returns @@ -149,7 +149,7 @@ def distance( ------ ValueError If x and y are not 1D, or 2D arrays. - If metric is not a valid string or callable. + If measure is not a valid string or callable. Examples -------- @@ -157,21 +157,21 @@ def distance( >>> from aeon.distances import distance >>> x = np.array([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]) >>> y = np.array([[11, 12, 13, 14, 15, 16, 17, 18, 19, 20]]) - >>> distance(x, y, metric="dtw") + >>> distance(x, y, measure="dtw") 768.0 """ - if metric in DISTANCES_DICT: - return DISTANCES_DICT[metric]["distance"](x, y, **kwargs) - elif isinstance(metric, Callable): - return metric(x, y, **kwargs) + if measure in DISTANCES_DICT: + return DISTANCES_DICT[measure]["distance"](x, y, **kwargs) + elif isinstance(measure, Callable): + return measure(x, y, **kwargs) else: - raise ValueError("Metric must be one of the supported strings or a callable") + raise ValueError("Measure must be one of the supported strings or a callable") def pairwise_distance( x: np.ndarray, y: Optional[np.ndarray] = None, - metric: Union[str, DistanceFunction, None] = None, + measure: Union[str, DistanceFunction, None] = None, symmetric: bool = True, **kwargs: Unpack[DistanceKwargs], ) -> np.ndarray: @@ -185,20 +185,20 @@ def pairwise_distance( y : np.ndarray or None, default=None A single series or a collection of time series of shape ``(m_timepoints,)`` or ``(m_cases, m_timepoints)`` or ``(m_cases, m_channels, m_timepoints)`` - metric : str or Callable - The distance metric to use. - A list of valid distance metrics can be found in the documentation for + measure : str or Callable + The distance measure to use. + A list of valid distance measure can be found in the documentation for :func:`aeon.distances.get_distance_function` or by calling the function :func:`aeon.distances.get_distance_function_names`. symmetric : bool, default=True - If True and a function is provided as the "metric" paramter, then it will + If True and a function is provided as the "measure" paramter, then it will compute a symmetric distance matrix where d(x, y) = d(y, x). Only the lower triangle is calculated, and the upper triangle is ignored. If False and a - function is provided as the "metric" parameter, then it will compute an + function is provided as the "measure" parameter, then it will compute an asymmetric distance matrix, and the entire matrix (including both upper and lower triangles) is returned. kwargs : Any - Extra arguments for metric. Refer to each metric documentation for a list of + Extra arguments for measure. Refer to each measure documentation for a list of possible arguments. Returns @@ -211,7 +211,7 @@ def pairwise_distance( ValueError If X is not 2D or 3D array when only passing X. If X and y are not 1D, 2D or 3D arrays when passing both X and y. - If metric is not a valid string or callable. + If measure is not a valid string or callable. Examples -------- @@ -219,7 +219,7 @@ def pairwise_distance( >>> from aeon.distances import pairwise_distance >>> # Distance between each time series in a collection of time series >>> X = np.array([[[1, 2, 3]],[[4, 5, 6]], [[7, 8, 9]]]) - >>> pairwise_distance(X, metric='dtw') + >>> pairwise_distance(X, measure='dtw') array([[ 0., 26., 108.], [ 26., 0., 26.], [108., 26., 0.]]) @@ -227,26 +227,26 @@ def pairwise_distance( >>> # Distance between two collections of time series >>> X = np.array([[[1, 2, 3]],[[4, 5, 6]], [[7, 8, 9]]]) >>> y = np.array([[[11, 12, 13]],[[14, 15, 16]], [[17, 18, 19]]]) - >>> pairwise_distance(X, y, metric='dtw') + >>> pairwise_distance(X, y, measure='dtw') array([[300., 507., 768.], [147., 300., 507.], [ 48., 147., 300.]]) >>> X = np.array([[[1, 2, 3]],[[4, 5, 6]], [[7, 8, 9]]]) >>> y_univariate = np.array([11, 12, 13]) - >>> pairwise_distance(X, y_univariate, metric='dtw') + >>> pairwise_distance(X, y_univariate, measure='dtw') array([[300.], [147.], [ 48.]]) """ - if metric in PAIRWISE_DISTANCE: - return DISTANCES_DICT[metric]["pairwise_distance"](x, y, **kwargs) - elif isinstance(metric, Callable): + if measure in PAIRWISE_DISTANCE: + return DISTANCES_DICT[measure]["pairwise_distance"](x, y, **kwargs) + elif isinstance(measure, Callable): if y is None and not symmetric: - return _custom_func_pairwise(x, x, metric, **kwargs) - return _custom_func_pairwise(x, y, metric, **kwargs) + return _custom_func_pairwise(x, x, measure, **kwargs) + return _custom_func_pairwise(x, y, measure, **kwargs) else: - raise ValueError("Metric must be one of the supported strings or a callable") + raise ValueError("Measure must be one of the supported strings or a callable") def _custom_func_pairwise( @@ -302,7 +302,7 @@ def _custom_from_multiple_to_multiple_distance( def alignment_path( x: np.ndarray, y: np.ndarray, - metric: Union[str, DistanceFunction, None] = None, + measure: Union[str, DistanceFunction, None] = None, **kwargs: Unpack[DistanceKwargs], ) -> tuple[list[tuple[int, int]], float]: """Compute the alignment path and distance between two time series. @@ -313,13 +313,13 @@ def alignment_path( First time series. y : np.ndarray, of shape (m_channels, m_timepoints) or (m_timepoints,) Second time series. - metric : str or Callable - The distance metric to use. - A list of valid distance metrics can be found in the documentation for + measure : str or Callable + The distance measure to use. + A list of valid distance measure can be found in the documentation for :func:`aeon.distances.get_distance_function` or by calling the function :func:`aeon.distances.get_distance_function_names`. kwargs : any - Arguments for metric. Refer to each metrics documentation for a list of + Arguments for measure. Refer to each measure documentation for a list of possible arguments. Returns @@ -335,7 +335,7 @@ def alignment_path( ------ ValueError If x and y are not 1D, or 2D arrays. - If metric is not one of the supported strings or a callable. + If measure is not one of the supported strings or a callable. Examples -------- @@ -343,21 +343,21 @@ def alignment_path( >>> from aeon.distances import alignment_path >>> x = np.array([[1, 2, 3, 6]]) >>> y = np.array([[1, 2, 3, 4]]) - >>> alignment_path(x, y, metric='dtw') + >>> alignment_path(x, y, measure='dtw') ([(0, 0), (1, 1), (2, 2), (3, 3)], 4.0) """ - if metric in ALIGNMENT_PATH: - return DISTANCES_DICT[metric]["alignment_path"](x, y, **kwargs) - elif isinstance(metric, Callable): - return metric(x, y, **kwargs) + if measure in ALIGNMENT_PATH: + return DISTANCES_DICT[measure]["alignment_path"](x, y, **kwargs) + elif isinstance(measure, Callable): + return measure(x, y, **kwargs) else: - raise ValueError("Metric must be one of the supported strings") + raise ValueError("Measure must be one of the supported strings") def cost_matrix( x: np.ndarray, y: np.ndarray, - metric: Union[str, DistanceFunction, None] = None, + measure: Union[str, DistanceFunction, None] = None, **kwargs: Unpack[DistanceKwargs], ) -> np.ndarray: """Compute the alignment path and distance between two time series. @@ -368,13 +368,13 @@ def cost_matrix( First time series. y : np.ndarray, of shape (m_channels, m_timepoints) or (m_timepoints,) Second time series. - metric : str or Callable - The distance metric to use. - A list of valid distance metrics can be found in the documentation for + measure : str or Callable + The distance measure to use. + A list of valid distance measures can be found in the documentation for :func:`aeon.distances.get_distance_function` or by calling the function :func:`aeon.distances.get_distance_function_names`. kwargs : Any - Arguments for metric. Refer to each metrics documentation for a list of + Arguments for measure. Refer to each measures documentation for a list of possible arguments. Returns @@ -386,7 +386,7 @@ def cost_matrix( ------ ValueError If x and y are not 1D, or 2D arrays. - If metric is not one of the supported strings or a callable. + If measure is not one of the supported strings or a callable. Examples -------- @@ -394,7 +394,7 @@ def cost_matrix( >>> from aeon.distances import cost_matrix >>> x = np.array([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]) >>> y = np.array([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]) - >>> cost_matrix(x, y, metric="dtw") + >>> cost_matrix(x, y, measure="dtw") array([[ 0., 1., 5., 14., 30., 55., 91., 140., 204., 285.], [ 1., 0., 1., 5., 14., 30., 55., 91., 140., 204.], [ 5., 1., 0., 1., 5., 14., 30., 55., 91., 140.], @@ -406,12 +406,12 @@ def cost_matrix( [204., 140., 91., 55., 30., 14., 5., 1., 0., 1.], [285., 204., 140., 91., 55., 30., 14., 5., 1., 0.]]) """ - if metric in COST_MATRIX: - return DISTANCES_DICT[metric]["cost_matrix"](x, y, **kwargs) - elif isinstance(metric, Callable): - return metric(x, y, **kwargs) + if measure in COST_MATRIX: + return DISTANCES_DICT[measure]["cost_matrix"](x, y, **kwargs) + elif isinstance(measure, Callable): + return measure(x, y, **kwargs) else: - raise ValueError("Metric must be one of the supported strings") + raise ValueError("Measure must be one of the supported strings") def get_distance_function_names() -> list[str]: @@ -440,11 +440,11 @@ def get_distance_function_names() -> list[str]: return sorted(DISTANCES_DICT.keys()) -def get_distance_function(metric: Union[str, DistanceFunction]) -> DistanceFunction: - """Get the distance function for a given metric string or callable. +def get_distance_function(measure: Union[str, DistanceFunction]) -> DistanceFunction: + """Get the distance function for a given measure string or callable. =============== ======================================== - metric Distance Function + measure Distance Function =============== ======================================== 'dtw' distances.dtw_distance 'shape_dtw' distances.shape_dtw_distance @@ -468,8 +468,8 @@ def get_distance_function(metric: Union[str, DistanceFunction]) -> DistanceFunct Parameters ---------- - metric : str or Callable - The distance metric to use. + measure : str or Callable + The distance measure to use. If string given then it will be resolved to a alignment path function. If a callable is given, the value must be a function that accepts two numpy arrays and **kwargs returns a float. @@ -477,12 +477,12 @@ def get_distance_function(metric: Union[str, DistanceFunction]) -> DistanceFunct Returns ------- Callable[[np.ndarray, np.ndarray, Any], float] - The distance function for the given metric. + The distance function for the given measure. Raises ------ ValueError - If metric is not one of the supported strings or a callable. + If measure is not one of the supported strings or a callable. Examples -------- @@ -494,16 +494,16 @@ def get_distance_function(metric: Union[str, DistanceFunction]) -> DistanceFunct >>> dtw_dist_func(x, y, window=0.2) 874.0 """ - return _resolve_key_from_distance(metric, "distance") + return _resolve_key_from_distance(measure, "distance") def get_pairwise_distance_function( - metric: Union[str, PairwiseFunction] + measure: Union[str, PairwiseFunction] ) -> PairwiseFunction: - """Get the pairwise distance function for a given metric string or callable. + """Get the pairwise distance function for a given measure string or callable. =============== ======================================== - metric Distance Function + measure Distance Function =============== ======================================== 'dtw' distances.dtw_pairwise_distance 'shape_dtw' distances.shape_dtw_pairwise_distance @@ -527,8 +527,8 @@ def get_pairwise_distance_function( Parameters ---------- - metric : str or Callable - The metric string to resolve to a alignment path function. + measure : str or Callable + The measure string to resolve to a alignment path function. If string given then it will be resolved to a alignment path function. If a callable is given, the value must be a function that accepts two numpy arrays and **kwargs returns a np.ndarray that is the pairwise distance @@ -537,12 +537,12 @@ def get_pairwise_distance_function( Returns ------- Callable[[np.ndarray, np.ndarray, Any], np.ndarray] - The pairwise distance function for the given metric. + The pairwise distance function for the given measure. Raises ------ ValueError - If metric is not one of the supported strings or a callable. + If measure is not one of the supported strings or a callable. Examples -------- @@ -556,14 +556,14 @@ def get_pairwise_distance_function( [147., 300., 507.], [ 48., 147., 300.]]) """ - return _resolve_key_from_distance(metric, "pairwise_distance") + return _resolve_key_from_distance(measure, "pairwise_distance") -def get_alignment_path_function(metric: str) -> AlignmentPathFunction: - """Get the alignment path function for a given metric string or callable. +def get_alignment_path_function(measure: str) -> AlignmentPathFunction: + """Get the alignment path function for a given measure string or callable. =============== ======================================== - metric Distance Function + measure Distance Function =============== ======================================== 'dtw' distances.dtw_alignment_path 'shape_dtw' distances.shape_dtw_alignment_path @@ -581,19 +581,19 @@ def get_alignment_path_function(metric: str) -> AlignmentPathFunction: Parameters ---------- - metric : str or Callable - The metric string to resolve to an alignment path function. + measure : str or Callable + The measure string to resolve to an alignment path function. Returns ------- Callable[[np.ndarray, np.ndarray, Any], Tuple[List[Tuple[int, int]], float]] - The alignment path function for the given metric. + The alignment path function for the given measure. Raises ------ ValueError - If metric is not one of the supported strings or a callable. - If the metric doesn't have an alignment path function. + If measure is not one of the supported strings or a callable. + If the measure doesn't have an alignment path function. Examples -------- @@ -605,14 +605,14 @@ def get_alignment_path_function(metric: str) -> AlignmentPathFunction: >>> dtw_alignment_path_func(x, y, window=0.2) ([(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)], 500.0) """ - return _resolve_key_from_distance(metric, "alignment_path") + return _resolve_key_from_distance(measure, "alignment_path") -def get_cost_matrix_function(metric: str) -> CostMatrixFunction: - """Get the cost matrix function for a given metric string or callable. +def get_cost_matrix_function(measure: str) -> CostMatrixFunction: + """Get the cost matrix function for a given measure string or callable. =============== ======================================== - metric Distance Function + measure Distance Function =============== ======================================== 'dtw' distances.dtw_cost_matrix 'shape_dtw' distances.shape_dtw_cost_matrix @@ -630,19 +630,19 @@ def get_cost_matrix_function(metric: str) -> CostMatrixFunction: Parameters ---------- - metric : str or Callable - The metric string to resolve to a cost matrix function. + measure : str or Callable + The measure string to resolve to a cost matrix function. Returns ------- Callable[[np.ndarray, np.ndarray, Any], np.ndarray] - The cost matrix function for the given metric. + The cost matrix function for the given measure. Raises ------ ValueError - If metric is not one of the supported strings or a callable. - If the metric doesn't have a cost matrix function. + If measure is not one of the supported strings or a callable. + If the measure doesn't have a cost matrix function. Examples -------- @@ -658,20 +658,20 @@ def get_cost_matrix_function(metric: str) -> CostMatrixFunction: [ inf, inf, 343., 400., 521.], [ inf, inf, inf, 424., 500.]]) """ - return _resolve_key_from_distance(metric, "cost_matrix") + return _resolve_key_from_distance(measure, "cost_matrix") -def _resolve_key_from_distance(metric: Union[str, Callable], key: str) -> Any: - if isinstance(metric, Callable): - return metric - if metric == "mpdist": +def _resolve_key_from_distance(measure: Union[str, Callable], key: str) -> Any: + if isinstance(measure, Callable): + return measure + if measure == "mpdist": return mp_distance - dist = DISTANCES_DICT.get(metric) + dist = DISTANCES_DICT.get(measure) if dist is None: - raise ValueError(f"Unknown metric {metric}") + raise ValueError(f"Unknown measure {measure}") dist_callable = dist.get(key) if dist_callable is None: - raise ValueError(f"Metric {metric} does not have a {key} function") + raise ValueError(f"Measure {measure} does not have a {key} function") return dist_callable diff --git a/aeon/distances/elastic/tests/test_alignment_path.py b/aeon/distances/elastic/tests/test_alignment_path.py index 6c2fa5ebfc..5fd1132783 100644 --- a/aeon/distances/elastic/tests/test_alignment_path.py +++ b/aeon/distances/elastic/tests/test_alignment_path.py @@ -32,7 +32,7 @@ def _validate_alignment_path_result( assert isinstance(alignment_path_result, tuple) assert isinstance(alignment_path_result[0], list) assert isinstance(alignment_path_result[1], float) - assert compute_alignment_path(x, y, metric=name) == alignment_path_result + assert compute_alignment_path(x, y, measure=name) == alignment_path_result # Test a callable being passed assert callable_alignment_path == alignment_path_result diff --git a/aeon/distances/elastic/tests/test_cost_matrix.py b/aeon/distances/elastic/tests/test_cost_matrix.py index 1971923ebf..46f4c0d47a 100644 --- a/aeon/distances/elastic/tests/test_cost_matrix.py +++ b/aeon/distances/elastic/tests/test_cost_matrix.py @@ -30,8 +30,8 @@ def _validate_cost_matrix_result( ---------- x (np.ndarray): The first input array. y (np.ndarray): The second input array. - name: The name of the distance metric. - distance: The distance metric function. + name: The name of the distance measure. + distance: The distance measure function. cost_matrix: The cost matrix function. """ original_x = x.copy() @@ -40,7 +40,7 @@ def _validate_cost_matrix_result( cost_matrix_callable_result = DISTANCES_DICT[name]["cost_matrix"](x, y) assert isinstance(cost_matrix_result, np.ndarray) - assert_almost_equal(cost_matrix_result, compute_cost_matrix(x, y, metric=name)) + assert_almost_equal(cost_matrix_result, compute_cost_matrix(x, y, measure=name)) assert_almost_equal(cost_matrix_callable_result, cost_matrix_result) if name == "ddtw" or name == "wddtw": assert cost_matrix_result.shape == (x.shape[-1] - 2, y.shape[-1] - 2) diff --git a/aeon/distances/tests/test_distances.py b/aeon/distances/tests/test_distances.py index a361d47ae0..efcbd1ee2e 100644 --- a/aeon/distances/tests/test_distances.py +++ b/aeon/distances/tests/test_distances.py @@ -37,7 +37,7 @@ def _validate_distance_result( ---------- x (np.ndarray): First array. y (np.ndarray): Second array. - name (str): Name of the distance metric. + name (str): Name of the distance measure. distance (callable): Distance function. expected_result (float): Expected distance result. check_xy_permuted: (bool): recursively call with swapped series @@ -50,8 +50,8 @@ def _validate_distance_result( dist_result = distance(x, y) assert isinstance(dist_result, float) assert_almost_equal(dist_result, expected_result) - assert_almost_equal(dist_result, compute_distance(x, y, metric=name)) - assert_almost_equal(dist_result, compute_distance(x, y, metric=distance)) + assert_almost_equal(dist_result, compute_distance(x, y, measure=name)) + assert_almost_equal(dist_result, compute_distance(x, y, measure=distance)) dist_result_to_self = distance(x, x) assert isinstance(dist_result_to_self, float) @@ -160,10 +160,10 @@ def test_get_distance_function_names(): def test_resolve_key_from_distance(): """Test _resolve_key_from_distance.""" - with pytest.raises(ValueError, match="Unknown metric"): - _resolve_key_from_distance(metric="FOO", key="cost_matrix") + with pytest.raises(ValueError, match="Unknown measure"): + _resolve_key_from_distance(measure="FOO", key="cost_matrix") with pytest.raises(ValueError): - _resolve_key_from_distance(metric="dtw", key="FOO") + _resolve_key_from_distance(measure="dtw", key="FOO") def foo(x, y): return 0 @@ -176,17 +176,23 @@ def test_incorrect_inputs(): x = np.array([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]) y = np.array([[11, 12, 13, 14, 15, 16, 17, 18, 19, 20]]) with pytest.raises( - ValueError, match="Metric must be one of the supported strings or a " "callable" + ValueError, + match="Measure must be one of the supported strings or a " "callable", ): - compute_distance(x, y, metric="FOO") + compute_distance(x, y, measure="FOO") with pytest.raises( - ValueError, match="Metric must be one of the supported strings or a " "callable" + ValueError, + match="Measure must be one of the supported strings or a " "callable", ): - pairwise_distance(x, y, metric="FOO") - with pytest.raises(ValueError, match="Metric must be one of the supported strings"): - alignment_path(x, y, metric="FOO") - with pytest.raises(ValueError, match="Metric must be one of the supported strings"): - cost_matrix(x, y, metric="FOO") + pairwise_distance(x, y, measure="FOO") + with pytest.raises( + ValueError, match="Measure must be one of the supported strings" + ): + alignment_path(x, y, measure="FOO") + with pytest.raises( + ValueError, match="Measure must be one of the supported strings" + ): + cost_matrix(x, y, measure="FOO") x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) with pytest.raises(ValueError, match="dist_func must be a callable"): diff --git a/aeon/distances/tests/test_numba_distance_parameters.py b/aeon/distances/tests/test_numba_distance_parameters.py index 4353b2ae8f..5b0bc385d0 100644 --- a/aeon/distances/tests/test_numba_distance_parameters.py +++ b/aeon/distances/tests/test_numba_distance_parameters.py @@ -93,7 +93,7 @@ def _test_distance_params( del param_dict["g"] results = [ distance_func(x, y, **param_dict.copy()), - distance(x, y, metric=distance_str, **param_dict.copy()), + distance(x, y, measure=distance_str, **param_dict.copy()), ] if distance_str in _expected_distance_results_params: diff --git a/aeon/distances/tests/test_pairwise.py b/aeon/distances/tests/test_pairwise.py index 6aaab3690b..152913aee0 100644 --- a/aeon/distances/tests/test_pairwise.py +++ b/aeon/distances/tests/test_pairwise.py @@ -52,7 +52,7 @@ def _validate_pairwise_result( Parameters ---------- x: Input np.ndarray. - name: Name of the distance metric. + name: Name of the distance measure. distance: Distance function. pairwise_distance: Pairwise distance function. """ @@ -64,11 +64,11 @@ def _validate_pairwise_result( assert isinstance(pairwise_result, np.ndarray) assert pairwise_result.shape == expected_size assert_almost_equal( - pairwise_result, compute_pairwise_distance(x, metric=name, symmetric=symmetric) + pairwise_result, compute_pairwise_distance(x, measure=name, symmetric=symmetric) ) assert_almost_equal( pairwise_result, - compute_pairwise_distance(x, metric=distance, symmetric=symmetric), + compute_pairwise_distance(x, measure=distance, symmetric=symmetric), ) if isinstance(x, np.ndarray): @@ -100,7 +100,7 @@ def _validate_multiple_to_multiple_result( ---------- x: Input array. y: Input array. - name: Name of the distance metric. + name: Name of the distance measure. distance: Distance function. multiple_to_multiple_distance: Mul-to-Mul distance function. check_xy_permuted: recursively call with swapped series @@ -123,11 +123,11 @@ def _validate_multiple_to_multiple_result( assert multiple_to_multiple_result.shape == expected_size assert_almost_equal( - multiple_to_multiple_result, compute_pairwise_distance(x, y, metric=name) + multiple_to_multiple_result, compute_pairwise_distance(x, y, measure=name) ) assert_almost_equal( multiple_to_multiple_result, - compute_pairwise_distance(x, y, metric=distance), + compute_pairwise_distance(x, y, measure=distance), ) if isinstance(x, np.ndarray) and isinstance(y, np.ndarray): @@ -168,7 +168,7 @@ def _validate_single_to_multiple_result( ---------- x: Input array. y: Input array. - name: Name of the distance metric. + name: Name of the distance measure. distance: Distance function. single_to_multiple_distance: Single to multiple distance function. run_inverse: Boolean that reruns the test with x and y swapped in position @@ -198,11 +198,11 @@ def _validate_single_to_multiple_result( assert single_to_multiple_result.shape[1] == expected_size assert_almost_equal( single_to_multiple_result, - compute_pairwise_distance(x, y, metric=name, symmetric=symmetric), + compute_pairwise_distance(x, y, measure=name, symmetric=symmetric), ) assert_almost_equal( single_to_multiple_result, - compute_pairwise_distance(x, y, metric=distance, symmetric=symmetric), + compute_pairwise_distance(x, y, measure=distance, symmetric=symmetric), ) if len(x_shape) < len(y_shape): diff --git a/aeon/pipeline/__init__.py b/aeon/pipeline/__init__.py index 732dae0187..a0b15f190f 100644 --- a/aeon/pipeline/__init__.py +++ b/aeon/pipeline/__init__.py @@ -1,6 +1,9 @@ """Pipeline maker utility.""" -__all__ = ["make_pipeline", "sklearn_to_aeon"] +__all__ = [ + "make_pipeline", + "sklearn_to_aeon", +] from aeon.pipeline._make_pipeline import make_pipeline from aeon.pipeline._sklearn_to_aeon import sklearn_to_aeon diff --git a/aeon/regression/compose/tests/test_ensemble.py b/aeon/regression/compose/tests/test_ensemble.py index 447afda327..4d5b288de7 100644 --- a/aeon/regression/compose/tests/test_ensemble.py +++ b/aeon/regression/compose/tests/test_ensemble.py @@ -14,7 +14,7 @@ make_example_3d_numpy, make_example_3d_numpy_list, ) -from aeon.testing.mock_estimators import MockHandlesAllInput, MockRegressor +from aeon.testing.mock_estimators import MockRegressor, MockRegressorFullTags mixed_ensemble = [ DummyRegressor(), @@ -114,7 +114,7 @@ def test_unequal_tag_inference(): n_cases=10, min_n_timepoints=8, max_n_timepoints=12, regression_target=True ) - r1 = MockHandlesAllInput() + r1 = MockRegressorFullTags() r2 = MockRegressor() assert r1.get_tag("capability:unequal_length") @@ -144,7 +144,7 @@ def test_missing_tag_inference(): X, y = make_example_3d_numpy(n_cases=10, n_timepoints=12, regression_target=True) X[5, 0, 4] = np.nan - r1 = MockHandlesAllInput() + r1 = MockRegressorFullTags() r2 = MockRegressor() assert r1.get_tag("capability:missing_values") @@ -175,7 +175,7 @@ def test_multivariate_tag_inference(): n_cases=10, n_channels=2, n_timepoints=12, regression_target=True ) - r1 = MockHandlesAllInput() + r1 = MockRegressorFullTags() r2 = MockRegressor() assert r1.get_tag("capability:multivariate") diff --git a/aeon/regression/distance_based/_time_series_neighbors.py b/aeon/regression/distance_based/_time_series_neighbors.py index 29056de961..d56120ea92 100644 --- a/aeon/regression/distance_based/_time_series_neighbors.py +++ b/aeon/regression/distance_based/_time_series_neighbors.py @@ -111,7 +111,7 @@ def _fit(self, X, y): y : array-like, shape = (n_cases) The output value. """ - self.metric_ = get_distance_function(metric=self.distance) + self.metric_ = get_distance_function(measure=self.distance) self.X_ = X self.y_ = y return self diff --git a/aeon/regression/tests/test_base.py b/aeon/regression/tests/test_base.py index 30302de8c7..d04469db8b 100644 --- a/aeon/regression/tests/test_base.py +++ b/aeon/regression/tests/test_base.py @@ -12,7 +12,7 @@ EQUAL_LENGTH_UNIVARIATE_REGRESSION, UNEQUAL_LENGTH_UNIVARIATE_REGRESSION, ) -from aeon.utils import COLLECTIONS_DATA_TYPES +from aeon.utils.data_types import COLLECTIONS_DATA_TYPES class _TestRegressor(BaseRegressor): diff --git a/aeon/segmentation/tests/test_base.py b/aeon/segmentation/tests/test_base.py index 013e4ecdad..870fbdb7e5 100644 --- a/aeon/segmentation/tests/test_base.py +++ b/aeon/segmentation/tests/test_base.py @@ -5,7 +5,7 @@ import pytest from aeon.segmentation.base import BaseSegmenter -from aeon.testing.mock_estimators import MockSegmenter, SupervisedMockSegmenter +from aeon.testing.mock_estimators import MockSegmenter, MockSegmenterRequiresY def test_fit_predict_correct(): @@ -25,7 +25,7 @@ def test_fit_predict_correct(): assert res.is_fitted res = seg.fit_predict(x_correct) assert isinstance(res, np.ndarray) - seg = SupervisedMockSegmenter() + seg = MockSegmenterRequiresY() res = seg.fit(x_correct, y=x_correct) assert res.is_fitted with pytest.raises( diff --git a/aeon/testing/estimator_checking/_estimator_checking.py b/aeon/testing/estimator_checking/_estimator_checking.py index c07815ea89..97eed5a0e9 100644 --- a/aeon/testing/estimator_checking/_estimator_checking.py +++ b/aeon/testing/estimator_checking/_estimator_checking.py @@ -193,6 +193,7 @@ class is passed. {'check_get_params(estimator=MockClassifier())': 'PASSED'} """ # check if estimator has soft dependencies installed + _check_soft_dependencies("pytest") _check_estimator_deps(estimator) checks = [] diff --git a/aeon/testing/estimator_checking/_yield_anomaly_detection_checks.py b/aeon/testing/estimator_checking/_yield_anomaly_detection_checks.py index 0d94fbc34f..5f2f05aaa9 100644 --- a/aeon/testing/estimator_checking/_yield_anomaly_detection_checks.py +++ b/aeon/testing/estimator_checking/_yield_anomaly_detection_checks.py @@ -3,7 +3,6 @@ from functools import partial import numpy as np -import pytest from aeon.base._base import _clone_estimator from aeon.base._base_series import VALID_SERIES_INNER_TYPES @@ -64,6 +63,8 @@ def check_anomaly_detector_overrides_and_tags(estimator_class): def check_anomaly_detector_univariate(estimator): """Test the anomaly detector on univariate data.""" + import pytest + estimator = _clone_estimator(estimator) if estimator.get_tag(tag_name="capability:univariate"): @@ -78,6 +79,8 @@ def check_anomaly_detector_univariate(estimator): def check_anomaly_detector_multivariate(estimator): """Test the anomaly detector on multivariate data.""" + import pytest + estimator = _clone_estimator(estimator) if estimator.get_tag(tag_name="capability:multivariate"): diff --git a/aeon/testing/estimator_checking/_yield_classification_checks.py b/aeon/testing/estimator_checking/_yield_classification_checks.py index 00b82705de..09f15877be 100644 --- a/aeon/testing/estimator_checking/_yield_classification_checks.py +++ b/aeon/testing/estimator_checking/_yield_classification_checks.py @@ -24,7 +24,7 @@ _assert_predict_probabilities, _get_tag, ) -from aeon.utils import COLLECTIONS_DATA_TYPES +from aeon.utils.data_types import COLLECTIONS_DATA_TYPES from aeon.utils.validation import get_n_cases diff --git a/aeon/testing/estimator_checking/_yield_estimator_checks.py b/aeon/testing/estimator_checking/_yield_estimator_checks.py index b5fb60c1d3..70f714d4d9 100644 --- a/aeon/testing/estimator_checking/_yield_estimator_checks.py +++ b/aeon/testing/estimator_checking/_yield_estimator_checks.py @@ -9,7 +9,6 @@ import joblib import numpy as np -import pytest from numpy.testing import assert_array_almost_equal from sklearn.exceptions import NotFittedError @@ -596,6 +595,8 @@ def check_fit_updates_state_and_cloning(estimator, datatype): def check_raises_not_fitted_error(estimator, datatype): """Check exception raised for non-fit method calls to unfitted estimators.""" + import pytest + estimator = _clone_estimator(estimator) for method in NON_STATE_CHANGING_METHODS: diff --git a/aeon/testing/estimator_checking/_yield_regression_checks.py b/aeon/testing/estimator_checking/_yield_regression_checks.py index bf6d2cb568..52933e81f7 100644 --- a/aeon/testing/estimator_checking/_yield_regression_checks.py +++ b/aeon/testing/estimator_checking/_yield_regression_checks.py @@ -20,7 +20,7 @@ ) from aeon.testing.testing_data import FULL_TEST_DATA_DICT from aeon.testing.utils.estimator_checks import _assert_predict_labels, _get_tag -from aeon.utils import COLLECTIONS_DATA_TYPES +from aeon.utils.data_types import COLLECTIONS_DATA_TYPES def _yield_regression_checks(estimator_class, estimator_instances, datatypes): diff --git a/aeon/testing/estimator_checking/_yield_segmentation_checks.py b/aeon/testing/estimator_checking/_yield_segmentation_checks.py index 6c2c964f5a..054ca56bd4 100644 --- a/aeon/testing/estimator_checking/_yield_segmentation_checks.py +++ b/aeon/testing/estimator_checking/_yield_segmentation_checks.py @@ -3,7 +3,6 @@ from functools import partial import numpy as np -import pytest from aeon.base._base import _clone_estimator from aeon.base._base_series import VALID_SERIES_INNER_TYPES @@ -42,6 +41,8 @@ def check_segmenter_base_functionality(estimator_class): def check_segmenter_instance(estimator): """Test segmenters.""" + import pytest + estimator = _clone_estimator(estimator) def _assert_output(output, dense, length): diff --git a/aeon/testing/estimator_checking/_yield_soft_dependency_checks.py b/aeon/testing/estimator_checking/_yield_soft_dependency_checks.py index 83c6f96b83..ed5c9605b1 100644 --- a/aeon/testing/estimator_checking/_yield_soft_dependency_checks.py +++ b/aeon/testing/estimator_checking/_yield_soft_dependency_checks.py @@ -6,8 +6,6 @@ from functools import partial -import pytest - from aeon.utils.validation._dependencies import ( _check_python_version, _check_soft_dependencies, @@ -23,6 +21,8 @@ def _yield_soft_dependency_checks(estimator_class, estimator_instances, datatype def check_python_version_softdep(estimator_class): """Test that estimators raise error if python version is wrong.""" + import pytest + # if dependencies are incompatible skip softdeps = estimator_class.get_class_tag("python_dependencies", None) if softdeps is not None and not _check_soft_dependencies(softdeps, severity="none"): @@ -46,6 +46,8 @@ def check_python_version_softdep(estimator_class): def check_python_dependency_softdep(estimator_class): """Test that estimators raise error if required soft dependencies are missing.""" + import pytest + # if python version is incompatible skip if not _check_python_version(estimator_class, severity="none"): return diff --git a/aeon/testing/estimator_checking/_yield_transformation_checks.py b/aeon/testing/estimator_checking/_yield_transformation_checks.py index 507c8c1e08..4a8c51f795 100644 --- a/aeon/testing/estimator_checking/_yield_transformation_checks.py +++ b/aeon/testing/estimator_checking/_yield_transformation_checks.py @@ -20,7 +20,7 @@ from aeon.testing.utils.estimator_checks import _run_estimator_method from aeon.transformations.collection.channel_selection.base import BaseChannelSelector from aeon.transformations.series import BaseSeriesTransformer -from aeon.utils import COLLECTIONS_DATA_TYPES +from aeon.utils.data_types import COLLECTIONS_DATA_TYPES def _yield_transformation_checks(estimator_class, estimator_instances, datatypes): diff --git a/aeon/testing/mock_estimators/__init__.py b/aeon/testing/mock_estimators/__init__.py index 32d947cb7d..219fc3e987 100644 --- a/aeon/testing/mock_estimators/__init__.py +++ b/aeon/testing/mock_estimators/__init__.py @@ -1,26 +1,46 @@ """Mock estimators for testing and debugging.""" __all__ = [ - "make_mock_estimator", + # anomaly detection + "MockAnomalyDetector", + "MockAnomalyDetectorRequiresFit", + "MockAnomalyDetectorRequiresY", + # classification "MockClassifier", "MockClassifierPredictProba", "MockClassifierFullTags", "MockClassifierParams", + "MockClassifierComposite", + # clustering "MockCluster", "MockDeepClusterer", - "MockSegmenter", - "SupervisedMockSegmenter", - "MockHandlesAllInput", + # collection transformation + "MockCollectionTransformer", + # forecasting + "MockForecaster", + # regression "MockRegressor", + "MockRegressorFullTags", + # segmentation + "MockSegmenter", + "MockSegmenterRequiresY", + # series transformation + "MockSeriesTransformer", + "MockUnivariateSeriesTransformer", "MockMultivariateSeriesTransformer", "MockSeriesTransformerNoFit", - "MockUnivariateSeriesTransformer", - "MockCollectionTransformer", - "MockSeriesTransformer", + # similarity search + "MockSimilaritySearch", ] +from aeon.testing.mock_estimators._mock_anomaly_detectors import ( + MockAnomalyDetector, + MockAnomalyDetectorRequiresFit, + MockAnomalyDetectorRequiresY, +) from aeon.testing.mock_estimators._mock_classifiers import ( MockClassifier, + MockClassifierComposite, MockClassifierFullTags, MockClassifierParams, MockClassifierPredictProba, @@ -29,13 +49,14 @@ from aeon.testing.mock_estimators._mock_collection_transformers import ( MockCollectionTransformer, ) +from aeon.testing.mock_estimators._mock_forecasters import MockForecaster from aeon.testing.mock_estimators._mock_regressors import ( - MockHandlesAllInput, MockRegressor, + MockRegressorFullTags, ) from aeon.testing.mock_estimators._mock_segmenters import ( MockSegmenter, - SupervisedMockSegmenter, + MockSegmenterRequiresY, ) from aeon.testing.mock_estimators._mock_series_transformers import ( MockMultivariateSeriesTransformer, @@ -43,3 +64,4 @@ MockSeriesTransformerNoFit, MockUnivariateSeriesTransformer, ) +from aeon.testing.mock_estimators._mock_similarity_search import MockSimilaritySearch diff --git a/aeon/testing/mock_estimators/_mock_anomaly_detectors.py b/aeon/testing/mock_estimators/_mock_anomaly_detectors.py index 78ddfe76f1..4ec14d35fa 100644 --- a/aeon/testing/mock_estimators/_mock_anomaly_detectors.py +++ b/aeon/testing/mock_estimators/_mock_anomaly_detectors.py @@ -1,4 +1,4 @@ -"""Mock anomaly detectors for testing.""" +"""Mock anomaly detectorsuseful for testing and debugging.""" __maintainer__ = ["MatthewMiddlehurst"] __all__ = [ diff --git a/aeon/testing/mock_estimators/_mock_classifiers.py b/aeon/testing/mock_estimators/_mock_classifiers.py index bcfcb8162e..1bf9357d60 100644 --- a/aeon/testing/mock_estimators/_mock_classifiers.py +++ b/aeon/testing/mock_estimators/_mock_classifiers.py @@ -1,7 +1,13 @@ -"""Mock classifiers useful for testing and debugging. - -Used in tests for the classifier base class. -""" +"""Mock classifiers useful for testing and debugging.""" + +__maintainer__ = ["MatthewMiddlehurst"] +__all__ = [ + "MockClassifier", + "MockClassifierPredictProba", + "MockClassifierFullTags", + "MockClassifierParams", + "MockClassifierComposite", +] import numpy as np diff --git a/aeon/testing/mock_estimators/_mock_clusterers.py b/aeon/testing/mock_estimators/_mock_clusterers.py index b920b83c30..53b1014290 100644 --- a/aeon/testing/mock_estimators/_mock_clusterers.py +++ b/aeon/testing/mock_estimators/_mock_clusterers.py @@ -1,3 +1,11 @@ +"""Mock clusterers useful for testing and debugging.""" + +__maintainer__ = [] +__all__ = [ + "MockCluster", + "MockDeepClusterer", +] + import numpy as np from aeon.clustering.base import BaseClusterer diff --git a/aeon/testing/mock_estimators/_mock_collection_transformers.py b/aeon/testing/mock_estimators/_mock_collection_transformers.py index bc59069283..7feb8d46a9 100644 --- a/aeon/testing/mock_estimators/_mock_collection_transformers.py +++ b/aeon/testing/mock_estimators/_mock_collection_transformers.py @@ -1,4 +1,9 @@ -"""Mock collection transformers.""" +"""Mock collection transformers useful for testing and debugging.""" + +__maintainer__ = [] +__all__ = [ + "MockCollectionTransformer", +] from aeon.transformations.collection import BaseCollectionTransformer diff --git a/aeon/testing/mock_estimators/_mock_forecasters.py b/aeon/testing/mock_estimators/_mock_forecasters.py index f5bb86d249..8eb2ba2635 100644 --- a/aeon/testing/mock_estimators/_mock_forecasters.py +++ b/aeon/testing/mock_estimators/_mock_forecasters.py @@ -1,7 +1,10 @@ -"""Mock forecasters useful for testing and debugging. +"""Mock forecasters useful for testing and debugging.""" + +__maintainer__ = ["TonyBagnall"] +__all__ = [ + "MockForecaster", +] -Used in tests for the forecasting base class. -""" from aeon.forecasting.base import BaseForecaster diff --git a/aeon/testing/mock_estimators/_mock_regressors.py b/aeon/testing/mock_estimators/_mock_regressors.py index 355534abac..5258019aab 100644 --- a/aeon/testing/mock_estimators/_mock_regressors.py +++ b/aeon/testing/mock_estimators/_mock_regressors.py @@ -1,3 +1,11 @@ +"""Mock regressors useful for testing and debugging.""" + +__maintainer__ = ["MatthewMiddlehurst"] +__all__ = [ + "MockRegressor", + "MockRegressorFullTags", +] + from sklearn.utils import check_random_state from aeon.regression.base import BaseRegressor @@ -20,7 +28,7 @@ def _predict(self, X): return rng.random(size=(len(X))) -class MockHandlesAllInput(BaseRegressor): +class MockRegressorFullTags(BaseRegressor): """Dummy regressor for testing base class fit/predict/predict_proba.""" _tags = { diff --git a/aeon/testing/mock_estimators/_mock_segmenters.py b/aeon/testing/mock_estimators/_mock_segmenters.py index 82ff6a81f6..45b7a524aa 100644 --- a/aeon/testing/mock_estimators/_mock_segmenters.py +++ b/aeon/testing/mock_estimators/_mock_segmenters.py @@ -1,4 +1,10 @@ -"""Mock segmenters for testing.""" +"""Mock segmenters useful for testing and debugging.""" + +__maintainer__ = [] +__all__ = [ + "MockSegmenter", + "MockSegmenterRequiresY", +] import numpy as np @@ -42,7 +48,7 @@ def _get_test_params(cls, parameter_set="default"): return {} -class SupervisedMockSegmenter(MockSegmenter): +class MockSegmenterRequiresY(MockSegmenter): """Mock segmenter for testing.""" _tags = { diff --git a/aeon/testing/mock_estimators/_mock_series_transformers.py b/aeon/testing/mock_estimators/_mock_series_transformers.py index 937f4a6325..66d62ef687 100644 --- a/aeon/testing/mock_estimators/_mock_series_transformers.py +++ b/aeon/testing/mock_estimators/_mock_series_transformers.py @@ -1,4 +1,12 @@ -"""Mock series transformers.""" +"""Mock series transformers useful for testing and debugging.""" + +__maintainer__ = [] +__all__ = [ + "MockSeriesTransformer", + "MockUnivariateSeriesTransformer", + "MockMultivariateSeriesTransformer", + "MockSeriesTransformerNoFit", +] import numpy as np diff --git a/aeon/testing/mock_estimators/_mock_similarity_search.py b/aeon/testing/mock_estimators/_mock_similarity_search.py index 8542b81a1b..55c9c435c7 100644 --- a/aeon/testing/mock_estimators/_mock_similarity_search.py +++ b/aeon/testing/mock_estimators/_mock_similarity_search.py @@ -1,12 +1,14 @@ -"""Mock similarity search useful for testing and debugging. +"""Mock similarity searchers useful for testing and debugging.""" -Used in tests for the query search base class. -""" +__maintainer__ = ["baraline"] +__all__ = [ + "MockSimilaritySearch", +] from aeon.similarity_search.base import BaseSimilaritySearch -class MocksimilaritySearch(BaseSimilaritySearch): +class MockSimilaritySearch(BaseSimilaritySearch): """Mock similarity search for testing base class predict.""" def _fit(self, X, y=None): diff --git a/aeon/testing/tests/__init__.py b/aeon/testing/tests/__init__.py index 69d1ff2d83..aaeacff24d 100644 --- a/aeon/testing/tests/__init__.py +++ b/aeon/testing/tests/__init__.py @@ -1 +1,13 @@ -"""Tests for the aeon package and testing module utiltiies.""" +"""Tests for the aeon package and testing module utilties.""" + +import pkgutil + +import aeon + +# collect all modules +ALL_AEON_MODULES = pkgutil.walk_packages(aeon.__path__, aeon.__name__ + ".") +ALL_AEON_MODULES = [x[1] for x in ALL_AEON_MODULES] + +ALL_AEON_MODULES_NO_TESTS = [ + x for x in ALL_AEON_MODULES if not any(part == "tests" for part in x.split(".")) +] diff --git a/aeon/testing/tests/test_core_imports.py b/aeon/testing/tests/test_core_imports.py new file mode 100644 index 0000000000..f13740c08e --- /dev/null +++ b/aeon/testing/tests/test_core_imports.py @@ -0,0 +1,26 @@ +"""Tests that non-core dependencies are handled correctly in modules.""" + +import re +from importlib import import_module + +from aeon.testing.tests import ALL_AEON_MODULES_NO_TESTS + +if __name__ == "__main__": + """Test imports in aeon modules with core dependencies only. + + Imports all modules and catch exceptions due to missing dependencies. + """ + for module in ALL_AEON_MODULES_NO_TESTS: + try: + import_module(module) + except ModuleNotFoundError as e: # pragma: no cover + dependency = "unknown" + match = re.search(r"\'(.+?)\'", str(e)) + if match: + dependency = match.group(1) + + raise ModuleNotFoundError( + f"The module: {module} should not require any non-core dependencies, " + f"but tried importing: '{dependency}'. Make sure non-core dependencies " + f"are properly isolated outside of tests/ directories." + ) from e diff --git a/aeon/testing/tests/test_softdeps.py b/aeon/testing/tests/test_softdeps.py index 830d0e80f0..2271d21497 100644 --- a/aeon/testing/tests/test_softdeps.py +++ b/aeon/testing/tests/test_softdeps.py @@ -1,23 +1,14 @@ """Tests that soft dependencies are handled correctly in modules.""" -__maintainer__ = [] - -import pkgutil import re from importlib import import_module import pytest -import aeon from aeon.testing.testing_config import PR_TESTING +from aeon.testing.tests import ALL_AEON_MODULES, ALL_AEON_MODULES_NO_TESTS -# collect all modules -modules = pkgutil.walk_packages(aeon.__path__, aeon.__name__ + ".") -modules = [x[1] for x in modules] - -if PR_TESTING: # pragma: no cover - # exclude test modules - modules = [x for x in modules if not any(part == "tests" for part in x.split("."))] +modules = ALL_AEON_MODULES_NO_TESTS if PR_TESTING else ALL_AEON_MODULES def test_module_crawl(): diff --git a/aeon/testing/tests/test_testing_data.py b/aeon/testing/tests/test_testing_data.py index 505cb474a8..f9afe264dd 100644 --- a/aeon/testing/tests/test_testing_data.py +++ b/aeon/testing/tests/test_testing_data.py @@ -20,7 +20,7 @@ UNEQUAL_LENGTH_UNIVARIATE_REGRESSION, UNEQUAL_LENGTH_UNIVARIATE_SIMILARITY_SEARCH, ) -from aeon.utils import COLLECTIONS_DATA_TYPES +from aeon.utils.data_types import COLLECTIONS_DATA_TYPES from aeon.utils.validation import ( has_missing, is_collection, diff --git a/aeon/testing/utils/deep_equals.py b/aeon/testing/utils/deep_equals.py index 86c6c5cd96..81f6d91534 100644 --- a/aeon/testing/utils/deep_equals.py +++ b/aeon/testing/utils/deep_equals.py @@ -1,6 +1,6 @@ """Testing utility to compare equality in value for nested objects.""" -__maintainer__ = [] +__maintainer__ = ["MatthewMiddlehurst"] __all__ = ["deep_equals"] from inspect import isclass diff --git a/aeon/testing/utils/output_supression.py b/aeon/testing/utils/output_suppression.py similarity index 100% rename from aeon/testing/utils/output_supression.py rename to aeon/testing/utils/output_suppression.py diff --git a/aeon/testing/utils/tests/test_output_supression.py b/aeon/testing/utils/tests/test_output_supression.py index 8e8b0a4862..56f7b18ec5 100644 --- a/aeon/testing/utils/tests/test_output_supression.py +++ b/aeon/testing/utils/tests/test_output_supression.py @@ -2,7 +2,7 @@ import sys -from aeon.testing.utils.output_supression import suppress_output +from aeon.testing.utils.output_suppression import suppress_output @suppress_output() diff --git a/aeon/transformations/collection/_hog1d.py b/aeon/transformations/collection/_hog1d.py index 3deddf5931..a3cc18d8aa 100644 --- a/aeon/transformations/collection/_hog1d.py +++ b/aeon/transformations/collection/_hog1d.py @@ -6,7 +6,7 @@ import numpy as np from aeon.transformations.collection.base import BaseCollectionTransformer -from aeon.utils import split_series +from aeon.utils.split import split_series class HOG1DTransformer(BaseCollectionTransformer): diff --git a/aeon/transformations/collection/_slope.py b/aeon/transformations/collection/_slope.py index 7a11cbcdb6..cf9d860478 100644 --- a/aeon/transformations/collection/_slope.py +++ b/aeon/transformations/collection/_slope.py @@ -8,7 +8,7 @@ import numpy as np from aeon.transformations.collection.base import BaseCollectionTransformer -from aeon.utils import split_series +from aeon.utils.split import split_series class SlopeTransformer(BaseCollectionTransformer): diff --git a/aeon/transformations/collection/channel_selection/_elbow_class.py b/aeon/transformations/collection/channel_selection/_elbow_class.py index d32b7fbb1e..be0f8102de 100644 --- a/aeon/transformations/collection/channel_selection/_elbow_class.py +++ b/aeon/transformations/collection/channel_selection/_elbow_class.py @@ -90,7 +90,7 @@ class values {len(class_vals)} must be of same length." lambda row: aeon_distance( row[: row.shape[0] // 2], row[row.shape[0] // 2 :], - metric="dtw", + measure="dtw", ), axis=1, arr=np.concatenate((cls1_ch, cls2_ch), axis=1), diff --git a/aeon/transformations/collection/compose/_identity.py b/aeon/transformations/collection/compose/_identity.py index 20c6674b6b..a359255242 100644 --- a/aeon/transformations/collection/compose/_identity.py +++ b/aeon/transformations/collection/compose/_identity.py @@ -1,7 +1,7 @@ """Identity transformer.""" from aeon.transformations.collection import BaseCollectionTransformer -from aeon.utils import COLLECTIONS_DATA_TYPES +from aeon.utils.data_types import COLLECTIONS_DATA_TYPES class CollectionId(BaseCollectionTransformer): diff --git a/aeon/transformations/series/tests/test_warping.py b/aeon/transformations/series/tests/test_warping.py index ca14ab65a3..f707d0e198 100644 --- a/aeon/transformations/series/tests/test_warping.py +++ b/aeon/transformations/series/tests/test_warping.py @@ -16,7 +16,7 @@ def test_warping_path_transformer(distance): x = make_example_2d_numpy_series(n_timepoints=20, n_channels=2) y = make_example_2d_numpy_series(n_timepoints=20, n_channels=2) - alignment_path_function = get_alignment_path_function(metric=distance) + alignment_path_function = get_alignment_path_function(measure=distance) warping_path = alignment_path_function(x, y)[0] diff --git a/aeon/utils/__init__.py b/aeon/utils/__init__.py index 8f16b7102d..e198bb676e 100644 --- a/aeon/utils/__init__.py +++ b/aeon/utils/__init__.py @@ -1,20 +1,7 @@ """Utility functionality.""" __all__ = [ - "split_series", - "ALL_TIME_SERIES_TYPES", - "COLLECTIONS_DATA_TYPES", - "SERIES_DATA_TYPES", - "HIERARCHICAL_DATA_TYPES", - # github debug util - "show_versions", + "show_versions", # github debug util ] -from aeon.utils._data_types import ( - ALL_TIME_SERIES_TYPES, - COLLECTIONS_DATA_TYPES, - HIERARCHICAL_DATA_TYPES, - SERIES_DATA_TYPES, -) -from aeon.utils._split import split_series from aeon.utils.show_versions import show_versions diff --git a/aeon/utils/conversion/_convert_collection.py b/aeon/utils/conversion/_convert_collection.py index e41ed0c8a2..0e3e28f1af 100644 --- a/aeon/utils/conversion/_convert_collection.py +++ b/aeon/utils/conversion/_convert_collection.py @@ -22,7 +22,7 @@ import pandas as pd from numba.typed import List as NumbaList -from aeon.utils._data_types import COLLECTIONS_DATA_TYPES +from aeon.utils.data_types import COLLECTIONS_DATA_TYPES from aeon.utils.validation.collection import _equal_length, get_type diff --git a/aeon/utils/conversion/tests/test_convert_collection.py b/aeon/utils/conversion/tests/test_convert_collection.py index e9940aa673..3776dc7f4f 100644 --- a/aeon/utils/conversion/tests/test_convert_collection.py +++ b/aeon/utils/conversion/tests/test_convert_collection.py @@ -8,7 +8,6 @@ EQUAL_LENGTH_UNIVARIATE_CLASSIFICATION, UNEQUAL_LENGTH_UNIVARIATE_CLASSIFICATION, ) -from aeon.utils import COLLECTIONS_DATA_TYPES from aeon.utils.conversion._convert_collection import ( _from_numpy2d_to_df_list, _from_numpy2d_to_np_list, @@ -24,6 +23,7 @@ resolve_equal_length_inner_type, resolve_unequal_length_inner_type, ) +from aeon.utils.data_types import COLLECTIONS_DATA_TYPES from aeon.utils.validation.collection import ( _equal_length, get_n_cases, diff --git a/aeon/utils/_data_types.py b/aeon/utils/data_types.py similarity index 100% rename from aeon/utils/_data_types.py rename to aeon/utils/data_types.py diff --git a/aeon/utils/networks/weight_norm.py b/aeon/utils/networks/weight_norm.py index 1a613f9b64..459cfd7104 100644 --- a/aeon/utils/networks/weight_norm.py +++ b/aeon/utils/networks/weight_norm.py @@ -5,7 +5,7 @@ if _check_soft_dependencies(["tensorflow"], severity="none"): import tensorflow as tf - class WeightNormalization(tf.keras.layers.Wrapper): + class _WeightNormalization(tf.keras.layers.Wrapper): """Apply weight normalization to a Keras layer.""" def __init__(self, layer, **kwargs): diff --git a/aeon/utils/sklearn.py b/aeon/utils/sklearn.py index 1f7d45a6fc..8ee26cf311 100644 --- a/aeon/utils/sklearn.py +++ b/aeon/utils/sklearn.py @@ -1,5 +1,15 @@ """Sklearn related typing and inheritance checking utility.""" +__maintainer__ = [] +__all__ = [ + "is_sklearn_estimator", + "sklearn_estimator_identifier", + "is_sklearn_transformer", + "is_sklearn_classifier", + "is_sklearn_regressor", + "is_sklearn_clusterer", +] + from inspect import isclass from sklearn.base import ( @@ -12,8 +22,6 @@ from sklearn.model_selection import GridSearchCV, RandomizedSearchCV from sklearn.pipeline import Pipeline -__maintainer__ = [] - from aeon.base import BaseAeonEstimator diff --git a/aeon/utils/_split.py b/aeon/utils/split.py similarity index 100% rename from aeon/utils/_split.py rename to aeon/utils/split.py diff --git a/aeon/utils/tags/_tags.py b/aeon/utils/tags/_tags.py index 554584115e..e1bacdd5ad 100644 --- a/aeon/utils/tags/_tags.py +++ b/aeon/utils/tags/_tags.py @@ -17,7 +17,7 @@ class : identifier for the base class of objects this tag applies to __maintainer__ = ["MatthewMiddlehurst"] __all__ = ["ESTIMATOR_TAGS"] -from aeon.utils import COLLECTIONS_DATA_TYPES, SERIES_DATA_TYPES +from aeon.utils.data_types import COLLECTIONS_DATA_TYPES, SERIES_DATA_TYPES ESTIMATOR_TAGS = { # all estimators diff --git a/aeon/utils/tests/test_show_versions.py b/aeon/utils/tests/test_show_versions.py index 866692a7d6..b47810ed98 100644 --- a/aeon/utils/tests/test_show_versions.py +++ b/aeon/utils/tests/test_show_versions.py @@ -1,6 +1,6 @@ """Test the show versions function.""" -from aeon.testing.utils.output_supression import suppress_output +from aeon.testing.utils.output_suppression import suppress_output from aeon.utils.show_versions import show_versions diff --git a/aeon/utils/tests/test_split.py b/aeon/utils/tests/test_split.py index 3c4f8df751..4e655f3aa2 100644 --- a/aeon/utils/tests/test_split.py +++ b/aeon/utils/tests/test_split.py @@ -3,7 +3,7 @@ import numpy as np import pytest -from aeon.utils import split_series +from aeon.utils.split import split_series X = np.arange(10) testdata = [ diff --git a/aeon/utils/tests/test_weighted_metrics.py b/aeon/utils/tests/test_weighted_metrics.py deleted file mode 100644 index c8a0b09135..0000000000 --- a/aeon/utils/tests/test_weighted_metrics.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Test weighted metric.""" - -import numpy as np -import pytest - -from aeon.utils.weighted_metrics import weighted_geometric_mean - - -def test_weighted_geometric_mean(): - """Test weighted_geometric_mean.""" - y = np.array([1.0, 2.0, 3.0]) - w = np.array([0.1, 0.8, 0.1]) - w2 = np.array([[0.1, 0.8, 0.1]]).T - res = weighted_geometric_mean(y, w) - assert round(res, 5) == 1.94328 - res2 = weighted_geometric_mean(y, w, axis=0) - assert res == res2 - y2 = np.array([[1.0, 2.0, 3.0]]).T - with pytest.raises(ValueError, match="do not match"): - weighted_geometric_mean(y2, w, axis=1) - weighted_geometric_mean(y2, w2, axis=1) - with pytest.raises( - ValueError, match="Input data and weights have inconsistent shapes" - ): - weighted_geometric_mean(y, w2) diff --git a/aeon/utils/tests/test_weightnorm.py b/aeon/utils/tests/test_weightnorm.py index 43b20293d5..0642530b2c 100644 --- a/aeon/utils/tests/test_weightnorm.py +++ b/aeon/utils/tests/test_weightnorm.py @@ -16,11 +16,11 @@ def test_weight_norm(): import numpy as np import tensorflow as tf - from aeon.utils.networks.weight_norm import WeightNormalization + from aeon.utils.networks.weight_norm import _WeightNormalization X = np.random.random((10, 10, 5)) _input = tf.keras.layers.Input((10, 5)) - l1 = WeightNormalization( + l1 = _WeightNormalization( tf.keras.layers.Conv1D(filters=5, kernel_size=1, dilation_rate=4) )(_input) model = tf.keras.models.Model(inputs=_input, outputs=l1) @@ -42,7 +42,7 @@ def test_weight_norm(): model_path = "test_weight_norm_model.h5" model.save(model_path) loaded_model = tf.keras.models.load_model( - model_path, custom_objects={"WeightNormalization": WeightNormalization} + model_path, custom_objects={"_WeightNormalization": _WeightNormalization} ) assert loaded_model is not None loaded_output = loaded_model.predict(X) diff --git a/aeon/utils/validation/__init__.py b/aeon/utils/validation/__init__.py index 14a16853ad..7e86a79a13 100644 --- a/aeon/utils/validation/__init__.py +++ b/aeon/utils/validation/__init__.py @@ -12,7 +12,6 @@ "check_window_length", "get_n_cases", "get_type", - "equal_length", "is_equal_length", "has_missing", "is_univariate", diff --git a/aeon/utils/validation/tests/test_collection.py b/aeon/utils/validation/tests/test_collection.py index b1c27e4a64..4c53572b32 100644 --- a/aeon/utils/validation/tests/test_collection.py +++ b/aeon/utils/validation/tests/test_collection.py @@ -13,7 +13,7 @@ make_example_3d_numpy_list, ) from aeon.testing.testing_data import EQUAL_LENGTH_UNIVARIATE_CLASSIFICATION -from aeon.utils import COLLECTIONS_DATA_TYPES +from aeon.utils.data_types import COLLECTIONS_DATA_TYPES from aeon.utils.validation.collection import ( _is_numpy_list_multivariate, _is_pd_wide, diff --git a/aeon/utils/weighted_metrics.py b/aeon/utils/weighted_metrics.py deleted file mode 100644 index 84ec005d2f..0000000000 --- a/aeon/utils/weighted_metrics.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Statistical functionality used throughout aeon.""" - -import numpy as np -from sklearn.utils.validation import check_consistent_length - -__maintainer__ = [] -__all__ = [ - "weighted_geometric_mean", -] - - -def weighted_geometric_mean(y, weights, axis=None): - """Calculate weighted version of geometric mean. - - Parameters - ---------- - y : np.ndarray - Values to take the weighted geometric mean of. - weights: np.ndarray - Weights for each value in `array`. Must be same shape as `array` or - of shape `(array.shape[0],)` if axis=0 or `(array.shape[1], ) if axis=1. - axis : int - The axis of `y` to apply the weights to. - - Returns - ------- - geometric_mean : float - Weighted geometric mean - """ - if weights.ndim == 1: - if axis == 0: - check_consistent_length(y, weights) - elif axis == 1: - if y.shape[1] != len(weights): - raise ValueError( - f"Input features ({y.shape[1]}) do not match " - f"number of `weights` ({len(weights)})." - ) - weight_sums = np.sum(weights) - else: - if y.shape != weights.shape: - raise ValueError("Input data and weights have inconsistent shapes.") - weight_sums = np.sum(weights, axis=axis) - return np.exp(np.sum(weights * np.log(y), axis=axis) / weight_sums) diff --git a/docs/api_reference.md b/docs/api_reference.md index 34a580ba74..d0c6335ca7 100644 --- a/docs/api_reference.md +++ b/docs/api_reference.md @@ -19,6 +19,7 @@ api_reference/clustering api_reference/data_format api_reference/datasets api_reference/distances +api_reference/forecasting api_reference/networks api_reference/regression api_reference/segmentation diff --git a/docs/api_reference/forecasting.md b/docs/api_reference/forecasting.md new file mode 100644 index 0000000000..131fb8be86 --- /dev/null +++ b/docs/api_reference/forecasting.md @@ -0,0 +1,14 @@ +# Forecasting + +```{eval-rst} +.. currentmodule:: aeon.datasets + +.. autosummary:: + :toctree: auto_generated/ + :template: class.rst + + DummyForecaster + BaseForecaster + RegressionForecaster + ETSForecaster +``` diff --git a/docs/api_reference/networks.rst b/docs/api_reference/networks.rst index 17493a7b6d..3eaee2c370 100644 --- a/docs/api_reference/networks.rst +++ b/docs/api_reference/networks.rst @@ -25,3 +25,7 @@ Deep learning networks LITENetwork AEBiGRUNetwork DisjointCNNNetwork + DCNNNetwork + AEDCNNNetwork + AEAttentionBiGRUNetwork + AEDRNNNetwork diff --git a/docs/api_reference/utils.rst b/docs/api_reference/utils.rst index adaacdf0f2..40dea9f67c 100644 --- a/docs/api_reference/utils.rst +++ b/docs/api_reference/utils.rst @@ -5,18 +5,13 @@ Utility functions ``aeon`` has a number of modules dedicated to utilities: -* :mod:`aeon.pipeline`, which contains generics for pipeline construction. +* :mod:`aeon.pipeline`, which contains functions for pipeline construction. +* :mod:`aeon.testing`, which contains functions for estimator testing and data generation. * :mod:`aeon.utils`, which contains generic utility functions. -Pipeline construction ---------------------- - -:mod:`aeon.pipeline` - -.. automodule:: aeon.pipeline - :no-members: - :no-inherited-members: +Pipeline +-------- .. currentmodule:: aeon.pipeline @@ -26,3 +21,274 @@ Pipeline construction make_pipeline sklearn_to_aeon + +Testing +------- + +Data Generation +^^^^^^^^^^^^^^^ + +.. currentmodule:: aeon.testing.data_generation + +.. autosummary:: + :toctree: auto_generated/ + :template: function.rst + + make_example_3d_numpy + make_example_2d_numpy_collection + make_example_3d_numpy_list + make_example_2d_numpy_list + make_example_dataframe_list + make_example_2d_dataframe_collection + make_example_multi_index_dataframe + make_example_1d_numpy + make_example_2d_numpy_series + make_example_pandas_series + make_example_dataframe_series + +Estimator Checking +^^^^^^^^^^^^^^^^^^ + +.. currentmodule:: aeon.testing.estimator_checking + +.. autosummary:: + :toctree: auto_generated/ + :template: function.rst + + check_estimator + parametrize_with_checks + +Mock Estimators +^^^^^^^^^^^^^^^ + +.. currentmodule:: aeon.testing.mock_estimators + +.. autosummary:: + :toctree: auto_generated/ + :template: class.rst + + MockAnomalyDetector + MockAnomalyDetectorRequiresFit + MockAnomalyDetectorRequiresY + MockClassifier + MockClassifierPredictProba + MockClassifierFullTags + MockClassifierParams + MockClassifierComposite + MockCluster + MockDeepClusterer + MockCollectionTransformer + MockForecaster + MockRegressor + MockRegressorFullTags + MockSegmenter + MockSegmenterRequiresY + MockSeriesTransformer + MockUnivariateSeriesTransformer + MockMultivariateSeriesTransformer + MockSeriesTransformerNoFit + MockSimilaritySearch + +Utilities +^^^^^^^^^ + +.. currentmodule:: aeon.testing.utils.deep_equals + +.. autosummary:: + :toctree: auto_generated/ + :template: function.rst + + deep_equals + +.. currentmodule:: aeon.testing.utils.output_suppression + +.. autosummary:: + :toctree: auto_generated/ + :template: function.rst + + suppress_output + +Utils +----- + +Estimator Discovery & Tags +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. currentmodule:: aeon.utils.base + +.. autosummary:: + :toctree: auto_generated/ + :template: function.rst + + get_identifier + +.. currentmodule:: aeon.utils.discovery + +.. autosummary:: + :toctree: auto_generated/ + :template: function.rst + + all_estimators + +.. currentmodule:: aeon.utils.tags + +.. autosummary:: + :toctree: auto_generated/ + :template: function.rst + + check_valid_tags + all_tags_for_estimator + + +Data Conversion & Validation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. currentmodule:: aeon.utils.conversion + +.. autosummary:: + :toctree: auto_generated/ + :template: function.rst + + resolve_equal_length_inner_type + resolve_unequal_length_inner_type + convert_collection + convert_series + +.. currentmodule:: aeon.utils.validation + +.. autosummary:: + :toctree: auto_generated/ + :template: function.rst + + is_int + is_float + is_timedelta + is_date_offset + is_timedelta_or_date_offset + check_n_jobs + check_window_length + get_n_cases + get_type + is_equal_length + has_missing + is_univariate + is_univariate_series + is_single_series + is_collection + is_tabular + is_hierarchical + +Numba +^^^^^ + +.. currentmodule:: aeon.utils.numba.general + +.. autosummary:: + :toctree: auto_generated/ + :template: function.rst + + unique_count + first_order_differences + first_order_differences_2d + first_order_differences_3d + z_normalise_series_with_mean + z_normalise_series + z_normalise_series_2d + z_normalise_series_3d + set_numba_random_seed + choice_log + get_subsequence + get_subsequence_with_mean_std + sliding_mean_std_one_series + combinations_1d + slope_derivative + slope_derivative_2d + slope_derivative_3d + generate_combinations + +.. currentmodule:: aeon.utils.numba.stats + +.. autosummary:: + :toctree: auto_generated/ + :template: function.rst + + mean + row_mean + count_mean_crossing + row_count_mean_crossing + count_above_mean + row_count_above_mean + quantile + row_quantile + median + row_median + quantile25 + row_quantile25 + quantile75 + row_quantile75 + std + std2 + row_std + numba_min + row_numba_min + numba_max + row_numba_max + slope + row_slope + iqr + row_iqr + ppv + row_ppv + fisher_score + prime_up_to + is_prime + +.. currentmodule:: aeon.utils.numba.wavelets + +.. autosummary:: + :toctree: auto_generated/ + :template: function.rst + + haar_transform + multilevel_haar_transform + +Other +^^^^^ + +.. currentmodule:: aeon.utils + +.. autosummary:: + :toctree: auto_generated/ + :template: function.rst + + show_versions + +.. currentmodule:: aeon.utils.sklearn + +.. autosummary:: + :toctree: auto_generated/ + :template: function.rst + + is_sklearn_estimator + sklearn_estimator_identifier + is_sklearn_transformer + is_sklearn_classifier + is_sklearn_regressor + is_sklearn_clusterer + +.. currentmodule:: aeon.utils.split + +.. autosummary:: + :toctree: auto_generated/ + :template: function.rst + + split_series + +.. currentmodule:: aeon.utils.windowing + +.. autosummary:: + :toctree: auto_generated/ + :template: function.rst + + sliding_windows + reverse_windowing diff --git a/docs/contributing.md b/docs/contributing.md index 009714f0f8..c07576fa6b 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -5,6 +5,13 @@ kinds of contributions, not just code. Improvements to docs, bug reports, and ta on communications or code of conduct responsibilities are all examples of valuable contributions beyond code which help make `aeon` a great package. +Please consider whether you will be able to tackle and issue or pull request before +assigning yourself to it. If the issue requires editing Python code, you should have +some experience with Python and be able to run tests. If the issue tackles the +specifics of a machine learning algorithm, some relevant knowledge of machine learning +will be required. While we want to encourage new contributors, a base level +of knowledge is required to make a meaningful contribution to certain issues. + In the following we will give a brief overview of how to contribute to `aeon`. Making contributions to open source projects takes a bit of proactivity and can be daunting at first, but members of the community are here to help and answer questions. If you get @@ -21,7 +28,9 @@ for creating a fork of `aeon`. to complete i.e. improving an algorithm, docstring or test. The [good first issue](https://github.com/aeon-toolkit/aeon/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) list may be a good place to start. 4. Post on the issue which you want to work on, so that others know you are working on -it. To assign yourself an **Issue/Pull Request**, please post a comment in the issue +it. **First ensure that the issue is not already being worked on. Look if there are any +linked PRs and search the issue number in the pull requests list.** +To assign yourself an **Issue/Pull Request**, please post a comment in the issue including '@aeon-actions-bot', the username of people to assign and the word `assign`: For example: @@ -33,9 +42,11 @@ working on. A Core Developer may suggest a different issue if the one you chose complex or somebody is already working on it. 5. Create a [pull request (PR)](https://github.com/aeon-toolkit/aeon/compare) with your changes from your fork. For help, see the [GitHub documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) -or ask in Slack. Follow the PR template and checklist. +or ask in Slack. Follow the PR template and checklist. Please make sure to include +an appropriate [title tag](contributing/issues.md). 6. A Core Developer will review your PR and may provide feedback, which you can then -address. If you are unsure about any feedback, please ask for clarification. +address. If you are unsure about any feedback, please ask for clarification. Please +be patient, as Core Developers are volunteers and may be busy with other tasks. 7. Once your PR is approved, it will be merged into the `aeon` repository. Thanks for making a contribution! Make sure you are included in the [list of contributors](contributors.md). @@ -58,7 +69,8 @@ recognise various types of contributions. Take a look at our past and current If you are a new contributor, make sure we add you to our list of contributors. All contributions are recorded in [.all-contributorsrc](https://github.com/aeon-toolkit/aeon/blob/main/.all-contributorsrc). Alternatively, you can use the [@all-contributors](https://allcontributors.org/docs/en/bot/usage) -bot to do this for you. A list of relevant tags can be found [here](https://allcontributors.org/docs/en/emoji-key). +bot to do this for you. If the contribution is related to a PR, please only create this +when the PR has merged. A list of relevant tags can be found [here](https://allcontributors.org/docs/en/emoji-key). ## Further Reading diff --git a/docs/developer_guide.md b/docs/developer_guide.md index 54bd15659f..b74a075c72 100644 --- a/docs/developer_guide.md +++ b/docs/developer_guide.md @@ -20,9 +20,6 @@ their [developer's guide](https://scikit-learn.org/stable/developers/index.html) :::{grid-item-card} :text-align: center -:::{grid-item-card} -:text-align: center - AEP's ^^^ diff --git a/docs/developer_guide/deprecation.md b/docs/developer_guide/deprecation.md index a4b294b86a..4b10d81cb2 100644 --- a/docs/developer_guide/deprecation.md +++ b/docs/developer_guide/deprecation.md @@ -20,7 +20,6 @@ Note that the deprecation policy does not necessarily apply to modules we class experimental. Currently experimental modules are: - `anomaly_detection` -- `benchmarking` - `forecasting` - `segmentation` - `similarity_search` diff --git a/docs/getting_started.md b/docs/getting_started.md index d5108ade79..36f18583cb 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -4,46 +4,50 @@ The following information is designed to get users up and running with `aeon` qu If installation is required, please see our [installation guide](installation) for installing `aeon`. -We assume basic familiarity with the [scikit-learn](https://scikit-learn.org/stable/index.html) -package. If you want help with scikit-learn you may want to view +We assume basic familiarity with the [`scikit-learn`](https://scikit-learn.org/stable/index.html) +package. If you want help with `scikit-learn` you may want to view [their getting started guides](https://scikit-learn.org/stable/getting_started.html). `aeon` is an open-source toolkit for learning from time series. It provides access to the very latest algorithms for time series machine learning, in addition to a range of classical techniques for the following learning tasks: -- **Classification**, where a collection of time series labelled with - a discrete value is used to train a model to predict unseen cases ([more details](examples/classification/classification.ipynb)). -- **Regression**, where a collection of time series labelled with - a continuous value is used to train a model to predict unseen cases ([more details](examples/regression/regression.ipynb)). -- **Clustering**, where a collection of time series without any - labels are used to train a model to label cases ([more details](examples/clustering/clustering.ipynb)). -- **Similarity search** where the goal is to evaluate the similarity -between a query time series and a collection of other longer time series ([more details](examples/similarity_search/similarity_search.ipynb)). -- **Anomaly detection** where the goal is to find values or areas of a - single time series that are not representative of the whole series. -- **Segmentation** where the goal is to split a single time series into - regions where the series are sofind areas of a time series that are not - representative of the whole series ([more details](examples/segmentation/segmentation.ipynb)). -- **Forecasting**, where the goal is to predict future values for a time - series (new module coming soon). +- [**Classification**](api_reference/classification), where a collection of time series + labelled with a discrete value is used to train a model to predict unseen cases + ([more details](examples/classification/classification.ipynb)). +- [**Regression**](api_reference/regression), where a collection of time series + labelled with a continuous value is used to train a model to predict unseen cases + ([more details](examples/regression/regression.ipynb)). +- [**Clustering**](api_reference/clustering), where a collection of time series without + any labels are used to train a model to label cases + ([more details](examples/clustering/clustering.ipynb)). +- [**Similarity search**](api_reference/similarity_search), where the goal is to evaluate + the similarity between a query time series and a collection of other longer time series + ([more details](examples/similarity_search/similarity_search.ipynb)). +- [**Anomaly detection**](api_reference/anomaly_detection), where the goal is to find + values or areas of a single time series that are not representative of the whole series. +- [**Forecasting**](api_reference/forecasting), where the goal is to predict future values + of a single time series + ([more details](examples/forecasting/forecasting.ipynb)). +- [**Segmentation**](api_reference/segmentation), where the goal is to split a single time + series into regions where the series are sofind areas of a time series that are not + representative of the whole series + ([more details](examples/segmentation/segmentation.ipynb)). `aeon` also provides core modules that are used by the modules above: -- Transformations, where a either a single series or collection is +- [**Transformations**](api_reference/transformations), where a either a single series or collection is transformed into a different representation or domain. ([more details](examples/transformations/transformations.ipynb)). -- Distances, which measure the dissimilarity between two time series or +- [**Distances**](api_reference/distances), which measure the dissimilarity between two time series or collections of series and include functions to align series ([more details](examples/distances/distances.ipynb)). -- Networks, provides core models for deep learning for all time series tasks ([more - details](examples/networks/deep_learning.ipynb)). +- [**Networks**](api_reference/networks), provides core models for deep learning for all time series tasks +- ([more details](examples/networks/deep_learning.ipynb)). -There are dedicated notebooks going into more detail for each of these modules -(linked above). This guide is meant to give you the briefest of -introductions to the main concepts and +There are dedicated notebooks going into more detail for each of these modules. This +guide is meant to give you the briefest of introductions to the main concepts and code for each task to get started. For more information on the variety of estimators available for each task, see the links above, the [API](api_reference) and -[examples](https://www.aeon-toolkit.org/en/latest/examples.html) -pages. +[examples](https://www.aeon-toolkit.org/en/latest/examples.html) pages. ## A Single Time Series @@ -51,8 +55,8 @@ A time series is a series of real valued data assumed to be ordered. A univariat time series has a single value at each time point. For example, the heartbeat ECG reading from a single sensor or the number of passengers using an airline per month would form a univariate series. Single time series are stored -by default in a numpy array (algorithms use numpy arrays internally whenever possible). -We can also handle `pd.Series` and `pd.DataFrame` objects, but these are simply +by default in a `np.ndarray` (which we try to use internally whenever possible). +We can also handle `pd.Series` and `pd.DataFrame` objects as inputs, but these may be converted to `np.ndarray` internally. The airline series is a classic example of a univariate series from the forecasting domain. The series is the monthly totals of international airline passengers, 1949 to 1960, in thousands. @@ -60,7 +64,7 @@ international airline passengers, 1949 to 1960, in thousands. ```{code-block} python >>> from aeon.datasets import load_airline >>> y = load_airline() # load an example univariate series as an array ->>> y[:5] +>>> y[:5] # first five time points 606.0 508.0 461.0 @@ -69,17 +73,18 @@ international airline passengers, 1949 to 1960, in thousands. ``` A multivariate time series is made up of multiple series or channels, where each -observation is a vector of related recordings in the same time index. An examples +observation is a vector of related recordings in the same time index. An example would be a motion trace from a smartwatch with at least three dimensions (X,Y,Z co-ordinates), or multiple financial statistics recorded over time. Single -multivariate series input typically -follows the shape `(n_channels, n_timepoints)` when stored in numpy arrays -(sometimes called wide format). +multivariate series input typically follows the shape `(n_channels, n_timepoints)` by +default. Algorithms may have an `axis` parameter to change this, where `axis=1` assumes +the default shape and is the default setting, and `axis=0` assumes the shape +`(n_timepoints, n_channels)`. ```{code-block} python >>> from aeon.datasets import load_uschange >>> data = load_uschange() # load an example multivariate series ->>> data[:,:5] +>>> data[:,:5] # all channels, first five time points [[ 0.61598622 0.46037569 0.87679142 -0.27424514 1.89737076] [ 0.97226104 1.16908472 1.55327055 -0.25527238 1.98715363] [-2.45270031 -0.55152509 -0.35870786 -2.18545486 1.90973412] @@ -94,12 +99,25 @@ the US Change data loaded above has five channels and 187 time points. For more details on our provided datasets and on how to load data into aeon compatible data structures, see our [datasets](examples/datasets/datasets.ipynb) notebooks. -## Single series modules +## Single Series Modules + +Different `aeon` modules work with individual series or collections of series. +Estimators in the `anomaly detection`, `forecasting` and `segmentation` modules use +single series input (they inherit from `BaseSeriesEstimator`). The functions in +`distances` take two series as arguments. + +### Anomaly Detection + +Anomaly detection (AD) is the process of identifying observations that are significantly +different from the rest of the data. More details to follow soon, once we have +written the notebook. -Different `aeon` module work with individual series or collections of series. Estimators -in the `anomaly detection` and `segmentation` modules use single -series input (they inherit from `BaseSeriesEstimator`). The functions in `distances` -take two series as arguments. +```{code-block} python +>>> from aeon.datasets import load_airline +>>> from aeon.anomaly_detection import STOMP +>>> stomp = STOMP(window_size=200) +>>> scores = est.fit_predict(X) # Get the anomaly scores +``` ### Segmentation @@ -108,9 +126,8 @@ segments or regions that are dissimilar to each other. This could, for example, be the problem of splitting the motion trace from a smartwatch into different activities such as walking, running, and sitting. It is closely related to the field of change point detection, which is a term used more in the statistics -literature. Full information is available in the [segmentation notebooks](Segmentation.ipynb). +literature. -The `aeon` ```{code-block} python >>> from aeon.datasets import load_airline >>> from aeon.segmentation import ClaSPSegmenter @@ -122,6 +139,7 @@ The `aeon` ``` ### Distances + Distances between time series is a primitive operation in very many time series tasks. We have an extensive set of distance functions in the `aeon.distances` module, all optimised using numba. They all work with multivariate and unequal length series. @@ -129,40 +147,18 @@ all optimised using numba. They all work with multivariate and unequal length se ```{code-block} python >>> from aeon.datasets import load_japanese_vowels >>> from aeon.distances import dtw_distance ->>> data = load_japanese_vowels() # load an example multivariate series +>>> data = load_japanese_vowels() # load an example multivariate series collection >>> dtw_distance(data[0], data[1]) # calculate the dtw distance 14.416269807978 ``` -### Anomaly Detection - -Anomaly detection (AD) is the process of identifying observations that are significantly -different from the rest of the data. More details to follow soon, once we have -written the notebook. - -```{code-block} python ->>> from aeon.datasets import load_airline ->>> from aeon.anomaly_detection import STOMP ->>> stomp = STOMP(window_size=200) ->>> scores = est.fit_predict(X) # Get the anomaly scores -``` - - - - -### Forecasting - -A new module for time series forecasting (TSF) is coming soon, we are relaunching our -forecasting module. - - ## Collections of Time Series -The estimators in the `classification`, -`regression` and `clustering` modules learn from collections of time -series (they inherit from the class `BaseCollectionEstimator`). Collections of -time series will often be accompanied by an array of target variables for supervised -learning. The module `similarity_search` also works with collections of time series. +The default storage for collections of time series is a 3D `np.ndarray`. +If `n_timepoints` varies between cases, we store a collection in a `list` of +`np.ndarray` arrays, each with the same number of channels. We do not have the +capability to use collections of time series with varying numbers of channels. +We also assume series length is always the same for all channels of a single series. ```{code-block} python >>> from aeon.datasets import load_italy_power_demand @@ -179,28 +175,17 @@ learning. The module `similarity_search` also works with collections of time ser ['1' '1' '2' '2' '1'] ``` -We use the terms case and instance interchangably when referring to a single time series -contained in a collection. The size of a collection of time series is referred to as -`n_cases` in code. Collections have the shape ` -(n_cases, n_channels, n_timepoints)` if the series are equal length. We -recommend storing collections in 3D numpy arrays even if each time series is univariate (i.e. -`n_channels == 1`). Collection estimators will work with 2D input of shape `(n_cases, -n_timepoints)` as you would -expect from `scikit-learn`, but it is possible to confuse a collection of -univariate series of shape `(n_cases, n_timepoints)` with a single multivariate -series of shape `(n_channels, n_timepoints)`. This potential confusion is one reason -we make the distinction between series and collection estimators. - -If `n_timepoints` varies between cases, we store a collection in a `list` of 2D numpy -arrays, each with the same number of channels. We do not have the capability to use -collections of time series with varying numbers of channels. We also assume series -length is always the same for all channels of a single series. +We use the terms case and instance interchangeably when referring to a single time +series contained in a collection. The size of a time series collection is referred to as +`n_cases` in code. Collections have the shape `(n_cases, n_channels, n_timepoints)`. -Collection estimators closely follow the `scikit-learn` estimator interface, using -`fit`, `predict`, `transform`, `predict_proba`, `fit_predict` and `fit_transform` -where appropriate. They are also designed to work directly with `scikit-learn` -functionality for e.g. model evaluation, parameter searching and pipelines where -appropriate. +We recommend storing collections in a 3D `np.ndarray` even if each time series is +univariate (i.e. `n_channels == 1`). Collection estimators will work with 2D input of +shape `(n_cases, n_timepoints)` as you would expect from `scikit-learn`, but it is +possible to confuse a collection of univariate series of shape `(n_cases, n_timepoints)` +with a single multivariate series of shape `(n_channels, n_timepoints)`. This potential +confusion is one reason we make the distinction between series and collection +estimators. ```{code-block} python >>>from aeon.datasets import load_basic_motions, load_plaid, load_japanese_vowels @@ -220,20 +205,30 @@ appropriate. >>> X4[0].shape (12, 20) ``` + ## Collection based modules +The estimators in the `classification`, `regression` and `clustering` modules learn +from collections of time series (they inherit from the class +`BaseCollectionEstimator`). Collections of time series will often be accompanied by an +array of target variables for supervised learning. The module `similarity_search` also +works with collections of time series. + +Collection estimators closely follow the `scikit-learn` estimator interface, using +`fit`, `predict`, `transform`, `predict_proba`, `fit_predict` and `fit_transform` +where appropriate. They are also designed to work directly with `scikit-learn` +functionality for e.g. model evaluation, parameter searching and pipelines where +appropriate. + ### Classification Time series classification (TSC) involves training a model on a labelled collection of time series. The labels, referred to as `y` in code, should be a `numpy` array of -type `float`, `int` or `str`. Internally the labels are converted to `int` for use -in a training algorithm. +type `int` or `str`. The classification estimator interface should be familiar if you have worked with `scikit-learn`. In this example we fit a [KNeighborsTimeSeriesClassifier](classification.distance_based.KNeighborsTimeSeriesClassifier) -with dynamic time warping (dtw) on our example data. - - +with dynamic time warping (DTW) on our example data. ```{code-block} python >>> import numpy as np @@ -254,9 +249,7 @@ KNeighborsTimeSeriesClassifier() Once the classifier has been fit using the training data and class labels, we can predict the labels for new cases. Like `scikit-learn`, `predict_proba` methods are available to predict class probabilities and a `score` method is present to -calculate accuracy on new data. Explore the wide range of -algorithms available in `aeon`, including the very latest state-of-the-art, in the -[classification notebooks](examples/classification/classification.ipynb). +calculate accuracy on new data. ### Regression @@ -270,12 +263,8 @@ Time series regression is a term commonly used in forecasting when used in conjunction with a sliding window. However, the term also includes "time series extrinsic regression" where the target variable is not future values but some external variable. - In the following example we use a [KNeighborsTimeSeriesRegressor](regression.distance_based.KNeighborsTimeSeriesRegressor) on an example time series regression problem called [Covid3Month](https://zenodo.org/record/3902690). -More info in our [regression notebook](examples/regression/regression.ipynb)). - - ```{code-block} python >>> from aeon.regression.distance_based import KNeighborsTimeSeriesRegressor @@ -298,9 +287,7 @@ KNeighborsTimeSeriesRegressor() Like classification and regression, time series clustering (TSCL) aims to follow the `scikit-learn` interface where possible. The same input data format is used as in the TSC and TSER modules. This example fits a [TimeSeriesKMeans](clustering._k_means.TimeSeriesKMeans) -clusterer on the -[ArrowHead](http://www.timeseriesclassification.com/description.php?Dataset=ArrowHead) -dataset. +clusterer on the [ArrowHead](http://www.timeseriesclassification.com/description.php?Dataset=ArrowHead) dataset. ```{code-block} python >>> from aeon.clustering import TimeSeriesKMeans @@ -318,8 +305,7 @@ TimeSeriesKMeans(n_clusters=3) After calling `fit`, the `labels_` attribute contains the cluster labels for each time series. The `predict` method can be used to predict the cluster labels for -new data. See our clustering notebook for [more details](examples/clustering/clustering.ipynb). - +new data. ### Similarity Search @@ -335,9 +321,16 @@ to this format. This collection, asked for the fit method, is stored as a database. It will be used in the predict method, which expects a single 2D time series as input -(n_channels, query_length). This 2D time series will be used as a query to search for in the 3D database. +(n_channels, query_length). This 2D time series will be used as a query to search for in +the 3D database. -The result of the predict method will then depends on wheter you use the [QuerySearch](similarity_search.query_search.QuerySearch) and the [SeriesSearch](similarity_search.series_search.SeriesSearch) estimator. In [QuerySearch](similarity_search.query_search.QuerySearch), the 2D series is a subsequence for which we want to indentify the best (or worst !) matches in the 3D database. For [SeriesSearch](similarity_search.series_search.SeriesSearch), we require a `length` parmater, and we will search for the best matches of all subsequences of size `length` in the 2D series inside the 3D database. By default, these estimators will use the Euclidean (or squared Euclidean) distance, but more distance will be added in the future. +The result of the predict method will then depends on wheter you use the [QuerySearch](similarity_search.query_search.QuerySearch) +and the [SeriesSearch](similarity_search.series_search.SeriesSearch) estimator. In [QuerySearch](similarity_search.query_search.QuerySearch), the 2D series is a subsequence +for which we want to indentify the best (or worst !) matches in the 3D database. +For [SeriesSearch](similarity_search.series_search.SeriesSearch), we require a `length` parmater, and we will search for the best +matches of all subsequences of size `length` in the 2D series inside the 3D database. +By default, these estimators will use the Euclidean (or squared Euclidean) distance, +but more distance will be added in the future. ```{code-block} python >>> import numpy as np @@ -359,7 +352,7 @@ to the subsequence `X[id_sample, :, id_timestamps:id_timestamp + q.shape[0]]`. ## Transformers We split transformers into two categories: those that transform single time series - and those that transform a collection. +and those that transform a collection. ### Transformers for Single Time Series @@ -381,11 +374,10 @@ class to extract the autocorrelation terms of a time series. [0.96019465 0.89567531 0.83739477 0.7977347 0.78594315] ``` - ### Transformers for Collections of Time Series The `aeon.transformations.collections` module contains a range of transformers for -collections of time series. By default these do not allow for single series input, +collections of time series. These do not allow for single series input, treat 2D input types as a collection of univariate series, and have no restrictions on the datatype of output. @@ -437,7 +429,6 @@ series and process unequal length collections. 3.48004859 3.91447337 3.19663426 0. 0. 0. ]]] ``` - ## Pipelines for aeon estimators Like `scikit-learn`, `aeon` provides pipeline classes which can be used to chain diff --git a/docs/index.md b/docs/index.md index e36cdfc8f9..974d181060 100644 --- a/docs/index.md +++ b/docs/index.md @@ -110,6 +110,25 @@ Anomaly Detection ::: +:::{grid-item-card} +:img-top: examples/forecasting/img/forecasting.png +:class-img-top: aeon-card-image +:text-align: center + +Get started with forecasting + ++++ + +```{button-ref} /examples/forecasting/forecasting.ipynb +:color: primary +:click-parent: +:expand: + +Forecasting +``` + +::: + :::{grid-item-card} :img-top: examples/segmentation/img/segmentation.png :class-img-top: aeon-card-image @@ -253,6 +272,7 @@ is relaxed, so it is suggested that you integrate these modules with care. The c experimental modules are: - `anomaly_detection` +- `forecasting` - `segmentation` - `similarity_search` - `visualisation` diff --git a/examples/distances/distances.ipynb b/examples/distances/distances.ipynb index fbd557ec47..6e441e5533 100644 --- a/examples/distances/distances.ipynb +++ b/examples/distances/distances.ipynb @@ -192,7 +192,7 @@ "\n", "d1 = euclidean_distance(first, second)\n", "d2 = euclidean_distance(first, third)\n", - "d3 = distance(second, third, metric=\"euclidean\")\n", + "d3 = distance(second, third, measure=\"euclidean\")\n", "print(d1, \",\", d2, \",\", d3)" ] }, @@ -568,7 +568,7 @@ "y = np.array([[2, 3, 4, 5, 6, 7]])\n", "p, d = dtw_alignment_path(x, y)\n", "print(\"path =\", p, \" distance = \", d)\n", - "p, d = alignment_path(x, y, metric=\"dtw\")\n", + "p, d = alignment_path(x, y, measure=\"dtw\")\n", "print(\"path =\", p, \" distance = \", d)" ] }, diff --git a/examples/forecasting/forecasting.ipynb b/examples/forecasting/forecasting.ipynb index e17b6667dc..0e0b4ac72f 100644 --- a/examples/forecasting/forecasting.ipynb +++ b/examples/forecasting/forecasting.ipynb @@ -109,7 +109,7 @@ { "cell_type": "code", "source": [ - "from aeon.utils import SERIES_DATA_TYPES\n", + "from aeon.utils.data_types import SERIES_DATA_TYPES\n", "\n", "print(\" Possible data structures for input to forecaster \", SERIES_DATA_TYPES)\n", "print(\"\\n Tags for BaseForecaster: \", BaseForecaster.get_class_tags())" diff --git a/examples/forecasting/img/forecasting.png b/examples/forecasting/img/forecasting.png new file mode 100644 index 0000000000..c9316dbe5a Binary files /dev/null and b/examples/forecasting/img/forecasting.png differ diff --git a/examples/networks/deep_learning.ipynb b/examples/networks/deep_learning.ipynb index 9e06feabbb..7cf092f7f3 100644 --- a/examples/networks/deep_learning.ipynb +++ b/examples/networks/deep_learning.ipynb @@ -64,8 +64,8 @@ "cell_type": "code", "metadata": { "ExecuteTime": { - "end_time": "2024-11-21T11:14:08.477299Z", - "start_time": "2024-11-21T11:14:08.433390Z" + "end_time": "2024-11-25T16:48:00.794715Z", + "start_time": "2024-11-25T16:48:00.780244Z" } }, "source": [ @@ -97,7 +97,7 @@ "from aeon.regression.deep_learning import InceptionTimeRegressor" ], "outputs": [], - "execution_count": 7 + "execution_count": 12 }, { "attachments": {}, @@ -116,17 +116,18 @@ "cell_type": "code", "metadata": { "ExecuteTime": { - "end_time": "2024-11-21T11:12:15.141664Z", - "start_time": "2024-11-21T11:12:02.084792Z" + "end_time": "2024-11-25T16:48:20.910216Z", + "start_time": "2024-11-25T16:48:02.721649Z" } }, "source": [ - "xtrain, ytrain = load_classification(name=\"ArrowHead\", split=\"train\")\n", - "xtest, ytest = load_classification(name=\"ArrowHead\", split=\"test\")\n", - "\n", - "inc = InceptionTimeClassifier(n_classifiers=5, use_custom_filters=False, n_epochs=3)\n", + "xtrain, ytrain = load_classification(name=\"GunPoint\", split=\"train\")\n", + "xtest, ytest = load_classification(name=\"GunPoint\", split=\"test\")\n", + "xtrain = xtrain[:10, :, :]\n", + "ytrain = ytrain[:10]\n", + "inc = InceptionTimeClassifier(n_classifiers=2, use_custom_filters=False, n_epochs=2)\n", "inc.fit(X=xtrain, y=ytrain)\n", - "ypred = inc.predict(X=xtest)\n", + "ypred = inc.predict(X=xtest[0:5, :, :])\n", "\n", "print(\"Predictions: \", ypred[0:5])\n", "print(\"Ground Truth: \", ytest[0:5])" @@ -136,17 +137,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001B[1m3/3\u001B[0m \u001B[32m━━━━━━━━━━━━━━━━━━━━\u001B[0m\u001B[37m\u001B[0m \u001B[1m0s\u001B[0m 72ms/step\n", - "\u001B[1m3/3\u001B[0m \u001B[32m━━━━━━━━━━━━━━━━━━━━\u001B[0m\u001B[37m\u001B[0m \u001B[1m0s\u001B[0m 78ms/step\n", - "\u001B[1m3/3\u001B[0m \u001B[32m━━━━━━━━━━━━━━━━━━━━\u001B[0m\u001B[37m\u001B[0m \u001B[1m0s\u001B[0m 72ms/step\n", - "\u001B[1m3/3\u001B[0m \u001B[32m━━━━━━━━━━━━━━━━━━━━\u001B[0m\u001B[37m\u001B[0m \u001B[1m0s\u001B[0m 74ms/step\n", - "\u001B[1m3/3\u001B[0m \u001B[32m━━━━━━━━━━━━━━━━━━━━\u001B[0m\u001B[37m\u001B[0m \u001B[1m0s\u001B[0m 82ms/step\n", + "\u001b[1m1/1\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 358ms/step\n", + "\u001b[1m1/1\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 386ms/step\n", "Predictions: ['2' '2' '2' '2' '2']\n", - "Ground Truth: ['0' '0' '0' '0' '0']\n" + "Ground Truth: ['1' '2' '2' '1' '1']\n" ] } ], - "execution_count": 2 + "execution_count": 13 }, { "attachments": {}, @@ -170,17 +168,14 @@ "cell_type": "code", "metadata": { "ExecuteTime": { - "end_time": "2024-11-21T11:12:30.252190Z", - "start_time": "2024-11-21T11:12:15.222773Z" + "end_time": "2024-11-25T16:48:52.055955Z", + "start_time": "2024-11-25T16:48:31.172431Z" } }, "source": [ - "xtrain, ytrain = load_classification(name=\"ArrowHead\", split=\"train\")\n", - "xtest, ytest = load_classification(name=\"ArrowHead\", split=\"test\")\n", - "\n", - "inc = InceptionTimeClassifier(n_classifiers=5, use_custom_filters=True, n_epochs=3)\n", + "inc = InceptionTimeClassifier(n_classifiers=2, use_custom_filters=True, n_epochs=2)\n", "inc.fit(X=xtrain, y=ytrain)\n", - "ypred = inc.predict(X=xtest)\n", + "ypred = inc.predict(X=xtest[0:5, :, :])\n", "\n", "print(\"Predictions: \", ypred[0:5])\n", "print(\"Ground Truth: \", ytest[0:5])" @@ -190,17 +185,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001B[1m3/3\u001B[0m \u001B[32m━━━━━━━━━━━━━━━━━━━━\u001B[0m\u001B[37m\u001B[0m \u001B[1m0s\u001B[0m 94ms/step\n", - "\u001B[1m3/3\u001B[0m \u001B[32m━━━━━━━━━━━━━━━━━━━━\u001B[0m\u001B[37m\u001B[0m \u001B[1m0s\u001B[0m 91ms/step\n", - "\u001B[1m3/3\u001B[0m \u001B[32m━━━━━━━━━━━━━━━━━━━━\u001B[0m\u001B[37m\u001B[0m \u001B[1m0s\u001B[0m 94ms/step\n", - "\u001B[1m3/3\u001B[0m \u001B[32m━━━━━━━━━━━━━━━━━━━━\u001B[0m\u001B[37m\u001B[0m \u001B[1m0s\u001B[0m 97ms/step\n", - "\u001B[1m3/3\u001B[0m \u001B[32m━━━━━━━━━━━━━━━━━━━━\u001B[0m\u001B[37m\u001B[0m \u001B[1m0s\u001B[0m 96ms/step\n", - "Predictions: ['0' '0' '0' '0' '0']\n", - "Ground Truth: ['0' '0' '0' '0' '0']\n" + "\u001b[1m1/1\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 557ms/step\n", + "\u001b[1m1/1\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 1s/step\n", + "Predictions: ['2' '2' '2' '2' '2']\n", + "Ground Truth: ['1' '2' '2' '1' '1']\n" ] } ], - "execution_count": 3 + "execution_count": 14 }, { "cell_type": "markdown", @@ -217,37 +209,36 @@ "cell_type": "code", "metadata": { "ExecuteTime": { - "end_time": "2024-11-21T11:12:43.183566Z", - "start_time": "2024-11-21T11:12:30.257417Z" + "end_time": "2024-11-25T16:49:32.976474Z", + "start_time": "2024-11-25T16:49:14.568788Z" } }, "source": [ - "xtrain, ytrain = load_regression(name=\"Covid3Month\", split=\"train\")\n", - "xtest, ytest = load_regression(name=\"Covid3Month\", split=\"test\")\n", + "x_train, y_train = load_regression(name=\"Covid3Month\", split=\"train\")\n", + "x_test, y_test = load_regression(name=\"Covid3Month\", split=\"test\")\n", + "x_train = x_train[:10, :, :]\n", + "y_train = y_train[:10]\n", "\n", - "inc = InceptionTimeRegressor(n_regressors=5, n_epochs=1, use_custom_filters=False)\n", - "inc.fit(X=xtrain, y=ytrain)\n", - "ypred = inc.predict(X=xtest)\n", + "inc = InceptionTimeRegressor(n_regressors=2, n_epochs=1, use_custom_filters=False)\n", + "inc.fit(X=x_train, y=y_train)\n", + "ypred = inc.predict(X=x_test[0:5, :, :])\n", "\n", "print(\"Predictions: \", ypred[0:5])\n", - "print(\"Ground Truth: \", ytest[0:5])" + "print(\"Ground Truth: \", y_train[0:5])" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\u001B[1m1/1\u001B[0m \u001B[32m━━━━━━━━━━━━━━━━━━━━\u001B[0m\u001B[37m\u001B[0m \u001B[1m0s\u001B[0m 107ms/step\n", - "\u001B[1m1/1\u001B[0m \u001B[32m━━━━━━━━━━━━━━━━━━━━\u001B[0m\u001B[37m\u001B[0m \u001B[1m0s\u001B[0m 109ms/step\n", - "\u001B[1m1/1\u001B[0m \u001B[32m━━━━━━━━━━━━━━━━━━━━\u001B[0m\u001B[37m\u001B[0m \u001B[1m0s\u001B[0m 112ms/step\n", - "\u001B[1m1/1\u001B[0m \u001B[32m━━━━━━━━━━━━━━━━━━━━\u001B[0m\u001B[37m\u001B[0m \u001B[1m0s\u001B[0m 114ms/step\n", - "\u001B[1m1/1\u001B[0m \u001B[32m━━━━━━━━━━━━━━━━━━━━\u001B[0m\u001B[37m\u001B[0m \u001B[1m0s\u001B[0m 108ms/step\n", - "Predictions: [-0.4258549 -0.0387525 -0.01732254 -0.60533425 -4.51287463]\n", - "Ground Truth: [0.0118838 0.00379507 0.08298755 0.04510921 0.12783075]\n" + "\u001b[1m1/1\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 374ms/step\n", + "\u001b[1m1/1\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 368ms/step\n", + "Predictions: [ -3.88514614 -0.3810918 -0.2344005 -5.31711912 -37.39011002]\n", + "Ground Truth: [0. 0.07758621 0. 0. 0.15400309]\n" ] } ], - "execution_count": 4 + "execution_count": 15 }, { "attachments": {}, @@ -274,18 +265,15 @@ "cell_type": "code", "metadata": { "ExecuteTime": { - "end_time": "2024-11-21T11:14:19.577898Z", - "start_time": "2024-11-21T11:14:16.120345Z" + "end_time": "2024-11-25T16:49:45.025079Z", + "start_time": "2024-11-25T16:49:38.455222Z" } }, "source": [ - "xtrain, _ = load_classification(name=\"ArrowHead\", split=\"train\")\n", - "xtest, ytest = load_classification(name=\"ArrowHead\", split=\"test\")\n", - "\n", "aefcn = AEFCNClusterer(\n", " temporal_latent_space=False,\n", " estimator=KMeans(n_clusters=3),\n", - " n_epochs=10,\n", + " n_epochs=3,\n", ")\n", "\n", "aefcn.fit(X=xtrain)\n", @@ -298,14 +286,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001B[1m2/2\u001B[0m \u001B[32m━━━━━━━━━━━━━━━━━━━━\u001B[0m\u001B[37m\u001B[0m \u001B[1m0s\u001B[0m 27ms/step\n", - "\u001B[1m6/6\u001B[0m \u001B[32m━━━━━━━━━━━━━━━━━━━━\u001B[0m\u001B[37m\u001B[0m \u001B[1m0s\u001B[0m 8ms/step \n", - "Predictions: [2 0 2 2 2]\n", - "Ground Truth: ['0' '0' '0' '0' '0']\n" + "\u001b[1m1/1\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 99ms/step\n", + "\u001b[1m5/5\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 14ms/step\n", + "Predictions: [1 0 1 1 0]\n", + "Ground Truth: ['1' '2' '2' '1' '1']\n" ] } ], - "execution_count": 8 + "execution_count": 16 }, { "attachments": {}, @@ -332,42 +320,13 @@ }, { "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "11/11 [==============================] - 0s 29ms/step\n", - "11/11 [==============================] - 0s 29ms/step\n", - "['1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", - " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", - " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", - " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", - " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", - " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", - " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", - " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", - " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", - " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1']\n", - "['1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", - " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", - " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", - " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", - " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", - " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", - " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", - " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", - " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", - " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1']\n" - ] + "metadata": { + "ExecuteTime": { + "end_time": "2024-11-25T16:49:51.216960Z", + "start_time": "2024-11-25T16:49:47.246635Z" } - ], + }, "source": [ - "xtrain, ytrain = load_classification(name=\"ArrowHead\", split=\"train\")\n", - "xtest, ytest = load_classification(name=\"ArrowHead\", split=\"test\")\n", - "\n", "fcn = FCNClassifier(\n", " save_best_model=True,\n", " save_last_model=True,\n", @@ -392,7 +351,36 @@ "\n", "os.remove(\"./best_fcn.keras\")\n", "os.remove(\"./last_fcn.keras\")" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1m10/10\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 15ms/step\n", + "\u001b[1m10/10\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 17ms/step\n", + "['1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", + " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", + " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", + " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", + " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", + " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", + " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", + " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", + " '1' '1' '1' '1' '1' '1']\n", + "['1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", + " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", + " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", + " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", + " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", + " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", + " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", + " '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1'\n", + " '1' '1' '1' '1' '1' '1']\n" + ] + } + ], + "execution_count": 17 }, { "attachments": {}, @@ -409,24 +397,23 @@ }, { "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-11-25T16:50:17.048341Z", + "start_time": "2024-11-25T16:50:17.030069Z" + } + }, "source": [ "# define self-supervised space dimension\n", "\n", "n_dim = 16\n", - "\n", - "# load the data\n", - "\n", - "xtrain, ytrain = load_classification(name=\"ArrowHead\", split=\"train\")\n", - "xtest, ytest = load_classification(name=\"ArrowHead\", split=\"test\")\n", - "\n", "# Flip axis to be handled correctly in tensorflow\n", "\n", "xtrain = np.transpose(xtrain, axes=(0, 2, 1))\n", "xtest = np.transpose(xtest, axes=(0, 2, 1))" - ] + ], + "outputs": [], + "execution_count": 18 }, { "cell_type": "markdown", @@ -437,9 +424,12 @@ }, { "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-11-25T16:50:22.513036Z", + "start_time": "2024-11-25T16:50:22.499412Z" + } + }, "source": [ "def triplet_loss_function(alpha):\n", " \"\"\"Create a triplet loss function for triplet-based training.\"\"\"\n", @@ -466,7 +456,9 @@ " return loss\n", "\n", " return temp" - ] + ], + "outputs": [], + "execution_count": 19 }, { "cell_type": "markdown", @@ -477,9 +469,12 @@ }, { "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-11-25T16:50:26.420369Z", + "start_time": "2024-11-25T16:50:26.207821Z" + } + }, "source": [ "# Define the triplets input layers\n", "\n", @@ -512,7 +507,9 @@ ")\n", "\n", "SSL_model.compile(loss=triplet_loss_function(alpha=1e-5))" - ] + ], + "outputs": [], + "execution_count": 20 }, { "cell_type": "markdown", @@ -523,9 +520,12 @@ }, { "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-11-25T16:50:28.681579Z", + "start_time": "2024-11-25T16:50:28.664819Z" + } + }, "source": [ "def triplet_generation(x):\n", " \"\"\"Generate triplet samples (ref, pos, neg) for triplet loss training.\"\"\"\n", @@ -570,7 +570,9 @@ " neg[i] = w1 * nota + w2 * b2 + w2 * c2\n", "\n", " return ref, pos, neg" - ] + ], + "outputs": [], + "execution_count": 21 }, { "cell_type": "markdown", @@ -581,12 +583,17 @@ }, { "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-11-25T16:50:30.987857Z", + "start_time": "2024-11-25T16:50:30.983090Z" + } + }, "source": [ "xtrain_ref, xtrain_pos, xtrain_neg = triplet_generation(x=xtrain)" - ] + ], + "outputs": [], + "execution_count": 22 }, { "cell_type": "markdown", @@ -597,9 +604,12 @@ }, { "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-11-25T16:50:35.325071Z", + "start_time": "2024-11-25T16:50:35.314592Z" + } + }, "source": [ "reduce_lr = tf.keras.callbacks.ReduceLROnPlateau(\n", " monitor=\"loss\", factor=0.5, patience=50, min_lr=0.0001\n", @@ -612,7 +622,9 @@ ")\n", "\n", "callbacks = [reduce_lr, model_checkpoint]" - ] + ], + "outputs": [], + "execution_count": 23 }, { "cell_type": "markdown", @@ -623,25 +635,17 @@ }, { "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAiMAAAGdCAYAAADAAnMpAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAzeUlEQVR4nO3de3xU9Z3/8fckkAmXZMJFkgDhoiCEOyKXhN8KajQiS0m7i0jdBi2y1YUKS+0qPqxU3W5sEZUqcqmFVBHxCmwRpSEKKkTl2gV0WbGRYE2CIswkARLMnN8fwsCETDJnbieTvJ6Px3k8PN/5nnM+x5Nx3n7nzPnaDMMwBAAAYJEYqwsAAAAtG2EEAABYijACAAAsRRgBAACWIowAAABLEUYAAIClCCMAAMBShBEAAGCpVlYX4A+3262vvvpKCQkJstlsVpcDAAD8YBiGKioq1LVrV8XE+B7/iIow8tVXXyktLc3qMgAAQACOHj2q7t27+3w9KsJIQkKCpO9PJjEx0eJqAACAP1wul9LS0jyf475ERRg5/9VMYmIiYQQAgCjT2C0WQd3A+thjj8lms2nu3LkN9nv11VfVv39/xcfHa/Dgwdq0aVMwhwUAAM1IwGFk586dWr58uYYMGdJgvx07dmjatGmaMWOG9u7dq5ycHOXk5OjAgQOBHhoAADQjAYWRyspK3XbbbfrDH/6gDh06NNh38eLFuummm/TLX/5S6enpevTRR3XVVVfpmWeeCahgAADQvAR0z8isWbM0ceJEZWVl6T//8z8b7FtUVKR58+Z5tWVnZ2v9+vU+t6murlZ1dbVn3eVyBVImACDEDMPQd999p9raWqtLQRMQGxurVq1aBf3YDdNhZO3atdqzZ4927tzpV/+ysjIlJyd7tSUnJ6usrMznNnl5eXr44YfNlgYACKOamhqVlpbq1KlTVpeCJqRt27ZKTU1VXFxcwPswFUaOHj2qOXPmqKCgQPHx8QEftDHz58/3Gk05/9MgAIA13G63iouLFRsbq65duyouLo6HULZwhmGopqZGX3/9tYqLi9W3b98GH2zWEFNhZPfu3Tp27JiuuuoqT1ttba3ee+89PfPMM6qurlZsbKzXNikpKSovL/dqKy8vV0pKis/j2O122e12M6UBAMKopqZGbrdbaWlpatu2rdXloIlo06aNWrdurSNHjqimpibggQpTEeb666/X/v37tW/fPs9y9dVX67bbbtO+ffsuCSKSlJGRocLCQq+2goICZWRkBFQwAMA6gf6fL5qvUPxNmBoZSUhI0KBBg7za2rVrp06dOnnac3Nz1a1bN+Xl5UmS5syZo3HjxmnRokWaOHGi1q5dq127dmnFihVBFw8AAKJfyCNuSUmJSktLPeuZmZlas2aNVqxYoaFDh+q1117T+vXrLwk1AAA0db169dJTTz3ld/+tW7fKZrPp5MmTYatJkvLz85WUlBTWY4RT0I+D37p1a4PrkjRlyhRNmTIl2EMBAGDK+PHjNWzYMFMBoiE7d+5Uu3bt/O6fmZmp0tJSORyOkBy/uYqKuWnC4Xe/k0pLpW+//X557DFp4ECrqwIARJphGKqtrVWrVo1/JF522WWm9h0XF9fgDzbwvRZ7J9Lvfy899ZT0/PPSxo1SSYnVFQFAdHC7pa+/tnZxuxuv8/bbb9e2bdu0ePFi2Ww22Ww2ffHFF56vTt566y2NGDFCdrtdH3zwgT7//HNNnjxZycnJat++vUaOHKktW7Z47bPu1zQ2m03PPfecfvjDH6pt27bq27ev/vu//9vzet2vac5/nbJ582alp6erffv2uummm7xub/juu+90zz33KCkpSZ06ddJ9992n6dOnKycnx9R1Wrp0qa644grFxcWpX79+euGFFzyvGYahX//61+rRo4fsdru6du2qe+65x/P6s88+q759+yo+Pl7Jycn653/+Z1PHNqvFjox07Cj9/e8X1r/91rpaACCaHD8udelibQ3HjkmNDVIsXrxY//d//6dBgwbpkUcekfT9yMYXX3whSbr//vv1+OOP6/LLL1eHDh109OhR3XzzzfrNb34ju92u559/XpMmTdKhQ4fUo0cPn8d5+OGH9bvf/U4LFy7U008/rdtuu01HjhxRx44d6+1/6tQpPf7443rhhRcUExOjf/mXf9G9996rF198UZL029/+Vi+++KJWrVql9PR0LV68WOvXr9e1117r97+fdevWac6cOXrqqaeUlZWljRs36o477lD37t117bXX6vXXX9eTTz6ptWvXauDAgSorK9Nf//pXSdKuXbt0zz336IUXXlBmZqa+/fZbvf/++34fOyBGFHA6nYYkw+l0hmyf48YZhnRh+f3vQ7ZrAGh2Tp8+bXzyySfG6dOnjWPHvP/7acVy7Jh/dY8b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P9/45JNPjH/91381kpKSjLKyMsMwDOMnP/mJcf/993v6b9++3WjVqpXx+OOPG59++qmxYMECo3Xr1sb+/futOgW/3X333YbD4TC2bt1qlJaWepZTp055+tQ934cfftjYvHmz8fnnnxu7d+82br31ViM+Pt44ePCgFafgt1/84hfG1q1bjeLiYmP79u1GVlaW0blzZ+PYsWOGYTSv62oY3/9qoEePHsZ99913yWvRfk0rKiqMvXv3Gnv37jUkGU888YSxd+9ezy9IHnvsMSMpKcnYsGGD8T//8z/G5MmTjd69exunT5/27OO6664znn76ac96Y+97qzR0rjU1NcYPfvADo3v37sa+ffu83sPV1dWefdQ918beC1Zp6FwrKiqMe++91ygqKjKKi4uNLVu2GFdddZXRt29f48yZM559RMt1NYzG/44NwzCcTqfRtm1bY+nSpfXuI1qubbg06zBiGIbx9NNPGz169DDi4uKMUaNGGR9++KHntXHjxhnTp0/36v/KK68YV155pREXF2cMHDjQePPNNyNccWAk1busWrXK06fu+c6dO9fz7yY5Odm4+eabjT179kS+eJOmTp1qpKamGnFxcUa3bt2MqVOnGocPH/a83pyuq2EYxubNmw1JxqFDhy55Ldqv6bvvvlvv3+35c3K73cavfvUrIzk52bDb7cb1119/yb+Hnj17GgsWLPBqa+h9b5WGzrW4uNjne/jdd9/17KPuuTb2XrBKQ+d66tQp48YbbzQuu+wyo3Xr1kbPnj2NmTNnXhIqouW6Gkbjf8eGYRjLly832rRpY5w8ebLefUTLtQ0Xm2EYRliHXgAAABrQbO8ZAQAA0YEwAgAALEUYAQAAliKMAAAASxFGAACApQgjAADAUoQRAABgKcIIAACwFGEEAABYijACAAAsRRgBAACWIowAAABL/X99WhCz4otkVAAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" + "metadata": { + "ExecuteTime": { + "end_time": "2024-11-25T16:50:53.529773Z", + "start_time": "2024-11-25T16:50:44.026512Z" } - ], + }, "source": [ "history = SSL_model.fit(\n", " [xtrain_ref, xtrain_pos, xtrain_neg],\n", " np.zeros(shape=len(xtrain)),\n", - " epochs=20,\n", + " epochs=4,\n", " callbacks=callbacks,\n", " verbose=False,\n", ")\n", @@ -650,7 +654,20 @@ "plt.plot(history.history[\"loss\"], lw=3, color=\"blue\", label=\"training loss\")\n", "plt.legend()\n", "plt.show()" - ] + ], + "outputs": [ + { + "data": { + "text/plain": [ + "
" + ], + "image/png": "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UyfNnTtXv/vd7ywmQyjr2LGjOnbsWL3dt29fff311yooKNCiRYssJgus0aNH64svvtAnn3xiO0rQa+ixiuTnrI4dO2rbtm0qKSnRm2++qeHDh2v9+vX1lgsbQuKMxUUXXaTo6GgdOnTIMT906JDS09PP+j3p6ele7R8uGnOs6oqNjVX37t311Vdf+SNiSKvvcZWcnKzmzZtbShU6evfuHVGPq5ycHL3zzjtau3atMjIyzrlvpD5nnebNsaorkp6z4uLidPnll6tnz57Kzc1V165dNXXq1LPua+sxFRLFIi4uTj179tTq1aurZ263W6tXr673taWsrCzH/pK0atWqevcPF405VnVVVVWpqKhIrVu39lfMkBWpjytf2bZtW0Q8rowxysnJ0fLly7VmzRpdeuml5/2eSH1sNeZY1RXJz1lut1vl5eVn/Zq1x5RfLw31oSVLlpj4+HizcOFC849//MM88cQTJjU11RQXFxtjjBk2bJiZNGlS9f4bNmwwMTEx5o9//KPZsWOHee6550xsbKwpKiqy9SsEjLfH6re//a354IMPzNdff222bNli7r33XtOsWTOzfft2W79CwBw/ftxs3brVbN261Ugy+fn5ZuvWrWbfvn3GGGMmTZpkhg0bVr3/7t27TUJCgvnlL39pduzYYWbOnGmio6PN+++/b+tXCBhvj1VBQYFZsWKF+fLLL01RUZEZN26ciYqKMh999JGtXyFgnnzySZOSkmLWrVtnDh48WH374YcfqvfhOcujMccqUp+zJk2aZNavX2/27Nlj/v73v5tJkyYZl8tlPvzwQ2NM8DymQqZYGGPM9OnTzSWXXGLi4uJM7969zaZNm6q/1r9/fzN8+HDH/q+//rq54oorTFxcnOncubN59913A5zYHm+O1fjx46v3TUtLM7feeqspLCy0kDrwTr8lsu7t9PEZPny46d+//xnf061bNxMXF2fat29vFixYEPDcNnh7rP77v//bXHbZZaZZs2amRYsWZsCAAWbNmjV2wgfY2Y6TJMdjhecsj8Ycq0h9znrkkUdM27ZtTVxcnGnVqpW54YYbqkuFMcHzmOJj0wEAgM+ExDUWAAAgNFAsAACAz1AsAACAz1AsAACAz1AsAACAz1AsAACAz1AsAACAz1AsAACAz1AsAACAz1AsAACAz1AsAACAz1AsAACAz/x/e/fSxE7DarcAAAAASUVORK5CYII=" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 24 }, { "cell_type": "markdown", @@ -661,27 +678,12 @@ }, { "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2/2 [==============================] - 1s 10ms/step\n" - ] - }, - { - "data": { - "image/png": "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2TfffFNPPPGEli5dqm3btqm6ulpLlixRW1ubp8z69etVVVWl6dOna/PmzRo/frx27twpSXrmmWd08OBB3X777Xr//fd13XXX6e2335YkVVdXe7bnnnvOvxfZC3RRAQHgqHEovyxfx5znv9El25JVklOi3LTA9MEDCKLu3uw5ADeFHjdunAYNGqTy8nLde++9XZbdsWOHpk+frocfftiz78JWmA5TpkzRlClTVFRUpIyMDJWWluo73/mOJGn8+PEaP368HnvsMc2fP1/r16/X//7f/1tjx47174X1ES04gJ85ahzK25LnFW4kqcHZoLwteXLUBHY2BYAgyMyUkpPdY218sViklBR3OT+LjY3VU089peXLl+uNN97Q4cOHtXPnTv3617++pOy4ceO0d+9evfvuu/rLX/6ip59+Wnv27PE8X1tbq6KiIlVVVeno0aPatm2bPvnkE6Wlpemrr77SsmXLVFFRoaNHj2rHjh3as2eP1xgdXzpadU6dOqXPP/9c1dXV+vjjj/3+e7gYLTiAH7naXcovy5ehSwcaGjJkkUUFZQWaPWG2rFH+HWgIIIisVqmkxD1bymLxHmzcEXqKi/0+wLjD008/rQEDBmjlypX67LPPZLfbfY6DeeCBB3TgwAHNmzdPFotF8+fP18MPP6zf//73kqTBgwfr0KFD2rhxo06cOCG73a5HHnlEDzzwgM6dO6cTJ05o4cKFam5u1vDhw5Wbm+s1aNmXKVOmeP573759Ki0t1TXXXHPJAGh/42ab3GwTflRxpEKzNs66bLnti7ZrZurMwFcIQLf55WabDod7NtWFA45TUtzhJkBTxM3GXzfbpAUH8KPG1u4NIOxuOQBhJjfXPRW8stI9oNhud3dLBajlBp0j4AB+ZB/avQGE3S0HIAxZrdLMmcGuRcRjkDHgR5mjMpVsS5ZFvgcaWmRRii1FmaP8P9AQAHAeAQfwI2uUVSU57vu6XBxyOh4X5xQzwBgAAoyAA/hZblquts7dqpG2kV77k23J2jp3K+vgAEA/YAwOEAC5abmaPWG2Kusq1djaKPtQuzJHZdJyAwD9hIADBIg1yspUcAAIErqoAACA6RBwAACA6RBwAAAwqSNHjshisai6ujrYVel3BBwAABAQDodDf/u3f6sRI0bIZrMpIyND7777br+8NgEHAAA/crW7VHGkQps+2qSKIxVytbuCXaWg+eCDD/S3f/u3euedd7Rv3z7NmjVLd955pw4cOBDw1ybgAADgJ44ah1JLUjVr4yzd47hHszbOUmpJqhw1joC+bnt7u1544QWNHTtWMTExGjVqlH7yk59cUs7lcmnp0qUaPXq0Bg0apAkTJqikpMSrTEVFhaZNm6YrrrhC8fHx+u53v6ujR49Kkv7whz9o1qxZGjp0qGw2m6ZOnaq9e/d2Wq/i4mItX75cN998s8aNG6ef/vSnGjdunP7t3/7Nv78AH5gmDgCAHzhqHMrbkidDhtf+BmeD8rbkBXShz6KiIr322mt68cUXNWPGDDU2NurQoUOXlGtvb1dycrLeeustXXXVVfrwww91//33y263a+7cuTp37pzmzJmj++67T5s2bVJbW5t2794ti8W9EvuCBQs0ZcoUvfzyy7JaraqurtbAgQO7Xc/29na1trZq2LBhfrv2zhBwAADoI1e7S/ll+ZeEG0kyZMgiiwrKCjR7wmy/L/jZ2tqqkpISvfTSS1q0aJEk6dprr9WMGTN05MgRr7IDBw7Us88+63k8evRoVVVVacuWLZo7d66cTqdaWlp0xx136Nprr5UkpaWlecrX1dXpySef1MSJEyVJ48aN61Fd165dq1OnTmnu3Lm9udQeoYsKAIA+qqyr1DHnsU6fN2So3lmvyrpKv792TU2Nzp49q1tvvbVb5detW6epU6dqxIgRGjJkiF599VXV1dVJkoYNG6bFixcrOztbd955p0pKStTY2Og5trCwUPfee6+ysrK0Zs0aHT582PPckCFDPNuDDz54yeuWlpbq2Wef1ZYtW3T11Vf38aovj4ADAEAfNbY2Xr5QD8r1xKBBg7pd9s0339QTTzyhpUuXatu2baqurtaSJUvU1tbmKbN+/XpVVVVp+vTp2rx5s8aPH6+dO3dKkp555hkdPHhQt99+u95//31dd911evvttyVJ1dXVnu2555675HXvvfdebdmyRVlZWX646ssj4AAA0Ef2oXa/luuJcePGadCgQSovL79s2R07dmj69Ol6+OGHNWXKFI0dO9arFabDlClTVFRUpA8//FDXX3+9SktLPc+NHz9ejz32mLZt26bc3FytX79ekjR27FjPdmELzaZNm7RkyRJt2rRJt99+ux+uuHsIOAAA9FHmqEwl25JlkcXn8xZZlGJLUeaoTL+/dmxsrJ56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LNDBgwf13nvv6Xe/+50++OAD3X///Zc97r777lNjY6Nne+GFF/qhtpFn8+bNKiws1KpVq7R//35NmjRJ2dnZOn78uM/yH374oebPn6+lS5fqwIEDmjNnjubMmaM//elP/VzzyNTT90tyr5J74d/S0aNH+7HGkev06dOaNGmS1q1b163ytbW1uv322zVr1ixVV1eroKBA9957r959990A1xR+Z8D0Pv74Y0OSsWfPHs++3//+94bFYjEaGho6Pe6WW24x8vPz+6GGmDZtmvHII494HrtcLiMpKclYvXq1z/Jz5841br/9dq996enpxgMPPBDQesKtp+/X+vXrjbi4uH6qHTojyXj77be7LLN8+XLjW9/6lte+efPmGdnZ2QGsGQKBFpwIUFVVpfj4eN10002efVlZWYqKitKuXbu6PPY3v/mNhg8fruuvv15FRUU6c+ZMoKsbcdra2rRv3z5lZWV59kVFRSkrK0tVVVU+j6mqqvIqL0nZ2dmdlof/9Ob9kqRTp07pmmuuUUpKimbPnq2DBw/2R3XRQ/xtmUdE3mwz0jQ1Nenqq6/22jdgwAANGzZMTU1NnR53zz336JprrlFSUpL++Mc/6qmnntKf//xnORyOQFc5onzxxRdyuVxKSEjw2p+QkKBDhw75PKapqcln+a7eT/hHb96vCRMm6PXXX9eNN96olpYWrV27VtOnT9fBgwe58W+I6exvy+l06quvvtKgQYOCVDP0FAEnjK1YsUI/+9nPuixTU1PT6/NfOEbnhhtukN1u16233qrDhw/r2muv7fV5gUiTkZGhjIwMz+Pp06crLS1Nv/rVr/T8888HsWaAeRFwwtjjjz+uxYsXd1lmzJgxSkxMvGTw47lz5/Tll18qMTGx26+Xnp4uSfr0008JOH40fPhwWa1WNTc3e+1vbm7u9P1JTEzsUXn4T2/er4sNHDhQU6ZM0aeffhqIKqIPOvvbstlstN6EGcbghLERI0Zo4sSJXW7R0dHKyMjQyZMntW/fPs+x77//vtrb2z2hpTuqq6slSXa73d+XEtGio6M1depUlZeXe/a1t7ervLzc61v/hTIyMrzKS9J7773XaXn4T2/er4u5XC599NFH/C2FIP62TCTYo5zRP3JycowpU6YYu3btMv7rv/7LGDdunDF//nzP88eOHTMmTJhg7Nq1yzAMw/j000+N5557zti7d69RW1tr/Ou//qsxZswY43vf+16wLsHU3nzzTSMmJsbYsGGD8fHHHxv333+/ER8fbzQ1NRmGYRh/93d/Z6xYscJTfseOHcaAAQOMtWvXGjU1NcaqVauMgQMHGh999FGwLiGi9PT9evbZZ413333XOHz4sLFv3z7j7rvvNmJjY42DBw8G6xIiRmtrq3HgwAHjwIEDhiTjF7/4hXHgwAHj6NGjhmEYxooVK4y/+7u/85T/7//+b2Pw4MHGk08+adTU1Bjr1q0zrFarUVZWFqxLQC8RcCLEiRMnjPnz5xtDhgwxbDabsWTJEqO1tdXzfG1trSHJ2L59u2EYhlFXV2d873vfM4YNG2bExMQYY8eONZ588kmjpaUlSFdgfv/4j/9ojBo1yoiOjjamTZtm7Ny50/PcLbfcYixatMir/JYtW4zx48cb0dHRxre+9S3j3//93/u5xpGtJ+9XQUGBp2xCQoLxgx/8wNi/f38Qah15tm/fbki6ZOt4fxYtWmTccsstlxwzefJkIzo62hgzZoyxfv36fq83+s5iGIYRxAYkAAAAv2MMDgAAMB0CDgAAMB0CDgAAMB0CDgAAMB0CDgAAMB0CDgAAMB0CDgAAMB0CDgAAMB0CDgAAMB0CDgAAMB0CDgAAMB0CDgAAMJ3/HzMY1LEzIuyPAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" + "metadata": { + "ExecuteTime": { + "end_time": "2024-11-25T16:50:57.858973Z", + "start_time": "2024-11-25T16:50:56.487914Z" } - ], + }, "source": [ "plt.cla()\n", "plt.clf()\n", @@ -711,16 +713,41 @@ "\n", "plt.legend()\n", "plt.show()" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1m1/1\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 931ms/step\n" + ] + }, + { + "data": { + "text/plain": [ + "
" + ], + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjgAAAGhCAYAAABs9M7gAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA3FElEQVR4nO3de3RU5b3/8c/kzsUkQiATIJAIwUBBggFCKC0qWYaWVlLwECNtMI1cKiISRQk/BLS1UXsopIXKoccLVhGkB2mLFKVBLDXDLZD2gECVBYTbTLgsMhAkgcz+/ZHD6MgQbtm5bN6vtfaCeea7936ebMP+uK82wzAMAQAAWEhAY3cAAACgvhFwAACA5RBwAACA5RBwAACA5RBwAACA5RBwAACA5RBwAACA5RBwAACA5RBwAACA5RBwAACA5TRIwFm4cKHi4uIUFhamlJQUbdmypc76FStWKDExUWFhYerdu7fWrFlzWc3u3bv1wAMPKCIiQq1atVL//v1VVlZm1hAAAEAzYnrAWb58ufLy8jR79mxt375dffr0UXp6usrLy/3WFxcXKysrS7m5udqxY4cyMjKUkZGhnTt3emv27dunwYMHKzExURs2bNC//vUvPffccwoLCzN7OAAAoBmwmf2yzZSUFPXv318LFiyQJHk8HsXGxmry5MmaPn36ZfWZmZmqrKzU6tWrvW0DBw5UUlKSFi1aJEl66KGHFBwcrD/84Q831CePx6OjR4/qtttuk81mu6FlAACAhmUYhs6cOaMOHTooIOAqx2gME1VVVRmBgYHG+++/79OenZ1tPPDAA37niY2NNebNm+fTNmvWLOOuu+4yDMMwampqjNatWxsvvPCCcf/99xvt2rUzBgwYcNk6vu78+fNGRUWFd/rss88MSUxMTExMTEzNcDp06NBVM0iQTHTixAnV1NQoOjrapz06Olp79uzxO4/T6fRb73Q6JUnl5eU6e/asXnrpJf3iF7/Qyy+/rLVr12rkyJH6+OOPNWTIkMuWWVBQoOeff/6y9kOHDik8PPxGhwcAABqQ2+1WbGysbrvttqvWmhpwzODxeCRJI0aM0NSpUyVJSUlJKi4u1qJFi/wGnPz8fOXl5Xk/X/oBhYeHE3AAAGhmruXyElMDTlRUlAIDA+VyuXzaXS6X7Ha733nsdnud9VFRUQoKClLPnj19anr06KF//OMffpcZGhqq0NDQGx0GAABoZky9iyokJETJyckqKirytnk8HhUVFSk1NdXvPKmpqT71krRu3TpvfUhIiPr376+9e/f61Pz73/9Wly5d6nkEAACgOTL9FFVeXp7Gjh2rfv36acCAAZo/f74qKyuVk5MjScrOzlbHjh1VUFAgSZoyZYqGDBmiuXPnavjw4Vq2bJm2bdumxYsXe5c5bdo0ZWZm6rvf/a7uvfderV27Vn/5y1+0YcMGs4cDAACaAdMDTmZmpo4fP65Zs2bJ6XQqKSlJa9eu9V5IXFZW5nOr16BBg7R06VLNnDlTM2bMUEJCglatWqVevXp5a370ox9p0aJFKigo0BNPPKE777xT//M//6PBgwfXW78Nw9DFixdVU1NTb8vEtQkMDFRQUBC38AMAbpjpz8FpitxutyIiIlRRUeH3IuPq6modO3ZM586da4TeQZJatmypmJgYhYSENHZXAABNxNX231/X7O6iMpvH49H+/fsVGBioDh06KCQkhCMJDcgwDFVXV+v48ePav3+/EhISrv4wJwAAvoGA8w3V1dXepy23bNmysbtzS2rRooWCg4N18OBBVVdX8woOAMB143+Nr4CjBo2Lnz8A4GZwBAcAANSfmhpp40bp2DEpJkb6znekwMAG7wb/m3wLOHDggGw2m0pLSxu7KwAAK1u5UoqLk+69V3r44do/4+Jq2xsYAQcNYsOGDbr77rsVGhqqbt266c0332zsLgEA6tPKldKDD0qHD/u2HzlS297AIYeAA9Pt379fw4cP17333qvS0lI9+eSTevTRR/Xhhx82dtcAAPWhpkaaMkXy9+SZS21PPllb10AIOCapqZE2bJDefbf2z4bYph6PR6+88oq6deum0NBQde7cWS+++KKfvtUoNzdX8fHxatGihe68804VFhb61GzYsEEDBgxQq1atFBkZqW9/+9s6ePCgJOmf//yn7r33Xt12220KDw9XcnKytm3bdsV+LVq0SPHx8Zo7d6569Oihxx9/XA8++KDmzZtXvz8AAEDj2Ljx8iM3X2cY0qFDtXUNhIuMTbByZW2Q/fq27tRJKiyURo40b735+fn6/e9/r3nz5mnw4ME6duyY9uzZc1mdx+NRp06dtGLFCrVt21bFxcUaP368YmJiNHr0aF28eFEZGRkaN26c3n33XVVXV2vLli3e5wGNGTNGffv21auvvqrAwECVlpYqODj4iv1yOBxKS0vzaUtPT9eTTz5Zr+MHADSSY8fqt64eEHDq2aVTkN88SnfpFOQf/2hOyDlz5owKCwu1YMECjR07VpLUtWtXDR48WAcOHPCpDQ4O1vPPP+/9HB8fL4fDoffee0+jR4+W2+1WRUWFfvCDH6hr166Sat/WfklZWZmmTZumxMRESVJCQkKdfXM6nd5Xc1wSHR0tt9utL7/8Ui1atLjhcQMAmoCYmPqtqwecoqpHjXkKcvfu3aqqqtLQoUOvqX7hwoVKTk5Wu3bt1Lp1ay1evFhlZWWSpDZt2uiRRx5Renq6fvjDH6qwsFDHvpa68/Ly9OijjyotLU0vvfSS9u3b5/2udevW3mnixIn1O0gAQNP0ne/Unqq40pP/bTYpNra2roEQcOpRY56CvJ6jIMuWLdPTTz+t3NxcffTRRyotLVVOTo6qq6u9NW+88YYcDocGDRqk5cuXq3v37tq0aZMkac6cOdq1a5eGDx+u9evXq2fPnnr//fclSaWlpd7phRdekCTZ7Xa5XC6fPrhcLoWHh3P0BgCsIDCw9joM6fKQc+nz/PkN+jwcAk49asxTkAkJCWrRooWKioquWvvpp59q0KBBeuyxx9S3b19169bN5yjMJX379lV+fr6Ki4vVq1cvLV261Ptd9+7dNXXqVH300UcaOXKk3njjDUlSt27dvFP79u0lSampqZf1a926dUpNTb2ZIQMAmpKRI2uvw+jY0be9Uyfzrs+oA9fg1KPGPAUZFhamZ599Vs8884xCQkL07W9/W8ePH9euXbsuO22VkJCgt956Sx9++KHi4+P1hz/8QVu3blV8fLyk2tu6Fy9erAceeEAdOnTQ3r179fnnnys7O1tffvmlpk2bpgcffFDx8fE6fPiwtm7dqlGjRl2xbxMnTtSCBQv0zDPP6Kc//anWr1+v9957Tx988EH9/yAAAI1n5EhpxIgm8SRjAk49unQK8sgR/9fh2Gy135t1CvK5555TUFCQZs2apaNHjyomJsbvdTATJkzQjh07lJmZKZvNpqysLD322GP661//Kklq2bKl9uzZoyVLlujkyZOKiYnRpEmTNGHCBF28eFEnT55Udna2XC6XoqKiNHLkSJ+Llr8pPj5eH3zwgaZOnarCwkJ16tRJ//3f/6309HRzfhAAgMYTGCjdc09j90I2w/C3K7Y2t9utiIgIVVRUKDw83Oe78+fPa//+/YqPj7+ht1hfuotK8g05l05BNsJRumbpZrcDAMB66tp/fxPX4NSzJnYKEgCAWxKnqEzQhE5BAgBwSyLgmKSJnIIEAOCWxCkqAABgOQQcAABgOQQcAABgOQQcAABgOQQcAABgOQQcAABgOQScW8CBAwdks9lUWlra2F0BAKBBEHBgumPHjunhhx9W9+7dFRAQoCeffLKxuwQAsDgCjllqaqQNG6R33639s6amsXvUaKqqqtSuXTvNnDlTffr0aezuAABuAQQcM6xcKcXFSffeKz38cO2fcXG17SbyeDx65ZVX1K1bN4WGhqpz58568cUXL6urqalRbm6u4uPj1aJFC915550qLCz0qdmwYYMGDBigVq1aKTIyUt/+9rd18OBBSdI///lP3XvvvbrtttsUHh6u5ORkbdu27Yr9iouLU2FhobKzsxUREVG/gwYAwA9e1VDfLr1O/JsvaT9ypLbdxDdu5ufn6/e//73mzZunwYMH69ixY9qzZ89ldR6PR506ddKKFSvUtm1bFRcXa/z48YqJidHo0aN18eJFZWRkaNy4cXr33XdVXV2tLVu2yPZ/r0QfM2aM+vbtq1dffVWBgYEqLS1VcHCwKWMCAOBGEHDqU02NNGXK5eFGqm2z2aQnn6x9E2c9v3nzzJkzKiws1IIFCzR27FhJUteuXTV48GAdOHDApzY4OFjPP/+893N8fLwcDofee+89jR49Wm63WxUVFfrBD36grl27SpJ69OjhrS8rK9O0adOUmJgoSUpISKjXsQAAcLM4RVWfNm6UDh++8veGIR06VFtXz3bv3q2qqioNHTr0muoXLlyo5ORktWvXTq1bt9bixYtVVlYmSWrTpo0eeeQRpaen64c//KEKCwt17Ngx77x5eXl69NFHlZaWppdeekn79u3zfte6dWvvNHHixPodJAAA16hBAs7ChQsVFxensLAwpaSkaMuWLXXWr1ixQomJiQoLC1Pv3r21Zs0an+8feeQR2Ww2n2nYsGFmDuHafC0E1EvddWjRosU11y5btkxPP/20cnNz9dFHH6m0tFQ5OTmqrq721rzxxhtyOBwaNGiQli9fru7du2vTpk2SpDlz5mjXrl0aPny41q9fr549e+r999+XJJWWlnqnF154oX4HCQDANTI94Cxfvlx5eXmaPXu2tm/frj59+ig9PV3l5eV+64uLi5WVlaXc3Fzt2LFDGRkZysjI0M6dO33qhg0bpmPHjnmnd9991+yhXF1MTP3WXYeEhAS1aNFCRUVFV6399NNPNWjQID322GPq27evunXr5nMU5pK+ffsqPz9fxcXF6tWrl5YuXer9rnv37po6dao++ugjjRw5Um+88YYkqVu3bt6pffv29TdAAACug+kB59e//rXGjRunnJwc9ezZU4sWLVLLli31+uuv+60vLCzUsGHDNG3aNPXo0UM///nPdffdd2vBggU+daGhobLb7d7p9ttvN3soV/ed70idOtVea+OPzSbFxtbW1bOwsDA9++yzeuaZZ/TWW29p37592rRpk1577bXLahMSErRt2zZ9+OGH+ve//63nnntOW7du9X6/f/9+5efny+Fw6ODBg/roo4/0+eefq0ePHvryyy/1+OOPa8OGDTp48KA+/fRTbd261ecaHX8uHdU5e/asjh8/rtLSUn322Wf1/nMAAEAy+SLj6upqlZSUKD8/39sWEBCgtLQ0ORwOv/M4HA7l5eX5tKWnp2vVqlU+bRs2bFD79u11++2367777tMvfvELtW3b1u8yq6qqVFVV5f3sdrtvcERXERgoFRbW3i1ls/lebHwp9MyfX+8XGF/y3HPPKSgoSLNmzdLRo0cVExPj9zqYCRMmaMeOHcrMzJTNZlNWVpYee+wx/fWvf5UktWzZUnv27NGSJUt08uRJxcTEaNKkSZowYYIuXryokydPKjs7Wy6XS1FRURo5cqTPRcv+9O3b1/v3kpISLV26VF26dLnsAmgAAOqDzTD83fJTP44ePaqOHTuquLhYqamp3vZnnnlGn3zyiTZv3nzZPCEhIVqyZImysrK8bb/73e/0/PPPy+VySaq9hqRly5aKj4/Xvn37NGPGDLVu3VoOh0OBfsLDnDlz/O6AKyoqFB4e7tN2/vx57d+/X/Hx8QoLC7uxga9cWXs31dcvOI6NrQ03Jt0ibjX1sh0AAJbidrsVERHhd//9Tc3yNvGHHnrI+/fevXvrrrvuUteuXbVhwwa/dxHl5+f7HBVyu92KjY01r4MjR9beCr5xY+0FxTExtaelTDpyAwAAfJkacKKiohQYGOg98nKJy+WS3W73O4/dbr+uekm64447FBUVpS+++MJvwAkNDVVoaOgNjOAmBAZK99zTsOsEAACS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" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 25 }, { "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-11-25T16:51:00.107975Z", + "start_time": "2024-11-25T16:51:00.093491Z" + } + }, "source": [ "os.remove(\"./best_ssl_model.keras\")" - ] + ], + "outputs": [], + "execution_count": 26 }, { "attachments": {}, diff --git a/examples/transformations/rocket.ipynb b/examples/transformations/rocket.ipynb index 3e1ed7db1d..c992fc1952 100644 --- a/examples/transformations/rocket.ipynb +++ b/examples/transformations/rocket.ipynb @@ -32,41 +32,49 @@ }, { "cell_type": "code", - "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:32:46.441933Z", "iopub.status.busy": "2020-12-19T14:32:46.441213Z", "iopub.status.idle": "2020-12-19T14:32:46.443225Z", "shell.execute_reply": "2020-12-19T14:32:46.444014Z" + }, + "ExecuteTime": { + "end_time": "2024-11-25T17:01:32.515016Z", + "start_time": "2024-11-25T17:01:32.504509Z" } }, - "outputs": [], "source": [ "# !pip install --upgrade numba" - ] + ], + "outputs": [], + "execution_count": 33 }, { "cell_type": "code", - "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:32:46.448396Z", "iopub.status.busy": "2020-12-19T14:32:46.447602Z", "iopub.status.idle": "2020-12-19T14:32:51.904418Z", "shell.execute_reply": "2020-12-19T14:32:51.905034Z" + }, + "ExecuteTime": { + "end_time": "2024-11-25T17:01:33.167188Z", + "start_time": "2024-11-25T17:01:33.161609Z" } }, - "outputs": [], "source": [ "import numpy as np\n", "from sklearn.linear_model import RidgeClassifierCV\n", "from sklearn.pipeline import make_pipeline\n", "\n", - "from aeon.datasets import load_arrow_head # univariate dataset\n", "from aeon.datasets import load_basic_motions # multivariate dataset\n", + "from aeon.datasets import load_gunpoint # univariate dataset\n", "from aeon.transformations.collection.convolution_based import Rocket" - ] + ], + "outputs": [], + "execution_count": 34 }, { "cell_type": "markdown", @@ -83,19 +91,34 @@ }, { "cell_type": "code", - "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:32:51.908710Z", "iopub.status.busy": "2020-12-19T14:32:51.908101Z", "iopub.status.idle": "2020-12-19T14:32:51.918987Z", "shell.execute_reply": "2020-12-19T14:32:51.919508Z" + }, + "ExecuteTime": { + "end_time": "2024-11-25T17:01:34.603321Z", + "start_time": "2024-11-25T17:01:34.573759Z" } }, - "outputs": [], "source": [ - "X_train, y_train = load_arrow_head(split=\"train\")" - ] + "X_train, y_train = load_gunpoint(split=\"train\")\n", + "X_train = X_train[:5, :, :]\n", + "y_train = y_train[:5]\n", + "print(X_train.shape)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(5, 1, 150)\n" + ] + } + ], + "execution_count": 35 }, { "cell_type": "markdown", @@ -106,21 +129,34 @@ }, { "cell_type": "code", - "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:32:51.923023Z", "iopub.status.busy": "2020-12-19T14:32:51.922451Z", "iopub.status.idle": "2020-12-19T14:32:52.164365Z", "shell.execute_reply": "2020-12-19T14:32:52.164864Z" + }, + "ExecuteTime": { + "end_time": "2024-11-25T17:01:35.852821Z", + "start_time": "2024-11-25T17:01:35.837832Z" } }, - "outputs": [], "source": [ - "rocket = Rocket() # by default, ROCKET uses 10,000 kernels\n", + "rocket = Rocket(n_kernels=100) # by default, ROCKET uses 10,000 kernels\n", "rocket.fit(X_train)\n", - "X_train_transform = rocket.transform(X_train)" - ] + "X_train_transform = rocket.transform(X_train)\n", + "print(X_train_transform.shape)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(5, 200)\n" + ] + } + ], + "execution_count": 36 }, { "cell_type": "markdown", @@ -138,30 +174,448 @@ }, { "cell_type": "code", - "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:32:52.168847Z", "iopub.status.busy": "2020-12-19T14:32:52.168155Z", "iopub.status.idle": "2020-12-19T14:32:52.284816Z", "shell.execute_reply": "2020-12-19T14:32:52.285506Z" + }, + "ExecuteTime": { + "end_time": "2024-11-25T17:01:38.060428Z", + "start_time": "2024-11-25T17:01:38.038775Z" } }, + "source": [ + "classifier = RidgeClassifierCV(alphas=np.logspace(-3, 3, 10))\n", + "classifier.fit(X_train_transform, y_train)" + ], "outputs": [ { "data": { - "text/plain": "RidgeClassifierCV(alphas=array([1.00000000e-03, 4.64158883e-03, 2.15443469e-02, 1.00000000e-01,\n 4.64158883e-01, 2.15443469e+00, 1.00000000e+01, 4.64158883e+01,\n 2.15443469e+02, 1.00000000e+03]))", - "text/html": "
RidgeClassifierCV(alphas=array([1.00000000e-03, 4.64158883e-03, 2.15443469e-02, 1.00000000e-01,\n       4.64158883e-01, 2.15443469e+00, 1.00000000e+01, 4.64158883e+01,\n       2.15443469e+02, 1.00000000e+03]))
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" + "text/plain": [ + "RidgeClassifierCV(alphas=array([1.00000000e-03, 4.64158883e-03, 2.15443469e-02, 1.00000000e-01,\n", + " 4.64158883e-01, 2.15443469e+00, 1.00000000e+01, 4.64158883e+01,\n", + " 2.15443469e+02, 1.00000000e+03]))" + ], + "text/html": [ + "
RidgeClassifierCV(alphas=array([1.00000000e-03, 4.64158883e-03, 2.15443469e-02, 1.00000000e-01,\n",
+       "       4.64158883e-01, 2.15443469e+00, 1.00000000e+01, 4.64158883e+01,\n",
+       "       2.15443469e+02, 1.00000000e+03]))
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" + ] }, - "execution_count": 5, + "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], - "source": [ - "classifier = RidgeClassifierCV(alphas=np.logspace(-3, 3, 10))\n", - "classifier.fit(X_train_transform, y_train)" - ] + "execution_count": 37 }, { "cell_type": "markdown", @@ -172,20 +626,24 @@ }, { "cell_type": "code", - "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:32:52.289448Z", "iopub.status.busy": "2020-12-19T14:32:52.288717Z", "iopub.status.idle": "2020-12-19T14:32:53.307829Z", "shell.execute_reply": "2020-12-19T14:32:53.308341Z" + }, + "ExecuteTime": { + "end_time": "2024-11-25T17:01:39.178929Z", + "start_time": "2024-11-25T17:01:39.136007Z" } }, - "outputs": [], "source": [ - "X_test, y_test = load_arrow_head(split=\"test\")\n", + "X_test, y_test = load_gunpoint(split=\"test\")\n", "X_test_transform = rocket.transform(X_test)" - ] + ], + "outputs": [], + "execution_count": 38 }, { "cell_type": "markdown", @@ -196,7 +654,6 @@ }, { "cell_type": "code", - "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:32:53.312125Z", @@ -204,21 +661,28 @@ "iopub.status.idle": "2020-12-19T14:32:53.409775Z", "shell.execute_reply": "2020-12-19T14:32:53.410342Z" }, - "scrolled": true + "scrolled": true, + "ExecuteTime": { + "end_time": "2024-11-25T17:01:40.547350Z", + "start_time": "2024-11-25T17:01:40.533334Z" + } }, + "source": [ + "classifier.score(X_test_transform, y_test)" + ], "outputs": [ { "data": { - "text/plain": "0.7771428571428571" + "text/plain": [ + "0.64" + ] }, - "execution_count": 7, + "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], - "source": [ - "classifier.score(X_test_transform, y_test)" - ] + "execution_count": 39 }, { "cell_type": "markdown", @@ -235,19 +699,23 @@ }, { "cell_type": "code", - "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:32:53.413597Z", "iopub.status.busy": "2020-12-19T14:32:53.412786Z", "iopub.status.idle": "2020-12-19T14:32:53.775638Z", "shell.execute_reply": "2020-12-19T14:32:53.776690Z" + }, + "ExecuteTime": { + "end_time": "2024-11-25T17:01:41.782580Z", + "start_time": "2024-11-25T17:01:41.767897Z" } }, - "outputs": [], "source": [ "X_train, y_train = load_basic_motions(split=\"train\")" - ] + ], + "outputs": [], + "execution_count": 40 }, { "cell_type": "markdown", @@ -258,21 +726,37 @@ }, { "cell_type": "code", - "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:32:53.794896Z", "iopub.status.busy": "2020-12-19T14:32:53.794345Z", "iopub.status.idle": "2020-12-19T14:32:54.613570Z", "shell.execute_reply": "2020-12-19T14:32:54.614198Z" + }, + "ExecuteTime": { + "end_time": "2024-11-25T17:01:42.949980Z", + "start_time": "2024-11-25T17:01:42.918211Z" } }, - "outputs": [], "source": [ - "rocket = Rocket()\n", + "rocket = Rocket(n_kernels=100) # by default, ROCKET uses 10,000 kernels\n", "rocket.fit(X_train)\n", - "X_train_transform = rocket.transform(X_train)" - ] + "X_train_transform = rocket.transform(X_train)\n", + "X_train_transform.shape" + ], + "outputs": [ + { + "data": { + "text/plain": [ + "(40, 200)" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 41 }, { "cell_type": "markdown", @@ -283,30 +767,448 @@ }, { "cell_type": "code", - "execution_count": 10, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:32:54.618359Z", "iopub.status.busy": "2020-12-19T14:32:54.617890Z", "iopub.status.idle": "2020-12-19T14:32:54.836560Z", "shell.execute_reply": "2020-12-19T14:32:54.837249Z" + }, + "ExecuteTime": { + "end_time": "2024-11-25T17:01:44.038154Z", + "start_time": "2024-11-25T17:01:44.002549Z" } }, + "source": [ + "classifier = RidgeClassifierCV(alphas=np.logspace(-3, 3, 10))\n", + "classifier.fit(X_train_transform, y_train)" + ], "outputs": [ { "data": { - "text/plain": "RidgeClassifierCV(alphas=array([1.00000000e-03, 4.64158883e-03, 2.15443469e-02, 1.00000000e-01,\n 4.64158883e-01, 2.15443469e+00, 1.00000000e+01, 4.64158883e+01,\n 2.15443469e+02, 1.00000000e+03]))", - "text/html": "
RidgeClassifierCV(alphas=array([1.00000000e-03, 4.64158883e-03, 2.15443469e-02, 1.00000000e-01,\n       4.64158883e-01, 2.15443469e+00, 1.00000000e+01, 4.64158883e+01,\n       2.15443469e+02, 1.00000000e+03]))
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" + "text/plain": [ + "RidgeClassifierCV(alphas=array([1.00000000e-03, 4.64158883e-03, 2.15443469e-02, 1.00000000e-01,\n", + " 4.64158883e-01, 2.15443469e+00, 1.00000000e+01, 4.64158883e+01,\n", + " 2.15443469e+02, 1.00000000e+03]))" + ], + "text/html": [ + "
RidgeClassifierCV(alphas=array([1.00000000e-03, 4.64158883e-03, 2.15443469e-02, 1.00000000e-01,\n",
+       "       4.64158883e-01, 2.15443469e+00, 1.00000000e+01, 4.64158883e+01,\n",
+       "       2.15443469e+02, 1.00000000e+03]))
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" + ] }, - "execution_count": 10, + "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], - "source": [ - "classifier = RidgeClassifierCV(alphas=np.logspace(-3, 3, 10))\n", - "classifier.fit(X_train_transform, y_train)" - ] + "execution_count": 42 }, { "cell_type": "markdown", @@ -317,20 +1219,24 @@ }, { "cell_type": "code", - "execution_count": 11, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:32:54.841004Z", "iopub.status.busy": "2020-12-19T14:32:54.840351Z", "iopub.status.idle": "2020-12-19T14:32:55.906455Z", "shell.execute_reply": "2020-12-19T14:32:55.907064Z" + }, + "ExecuteTime": { + "end_time": "2024-11-25T17:01:45.150937Z", + "start_time": "2024-11-25T17:01:45.106121Z" } }, - "outputs": [], "source": [ "X_test, y_test = load_basic_motions(split=\"test\")\n", "X_test_transform = rocket.transform(X_test)" - ] + ], + "outputs": [], + "execution_count": 43 }, { "cell_type": "markdown", @@ -341,7 +1247,6 @@ }, { "cell_type": "code", - "execution_count": 12, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:32:55.910253Z", @@ -349,21 +1254,28 @@ "iopub.status.idle": "2020-12-19T14:32:56.008364Z", "shell.execute_reply": "2020-12-19T14:32:56.008931Z" }, - "scrolled": true + "scrolled": true, + "ExecuteTime": { + "end_time": "2024-11-25T17:01:46.229312Z", + "start_time": "2024-11-25T17:01:46.215072Z" + } }, + "source": [ + "classifier.score(X_test_transform, y_test)" + ], "outputs": [ { "data": { - "text/plain": "0.975" + "text/plain": [ + "0.975" + ] }, - "execution_count": 12, + "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], - "source": [ - "classifier.score(X_test_transform, y_test)" - ] + "execution_count": 44 }, { "cell_type": "markdown", @@ -380,21 +1292,25 @@ }, { "cell_type": "code", - "execution_count": 13, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:32:56.012465Z", "iopub.status.busy": "2020-12-19T14:32:56.011939Z", "iopub.status.idle": "2020-12-19T14:32:56.013801Z", "shell.execute_reply": "2020-12-19T14:32:56.014399Z" + }, + "ExecuteTime": { + "end_time": "2024-11-25T17:01:47.349648Z", + "start_time": "2024-11-25T17:01:47.345129Z" } }, - "outputs": [], "source": [ "rocket_pipeline = make_pipeline(\n", " Rocket(), RidgeClassifierCV(alphas=np.logspace(-3, 3, 10))\n", ")" - ] + ], + "outputs": [], + "execution_count": 45 }, { "cell_type": "markdown", @@ -405,33 +1321,457 @@ }, { "cell_type": "code", - "execution_count": 14, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:32:56.017692Z", "iopub.status.busy": "2020-12-19T14:32:56.017166Z", "iopub.status.idle": "2020-12-19T14:32:56.420648Z", "shell.execute_reply": "2020-12-19T14:32:56.421247Z" + }, + "ExecuteTime": { + "end_time": "2024-11-25T17:01:49.740497Z", + "start_time": "2024-11-25T17:01:48.459632Z" } }, + "source": [ + "# it is necessary to pass y_train to the pipeline\n", + "# y_train is not used for the transform, but it is used by the classifier\n", + "rocket_pipeline.fit(X_train, y_train)" + ], "outputs": [ { "data": { - "text/plain": "Pipeline(steps=[('rocket', Rocket()),\n ('ridgeclassifiercv',\n RidgeClassifierCV(alphas=array([1.00000000e-03, 4.64158883e-03, 2.15443469e-02, 1.00000000e-01,\n 4.64158883e-01, 2.15443469e+00, 1.00000000e+01, 4.64158883e+01,\n 2.15443469e+02, 1.00000000e+03])))])", - "text/html": "
Pipeline(steps=[('rocket', Rocket()),\n                ('ridgeclassifiercv',\n                 RidgeClassifierCV(alphas=array([1.00000000e-03, 4.64158883e-03, 2.15443469e-02, 1.00000000e-01,\n       4.64158883e-01, 2.15443469e+00, 1.00000000e+01, 4.64158883e+01,\n       2.15443469e+02, 1.00000000e+03])))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" + "text/plain": [ + "Pipeline(steps=[('rocket', Rocket()),\n", + " ('ridgeclassifiercv',\n", + " RidgeClassifierCV(alphas=array([1.00000000e-03, 4.64158883e-03, 2.15443469e-02, 1.00000000e-01,\n", + " 4.64158883e-01, 2.15443469e+00, 1.00000000e+01, 4.64158883e+01,\n", + " 2.15443469e+02, 1.00000000e+03])))])" + ], + "text/html": [ + "
Pipeline(steps=[('rocket', Rocket()),\n",
+       "                ('ridgeclassifiercv',\n",
+       "                 RidgeClassifierCV(alphas=array([1.00000000e-03, 4.64158883e-03, 2.15443469e-02, 1.00000000e-01,\n",
+       "       4.64158883e-01, 2.15443469e+00, 1.00000000e+01, 4.64158883e+01,\n",
+       "       2.15443469e+02, 1.00000000e+03])))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" + ] }, - "execution_count": 14, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], - "source": [ - "X_train, y_train = load_arrow_head(split=\"train\")\n", - "\n", - "# it is necessary to pass y_train to the pipeline\n", - "# y_train is not used for the transform, but it is used by the classifier\n", - "rocket_pipeline.fit(X_train, y_train)" - ] + "execution_count": 46 }, { "cell_type": "markdown", @@ -442,30 +1782,41 @@ }, { "cell_type": "code", - "execution_count": 15, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:32:56.425026Z", "iopub.status.busy": "2020-12-19T14:32:56.424348Z", "iopub.status.idle": "2020-12-19T14:32:57.602704Z", "shell.execute_reply": "2020-12-19T14:32:57.603291Z" + }, + "ExecuteTime": { + "end_time": "2024-11-25T17:01:50.960464Z", + "start_time": "2024-11-25T17:01:49.763086Z" } }, + "source": [ + "rocket_pipeline.score(X_test, y_test)" + ], "outputs": [ { "data": { - "text/plain": "0.7885714285714286" + "text/plain": [ + "0.975" + ] }, - "execution_count": 15, + "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], - "source": [ - "X_test, y_test = load_arrow_head(split=\"test\")\n", - "\n", - "rocket_pipeline.score(X_test, y_test)" - ] + "execution_count": 47 + }, + { + "metadata": {}, + "cell_type": "code", + "outputs": [], + "execution_count": null, + "source": "" } ], "metadata": { diff --git a/examples/transformations/tsfresh.ipynb b/examples/transformations/tsfresh.ipynb index da00e2f48e..d1d37d1761 100644 --- a/examples/transformations/tsfresh.ipynb +++ b/examples/transformations/tsfresh.ipynb @@ -17,39 +17,47 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:30:39.713903Z", "iopub.status.busy": "2020-12-19T14:30:39.713342Z", "iopub.status.idle": "2020-12-19T14:30:39.715128Z", "shell.execute_reply": "2020-12-19T14:30:39.715641Z" + }, + "ExecuteTime": { + "end_time": "2024-11-25T14:07:05.457198Z", + "start_time": "2024-11-25T14:07:05.449815Z" } }, - "outputs": [], "source": [ "# !pip install --upgrade tsfresh" - ] + ], + "outputs": [], + "execution_count": 1 }, { "cell_type": "code", - "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:30:39.719083Z", "iopub.status.busy": "2020-12-19T14:30:39.718586Z", "iopub.status.idle": "2020-12-19T14:30:40.743724Z", "shell.execute_reply": "2020-12-19T14:30:40.744213Z" + }, + "ExecuteTime": { + "end_time": "2024-11-25T14:07:07.829632Z", + "start_time": "2024-11-25T14:07:06.056664Z" } }, - "outputs": [], "source": [ "from sklearn.ensemble import RandomForestClassifier\n", "from sklearn.pipeline import make_pipeline\n", "\n", "from aeon.datasets import load_arrow_head, load_basic_motions\n", "from aeon.transformations.collection.feature_based import TSFresh, TSFreshRelevant" - ] + ], + "outputs": [], + "execution_count": 2 }, { "cell_type": "markdown", @@ -59,46 +67,43 @@ "\n", "We use the ArrowHead data from the [UCR TSC archive](https://timeseriesclassification.com).\n", "as an example dataset. See\n", - "[dataset notebook](https://github.com/aeon-toolkit/aeon/blob/main/examples/datasets\n", - "/provided_data.ipynb) for more details." + "[dataset notebook](https://github.com/aeon-toolkit/aeon/blob/main/examples/datasets/provided_data.ipynb) for more details. We only use the first few cases for examples to speed up the \n", + "notebook. " ] }, { "cell_type": "code", - "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:30:40.748159Z", "iopub.status.busy": "2020-12-19T14:30:40.747656Z", "iopub.status.idle": "2020-12-19T14:30:40.795200Z", "shell.execute_reply": "2020-12-19T14:30:40.795889Z" + }, + "ExecuteTime": { + "end_time": "2024-11-25T14:07:09.120656Z", + "start_time": "2024-11-25T14:07:09.090118Z" } }, - "outputs": [], "source": [ - "X_train, y_train = load_arrow_head(split=\"train\")\n", - "X_test, y_test = load_arrow_head(split=\"test\")\n", + "X, y = load_arrow_head()\n", + "n_cases = 24\n", + "X_train = X[:n_cases, :, :]\n", + "y_train = y[:n_cases]\n", + "X_test = X[n_cases : 2 * n_cases, :, :]\n", + "y_test = y[n_cases : 2 * n_cases]\n", "print(X_train.shape, y_train.shape, X_test.shape, y_test.shape)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "execution": { - "iopub.execute_input": "2020-12-19T14:30:40.808841Z", - "iopub.status.busy": "2020-12-19T14:30:40.808198Z", - "iopub.status.idle": "2020-12-19T14:30:40.816155Z", - "shell.execute_reply": "2020-12-19T14:30:40.816682Z" - }, - "jupyter": { - "outputs_hidden": false + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(24, 1, 251) (24,) (24, 1, 251) (24,)\n" + ] } - }, - "outputs": [], - "source": [ - "X_train[0]" - ] + ], + "execution_count": 3 }, { "cell_type": "markdown", @@ -114,22 +119,34 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:30:40.829452Z", "iopub.status.busy": "2020-12-19T14:30:40.828907Z", "iopub.status.idle": "2020-12-19T14:30:53.049755Z", "shell.execute_reply": "2020-12-19T14:30:53.050249Z" + }, + "ExecuteTime": { + "end_time": "2024-11-25T14:07:16.339473Z", + "start_time": "2024-11-25T14:07:11.573523Z" } }, - "outputs": [], "source": [ "t = TSFresh()\n", "Xt = t.fit_transform(X_train)\n", - "Xt.shape\n", - "Xt2 = t.transform(X_test)" - ] + "Xt2 = t.transform(X_test)\n", + "print(f\"Train shape = {Xt.shape} test shape = {Xt2.shape}\")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train shape = (24, 777) test shape = (24, 777)\n" + ] + } + ], + "execution_count": 4 }, { "cell_type": "markdown", @@ -143,8 +160,6 @@ }, { "cell_type": "code", - "execution_count": null, - "outputs": [], "source": [ "t = TSFreshRelevant()\n", "t.fit(X_train, y_train)\n", @@ -152,8 +167,25 @@ "Xt.shape" ], "metadata": { - "collapsed": false - } + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-11-25T14:07:32.455607Z", + "start_time": "2024-11-25T14:07:26.124172Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(24, 75)" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 5 }, { "cell_type": "markdown", @@ -166,16 +198,18 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:30:53.062147Z", "iopub.status.busy": "2020-12-19T14:30:53.061631Z", "iopub.status.idle": "2020-12-19T14:31:09.307275Z", "shell.execute_reply": "2020-12-19T14:31:09.307781Z" + }, + "ExecuteTime": { + "end_time": "2024-11-25T14:07:41.090159Z", + "start_time": "2024-11-25T14:07:36.403997Z" } }, - "outputs": [], "source": [ "classifier = make_pipeline(\n", " TSFresh(default_fc_parameters=\"efficient\", show_warnings=False),\n", @@ -183,7 +217,20 @@ ")\n", "classifier.fit(X_train, y_train)\n", "classifier.score(X_test, y_test)" - ] + ], + "outputs": [ + { + "data": { + "text/plain": [ + "0.625" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 6 }, { "cell_type": "markdown", @@ -197,26 +244,12 @@ }, { "cell_type": "code", - "execution_count": null, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[7 1 4 4 0 4 4 5 2 1 5 7 4 4 5 0 0 6 6 0 6 4 5 6 3 6 7 1 6 4 4 1 5 0 4 4 7\n", - " 6 6 2 1 0 0 4 6 5 4 6 4 6 6 0 4 6 1 1 4 1 4 1 4 0 1 4 1 5 4 7 4 7 6 4 6 1\n", - " 6 4 6 7 4 6 6 1 6 1 4 7 6 4 6 0 4 6 4 6 6 4 0 3 4 6 4 1 0 0 4 4 6 1 0 7 4\n", - " 6 0 4 4 0 1 6 6 0 2 0 6 0 3 6 5 7 6 4 4 3 6 6 6 1 7 4 6 6 4 4 6 6 0 4 6 4\n", - " 5 0 4 4 6 4 6 1 5 6 6 0 6 0 3 4 4 6 1 5 3 7 6 6 6 7 4]\n" - ] - } - ], "source": [ "from aeon.classification.feature_based import TSFreshClassifier\n", "from aeon.clustering.feature_based import TSFreshClusterer\n", "\n", - "cls = TSFreshClassifier()\n", - "clst = TSFreshClusterer()\n", + "cls = TSFreshClassifier(relevant_feature_extractor=False)\n", + "clst = TSFreshClusterer(n_clusters=2)\n", "\n", "cls.fit(X_train, y_train)\n", "cls.score(X_test, y_test)\n", @@ -225,8 +258,24 @@ "print(clst.predict(X_test))" ], "metadata": { - "collapsed": false - } + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-11-25T14:08:02.405107Z", + "start_time": "2024-11-25T14:07:50.878523Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['0' '1' '0' '1' '1' '2' '0' '1' '1' '0' '1' '1' '0' '2' '0' '0' '0' '2'\n", + " '2' '1' '0' '0' '0' '0']\n", + "[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0]\n" + ] + } + ], + "execution_count": 7 }, { "cell_type": "markdown", @@ -242,29 +291,33 @@ }, { "cell_type": "code", - "execution_count": null, + "source": [ + "from aeon.classification.sklearn import RotationForestClassifier\n", + "\n", + "cls = TSFreshClassifier(estimator=RotationForestClassifier(n_estimators=5))\n", + "cls.fit(X_train, y_train)\n", + "cls.score(X_test, y_test)" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-11-25T14:08:13.304452Z", + "start_time": "2024-11-25T14:08:06.677532Z" + } + }, "outputs": [ { "data": { - "text/plain": "0.5771428571428572" + "text/plain": [ + "0.5833333333333334" + ] }, - "execution_count": 9, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], - "source": [ - "from aeon.classification.sklearn import RotationForestClassifier\n", - "\n", - "cls = TSFreshClassifier(\n", - " relevant_feature_extractor=False, estimator=RotationForestClassifier(n_estimators=5)\n", - ") #\n", - "cls.fit(X_train, y_train)\n", - "cls.score(X_test, y_test)" - ], - "metadata": { - "collapsed": false - } + "execution_count": 8 }, { "cell_type": "markdown", @@ -279,20 +332,6 @@ }, { "cell_type": "code", - "execution_count": null, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[1 0 1 1 1 1 1 0 2 0 0 1 1 1 0 1 1 1 1 1 1 1 0 1 0 1 1 1 1 1 1 1 0 1 1 1 1\n", - " 1 1 2 1 1 1 1 1 0 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 1\n", - " 1 1 1 1 1 1 1 1 1 0 1 2 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1\n", - " 1 1 1 1 1 0 1 1 1 2 1 1 1 0 1 0 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n", - " 0 1 1 1 1 1 1 0 0 1 1 1 1 1 0 1 1 1 0 0 0 1 1 1 1 1 1]\n" - ] - } - ], "source": [ "from sklearn.cluster import KMeans\n", "\n", @@ -301,8 +340,22 @@ "print(clst.predict(X_test))" ], "metadata": { - "collapsed": false - } + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-11-25T14:08:38.025066Z", + "start_time": "2024-11-25T14:08:33.300907Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 0 2 0 0 1]\n" + ] + } + ], + "execution_count": 9 }, { "cell_type": "markdown", @@ -316,39 +369,442 @@ }, { "cell_type": "code", - "execution_count": null, - "outputs": [ - { - "data": { - "text/plain": "TSFreshRegressor()", - "text/html": "
TSFreshRegressor()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": "TSFreshRegressor()", - "text/html": "
TSFreshRegressor()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ "from aeon.regression.feature_based import TSFreshRegressor\n", "\n", - "reg = TSFreshRegressor()\n", + "reg = TSFreshRegressor(relevant_feature_extractor=False)\n", "from aeon.datasets import load_covid_3month\n", "\n", - "X, y = load_covid_3month()\n", + "X, y = load_covid_3month(split=\"train\")\n", "reg.fit(X, y)" ], "metadata": { - "collapsed": false - } + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-11-25T14:09:11.745540Z", + "start_time": "2024-11-25T14:08:56.573376Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "TSFreshRegressor(relevant_feature_extractor=False)" + ], + "text/html": [ + "
TSFreshRegressor(relevant_feature_extractor=False)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 10 }, { "cell_type": "markdown", @@ -363,14 +819,13 @@ "source": [ "## TSFresh with multivariate time series data\n", "\n", - "All three estimators can be used with multivariate time series. The estimators\n", - "calculate the features on each channel independently then concatenate the results.\n", - "The full transform creates `777*n_channels` features." + "``TSFresh`` transformers and all three estimators can be used with multivariate time \n", + "series. The transform calculates the features on each channel independently then \n", + "concatenate the results. The full transform creates `777*n_channels` features." ] }, { "cell_type": "code", - "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2020-12-19T14:31:09.311742Z", @@ -378,8 +833,17 @@ "iopub.status.idle": "2020-12-19T14:31:09.380791Z", "shell.execute_reply": "2020-12-19T14:31:09.381304Z" }, - "scrolled": true + "scrolled": true, + "ExecuteTime": { + "end_time": "2024-11-25T14:11:57.583864Z", + "start_time": "2024-11-25T14:11:57.545946Z" + } }, + "source": [ + "X_train, y_train = load_basic_motions(split=\"train\")\n", + "X_test, y_test = load_basic_motions(split=\"test\")\n", + "print(X_train.shape, y_train.shape, X_test.shape, y_test.shape)" + ], "outputs": [ { "name": "stdout", @@ -389,16 +853,10 @@ ] } ], - "source": [ - "X_train, y_train = load_basic_motions(split=\"train\")\n", - "X_test, y_test = load_basic_motions(split=\"test\")\n", - "print(X_train.shape, y_train.shape, X_test.shape, y_test.shape)" - ] + "execution_count": 14 }, { "cell_type": "code", - "execution_count": null, - "outputs": [], "source": [ "tsfresh = TSFresh()\n", "X = tsfresh.fit_transform(X_train, y_train)\n", @@ -408,24 +866,32 @@ "collapsed": false, "pycharm": { "is_executing": true + }, + "ExecuteTime": { + "end_time": "2024-11-25T14:12:19.453228Z", + "start_time": "2024-11-25T14:11:58.795027Z" } - } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(40, 4662)" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 15 }, { + "metadata": {}, "cell_type": "code", - "execution_count": null, "outputs": [], - "source": [ - "cls = TSFreshClassifier()\n", - "clst = TSFreshClusterer(estimator=KMeans(n_clusters=4))\n", - "cls.fit(X_train, y_train)\n", - "cls.score(X_test, y_test)\n", - "clst.fit(X_train)\n", - "print(cls.predict(X_test))" - ], - "metadata": { - "collapsed": false - } + "execution_count": null, + "source": "" } ], "metadata": {