Crates.io | discorec |
lib.rs | discorec |
version | 0.2.1 |
source | src |
created_at | 2021-12-01 08:56:08.039893 |
updated_at | 2024-06-03 02:35:29.363277 |
description | Recommendations for Rust using collaborative filtering |
homepage | |
repository | https://github.com/ankane/disco-rust |
max_upload_size | |
id | 490163 |
size | 35,758 |
🔥 Recommendations for Rust using collaborative filtering
🎉 Zero dependencies
Add this line to your application’s Cargo.toml
under [dependencies]
:
discorec = "0.2"
Prep your data in the format user_id, item_id, value
use discorec::{Dataset, Recommender};
let mut data = Dataset::new();
data.push("user_a", "item_a", 5.0);
data.push("user_a", "item_b", 3.5);
data.push("user_b", "item_a", 4.0);
IDs can be integers, strings, or any other hashable data type
data.push(1, "item_a".to_string(), 5.0);
If users rate items directly, this is known as explicit feedback. Fit the recommender with:
let recommender = Recommender::fit_explicit(&data);
If users don’t rate items directly (for instance, they’re purchasing items or reading posts), this is known as implicit feedback. Use 1.0
or a value like number of purchases or page views for the dataset, and fit the recommender with:
let recommender = Recommender::fit_implicit(&data);
Get user-based recommendations - “users like you also liked”
recommender.user_recs(&user_id, 5);
Get item-based recommendations - “users who liked this item also liked”
recommender.item_recs(&item_id, 5);
Get predicted ratings for a specific user and item
recommender.predict(&user_id, &item_id);
Get similar users
recommender.similar_users(&user_id, 5);
Download the MovieLens 100K dataset.
Add these lines to your application’s Cargo.toml
under [dependencies]
:
csv = "1"
serde = { version = "1", features = ["derive"] }
And use:
use csv::ReaderBuilder;
use discorec::{Dataset, RecommenderBuilder};
use serde::Deserialize;
use std::fs::File;
#[derive(Debug, Deserialize)]
struct Row {
user_id: i32,
item_id: i32,
rating: f32,
}
fn main() {
let mut train_set = Dataset::new();
let mut valid_set = Dataset::new();
let file = File::open("u.data").unwrap();
let mut rdr = ReaderBuilder::new()
.has_headers(false)
.delimiter(b'\t')
.from_reader(file);
for (i, record) in rdr.records().enumerate() {
let row: Row = record.unwrap().deserialize(None).unwrap();
let dataset = if i < 80000 { &mut train_set } else { &mut valid_set };
dataset.push(row.user_id, row.item_id, row.rating);
}
let recommender = RecommenderBuilder::new()
.factors(20)
.fit_explicit(&train_set);
println!("RMSE: {:?}", recommender.rmse(&valid_set));
}
Save recommendations to your database.
Alternatively, you can store only the factors and use a library like pgvector-rust. See an example.
Disco uses high-performance matrix factorization.
Specify the number of factors and iterations
RecommenderBuilder::new()
.factors(8)
.iterations(20)
.fit_explicit(&train_set);
Pass a callback to show progress
RecommenderBuilder::new()
.callback(|info| println!("{:?}", info))
.fit_explicit(&train_set);
Note: train_loss
and valid_loss
are not available for implicit feedback
Pass a validation set with explicit feedback
RecommenderBuilder::new()
.callback(|info| println!("{:?}", info))
.fit_eval_explicit(&train_set, &valid_set);
The loss function is RMSE
Collaborative filtering suffers from the cold start problem. It’s unable to make good recommendations without data on a user or item, which is problematic for new users and items.
recommender.user_recs(&new_user_id, 5); // returns empty array
There are a number of ways to deal with this, but here are some common ones:
Get ids
recommender.user_ids();
recommender.item_ids();
Get the global mean
recommender.global_mean();
Get factors
recommender.user_factors(&user_id);
recommender.item_factors(&item_id);
View the changelog
Everyone is encouraged to help improve this project. Here are a few ways you can help:
To get started with development:
git clone https://github.com/ankane/disco-rust.git
cd disco-rust
cargo test