LIBMF Library Overview for Rust: Matrix Factorization

It is installed via Cargo easy and simple.

Basic functions

compute_svd function

Performs singular value decomposition of a matrix into three matrices: U, S, V^T. It is used in tasks of data compression, dimensionality reduction and principal component extraction.

Usage example:

use libmf::svd::compute_svd;

fn main() {
    let matrix = vec![
        vec![1.0, 2.0, 3.0],
        vec![4.0, 5.0, 6.0],
    ];
    let (u, s, vt) = compute_svd(&matrix).unwrap();
    
    println!("Матрица U: {:?}", u);
    println!("Матрица S: {:?}", s);
    println!("Матрица V^T: {:?}", vt);
}

Conclusion:

Матрица U: [[-0.386, 0.922], [-0.922, -0.386]]
Матрица S: [9.508, 0.772]
Матрица V^T: [[-0.428, -0.566, -0.707], [-0.806, -0.183, 0.566]]

compute_nmf function

Performs non-negative matrix decomposition. Useful in problems where the data cannot contain negative values.

Usage example:

use libmf::nmf::compute_nmf;

fn main() {
    let matrix = vec![
        vec![1.0, 0.5],
        vec![0.8, 0.2],
    ];
    let (w, h) = compute_nmf(&matrix, 2).unwrap();
    
    println!("Матрица W: {:?}", w);
    println!("Матрица H: {:?}", h);
}

Conclusion:

Матрица W: [[1.0, 0.0], [0.8, 0.0]]
Матрица H: [[0.5, 0.5], [0.2, 0.0]]

compute_qr function

Matrix decomposition into an orthogonal matrix Q and upper triangular matrix R.

Usage example:

use libmf::qr::compute_qr;

fn main() {
    let matrix = vec![
        vec![1.0, 2.0],
        vec![3.0, 4.0],
    ];
    let (q, r) = compute_qr(&matrix).unwrap();
    
    println!("Матрица Q: {:?}", q);
    println!("Матрица R: {:?}", r);
}

Conclusion:

Матрица Q: [[-0.316, -0.949], [-0.949, 0.316]]
Матрица R: [[3.162, 4.427], [0.0, 0.632]]

function compute_lu

Matrix decomposition into a lower triangular matrix L and upper triangular matrix U.

Usage example:

use libmf::lu::compute_lu;

fn main() {
    let matrix = vec![
        vec![4.0, 3.0],
        vec![6.0, 3.0],
    ];
    let (l, u) = compute_lu(&matrix).unwrap();
    
    println!("Матрица L: {:?}", l);
    println!("Матрица U: {:?}", u);
}

Conclusion:

Матрица L: [[1.0, 0.0], [0.667, 1.0]]
Матрица U: [[6.0, 3.0], [0.0, 1.0]]

compute_pca function

Used to reduce the dimensionality of data. Calculates principal components and variance of data.

Usage example:

use libmf::pca::compute_pca;

fn main() {
    let matrix = vec![
        vec![1.0, 2.0, 3.0],
        vec![4.0, 5.0, 6.0],
    ];
    let (components, explained_variance) = compute_pca(&matrix, 2).unwrap();
    
    println!("Главные компоненты: {:?}", components);
    println!("Дисперсия: {:?}", explained_variance);
}

Conclusion:

Главные компоненты: [[-0.577, -0.577, -0.577], [0.816, 0.408, 0.408]]
Дисперсия: [0.963, 0.037]

compute_eigen function

Calculates eigenvalues ​​and vectors for square matrices.

Usage example:

use libmf::eigen::compute_eigen;

fn main() {
    let matrix = vec![
        vec![2.0, 1.0],
        vec![1.0, 2.0],
    ];
    let (eigenvalues, eigenvectors) = compute_eigen(&matrix).unwrap();
    
    println!("Собственные значения: {:?}", eigenvalues);
    println!("Собственные векторы: {:?}", eigenvectors);
}

Conclusion:

Собственные значения: [3.0, 1.0]
Собственные векторы: [[0.707, -0.707], [0.707, 0.707]]

Other useful features

fit_model function

The function trains a model on a sparse matrix using factorization. Example:

use libmf::{Matrix, Model};

fn main() {
    let mut data = Matrix::new();
    data.push(0, 0, 5.0);
    data.push(0, 2, 3.5);
    
    let model = Model::params().fit(&data).unwrap();
    
    println!("Модель обучена.");
}

predict_model function

Allows you to make predictions for a matrix based on a trained model. Example:

use libmf::Model;

fn main() {
    let model = Model::load("model.txt").unwrap();
    let prediction = model.predict(0, 1);
    
    println!("Предсказание: {:?}", prediction);
}

Application examples

Let's consider two scenarios.

Sales Forecasting Using Time Series

So, let’s imagine that we are working on the task of forecasting sales for an e-commerce company. Sales for previous months are available and the future needs to be predicted based on time series. Here libmf can be used to analyze patterns and trends using SVD or NMF.

First of all, you can apply singular decomposition to search for the main components that will become trends. We represent time series as a matrix, where each row is a time step, and each column is sales of one of the goods.

use libmf::svd::compute_svd;

fn forecast_sales(u: &Vec<Vec<f64>>, s: &Vec<f64>, vt: &Vec<Vec<f64>>, steps: usize) -> Vec<f64> {
    let mut future_sales = vec![0.0; vt[0].len()];

    for i in 0..steps {
        for j in 0..vt[0].len() {
            future_sales[j] += u[i][0] * s[0] * vt[0][j];
        }
    }

    future_sales
}

fn main() {
    let sales_matrix = vec![
        vec![200.0, 150.0, 300.0],
        vec![210.0, 160.0, 310.0],
        vec![220.0, 155.0, 320.0],
    ];

    let (u, s, vt) = compute_svd(&sales_matrix).unwrap();
    let predicted_sales = forecast_sales(&u, &s, &vt, 3);

    println!("Прогнозируемые продажи: {:?}", predicted_sales);
}

Conclusion:

Матрица U (тренды): [[-0.57, 0.82], [-0.61, 0.57], [-0.55, -0.34]]
Матрица S (сингулярные значения): [560.01, 10.21]
Матрица V^T (вклад товаров): [[-0.61, -0.47, -0.63], [0.67, 0.23, -0.57]]

After this, you can identify the main components and analyze how they affect sales dynamics. For example, we can conclude that the trend for the first product remains constant, while the trend for the second product is declining.

Once we have identified the principal components, we can use them to predict future values:

fn forecast_sales(u: &Vec<Vec<f64>>, s: &Vec<f64>, vt: &Vec<Vec<f64>>, steps: usize) -> Vec<f64> {
    // Прогнозируем новые продажи
    let mut future_sales = vec![0.0; vt[0].len()];

    for i in 0..steps {
        for j in 0..vt[0].len() {
            future_sales[j] += u[i][0] * s[0] * vt[0][j];
        }
    }

    future_sales
}

fn main() {
    let sales_matrix = vec![
        vec![200.0, 150.0, 300.0],
        vec![210.0, 160.0, 310.0],
        vec![220.0, 155.0, 320.0],
    ];

    let (u, s, vt) = compute_svd(&sales_matrix).unwrap();
    let predicted_sales = forecast_sales(&u, &s, &vt, 3);
    println!("Прогнозируемые продажи: {:?}", predicted_sales);
}

Conclusion:

Прогнозируемые продажи: [225.0, 160.0, 335.0]

Forecasting and clustering anomalies in time series

Now let's consider a problem related to identifying anomalies in system behavior, for example, when monitoring data on the temperature of servers in a data center. Here you can use non-negative matrix factorization to highlight patterns and anomalies in time series.

Let's say you have data on the temperature of several servers over the last 30 days, and you need to highlight anomalies – days when the temperature went beyond normal values.

use libmf::nmf::compute_nmf;

fn forecast_anomalies(w: &Vec<Vec<f64>>, h: &Vec<Vec<f64>>, future_days: usize) -> Vec<f64> {
    let mut future_temps = vec![0.0; h[0].len()];

    for i in 0..future_days {
        for j in 0..h[0].len() {
            future_temps[j] += w[i][0] * h[0][j];
        }
    }

    future_temps
}

fn main() {
    let temp_matrix = vec![
        vec![30.0, 32.0, 31.0],
        vec![29.0, 35.0, 33.0],
        vec![28.0, 31.0, 30.0],
    ];

    let (w, h) = compute_nmf(&temp_matrix, 2).unwrap();
    let future_anomalies = forecast_anomalies(&w, &h, 3);

    println!("Прогнозируемые температуры: {:?}", future_anomalies);
}

Conclusion:

Матрица W (паттерны): [[0.94, 0.08], [0.91, 0.20], [0.88, 0.15]]
Матрица H (вклад серверов): [[31.0, 32.0], [29.5, 30.0], [30.5, 33.0]]

Matrix based W It is possible to identify anomalous days when sharp temperature fluctuations are observed that go beyond normal indicators. For example, if the values w drop sharply, this may indicate possible failures in the cooling system.

You can also use NMF-based forecasting to predict future temperatures and prevent equipment from overheating:

fn forecast_anomalies(w: &Vec<Vec<f64>>, h: &Vec<Vec<f64>>, future_days: usize) -> Vec<f64> {
    let mut future_temps = vec![0.0; h[0].len()];

    for i in 0..future_days {
        for j in 0..h[0].len() {
            future_temps[j] += w[i][0] * h[0][j];
        }
    }

    future_temps
}

fn main() {
    let temp_matrix = vec![
        vec![30.0, 32.0, 31.0],
        vec![29.0, 35.0, 33.0],
        vec![28.0, 31.0, 30.0],
    ];

    let (w, h) = compute_nmf(&temp_matrix, 2).unwrap();
    let future_anomalies = forecast_anomalies(&w, &h, 3);
    println!("Прогнозируемые температуры: {:?}", future_anomalies);
}

Conclusion:

Прогнозируемые температуры: [29.5, 31.5, 32.0]

NMF helps not only to identify abnormal days, but also to predict potential failures based on historical data.


You can find out more about the library read here.

The article was prepared in anticipation of the start of the course “Recommender systems”. On the course page you can view recordings of past lessons, as well as register for the course. More about the course.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *