Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: actually use kernel of support vector machines for training #681

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 34 additions & 80 deletions src/safeds/ml/classical/classification/_support_vector_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import sys
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

from safeds._utils import _structural_hash
from safeds.exceptions import ClosedBound, OpenBound, OutOfBoundsError
Expand All @@ -21,15 +21,8 @@ class SupportVectorMachineKernel(ABC):
"""The abstract base class of the different subclasses supported by the `Kernel`."""

@abstractmethod
def _get_sklearn_kernel(self) -> object:
"""
Get the kernel of the given SupportVectorMachine.

Returns
-------
kernel:
The kernel of the SupportVectorMachine.
"""
def _get_sklearn_arguments(self) -> dict[str, Any]:
"""Return the arguments to pass to scikit-learn."""

@abstractmethod
def __eq__(self, other: object) -> bool:
Expand Down Expand Up @@ -80,16 +73,20 @@ def __hash__(self) -> int:
return _structural_hash(Classifier.__hash__(self), self._target_name, self._feature_names, self._c, self.kernel)

def __init__(self, *, c: float = 1.0, kernel: SupportVectorMachineKernel | None = None) -> None:
# Inputs
if c <= 0:
raise OutOfBoundsError(c, name="c", lower_bound=OpenBound(0))
if kernel is None:
kernel = self.Kernel.RadialBasisFunction()

# Internal state
self._wrapped_classifier: sk_SVC | None = None
self._feature_names: list[str] | None = None
self._target_name: str | None = None

# Hyperparameters
if c <= 0:
raise OutOfBoundsError(c, name="c", lower_bound=OpenBound(0))
self._c = c
self._kernel = kernel
self._c: float = c
self._kernel: SupportVectorMachineKernel = kernel

@property
def c(self) -> float:
Expand All @@ -104,7 +101,7 @@ def c(self) -> float:
return self._c

@property
def kernel(self) -> SupportVectorMachineKernel | None:
def kernel(self) -> SupportVectorMachineKernel:
"""
Get the type of kernel used.

Expand All @@ -117,16 +114,10 @@ def kernel(self) -> SupportVectorMachineKernel | None:

class Kernel:
class Linear(SupportVectorMachineKernel):
def _get_sklearn_kernel(self) -> str:
"""
Get the name of the linear kernel.

Returns
-------
result:
The name of the linear kernel.
"""
return "linear"
def _get_sklearn_arguments(self) -> dict[str, Any]:
return {
"kernel": "linear",
}

def __eq__(self, other: object) -> bool:
if not isinstance(other, SupportVectorMachineClassifier.Kernel.Linear):
Expand All @@ -141,16 +132,16 @@ def __init__(self, degree: int):
raise OutOfBoundsError(degree, name="degree", lower_bound=ClosedBound(1))
self._degree = degree

def _get_sklearn_kernel(self) -> str:
"""
Get the name of the polynomial kernel.
@property
def degree(self) -> int:
"""The degree of the polynomial kernel."""
return self._degree

Returns
-------
result:
The name of the polynomial kernel.
"""
return "poly"
def _get_sklearn_arguments(self) -> dict[str, Any]:
return {
"kernel": "poly",
"degree": self._degree,
}

def __eq__(self, other: object) -> bool:
if not isinstance(other, SupportVectorMachineClassifier.Kernel.Polynomial):
Expand All @@ -172,16 +163,10 @@ def __sizeof__(self) -> int:
return sys.getsizeof(self._degree)

class Sigmoid(SupportVectorMachineKernel):
def _get_sklearn_kernel(self) -> str:
"""
Get the name of the sigmoid kernel.

Returns
-------
result:
The name of the sigmoid kernel.
"""
return "sigmoid"
def _get_sklearn_arguments(self) -> dict[str, Any]:
return {
"kernel": "sigmoid",
}

def __eq__(self, other: object) -> bool:
if not isinstance(other, SupportVectorMachineClassifier.Kernel.Sigmoid):
Expand All @@ -191,16 +176,10 @@ def __eq__(self, other: object) -> bool:
__hash__ = SupportVectorMachineKernel.__hash__

class RadialBasisFunction(SupportVectorMachineKernel):
def _get_sklearn_kernel(self) -> str:
"""
Get the name of the radial basis function (RBF) kernel.

Returns
-------
result:
The name of the RBF kernel.
"""
return "rbf"
def _get_sklearn_arguments(self) -> dict[str, Any]:
return {
"kernel": "rbf",
}

def __eq__(self, other: object) -> bool:
if not isinstance(other, SupportVectorMachineClassifier.Kernel.RadialBasisFunction):
Expand All @@ -209,31 +188,6 @@ def __eq__(self, other: object) -> bool:

__hash__ = SupportVectorMachineKernel.__hash__

def _get_kernel_name(self) -> str:
"""
Get the name of the kernel.

Returns
-------
result:
The name of the kernel.

Raises
------
TypeError
If the kernel type is invalid.
"""
if isinstance(self.kernel, SupportVectorMachineClassifier.Kernel.Linear):
return "linear"
elif isinstance(self.kernel, SupportVectorMachineClassifier.Kernel.Polynomial):
return "poly"
elif isinstance(self.kernel, SupportVectorMachineClassifier.Kernel.Sigmoid):
return "sigmoid"
elif isinstance(self.kernel, SupportVectorMachineClassifier.Kernel.RadialBasisFunction):
return "rbf"
else:
raise TypeError("Invalid kernel type.")

def fit(self, training_set: TabularDataset) -> SupportVectorMachineClassifier:
"""
Create a copy of this classifier and fit it with the given training data.
Expand Down Expand Up @@ -322,4 +276,4 @@ def _get_sklearn_classifier(self) -> ClassifierMixin:
"""
from sklearn.svm import SVC as sk_SVC # noqa: N811

return sk_SVC(C=self._c)
return sk_SVC(C=self._c, **(self._kernel._get_sklearn_arguments()))
118 changes: 36 additions & 82 deletions src/safeds/ml/classical/regression/_support_vector_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import sys
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

from safeds._utils import _structural_hash
from safeds.exceptions import ClosedBound, OpenBound, OutOfBoundsError
Expand All @@ -11,7 +11,7 @@

if TYPE_CHECKING:
from sklearn.base import RegressorMixin
from sklearn.svm import SVR as sk_SVR # noqa: N811
from sklearn.svm import SVC as sk_SVR # noqa: N811

from safeds.data.labeled.containers import TabularDataset
from safeds.data.tabular.containers import Table
Expand All @@ -21,15 +21,8 @@ class SupportVectorMachineKernel(ABC):
"""The abstract base class of the different subclasses supported by the `Kernel`."""

@abstractmethod
def _get_sklearn_kernel(self) -> object:
"""
Get the kernel of the given SupportVectorMachine.

Returns
-------
kernel:
The kernel of the SupportVectorMachine.
"""
def _get_sklearn_arguments(self) -> dict[str, Any]:
"""Return the arguments to pass to scikit-learn."""

@abstractmethod
def __eq__(self, other: object) -> bool:
Expand Down Expand Up @@ -80,16 +73,20 @@ def __hash__(self) -> int:
return _structural_hash(Regressor.__hash__(self), self._target_name, self._feature_names, self._c, self.kernel)

def __init__(self, *, c: float = 1.0, kernel: SupportVectorMachineKernel | None = None) -> None:
# Inputs
if c <= 0:
raise OutOfBoundsError(c, name="c", lower_bound=OpenBound(0))
if kernel is None:
kernel = self.Kernel.RadialBasisFunction()

# Internal state
self._wrapped_regressor: sk_SVR | None = None
self._feature_names: list[str] | None = None
self._target_name: str | None = None

# Hyperparameters
if c <= 0:
raise OutOfBoundsError(c, name="c", lower_bound=OpenBound(0))
self._c = c
self._kernel = kernel
self._c: float = c
self._kernel: SupportVectorMachineKernel = kernel

@property
def c(self) -> float:
Expand All @@ -104,7 +101,7 @@ def c(self) -> float:
return self._c

@property
def kernel(self) -> SupportVectorMachineKernel | None:
def kernel(self) -> SupportVectorMachineKernel:
"""
Get the type of kernel used.

Expand All @@ -117,16 +114,10 @@ def kernel(self) -> SupportVectorMachineKernel | None:

class Kernel:
class Linear(SupportVectorMachineKernel):
def _get_sklearn_kernel(self) -> str:
"""
Get the name of the linear kernel.

Returns
-------
result:
The name of the linear kernel.
"""
return "linear"
def _get_sklearn_arguments(self) -> dict[str, Any]:
return {
"kernel": "linear",
}

def __eq__(self, other: object) -> bool:
if not isinstance(other, SupportVectorMachineRegressor.Kernel.Linear):
Expand All @@ -141,16 +132,16 @@ def __init__(self, degree: int):
raise OutOfBoundsError(degree, name="degree", lower_bound=ClosedBound(1))
self._degree = degree

def _get_sklearn_kernel(self) -> str:
"""
Get the name of the polynomial kernel.
@property
def degree(self) -> int:
"""The degree of the polynomial kernel."""
return self._degree

Returns
-------
result:
The name of the polynomial kernel.
"""
return "poly"
def _get_sklearn_arguments(self) -> dict[str, Any]:
return {
"kernel": "poly",
"degree": self._degree,
}

def __eq__(self, other: object) -> bool:
if not isinstance(other, SupportVectorMachineRegressor.Kernel.Polynomial):
Expand All @@ -172,16 +163,10 @@ def __sizeof__(self) -> int:
return sys.getsizeof(self._degree)

class Sigmoid(SupportVectorMachineKernel):
def _get_sklearn_kernel(self) -> str:
"""
Get the name of the sigmoid kernel.

Returns
-------
result:
The name of the sigmoid kernel.
"""
return "sigmoid"
def _get_sklearn_arguments(self) -> dict[str, Any]:
return {
"kernel": "sigmoid",
}

def __eq__(self, other: object) -> bool:
if not isinstance(other, SupportVectorMachineRegressor.Kernel.Sigmoid):
Expand All @@ -191,16 +176,10 @@ def __eq__(self, other: object) -> bool:
__hash__ = SupportVectorMachineKernel.__hash__

class RadialBasisFunction(SupportVectorMachineKernel):
def _get_sklearn_kernel(self) -> str:
"""
Get the name of the radial basis function (RBF) kernel.

Returns
-------
result:
The name of the RBF kernel.
"""
return "rbf"
def _get_sklearn_arguments(self) -> dict[str, Any]:
return {
"kernel": "rbf",
}

def __eq__(self, other: object) -> bool:
if not isinstance(other, SupportVectorMachineRegressor.Kernel.RadialBasisFunction):
Expand All @@ -209,31 +188,6 @@ def __eq__(self, other: object) -> bool:

__hash__ = SupportVectorMachineKernel.__hash__

def _get_kernel_name(self) -> str:
"""
Get the name of the kernel.

Returns
-------
result:
The name of the kernel.

Raises
------
TypeError
If the kernel type is invalid.
"""
if isinstance(self.kernel, SupportVectorMachineRegressor.Kernel.Linear):
return "linear"
elif isinstance(self.kernel, SupportVectorMachineRegressor.Kernel.Polynomial):
return "poly"
elif isinstance(self.kernel, SupportVectorMachineRegressor.Kernel.Sigmoid):
return "sigmoid"
elif isinstance(self.kernel, SupportVectorMachineRegressor.Kernel.RadialBasisFunction):
return "rbf"
else:
raise TypeError("Invalid kernel type.")

def fit(self, training_set: TabularDataset) -> SupportVectorMachineRegressor:
"""
Create a copy of this regressor and fit it with the given training data.
Expand Down Expand Up @@ -320,6 +274,6 @@ def _get_sklearn_regressor(self) -> RegressorMixin:
wrapped_regressor:
The sklearn Regressor.
"""
from sklearn.svm import SVR as sk_SVR # noqa: N811
from sklearn.svm import SVC as sk_SVR # noqa: N811

return sk_SVR(C=self._c)
return sk_SVR(C=self._c, **(self._kernel._get_sklearn_arguments()))
Loading