OmniSciDB  a5dc49c757
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
MLPackFunctions.hpp
Go to the documentation of this file.
1 /*
2  * Copyright 2022 HEAVY.AI, Inc., Inc.
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 #ifndef __CUDACC__
20 #ifdef HAVE_MLPACK
21 #ifdef HAVE_TBB
22 
23 #include <tbb/parallel_for.h>
24 #include <mlpack/methods/dbscan/dbscan.hpp>
25 #include <mlpack/methods/kmeans/kmeans.hpp>
28 
29 using MatrixT = arma::Mat<double>;
30 
31 inline arma::Mat<double> create_input_matrix(
32  const std::vector<const float*>& input_features,
33  const int64_t num_rows) {
34  const int64_t num_features = input_features.size();
35  arma::Mat<double> features_matrix(num_rows, num_features);
36  for (int64_t c = 0; c < num_features; ++c) {
37  double* matrix_col_ptr = features_matrix.colptr(c);
38  const float* input_features_col = input_features[c];
39  tbb::parallel_for(tbb::blocked_range<int64_t>(0, num_rows),
40  [&](const tbb::blocked_range<int64_t>& r) {
41  const int64_t start_idx = r.begin();
42  const int64_t end_idx = r.end();
43  for (int64_t r = start_idx; r < end_idx; ++r) {
44  matrix_col_ptr[r] = input_features_col[r];
45  }
46  });
47  }
48  return features_matrix;
49 }
50 
51 inline arma::Mat<double> create_input_matrix(
52  const std::vector<const double*>& input_features,
53  const int64_t num_rows) {
54  const int64_t num_features = input_features.size();
55  arma::Mat<double> features_matrix(num_rows, num_features);
56  for (int64_t c = 0; c < num_features; ++c) {
57  memcpy(features_matrix.colptr(c), input_features[c], sizeof(double) * num_rows);
58  }
59  return features_matrix;
60 }
61 
62 inline void rewrite_cluster_id_nulls(const arma::Row<size_t>& cluster_assignments,
63  int32_t* output_clusters,
64  const int64_t num_rows) {
65  tbb::parallel_for(tbb::blocked_range<int64_t>(0, num_rows),
66  [&](const tbb::blocked_range<int64_t>& r) {
67  const int64_t start_idx = r.begin();
68  const int64_t end_idx = r.end();
69  for (int64_t r = start_idx; r < end_idx; ++r) {
70  output_clusters[r] = cluster_assignments[r] == SIZE_MAX
71  ? -1
72  : cluster_assignments[r];
73  }
74  });
75 }
76 
77 template <typename InitStrategy>
78 void run_kmeans(const MatrixT& input_features_matrix_transposed,
79  arma::Row<size_t>& cluster_assignments,
80  const int32_t num_clusters) {
81  mlpack::kmeans::KMeans<mlpack::metric::EuclideanDistance,
82  InitStrategy,
83  mlpack::kmeans::MaxVarianceNewCluster,
84  mlpack::kmeans::NaiveKMeans,
85  MatrixT>
86  kmeans;
87  kmeans.Cluster(input_features_matrix_transposed,
88  static_cast<size_t>(num_clusters),
89  cluster_assignments);
90 }
91 
92 template <typename T>
93 NEVER_INLINE HOST int32_t mlpack_kmeans_impl(const std::vector<const T*>& input_features,
94  int32_t* output_clusters,
95  const int64_t num_rows,
96  const int32_t num_clusters,
97  const int32_t num_iterations,
98  const KMeansInitStrategy init_type) {
99  try {
100  const auto input_features_matrix = create_input_matrix(input_features, num_rows);
101  const MatrixT input_features_matrix_transposed = input_features_matrix.t();
102  arma::Row<size_t> cluster_assignments;
103  switch (init_type) {
106  run_kmeans<mlpack::kmeans::SampleInitialization>(
107  input_features_matrix_transposed, cluster_assignments, num_clusters);
108  break;
109  }
110  // Note that PlusPlus strategy is unimplemented for version of MLPack pulled via apt
111  // in Ubuntu 20.04, but landed at the beginning of 2021 so will be available if we
112  // package it
113  // case KMeansInitStrategy::PLUS_PLUS: {
114  // //run_kmeans<mlpack::kmeans::KMeansPlusPlusInitialization>(input_features_matrix_transposed,
115  // cluster_assignments, num_clusters);
116  // //break;
117  //}
118  default: {
119  throw std::runtime_error(
120  "MLPack KMeans initialization not implemented for given strategy.");
121  break;
122  }
123  }
124 
125  rewrite_cluster_id_nulls(cluster_assignments, output_clusters, num_rows);
126  } catch (std::exception& e) {
127  throw std::runtime_error(e.what());
128  }
129  return num_rows;
130 }
131 
132 template <typename T>
133 NEVER_INLINE HOST int32_t mlpack_dbscan_impl(const std::vector<const T*>& input_features,
134  int32_t* output_clusters,
135  const int64_t num_rows,
136  const double epsilon,
137  const int32_t min_observations) {
138  try {
139  const auto input_features_matrix = create_input_matrix(input_features, num_rows);
140  const MatrixT input_features_matrix_transposed = input_features_matrix.t();
141 
142  mlpack::dbscan::DBSCAN<> dbscan(epsilon, min_observations);
143  arma::Row<size_t> cluster_assignments;
144  dbscan.Cluster(input_features_matrix_transposed, cluster_assignments);
145 
146  rewrite_cluster_id_nulls(cluster_assignments, output_clusters, num_rows);
147  } catch (std::exception& e) {
148  throw std::runtime_error(e.what());
149  }
150  return num_rows;
151 }
152 
153 template <typename T>
154 NEVER_INLINE HOST int32_t
155 mlpack_linear_reg_fit_impl(const T* input_labels,
156  const std::vector<const T*>& input_features,
157  int64_t* output_coef_idxs,
158  double* output_coefs,
159  const int64_t num_rows) {
160  try {
161  // Implement simple linear regression entirely in Armadillo
162  // to avoid overhead of mlpack copies and extra matrix inversion
163  arma::Mat<T> X(num_rows, input_features.size() + 1);
164  // Intercept
165  X.unsafe_col(0).fill(1);
166  // Now copy feature column pointers
167  const int64_t num_features = input_features.size();
168  for (int64_t feature_idx = 0; feature_idx < num_features; ++feature_idx) {
169  memcpy(
170  X.colptr(feature_idx + 1), input_features[feature_idx], sizeof(T) * num_rows);
171  }
172  const arma::Mat<T> Xt = X.t();
173  const arma::Mat<T> XtX = Xt * X; // XtX aka "Gram Matrix"
174  const arma::Col<T> Y(input_labels, num_rows);
175  const arma::Col<T> B_est = arma::solve(XtX, Xt * Y);
176  for (int64_t coef_idx = 0; coef_idx < num_features + 1; ++coef_idx) {
177  output_coef_idxs[coef_idx] = coef_idx;
178  output_coefs[coef_idx] = B_est[coef_idx];
179  }
180  return num_features + 1;
181  } catch (std::exception& e) {
182  throw std::runtime_error(e.what());
183  }
184 }
185 
186 template <typename T>
187 NEVER_INLINE HOST int32_t
188 mlpack_linear_reg_predict_impl(const std::shared_ptr<LinearRegressionModel>& model,
189  const std::vector<const T*>& input_features,
190  T* output_predictions,
191  const int64_t num_rows) {
192  CHECK(model->getModelType() == MLModelType::LINEAR_REG);
193  try {
194  if (model->getNumFeatures() != static_cast<int64_t>(input_features.size())) {
195  throw std::runtime_error(
196  "Number of model coefficients does not match number of input features.");
197  }
198  // Implement simple linear regression entirely in Armadillo
199  // to avoid overhead of mlpack copies and extra matrix inversion
200  const int64_t num_features = input_features.size();
201  const int64_t num_coefs = num_features + 1;
202  arma::Mat<T> X(num_rows, num_coefs);
203  X.unsafe_col(0).fill(1);
204  for (int64_t feature_idx = 0; feature_idx < num_features; ++feature_idx) {
205  memcpy(
206  X.colptr(feature_idx + 1), input_features[feature_idx], sizeof(T) * num_rows);
207  }
208  std::vector<T> casted_coefs(num_coefs);
209  const auto& coefs = model->getCoefs();
210  for (int64_t coef_idx = 0; coef_idx < num_coefs; ++coef_idx) {
211  casted_coefs[coef_idx] = coefs[coef_idx];
212  }
213  const arma::Col<T> B(casted_coefs.data(), num_coefs);
214  const arma::Col<T> Y_hat = X * B;
215  memcpy(output_predictions, Y_hat.colptr(0), sizeof(T) * num_rows);
216  return num_rows;
217  } catch (std::exception& e) {
218  throw std::runtime_error(e.what());
219  }
220 }
221 
222 #endif // #ifdef HAVE_TBB
223 #endif // #ifdef HAVE_MLPACK
224 #endif // #ifdef __CUDACC__
KMeansInitStrategy
#define HOST
void parallel_for(const blocked_range< Int > &range, const Body &body, const Partitioner &p=Partitioner())
#define NEVER_INLINE
#define CHECK(condition)
Definition: Logger.h:291