Cpp ML Library  1.0.0
A library of Machine Learning Algorithmns seen from the Udemy course Machine Learning A to Z.
SupportVectorRegression.hpp
Go to the documentation of this file.
1 #ifndef SUPPORT_VECTOR_REGRESSION_HPP
2 #define SUPPORT_VECTOR_REGRESSION_HPP
3 
4 #include <vector>
5 #include <cmath>
6 #include <algorithm>
7 #include <limits>
8 #include <functional>
9 #include <numeric>
10 #include <random>
11 #include <cassert>
12 
23 public:
27  enum class KernelType {
28  LINEAR,
29  POLYNOMIAL,
30  RBF
31  };
32 
42  SupportVectorRegression(double C = 1.0, double epsilon = 0.1, KernelType kernel_type = KernelType::RBF,
43  int degree = 3, double gamma = 1.0, double coef0 = 0.0);
44 
49 
55  void fit(const std::vector<std::vector<double>>& X, const std::vector<double>& y);
56 
62  std::vector<double> predict(const std::vector<std::vector<double>>& X) const;
63 
64 private:
65  double C;
66  double epsilon;
67  KernelType kernel_type;
68  int degree;
69  double gamma;
70  double coef0;
71 
72  std::vector<std::vector<double>> X_train;
73  std::vector<double> y_train;
74  std::vector<double> alpha;
75  std::vector<double> alpha_star;
76  double b;
77 
78  std::function<double(const std::vector<double>&, const std::vector<double>&)> kernel;
79 
83  void initialize_kernel();
84 
88  void solve();
89 
95  double predict_sample(const std::vector<double>& x) const;
96 
103  double compute_kernel(const std::vector<double>& x1, const std::vector<double>& x2) const;
104 
108  std::mt19937 rng;
109 
113  std::vector<double> errors;
114 
118  void initialize_errors();
119 
124  void update_error(size_t i);
125 
131  size_t select_second_index(size_t i);
132 };
133 
134 SupportVectorRegression::SupportVectorRegression(double C, double epsilon, KernelType kernel_type,
135  int degree, double gamma, double coef0)
136  : C(C), epsilon(epsilon), kernel_type(kernel_type), degree(degree), gamma(gamma), coef0(coef0), b(0.0) {
137  initialize_kernel();
138  rng.seed(std::random_device{}());
139 }
140 
142 
143 void SupportVectorRegression::initialize_kernel() {
144  if (kernel_type == KernelType::LINEAR) {
145  kernel = [](const std::vector<double>& x1, const std::vector<double>& x2) {
146  return std::inner_product(x1.begin(), x1.end(), x2.begin(), 0.0);
147  };
148  } else if (kernel_type == KernelType::POLYNOMIAL) {
149  kernel = [this](const std::vector<double>& x1, const std::vector<double>& x2) {
150  return std::pow(gamma * std::inner_product(x1.begin(), x1.end(), x2.begin(), 0.0) + coef0, degree);
151  };
152  } else if (kernel_type == KernelType::RBF) {
153  kernel = [this](const std::vector<double>& x1, const std::vector<double>& x2) {
154  double sum = 0.0;
155  for (size_t i = 0; i < x1.size(); ++i) {
156  double diff = x1[i] - x2[i];
157  sum += diff * diff;
158  }
159  return std::exp(-gamma * sum);
160  };
161  }
162 }
163 
164 void SupportVectorRegression::fit(const std::vector<std::vector<double>>& X, const std::vector<double>& y) {
165  X_train = X;
166  y_train = y;
167  size_t n_samples = X_train.size();
168 
169  alpha.resize(n_samples, 0.0);
170  alpha_star.resize(n_samples, 0.0);
171 
172  initialize_errors();
173 
174  solve();
175 }
176 
177 std::vector<double> SupportVectorRegression::predict(const std::vector<std::vector<double>>& X) const {
178  std::vector<double> predictions;
179  predictions.reserve(X.size());
180  for (const auto& x : X) {
181  predictions.push_back(predict_sample(x));
182  }
183  return predictions;
184 }
185 
186 void SupportVectorRegression::initialize_errors() {
187  size_t n_samples = X_train.size();
188  errors.resize(n_samples);
189  for (size_t i = 0; i < n_samples; ++i) {
190  errors[i] = predict_sample(X_train[i]) - y_train[i];
191  }
192 }
193 
194 double SupportVectorRegression::predict_sample(const std::vector<double>& x) const {
195  double result = b;
196  size_t n_samples = X_train.size();
197  for (size_t i = 0; i < n_samples; ++i) {
198  double coeff = alpha[i] - alpha_star[i];
199  if (std::abs(coeff) > 1e-8) {
200  result += coeff * compute_kernel(X_train[i], x);
201  }
202  }
203  return result;
204 }
205 
206 double SupportVectorRegression::compute_kernel(const std::vector<double>& x1, const std::vector<double>& x2) const {
207  return kernel(x1, x2);
208 }
209 
210 void SupportVectorRegression::update_error(size_t i) {
211  errors[i] = predict_sample(X_train[i]) - y_train[i];
212 }
213 
214 size_t SupportVectorRegression::select_second_index(size_t i) {
215  size_t n_samples = X_train.size();
216  std::uniform_int_distribution<size_t> dist(0, n_samples - 1);
217  size_t j = dist(rng);
218  while (j == i) {
219  j = dist(rng);
220  }
221  return j;
222 }
223 
224 void SupportVectorRegression::solve() {
225  size_t n_samples = X_train.size();
226  size_t max_passes = 5;
227  size_t passes = 0;
228  double tol = 1e-3;
229 
230  while (passes < max_passes) {
231  size_t num_changed_alphas = 0;
232  for (size_t i = 0; i < n_samples; ++i) {
233  double E_i = errors[i];
234 
235  // Check KKT conditions for alpha[i]
236  bool violate_KKT_alpha = ((alpha[i] < C) && (E_i > epsilon)) || ((alpha[i] > 0) && (E_i < epsilon));
237 
238  // Check KKT conditions for alpha_star[i]
239  bool violate_KKT_alpha_star = ((alpha_star[i] < C) && (E_i < -epsilon)) || ((alpha_star[i] > 0) && (E_i > -epsilon));
240 
241  if (violate_KKT_alpha || violate_KKT_alpha_star) {
242  size_t j = select_second_index(i);
243  double E_j = errors[j];
244 
245  // Compute eta
246  double K_ii = compute_kernel(X_train[i], X_train[i]);
247  double K_jj = compute_kernel(X_train[j], X_train[j]);
248  double K_ij = compute_kernel(X_train[i], X_train[j]);
249  double eta = K_ii + K_jj - 2 * K_ij;
250 
251  if (eta <= 0) {
252  continue;
253  }
254 
255  double alpha_i_old = alpha[i];
256  double alpha_star_i_old = alpha_star[i];
257  double alpha_j_old = alpha[j];
258  double alpha_star_j_old = alpha_star[j];
259 
260  // Update alpha[i] and alpha[j]
261  double delta_alpha = 0.0;
262 
263  if (violate_KKT_alpha) {
264  delta_alpha = std::min(C - alpha[i], std::max(-alpha[i], (E_i - E_j) / eta));
265  alpha[i] += delta_alpha;
266  alpha[j] -= delta_alpha;
267  } else if (violate_KKT_alpha_star) {
268  delta_alpha = std::min(C - alpha_star[i], std::max(-alpha_star[i], -(E_i - E_j) / eta));
269  alpha_star[i] += delta_alpha;
270  alpha_star[j] -= delta_alpha;
271  }
272 
273  // Update threshold b
274  double b1 = b - E_i - delta_alpha * (K_ii - K_ij);
275  double b2 = b - E_j - delta_alpha * (K_ij - K_jj);
276 
277  if ((alpha[i] > 0 && alpha[i] < C) || (alpha_star[i] > 0 && alpha_star[i] < C))
278  b = b1;
279  else if ((alpha[j] > 0 && alpha[j] < C) || (alpha_star[j] > 0 && alpha_star[j] < C))
280  b = b2;
281  else
282  b = (b1 + b2) / 2.0;
283 
284  // Update error cache
285  update_error(i);
286  update_error(j);
287 
288  num_changed_alphas++;
289  }
290  }
291 
292  if (num_changed_alphas == 0)
293  passes++;
294  else
295  passes = 0;
296  }
297 }
298 
299 #endif // SUPPORT_VECTOR_REGRESSION_HPP
Support Vector Regression using the ε-insensitive loss function.
Definition: SupportVectorRegression.hpp:22
~SupportVectorRegression()
Destructor for SupportVectorRegression.
Definition: SupportVectorRegression.hpp:141
std::vector< double > predict(const std::vector< std::vector< double >> &X) const
Predicts target values for the given input data.
Definition: SupportVectorRegression.hpp:177
KernelType
Kernel function types.
Definition: SupportVectorRegression.hpp:27
void fit(const std::vector< std::vector< double >> &X, const std::vector< double > &y)
Fits the SVR model to the training data.
Definition: SupportVectorRegression.hpp:164
SupportVectorRegression(double C=1.0, double epsilon=0.1, KernelType kernel_type=KernelType::RBF, int degree=3, double gamma=1.0, double coef0=0.0)
Constructs a SupportVectorRegression model.
Definition: SupportVectorRegression.hpp:134