smosolver.h
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2019-2024, NVIDIA CORPORATION.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #pragma once
18 
19 #include <cuml/common/logger.hpp>
20 #include <cuml/svm/svm_model.h>
21 
22 #include <raft/core/handle.hpp>
23 
24 #include <rmm/device_scalar.hpp>
25 #include <rmm/device_uvector.hpp>
26 
27 #include <thrust/device_ptr.h>
28 
29 #include <cassert>
30 #include <chrono>
31 #include <cstdlib>
32 #include <iostream>
33 #include <limits>
34 #include <sstream>
35 #include <string>
36 #include <type_traits>
37 
38 namespace ML {
39 namespace SVM {
40 
66 template <typename math_t>
67 class SmoSolver {
68  public:
69  SmoSolver(const raft::handle_t& handle,
70  SvmParameter param,
71  raft::distance::kernels::KernelType kernel_type,
72  raft::distance::kernels::GramMatrixBase<math_t>* kernel)
73  : handle(handle),
74  C(param.C),
75  tol(param.tol),
76  kernel(kernel),
77  kernel_type(kernel_type),
78  cache_size(param.cache_size),
79  nochange_steps(param.nochange_steps),
80  epsilon(param.epsilon),
81  svmType(param.svmType),
82  stream(handle.get_stream()),
83  return_buff(2, stream),
84  alpha(0, stream),
85  C_vec(0, stream),
86  delta_alpha(0, stream),
87  f(0, stream),
88  y_label(0, stream)
89  {
91  }
92 
93  void GetNonzeroDeltaAlpha(const math_t* vec,
94  int n_ws,
95  const int* idx,
96  math_t* nz_vec,
97  int* n_nz,
98  int* nz_idx,
99  cudaStream_t stream);
121  template <typename MatrixViewType>
122  void Solve(MatrixViewType matrix,
123  int n_rows,
124  int n_cols,
125  math_t* y,
126  const math_t* sample_weight,
127  math_t** dual_coefs,
128  int* n_support,
129  SupportStorage<math_t>* support_matrix,
130  int** idx,
131  math_t* b,
132  int max_outer_iter = -1,
133  int max_inner_iter = 10000);
134 
150  void UpdateF(math_t* f, int n_rows, const math_t* delta_alpha, int n_ws, const math_t* cacheTile);
151 
173  void Initialize(math_t** y, const math_t* sample_weight, int n_rows, int n_cols);
174 
175  void InitPenalty(math_t* C_vec, const math_t* sample_weight, int n_rows);
176 
189  void SvcInit(const math_t* y);
190 
224  void SvrInit(const math_t* yr, int n_rows, math_t* yc, math_t* f);
225 
226  private:
227  const raft::handle_t& handle;
228  cudaStream_t stream;
229 
230  int n_rows = 0;
231  int n_cols = 0;
232  int n_ws = 0;
233  int n_train = 0;
234 
235  // Buffers for the domain [n_train]
236  rmm::device_uvector<math_t> alpha;
237  rmm::device_uvector<math_t> f;
238  rmm::device_uvector<math_t> y_label;
239 
240  rmm::device_uvector<math_t> C_vec;
241 
242  // Buffers for the working set [n_ws]
244  rmm::device_uvector<math_t> delta_alpha;
245 
246  // Buffers to return some parameters from the kernel (iteration number, and
247  // convergence information)
248  rmm::device_uvector<math_t> return_buff;
249  math_t host_return_buff[2];
250 
251  math_t C;
252  math_t tol;
253  math_t epsilon;
254 
255  raft::distance::kernels::GramMatrixBase<math_t>* kernel;
256  raft::distance::kernels::KernelType kernel_type;
257  float cache_size;
258 
259  SvmType svmType;
260 
261  // Variables to track convergence of training
262  math_t diff_prev;
263  int n_small_diff;
264  int nochange_steps;
265  int n_increased_diff;
266  int n_iter;
267  bool report_increased_diff;
268 
269  bool CheckStoppingCondition(math_t diff)
270  {
271  if (diff > diff_prev * 1.5 && n_iter > 0) {
272  // Ideally, diff should decrease monotonically. In practice we can have
273  // small fluctuations (10% increase is not uncommon). Here we consider a
274  // 50% increase in the diff value large enough to indicate a problem.
275  // The 50% value is an educated guess that triggers the convergence debug
276  // message for problematic use cases while avoids false alarms in many
277  // other cases.
278  n_increased_diff++;
279  }
280  if (report_increased_diff && n_iter > 100 && n_increased_diff > n_iter * 0.1) {
282  "Solver is not converging monotonically. This might be caused by "
283  "insufficient normalization of the feature columns. In that case "
284  "MinMaxScaler((0,1)) could help. Alternatively, for nonlinear kernels, "
285  "you can try to increase the gamma parameter. To limit execution time, "
286  "you can also adjust the number of iterations using the max_iter "
287  "parameter.");
288  report_increased_diff = false;
289  }
290  bool keep_going = true;
291  if (abs(diff - diff_prev) < 0.001 * tol) {
292  n_small_diff++;
293  } else {
294  diff_prev = diff;
295  n_small_diff = 0;
296  }
297  if (n_small_diff > nochange_steps) {
299  "SMO error: Stopping due to unchanged diff over %d"
300  " consecutive steps",
301  nochange_steps);
302  keep_going = false;
303  }
304  if (diff < tol) keep_going = false;
305  if (isnan(diff)) {
306  std::string txt;
307  if (std::is_same<float, math_t>::value) {
308  txt +=
309  " This might be caused by floating point overflow. In such case using"
310  " fp64 could help. Alternatively, try gamma='scale' kernel"
311  " parameter.";
312  }
313  THROW("SMO error: NaN found during fitting.%s", txt.c_str());
314  }
315  return keep_going;
316  }
317 
319  int GetDefaultMaxIter(int n_train, int max_outer_iter)
320  {
321  if (max_outer_iter == -1) {
322  max_outer_iter = n_train < std::numeric_limits<int>::max() / 100
323  ? n_train * 100
325  max_outer_iter = max(100000, max_outer_iter);
326  }
327  // else we have user defined iteration count which we do not change
328  return max_outer_iter;
329  }
330 
331  void ResizeBuffers(int n_train, int n_cols)
332  {
333  // This needs to know n_train, therefore it can be only called during solve
334  alpha.resize(n_train, stream);
335  C_vec.resize(n_train, stream);
336  f.resize(n_train, stream);
337  delta_alpha.resize(n_ws, stream);
338  if (svmType == EPSILON_SVR) y_label.resize(n_train, stream);
339  }
340 
341  void ReleaseBuffers()
342  {
343  alpha.release();
344  delta_alpha.release();
345  f.release();
346  y_label.release();
347  }
348 };
349 
350 }; // end namespace SVM
351 }; // end namespace ML
void setLevel(int level)
Set the logging level.
Definition: logger.cpp:67
static Logger & get()
Singleton method to get the underlying logger object.
Definition: logger.cpp:52
Solve the quadratic optimization problem using two level decomposition and Sequential Minimal Optimiz...
Definition: smosolver.h:67
void SvrInit(const math_t *yr, int n_rows, math_t *yc, math_t *f)
Initializes the solver for epsilon-SVR.
void UpdateF(math_t *f, int n_rows, const math_t *delta_alpha, int n_ws, const math_t *cacheTile)
Update the f vector after a block solve step.
void Initialize(math_t **y, const math_t *sample_weight, int n_rows, int n_cols)
Initialize the problem to solve.
void SvcInit(const math_t *y)
Initialize Support Vector Classification.
void GetNonzeroDeltaAlpha(const math_t *vec, int n_ws, const int *idx, math_t *nz_vec, int *n_nz, int *nz_idx, cudaStream_t stream)
void Solve(MatrixViewType matrix, int n_rows, int n_cols, math_t *y, const math_t *sample_weight, math_t **dual_coefs, int *n_support, SupportStorage< math_t > *support_matrix, int **idx, math_t *b, int max_outer_iter=-1, int max_inner_iter=10000)
Solve the quadratic optimization problem.
void InitPenalty(math_t *C_vec, const math_t *sample_weight, int n_rows)
SmoSolver(const raft::handle_t &handle, SvmParameter param, raft::distance::kernels::KernelType kernel_type, raft::distance::kernels::GramMatrixBase< math_t > *kernel)
Definition: smosolver.h:69
#define CUML_LOG_ERROR(fmt,...)
Definition: logger.hpp:222
#define CUML_LOG_DEBUG(fmt,...)
Definition: logger.hpp:198
SvmType
Definition: svm_parameter.h:21
@ EPSILON_SVR
Definition: svm_parameter.h:21
math_t max(math_t a, math_t b)
Definition: learning_rate.h:27
Definition: dbscan.hpp:30
Definition: svm_model.h:23
Definition: svm_parameter.h:34
int verbosity
Print information about training.
Definition: svm_parameter.h:42