7 unstable releases

new 0.4.0 Apr 18, 2026
0.3.0 Jul 11, 2024
0.2.2 Sep 26, 2022
0.2.1 Nov 16, 2021
0.1.1 Jul 27, 2021

#312 in Math

BSD-3-Clause

200KB
5K SLoC

C++ 4.5K SLoC // 0.1% comments Rust 702 SLoC // 0.0% comments

LIBMF Rust

LIBMF - large-scale sparse matrix factorization - for Rust

Check out Disco for higher-level collaborative filtering

Build Status

Installation

Add this line to your application’s Cargo.toml under [dependencies]:

libmf = "0.4"

Getting Started

Prep your data in the format row_index, column_index, value

let mut data = libmf::Matrix::new();
data.push(0, 0, 5.0);
data.push(0, 2, 3.5);
data.push(1, 1, 4.0);

Fit a model

let model = libmf::Model::params().fit(&data)?;

Make predictions

let prediction = model.predict(row_index, column_index);

Get the latent factors (these approximate the training matrix)

let p = model.p(row_index);
let q = model.q(column_index);
// or
for p in model.p_iter() { ... }
for q in model.q_iter() { ... }

Get the bias (average of all elements in the training matrix)

let bias = model.bias();

Save the model to a file

model.save("model.txt")?;

Load a model from a file

let model = libmf::Model::load("model.txt")?;

Pass a validation set

let model = libmf::Model::params().fit_eval(&train_set, &eval_set)?;

Cross-Validation

Perform cross-validation

let avg_error = libmf::Model::params().cv(&data, 5)?;

Parameters

Set parameters - default values below

let model = libmf::Model::params()
    .loss(libmf::Loss::RealL2)     // loss function
    .factors(8)                    // number of latent factors
    .threads(12)                   // number of threads
    .bins(25)                      // number of bins
    .iterations(20)                // number of iterations
    .lambda_p1(0.0)                // L1-regularization parameter for P
    .lambda_p2(0.1)                // L2-regularization parameter for P
    .lambda_q1(0.0)                // L1-regularization parameter for Q
    .lambda_q2(0.1)                // L2-regularization parameter for Q
    .learning_rate(0.1)            // learning rate
    .alpha(1.0)                    // importance of negative entries
    .c(0.0001)                     // desired value of negative entries
    .nmf(false)                    // perform non-negative MF (NMF)
    .quiet(false)                  // no outputs to stdout
    .fit(&data)?;

Loss Functions

For real-valued matrix factorization

  • Loss::RealL2 - squared error (L2-norm)
  • Loss::RealL1 - absolute error (L1-norm)
  • Loss::RealKL - generalized KL-divergence

For binary matrix factorization

  • Loss::BinaryLog - logarithmic error
  • Loss::BinaryL2 - squared hinge loss
  • Loss::BinaryL1 - hinge loss

For one-class matrix factorization

  • Loss::OneClassRow - row-oriented pair-wise logarithmic loss
  • Loss::OneClassCol - column-oriented pair-wise logarithmic loss
  • Loss::OneClassL2 - squared error (L2-norm)

Metrics

Calculate RMSE (for real-valued MF)

let rmse = model.rmse(&data)?;

Calculate MAE (for real-valued MF)

let mae = model.mae(&data)?;

Calculate generalized KL-divergence (for non-negative real-valued MF)

let gkl = model.gkl(&data)?;

Calculate logarithmic loss (for binary MF)

let logloss = model.logloss(&data)?;

Calculate accuracy (for binary MF)

let accuracy = model.accuracy(&data)?;

Calculate MPR (for one-class MF)

let mpr = model.mpr(&data, transpose)?;

Calculate AUC (for one-class MF)

let auc = model.auc(&data, transpose)?;

Example

Download the MovieLens 100K dataset and use:

use std::fs::File;
use std::io::{BufRead, BufReader};

fn main() {
    let mut train_set = libmf::Matrix::with_capacity(80000);
    let mut valid_set = libmf::Matrix::with_capacity(20000);

    let file = File::open("path/to/ml-100k/u.data").unwrap();
    let rdr = BufReader::new(file);
    for (i, line) in rdr.lines().enumerate() {
        let line = line.unwrap();
        let mut row = line.split('\t');

        let user_id = row.next().unwrap().parse().unwrap();
        let item_id = row.next().unwrap().parse().unwrap();
        let rating = row.next().unwrap().parse().unwrap();

        let matrix = if i < 80000 { &mut train_set } else { &mut valid_set };
        matrix.push(user_id, item_id, rating);
    }

    let model = libmf::Model::params()
        .factors(20)
        .fit_eval(&train_set, &valid_set)
        .unwrap();
    println!("RMSE: {:?}", model.rmse(&valid_set));
}

Reference

Specify the initial capacity for a matrix

let mut data = libmf::Matrix::with_capacity(3);

Resources

History

View the changelog

Contributing

Everyone is encouraged to help improve this project. Here are a few ways you can help:

To get started with development:

git clone --recursive https://github.com/ankane/libmf-rust.git
cd libmf-rust
cargo test

Dependencies

~235KB