Algorithms_in_C++  1.0.0
Set of algorithms implemented in C++.
ordinary_least_squares_regressor.cpp File Reference

Linear regression example using Ordinary least squares More...

#include <cassert>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <vector>
Include dependency graph for ordinary_least_squares_regressor.cpp:

Functions

template<typename T >
std::ostreamoperator<< (std::ostream &out, std::vector< std::vector< T >> const &v)
 
template<typename T >
std::ostreamoperator<< (std::ostream &out, std::vector< T > const &v)
 
template<typename T >
bool is_square (std::vector< std::vector< T >> const &A)
 
template<typename T >
std::vector< std::vector< T > > operator* (std::vector< std::vector< T >> const &A, std::vector< std::vector< T >> const &B)
 
template<typename T >
std::vector< T > operator* (std::vector< std::vector< T >> const &A, std::vector< T > const &B)
 
template<typename T >
std::vector< float > operator* (float const scalar, std::vector< T > const &A)
 
template<typename T >
std::vector< float > operator* (std::vector< T > const &A, float const scalar)
 
template<typename T >
std::vector< float > operator/ (std::vector< T > const &A, float const scalar)
 
template<typename T >
std::vector< T > operator- (std::vector< T > const &A, std::vector< T > const &B)
 
template<typename T >
std::vector< T > operator+ (std::vector< T > const &A, std::vector< T > const &B)
 
template<typename T >
std::vector< std::vector< float > > get_inverse (std::vector< std::vector< T >> const &A)
 
template<typename T >
std::vector< std::vector< T > > get_transpose (std::vector< std::vector< T >> const &A)
 
template<typename T >
std::vector< float > fit_OLS_regressor (std::vector< std::vector< T >> const &X, std::vector< T > const &Y)
 
template<typename T >
std::vector< float > predict_OLS_regressor (std::vector< std::vector< T >> const &X, std::vector< float > const &beta)
 
void ols_test ()
 
int main ()
 

Detailed Description

Linear regression example using Ordinary least squares

Program that gets the number of data samples and number of features per sample along with output per sample. It applies OLS regression to compute the regression output for additional test data samples.

Author
Krishna Vedala

Function Documentation

◆ fit_OLS_regressor()

template<typename T >
std::vector<float> fit_OLS_regressor ( std::vector< std::vector< T >> const &  X,
std::vector< T > const &  Y 
)

Perform Ordinary Least Squares curve fit. This operation is defined as

\[\beta = \left(X^TXX^T\right)Y\]

Parameters
Xfeature matrix with rows representing sample vector of features
Yknown regression value for each sample
Returns
fitted regression model polynomial coefficients
313  {
314  // NxF
316  for (size_t i = 0; i < X2.size(); i++)
317  // add Y-intercept -> Nx(F+1)
318  X2[i].push_back(1);
319  // (F+1)xN
321  // (F+1)x(F+1)
322  std::vector<std::vector<T>> tmp = get_inverse(Xt * X2);
323  // (F+1)xN
324  std::vector<std::vector<float>> out = tmp * Xt;
325  // cout << endl
326  // << "Projection matrix: " << X2 * out << endl;
327 
328  // Fx1,1 -> (F+1)^th element is the independent constant
329  return out * Y;
330 }
Here is the call graph for this function:

◆ get_inverse()

template<typename T >
std::vector<std::vector<float> > get_inverse ( std::vector< std::vector< T >> const &  A)

Get matrix inverse using Row-trasnformations. Given matrix must be a square and non-singular.

Returns
inverse matrix
221  {
222  // Assuming A is square matrix
223  size_t N = A.size();
224 
225  std::vector<std::vector<float>> inverse(N);
226  for (size_t row = 0; row < N; row++) {
227  // preallocatae a resultant identity matrix
228  inverse[row] = std::vector<float>(N);
229  for (size_t col = 0; col < N; col++)
230  inverse[row][col] = (row == col) ? 1.f : 0.f;
231  }
232 
233  if (!is_square(A)) {
234  std::cerr << "A must be a square matrix!" << std::endl;
235  return inverse;
236  }
237 
238  // preallocatae a temporary matrix identical to A
240  for (size_t row = 0; row < N; row++) {
241  std::vector<float> v(N);
242  for (size_t col = 0; col < N; col++)
243  v[col] = static_cast<float>(A[row][col]);
244  temp[row] = v;
245  }
246 
247  // start transformations
248  for (size_t row = 0; row < N; row++) {
249  for (size_t row2 = row; row2 < N && temp[row][row] == 0; row2++) {
250  // this to ensure diagonal elements are not 0
251  temp[row] = temp[row] + temp[row2];
252  inverse[row] = inverse[row] + inverse[row2];
253  }
254 
255  for (size_t col2 = row; col2 < N && temp[row][row] == 0; col2++) {
256  // this to further ensure diagonal elements are not 0
257  for (size_t row2 = 0; row2 < N; row2++) {
258  temp[row2][row] = temp[row2][row] + temp[row2][col2];
259  inverse[row2][row] = inverse[row2][row] + inverse[row2][col2];
260  }
261  }
262 
263  if (temp[row][row] == 0) {
264  // Probably a low-rank matrix and hence singular
265  std::cerr << "Low-rank matrix, no inverse!" << std::endl;
266  return inverse;
267  }
268 
269  // set diagonal to 1
270  float divisor = static_cast<float>(temp[row][row]);
271  temp[row] = temp[row] / divisor;
272  inverse[row] = inverse[row] / divisor;
273  // Row transformations
274  for (size_t row2 = 0; row2 < N; row2++) {
275  if (row2 == row)
276  continue;
277  float factor = temp[row2][row];
278  temp[row2] = temp[row2] - factor * temp[row];
279  inverse[row2] = inverse[row2] - factor * inverse[row];
280  }
281  }
282 
283  return inverse;
284 }
Here is the call graph for this function:

◆ get_transpose()

template<typename T >
std::vector<std::vector<T> > get_transpose ( std::vector< std::vector< T >> const &  A)

matrix transpose

Returns
resultant matrix
292  {
293  std::vector<std::vector<T>> result(A[0].size());
294 
295  for (size_t row = 0; row < A[0].size(); row++) {
296  std::vector<T> v(A.size());
297  for (size_t col = 0; col < A.size(); col++) v[col] = A[col][row];
298 
299  result[row] = v;
300  }
301  return result;
302 }

◆ is_square()

template<typename T >
bool is_square ( std::vector< std::vector< T >> const &  A)
inline

function to check if given matrix is a square matrix

Returns
1 if true, 0 if false
57  {
58  // Assuming A is square matrix
59  size_t N = A.size();
60  for (size_t i = 0; i < N; i++)
61  if (A[i].size() != N)
62  return false;
63  return true;
64 }

◆ main()

int main ( )

main function

410  {
411  ols_test();
412 
413  size_t N, F;
414 
415  std::cout << "Enter number of features: ";
416  // number of features = columns
417  std::cin >> F;
418  std::cout << "Enter number of samples: ";
419  // number of samples = rows
420  std::cin >> N;
421 
423  std::vector<float> Y(N);
424 
425  std::cout
426  << "Enter training data. Per sample, provide features and one output."
427  << std::endl;
428 
429  for (size_t rows = 0; rows < N; rows++) {
430  std::vector<float> v(F);
431  std::cout << "Sample# " << rows + 1 << ": ";
432  for (size_t cols = 0; cols < F; cols++)
433  // get the F features
434  std::cin >> v[cols];
435  data[rows] = v;
436  // get the corresponding output
437  std::cin >> Y[rows];
438  }
439 
441  std::cout << std::endl << std::endl << "beta:" << beta << std::endl;
442 
443  size_t T;
444  std::cout << "Enter number of test samples: ";
445  // number of test sample inputs
446  std::cin >> T;
448  // vector<float> Y2(T);
449 
450  for (size_t rows = 0; rows < T; rows++) {
451  std::cout << "Sample# " << rows + 1 << ": ";
452  std::vector<float> v(F);
453  for (size_t cols = 0; cols < F; cols++) std::cin >> v[cols];
454  data2[rows] = v;
455  }
456 
457  std::vector<float> out = predict_OLS_regressor(data2, beta);
458  for (size_t rows = 0; rows < T; rows++) std::cout << out[rows] << std::endl;
459 
460  return 0;
461 }
Here is the call graph for this function:

◆ ols_test()

void ols_test ( )

Self test checks

358  {
359  int F = 3, N = 5;
360 
361  /* test function = x^2 -5 */
362  std::cout << "Test 1 (quadratic function)....";
363  // create training data set with features = x, x^2, x^3
365  {{-5, 25, -125}, {-1, 1, -1}, {0, 0, 0}, {1, 1, 1}, {6, 36, 216}});
366  // create corresponding outputs
367  std::vector<float> Y1({20, -4, -5, -4, 31});
368  // perform regression modelling
369  std::vector<float> beta1 = fit_OLS_regressor(data1, Y1);
370  // create test data set with same features = x, x^2, x^3
371  std::vector<std::vector<float>> test_data1(
372  {{-2, 4, -8}, {2, 4, 8}, {-10, 100, -1000}, {10, 100, 1000}});
373  // expected regression outputs
374  std::vector<float> expected1({-1, -1, 95, 95});
375  // predicted regression outputs
376  std::vector<float> out1 = predict_OLS_regressor(test_data1, beta1);
377  // compare predicted results are within +-0.01 limit of expected
378  for (size_t rows = 0; rows < out1.size(); rows++)
379  assert(std::abs(out1[rows] - expected1[rows]) < 0.01);
380  std::cout << "passed\n";
381 
382  /* test function = x^3 + x^2 - 100 */
383  std::cout << "Test 2 (cubic function)....";
384  // create training data set with features = x, x^2, x^3
386  {{-5, 25, -125}, {-1, 1, -1}, {0, 0, 0}, {1, 1, 1}, {6, 36, 216}});
387  // create corresponding outputs
388  std::vector<float> Y2({-200, -100, -100, 98, 152});
389  // perform regression modelling
390  std::vector<float> beta2 = fit_OLS_regressor(data2, Y2);
391  // create test data set with same features = x, x^2, x^3
392  std::vector<std::vector<float>> test_data2(
393  {{-2, 4, -8}, {2, 4, 8}, {-10, 100, -1000}, {10, 100, 1000}});
394  // expected regression outputs
395  std::vector<float> expected2({-104, -88, -1000, 1000});
396  // predicted regression outputs
397  std::vector<float> out2 = predict_OLS_regressor(test_data2, beta2);
398  // compare predicted results are within +-0.01 limit of expected
399  for (size_t rows = 0; rows < out2.size(); rows++)
400  assert(std::abs(out2[rows] - expected2[rows]) < 0.01);
401  std::cout << "passed\n";
402 
403  std::cout << std::endl; // ensure test results are displayed on screen
404  // (flush stdout)
405 }
Here is the call graph for this function:

◆ operator*() [1/4]

template<typename T >
std::vector<float> operator* ( float const  scalar,
std::vector< T > const &  A 
)

pre-multiplication of a vector by a scalar

Returns
resultant vector
133  {
134  // Number of rows in A
135  size_t N_A = A.size();
136 
137  std::vector<float> result(N_A);
138 
139  for (size_t row = 0; row < N_A; row++) {
140  result[row] += A[row] * static_cast<float>(scalar);
141  }
142 
143  return result;
144 }
Here is the call graph for this function:

◆ operator*() [2/4]

template<typename T >
std::vector<std::vector<T> > operator* ( std::vector< std::vector< T >> const &  A,
std::vector< std::vector< T >> const &  B 
)

Matrix multiplication such that if A is size (mxn) and B is of size (pxq) then the multiplication is defined only when n = p and the resultant matrix is of size (mxq)

Returns
resultant matrix
75  {
76  // Number of rows in A
77  size_t N_A = A.size();
78  // Number of columns in B
79  size_t N_B = B[0].size();
80 
81  std::vector<std::vector<T>> result(N_A);
82 
83  if (A[0].size() != B.size()) {
84  std::cerr << "Number of columns in A != Number of rows in B ("
85  << A[0].size() << ", " << B.size() << ")" << std::endl;
86  return result;
87  }
88 
89  for (size_t row = 0; row < N_A; row++) {
90  std::vector<T> v(N_B);
91  for (size_t col = 0; col < N_B; col++) {
92  v[col] = static_cast<T>(0);
93  for (size_t j = 0; j < B.size(); j++)
94  v[col] += A[row][j] * B[j][col];
95  }
96  result[row] = v;
97  }
98 
99  return result;
100 }
Here is the call graph for this function:

◆ operator*() [3/4]

template<typename T >
std::vector<T> operator* ( std::vector< std::vector< T >> const &  A,
std::vector< T > const &  B 
)

multiplication of a matrix with a column vector

Returns
resultant vector
108  {
109  // Number of rows in A
110  size_t N_A = A.size();
111 
112  std::vector<T> result(N_A);
113 
114  if (A[0].size() != B.size()) {
115  std::cerr << "Number of columns in A != Number of rows in B ("
116  << A[0].size() << ", " << B.size() << ")" << std::endl;
117  return result;
118  }
119 
120  for (size_t row = 0; row < N_A; row++) {
121  result[row] = static_cast<T>(0);
122  for (size_t j = 0; j < B.size(); j++) result[row] += A[row][j] * B[j];
123  }
124 
125  return result;
126 }
Here is the call graph for this function:

◆ operator*() [4/4]

template<typename T >
std::vector<float> operator* ( std::vector< T > const &  A,
float const  scalar 
)

post-multiplication of a vector by a scalar

Returns
resultant vector
151  {
152  // Number of rows in A
153  size_t N_A = A.size();
154 
155  std::vector<float> result(N_A);
156 
157  for (size_t row = 0; row < N_A; row++)
158  result[row] = A[row] * static_cast<float>(scalar);
159 
160  return result;
161 }
Here is the call graph for this function:

◆ operator+()

template<typename T >
std::vector<T> operator+ ( std::vector< T > const &  A,
std::vector< T > const &  B 
)

addition of two vectors of identical lengths

Returns
resultant vector
198  {
199  // Number of rows in A
200  size_t N = A.size();
201 
202  std::vector<T> result(N);
203 
204  if (B.size() != N) {
205  std::cerr << "Vector dimensions shouldbe identical!" << std::endl;
206  return A;
207  }
208 
209  for (size_t row = 0; row < N; row++) result[row] = A[row] + B[row];
210 
211  return result;
212 }
Here is the call graph for this function:

◆ operator-()

template<typename T >
std::vector<T> operator- ( std::vector< T > const &  A,
std::vector< T > const &  B 
)

subtraction of two vectors of identical lengths

Returns
resultant vector
177  {
178  // Number of rows in A
179  size_t N = A.size();
180 
181  std::vector<T> result(N);
182 
183  if (B.size() != N) {
184  std::cerr << "Vector dimensions shouldbe identical!" << std::endl;
185  return A;
186  }
187 
188  for (size_t row = 0; row < N; row++) result[row] = A[row] - B[row];
189 
190  return result;
191 }
Here is the call graph for this function:

◆ operator/()

template<typename T >
std::vector<float> operator/ ( std::vector< T > const &  A,
float const  scalar 
)

division of a vector by a scalar

Returns
resultant vector
168  {
169  return (1.f / scalar) * A;
170 }

◆ operator<<() [1/2]

template<typename T >
std::ostream& operator<< ( std::ostream out,
std::vector< std::vector< T >> const &  v 
)

operator to print a matrix

23  {
24  const int width = 10;
25  const char separator = ' ';
26 
27  for (size_t row = 0; row < v.size(); row++) {
28  for (size_t col = 0; col < v[row].size(); col++)
29  out << std::left << std::setw(width) << std::setfill(separator)
30  << v[row][col];
31  out << std::endl;
32  }
33 
34  return out;
35 }
Here is the call graph for this function:

◆ operator<<() [2/2]

template<typename T >
std::ostream& operator<< ( std::ostream out,
std::vector< T > const &  v 
)

operator to print a vector

41  {
42  const int width = 15;
43  const char separator = ' ';
44 
45  for (size_t row = 0; row < v.size(); row++)
46  out << std::left << std::setw(width) << std::setfill(separator)
47  << v[row];
48 
49  return out;
50 }
Here is the call graph for this function:

◆ predict_OLS_regressor()

template<typename T >
std::vector<float> predict_OLS_regressor ( std::vector< std::vector< T >> const &  X,
std::vector< float > const &  beta 
)

Given data and OLS model coeffficients, predict regression estimates. This operation is defined as

\[y_{\text{row}=i} = \sum_{j=\text{columns}}\beta_j\cdot X_{i,j}\]

Parameters
Xfeature matrix with rows representing sample vector of features
betafitted regression model
Returns
vector with regression values for each sample
344  {
345  std::vector<float> result(X.size());
346 
347  for (size_t rows = 0; rows < X.size(); rows++) {
348  // -> start with constant term
349  result[rows] = beta[X[0].size()];
350  for (size_t cols = 0; cols < X[0].size(); cols++)
351  result[rows] += beta[cols] * X[rows][cols];
352  }
353  // Nx1
354  return result;
355 }
std::vector
STL class.
std::vector::size
T size(T... args)
std::setfill
T setfill(T... args)
is_square
bool is_square(std::vector< std::vector< T >> const &A)
Definition: ordinary_least_squares_regressor.cpp:57
std::cerr
predict_OLS_regressor
std::vector< float > predict_OLS_regressor(std::vector< std::vector< T >> const &X, std::vector< float > const &beta)
Definition: ordinary_least_squares_regressor.cpp:342
data
int data[MAX]
test data
Definition: hash_search.cpp:24
fit_OLS_regressor
std::vector< float > fit_OLS_regressor(std::vector< std::vector< T >> const &X, std::vector< T > const &Y)
Definition: ordinary_least_squares_regressor.cpp:312
std::endl
T endl(T... args)
get_inverse
std::vector< std::vector< float > > get_inverse(std::vector< std::vector< T >> const &A)
Definition: ordinary_least_squares_regressor.cpp:220
std::left
T left(T... args)
ols_test
void ols_test()
Definition: ordinary_least_squares_regressor.cpp:358
std::setw
T setw(T... args)
std::cin
get_transpose
std::vector< std::vector< T > > get_transpose(std::vector< std::vector< T >> const &A)
Definition: ordinary_least_squares_regressor.cpp:291