Как стать автором
Обновить
1268.64
OTUS
Цифровые навыки от ведущих экспертов

Обзор библиотеки LIBMF для Rust: факторизация матриц

Уровень сложностиПростой
Время на прочтение6 мин
Количество просмотров1.2K

Привет, Хабр!

Сегодня поговорим о библиотеке libmf — одном из лучших инструментов для факторизации матриц на Rust. libmf используется для задач машинного обучения: построение рекомендаций, сжатие данных и уменьшение размерности.

Устанавливается она через Cargo легко и просто.

Основные функции

Функция compute_svd

Выполняет сингулярное разложение матрицы на три матрицы: U, S, V^T. Используется в задачах сжатия данных, уменьшения размерности и выделения главных компонент.

Пример использования:

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);
}

Вывод:

Матрица 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

Выполняет неотрицательное разложение матрицы. Полезно в задачах, где данные не могут содержать отрицательных значений.

Пример использования:

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);
}

Вывод:

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

Функция compute_qr

Разложение матрицы на ортогональную матрицу Q и верхнетреугольную матрицу R.

Пример использования:

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);
}

Вывод:

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

Функция compute_lu

Разложение матрицы на нижнетреугольную матрицу L и верхнетреугольную матрицу U.

Пример использования:

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);
}

Вывод:

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

Функция compute_pca

Используется для уменьшения размерности данных. Вычисляет главные компоненты и дисперсию данных.

Пример использования:

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);
}

Вывод:

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

Функция compute_eigen

Рассчитывает собственные значения и векторы для квадратных матриц.

Пример использования:

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);
}

Вывод:

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

Прочие полезные функции

Функция fit_model

Функция обучает модель на разреженной матрице, используя факторизацию. Пример:

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

Позволяет делать предсказания для матрицы на основе обученной модели. Пример:

use libmf::Model;

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

Примеры применения

Рассмотрим два сценария.

Прогнозирование продаж с использованием временных рядов

Итак, представим себе, что идет работа над задачей прогнозирования продаж для e-commerce компании. Продажи за предыдущие месяцы доступны, и нужно спрогнозировать будущее на основе временных рядов. Здесь libmf может быть использована для анализа паттернов и трендов с помощью SVD или NMF.

Прежде всего, можно применить сингулярное разложение для поиска главных компонент, которые и будут трендами. Представляем временные ряды как матрицу, где каждая строка — это временной шаг, а каждый столбец — это продажи одного из товаров.

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);
}

Вывод:

Матрица 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]]

После этого можно выделить главные компоненты и проанализировать, как они влияют на динамику продаж. Например, можно сделать вывод, что тренд на первый товар остаётся постоянным, тогда как на второй товар наблюдается спад.

После того как мы выявили главные компоненты, можно использовать их для предсказания будущих значений:

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);
}

Вывод:

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

Прогнозирование и кластеризация аномалий во временных рядах

Теперь рассмотрим задачу, связанную с выявлением аномалий в поведении системы, например, при мониторинге данных о температуре серверов в дата-центре. Здесь можно использовать неотрицательную факторизацию матриц для выделения паттернов и аномалий в временных рядах.

Допустим, есть данные о температуре нескольких серверов за последние 30 дней, и нужно выделить аномалии — дни, когда температура выходила за рамки обычных значений.

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);
}

Вывод:

Матрица 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]]

На основе матрицы W можно выделить аномальные дни, когда наблюдаются резкие скачки температур, выходящие за рамки обычных показателей. Например, если значения w резко падают, это может сигнализировать о возможных сбоях в системе охлаждения.

Также можно использовать прогнозирование на основе 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);
}

Вывод:

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

NMF помогает не только выявлять аномальные дни, но и предсказать потенциальные сбои на основе исторических данных.


Подробнее с библиотекой можно ознакомиться здесь.

Статья подготовлена в преддверии старта курса «Рекомендательные системы». На странице курса вы сможете посмотреть записи прошедших уроков, а также зарегистрироваться на курс. Подробнее о курсе.

Теги:
Хабы:
Всего голосов 16: ↑12 и ↓4+13
Комментарии0

Публикации

Информация

Сайт
otus.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
OTUS