From c6a40731094cc8649280843063ca0a8cf51ac256 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 13 May 2024 17:31:44 +0200 Subject: [PATCH] feat: move NN converters and layers to separate packages (#759) ### Summary of Changes NN converters and layers are now in separate packages. We can later re-export them from the `safeds.ml.nn` package based on user feedback. --- src/safeds/ml/nn/__init__.py | 56 +------------------ src/safeds/ml/nn/_model.py | 17 +++--- src/safeds/ml/nn/converters/__init__.py | 48 ++++++++++++++++ .../nn/{ => converters}/_input_conversion.py | 12 ++-- .../_input_conversion_image.py | 4 +- .../_input_conversion_table.py | 9 +-- .../_input_conversion_time_series.py | 7 ++- .../nn/{ => converters}/_output_conversion.py | 6 +- .../_output_conversion_image.py | 6 +- .../_output_conversion_table.py | 9 +-- .../_output_conversion_time_series.py | 7 ++- src/safeds/ml/nn/layers/__init__.py | 38 +++++++++++++ .../nn/{ => layers}/_convolutional2d_layer.py | 4 +- .../ml/nn/{ => layers}/_flatten_layer.py | 4 +- .../ml/nn/{ => layers}/_forward_layer.py | 6 +- src/safeds/ml/nn/{ => layers}/_layer.py | 0 src/safeds/ml/nn/{ => layers}/_lstm_layer.py | 9 ++- .../ml/nn/{ => layers}/_pooling2d_layer.py | 6 +- .../containers/_table/test_from_csv_file.py | 10 ++-- tests/safeds/ml/nn/converters/__init__.py | 0 .../test_input_conversion_image.py | 2 +- .../test_input_conversion_table.py} | 2 +- .../test_input_conversion_time_series.py | 8 ++- .../test_output_conversion_image.py | 8 ++- .../test_output_conversion_time_series.py | 5 +- tests/safeds/ml/nn/layers/__init__.py | 0 .../test_convolutional2d_layer.py | 2 +- .../ml/nn/{ => layers}/test_flatten_layer.py | 2 +- .../ml/nn/{ => layers}/test_forward_layer.py | 2 +- .../ml/nn/{ => layers}/test_lstm_layer.py | 2 +- .../nn/{ => layers}/test_pooling2d_layer.py | 42 +++++++------- tests/safeds/ml/nn/test_cnn_workflow.py | 21 ++++--- tests/safeds/ml/nn/test_forward_workflow.py | 8 ++- tests/safeds/ml/nn/test_lstm_workflow.py | 10 +++- tests/safeds/ml/nn/test_model.py | 40 +++++++------ 35 files changed, 237 insertions(+), 175 deletions(-) rename src/safeds/ml/nn/{ => converters}/_input_conversion.py (91%) rename src/safeds/ml/nn/{ => converters}/_input_conversion_image.py (99%) rename src/safeds/ml/nn/{ => converters}/_input_conversion_table.py (97%) rename src/safeds/ml/nn/{ => converters}/_input_conversion_time_series.py (97%) rename src/safeds/ml/nn/{ => converters}/_output_conversion.py (91%) rename src/safeds/ml/nn/{ => converters}/_output_conversion_image.py (99%) rename src/safeds/ml/nn/{ => converters}/_output_conversion_table.py (95%) rename src/safeds/ml/nn/{ => converters}/_output_conversion_time_series.py (98%) rename src/safeds/ml/nn/{ => layers}/_convolutional2d_layer.py (99%) rename src/safeds/ml/nn/{ => layers}/_flatten_layer.py (99%) rename src/safeds/ml/nn/{ => layers}/_forward_layer.py (99%) rename src/safeds/ml/nn/{ => layers}/_layer.py (100%) rename src/safeds/ml/nn/{ => layers}/_lstm_layer.py (99%) rename src/safeds/ml/nn/{ => layers}/_pooling2d_layer.py (98%) create mode 100644 tests/safeds/ml/nn/converters/__init__.py rename tests/safeds/ml/nn/{ => converters}/test_input_conversion_image.py (99%) rename tests/safeds/ml/nn/{test_table_conversion.py => converters/test_input_conversion_table.py} (88%) rename tests/safeds/ml/nn/{ => converters}/test_input_conversion_time_series.py (92%) rename tests/safeds/ml/nn/{ => converters}/test_output_conversion_image.py (96%) rename tests/safeds/ml/nn/{ => converters}/test_output_conversion_time_series.py (98%) create mode 100644 tests/safeds/ml/nn/layers/__init__.py rename tests/safeds/ml/nn/{ => layers}/test_convolutional2d_layer.py (99%) rename tests/safeds/ml/nn/{ => layers}/test_flatten_layer.py (97%) rename tests/safeds/ml/nn/{ => layers}/test_forward_layer.py (99%) rename tests/safeds/ml/nn/{ => layers}/test_lstm_layer.py (99%) rename tests/safeds/ml/nn/{ => layers}/test_pooling2d_layer.py (80%) diff --git a/src/safeds/ml/nn/__init__.py b/src/safeds/ml/nn/__init__.py index a2ceb7ac5..7bc88f61e 100644 --- a/src/safeds/ml/nn/__init__.py +++ b/src/safeds/ml/nn/__init__.py @@ -1,75 +1,21 @@ -"""Classes for classification tasks.""" +"""Neural networks for various tasks.""" from typing import TYPE_CHECKING import apipkg if TYPE_CHECKING: - from ._convolutional2d_layer import Convolutional2DLayer, ConvolutionalTranspose2DLayer - from ._flatten_layer import FlattenLayer - from ._forward_layer import ForwardLayer - from ._input_conversion import InputConversion - from ._input_conversion_image import InputConversionImage - from ._input_conversion_table import InputConversionTable - from ._input_conversion_time_series import InputConversionTimeSeries - from ._layer import Layer - from ._lstm_layer import LSTMLayer from ._model import NeuralNetworkClassifier, NeuralNetworkRegressor - from ._output_conversion import OutputConversion - from ._output_conversion_image import ( - OutputConversionImageToColumn, - OutputConversionImageToImage, - OutputConversionImageToTable, - ) - from ._output_conversion_table import OutputConversionTable - from ._output_conversion_time_series import OutputConversionTimeSeries - from ._pooling2d_layer import AvgPooling2DLayer, MaxPooling2DLayer apipkg.initpkg( __name__, { - "AvgPooling2DLayer": "._pooling2d_layer:AvgPooling2DLayer", - "Convolutional2DLayer": "._convolutional2d_layer:Convolutional2DLayer", - "ConvolutionalTranspose2DLayer": "._convolutional2d_layer:ConvolutionalTranspose2DLayer", - "FlattenLayer": "._flatten_layer:FlattenLayer", - "ForwardLayer": "._forward_layer:ForwardLayer", - "InputConversion": "._input_conversion:InputConversion", - "InputConversionImage": "._input_conversion_image:InputConversionImage", - "InputConversionTable": "._input_conversion_table:InputConversionTable", - "Layer": "._layer:Layer", - "OutputConversion": "._output_conversion:OutputConversion", - "InputConversionTimeSeries": "._input_conversion_time_series:InputConversionTimeSeries", - "LSTMLayer": "._lstm_layer:LSTMLayer", - "OutputConversionTable": "._output_conversion_table:OutputConversionTable", - "OutputConversionTimeSeries": "._output_conversion_time_series:OutputConversionTimeSeries", - "MaxPooling2DLayer": "._pooling2d_layer:MaxPooling2DLayer", "NeuralNetworkClassifier": "._model:NeuralNetworkClassifier", "NeuralNetworkRegressor": "._model:NeuralNetworkRegressor", - "OutputConversionImageToColumn": "._output_conversion_image:OutputConversionImageToColumn", - "OutputConversionImageToImage": "._output_conversion_image:OutputConversionImageToImage", - "OutputConversionImageToTable": "._output_conversion_image:OutputConversionImageToTable", }, ) __all__ = [ - "AvgPooling2DLayer", - "Convolutional2DLayer", - "ConvolutionalTranspose2DLayer", - "FlattenLayer", - "ForwardLayer", - "InputConversion", - "InputConversionImage", - "InputConversionTable", - "Layer", - "MaxPooling2DLayer", - "OutputConversion", - "InputConversionTimeSeries", - "LSTMLayer", - "OutputConversionTable", - "OutputConversionTimeSeries", "NeuralNetworkClassifier", "NeuralNetworkRegressor", - "OutputConversionImageToColumn", - "OutputConversionImageToImage", - "OutputConversionImageToTable", ] diff --git a/src/safeds/ml/nn/_model.py b/src/safeds/ml/nn/_model.py index 8f80a25ac..31e62e5ac 100644 --- a/src/safeds/ml/nn/_model.py +++ b/src/safeds/ml/nn/_model.py @@ -14,17 +14,19 @@ InvalidModelStructureError, ModelNotFittedError, ) -from safeds.ml.nn import ( - Convolutional2DLayer, - FlattenLayer, - ForwardLayer, +from safeds.ml.nn.converters import ( InputConversionImage, OutputConversionImageToColumn, OutputConversionImageToImage, OutputConversionImageToTable, ) -from safeds.ml.nn._output_conversion_image import _OutputConversionImage -from safeds.ml.nn._pooling2d_layer import _Pooling2DLayer +from safeds.ml.nn.converters._output_conversion_image import _OutputConversionImage +from safeds.ml.nn.layers import ( + Convolutional2DLayer, + FlattenLayer, + ForwardLayer, +) +from safeds.ml.nn.layers._pooling2d_layer import _Pooling2DLayer if TYPE_CHECKING: from collections.abc import Callable @@ -32,7 +34,8 @@ from torch import Tensor, nn from safeds.data.image.typing import ImageSize - from safeds.ml.nn import InputConversion, Layer, OutputConversion + from safeds.ml.nn.converters import InputConversion, OutputConversion + from safeds.ml.nn.layers import Layer IFT = TypeVar("IFT", TabularDataset, TimeSeriesDataset, ImageDataset) # InputFitType diff --git a/src/safeds/ml/nn/converters/__init__.py b/src/safeds/ml/nn/converters/__init__.py index e69de29bb..c2928fe73 100644 --- a/src/safeds/ml/nn/converters/__init__.py +++ b/src/safeds/ml/nn/converters/__init__.py @@ -0,0 +1,48 @@ +"""Converters between our data contains and tensors.""" + +from typing import TYPE_CHECKING + +import apipkg + +if TYPE_CHECKING: + from ._input_conversion import InputConversion + from ._input_conversion_image import InputConversionImage + from ._input_conversion_table import InputConversionTable + from ._input_conversion_time_series import InputConversionTimeSeries + from ._output_conversion import OutputConversion + from ._output_conversion_image import ( + OutputConversionImageToColumn, + OutputConversionImageToImage, + OutputConversionImageToTable, + ) + from ._output_conversion_table import OutputConversionTable + from ._output_conversion_time_series import OutputConversionTimeSeries + +apipkg.initpkg( + __name__, + { + "InputConversion": "._input_conversion:InputConversion", + "InputConversionImage": "._input_conversion_image:InputConversionImage", + "InputConversionTable": "._input_conversion_table:InputConversionTable", + "InputConversionTimeSeries": "._input_conversion_time_series:InputConversionTimeSeries", + "OutputConversion": "._output_conversion:OutputConversion", + "OutputConversionImageToColumn": "._output_conversion_image:OutputConversionImageToColumn", + "OutputConversionImageToImage": "._output_conversion_image:OutputConversionImageToImage", + "OutputConversionImageToTable": "._output_conversion_image:OutputConversionImageToTable", + "OutputConversionTable": "._output_conversion_table:OutputConversionTable", + "OutputConversionTimeSeries": "._output_conversion_time_series:OutputConversionTimeSeries", + }, +) + +__all__ = [ + "InputConversion", + "InputConversionImage", + "InputConversionTable", + "InputConversionTimeSeries", + "OutputConversion", + "OutputConversionImageToColumn", + "OutputConversionImageToImage", + "OutputConversionImageToTable", + "OutputConversionTable", + "OutputConversionTimeSeries", +] diff --git a/src/safeds/ml/nn/_input_conversion.py b/src/safeds/ml/nn/converters/_input_conversion.py similarity index 91% rename from src/safeds/ml/nn/_input_conversion.py rename to src/safeds/ml/nn/converters/_input_conversion.py index b3e1e41a6..1ac505c2d 100644 --- a/src/safeds/ml/nn/_input_conversion.py +++ b/src/safeds/ml/nn/converters/_input_conversion.py @@ -3,16 +3,16 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING, Any, Generic, TypeVar -if TYPE_CHECKING: - from torch.utils.data import DataLoader - -from safeds.data.image.containers._single_size_image_list import _SingleSizeImageList -from safeds.data.image.typing import ImageSize - from safeds.data.image.containers import ImageList from safeds.data.labeled.containers import ImageDataset, TabularDataset, TimeSeriesDataset from safeds.data.tabular.containers import Table +if TYPE_CHECKING: + from torch.utils.data import DataLoader + + from safeds.data.image.containers._single_size_image_list import _SingleSizeImageList + from safeds.data.image.typing import ImageSize + FT = TypeVar("FT", TabularDataset, TimeSeriesDataset, ImageDataset) PT = TypeVar("PT", Table, TimeSeriesDataset, ImageList) diff --git a/src/safeds/ml/nn/_input_conversion_image.py b/src/safeds/ml/nn/converters/_input_conversion_image.py similarity index 99% rename from src/safeds/ml/nn/_input_conversion_image.py rename to src/safeds/ml/nn/converters/_input_conversion_image.py index e19b38b68..9d0d6cc38 100644 --- a/src/safeds/ml/nn/_input_conversion_image.py +++ b/src/safeds/ml/nn/converters/_input_conversion_image.py @@ -9,12 +9,12 @@ from safeds.data.labeled.containers import ImageDataset from safeds.data.labeled.containers._image_dataset import _ColumnAsTensor, _TableAsTensor +from ._input_conversion import InputConversion + if TYPE_CHECKING: from safeds.data.image.typing import ImageSize from safeds.data.tabular.transformation import OneHotEncoder -from safeds.ml.nn import InputConversion - class InputConversionImage(InputConversion[ImageDataset, ImageList]): """The input conversion for a neural network, defines the input parameters for the neural network.""" diff --git a/src/safeds/ml/nn/_input_conversion_table.py b/src/safeds/ml/nn/converters/_input_conversion_table.py similarity index 97% rename from src/safeds/ml/nn/_input_conversion_table.py rename to src/safeds/ml/nn/converters/_input_conversion_table.py index e5c009f56..eecd3e683 100644 --- a/src/safeds/ml/nn/_input_conversion_table.py +++ b/src/safeds/ml/nn/converters/_input_conversion_table.py @@ -2,12 +2,13 @@ from typing import TYPE_CHECKING, Any -if TYPE_CHECKING: - from torch.utils.data import DataLoader - from safeds.data.labeled.containers import TabularDataset from safeds.data.tabular.containers import Table -from safeds.ml.nn import InputConversion + +from ._input_conversion import InputConversion + +if TYPE_CHECKING: + from torch.utils.data import DataLoader class InputConversionTable(InputConversion[TabularDataset, Table]): diff --git a/src/safeds/ml/nn/_input_conversion_time_series.py b/src/safeds/ml/nn/converters/_input_conversion_time_series.py similarity index 97% rename from src/safeds/ml/nn/_input_conversion_time_series.py rename to src/safeds/ml/nn/converters/_input_conversion_time_series.py index 18cf9fb23..992f6bfbc 100644 --- a/src/safeds/ml/nn/_input_conversion_time_series.py +++ b/src/safeds/ml/nn/converters/_input_conversion_time_series.py @@ -2,12 +2,13 @@ from typing import TYPE_CHECKING, Any +from safeds.data.labeled.containers import TimeSeriesDataset + +from ._input_conversion import InputConversion + if TYPE_CHECKING: from torch.utils.data import DataLoader -from safeds.data.labeled.containers import TimeSeriesDataset -from safeds.ml.nn._input_conversion import InputConversion - class InputConversionTimeSeries(InputConversion[TimeSeriesDataset, TimeSeriesDataset]): """The input conversion for a neural network, defines the input parameters for the neural network.""" diff --git a/src/safeds/ml/nn/_output_conversion.py b/src/safeds/ml/nn/converters/_output_conversion.py similarity index 91% rename from src/safeds/ml/nn/_output_conversion.py rename to src/safeds/ml/nn/converters/_output_conversion.py index f29867e31..d44a75d54 100644 --- a/src/safeds/ml/nn/_output_conversion.py +++ b/src/safeds/ml/nn/converters/_output_conversion.py @@ -4,14 +4,12 @@ from typing import TYPE_CHECKING, Any, Generic, TypeVar from safeds.data.image.containers import ImageList -from safeds.data.labeled.containers import ImageDataset, TabularDataset +from safeds.data.labeled.containers import ImageDataset, TabularDataset, TimeSeriesDataset +from safeds.data.tabular.containers import Table if TYPE_CHECKING: from torch import Tensor -from safeds.data.labeled.containers import TimeSeriesDataset -from safeds.data.tabular.containers import Table - IT = TypeVar("IT", Table, TimeSeriesDataset, ImageList) OT = TypeVar("OT", TabularDataset, TimeSeriesDataset, ImageDataset) diff --git a/src/safeds/ml/nn/_output_conversion_image.py b/src/safeds/ml/nn/converters/_output_conversion_image.py similarity index 99% rename from src/safeds/ml/nn/_output_conversion_image.py rename to src/safeds/ml/nn/converters/_output_conversion_image.py index fd19ff322..555f8758c 100644 --- a/src/safeds/ml/nn/_output_conversion_image.py +++ b/src/safeds/ml/nn/converters/_output_conversion_image.py @@ -10,13 +10,13 @@ from safeds.data.labeled.containers import ImageDataset from safeds.data.labeled.containers._image_dataset import _ColumnAsTensor, _TableAsTensor from safeds.data.tabular.containers import Column, Table +from safeds.data.tabular.transformation import OneHotEncoder + +from ._output_conversion import OutputConversion if TYPE_CHECKING: from torch import Tensor -from safeds.data.tabular.transformation import OneHotEncoder -from safeds.ml.nn import OutputConversion - class _OutputConversionImage(OutputConversion[ImageList, ImageDataset], ABC): diff --git a/src/safeds/ml/nn/_output_conversion_table.py b/src/safeds/ml/nn/converters/_output_conversion_table.py similarity index 95% rename from src/safeds/ml/nn/_output_conversion_table.py rename to src/safeds/ml/nn/converters/_output_conversion_table.py index 4146aaef1..5084856ac 100644 --- a/src/safeds/ml/nn/_output_conversion_table.py +++ b/src/safeds/ml/nn/converters/_output_conversion_table.py @@ -2,12 +2,13 @@ from typing import TYPE_CHECKING, Any -if TYPE_CHECKING: - from torch import Tensor - from safeds.data.labeled.containers import TabularDataset from safeds.data.tabular.containers import Column, Table -from safeds.ml.nn import OutputConversion + +from ._output_conversion import OutputConversion + +if TYPE_CHECKING: + from torch import Tensor class OutputConversionTable(OutputConversion[Table, TabularDataset]): diff --git a/src/safeds/ml/nn/_output_conversion_time_series.py b/src/safeds/ml/nn/converters/_output_conversion_time_series.py similarity index 98% rename from src/safeds/ml/nn/_output_conversion_time_series.py rename to src/safeds/ml/nn/converters/_output_conversion_time_series.py index 8b87d94a2..95819e6b1 100644 --- a/src/safeds/ml/nn/_output_conversion_time_series.py +++ b/src/safeds/ml/nn/converters/_output_conversion_time_series.py @@ -4,12 +4,13 @@ from typing import TYPE_CHECKING, Any from safeds._utils import _structural_hash +from safeds.data.labeled.containers import TimeSeriesDataset +from safeds.data.tabular.containers import Column + +from ._output_conversion import OutputConversion if TYPE_CHECKING: from torch import Tensor -from safeds.data.labeled.containers import TimeSeriesDataset -from safeds.data.tabular.containers import Column -from safeds.ml.nn._output_conversion import OutputConversion class OutputConversionTimeSeries(OutputConversion[TimeSeriesDataset, TimeSeriesDataset]): diff --git a/src/safeds/ml/nn/layers/__init__.py b/src/safeds/ml/nn/layers/__init__.py index e69de29bb..a499a3e0e 100644 --- a/src/safeds/ml/nn/layers/__init__.py +++ b/src/safeds/ml/nn/layers/__init__.py @@ -0,0 +1,38 @@ +"""Layers of neural networks.""" + +from typing import TYPE_CHECKING + +import apipkg + +if TYPE_CHECKING: + from ._convolutional2d_layer import Convolutional2DLayer, ConvolutionalTranspose2DLayer + from ._flatten_layer import FlattenLayer + from ._forward_layer import ForwardLayer + from ._layer import Layer + from ._lstm_layer import LSTMLayer + from ._pooling2d_layer import AveragePooling2DLayer, MaxPooling2DLayer + +apipkg.initpkg( + __name__, + { + "Convolutional2DLayer": "._convolutional2d_layer:Convolutional2DLayer", + "ConvolutionalTranspose2DLayer": "._convolutional2d_layer:ConvolutionalTranspose2DLayer", + "FlattenLayer": "._flatten_layer:FlattenLayer", + "ForwardLayer": "._forward_layer:ForwardLayer", + "Layer": "._layer:Layer", + "LSTMLayer": "._lstm_layer:LSTMLayer", + "AveragePooling2DLayer": "._pooling2d_layer:AveragePooling2DLayer", + "MaxPooling2DLayer": "._pooling2d_layer:MaxPooling2DLayer", + }, +) + +__all__ = [ + "Convolutional2DLayer", + "ConvolutionalTranspose2DLayer", + "FlattenLayer", + "ForwardLayer", + "Layer", + "LSTMLayer", + "AveragePooling2DLayer", + "MaxPooling2DLayer", +] diff --git a/src/safeds/ml/nn/_convolutional2d_layer.py b/src/safeds/ml/nn/layers/_convolutional2d_layer.py similarity index 99% rename from src/safeds/ml/nn/_convolutional2d_layer.py rename to src/safeds/ml/nn/layers/_convolutional2d_layer.py index f483e668b..3adbf0d8e 100644 --- a/src/safeds/ml/nn/_convolutional2d_layer.py +++ b/src/safeds/ml/nn/layers/_convolutional2d_layer.py @@ -8,11 +8,11 @@ from safeds._utils import _structural_hash from safeds.data.image.typing import ImageSize +from ._layer import Layer + if TYPE_CHECKING: from torch import Tensor, nn -from safeds.ml.nn import Layer - def _create_internal_model( input_size: int, diff --git a/src/safeds/ml/nn/_flatten_layer.py b/src/safeds/ml/nn/layers/_flatten_layer.py similarity index 99% rename from src/safeds/ml/nn/_flatten_layer.py rename to src/safeds/ml/nn/layers/_flatten_layer.py index ea10faa64..5ac58e318 100644 --- a/src/safeds/ml/nn/_flatten_layer.py +++ b/src/safeds/ml/nn/layers/_flatten_layer.py @@ -6,13 +6,13 @@ from safeds._config import _init_default_device from safeds._utils import _structural_hash +from ._layer import Layer + if TYPE_CHECKING: from torch import Tensor, nn from safeds.data.image.typing import ImageSize -from safeds.ml.nn import Layer - def _create_internal_model() -> nn.Module: from torch import nn diff --git a/src/safeds/ml/nn/_forward_layer.py b/src/safeds/ml/nn/layers/_forward_layer.py similarity index 99% rename from src/safeds/ml/nn/_forward_layer.py rename to src/safeds/ml/nn/layers/_forward_layer.py index 59b4abba8..ff13cea16 100644 --- a/src/safeds/ml/nn/_forward_layer.py +++ b/src/safeds/ml/nn/layers/_forward_layer.py @@ -3,15 +3,15 @@ from typing import TYPE_CHECKING, Any from safeds._config import _init_default_device +from safeds._utils import _structural_hash from safeds._validation import _check_bounds, _ClosedBound from safeds.data.image.typing import ImageSize +from ._layer import Layer + if TYPE_CHECKING: from torch import Tensor, nn -from safeds._utils import _structural_hash -from safeds.ml.nn import Layer - def _create_internal_model(input_size: int, output_size: int, activation_function: str) -> nn.Module: from torch import nn diff --git a/src/safeds/ml/nn/_layer.py b/src/safeds/ml/nn/layers/_layer.py similarity index 100% rename from src/safeds/ml/nn/_layer.py rename to src/safeds/ml/nn/layers/_layer.py diff --git a/src/safeds/ml/nn/_lstm_layer.py b/src/safeds/ml/nn/layers/_lstm_layer.py similarity index 99% rename from src/safeds/ml/nn/_lstm_layer.py rename to src/safeds/ml/nn/layers/_lstm_layer.py index 77e2f05f4..722a5294f 100644 --- a/src/safeds/ml/nn/_lstm_layer.py +++ b/src/safeds/ml/nn/layers/_lstm_layer.py @@ -1,19 +1,18 @@ from __future__ import annotations +import sys from typing import TYPE_CHECKING, Any from safeds._config import _init_default_device +from safeds._utils import _structural_hash from safeds._validation import _check_bounds, _ClosedBound from safeds.data.image.typing import ImageSize +from ._layer import Layer + if TYPE_CHECKING: from torch import Tensor, nn -import sys - -from safeds._utils import _structural_hash -from safeds.ml.nn import Layer - def _create_internal_model(input_size: int, output_size: int, activation_function: str) -> nn.Module: from torch import nn diff --git a/src/safeds/ml/nn/_pooling2d_layer.py b/src/safeds/ml/nn/layers/_pooling2d_layer.py similarity index 98% rename from src/safeds/ml/nn/_pooling2d_layer.py rename to src/safeds/ml/nn/layers/_pooling2d_layer.py index dd4584b2e..9a615b376 100644 --- a/src/safeds/ml/nn/_pooling2d_layer.py +++ b/src/safeds/ml/nn/layers/_pooling2d_layer.py @@ -8,11 +8,11 @@ from safeds._utils import _structural_hash from safeds.data.image.typing import ImageSize +from ._layer import Layer + if TYPE_CHECKING: from torch import Tensor, nn -from safeds.ml.nn import Layer - def _create_internal_model(strategy: Literal["max", "avg"], kernel_size: int, padding: int, stride: int) -> nn.Module: from torch import nn @@ -194,7 +194,7 @@ def __init__(self, kernel_size: int, *, stride: int = -1, padding: int = 0) -> N super().__init__("max", kernel_size, stride=stride, padding=padding) -class AvgPooling2DLayer(_Pooling2DLayer): +class AveragePooling2DLayer(_Pooling2DLayer): def __init__(self, kernel_size: int, *, stride: int = -1, padding: int = 0) -> None: """ diff --git a/tests/safeds/data/tabular/containers/_table/test_from_csv_file.py b/tests/safeds/data/tabular/containers/_table/test_from_csv_file.py index 55521a221..5b551578f 100644 --- a/tests/safeds/data/tabular/containers/_table/test_from_csv_file.py +++ b/tests/safeds/data/tabular/containers/_table/test_from_csv_file.py @@ -23,15 +23,15 @@ def test_should_create_table_from_csv_file(path: str | Path, expected: Table) -> @pytest.mark.parametrize( - ("path", "expected_error_message"), + "path", [ - ("test_table_from_csv_file_invalid.csv", r"test_table_from_csv_file_invalid.csv\" does not exist"), - (Path("test_table_from_csv_file_invalid.csv"), r"test_table_from_csv_file_invalid.csv\" does not exist"), + "test_table_from_csv_file_invalid.csv", + Path("test_table_from_csv_file_invalid.csv"), ], ids=["by String", "by path"], ) -def test_should_raise_error_if_file_not_found(path: str | Path, expected_error_message: str) -> None: - with pytest.raises(FileNotFoundError, match=expected_error_message): +def test_should_raise_error_if_file_not_found(path: str | Path) -> None: + with pytest.raises(FileNotFoundError): Table.from_csv_file(resolve_resource_path(path)) diff --git a/tests/safeds/ml/nn/converters/__init__.py b/tests/safeds/ml/nn/converters/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/safeds/ml/nn/test_input_conversion_image.py b/tests/safeds/ml/nn/converters/test_input_conversion_image.py similarity index 99% rename from tests/safeds/ml/nn/test_input_conversion_image.py rename to tests/safeds/ml/nn/converters/test_input_conversion_image.py index f8928cd62..15eba9a50 100644 --- a/tests/safeds/ml/nn/test_input_conversion_image.py +++ b/tests/safeds/ml/nn/converters/test_input_conversion_image.py @@ -5,7 +5,7 @@ from safeds.data.image.typing import ImageSize from safeds.data.labeled.containers import ImageDataset from safeds.data.tabular.containers import Column, Table -from safeds.ml.nn import InputConversionImage +from safeds.ml.nn.converters import InputConversionImage from tests.helpers import images_all, resolve_resource_path diff --git a/tests/safeds/ml/nn/test_table_conversion.py b/tests/safeds/ml/nn/converters/test_input_conversion_table.py similarity index 88% rename from tests/safeds/ml/nn/test_table_conversion.py rename to tests/safeds/ml/nn/converters/test_input_conversion_table.py index d0bee7b33..3bd04e6f9 100644 --- a/tests/safeds/ml/nn/test_table_conversion.py +++ b/tests/safeds/ml/nn/converters/test_input_conversion_table.py @@ -1,5 +1,5 @@ from safeds.data.labeled.containers import TabularDataset -from safeds.ml.nn import ( +from safeds.ml.nn.converters import ( InputConversionTable, ) diff --git a/tests/safeds/ml/nn/test_input_conversion_time_series.py b/tests/safeds/ml/nn/converters/test_input_conversion_time_series.py similarity index 92% rename from tests/safeds/ml/nn/test_input_conversion_time_series.py rename to tests/safeds/ml/nn/converters/test_input_conversion_time_series.py index c40c0b941..3e43eb72f 100644 --- a/tests/safeds/ml/nn/test_input_conversion_time_series.py +++ b/tests/safeds/ml/nn/converters/test_input_conversion_time_series.py @@ -1,10 +1,14 @@ from safeds.data.tabular.containers import Table from safeds.ml.nn import ( - InputConversionTimeSeries, - LSTMLayer, NeuralNetworkRegressor, +) +from safeds.ml.nn.converters import ( + InputConversionTimeSeries, OutputConversionTimeSeries, ) +from safeds.ml.nn.layers import ( + LSTMLayer, +) def test_should_raise_if_is_fitted_is_set_correctly_lstm() -> None: diff --git a/tests/safeds/ml/nn/test_output_conversion_image.py b/tests/safeds/ml/nn/converters/test_output_conversion_image.py similarity index 96% rename from tests/safeds/ml/nn/test_output_conversion_image.py rename to tests/safeds/ml/nn/converters/test_output_conversion_image.py index afa6a69db..fe84040aa 100644 --- a/tests/safeds/ml/nn/test_output_conversion_image.py +++ b/tests/safeds/ml/nn/converters/test_output_conversion_image.py @@ -6,8 +6,12 @@ from safeds.data.image.containers._single_size_image_list import _SingleSizeImageList from safeds.data.tabular.containers import Table from safeds.data.tabular.transformation import OneHotEncoder -from safeds.ml.nn import OutputConversionImageToColumn, OutputConversionImageToImage, OutputConversionImageToTable -from safeds.ml.nn._output_conversion_image import _OutputConversionImage +from safeds.ml.nn.converters import ( + OutputConversionImageToColumn, + OutputConversionImageToImage, + OutputConversionImageToTable, +) +from safeds.ml.nn.converters._output_conversion_image import _OutputConversionImage class TestDataConversionImage: diff --git a/tests/safeds/ml/nn/test_output_conversion_time_series.py b/tests/safeds/ml/nn/converters/test_output_conversion_time_series.py similarity index 98% rename from tests/safeds/ml/nn/test_output_conversion_time_series.py rename to tests/safeds/ml/nn/converters/test_output_conversion_time_series.py index 4267c9827..f85266d40 100644 --- a/tests/safeds/ml/nn/test_output_conversion_time_series.py +++ b/tests/safeds/ml/nn/converters/test_output_conversion_time_series.py @@ -1,7 +1,8 @@ -import pytest import sys + +import pytest from safeds.data.tabular.containers import Table -from safeds.ml.nn import OutputConversionTimeSeries +from safeds.ml.nn.converters import OutputConversionTimeSeries def test_output_conversion_time_series() -> None: diff --git a/tests/safeds/ml/nn/layers/__init__.py b/tests/safeds/ml/nn/layers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/safeds/ml/nn/test_convolutional2d_layer.py b/tests/safeds/ml/nn/layers/test_convolutional2d_layer.py similarity index 99% rename from tests/safeds/ml/nn/test_convolutional2d_layer.py rename to tests/safeds/ml/nn/layers/test_convolutional2d_layer.py index 9a9a50d6c..fdc234a52 100644 --- a/tests/safeds/ml/nn/test_convolutional2d_layer.py +++ b/tests/safeds/ml/nn/layers/test_convolutional2d_layer.py @@ -3,7 +3,7 @@ import pytest from safeds.data.image.typing import ImageSize -from safeds.ml.nn import Convolutional2DLayer, ConvolutionalTranspose2DLayer +from safeds.ml.nn.layers import Convolutional2DLayer, ConvolutionalTranspose2DLayer from torch import nn diff --git a/tests/safeds/ml/nn/test_flatten_layer.py b/tests/safeds/ml/nn/layers/test_flatten_layer.py similarity index 97% rename from tests/safeds/ml/nn/test_flatten_layer.py rename to tests/safeds/ml/nn/layers/test_flatten_layer.py index e3319db9a..9e998ddc1 100644 --- a/tests/safeds/ml/nn/test_flatten_layer.py +++ b/tests/safeds/ml/nn/layers/test_flatten_layer.py @@ -3,7 +3,7 @@ import pytest from safeds.data.image.typing import ImageSize from safeds.data.tabular.containers import Table -from safeds.ml.nn import FlattenLayer +from safeds.ml.nn.layers import FlattenLayer from torch import nn diff --git a/tests/safeds/ml/nn/test_forward_layer.py b/tests/safeds/ml/nn/layers/test_forward_layer.py similarity index 99% rename from tests/safeds/ml/nn/test_forward_layer.py rename to tests/safeds/ml/nn/layers/test_forward_layer.py index 54c055f38..c340e2fdf 100644 --- a/tests/safeds/ml/nn/test_forward_layer.py +++ b/tests/safeds/ml/nn/layers/test_forward_layer.py @@ -4,7 +4,7 @@ import pytest from safeds.data.image.typing import ImageSize from safeds.exceptions import OutOfBoundsError -from safeds.ml.nn import ForwardLayer +from safeds.ml.nn.layers import ForwardLayer from torch import nn diff --git a/tests/safeds/ml/nn/test_lstm_layer.py b/tests/safeds/ml/nn/layers/test_lstm_layer.py similarity index 99% rename from tests/safeds/ml/nn/test_lstm_layer.py rename to tests/safeds/ml/nn/layers/test_lstm_layer.py index 224a757a6..386cfd9db 100644 --- a/tests/safeds/ml/nn/test_lstm_layer.py +++ b/tests/safeds/ml/nn/layers/test_lstm_layer.py @@ -4,7 +4,7 @@ import pytest from safeds.data.image.typing import ImageSize from safeds.exceptions import OutOfBoundsError -from safeds.ml.nn import LSTMLayer +from safeds.ml.nn.layers import LSTMLayer from torch import nn diff --git a/tests/safeds/ml/nn/test_pooling2d_layer.py b/tests/safeds/ml/nn/layers/test_pooling2d_layer.py similarity index 80% rename from tests/safeds/ml/nn/test_pooling2d_layer.py rename to tests/safeds/ml/nn/layers/test_pooling2d_layer.py index 2218c7539..e5dc243f5 100644 --- a/tests/safeds/ml/nn/test_pooling2d_layer.py +++ b/tests/safeds/ml/nn/layers/test_pooling2d_layer.py @@ -4,8 +4,8 @@ import pytest from safeds.data.image.typing import ImageSize from safeds.data.tabular.containers import Table -from safeds.ml.nn import AvgPooling2DLayer, MaxPooling2DLayer -from safeds.ml.nn._pooling2d_layer import _Pooling2DLayer +from safeds.ml.nn.layers import AveragePooling2DLayer, MaxPooling2DLayer +from safeds.ml.nn.layers._pooling2d_layer import _Pooling2DLayer from torch import nn @@ -64,8 +64,8 @@ class TestEq: [ (MaxPooling2DLayer(2), MaxPooling2DLayer(2)), (MaxPooling2DLayer(2, stride=3, padding=4), MaxPooling2DLayer(2, stride=3, padding=4)), - (AvgPooling2DLayer(2), AvgPooling2DLayer(2)), - (AvgPooling2DLayer(2, stride=3, padding=4), AvgPooling2DLayer(2, stride=3, padding=4)), + (AveragePooling2DLayer(2), AveragePooling2DLayer(2)), + (AveragePooling2DLayer(2, stride=3, padding=4), AveragePooling2DLayer(2, stride=3, padding=4)), ], ) def test_should_be_equal( @@ -80,8 +80,8 @@ def test_should_be_equal( [ MaxPooling2DLayer(2), MaxPooling2DLayer(2, stride=3, padding=4), - AvgPooling2DLayer(2), - AvgPooling2DLayer(2, stride=3, padding=4), + AveragePooling2DLayer(2), + AveragePooling2DLayer(2, stride=3, padding=4), ], ) @pytest.mark.parametrize( @@ -91,10 +91,10 @@ def test_should_be_equal( MaxPooling2DLayer(1, stride=3, padding=4), MaxPooling2DLayer(2, stride=1, padding=4), MaxPooling2DLayer(2, stride=3, padding=1), - AvgPooling2DLayer(1), - AvgPooling2DLayer(1, stride=3, padding=4), - AvgPooling2DLayer(2, stride=1, padding=4), - AvgPooling2DLayer(2, stride=3, padding=1), + AveragePooling2DLayer(1), + AveragePooling2DLayer(1, stride=3, padding=4), + AveragePooling2DLayer(2, stride=1, padding=4), + AveragePooling2DLayer(2, stride=3, padding=1), ], ) def test_should_not_be_equal( @@ -106,7 +106,7 @@ def test_should_not_be_equal( def test_should_be_not_implemented(self) -> None: max_pooling_2d_layer = MaxPooling2DLayer(1) - avg_pooling_2d_layer = AvgPooling2DLayer(1) + avg_pooling_2d_layer = AveragePooling2DLayer(1) other = Table() assert max_pooling_2d_layer.__eq__(other) is NotImplemented assert max_pooling_2d_layer.__eq__(avg_pooling_2d_layer) is NotImplemented @@ -120,8 +120,8 @@ class TestHash: [ (MaxPooling2DLayer(2), MaxPooling2DLayer(2)), (MaxPooling2DLayer(2, stride=3, padding=4), MaxPooling2DLayer(2, stride=3, padding=4)), - (AvgPooling2DLayer(2), AvgPooling2DLayer(2)), - (AvgPooling2DLayer(2, stride=3, padding=4), AvgPooling2DLayer(2, stride=3, padding=4)), + (AveragePooling2DLayer(2), AveragePooling2DLayer(2)), + (AveragePooling2DLayer(2, stride=3, padding=4), AveragePooling2DLayer(2, stride=3, padding=4)), ], ) def test_hash_should_be_equal( @@ -136,8 +136,8 @@ def test_hash_should_be_equal( [ MaxPooling2DLayer(2), MaxPooling2DLayer(2, stride=3, padding=4), - AvgPooling2DLayer(2), - AvgPooling2DLayer(2, stride=3, padding=4), + AveragePooling2DLayer(2), + AveragePooling2DLayer(2, stride=3, padding=4), ], ) @pytest.mark.parametrize( @@ -147,10 +147,10 @@ def test_hash_should_be_equal( MaxPooling2DLayer(1, stride=3, padding=4), MaxPooling2DLayer(2, stride=1, padding=4), MaxPooling2DLayer(2, stride=3, padding=1), - AvgPooling2DLayer(1), - AvgPooling2DLayer(1, stride=3, padding=4), - AvgPooling2DLayer(2, stride=1, padding=4), - AvgPooling2DLayer(2, stride=3, padding=1), + AveragePooling2DLayer(1), + AveragePooling2DLayer(1, stride=3, padding=4), + AveragePooling2DLayer(2, stride=1, padding=4), + AveragePooling2DLayer(2, stride=3, padding=1), ], ) def test_hash_should_not_be_equal( @@ -167,8 +167,8 @@ class TestSizeOf: [ MaxPooling2DLayer(2), MaxPooling2DLayer(2, stride=3, padding=4), - AvgPooling2DLayer(2), - AvgPooling2DLayer(2, stride=3, padding=4), + AveragePooling2DLayer(2), + AveragePooling2DLayer(2, stride=3, padding=4), ], ) def test_should_size_be_greater_than_normal_object(self, pooling_2d_layer: _Pooling2DLayer) -> None: diff --git a/tests/safeds/ml/nn/test_cnn_workflow.py b/tests/safeds/ml/nn/test_cnn_workflow.py index 78c221cad..c4e581a3e 100644 --- a/tests/safeds/ml/nn/test_cnn_workflow.py +++ b/tests/safeds/ml/nn/test_cnn_workflow.py @@ -9,25 +9,30 @@ from safeds.data.tabular.containers import Column, Table from safeds.data.tabular.transformation import OneHotEncoder from safeds.ml.nn import ( - AvgPooling2DLayer, + NeuralNetworkClassifier, + NeuralNetworkRegressor, +) +from safeds.ml.nn.converters import ( + InputConversionImage, + OutputConversionImageToColumn, + OutputConversionImageToImage, + OutputConversionImageToTable, +) +from safeds.ml.nn.layers import ( + AveragePooling2DLayer, Convolutional2DLayer, ConvolutionalTranspose2DLayer, FlattenLayer, ForwardLayer, - InputConversionImage, MaxPooling2DLayer, - NeuralNetworkClassifier, - NeuralNetworkRegressor, - OutputConversionImageToTable, ) -from safeds.ml.nn._output_conversion_image import OutputConversionImageToColumn, OutputConversionImageToImage from syrupy import SnapshotAssertion from torch.types import Device from tests.helpers import configure_test_with_device, device_cpu, device_cuda, images_all, resolve_resource_path if TYPE_CHECKING: - from safeds.ml.nn import Layer + from safeds.ml.nn.layers import Layer class TestImageToTableClassifier: @@ -146,7 +151,7 @@ def test_should_train_and_predict_model( image_dataset = ImageDataset(image_list, image_classes, shuffle=True) num_of_classes: int = image_dataset.output_size if isinstance(image_dataset.output_size, int) else 0 - layers = [Convolutional2DLayer(1, 2), AvgPooling2DLayer(10), FlattenLayer(), ForwardLayer(num_of_classes)] + layers = [Convolutional2DLayer(1, 2), AveragePooling2DLayer(10), FlattenLayer(), ForwardLayer(num_of_classes)] nn_original = NeuralNetworkClassifier( InputConversionImage(image_dataset.input_size), layers, diff --git a/tests/safeds/ml/nn/test_forward_workflow.py b/tests/safeds/ml/nn/test_forward_workflow.py index 08102224c..6d27ba82c 100644 --- a/tests/safeds/ml/nn/test_forward_workflow.py +++ b/tests/safeds/ml/nn/test_forward_workflow.py @@ -3,11 +3,15 @@ from safeds.data.tabular.containers import Table from safeds.data.tabular.transformation import StandardScaler from safeds.ml.nn import ( - ForwardLayer, - InputConversionTable, NeuralNetworkRegressor, +) +from safeds.ml.nn.converters import ( + InputConversionTable, OutputConversionTable, ) +from safeds.ml.nn.layers import ( + ForwardLayer, +) from torch.types import Device from tests.helpers import configure_test_with_device, get_devices, get_devices_ids, resolve_resource_path diff --git a/tests/safeds/ml/nn/test_lstm_workflow.py b/tests/safeds/ml/nn/test_lstm_workflow.py index daa7cc6b1..991019aeb 100644 --- a/tests/safeds/ml/nn/test_lstm_workflow.py +++ b/tests/safeds/ml/nn/test_lstm_workflow.py @@ -3,12 +3,16 @@ from safeds.data.tabular.containers import Table from safeds.data.tabular.transformation import RangeScaler from safeds.ml.nn import ( - ForwardLayer, - InputConversionTimeSeries, - LSTMLayer, NeuralNetworkRegressor, +) +from safeds.ml.nn.converters import ( + InputConversionTimeSeries, OutputConversionTimeSeries, ) +from safeds.ml.nn.layers import ( + ForwardLayer, + LSTMLayer, +) from torch.types import Device from tests.helpers import configure_test_with_device, get_devices, get_devices_ids, resolve_resource_path diff --git a/tests/safeds/ml/nn/test_model.py b/tests/safeds/ml/nn/test_model.py index 10d8c0c2f..f569c633b 100644 --- a/tests/safeds/ml/nn/test_model.py +++ b/tests/safeds/ml/nn/test_model.py @@ -10,25 +10,29 @@ OutOfBoundsError, ) from safeds.ml.nn import ( - AvgPooling2DLayer, - Convolutional2DLayer, - ConvolutionalTranspose2DLayer, - FlattenLayer, - ForwardLayer, + NeuralNetworkClassifier, + NeuralNetworkRegressor, +) +from safeds.ml.nn.converters import ( InputConversion, InputConversionImage, InputConversionTable, - Layer, - LSTMLayer, - MaxPooling2DLayer, - NeuralNetworkClassifier, - NeuralNetworkRegressor, OutputConversion, OutputConversionImageToImage, OutputConversionImageToTable, OutputConversionTable, ) -from safeds.ml.nn._output_conversion_image import OutputConversionImageToColumn +from safeds.ml.nn.converters._output_conversion_image import OutputConversionImageToColumn +from safeds.ml.nn.layers import ( + AveragePooling2DLayer, + Convolutional2DLayer, + ConvolutionalTranspose2DLayer, + FlattenLayer, + ForwardLayer, + Layer, + LSTMLayer, + MaxPooling2DLayer, +) from torch.types import Device from tests.helpers import configure_test_with_device, get_devices, get_devices_ids @@ -331,7 +335,7 @@ def callback_was_called(self) -> bool: ), ( InputConversionTable(), - [AvgPooling2DLayer(1)], + [AveragePooling2DLayer(1)], OutputConversionTable(), r"You cannot use a 2-dimensional layer with 1-dimensional data.", ), @@ -385,13 +389,13 @@ def callback_was_called(self) -> bool: ), ( InputConversionImage(ImageSize(1, 1, 1)), - [AvgPooling2DLayer(1)], + [AveragePooling2DLayer(1)], OutputConversionImageToTable(), r"The output data would be 2-dimensional but the provided output conversion uses 1-dimensional data.", ), ( InputConversionImage(ImageSize(1, 1, 1)), - [AvgPooling2DLayer(1)], + [AveragePooling2DLayer(1)], OutputConversionImageToColumn(), r"The output data would be 2-dimensional but the provided output conversion uses 1-dimensional data.", ), @@ -433,13 +437,13 @@ def callback_was_called(self) -> bool: ), ( InputConversionImage(ImageSize(1, 1, 1)), - [FlattenLayer(), AvgPooling2DLayer(1)], + [FlattenLayer(), AveragePooling2DLayer(1)], OutputConversionImageToTable(), r"You cannot use a 2-dimensional layer with 1-dimensional data.", ), ( InputConversionImage(ImageSize(1, 1, 1)), - [FlattenLayer(), AvgPooling2DLayer(1)], + [FlattenLayer(), AveragePooling2DLayer(1)], OutputConversionImageToColumn(), r"You cannot use a 2-dimensional layer with 1-dimensional data.", ), @@ -724,7 +728,7 @@ def callback_was_called(self) -> bool: ), ( InputConversionTable(), - [AvgPooling2DLayer(1)], + [AveragePooling2DLayer(1)], OutputConversionTable(), r"You cannot use a 2-dimensional layer with 1-dimensional data.", ), @@ -772,7 +776,7 @@ def callback_was_called(self) -> bool: ), ( InputConversionImage(ImageSize(1, 1, 1)), - [FlattenLayer(), AvgPooling2DLayer(1)], + [FlattenLayer(), AveragePooling2DLayer(1)], OutputConversionImageToImage(), r"You cannot use a 2-dimensional layer with 1-dimensional data.", ),