diff --git a/src/safeds/ml/classical/classification/__init__.py b/src/safeds/ml/classical/classification/__init__.py index dcae90c57..c048ea225 100644 --- a/src/safeds/ml/classical/classification/__init__.py +++ b/src/safeds/ml/classical/classification/__init__.py @@ -7,6 +7,7 @@ from ._k_nearest_neighbors import KNearestNeighbors from ._logistic_regression import LogisticRegression from ._random_forest import RandomForest +from ._support_vector_machine import SupportVectorMachine __all__ = [ "AdaBoost", @@ -16,4 +17,5 @@ "KNearestNeighbors", "LogisticRegression", "RandomForest", + "SupportVectorMachine", ] diff --git a/src/safeds/ml/classical/classification/_support_vector_machine.py b/src/safeds/ml/classical/classification/_support_vector_machine.py new file mode 100644 index 000000000..7d066deb8 --- /dev/null +++ b/src/safeds/ml/classical/classification/_support_vector_machine.py @@ -0,0 +1,90 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from sklearn.svm import SVC as sk_SVC # noqa: N811 + +from safeds.ml.classical._util_sklearn import fit, predict + +from ._classifier import Classifier + +if TYPE_CHECKING: + from safeds.data.tabular.containers import Table, TaggedTable + + +class SupportVectorMachine(Classifier): + """Support vector machine.""" + + def __init__(self) -> None: + self._wrapped_classifier: sk_SVC | None = None + self._feature_names: list[str] | None = None + self._target_name: str | None = None + + def fit(self, training_set: TaggedTable) -> SupportVectorMachine: + """ + Create a copy of this classifier and fit it with the given training data. + + This classifier is not modified. + + Parameters + ---------- + training_set : TaggedTable + The training data containing the feature and target vectors. + + Returns + ------- + fitted_classifier : SupportVectorMachine + The fitted classifier. + + Raises + ------ + LearningError + If the training data contains invalid values or if the training failed. + """ + wrapped_classifier = sk_SVC() + fit(wrapped_classifier, training_set) + + result = SupportVectorMachine() + result._wrapped_classifier = wrapped_classifier + result._feature_names = training_set.features.column_names + result._target_name = training_set.target.name + + return result + + def predict(self, dataset: Table) -> TaggedTable: + """ + Predict a target vector using a dataset containing feature vectors. The model has to be trained first. + + Parameters + ---------- + dataset : Table + The dataset containing the feature vectors. + + Returns + ------- + table : TaggedTable + A dataset containing the given feature vectors and the predicted target vector. + + Raises + ------ + ModelNotFittedError + If the model has not been fitted yet. + DatasetContainsTargetError + If the dataset contains the target column already. + DatasetMissesFeaturesError + If the dataset misses feature columns. + PredictionError + If predicting with the given dataset failed. + """ + return predict(self._wrapped_classifier, dataset, self._feature_names, self._target_name) + + def is_fitted(self) -> bool: + """ + Check if the classifier is fitted. + + Returns + ------- + is_fitted : bool + Whether the classifier is fitted. + """ + return self._wrapped_classifier is not None diff --git a/src/safeds/ml/classical/regression/__init__.py b/src/safeds/ml/classical/regression/__init__.py index 7e4aceffd..d70456dd9 100644 --- a/src/safeds/ml/classical/regression/__init__.py +++ b/src/safeds/ml/classical/regression/__init__.py @@ -10,6 +10,7 @@ from ._random_forest import RandomForest from ._regressor import Regressor from ._ridge_regression import RidgeRegression +from ._support_vector_machine import SupportVectorMachine __all__ = [ "AdaBoost", @@ -22,4 +23,5 @@ "RandomForest", "Regressor", "RidgeRegression", + "SupportVectorMachine", ] diff --git a/src/safeds/ml/classical/regression/_support_vector_machine.py b/src/safeds/ml/classical/regression/_support_vector_machine.py new file mode 100644 index 000000000..e7d843cbc --- /dev/null +++ b/src/safeds/ml/classical/regression/_support_vector_machine.py @@ -0,0 +1,90 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from sklearn.svm import SVR as sk_SVR # noqa: N811 + +from safeds.ml.classical._util_sklearn import fit, predict + +from ._regressor import Regressor + +if TYPE_CHECKING: + from safeds.data.tabular.containers import Table, TaggedTable + + +class SupportVectorMachine(Regressor): + """Support vector machine.""" + + def __init__(self) -> None: + self._wrapped_regressor: sk_SVR | None = None + self._feature_names: list[str] | None = None + self._target_name: str | None = None + + def fit(self, training_set: TaggedTable) -> SupportVectorMachine: + """ + Create a copy of this regressor and fit it with the given training data. + + This regressor is not modified. + + Parameters + ---------- + training_set : TaggedTable + The training data containing the feature and target vectors. + + Returns + ------- + fitted_regressor : SupportVectorMachine + The fitted regressor. + + Raises + ------ + LearningError + If the training data contains invalid values or if the training failed. + """ + wrapped_regressor = sk_SVR() + fit(wrapped_regressor, training_set) + + result = SupportVectorMachine() + result._wrapped_regressor = wrapped_regressor + result._feature_names = training_set.features.column_names + result._target_name = training_set.target.name + + return result + + def predict(self, dataset: Table) -> TaggedTable: + """ + Predict a target vector using a dataset containing feature vectors. The model has to be trained first. + + Parameters + ---------- + dataset : Table + The dataset containing the feature vectors. + + Returns + ------- + table : TaggedTable + A dataset containing the given feature vectors and the predicted target vector. + + Raises + ------ + ModelNotFittedError + If the model has not been fitted yet. + DatasetContainsTargetError + If the dataset contains the target column already. + DatasetMissesFeaturesError + If the dataset misses feature columns. + PredictionError + If predicting with the given dataset failed. + """ + return predict(self._wrapped_regressor, dataset, self._feature_names, self._target_name) + + def is_fitted(self) -> bool: + """ + Check if the regressor is fitted. + + Returns + ------- + is_fitted : bool + Whether the regressor is fitted. + """ + return self._wrapped_regressor is not None diff --git a/tests/safeds/ml/classical/classification/test_classifier.py b/tests/safeds/ml/classical/classification/test_classifier.py index 350ef707a..1be9af161 100644 --- a/tests/safeds/ml/classical/classification/test_classifier.py +++ b/tests/safeds/ml/classical/classification/test_classifier.py @@ -12,6 +12,7 @@ KNearestNeighbors, LogisticRegression, RandomForest, + SupportVectorMachine, ) from safeds.ml.exceptions import ( DatasetContainsTargetError, @@ -37,7 +38,15 @@ def classifiers() -> list[Classifier]: classifiers : list[Classifier] The list of classifiers to test. """ - return [AdaBoost(), DecisionTree(), GradientBoosting(), KNearestNeighbors(2), LogisticRegression(), RandomForest()] + return [ + AdaBoost(), + DecisionTree(), + GradientBoosting(), + KNearestNeighbors(2), + LogisticRegression(), + RandomForest(), + SupportVectorMachine(), + ] @pytest.fixture() diff --git a/tests/safeds/ml/classical/regression/test_regressor.py b/tests/safeds/ml/classical/regression/test_regressor.py index fa85ce29a..029fd4fa5 100644 --- a/tests/safeds/ml/classical/regression/test_regressor.py +++ b/tests/safeds/ml/classical/regression/test_regressor.py @@ -17,6 +17,7 @@ RandomForest, Regressor, RidgeRegression, + SupportVectorMachine, ) # noinspection PyProtectedMember @@ -55,6 +56,7 @@ def regressors() -> list[Regressor]: LinearRegression(), RandomForest(), RidgeRegression(), + SupportVectorMachine(), ]