Implicit 0.7.2

dotnet add package Implicit --version 0.7.2
                    
NuGet\Install-Package Implicit -Version 0.7.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Implicit" Version="0.7.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Implicit" Version="0.7.2" />
                    
Directory.Packages.props
<PackageReference Include="Implicit" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Implicit --version 0.7.2
                    
#r "nuget: Implicit, 0.7.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Implicit@0.7.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Implicit&version=0.7.2
                    
Install as a Cake Addin
#tool nuget:?package=Implicit&version=0.7.2
                    
Install as a Cake Tool

Implicit

CI NuGet NuGet License

Fast .NET collaborative filtering for implicit feedback datasets.

Developer documentation: getting started, architecture, API, and implementation notes.

Highlights

Implicit learns user and item representations from interactions such as views, purchases, or listening history. A trained model can recommend items, find similar users or items, and infer a temporary profile from a new interaction history.

Main features are:

  • Alternating Least Squares (ALS) with conjugate gradient or direct solving
  • Parallel user and item updates during training
  • Positive and negative feedback with configurable confidence scaling
  • Recommendations and user/item similarity queries
  • Feature inference for new users and items against an existing model
  • Optional TF-IDF and BM25 preprocessing
  • Binary persistence for models and fitted weighting transforms
  • Pooled result storage for repeated queries
  • MathNet.Numerics linear algebra with support for its native providers

ALS is the only implemented recommendation algorithm. TF-IDF and BM25 transform interaction values; they are not standalone item-neighbor recommenders. Models are trained in batches, with no incremental update or public training-resumption API. Applications supply interaction data, candidate filtering, and recommendation-quality evaluation.

Installation

Add the package to your application:

dotnet add package Implicit

The library targets .NET Standard 2.0/2.1 and .NET 8/9/10. The .NET Standard 2.0 build also supports .NET Framework consumers; the repository tests it on .NET Framework 4.7.2.

The examples below use modern C# and the API in this checkout. To try unreleased changes, reference src/Implicit/Implicit.csproj from a console application instead of using the published package. Use the Implicit namespace for models and Implicit.Weights for preprocessing.

A Quick Example

This complete console example builds an interaction matrix, trains a model, and recommends books that Alice has not already read. The small dataset demonstrates the API rather than recommendation quality.

using System;
using System.Collections.Generic;
using System.Linq;
using Implicit;

var interactions = new[]
{
    new UserItemValue("alice", "book-a", 3f),
    new UserItemValue("alice", "book-b", 1f),
    new UserItemValue("bob", "book-a", 2f),
    new UserItemValue("bob", "book-c", 4f),
    new UserItemValue("carol", "book-b", 2f),
    new UserItemValue("carol", "book-c", 3f),
};

var matrix = UserItemMatrix.Build(interactions);
var parameters = new AlternatingLeastSquaresParameters(
    factors: 3,
    regularization: 0.1f,
    alpha: 10f,
    iterations: 15,
    random: new Random(42));

var model = AlternatingLeastSquaresRecommender.Fit(matrix, parameters);
var seen = new HashSet<string>(
    interactions.Where(x => x.UserId == "alice").Select(x => x.ItemId));

// Dispose the result to return its list to the pool.
using (var recommendations = model.Recommend("alice"))
{
    foreach (var item in recommendations.Where(x => !seen.Contains(x.Key)).Take(5))
    {
        Console.WriteLine($"{item.Key}: {item.Value:F4}");
    }
}

Here book-c is the only unseen candidate. Result keys are item IDs and values are ranking scores, not probabilities or predicted star ratings. The model does not retain interaction history, so keep that history separately when filtering previously seen items.

The following examples build on interactions, matrix, parameters, and model from this example.

Interaction Data

UserItemMatrix.Build accepts either a sequence of UserItemValue records or a Dictionary<string, Dictionary<string, float>> mapping user IDs to item values. It builds a sparse matrix with users as rows and items as columns. Duplicate records for the same pair are summed; exact zero inputs are skipped. Users and items represented only by zero inputs are omitted.

The sign of an aggregated value expresses preference, while its magnitude controls confidence:

Input value Target preference Confidence used by ALS
Positive v 1 Alpha * v
Negative v 0 abs(Alpha * v)
Missing or zero 0 1

Observed confidence does not include an added baseline of one: a value of 2 with Alpha = 10 produces confidence 20. Negative observations target zero, not minus one. For example, an explicit dislike can be represented by a negative value with a deliberately chosen magnitude.

Choose an event aggregation policy before training, and validate finite values and meaningful IDs in your application. See data and weighting for duplicate cancellation, zero entries, and preprocessing details.

Training Configuration

Pass AlternatingLeastSquaresParameters to Fit to control model size, regularization, and solving:

Argument Default Purpose
factors 64 Number of latent features per user and item.
regularization 0.01f Nonnegative penalty on factor magnitudes.
alpha 1f Positive multiplier for observed confidence values.
iterations 15 Number of complete user/item update passes.
useConjugateGradient true Use CG; set to false for direct solving.
conjugateGradientSteps 3 Maximum CG steps per row update.
calculateLossAtIteration false Calculate loss after each iteration.
parallelOptions New ParallelOptions Control parallelism and cancellation.
random MathNet default random source Initialize factor matrices.

To report progress and limit training concurrency:

var monitoredParameters = new AlternatingLeastSquaresParameters(
    factors: 32,
    regularization: 0.1f,
    alpha: 10f,
    calculateLossAtIteration: true,
    parallelOptions: new System.Threading.Tasks.ParallelOptions
    {
        MaxDegreeOfParallelism = 4,
    },
    random: new Random(42));

monitoredParameters.OnIterationCompleted = context =>
    Console.WriteLine(
        $"Iteration {context.Iteration + 1}: loss={context.Loss}, time={context.Elapsed}");

var monitoredModel = AlternatingLeastSquaresRecommender.Fit(matrix, monitoredParameters);

Iteration is zero-based, and Loss is null when loss calculation is disabled. Calculating loss adds work after every iteration; it is the normalized training objective, not a ranking-quality metric. Supply a cancellation token through ParallelOptions.CancellationToken to abort fitting.

A random seed stabilizes initialization, but numerical providers and parallel execution can affect floating-point results. Treat trained or loaded models as fixed snapshots: publicly exposed maps and factor matrices are mutable, but changing them does not invalidate cached norms or Gram matrices. See ALS implementation for the objective and solver behavior.

Recommendations and Similarity

Query results are sorted by descending score. Lookup-based queries for unknown IDs return an empty result; feature lookups return null.

Method Candidates
Recommend(userId) All model items, scored against the user's factors.
SimilarUsers(userId) All model users, including the queried user.
SimilarItems(itemId) All model items, including the queried item.
GetUserFeatures(userId) A known user's factors, or null.
GetItemFeatures(itemId) A known item's factors, or null.

Filter out the query item when displaying neighbors:

using (var similar = model.SimilarItems("book-a"))
{
    foreach (var item in similar.Where(x => x.Key != "book-a").Take(5))
    {
        Console.WriteLine($"Similar book: {item.Key}, score={item.Value:F4}");
    }
}

Recommendation scores are user/item dot products. Similarity scores divide the dot product by the candidate's norm only. For a fixed nonzero query this preserves cosine-similarity ordering, but scores are scaled by the query norm and need not fall between minus one and one. Equal-score ordering is unspecified. See API behavior for scoring and query contracts.

New Users and Items

ComputeUserFeatures infers a temporary user vector from interactions with known items. It does not insert a user into the model or retrain existing factors. Unknown item IDs and zero values are skipped; if there are no accepted interactions, it returns null.

var history = new Dictionary<string, float>
{
    ["book-a"] = 2f,
    ["book-c"] = 1f,
};
var profile = model.ComputeUserFeatures(history);

if (profile is not null)
{
    using var recommendations = model.Recommend(profile);
    var topItems = recommendations
        .Where(x => !history.ContainsKey(x.Key))
        .Take(5)
        .ToArray();

    foreach (var item in topItems)
    {
        Console.WriteLine($"For the new reader: {item.Key}, score={item.Value:F4}");
    }
}

ComputeItemFeatures performs the symmetric operation using known user IDs. Pass inferred features to SimilarItems, SimilarUsers, or Recommend as appropriate. RecommenderFeatures.SimilarFeatures also scores a supplied collection of candidate features from the same latent space.

Apply the same preprocessing used for training. Feature inference applies the model's Alpha internally, so do not scale input values by alpha again.

Result Lifetime

RecommenderResult implements IReadOnlyList<KeyValuePair<string, float>> and IDisposable. Always dispose results after reading them. Use ToArray() or ToList() inside the using scope when data must outlive the result; returning a deferred query such as result.Take(10) is not sufficient.

For pooled results, accessing the count or indexer, obtaining an enumerator, or using an existing enumerator after result disposal throws ObjectDisposedException. This also applies to copied and boxed enumerators. The shared RecommenderResult.Empty remains readable after disposal. Keep disposal outside active enumeration; lifetime checks do not synchronize concurrent access.

Weighting Interactions

TF-IDF and BM25 can adjust interaction strengths before training. They are optional and are never automatically applied by ALS. The following example aggregates the original records and fits TF-IDF:

var userItems = UserItemValueExtensions.ToUserItems(interactions);
var fittingRows = userItems.Values
    .Select((items, index) => new { Index = index, Items = items })
    .ToDictionary(x => x.Index, x => x.Items);

var weight = Implicit.Weights.TFIDFWeight.Fit(fittingRows);
weight.Normalize(userItems);

var weightedMatrix = UserItemMatrix.Build(userItems);
var weightedModel = AlternatingLeastSquaresRecommender.Fit(weightedMatrix, parameters);

The fitting API uses integer outer keys; matrix construction uses string user IDs. Fit collects statistics, while Normalize changes dictionary values in place. Clone the nested dictionaries if you need to retain their original values. Applying normalization twice repeats the transform.

TF-IDF multiplies values by ln(userCount / itemFrequency). An item present in every fitting row receives zero weight. To use BM25 instead, replace the fitting call with Implicit.Weights.BM25Weight.Fit(fittingRows, k1: 100f, b: 0.8f). BM25 can produce negative weights for frequent items, which ALS interprets as zero-preference feedback. Review that behavior against your event semantics before using it.

Retain the fitted transform for inference and normalize new histories once before calling weightedModel.ComputeUserFeatures. Unknown items are left unchanged by normalization. Both transforms support Save(Stream) and their own static Load(Stream) methods; save them separately from the model. See data and weighting for formulas and edge cases.

Saving and Loading Models

Persist a trained model to a stream and load it without fitting again:

using (var output = System.IO.File.Create("model.bin"))
{
    model.Save(output);
}

using (var input = System.IO.File.OpenRead("model.bin"))
{
    var restored = AlternatingLeastSquaresRecommender.Load(input);
    using var recommendations = restored.Recommend("alice");
    var topItems = recommendations.Where(x => !seen.Contains(x.Key)).Take(5).ToArray();
    Console.WriteLine($"Restored model candidates: {topItems.Length}");
}

Saving preserves IDs, factors, regularization, and alpha. It does not preserve interaction history, weighting transforms, or training configuration. Streams remain open and are read or written at their current position; rewind a MemoryStream before loading from it.

The binary format has no version header or integrity check. Load trusted model artifacts and retain the producing library version and preprocessing settings alongside them. See binary persistence for the format and compatibility constraints.

Performance and Benchmarks

Training uses MathNet.Numerics and parallel row updates. MathNet's native providers can accelerate linear algebra; the repository benchmark compares its managed provider with native Intel MKL on Windows x64. See the benchmark configuration for provider setup.

Queries score and sort the full candidate population before application filters or Take(k) run. Taking ten results therefore does not reduce scoring work. Factor matrices contain roughly 4 * (userCount + itemCount) * factors bytes of float data, excluding object overhead, caches, training data, and working buffers.

Result lists are pooled to reduce repeated buffer allocation. Pool misses, larger result capacities, and result wrapper objects still allocate. Dispose results promptly to make their lists available for reuse; retained lists keep their capacity after clearing.

ALS training results

Measured on 2026-10-18 at commit 453c635, using BenchmarkDotNet's default job in Release configuration. Each operation trains a complete model on a synthetic 1024-by-1024 checkerboard with 523,264 interactions, 64 factors, 15 iterations, and 3 CG steps per row update. Training uses alpha = 1, regularization = 0.01, Random(42), and no loss calculation. Input construction occurs outside the timed operation; factor initialization is included.

MathNet's Control.MaxDegreeOfParallelism is set to 1. ALS retains its default ParallelOptions and performs parallel row updates, so this is a parallel training benchmark. MathNet.Numerics is version 5.0.0; the native provider package is MathNet.Numerics.MKL.Win-x64 3.0.0, reporting Intel MKL 2022.0 (x64, provider revision 15).

BenchmarkDotNet v0.15.8, Windows 11 (10.0.26300.9711)
Intel Core i9-14900K 3.20GHz, 1 CPU, 32 logical and 24 physical cores
.NET SDK 10.0.402
  [Host]     : .NET 10.0.13 (10.0.13, 10.0.1326.50504), X64 RyuJIT x86-64-v3
  DefaultJob : .NET 10.0.13 (10.0.13, 10.0.1326.50504), X64 RyuJIT x86-64-v3
GC: Concurrent Workstation
Provider Mean Error StdDev Allocated
Managed 919.4 ms 12.50 ms 11.70 ms 218.67 MB
NativeMKL 752.0 ms 5.19 ms 4.60 ms 135.02 MB

Error is half of the 99.9% confidence interval; StdDev is the standard deviation. Allocated measures managed allocations per complete training operation (1 MB = 1,048,576 bytes), excluding native MKL allocations. These values do not represent peak process memory.

To reproduce both measurements from the repository root:

dotnet run --project src/Implicit.Benchmark/Implicit.Benchmark.csproj -c Release -- --filter '*AlternatingLeastSquares*' --artifacts artifacts/readme-benchmarks-2026-10-18

MKL reduced mean training time by approximately 18% and managed allocations by 38% in this run. Results depend on hardware, runtime, concurrency, and data. The workload is roughly half dense and measures CG training time and allocations; benchmark representative data separately when assessing recommendation latency, relevance, or production capacity.

Development

Use the .NET SDK selected by global.json. From the repository root:

dotnet restore Implicit.slnx
dotnet build Implicit.slnx -c Release --no-restore
dotnet test src/Implicit.Tests/Implicit.Tests.csproj -c Release --no-build

The full test matrix requires Windows with .NET Framework 4.7.2-compatible execution and .NET 8/9/10 runtimes. For a focused run, use dotnet test src/Implicit.Tests/Implicit.Tests.csproj -c Release -f net10.0.

See the contributor guide for coding conventions and development and testing for packaging, analyzers, and benchmark workflows. The developer documentation also covers architecture and implementation constraints.

Based On and Planned Work

This project is based on the Python Implicit library by Ben Frederickson.

The ALS implementation follows the approaches described in Collaborative Filtering for Implicit Feedback Datasets and Applications of the Conjugate Gradient Method for Implicit Feedback Collaborative Filtering.

Previously listed planned algorithms remain unimplemented:

License

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 is compatible. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.7.2 106 10/18/2026
0.7.1 301 2/15/2026
0.7.0 168 2/14/2026
0.6.1 177 1/18/2026
0.6.0 370 12/17/2025
0.5.3 572 11/13/2024
0.5.2 451 3/22/2024
0.5.1 407 11/23/2023
0.5.0 322 9/23/2023
0.4.15 773 11/9/2022
0.4.14 519 11/9/2022
0.4.9 522 11/7/2022
0.4.4 763 5/5/2022
0.4.3 620 4/28/2022
0.4.2 609 4/28/2022
0.4.1 607 4/27/2022
0.4.0 623 4/26/2022
0.3.3 594 12/15/2021
0.3.2 674 6/16/2021
0.3.1 1,022 12/13/2019
Loading failed