|
| 1 | +#include <iostream> |
| 2 | +#include <vector> |
| 3 | +#include <cmath> |
| 4 | +#include <random> |
| 5 | +#include <fstream> |
| 6 | +#include <numeric> |
| 7 | +#include <Eigen/Dense> |
| 8 | + |
| 9 | +using namespace std; |
| 10 | +using namespace Eigen; |
| 11 | + |
| 12 | +// Utility functions |
| 13 | +double mean_squared_error(const VectorXd& y_true, const VectorXd& y_pred) { |
| 14 | + return (y_true - y_pred).squaredNorm() / y_true.size(); |
| 15 | +} |
| 16 | + |
| 17 | +double r2_score(const VectorXd& y_true, const VectorXd& y_pred) { |
| 18 | + double mean_y = y_true.mean(); |
| 19 | + double total = (y_true.array() - mean_y).square().sum(); |
| 20 | + double residual = (y_true - y_pred).squaredNorm(); |
| 21 | + return 1.0 - residual / total; |
| 22 | +} |
| 23 | + |
| 24 | +void save_csv(const string& filename, const MatrixXd& X, const VectorXd& y_true, const VectorXd& y_pred) { |
| 25 | + ofstream file(filename); |
| 26 | + file << "X,True Y,Predicted Y\n"; |
| 27 | + for (int i = 0; i < X.rows(); ++i) { |
| 28 | + file << X(i, 0) << "," << y_true(i) << "," << y_pred(i) << "\n"; |
| 29 | + } |
| 30 | + file.close(); |
| 31 | +} |
| 32 | + |
| 33 | +// Linear Regression |
| 34 | +class LinearRegression { |
| 35 | +public: |
| 36 | + VectorXd weights; |
| 37 | + |
| 38 | + void fit(const MatrixXd& X, const VectorXd& y) { |
| 39 | + MatrixXd X_bias(X.rows(), X.cols() + 1); |
| 40 | + X_bias << MatrixXd::Ones(X.rows(), 1), X; |
| 41 | + weights = (X_bias.transpose() * X_bias).ldlt().solve(X_bias.transpose() * y); |
| 42 | + } |
| 43 | + |
| 44 | + VectorXd predict(const MatrixXd& X) const { |
| 45 | + MatrixXd X_bias(X.rows(), X.cols() + 1); |
| 46 | + X_bias << MatrixXd::Ones(X.rows(), 1), X; |
| 47 | + return X_bias * weights; |
| 48 | + } |
| 49 | +}; |
| 50 | + |
| 51 | +// Ridge Regression |
| 52 | +class RidgeRegression { |
| 53 | +public: |
| 54 | + VectorXd weights; |
| 55 | + double alpha; |
| 56 | + |
| 57 | + RidgeRegression(double alpha = 1.0) : alpha(alpha) {} |
| 58 | + |
| 59 | + void fit(const MatrixXd& X, const VectorXd& y) { |
| 60 | + MatrixXd X_bias(X.rows(), X.cols() + 1); |
| 61 | + X_bias << MatrixXd::Ones(X.rows(), 1), X; |
| 62 | + MatrixXd I = MatrixXd::Identity(X_bias.cols(), X_bias.cols()); |
| 63 | + I(0, 0) = 0; // Don't regularize bias |
| 64 | + weights = (X_bias.transpose() * X_bias + alpha * I).ldlt().solve(X_bias.transpose() * y); |
| 65 | + } |
| 66 | + |
| 67 | + VectorXd predict(const MatrixXd& X) const { |
| 68 | + MatrixXd X_bias(X.rows(), X.cols() + 1); |
| 69 | + X_bias << MatrixXd::Ones(X.rows(), 1), X; |
| 70 | + return X_bias * weights; |
| 71 | + } |
| 72 | +}; |
| 73 | + |
| 74 | +// Kernel Ridge Regression (RBF kernel) |
| 75 | +class KernelRidgeRegression { |
| 76 | +public: |
| 77 | + double alpha, gamma; |
| 78 | + MatrixXd X_train; |
| 79 | + VectorXd alpha_vec; |
| 80 | + |
| 81 | + KernelRidgeRegression(double alpha = 1.0, double gamma = 1.0) : alpha(alpha), gamma(gamma) {} |
| 82 | + |
| 83 | + MatrixXd rbf_kernel(const MatrixXd& A, const MatrixXd& B) const { |
| 84 | + MatrixXd K(A.rows(), B.rows()); |
| 85 | + for (int i = 0; i < A.rows(); ++i) { |
| 86 | + for (int j = 0; j < B.rows(); ++j) { |
| 87 | + K(i, j) = exp(-gamma * (A.row(i) - B.row(j)).squaredNorm()); |
| 88 | + } |
| 89 | + } |
| 90 | + return K; |
| 91 | + } |
| 92 | + |
| 93 | + void fit(const MatrixXd& X, const VectorXd& y) { |
| 94 | + X_train = X; |
| 95 | + MatrixXd K = rbf_kernel(X, X); |
| 96 | + alpha_vec = (K + alpha * MatrixXd::Identity(K.rows(), K.cols())).ldlt().solve(y); |
| 97 | + } |
| 98 | + |
| 99 | + VectorXd predict(const MatrixXd& X) const { |
| 100 | + MatrixXd K = rbf_kernel(X, X_train); |
| 101 | + return K * alpha_vec; |
| 102 | + } |
| 103 | +}; |
| 104 | + |
| 105 | +int main() { |
| 106 | + // Generate synthetic data |
| 107 | + int n_samples = 100; |
| 108 | + MatrixXd X(n_samples, 1); |
| 109 | + VectorXd y(n_samples); |
| 110 | + std::mt19937 gen(42); |
| 111 | + std::uniform_real_distribution<> dist(0, 2); |
| 112 | + std::normal_distribution<> noise(0, 0.5); |
| 113 | + |
| 114 | + for (int i = 0; i < n_samples; ++i) { |
| 115 | + X(i, 0) = dist(gen); |
| 116 | + y(i) = 4.0 + 3.0 * X(i, 0) + noise(gen); |
| 117 | + } |
| 118 | + |
| 119 | + // Train and evaluate models |
| 120 | + LinearRegression linear; |
| 121 | + RidgeRegression ridge(1.0); |
| 122 | + KernelRidgeRegression kernel_ridge(1.0, 5.0); |
| 123 | + |
| 124 | + linear.fit(X, y); |
| 125 | + ridge.fit(X, y); |
| 126 | + kernel_ridge.fit(X, y); |
| 127 | + |
| 128 | + VectorXd y_pred_linear = linear.predict(X); |
| 129 | + VectorXd y_pred_ridge = ridge.predict(X); |
| 130 | + VectorXd y_pred_kernel = kernel_ridge.predict(X); |
| 131 | + |
| 132 | + cout << "Linear -> MSE: " << mean_squared_error(y, y_pred_linear) << ", R2: " << r2_score(y, y_pred_linear) << endl; |
| 133 | + cout << "Ridge -> MSE: " << mean_squared_error(y, y_pred_ridge) << ", R2: " << r2_score(y, y_pred_ridge) << endl; |
| 134 | + cout << "Kernel -> MSE: " << mean_squared_error(y, y_pred_kernel) << ", R2: " << r2_score(y, y_pred_kernel) << endl; |
| 135 | + |
| 136 | + save_csv("predictions_linear.csv", X, y, y_pred_linear); |
| 137 | + save_csv("predictions_ridge.csv", X, y, y_pred_ridge); |
| 138 | + save_csv("predictions_kernel_ridge.csv", X, y, y_pred_kernel); |
| 139 | + |
| 140 | + return 0; |
| 141 | +} |
| 142 | + |
| 143 | + |
| 144 | +// Lasso Regression (Coordinate Descent) |
| 145 | +class LassoRegression { |
| 146 | +public: |
| 147 | + VectorXd weights; |
| 148 | + double alpha; |
| 149 | + int max_iter; |
| 150 | + double tol; |
| 151 | + |
| 152 | + LassoRegression(double alpha = 0.1, int max_iter = 1000, double tol = 1e-4) |
| 153 | + : alpha(alpha), max_iter(max_iter), tol(tol) {} |
| 154 | + |
| 155 | + void fit(const MatrixXd& X, const VectorXd& y) { |
| 156 | + MatrixXd X_bias(X.rows(), X.cols() + 1); |
| 157 | + X_bias << MatrixXd::Ones(X.rows(), 1), X; |
| 158 | + int n_samples = X_bias.rows(); |
| 159 | + int n_features = X_bias.cols(); |
| 160 | + weights = VectorXd::Zero(n_features); |
| 161 | + |
| 162 | + for (int iter = 0; iter < max_iter; ++iter) { |
| 163 | + VectorXd weights_old = weights; |
| 164 | + for (int j = 0; j < n_features; ++j) { |
| 165 | + double tmp = 0.0; |
| 166 | + for (int i = 0; i < n_samples; ++i) { |
| 167 | + double dot = 0.0; |
| 168 | + for (int k = 0; k < n_features; ++k) { |
| 169 | + if (k != j) |
| 170 | + dot += X_bias(i, k) * weights(k); |
| 171 | + } |
| 172 | + tmp += X_bias(i, j) * (y(i) - dot); |
| 173 | + } |
| 174 | + double rho = tmp; |
| 175 | + double norm_sq = X_bias.col(j).squaredNorm(); |
| 176 | + |
| 177 | + if (j == 0) { |
| 178 | + weights(j) = rho / norm_sq; |
| 179 | + } else { |
| 180 | + if (rho < -alpha / 2) |
| 181 | + weights(j) = (rho + alpha / 2) / norm_sq; |
| 182 | + else if (rho > alpha / 2) |
| 183 | + weights(j) = (rho - alpha / 2) / norm_sq; |
| 184 | + else |
| 185 | + weights(j) = 0.0; |
| 186 | + } |
| 187 | + } |
| 188 | + if ((weights - weights_old).lpNorm<1>() < tol) |
| 189 | + break; |
| 190 | + } |
| 191 | + } |
| 192 | + |
| 193 | + VectorXd predict(const MatrixXd& X) const { |
| 194 | + MatrixXd X_bias(X.rows(), X.cols() + 1); |
| 195 | + X_bias << MatrixXd::Ones(X.rows(), 1), X; |
| 196 | + return X_bias * weights; |
| 197 | + } |
| 198 | +}; |
| 199 | + |
| 200 | + LassoRegression lasso(0.1); |
| 201 | + lasso.fit(X, y); |
| 202 | + VectorXd y_pred_lasso = lasso.predict(X); |
| 203 | + cout << "Lasso -> MSE: " << mean_squared_error(y, y_pred_lasso) << ", R2: " << r2_score(y, y_pred_lasso) << endl; |
| 204 | + save_csv("predictions_lasso.csv", X, y, y_pred_lasso); |
0 commit comments