Beispiel #1
0
void test_l2_penalty(){
    int n_features = 2;
    int k = 1;
    ffm_coef *coef =  alloc_fm_coef(n_features, k, false);
    ffm_vector_set(coef->w, 0, 1);
    ffm_vector_set(coef->w, 1, 2);
    ffm_matrix_set(coef->V, 0, 0, 3); ffm_matrix_set(coef->V, 0, 1, 4);

    coef->lambda_w = 0.5;
    double lambda_V_all = 0.5;
    ffm_vector_set_all(coef->lambda_V, lambda_V_all);

    double true_loss =  coef->lambda_w * 5 + lambda_V_all * 25;
    double loss = l2_penalty(coef);
    g_assert_cmpfloat(true_loss, == , loss);
    free_ffm_coef(coef);
}
void test_update_second_order_error(TestFixture_T *pFix, gconstpointer pg) {
  ffm_vector *a_theta_v = ffm_vector_calloc(5);
  ffm_vector_set(a_theta_v, 2, 1);
  ffm_vector_set(a_theta_v, 3, 2);

  ffm_vector *error = ffm_vector_calloc(5);
  ffm_vector_set_all(error, 1.5);

  double delta = 0.5;
  int column = 1;
  update_second_order_error(column, pFix->X, a_theta_v, delta, error);

  g_assert_cmpfloat(1.5, ==, ffm_vector_get(error, 0));
  g_assert_cmpfloat(1.5, ==, ffm_vector_get(error, 1));
  g_assert_cmpfloat(1.5, ==, ffm_vector_get(error, 2));
  g_assert_cmpfloat(2.5, ==, ffm_vector_get(error, 3));
  g_assert_cmpfloat(1.5, ==, ffm_vector_get(error, 4));

  ffm_vector_free_all(error, a_theta_v);
}
void test_sparse_map_gibbs_first_order_interactions(void) {
  int n_features = 10;
  int n_samples = 100;
  int k = 0;

  TestFixture_T *data = makeTestFixture(124, n_samples, n_features, k);

  ffm_vector *y_pred = ffm_vector_calloc(n_samples);

  ffm_coef *coef = alloc_fm_coef(n_features, k, false);
  ffm_param param = {.n_iter = 200, .init_sigma = 0.1, .SOLVER = SOLVER_MCMC};
  sparse_fit(coef, data->X, data->X, data->y, y_pred, param);

  g_assert_cmpfloat(ffm_r2_score(data->y, y_pred), >, .99);
  ffm_vector_free(y_pred);
  free_ffm_coef(coef);
  TestFixtureDestructor(data, NULL);
}

void test_train_test_data(void) {
  // test if training and test data a propertly handeled
  // no check of prediction quality
  int n_features = 10;
  int n_samples_train = 100;
  int n_samples_test = 30;
  int k = 3;

  TestFixture_T *data_train =
      makeTestFixture(124, n_samples_train, n_features, k);
  TestFixture_T *data_test =
      makeTestFixture(124, n_samples_test, n_features, k);

  ffm_vector *y_pred = ffm_vector_calloc(n_samples_test);
  // gibts
  ffm_coef *coef = alloc_fm_coef(n_features, k, false);
  ffm_param param = {.n_iter = 200, .init_sigma = 0.1, .SOLVER = SOLVER_MCMC};
  sparse_fit(coef, data_train->X, data_test->X, data_train->y, y_pred, param);
  free_ffm_coef(coef);
  // als
  coef = alloc_fm_coef(n_features, k, false);
  ffm_param param_als = {
      .n_iter = 200, .init_sigma = 0.1, .SOLVER = SOLVER_ALS};
  sparse_fit(coef, data_train->X, data_test->X, data_train->y, y_pred,
             param_als);
  sparse_predict(coef, data_test->X, y_pred);

  free_ffm_coef(coef);
  TestFixtureDestructor(data_train, NULL);
  TestFixtureDestructor(data_test, NULL);
}

void test_sparse_map_gibbs_second_interactions(void) {
  int n_features = 10;
  int n_samples = 1000;
  int k = 2;
  double init_sigma = 0.1;

  TestFixture_T *data = makeTestFixture(124, n_samples, n_features, k);

  ffm_vector *y_pred = ffm_vector_calloc(n_samples);

  ffm_coef *coef = alloc_fm_coef(n_features, k, false);
  ffm_param param = {
      .n_iter = 5, .init_sigma = init_sigma, .SOLVER = SOLVER_MCMC};
  sparse_fit(coef, data->X, data->X, data->y, y_pred, param);
  double score_5_samples = ffm_r2_score(data->y, y_pred);

  free_ffm_coef(coef);
  ffm_vector_set_all(y_pred, 0);
  coef = alloc_fm_coef(n_features, 0, false);
  ffm_param param_50 = {
      .n_iter = 50, .init_sigma = init_sigma, .SOLVER = SOLVER_MCMC};
  sparse_fit(coef, data->X, data->X, data->y, y_pred, param_50);
  double score_50_samples_first_order = ffm_r2_score(data->y, y_pred);

  free_ffm_coef(coef);
  ffm_vector_set_all(y_pred, 0);
  coef = alloc_fm_coef(n_features, k + 5, false);
  sparse_fit(coef, data->X, data->X, data->y, y_pred, param_50);
  double score_50_samples = ffm_r2_score(data->y, y_pred);

  g_assert_cmpfloat(score_50_samples, >, score_50_samples_first_order);
  g_assert_cmpfloat(score_50_samples, >, score_5_samples);
  g_assert_cmpfloat(score_50_samples, >, .72);

  ffm_vector_free(y_pred);
  free_ffm_coef(coef);
  TestFixtureDestructor(data, NULL);
}

void test_sparse_als_classification(void) {
  int n_features = 10;
  int n_samples = 100;
  int k = 2;
  double init_sigma = 0.01;

  TestFixture_T *data = makeTestFixture(124, n_samples, n_features, k);
  // map to classification problem
  ffm_vector_make_labels(data->y);

  ffm_vector *y_pred = ffm_vector_calloc(n_samples);

  ffm_coef *coef = alloc_fm_coef(n_features, k, false);
  ffm_param param = {.n_iter = 50,
                     .init_sigma = init_sigma,
                     .SOLVER = SOLVER_ALS,
                     .TASK = TASK_CLASSIFICATION};
  param.init_lambda_w = 5.5;
  param.init_lambda_V = 5.5;
  sparse_fit(coef, data->X, NULL, data->y, NULL, param);
  sparse_predict(coef, data->X, y_pred);
  ffm_vector_normal_cdf(y_pred);

  g_assert_cmpfloat(ffm_vector_accuracy(data->y, y_pred), >=, .8);

  ffm_vector_free(y_pred);
  free_ffm_coef(coef);
  TestFixtureDestructor(data, NULL);
}

void test_sparse_als_classification_path(void) {
  int n_features = 10;
  int n_samples = 200;
  int k = 4;
  double init_sigma = 0.1;

  TestFixture_T *data = makeTestFixture(124, n_samples, n_features, k);
  // map to classification problem
  ffm_vector_make_labels(data->y);

  ffm_vector *y_pred = ffm_vector_calloc(n_samples);

  ffm_coef *coef = alloc_fm_coef(n_features, k, false);
  ffm_param param = {.n_iter = 0,
                     .init_sigma = init_sigma,
                     .SOLVER = SOLVER_ALS,
                     .TASK = TASK_CLASSIFICATION};
  param.init_lambda_w = 5.5;
  param.init_lambda_V = 5.5;

  double acc = 0;
  // objective does not decline strigtly monotonic because of latend target
  // but should still decrease on average (at least till convergence)
  for (int i = 1; i < 9; i = i * 2) {
    param.n_iter = i;
    sparse_fit(coef, data->X, NULL, data->y, NULL, param);
    sparse_predict(coef, data->X, y_pred);
    ffm_vector_normal_cdf(y_pred);
    double tmp_acc = ffm_vector_accuracy(data->y, y_pred);
    // training error should (almost) always decrease
    // printf("iter %d, last acc %f\n", i, acc);
    g_assert_cmpfloat(tmp_acc, >=, acc);
    acc = tmp_acc;
  }

  ffm_vector_free(y_pred);
  free_ffm_coef(coef);
  TestFixtureDestructor(data, NULL);
}

void test_sparse_mcmc_classification(void) {
  int n_features = 10;
  int n_samples = 100;
  int k = 2;
  double init_sigma = 0.1;

  TestFixture_T *data = makeTestFixture(124, n_samples, n_features, k);
  // map to classification problem
  ffm_vector_make_labels(data->y);

  ffm_vector *y_pred = ffm_vector_calloc(n_samples);

  ffm_coef *coef = alloc_fm_coef(n_features, k, false);
  ffm_param param = {.n_iter = 50,
                     .init_sigma = init_sigma,
                     .SOLVER = SOLVER_MCMC,
                     .TASK = TASK_CLASSIFICATION};
  param.init_lambda_w = 5.5;
  param.init_lambda_V = 5.5;
  sparse_fit(coef, data->X, data->X, data->y, y_pred, param);
  sparse_predict(coef, data->X, y_pred);

  g_assert_cmpfloat(ffm_vector_accuracy(data->y, y_pred), >=, .84);

  ffm_vector_free(y_pred);
  free_ffm_coef(coef);
  TestFixtureDestructor(data, NULL);
}
void test_numerical_stability(void) {
  int n_features = 10;
  int n_samples = 10000;
  int k = 2;

  TestFixture_T *data = makeTestFixture(15, n_samples, n_features, k);

  ffm_vector *y_pred = ffm_vector_calloc(n_samples);

  ffm_coef *coef = alloc_fm_coef(n_features, k, false);
  ffm_param param = {.n_iter = 7, .init_sigma = 0.01, .SOLVER = SOLVER_ALS};
  param.init_lambda_w = 400;
  param.init_lambda_V = 400;
  sparse_fit(coef, data->X, data->X, data->y, y_pred, param);
  sparse_predict(coef, data->X, y_pred);
  double score_als = ffm_r2_score(data->y, y_pred);
  g_assert_cmpfloat(score_als, >, .98);

  free_ffm_coef(coef);
  ffm_vector_set_all(y_pred, 0);

  coef = alloc_fm_coef(n_features, k, false);
  ffm_param param_mcmc = {
      .n_iter = 50, .init_sigma = 0.01, .SOLVER = SOLVER_MCMC};
  sparse_fit(coef, data->X, data->X, data->y, y_pred, param_mcmc);
  double score_gibbs = ffm_r2_score(data->y, y_pred);
  g_assert_cmpfloat(score_gibbs, >, .99);

  ffm_vector_free(y_pred);
  free_ffm_coef(coef);
  TestFixtureDestructor(data, NULL);
}

void test_map_update_target(void) {
  double pred[] = {0.5, 0.2, -0.2, -0.5, -0.1, 0.8};
  double true_[] = {1, 1, -1, -1, 1, -1};
  double z[] = {0.509160434,  0.6750731798, -0.6750731798,
                -0.509160434, 0.8626174715, -1.3674022692};
  ffm_vector y_pred = {.data = pred, .size = 6};
  ffm_vector y_true = {.data = true_, .size = 6};
  ffm_vector *z_target = ffm_vector_alloc(6);
  map_update_target(&y_pred, z_target, &y_true);
  for (int i = 0; i < 6; i++)
    g_assert_cmpfloat(fabs(z_target->data[i] - z[i]), <=, 1e-9);
  ffm_vector_free(z_target);
}

void test_als_warm_start(TestFixture_T *pFix, gconstpointer pg) {
  int n_features = pFix->X->n;
  int n_samples = pFix->X->m;
  int k = 4;

  ffm_vector *y_10_iter = ffm_vector_calloc(n_samples);
  ffm_vector *y_15_iter = ffm_vector_calloc(n_samples);
  ffm_vector *y_5_plus_5_iter = ffm_vector_calloc(n_samples);

  ffm_param param = {.warm_start = false,
                     .init_sigma = 0.1,
                     .SOLVER = SOLVER_ALS,
                     .TASK = TASK_REGRESSION,
                     .rng_seed = 123};

  param.n_iter = 10;
  ffm_coef *coef = alloc_fm_coef(n_features, k, false);
  sparse_fit(coef, pFix->X, NULL, pFix->y, NULL, param);
  sparse_predict(coef, pFix->X, y_10_iter);

  param.n_iter = 15;
  sparse_fit(coef, pFix->X, NULL, pFix->y, NULL, param);
  sparse_predict(coef, pFix->X, y_15_iter);

  param.n_iter = 5;
  sparse_fit(coef, pFix->X, NULL, pFix->y, NULL, param);
  param.warm_start = true;
  sparse_fit(coef, pFix->X, NULL, pFix->y, NULL, param);
  sparse_predict(coef, pFix->X, y_5_plus_5_iter);

  // check that the results are equal
  double mse = ffm_vector_mean_squared_error(y_10_iter, y_5_plus_5_iter);
  double mse_diff = ffm_vector_mean_squared_error(y_15_iter, y_5_plus_5_iter);

  g_assert_cmpfloat(mse, <=, 1e-8);
  g_assert_cmpfloat(mse, <, mse_diff);

  free_ffm_coef(coef);
  ffm_vector_free_all(y_10_iter, y_5_plus_5_iter);
}

void test_mcmc_warm_start(TestFixture_T *pFix, gconstpointer pg) {
  int n_features = pFix->X->n;
  int n_samples = pFix->X->m;
  int k = 4;

  ffm_vector *y_10_iter = ffm_vector_calloc(n_samples);
  ffm_vector *y_15_iter = ffm_vector_calloc(n_samples);
  ffm_vector *y_5_plus_5_iter = ffm_vector_calloc(n_samples);

  ffm_param param = {.warm_start = false,
                     .init_sigma = 0.1,
                     .SOLVER = SOLVER_MCMC,
                     .TASK = TASK_REGRESSION,
                     .rng_seed = 125};

  param.n_iter = 100;
  // printf("n_iter %d\n", param.n_iter);
  ffm_coef *coef = alloc_fm_coef(n_features, k, false);
  sparse_fit(coef, pFix->X, pFix->X, pFix->y, y_10_iter, param);

  param.n_iter = 150;
  sparse_fit(coef, pFix->X, pFix->X, pFix->y, y_15_iter, param);

  param.n_iter = 50;
  sparse_fit(coef, pFix->X, pFix->X, pFix->y, y_5_plus_5_iter, param);
  param.warm_start = true;
  param.iter_count = param.n_iter;
  param.n_iter += 50;  // add more iterations
  sparse_fit(coef, pFix->X, pFix->X, pFix->y, y_5_plus_5_iter, param);

  // check that the results are equal
  // double mse10 = ffm_vector_mean_squared_error(pFix->y, y_5_plus_5_iter);
  double mse10_55 = ffm_vector_mean_squared_error(y_10_iter, y_5_plus_5_iter);
  double mse15_55 = ffm_vector_mean_squared_error(y_15_iter, y_5_plus_5_iter);

  g_assert_cmpfloat(mse10_55, <, mse15_55);

  free_ffm_coef(coef);
  ffm_vector_free_all(y_10_iter, y_5_plus_5_iter);
}

int main(int argc, char **argv) {
  /*
  feenableexcept(FE_INVALID   |
                 FE_DIVBYZERO |
                 FE_OVERFLOW  |
                 FE_UNDERFLOW);
  */

  g_test_init(&argc, &argv, NULL);

  TestFixture_T Fixture;

  g_test_add("/als/update second-order error", TestFixture_T, &Fixture,
             TestFixtureContructorLong, test_update_second_order_error,
             TestFixtureDestructor);

  g_test_add("/als/eval second-order term", TestFixture_T, &Fixture,
             TestFixtureContructorLong, test_eval_second_order_term,
             TestFixtureDestructor);

  g_test_add("/als/update v_ij", TestFixture_T, &Fixture,
             TestFixtureContructorLong, test_sparse_update_v_ij,
             TestFixtureDestructor);

  g_test_add("/general/predict", TestFixture_T, &Fixture,
             TestFixtureContructorLong, test_sparse_predict,
             TestFixtureDestructor);

  g_test_add("/general/row_predict", TestFixture_T, &Fixture,
             TestFixtureContructorLong, test_row_predict,
             TestFixtureDestructor);

  g_test_add("/general/col_predict", TestFixture_T, &Fixture,
             TestFixtureContructorLong, test_col_predict,
             TestFixtureDestructor);

  g_test_add("/als/zero order only", TestFixture_T, &Fixture,
             TestFixtureContructorSimple, test_sparse_als_zero_order_only,
             TestFixtureDestructor);

  g_test_add("/als/first order only", TestFixture_T, &Fixture,
             TestFixtureContructorSimple, test_sparse_als_first_order_only,
             TestFixtureDestructor);

  g_test_add("/als/second order only", TestFixture_T, &Fixture,
             TestFixtureContructorSimple, test_sparse_als_second_order_only,
             TestFixtureDestructor);

  g_test_add("/als/all interactions", TestFixture_T, &Fixture,
             TestFixtureContructorSimple, test_sparse_als_all_interactions,
             TestFixtureDestructor);

  g_test_add("/als/first order", TestFixture_T, &Fixture,
             TestFixtureContructorLong,
             test_sparse_als_first_order_interactions, TestFixtureDestructor);

  g_test_add("/als/second order", TestFixture_T, &Fixture,
             TestFixtureContructorLong, test_sparse_als_second_interactions,
             TestFixtureDestructor);

  g_test_add("/mcmc/second order", TestFixture_T, &Fixture,
             TestFixtureContructorLong, test_sparse_mcmc_second_interactions,
             TestFixtureDestructor);

  g_test_add("/mcmc/second order classification", TestFixture_T, &Fixture,
             TestFixtureContructorLong,
             test_sparse_mcmc_second_interactions_classification,
             TestFixtureDestructor);

  g_test_add("/general/train test different size", TestFixture_T, &Fixture,
             TestFixtureContructorLong, test_train_test_of_different_size,
             TestFixtureDestructor);

  g_test_add_func("/als/generated data", test_sparse_als_generated_data);

  g_test_add_func("/mcmc/MAP gibbs first order",
                  test_sparse_map_gibbs_first_order_interactions);

  g_test_add_func("/mcmc/hyperparameter sampling", test_hyerparameter_sampling);

  g_test_add_func("/mcmc/MAP gibbs second order",
                  test_sparse_map_gibbs_second_interactions);

  g_test_add_func("/general/numerical stability", test_numerical_stability);

  g_test_add_func("/mcmc/map update target", test_map_update_target);

  g_test_add_func("/als/classification", test_sparse_als_classification);
  g_test_add_func("/als/classification path",
                  test_sparse_als_classification_path);
  g_test_add_func("/mcmc/classification", test_sparse_mcmc_classification);

  g_test_add("/als/warm_start", TestFixture_T, &Fixture,
             TestFixtureContructorSimple, test_als_warm_start,
             TestFixtureDestructor);

  g_test_add("/mcmc/warm_start", TestFixture_T, &Fixture,
             TestFixtureContructorSimple, test_mcmc_warm_start,
             TestFixtureDestructor);

  return g_test_run();
}
Beispiel #4
0
void ffm_predict(double *w_0, double *w, double *V, cs *X, double *y_pred, int k) {
    int n_samples = X->m;
    int n_features = X->n;
    ffm_vector ffm_w = {.size=n_features, .data=w, .owner=0};
    ffm_matrix ffm_V = {.size0=k, .size1=n_features, .data=V, .owner=0};
    ffm_coef coef = {.w_0=*w_0, .w=&ffm_w, .V=&ffm_V};

    ffm_vector ffm_y_pred = {.size=n_samples, .data=y_pred, .owner=0};
    sparse_predict(&coef, X, &ffm_y_pred);
}

void ffm_als_fit(double *w_0, double *w, double *V, cs *X, double *y,
                 ffm_param *param) {
    param->SOLVER = SOLVER_ALS;
    int n_samples = X->m;
    int n_features = X->n;

    ffm_vector ffm_w = {.size=n_features, .data=w, .owner=0};
    ffm_matrix ffm_V = {.size0=param->k, .size1=n_features, .data=V, .owner=0};
    ffm_coef coef = {.w_0=*w_0, .w=&ffm_w, .V=&ffm_V,
                     .lambda_w=param->init_lambda_w
                    };
    if (param->k > 0)
    {
        coef.lambda_V = ffm_vector_alloc(param->k);
        coef.mu_V = ffm_vector_alloc(param->k);
        ffm_vector_set_all(coef.lambda_V, param->init_lambda_V);
    } else
    {
        coef.lambda_V = NULL;
        coef.mu_V = NULL;
    }

    ffm_vector ffm_y = {.size=n_samples, .data=y, .owner=0};
    sparse_fit(&coef, X, NULL, &ffm_y, NULL, *param);

    // copy the last coef values back into the python memory
    *w_0 = coef.w_0;
    ffm_vector_free_all(coef.lambda_V, coef.mu_V);
}


void ffm_mcmc_fit_predict(double *w_0, double *w, double *V,
                          cs *X_train, cs *X_test, double *y_train, double *y_pred,
                          ffm_param *param) {
    param->SOLVER = SOLVER_MCMC;
    int k = param->k;
    double * hyper_param = param->hyper_param;
    int n_test_samples = X_test->m;
    int n_train_samples = X_train->m;
    int n_features = X_train->n;
    ffm_vector ffm_w = {.size=n_features, .data=w, .owner=0};
    ffm_matrix ffm_V = {.size0=param->k, .size1=n_features, .data=V, .owner=0};
    ffm_coef coef = {.w_0=*w_0, .w=&ffm_w, .V=&ffm_V,
                     .lambda_w=param->init_lambda_w, .alpha=1, .mu_w=0
                    };
    if (k > 0)
    {
        coef.lambda_V = ffm_vector_alloc(param->k);
        coef.mu_V = ffm_vector_alloc(param->k);
    }
    else
    {
        coef.lambda_V = NULL;
        coef.mu_V = NULL;
    }

    // set inital values for hyperparameter
    int w_groups = 1;
    assert(param->n_hyper_param == 1 + 2 * k + 2 * w_groups &&
           "hyper_parameter vector has wrong size");
    if (param->warm_start)
    {
        coef.alpha = hyper_param[0];
        coef.lambda_w = hyper_param[1];
        // copy V lambda's over
        for (int i=0; i<k; i++) ffm_vector_set(coef.lambda_V, i,
                                                   hyper_param[i + 1 + w_groups]);
        coef.mu_w = hyper_param[k + 1 + w_groups];
        // copy V mu's over
        for (int i=0; i<k; i++) ffm_vector_set(coef.mu_V, i,
                                                   hyper_param[i + 1 + (2 * w_groups) + k]);
    }

    ffm_vector ffm_y_train = {.size=n_train_samples, .data=y_train, .owner=0};
    ffm_vector ffm_y_pred = {.size=n_test_samples, .data=y_pred, .owner=0};
    sparse_fit(&coef, X_train, X_test, &ffm_y_train, &ffm_y_pred, *param);
    // copy the last coef values back into the python memory
    *w_0 = coef.w_0;

    // copy current hyperparameter back
    hyper_param[0] = coef.alpha;
    hyper_param[1] = coef.lambda_w;
    // copy V lambda's back
    for (int i=0; i<k; i++) hyper_param[i + 1 + w_groups] =
            ffm_vector_get(coef.lambda_V, i);
    hyper_param[k + 1 + w_groups] = coef.mu_w;
    // copy mu's back
    for (int i=0; i<k; i++) hyper_param[i + 1 + (2 * w_groups) + k] =
            ffm_vector_get(coef.mu_V, i);

    if ( k > 0 )
        ffm_vector_free_all(coef.lambda_V, coef.mu_V);
}


void ffm_sgd_bpr_fit(double *w_0, double *w, double *V,
                     cs *X, double *pairs, int n_pairs, ffm_param *param) {

    int n_features = X->m;
    ffm_vector ffm_w = {.size=n_features, .data=w, .owner=0};
    ffm_matrix ffm_V = {.size0=param->k, .size1=n_features, .data=V, .owner=0};
    ffm_coef coef = {.w_0=*w_0, .w=&ffm_w, .V=&ffm_V,
                     .lambda_w=param->init_lambda_w
                    };
    if (param->k > 0)
    {
        coef.lambda_V = ffm_vector_alloc(param->k);
        coef.mu_V = ffm_vector_alloc(param->k);
    }
    else
    {
        coef.lambda_V = NULL;
        coef.mu_V = NULL;
    }

    ffm_matrix ffm_y = {.size0=n_pairs, .size1=2, .data=pairs, .owner=0};
    ffm_fit_sgd_bpr(&coef, X, &ffm_y, *param);

    // copy the last coef values back into the python memory
    *w_0 = coef.w_0;
    if ( param->k > 0 )
        ffm_vector_free_all(coef.lambda_V, coef.mu_V);
}

void ffm_sgd_fit(double *w_0, double *w, double *V,
                 cs *X, double *y, ffm_param *param) {
    int n_samples = X->n;
    int n_features = X->m;

    ffm_vector ffm_w = {.size=n_features, .data=w, .owner=0};
    ffm_matrix ffm_V = {.size0=param->k, .size1=n_features, .data=V, .owner=0};
    ffm_coef coef = {.w_0=*w_0, .w=&ffm_w, .V=&ffm_V,
                     .lambda_w=param->init_lambda_w
                    };
    if (param->k > 0)
    {
        coef.lambda_V = ffm_vector_alloc(param->k);
        coef.mu_V = ffm_vector_alloc(param->k);
    }
    else
    {
        coef.lambda_V = NULL;
        coef.mu_V = NULL;
    }

    ffm_vector ffm_y = {.size=n_samples, .data=y, .owner=0};
    ffm_fit_sgd(&coef, X, &ffm_y, param);

    // copy the last coef values back into the python memory
    *w_0 = coef.w_0;
    if ( param->k > 0 )
        ffm_vector_free_all(coef.lambda_V, coef.mu_V);
}