PLSSVM - Parallel Least Squares Support Vector Machine  2.0.0
A Least Squares Support Vector Machine implementation using different backends.
csvm.hpp
Go to the documentation of this file.
1 
12 #ifndef PLSSVM_CSVM_HPP_
13 #define PLSSVM_CSVM_HPP_
14 #pragma once
15 
16 #include "plssvm/data_set.hpp" // plssvm::data_set
17 #include "plssvm/default_value.hpp" // plssvm::default_value, plssvm::default_init
18 #include "plssvm/detail/logger.hpp" // plssvm::detail::log, plssvm::verbosity_level
19 #include "plssvm/detail/operators.hpp" // plssvm::operators::sign
20 #include "plssvm/detail/performance_tracker.hpp" // plssvm::detail::performance_tracker
21 #include "plssvm/detail/type_traits.hpp" // PLSSVM_REQUIRES, plssvm::detail::remove_cvref_t
22 #include "plssvm/detail/utility.hpp" // plssvm::detail::to_underlying
23 #include "plssvm/exceptions/exceptions.hpp" // plssvm::invalid_parameter_exception
24 #include "plssvm/kernel_function_types.hpp" // plssvm::kernel_function_type
25 #include "plssvm/model.hpp" // plssvm::model
26 #include "plssvm/parameter.hpp" // plssvm::parameter, plssvm::detail::{get_value_from_named_parameter, has_only_parameter_named_args_v}
27 #include "plssvm/target_platforms.hpp" // plssvm::target_platform
28 
29 #include "fmt/core.h" // fmt::format
30 #include "igor/igor.hpp" // igor::parser
31 
32 #include <chrono> // std::chrono::{time_point, steady_clock, duration_cast}
33 #include <iostream> // std::cout, std::endl
34 #include <tuple> // std::tie
35 #include <type_traits> // std::enable_if_t, std::is_same_v, std::is_convertible_v, std::false_type
36 #include <utility> // std::pair, std::forward
37 #include <vector> // std::vector
38 
39 namespace plssvm {
40 
50 class csvm {
51  public:
57  explicit csvm(parameter params = {});
63  template <typename... Args>
64  explicit csvm(Args &&...args);
65 
69  csvm(const csvm &) = delete;
73  csvm(csvm &&) noexcept = default;
78  csvm &operator=(const csvm &) = delete;
83  csvm &operator=(csvm &&) noexcept = default;
87  virtual ~csvm() = default;
88 
93  [[nodiscard]] target_platform get_target_platform() const noexcept { return target_;}
98  [[nodiscard]] parameter get_params() const noexcept { return params_; }
99 
104  void set_params(parameter params) noexcept { params_ = params; }
110  template <typename... Args, PLSSVM_REQUIRES(detail::has_only_parameter_named_args_v<Args...>)>
111  void set_params(Args &&...named_args);
112 
113  //*************************************************************************************************************************************//
114  // fit model //
115  //*************************************************************************************************************************************//
129  template <typename real_type, typename label_type, typename... Args>
130  [[nodiscard]] model<real_type, label_type> fit(const data_set<real_type, label_type> &data, Args &&...named_args) const;
131 
132  //*************************************************************************************************************************************//
133  // predict and score //
134  //*************************************************************************************************************************************//
145  template <typename real_type, typename label_type>
146  [[nodiscard]] std::vector<label_type> predict(const model<real_type, label_type> &model, const data_set<real_type, label_type> &data) const;
147 
156  template <typename real_type, typename label_type>
157  [[nodiscard]] real_type score(const model<real_type, label_type> &model) const;
169  template <typename real_type, typename label_type>
170  [[nodiscard]] real_type score(const model<real_type, label_type> &model, const data_set<real_type, label_type> &data) const;
171 
172  protected:
173  //*************************************************************************************************************************************//
174  // pure virtual functions, must be implemented for all subclasses; doing the actual work //
175  //*************************************************************************************************************************************//
188  [[nodiscard]] virtual std::pair<std::vector<float>, float> solve_system_of_linear_equations(const detail::parameter<float> &params, const std::vector<std::vector<float>> &A, std::vector<float> b, float eps, unsigned long long max_iter) const = 0;
192  [[nodiscard]] virtual std::pair<std::vector<double>, double> solve_system_of_linear_equations(const detail::parameter<double> &params, const std::vector<std::vector<double>> &A, std::vector<double> b, double eps, unsigned long long max_iter) const = 0;
204  [[nodiscard]] virtual std::vector<float> predict_values(const detail::parameter<float> &params, const std::vector<std::vector<float>> &support_vectors, const std::vector<float> &alpha, float rho, std::vector<float> &w, const std::vector<std::vector<float>> &predict_points) const = 0;
208  [[nodiscard]] virtual std::vector<double> predict_values(const detail::parameter<double> &params, const std::vector<std::vector<double>> &support_vectors, const std::vector<double> &alpha, double rho, std::vector<double> &w, const std::vector<std::vector<double>> &predict_points) const = 0;
209 
212  private:
218  void sanity_check_parameter() const;
219 
222 };
223 
224 inline csvm::csvm(parameter params) :
225  params_{ params } {
226  this->sanity_check_parameter();
227 }
228 
229 template <typename... Args>
230 csvm::csvm(Args &&...named_args) :
231  params_{ std::forward<Args>(named_args)... } {
232  this->sanity_check_parameter();
233 }
234 
235 template <typename... Args, std::enable_if_t<detail::has_only_parameter_named_args_v<Args...>, bool>>
236 void csvm::set_params(Args &&...named_args) {
237  static_assert(sizeof...(Args) > 0, "At least one named parameter mus be given when calling set_params()!");
238 
239  // create new parameter struct which is responsible for parsing the named_args
240  parameter provided_params{ std::forward<Args>(named_args)... };
241 
242  // set the value of params_ if and only if the respective value in provided_params isn't the default value
243  if (!provided_params.kernel_type.is_default()) {
244  params_.kernel_type = provided_params.kernel_type.value();
245  }
246  if (!provided_params.gamma.is_default()) {
247  params_.gamma = provided_params.gamma.value();
248  }
249  if (!provided_params.degree.is_default()) {
250  params_.degree = provided_params.degree.value();
251  }
252  if (!provided_params.coef0.is_default()) {
253  params_.coef0 = provided_params.coef0.value();
254  }
255  if (!provided_params.cost.is_default()) {
256  params_.cost = provided_params.cost.value();
257  }
258 
259  // check if the new parameters make sense
260  this->sanity_check_parameter();
261 }
262 
263 template <typename real_type, typename label_type, typename... Args>
265  igor::parser parser{ std::forward<Args>(named_args)... };
266 
267  // set default values
268  default_value epsilon_val{ default_init<real_type>{ 0.001 } };
270 
271  // compile time check: only named parameter are permitted
272  static_assert(!parser.has_unnamed_arguments(), "Can only use named parameter!");
273  // compile time check: each named parameter must only be passed once
274  static_assert(!parser.has_duplicates(), "Can only use each named parameter once!");
275  // compile time check: only some named parameters are allowed
276  static_assert(!parser.has_other_than(epsilon, max_iter), "An illegal named parameter has been passed!");
277 
278  // compile time/runtime check: the values must have the correct types
279  if constexpr (parser.has(epsilon)) {
280  // get the value of the provided named parameter
281  epsilon_val = detail::get_value_from_named_parameter<typename decltype(epsilon_val)::value_type>(parser, epsilon);
282  // check if value makes sense
283  if (epsilon_val <= static_cast<typename decltype(epsilon_val)::value_type>(0)) {
284  throw invalid_parameter_exception{ fmt::format("epsilon must be less than 0.0, but is {}!", epsilon_val) };
285  }
286  }
287  if constexpr (parser.has(max_iter)) {
288  // get the value of the provided named parameter
289  max_iter_val = detail::get_value_from_named_parameter<typename decltype(max_iter_val)::value_type>(parser, max_iter);
290  // check if value makes sense
291  if (max_iter_val == static_cast<typename decltype(max_iter_val)::value_type>(0)) {
292  throw invalid_parameter_exception{ fmt::format("max_iter must be greater than 0, but is {}!", max_iter_val) };
293  }
294  }
295 
296  // start fitting the data set using a C-SVM
297 
298  if (!data.has_labels()) {
299  throw invalid_parameter_exception{ "No labels given for training! Maybe the data is only usable for prediction?" };
300  }
301 
302  // copy parameter and set gamma if necessary
303  parameter params{ params_ };
304  if (params.gamma.is_default()) {
305  // no gamma provided -> use default value which depends on the number of features of the data set
306  params.gamma = 1.0 / data.num_features();
307  }
308 
309  const std::chrono::time_point start_time = std::chrono::steady_clock::now();
310 
311  // create model
312  model<real_type, label_type> csvm_model{ params, data };
313 
314  // solve the minimization problem
315  std::tie(*csvm_model.alpha_ptr_, csvm_model.rho_) = solve_system_of_linear_equations(static_cast<detail::parameter<real_type>>(params), data.data(), *data.y_ptr_, epsilon_val.value(), max_iter_val.value());
316 
317  const std::chrono::time_point end_time = std::chrono::steady_clock::now();
319  "Solved minimization problem (r = b - Ax) using the Conjugate Gradient (CG) methode in {}.\n\n",
320  detail::tracking_entry{ "cg", "total_runtime", std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time) });
321 
322  return csvm_model;
323 }
324 
325 template <typename real_type, typename label_type>
326 std::vector<label_type> csvm::predict(const model<real_type, label_type> &model, const data_set<real_type, label_type> &data) const {
327  if (model.num_features() != data.num_features()) {
328  throw invalid_parameter_exception{ fmt::format("Number of features per data point ({}) must match the number of features per support vector of the provided model ({})!", data.num_features(), model.num_features()) };
329  }
330 
331  // predict values
332  const std::vector<real_type> predicted_values = predict_values(static_cast<detail::parameter<real_type>>(model.params_), model.data_.data(), *model.alpha_ptr_, model.rho_, *model.w_, data.data());
333 
334  // convert predicted values to the correct labels
335  std::vector<label_type> predicted_labels(predicted_values.size());
336 
337  #pragma omp parallel for default(none) shared(predicted_labels, predicted_values, model) if (!std::is_same_v<label_type, bool>)
338  for (typename std::vector<label_type>::size_type i = 0; i < predicted_labels.size(); ++i) {
339  predicted_labels[i] = model.data_.mapping_->get_label_by_mapped_value(plssvm::operators::sign(predicted_values[i]));
340  }
341 
342  return predicted_labels;
343 }
344 
345 template <typename real_type, typename label_type>
347  return this->score(model, model.data_);
348 }
349 
350 template <typename real_type, typename label_type>
352  // the data set must contain labels in order to score the learned model
353  if (!data.has_labels()) {
354  throw invalid_parameter_exception{ "The data set to score must have labels!" };
355  }
356  // the number of features must be equal
357  if (model.num_features() != data.num_features()) {
358  throw invalid_parameter_exception{ fmt::format("Number of features per data point ({}) must match the number of features per support vector of the provided model ({})!", data.num_features(), model.num_features()) };
359  }
360 
361  // predict labels
362  const std::vector<label_type> predicted_labels = predict(model, data);
363  // correct labels
364  const std::vector<label_type> &correct_labels = data.labels().value();
365 
366  // calculate the accuracy
367  typename std::vector<label_type>::size_type correct{ 0 };
368  #pragma omp parallel for reduction(+ : correct) default(none) shared(predicted_labels, correct_labels)
369  for (typename std::vector<label_type>::size_type i = 0; i < predicted_labels.size(); ++i) {
370  if (predicted_labels[i] == correct_labels[i]) {
371  ++correct;
372  }
373  }
374  return static_cast<real_type>(correct) / static_cast<real_type>(predicted_labels.size());
375 }
376 
377 inline void csvm::sanity_check_parameter() const {
378  // kernel: valid kernel function
380  throw invalid_parameter_exception{ fmt::format("Invalid kernel function {} given!", detail::to_underlying(params_.kernel_type)) };
381  }
382 
383  // gamma: must be greater than 0 IF explicitly provided, but only in the polynomial and rbf kernel
385  throw invalid_parameter_exception{ fmt::format("gamma must be greater than 0.0, but is {}!", params_.gamma) };
386  }
387  // degree: all allowed
388  // coef0: all allowed
389  // cost: all allowed
390 }
391 
393 namespace detail {
394 
399 template <typename T>
400 struct csvm_backend_exists : std::false_type {};
401 
402 } // namespace detail
404 
409 template <typename T>
410 struct csvm_backend_exists : detail::csvm_backend_exists<detail::remove_cvref_t<T>> {};
411 
415 template <typename T>
417 
418 } // namespace plssvm
419 
420 #endif // PLSSVM_CSVM_HPP_
Base class for all C-SVM backends.
Definition: csvm.hpp:50
parameter params_
The SVM parameter (e.g., cost, degree, gamma, coef0) currently in use.
Definition: csvm.hpp:221
std::vector< label_type > predict(const model< real_type, label_type > &model, const data_set< real_type, label_type > &data) const
Predict the labels for the data set using the model.
Definition: csvm.hpp:326
csvm(const csvm &)=delete
Delete copy-constructor since a CSVM is a move-only type.
virtual std::vector< float > predict_values(const detail::parameter< float > &params, const std::vector< std::vector< float >> &support_vectors, const std::vector< float > &alpha, float rho, std::vector< float > &w, const std::vector< std::vector< float >> &predict_points) const =0
Uses the already learned model to predict the class of multiple (new) data points.
virtual std::pair< std::vector< float >, float > solve_system_of_linear_equations(const detail::parameter< float > &params, const std::vector< std::vector< float >> &A, std::vector< float > b, float eps, unsigned long long max_iter) const =0
Solves the equation using the Conjugated Gradients algorithm.
virtual std::vector< double > predict_values(const detail::parameter< double > &params, const std::vector< std::vector< double >> &support_vectors, const std::vector< double > &alpha, double rho, std::vector< double > &w, const std::vector< std::vector< double >> &predict_points) const =0
Uses the already learned model to predict the class of multiple (new) data points.
target_platform get_target_platform() const noexcept
Return the target platform (i.e, CPU or GPU including the vendor) this SVM runs on.
Definition: csvm.hpp:93
csvm(parameter params={})
Construct a C-SVM using the SVM parameter params.
Definition: csvm.hpp:224
parameter get_params() const noexcept
Return the currently used SVM parameter.
Definition: csvm.hpp:98
csvm(csvm &&) noexcept=default
Default move-constructor since a virtual destructor has been declared.
virtual std::pair< std::vector< double >, double > solve_system_of_linear_equations(const detail::parameter< double > &params, const std::vector< std::vector< double >> &A, std::vector< double > b, double eps, unsigned long long max_iter) const =0
Solves the equation using the Conjugated Gradients algorithm.
real_type score(const model< real_type, label_type > &model) const
Calculate the accuracy of the model.
Definition: csvm.hpp:346
model< real_type, label_type > fit(const data_set< real_type, label_type > &data, Args &&...named_args) const
Fit a model using the current SVM on the data.
Definition: csvm.hpp:264
void set_params(parameter params) noexcept
Override the old SVM parameter with the new plssvm::parameter params.
Definition: csvm.hpp:104
target_platform target_
The target platform of this SVM.
Definition: csvm.hpp:211
void sanity_check_parameter() const
Perform some sanity checks on the passed SVM parameters.
Definition: csvm.hpp:377
bool has_labels() const noexcept
Returns whether this data set contains labels or not.
Definition: data_set.hpp:194
std::shared_ptr< const label_mapper > mapping_
The mapping used to convert the original label to its mapped value and vice versa; may be nullptr if ...
Definition: data_set.hpp:285
const std::vector< std::vector< real_type > > & data() const noexcept
Return the data points in this data set.
Definition: data_set.hpp:189
std::shared_ptr< std::vector< real_type > > y_ptr_
A pointer to the mapped values of the labels of this data set; may be nullptr if no labels have been ...
Definition: data_set.hpp:277
size_type num_features() const noexcept
Returns the number of features in this data set.
Definition: data_set.hpp:218
optional_ref< const std::vector< label_type > > labels() const noexcept
Returns an optional reference to the labels in this data set.
Definition: data_set.hpp:625
size_type num_data_points() const noexcept
Returns the number of data points in this data set.
Definition: data_set.hpp:213
This class encapsulates a value that may be a default value or not.
Definition: default_value.hpp:62
constexpr const value_type & value() const noexcept
Get the currently active value: the user provided value if provided, otherwise the default value is r...
Definition: default_value.hpp:150
constexpr bool is_default() const noexcept
Check whether the currently active value is the default value.
Definition: default_value.hpp:170
Exception type thrown if the provided parameter is invalid.
Definition: exceptions.hpp:62
Implements a class encapsulating the result of a call to the SVM fit function. A model is used to pre...
Definition: model.hpp:50
data_set< real_type, label_type > data_
The data (support vectors + respective label) used to learn this model.
Definition: model.hpp:150
real_type rho_
The bias after learning this model.
Definition: model.hpp:159
parameter params_
The SVM parameter used to learn this model.
Definition: model.hpp:148
size_type num_features() const noexcept
The number of features of the support vectors used in this model.
Definition: model.hpp:88
std::shared_ptr< std::vector< real_type > > w_
A vector used to speedup the prediction in case of the linear kernel function.
Definition: model.hpp:166
std::shared_ptr< std::vector< real_type > > alpha_ptr_
The learned weights for each support vector.
Definition: model.hpp:157
Implements a data set class encapsulating all data points, features, and potential labels.
Implements a class used to be able to distinguish between the default value of a variable and an assi...
Defines universal utility functions.
Implements custom exception classes derived from std::runtime_error including source location informa...
Defines an enumeration holding all possible kernel function types.
Defines a simple logging function.
Implements a model class encapsulating the results of a SVM fit call.
void log(const verbosity_level verb, const std::string_view msg, Args &&...args)
Definition: logger.hpp:109
constexpr bool has_only_parameter_named_args_v
Trait to check whether Args only contains named-parameter that can be used to initialize a plssvm::pa...
Definition: parameter.hpp:66
constexpr std::underlying_type_t< Enum > to_underlying(const Enum e) noexcept
Converts an enumeration to its underlying type.
Definition: utility.hpp:63
constexpr T sign(const T x)
Returns +1 if x is positive and -1 if x is negative or 0.
Definition: operators.hpp:179
The main namespace containing all public API functions.
Definition: backend_types.hpp:24
target_platform
Enum class for all possible targets.
Definition: target_platforms.hpp:25
constexpr bool csvm_backend_exists_v
Sets the value of the value member to true if T is a C-SVM using an available backend....
Definition: csvm.hpp:416
Defines (arithmetic) functions on std::vector and scalars.
Implements the parameter class encapsulating all important C-SVM parameters.
Defines a performance tracker which can dump performance information in a YAML file.
Sets the value of the value member to true if T is a C-SVM using an available backend....
Definition: csvm.hpp:410
This class denotes an explicit default value initialization used to distinguish between the default v...
Definition: default_value.hpp:37
default_value< real_type > cost
The cost parameter in the C-SVM.
Definition: parameter.hpp:165
default_value< real_type > coef0
The coef0 parameter used in the polynomial kernel function.
Definition: parameter.hpp:163
default_value< int > degree
The degree parameter used in the polynomial kernel function.
Definition: parameter.hpp:159
default_value< kernel_function_type > kernel_type
The used kernel function: linear, polynomial, or radial basis functions (rbf).
Definition: parameter.hpp:157
default_value< real_type > gamma
The gamma parameter used in the polynomial and rbf kernel functions.
Definition: parameter.hpp:161
A single tracking entry containing a specific category, a unique name, and the actual value to be tra...
Definition: performance_tracker.hpp:40
Defines an enumeration holding all possible target platforms. Can also include targets not available ...
Defines some generic type traits used in the PLSSVM library.
#define PLSSVM_REQUIRES(...)
A shorthand macro for the std::enable_if_t type trait.
Definition: type_traits.hpp:33