PyDLLib 1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- DLL/Data/Metrics.py +626 -0
- DLL/Data/Preprocessing.py +384 -0
- DLL/Data/_DataReader.py +55 -0
- DLL/Data/__init__.py +1 -0
- DLL/DeepLearning/Callbacks/_BackUp.py +71 -0
- DLL/DeepLearning/Callbacks/_BaseCallback.py +43 -0
- DLL/DeepLearning/Callbacks/_EarlyStopping.py +102 -0
- DLL/DeepLearning/Callbacks/_ReduceLROnPlateau.py +90 -0
- DLL/DeepLearning/Callbacks/__init__.py +4 -0
- DLL/DeepLearning/Initialisers/_Initialiser.py +24 -0
- DLL/DeepLearning/Initialisers/_Kaiming_He.py +55 -0
- DLL/DeepLearning/Initialisers/_Xavier_Glorot.py +39 -0
- DLL/DeepLearning/Initialisers/__init__.py +2 -0
- DLL/DeepLearning/Layers/Activations/_Activation.py +23 -0
- DLL/DeepLearning/Layers/Activations/_ReLU.py +51 -0
- DLL/DeepLearning/Layers/Activations/_Sigmoid.py +51 -0
- DLL/DeepLearning/Layers/Activations/_SoftMax.py +83 -0
- DLL/DeepLearning/Layers/Activations/_Tanh.py +47 -0
- DLL/DeepLearning/Layers/Activations/__init__.py +4 -0
- DLL/DeepLearning/Layers/Regularisation/_BaseRegularisation.py +12 -0
- DLL/DeepLearning/Layers/Regularisation/_BatchNormalisation.py +106 -0
- DLL/DeepLearning/Layers/Regularisation/_Dropout.py +79 -0
- DLL/DeepLearning/Layers/Regularisation/_GroupNormalisation.py +112 -0
- DLL/DeepLearning/Layers/Regularisation/_InstanceNormalisation.py +50 -0
- DLL/DeepLearning/Layers/Regularisation/_LayerNormalisation.py +37 -0
- DLL/DeepLearning/Layers/Regularisation/__init__.py +5 -0
- DLL/DeepLearning/Layers/_Add.py +109 -0
- DLL/DeepLearning/Layers/_Attention.py +161 -0
- DLL/DeepLearning/Layers/_BaseLayer.py +87 -0
- DLL/DeepLearning/Layers/_Bidirectional.py +113 -0
- DLL/DeepLearning/Layers/_Conv2D.py +133 -0
- DLL/DeepLearning/Layers/_Dense.py +109 -0
- DLL/DeepLearning/Layers/_DenseKAN.py +184 -0
- DLL/DeepLearning/Layers/_Flatten.py +55 -0
- DLL/DeepLearning/Layers/_Identity.py +50 -0
- DLL/DeepLearning/Layers/_Input.py +11 -0
- DLL/DeepLearning/Layers/_LSTM.py +261 -0
- DLL/DeepLearning/Layers/_LayerList.py +98 -0
- DLL/DeepLearning/Layers/_MaxPooling2D.py +101 -0
- DLL/DeepLearning/Layers/_RNN.py +185 -0
- DLL/DeepLearning/Layers/_Reshape.py +52 -0
- DLL/DeepLearning/Layers/__init__.py +14 -0
- DLL/DeepLearning/Losses/_BCE.py +96 -0
- DLL/DeepLearning/Losses/_BaseLoss.py +18 -0
- DLL/DeepLearning/Losses/_CCE.py +87 -0
- DLL/DeepLearning/Losses/_Exponential.py +101 -0
- DLL/DeepLearning/Losses/_Huber.py +104 -0
- DLL/DeepLearning/Losses/_MAE.py +85 -0
- DLL/DeepLearning/Losses/_MSE.py +86 -0
- DLL/DeepLearning/Losses/__init__.py +6 -0
- DLL/DeepLearning/Model.py +265 -0
- DLL/DeepLearning/Optimisers/_ADADELTA.py +53 -0
- DLL/DeepLearning/Optimisers/_ADAGRAD.py +51 -0
- DLL/DeepLearning/Optimisers/_ADAM.py +65 -0
- DLL/DeepLearning/Optimisers/_BaseOptimiser.py +20 -0
- DLL/DeepLearning/Optimisers/_LBFGS.py +120 -0
- DLL/DeepLearning/Optimisers/_RMSPROP.py +62 -0
- DLL/DeepLearning/Optimisers/_SGD.py +44 -0
- DLL/DeepLearning/Optimisers/__init__.py +6 -0
- DLL/DeepLearning/__init__.py +0 -0
- DLL/Exceptions/_Exceptions.py +19 -0
- DLL/Exceptions/__init__.py +2 -0
- DLL/MachineLearning/SupervisedLearning/Calibration/_CalibratedClassifier.py +88 -0
- DLL/MachineLearning/SupervisedLearning/Calibration/_IsotonicRegression.py +114 -0
- DLL/MachineLearning/SupervisedLearning/Calibration/__init__.py +2 -0
- DLL/MachineLearning/SupervisedLearning/GaussianProcesses/_GaussianProcessRegressor.py +190 -0
- DLL/MachineLearning/SupervisedLearning/GaussianProcesses/__init__.py +1 -0
- DLL/MachineLearning/SupervisedLearning/Kernels.py +851 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/TimeSeries.py +147 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/_Elasticnet.py +161 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/_LassoRegression.py +157 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/_LinearRegression.py +81 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/_LogisticRegression.py +182 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/_RANSAC.py +97 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/_RidgeRegression.py +78 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/__init__.py +6 -0
- DLL/MachineLearning/SupervisedLearning/NaiveBayes/_BernoulliNaiveBayes.py +115 -0
- DLL/MachineLearning/SupervisedLearning/NaiveBayes/_GaussianNaiveBayes.py +111 -0
- DLL/MachineLearning/SupervisedLearning/NaiveBayes/_MultinomialNaiveBayes.py +115 -0
- DLL/MachineLearning/SupervisedLearning/NaiveBayes/__init__.py +3 -0
- DLL/MachineLearning/SupervisedLearning/Neighbors/_KNearestNeighborsClassifier.py +115 -0
- DLL/MachineLearning/SupervisedLearning/Neighbors/_KNearestNeighborsRegressor.py +90 -0
- DLL/MachineLearning/SupervisedLearning/Neighbors/__init__.py +2 -0
- DLL/MachineLearning/SupervisedLearning/SupportVectorMachines/_SVC.py +293 -0
- DLL/MachineLearning/SupervisedLearning/SupportVectorMachines/_SVR.py +100 -0
- DLL/MachineLearning/SupervisedLearning/SupportVectorMachines/__init__.py +2 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_AdaBoostClassifier.py +179 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_AdaBoostRegressor.py +141 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_DecisionTree.py +219 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_GradientBoostingClassifier.py +207 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_GradientBoostingRegressor.py +122 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_LGBMClassifier.py +288 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_LGBMRegressor.py +168 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_LGBMTree.py +199 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_RandomForest.py +96 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_RegressionForest.py +75 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_RegressionTree.py +148 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_XGBoostClassifier.py +239 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_XGBoostRegressor.py +131 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_XGBoostTree.py +105 -0
- DLL/MachineLearning/SupervisedLearning/Trees/__init__.py +12 -0
- DLL/MachineLearning/SupervisedLearning/__init__.py +0 -0
- DLL/MachineLearning/UnsupervisedLearning/Clustering/_DBScan.py +87 -0
- DLL/MachineLearning/UnsupervisedLearning/Clustering/_GaussianMixtureModels.py +121 -0
- DLL/MachineLearning/UnsupervisedLearning/Clustering/_KMeans.py +120 -0
- DLL/MachineLearning/UnsupervisedLearning/Clustering/_SpectralClustering.py +105 -0
- DLL/MachineLearning/UnsupervisedLearning/Clustering/__init__.py +4 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_LDA.py +118 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_PCA.py +101 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_QDA.py +69 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_RobustPCA.py +143 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_TSNE.py +151 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_UMAP.py +176 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/__init__.py +6 -0
- DLL/MachineLearning/UnsupervisedLearning/OutlierDetection/_IsolationForest.py +175 -0
- DLL/MachineLearning/UnsupervisedLearning/OutlierDetection/__init__.py +1 -0
- DLL/MachineLearning/UnsupervisedLearning/__init__.py +0 -0
- DLL/MachineLearning/__init__.py +0 -0
- DLL/__init__.py +0 -0
- pydllib-1.0.dist-info/METADATA +104 -0
- pydllib-1.0.dist-info/RECORD +124 -0
- pydllib-1.0.dist-info/WHEEL +5 -0
- pydllib-1.0.dist-info/licenses/LICENSE +21 -0
- pydllib-1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
from . import LinearRegression
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SARIMA:
|
|
7
|
+
"""
|
|
8
|
+
The Seasonal auto regressive moving average model for time series analysis.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
series (torch.Tensor of shape (n_samples,)): The time series for fitting. Must be one dimensional.
|
|
12
|
+
order (tuple of ints): The orders of the non-seasonal parts. Follows the format (p, d, q).
|
|
13
|
+
seasonal_order (tuple of ints): The orders of the seasonal parts. Follows the format (P, D, Q, S). If a seasonal component is not needed, the seasonal order should be put as (0, 0, 0, 1).
|
|
14
|
+
"""
|
|
15
|
+
def __init__(self, series, order, seasonal_order):
|
|
16
|
+
if not isinstance(series, torch.Tensor) or series.ndim != 1:
|
|
17
|
+
raise TypeError("series must be a one-dimensional torch tensor.")
|
|
18
|
+
if not isinstance(order, tuple | list) or len(order) != 3:
|
|
19
|
+
raise TypeError("order must be a tuple of length 3.")
|
|
20
|
+
if any([not isinstance(val, int) or val < 0 for val in order]):
|
|
21
|
+
raise ValueError("order must only contain non-negative integers.")
|
|
22
|
+
if not isinstance(seasonal_order, tuple | list) or len(seasonal_order) != 4:
|
|
23
|
+
raise TypeError("seasonal_order must be a tuple of length 4.")
|
|
24
|
+
if any([not isinstance(val, int) or val < 0 for val in seasonal_order]):
|
|
25
|
+
raise ValueError("seasonal_order must only contain non-negative integers.")
|
|
26
|
+
|
|
27
|
+
self.p, self.d, self.q = order
|
|
28
|
+
self.P, self.D, self.Q, self.S = seasonal_order
|
|
29
|
+
|
|
30
|
+
self.series = series
|
|
31
|
+
self._discarded = {}
|
|
32
|
+
if self.d > 0: series = self._differentiate(series, order=self.d)
|
|
33
|
+
if self.D > 0: series = self._differentiate(series, lag=self.S, order=self.D)
|
|
34
|
+
self.diff_series = series
|
|
35
|
+
|
|
36
|
+
min_length = max(self.p, self.q, self.P * self.S, self.Q * self.S)
|
|
37
|
+
if len(self.diff_series) <= min_length:
|
|
38
|
+
raise ValueError(f"Differentiated series' length {len(self.diff_series)} is less than or equal minimum required length {min_length} for the given orders.")
|
|
39
|
+
|
|
40
|
+
def fit(self):
|
|
41
|
+
"""
|
|
42
|
+
Fits the ARMA model to the given time series. Currently, the function fits two linear regression models separately for the AR and MA components.
|
|
43
|
+
|
|
44
|
+
Note:
|
|
45
|
+
This approach is suboptimal for the MA component, as it should be fitted using Kalman filters for correctness.
|
|
46
|
+
"""
|
|
47
|
+
residuals = self._fit_ar()
|
|
48
|
+
self._fit_ma(residuals)
|
|
49
|
+
|
|
50
|
+
def _fit_ar(self):
|
|
51
|
+
X, y = self._train_data(part="ar")
|
|
52
|
+
|
|
53
|
+
self.ar_model = LinearRegression()
|
|
54
|
+
self.ar_model.fit(X, y)
|
|
55
|
+
return y - self.ar_model.predict(X)
|
|
56
|
+
|
|
57
|
+
def _fit_ma(self, residuals):
|
|
58
|
+
X, _ = self._train_data(part="ma")
|
|
59
|
+
length = min(len(X), len(residuals))
|
|
60
|
+
X, residuals = X[-length:], residuals[-length:]
|
|
61
|
+
|
|
62
|
+
self.ma_model = LinearRegression()
|
|
63
|
+
self.ma_model.fit(X, residuals)
|
|
64
|
+
|
|
65
|
+
def _train_data(self, part):
|
|
66
|
+
if part == "ar":
|
|
67
|
+
X_series, X_targets = self._lagged_terms(order=self.p)
|
|
68
|
+
seasonal_X_series, seasonal_X_targets = self._lagged_terms(order=self.P, lag=self.S)
|
|
69
|
+
elif part == "ma":
|
|
70
|
+
X_series, X_targets = self._lagged_terms(order=self.q)
|
|
71
|
+
seasonal_X_series, seasonal_X_targets = self._lagged_terms(order=self.Q, lag=self.S)
|
|
72
|
+
length = min(len(X_series), len(seasonal_X_series))
|
|
73
|
+
X = torch.cat((X_series[-length:], seasonal_X_series[-length:]), dim=1)
|
|
74
|
+
y = X_targets if len(X_targets) < len(seasonal_X_targets) else seasonal_X_targets
|
|
75
|
+
return X, y
|
|
76
|
+
|
|
77
|
+
def _lagged_terms(self, order, lag=1):
|
|
78
|
+
points = []
|
|
79
|
+
for i in range(lag * order, len(self.diff_series)):
|
|
80
|
+
indicies = i - lag * torch.arange(1, order + 1)
|
|
81
|
+
features = self.diff_series[indicies]
|
|
82
|
+
points.append(features)
|
|
83
|
+
targets = self.diff_series[lag * order:]
|
|
84
|
+
return torch.stack(points, dim=0), targets
|
|
85
|
+
|
|
86
|
+
def _differentiate(self, series, lag=1, order=1):
|
|
87
|
+
discarded = []
|
|
88
|
+
for _ in range(order):
|
|
89
|
+
discarded.append(series[:lag])
|
|
90
|
+
series = series[lag:] - series[:-lag]
|
|
91
|
+
self._discarded[lag] = torch.stack(discarded, dim=0)
|
|
92
|
+
return series
|
|
93
|
+
|
|
94
|
+
def _integrate(self, differenced, lag=1, order=1):
|
|
95
|
+
for j in range(order):
|
|
96
|
+
restored = torch.zeros(len(differenced) + lag)
|
|
97
|
+
restored[:lag] = self._discarded[lag][-j - 1]
|
|
98
|
+
for i in range(lag, len(restored)):
|
|
99
|
+
restored[i] = differenced[i - lag] + restored[i - lag]
|
|
100
|
+
differenced = restored
|
|
101
|
+
return differenced
|
|
102
|
+
|
|
103
|
+
def predict(self, steps=1, fit_between_steps=False):
|
|
104
|
+
"""
|
|
105
|
+
Predicts the next values of the given time series.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
steps (int, optional): The number of next values to predict. Must be a positive integer. Defaults to 1.
|
|
109
|
+
fit_between_steps (bool, optional): Determines if the model should be refitted between each prediction. Defaults to False.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
torch.Tensor: The predicted values as a one-dimensional torch Tensor.
|
|
113
|
+
"""
|
|
114
|
+
if not isinstance(steps, int) or steps <= 0:
|
|
115
|
+
raise ValueError("steps must be a positive integer.")
|
|
116
|
+
if not isinstance(fit_between_steps, bool):
|
|
117
|
+
raise TypeError("fit_between_steps must be a boolean.")
|
|
118
|
+
|
|
119
|
+
diff_series = self.diff_series.clone()
|
|
120
|
+
original_diff_series = self.diff_series.clone()
|
|
121
|
+
for _ in range(steps):
|
|
122
|
+
pred = self._predict_next(diff_series)
|
|
123
|
+
diff_series = torch.cat((diff_series, pred), dim=0)
|
|
124
|
+
|
|
125
|
+
if fit_between_steps:
|
|
126
|
+
self.diff_series = diff_series
|
|
127
|
+
residuals = self._fit_ar()
|
|
128
|
+
self._fit_ma(residuals)
|
|
129
|
+
|
|
130
|
+
if self.D > 0: diff_series = self._integrate(diff_series, lag=self.S, order=self.D)
|
|
131
|
+
if self.d > 0: diff_series = self._integrate(diff_series, order=self.d)
|
|
132
|
+
self.diff_series = original_diff_series
|
|
133
|
+
result = diff_series
|
|
134
|
+
return result[-steps:]
|
|
135
|
+
|
|
136
|
+
def _predict_next(self, diff_series):
|
|
137
|
+
indicies = -torch.arange(1, self.p + 1)
|
|
138
|
+
indicies = torch.cat((-self.S * torch.arange(1, self.P + 1), indicies), dim=0)
|
|
139
|
+
X_ar = diff_series[indicies].unsqueeze(0)
|
|
140
|
+
|
|
141
|
+
indicies = -torch.arange(1, self.q + 1)
|
|
142
|
+
indicies = torch.cat((-self.S * torch.arange(1, self.Q + 1), indicies), dim=0)
|
|
143
|
+
X_ma = diff_series[indicies].unsqueeze(0)
|
|
144
|
+
|
|
145
|
+
ar_pred = self.ar_model.predict(X_ar)
|
|
146
|
+
ma_correction = self.ma_model.predict(X_ma)
|
|
147
|
+
return ar_pred + ma_correction
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from math import floor
|
|
3
|
+
|
|
4
|
+
from ....Data.Metrics import calculate_metrics, _round_dictionary
|
|
5
|
+
from ....Exceptions import NotFittedError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ElasticNetRegression:
|
|
9
|
+
"""
|
|
10
|
+
Implements a linear regression model with L1 and L2 regularization.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
alpha (int | float, optional): The regularization parameter. Larger alpha will force the l1 and l2 norms of the weights to be lower. Must be a non-negative real number. Defaults to 1.
|
|
14
|
+
l1_ratio (int | float, optional): The proportion of l1 regularisation compared to l2 regularisation. Must be in the range [0, 1]. Defaults to 0.5.
|
|
15
|
+
loss (:ref:`losses_section_label`, optional): A loss function used for training the model. Defaults to the mean squared error.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
n_features (int): The number of features. Available after fitting.
|
|
19
|
+
weights (torch.Tensor of shape (n_features + 1,)): The weights of the linear regression model. The first element is the bias of the model. Available after fitting.
|
|
20
|
+
residuals (torch.Tensor of shape (n_samples,)): The residuals of the fitted model. For a good fit, the residuals should be normally distributed with zero mean and constant variance. Available after fitting.
|
|
21
|
+
"""
|
|
22
|
+
def __init__(self, alpha=1.0, l1_ratio=0.5):
|
|
23
|
+
if not isinstance(alpha, int | float) or alpha < 0:
|
|
24
|
+
raise ValueError("alpha must be a non-negative real number.")
|
|
25
|
+
if not isinstance(l1_ratio, int | float) or not ((0 <= l1_ratio) and (l1_ratio <= 1)):
|
|
26
|
+
raise ValueError("l1_ratio must be in range [0, 1].")
|
|
27
|
+
|
|
28
|
+
self.alpha = alpha
|
|
29
|
+
self.l1_ratio = l1_ratio
|
|
30
|
+
|
|
31
|
+
def fit(self, X, y, sample_weight=None, val_data=None, epochs=100, callback_frequency=1, metrics=["loss"], verbose=False):
|
|
32
|
+
"""
|
|
33
|
+
Fits the ElasticNetRegression model to the input data by minimizing the mean squared error loss function using cyclic coordinate-wise descent from `this paper <https://aaltodoc.aalto.fi/server/api/core/bitstreams/091c41eb-7348-48d3-b25d-c8de25fbab03/content>`_.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
|
|
37
|
+
y (torch.Tensor of shape (n_samples,)): The target values corresponding to each sample.
|
|
38
|
+
val_data (tuple[X_val, y_val] | None, optional): Optional validation samples. If None, no validation data is used. Defaults to None.
|
|
39
|
+
epochs (int, optional): The number of training iterations. Must be a positive integer. Defaults to 100.
|
|
40
|
+
callback_frequency (int, optional): The number of iterations between printing info from training. Must be a positive integer. Defaults to 1, which means that every iteration, info is printed assuming verbose=True.
|
|
41
|
+
metrics (list[str], optional): The metrics that will be tracked during training. Defaults to ["loss"].
|
|
42
|
+
verbose (bool, optional): If True, prints info of the chosen metrics during training. Defaults to False.
|
|
43
|
+
Returns:
|
|
44
|
+
history (dict[str, torch.Tensor], each tensor is floor(epochs / callback_frequency) long.): A dictionary tracking the evolution of selected metrics at intervals defined by callback_frequency.
|
|
45
|
+
Raises:
|
|
46
|
+
TypeError: If the input matrix or the target matrix is not a PyTorch tensor or if other parameters are of wrong type.
|
|
47
|
+
ValueError: If the input matrix or the target matrix is not the correct shape or if other parameters have incorrect values.
|
|
48
|
+
"""
|
|
49
|
+
if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
|
|
50
|
+
raise TypeError("The input matrix and the target matrix must be a PyTorch tensor.")
|
|
51
|
+
if X.ndim != 2:
|
|
52
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor.")
|
|
53
|
+
if y.ndim != 1 or y.shape[0] != X.shape[0]:
|
|
54
|
+
raise ValueError("The targets must be 1 dimensional with the same number of samples as the input data")
|
|
55
|
+
if not isinstance(val_data, tuple) and val_data is not None:
|
|
56
|
+
raise TypeError("val_data must either be a tuple containing validation samples or None.")
|
|
57
|
+
if isinstance(val_data, tuple) and len(val_data) != 2:
|
|
58
|
+
raise ValueError("val_data must contain both X_val and y_val.")
|
|
59
|
+
if isinstance(val_data, tuple) and len(val_data) == 2 and (val_data[0].ndim != 2 or val_data[1].ndim != 1 or val_data[0].shape[1] != X.shape[1] or len(val_data[0]) != len(val_data[1])):
|
|
60
|
+
raise ValueError("X_val and y_val must be of correct shape.")
|
|
61
|
+
if not isinstance(epochs, int) or epochs <= 0:
|
|
62
|
+
raise ValueError("epochs must be a positive integer.")
|
|
63
|
+
if not isinstance(callback_frequency, int) or callback_frequency <= 0:
|
|
64
|
+
raise ValueError("callback_frequency must be a positive integer.")
|
|
65
|
+
if not isinstance(metrics, list | tuple):
|
|
66
|
+
raise TypeError("metrics must be a list or a tuple containing the strings of wanted metrics.")
|
|
67
|
+
if not isinstance(verbose, bool):
|
|
68
|
+
raise TypeError("verbose must be a boolean.")
|
|
69
|
+
if not isinstance(sample_weight, torch.Tensor) and sample_weight is not None:
|
|
70
|
+
raise TypeError("sample_weight must be torch.Tensor or None.")
|
|
71
|
+
if isinstance(sample_weight, torch.Tensor) and (sample_weight.ndim != 1 or len(X) != len(sample_weight)):
|
|
72
|
+
raise ValueError("sample_weight must be of shape (n_samples,)")
|
|
73
|
+
|
|
74
|
+
# self.n_features = X.shape[1]
|
|
75
|
+
# history = {metric: torch.zeros(floor(epochs / callback_frequency)) for metric in metrics}
|
|
76
|
+
# batch_size = len(X) if batch_size is None else batch_size
|
|
77
|
+
# data_reader = DataReader(X, y, batch_size=batch_size, shuffle=shuffle_data, shuffle_every_epoch=shuffle_every_epoch)
|
|
78
|
+
|
|
79
|
+
# self.weights = torch.randn((self.n_features,))
|
|
80
|
+
# self.bias = torch.zeros((1,))
|
|
81
|
+
# optimiser = ADAM() if optimiser is None else optimiser
|
|
82
|
+
# optimiser.initialise_parameters([self.weights, self.bias])
|
|
83
|
+
|
|
84
|
+
# for epoch in range(epochs):
|
|
85
|
+
# for x_batch, y_batch in data_reader.get_data():
|
|
86
|
+
# predictions = self.predict(x_batch)
|
|
87
|
+
# dCdy = self.loss.gradient(predictions, y_batch)
|
|
88
|
+
# dCdweights = (x_batch.T @ dCdy) + self.alpha * self.l1_ratio * torch.sign(self.weights) + self.alpha * (1 - self.l1_ratio) * self.weights
|
|
89
|
+
# dCdbias = dCdy.mean(dim=0, keepdim=True)
|
|
90
|
+
# self.weights.grad = dCdweights
|
|
91
|
+
# self.bias.grad = dCdbias
|
|
92
|
+
# optimiser.update_parameters()
|
|
93
|
+
# if epoch % callback_frequency == 0:
|
|
94
|
+
# values = calculate_metrics(data=(self.predict(X), y), metrics=metrics, loss=self.loss.loss)
|
|
95
|
+
# if val_data is not None:
|
|
96
|
+
# val_values = calculate_metrics(data=(self.predict(val_data[0]), val_data[1]), metrics=metrics, loss=self.loss.loss, validation=True)
|
|
97
|
+
# values |= val_values
|
|
98
|
+
# for metric, value in values.items():
|
|
99
|
+
# history[metric][int(epoch / callback_frequency)] = value
|
|
100
|
+
# if verbose: print(f"Epoch: {epoch + 1} - Metrics: {_round_dictionary(values)}")
|
|
101
|
+
# self.residuals = y - self.predict(X)
|
|
102
|
+
# return history
|
|
103
|
+
|
|
104
|
+
n_samples, self.n_features = X.shape
|
|
105
|
+
X = torch.cat((torch.ones(size=(X.shape[0], 1)), X), dim=1)
|
|
106
|
+
|
|
107
|
+
history = {metric: torch.zeros(floor(epochs / callback_frequency)) for metric in metrics}
|
|
108
|
+
self.weights = torch.randn((self.n_features + 1))
|
|
109
|
+
|
|
110
|
+
if sample_weight is None:
|
|
111
|
+
sample_weight = torch.ones(n_samples)
|
|
112
|
+
|
|
113
|
+
normalisation_const = torch.sum(sample_weight[:, None] * X**2, axis=0)
|
|
114
|
+
|
|
115
|
+
for epoch in range(epochs):
|
|
116
|
+
for j in range(self.n_features + 1):
|
|
117
|
+
residual = y - X @ self.weights + self.weights[j] * X[:, j]
|
|
118
|
+
weighted_prod = torch.dot(X[:, j], sample_weight * residual)
|
|
119
|
+
if normalisation_const[j] == 0:
|
|
120
|
+
continue
|
|
121
|
+
soft_thresholding = torch.sign(weighted_prod) * max(abs(weighted_prod) - self.alpha * self.l1_ratio, 0) / (normalisation_const[j] + self.alpha * (1 - self.l1_ratio))
|
|
122
|
+
self.weights[j] = soft_thresholding
|
|
123
|
+
|
|
124
|
+
if epoch % callback_frequency == 0:
|
|
125
|
+
values = calculate_metrics(data=(self.predict(X), y), metrics=metrics, loss=self._loss)
|
|
126
|
+
if val_data is not None:
|
|
127
|
+
val_values = calculate_metrics(data=(self.predict(val_data[0]), val_data[1]), metrics=metrics, loss=self._loss, validation=True)
|
|
128
|
+
values |= val_values
|
|
129
|
+
for metric, value in values.items():
|
|
130
|
+
history[metric][int(epoch / callback_frequency)] = value
|
|
131
|
+
if verbose: print(f"Epoch: {epoch + 1} - Metrics: {_round_dictionary(values)}")
|
|
132
|
+
|
|
133
|
+
# self.weights *= 1 + self.alpha * (1 - self.l1_ratio) # author of paper suggests this for a correction to the double shrinkage effect, however, the results are not that good.
|
|
134
|
+
self.residuals = y - self.predict(X)
|
|
135
|
+
return history
|
|
136
|
+
|
|
137
|
+
def _loss(self, prediction, true_outputs):
|
|
138
|
+
return torch.sum((prediction - true_outputs) ** 2) + self.alpha * (2 * self.l1_ratio * torch.linalg.norm(self.weights, ord=1) + (1 - self.l1_ratio) * torch.linalg.norm(self.weights, ord=2) ** 2)
|
|
139
|
+
|
|
140
|
+
def predict(self, X):
|
|
141
|
+
"""
|
|
142
|
+
Applies the fitted ElasticNetRegression model to the input data, predicting the correct values.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data to be regressed.
|
|
146
|
+
Returns:
|
|
147
|
+
target values (torch.Tensor of shape (n_samples,)): The predicted values corresponding to each sample.
|
|
148
|
+
Raises:
|
|
149
|
+
NotFittedError: If the ElasticNetRegression model has not been fitted before predicting.
|
|
150
|
+
TypeError: If the input matrix is not a PyTorch tensor.
|
|
151
|
+
ValueError: If the input matrix is not the correct shape.
|
|
152
|
+
"""
|
|
153
|
+
if not hasattr(self, "weights"):
|
|
154
|
+
raise NotFittedError("ElasticNetRegression.fit() must be called before predicting.")
|
|
155
|
+
if not isinstance(X, torch.Tensor):
|
|
156
|
+
raise TypeError("The input matrix must be a PyTorch tensor.")
|
|
157
|
+
if X.ndim != 2 or (X.shape[1] != self.n_features and (X.shape[1] != self.n_features + 1 or torch.any(X[:, 0] != torch.ones(len(X))))):
|
|
158
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
|
|
159
|
+
|
|
160
|
+
if X.shape[1] != self.n_features + 1: X = torch.cat((torch.ones(size=(X.shape[0], 1)), X), dim=1)
|
|
161
|
+
return X @ self.weights
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from math import floor
|
|
3
|
+
|
|
4
|
+
from ....Data.Metrics import calculate_metrics, _round_dictionary
|
|
5
|
+
from ....Exceptions import NotFittedError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LASSORegression:
|
|
9
|
+
"""
|
|
10
|
+
Implements a linear regression model with L1 regularization.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
alpha (int | float, optional): The regularization parameter. Larger alpha will force the l1 norm of the weights to be lower. Must be a positive real number. Defaults to 1.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
n_features (int): The number of features. Available after fitting.
|
|
17
|
+
weights (torch.Tensor of shape (n_features + 1,)): The weights of the linear regression model. Available after fitting.
|
|
18
|
+
residuals (torch.Tensor of shape (n_samples,)): The residuals of the fitted model. For a good fit, the residuals should be normally distributed with zero mean and constant variance. Available after fitting.
|
|
19
|
+
"""
|
|
20
|
+
def __init__(self, alpha=1.0):
|
|
21
|
+
if not isinstance(alpha, int | float) or alpha < 0:
|
|
22
|
+
raise ValueError("alpha must be a non-negative real number.")
|
|
23
|
+
|
|
24
|
+
self.alpha = alpha
|
|
25
|
+
|
|
26
|
+
def fit(self, X, y, sample_weight=None, val_data=None, epochs=100, callback_frequency=1, metrics=["loss"], verbose=False):
|
|
27
|
+
"""
|
|
28
|
+
Fits the LASSORegression model to the input data by minimizing the mean squared error loss function using cyclic coordinate-wise descent from `this paper <https://aaltodoc.aalto.fi/server/api/core/bitstreams/091c41eb-7348-48d3-b25d-c8de25fbab03/content>`_.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
|
|
32
|
+
y (torch.Tensor of shape (n_samples,)): The target values corresponding to each sample.
|
|
33
|
+
val_data (tuple[X_val, y_val] | None, optional): Optional validation samples. If None, no validation data is used. Defaults to None.
|
|
34
|
+
epochs (int, optional): The number of training iterations. Must be a positive integer. Defaults to 100.
|
|
35
|
+
callback_frequency (int, optional): The number of iterations between printing info from training. Must be a positive integer. Defaults to 1, which means that every iteration, info is printed assuming verbose=True.
|
|
36
|
+
metrics (list[str], optional): The metrics that will be tracked during training. Defaults to ["loss"].
|
|
37
|
+
verbose (bool, optional): If True, prints info of the chosen metrics during training. Defaults to False.
|
|
38
|
+
Returns:
|
|
39
|
+
history (dict[str, torch.Tensor], each tensor is floor(epochs / callback_frequency) long.): A dictionary tracking the evolution of selected metrics at intervals defined by callback_frequency.
|
|
40
|
+
Raises:
|
|
41
|
+
TypeError: If the input matrix or the target matrix is not a PyTorch tensor or if other parameters are of wrong type.
|
|
42
|
+
ValueError: If the input matrix or the target matrix is not the correct shape or if other parameters have incorrect values.
|
|
43
|
+
"""
|
|
44
|
+
if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
|
|
45
|
+
raise TypeError("The input matrix and the target matrix must be a PyTorch tensor.")
|
|
46
|
+
if X.ndim != 2:
|
|
47
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor.")
|
|
48
|
+
if y.ndim != 1 or y.shape[0] != X.shape[0]:
|
|
49
|
+
raise ValueError("The targets must be 1 dimensional with the same number of samples as the input data")
|
|
50
|
+
if not isinstance(val_data, tuple) and val_data is not None:
|
|
51
|
+
raise TypeError("val_data must either be a tuple containing validation samples or None.")
|
|
52
|
+
if isinstance(val_data, tuple) and len(val_data) != 2:
|
|
53
|
+
raise ValueError("val_data must contain both X_val and y_val.")
|
|
54
|
+
if isinstance(val_data, tuple) and len(val_data) == 2 and (val_data[0].ndim != 2 or val_data[1].ndim != 1 or val_data[0].shape[1] != X.shape[1] or len(val_data[0]) != len(val_data[1])):
|
|
55
|
+
raise ValueError("X_val and y_val must be of correct shape.")
|
|
56
|
+
if not isinstance(epochs, int) or epochs <= 0:
|
|
57
|
+
raise ValueError("epochs must be a positive integer.")
|
|
58
|
+
if not isinstance(callback_frequency, int) or callback_frequency <= 0:
|
|
59
|
+
raise ValueError("callback_frequency must be a positive integer.")
|
|
60
|
+
if not isinstance(metrics, list | tuple):
|
|
61
|
+
raise TypeError("metrics must be a list or a tuple containing the strings of wanted metrics.")
|
|
62
|
+
if not isinstance(verbose, bool):
|
|
63
|
+
raise TypeError("verbose must be a boolean.")
|
|
64
|
+
if not isinstance(sample_weight, torch.Tensor) and sample_weight is not None:
|
|
65
|
+
raise TypeError("sample_weight must be torch.Tensor or None.")
|
|
66
|
+
if isinstance(sample_weight, torch.Tensor) and (sample_weight.ndim != 1 or len(X) != len(sample_weight)):
|
|
67
|
+
raise ValueError("sample_weight must be of shape (n_samples,)")
|
|
68
|
+
|
|
69
|
+
# self.n_features = X.shape[1]
|
|
70
|
+
# self.metrics = metrics
|
|
71
|
+
# history = {metric: torch.zeros(floor(epochs / callback_frequency)) for metric in metrics}
|
|
72
|
+
# batch_size = len(X) if batch_size is None else batch_size
|
|
73
|
+
# data_reader = DataReader(X, y, batch_size=batch_size, shuffle=shuffle_data, shuffle_every_epoch=shuffle_every_epoch)
|
|
74
|
+
|
|
75
|
+
# self.weights = torch.randn((self.n_features,))
|
|
76
|
+
# self.bias = torch.zeros((1,))
|
|
77
|
+
# optimiser = ADAM() if optimiser is None else optimiser
|
|
78
|
+
# optimiser.initialise_parameters([self.weights, self.bias])
|
|
79
|
+
|
|
80
|
+
# for epoch in range(epochs):
|
|
81
|
+
# for x_batch, y_batch in data_reader.get_data():
|
|
82
|
+
# predictions = self.predict(x_batch)
|
|
83
|
+
# dCdy = self.loss.gradient(predictions, y_batch)
|
|
84
|
+
# dCdweights = (x_batch.T @ dCdy) + self.alpha * torch.sign(self.weights)
|
|
85
|
+
# dCdbias = dCdy.mean(dim=0, keepdim=True)
|
|
86
|
+
# self.weights.grad = dCdweights
|
|
87
|
+
# self.bias.grad = dCdbias
|
|
88
|
+
# optimiser.update_parameters()
|
|
89
|
+
# if epoch % callback_frequency == 0:
|
|
90
|
+
# values = calculate_metrics(data=(self.predict(X), y), metrics=self.metrics, loss=self.loss.loss)
|
|
91
|
+
# if val_data is not None:
|
|
92
|
+
# val_values = calculate_metrics(data=(self.predict(val_data[0]), val_data[1]), metrics=self.metrics, loss=self.loss.loss, validation=True)
|
|
93
|
+
# values |= val_values
|
|
94
|
+
# for metric, value in values.items():
|
|
95
|
+
# history[metric][int(epoch / callback_frequency)] = value
|
|
96
|
+
# if verbose: print(f"Epoch: {epoch + 1} - Metrics: {_round_dictionary(values)}")
|
|
97
|
+
# self.residuals = y - self.predict(X)
|
|
98
|
+
# return history
|
|
99
|
+
|
|
100
|
+
n_samples, self.n_features = X.shape
|
|
101
|
+
X = torch.cat((torch.ones(size=(X.shape[0], 1)), X), dim=1)
|
|
102
|
+
|
|
103
|
+
history = {metric: torch.zeros(floor(epochs / callback_frequency)) for metric in metrics}
|
|
104
|
+
self.weights = torch.randn((self.n_features + 1))
|
|
105
|
+
|
|
106
|
+
if sample_weight is None:
|
|
107
|
+
sample_weight = torch.ones(n_samples)
|
|
108
|
+
|
|
109
|
+
normalisation_const = torch.sum(sample_weight[:, None] * X ** 2, axis=0)
|
|
110
|
+
|
|
111
|
+
for epoch in range(epochs):
|
|
112
|
+
for j in range(self.n_features + 1):
|
|
113
|
+
residual = y - X @ self.weights + self.weights[j] * X[:, j]
|
|
114
|
+
weighted_prod = torch.dot(X[:, j], sample_weight * residual)
|
|
115
|
+
if normalisation_const[j] == 0:
|
|
116
|
+
continue
|
|
117
|
+
soft_thresholding = torch.sign(weighted_prod) * max(abs(weighted_prod) - self.alpha, 0) / normalisation_const[j]
|
|
118
|
+
self.weights[j] = soft_thresholding
|
|
119
|
+
|
|
120
|
+
if epoch % callback_frequency == 0:
|
|
121
|
+
values = calculate_metrics(data=(self.predict(X), y), metrics=metrics, loss=self._loss)
|
|
122
|
+
if val_data is not None:
|
|
123
|
+
val_values = calculate_metrics(data=(self.predict(val_data[0]), val_data[1]), metrics=metrics, loss=self._loss, validation=True)
|
|
124
|
+
values |= val_values
|
|
125
|
+
for metric, value in values.items():
|
|
126
|
+
history[metric][int(epoch / callback_frequency)] = value
|
|
127
|
+
if verbose: print(f"Epoch: {epoch + 1} - Metrics: {_round_dictionary(values)}")
|
|
128
|
+
|
|
129
|
+
self.residuals = y - self.predict(X)
|
|
130
|
+
return history
|
|
131
|
+
|
|
132
|
+
def _loss(self, prediction, true_outputs):
|
|
133
|
+
return torch.sum((prediction - true_outputs) ** 2) + 2 * self.alpha * torch.linalg.norm(self.weights, ord=1)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def predict(self, X):
|
|
137
|
+
"""
|
|
138
|
+
Applies the fitted LASSORegression model to the input data, predicting the correct values.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data to be regressed.
|
|
142
|
+
Returns:
|
|
143
|
+
target values (torch.Tensor of shape (n_samples,)): The predicted values corresponding to each sample.
|
|
144
|
+
Raises:
|
|
145
|
+
NotFittedError: If the LASSORegression model has not been fitted before predicting.
|
|
146
|
+
TypeError: If the input matrix is not a PyTorch tensor.
|
|
147
|
+
ValueError: If the input matrix is not the correct shape.
|
|
148
|
+
"""
|
|
149
|
+
if not hasattr(self, "weights"):
|
|
150
|
+
raise NotFittedError("LASSORegression.fit() must be called before predicting.")
|
|
151
|
+
if not isinstance(X, torch.Tensor):
|
|
152
|
+
raise TypeError("The input matrix must be a PyTorch tensor.")
|
|
153
|
+
if X.ndim != 2 or (X.shape[1] != self.n_features and (X.shape[1] != self.n_features + 1 or torch.any(X[:, 0] != X[0, 0]))):
|
|
154
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
|
|
155
|
+
|
|
156
|
+
if X.shape[1] != self.n_features + 1: X = torch.cat((torch.ones(size=(X.shape[0], 1)), X), dim=1)
|
|
157
|
+
return X @ self.weights
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
from ....Exceptions import NotFittedError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LinearRegression:
|
|
7
|
+
"""
|
|
8
|
+
Implements the basic linear regression model.
|
|
9
|
+
|
|
10
|
+
Attributes:
|
|
11
|
+
n_features (int): The number of features. Available after fitting.
|
|
12
|
+
beta (torch.Tensor of shape (n_features + 1,)): The weights of the linear regression model. Available after fitting.
|
|
13
|
+
residuals (torch.Tensor of shape (n_samples,)): The residuals of the fitted model. For a good fit, the residuals should be normally distributed with zero mean and constant variance. Available after fitting.
|
|
14
|
+
"""
|
|
15
|
+
def fit(self, X, y, include_bias=True, method="ols", sample_weight=None):
|
|
16
|
+
"""
|
|
17
|
+
Fits the LinearRegression model to the input data by minimizing the squared error.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
|
|
21
|
+
y (torch.Tensor of shape (n_samples,)): The target values corresponding to each sample.
|
|
22
|
+
include_bias (bool, optional): Decides if a bias is included in a model. Defaults to True.
|
|
23
|
+
method (str, optional): Determines if the loss function is ordinary least squares or total least squares. Must be one of "ols" or "tls". Defaults to "ols".
|
|
24
|
+
sample_weight (torch.Tensor of shape (n_samples,) or None): A weight given to each sample in the regression. If None, this parameter is ignored. sample_weight is ignored if method == "tls".
|
|
25
|
+
Returns:
|
|
26
|
+
None
|
|
27
|
+
Raises:
|
|
28
|
+
TypeError: If the input matrix or the target matrix is not a PyTorch tensor.
|
|
29
|
+
ValueError: If the input matrix or the target matrix is not the correct shape.
|
|
30
|
+
"""
|
|
31
|
+
if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
|
|
32
|
+
raise TypeError("The input matrix and the target matrix must be a PyTorch tensor.")
|
|
33
|
+
if X.ndim != 2:
|
|
34
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor.")
|
|
35
|
+
if y.ndim != 1 or y.shape[0] != X.shape[0]:
|
|
36
|
+
raise ValueError("The targets must be 1 dimensional with the same number of samples as the input data")
|
|
37
|
+
if not isinstance(include_bias, bool):
|
|
38
|
+
raise TypeError("include_bias must be a boolean.")
|
|
39
|
+
if method not in ["ols", "tls"]:
|
|
40
|
+
raise ValueError('method must be one of "ols" or "tls".')
|
|
41
|
+
if not isinstance(sample_weight, torch.Tensor) and sample_weight is not None:
|
|
42
|
+
raise TypeError("sample_weight must be torch.Tensor or None.")
|
|
43
|
+
if isinstance(sample_weight, torch.Tensor) and (sample_weight.ndim != 1 or len(X) != len(sample_weight)):
|
|
44
|
+
raise ValueError("sample_weight must be of shape (n_samples,)")
|
|
45
|
+
|
|
46
|
+
self.include_bias = include_bias
|
|
47
|
+
self.n_features = X.shape[1]
|
|
48
|
+
X_ = torch.cat((torch.ones(size=(X.shape[0], 1)), X), dim=1) if self.include_bias else X
|
|
49
|
+
if method == "ols":
|
|
50
|
+
sample_weight = torch.diag(sample_weight) if sample_weight is not None else torch.eye(len(X))
|
|
51
|
+
self.beta = torch.linalg.lstsq(X_.T @ sample_weight @ X_, X_.T @ sample_weight @ y).solution
|
|
52
|
+
elif method == "tls":
|
|
53
|
+
C = torch.cat((X_, y.unsqueeze(1)), dim=1)
|
|
54
|
+
_, _, Vt = torch.linalg.svd(C, full_matrices=False)
|
|
55
|
+
v_min = Vt[-1]
|
|
56
|
+
self.beta = -v_min[:-1] / v_min[-1]
|
|
57
|
+
|
|
58
|
+
self.residuals = y - self.predict(X)
|
|
59
|
+
|
|
60
|
+
def predict(self, X):
|
|
61
|
+
"""
|
|
62
|
+
Applies the fitted LinearRegression model to the input data, predicting the correct values.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data to be regressed.
|
|
66
|
+
Returns:
|
|
67
|
+
target values (torch.Tensor of shape (n_samples,)): The predicted values corresponding to each sample.
|
|
68
|
+
Raises:
|
|
69
|
+
NotFittedError: If the LinearRegression model has not been fitted before predicting.
|
|
70
|
+
TypeError: If the input matrix is not a PyTorch tensor.
|
|
71
|
+
ValueError: If the input matrix is not the correct shape.
|
|
72
|
+
"""
|
|
73
|
+
if not hasattr(self, "beta"):
|
|
74
|
+
raise NotFittedError("LinearRegression.fit() must be called before predicting.")
|
|
75
|
+
if not isinstance(X, torch.Tensor):
|
|
76
|
+
raise TypeError("The input matrix must be a PyTorch tensor.")
|
|
77
|
+
if X.ndim != 2 or X.shape[1] != self.n_features:
|
|
78
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
|
|
79
|
+
|
|
80
|
+
if self.include_bias: X = torch.cat((torch.ones(size=(X.shape[0], 1)), X), dim=1)
|
|
81
|
+
return X @ self.beta
|