Bandit-based recommender systems for enterprise applications with context-free, parametric, and non-parametric contextual policies. Designed for modularity, rapid experimentation, and rigorous evaluation.
1 Department of Computer Science, Brown University 2 AI Center of Excellence, Fidelity Investments
# Example of how to train a single recommender to generate top-4 recommendations
# Import
from mab2rec import BanditRecommender, LearningPolicy
from mab2rec.pipeline import train, score
# LinGreedy recommender to select top-4 items with 10% random exploration
rec = BanditRecommender(LearningPolicy.LinGreedy(epsilon=0.1), top_k=4)
# Train on (user, item, response) interactions in train data using user features
train(rec, data='data/data_train.csv',
user_features='data/features_user.csv')
# Score recommendations for users in test data. The output df holds
# user_id, item_id, score columns for every test user for top-k items
df = score(rec, data='data/data_test.csv',
user_features='data/features_user.csv')
@inproceedings{mab2rec,
title={Building higher-order abstractions from the components of recommender systems},
author={Kad{\i}o{\u{g}}lu, Serdar and Kleynhans, Bernard},
booktitle={Proceedings of the AAAI Conference on Artificial Intelligence},
volume={38},
number={21},
pages={22998--23004},
year={2024}
}
@article{kadiouglu2025open,
title={Open-source AI at scale: Establishing an enterprise AI strategy through modular frameworks},
author={Kad{\i}o{\u{g}}lu, Serdar},
journal={AI Magazine},
volume={46},
number={3},
pages={e70032},
year={2025},
publisher={Wiley Online Library}
}
Mab2Rec composes specialized open-source components for recommendation, representation learning, fairness and performance evaluation.
Core engine to create multi-armed bandit recommendation algorithms used by Mab2Rec.
# An example that shows how to use the UCB1 learning policy # to choose between two arms based on their expected rewards. # Import MABWiser Library from mabwiser.mab import MAB, LearningPolicy, NeighborhoodPolicy # Data arms = ['Arm1', 'Arm2'] decisions = ['Arm1', 'Arm1', 'Arm2', 'Arm1'] rewards = [20, 17, 25, 9] # Model mab = MAB(arms, LearningPolicy.UCB1(alpha=1.25)) # Train mab.fit(decisions, rewards) # Test mab.predict()
@article{mabwiser,
title={MABWiser: parallelizable contextual multi-armed bandits},
author={Strong, Emily and Kleynhans, Bernard and Kad{\i}{\u{g}}lu, Serdar},
journal={International Journal on Artificial Intelligence Tools},
volume={30},
number={04},
pages={2150021},
year={2021},
publisher={World Scientific}
}
Text featurization toolkit to create item representations for recommender models.
# Conceptually, TextWiser is composed of an Embedding, potentially with a pretrained model, # that can be chained into zero or more Transformations from textwiser import TextWiser, Embedding, Transformation, WordOptions, PoolOptions # Data documents = ["Some document", "More documents. Including multi-sentence documents."] # Model: TFIDF `min_df` parameter gets passed to sklearn automatically emb = TextWiser(Embedding.TfIdf(min_df=1)) # Model: TFIDF followed with an NMF + SVD emb = TextWiser(Embedding.TfIdf(min_df=1), [Transformation.NMF(n_components=30), Transformation.SVD(n_components=10)]) # Model: Word2Vec with no pretraining that learns from the input data emb = TextWiser(Embedding.Word(word_option=WordOptions.word2vec, pretrained=None), Transformation.Pool(pool_option=PoolOptions.min)) # Model: BERT with the pretrained bert-base-uncased embedding emb = TextWiser(Embedding.Word(word_option=WordOptions.bert), Transformation.Pool(pool_option=PoolOptions.first)) # Features vecs = emb.fit_transform(documents)
@inproceedings{textwiser,
title={Representing the unification of text featurization using a context-free grammar},
author={Kilit{\c{c}}ioglu, Doruk and Kad{\i}{\u{g}}lu, Serdar},
booktitle={Proceedings of the AAAI Conference on Artificial Intelligence},
volume={35},
number={17},
pages={15439--15445},
year={2021}
}
Feature selection toolkit to create user representations from structured and high-dimensional data.
# Import Selective and SelectionMethod
from sklearn.datasets import fetch_california_housing
from feature.utils import get_data_label
from feature.selector import Selective, SelectionMethod
# Data
data, label = get_data_label(fetch_california_housing())
# Feature selectors from simple to more complex
selector = Selective(SelectionMethod.Variance(threshold=0.0))
selector = Selective(SelectionMethod.Correlation(threshold=0.5, method="pearson"))
selector = Selective(SelectionMethod.Statistical(num_features=3, method="anova"))
selector = Selective(SelectionMethod.Linear(num_features=3, regularization="none"))
selector = Selective(SelectionMethod.TreeBased(num_features=3))
# Feature reduction
subset = selector.fit_transform(data, label)
print("Reduction:", list(subset.columns))
print("Scores:", list(selector.get_absolute_scores()))
@article{selective,
title={Integrating optimized item selection with active learning for continuous exploration in recommender systems},
author={Kad{\i}{\u{g}}lu, Serdar and Kleynhans, Bernard and Wang, Xin},
journal={Annals of Mathematics and Artificial Intelligence},
volume={92},
number={6},
pages={1585--1607},
year={2024},
publisher={Springer}
}
Sequential pattern mining toolkit to construct user representations from temporal interaction sequences.
# Example to show how to find frequent sequential patterns
# from a given sequence database subject to constraints
from sequential.seq2pat import Seq2Pat, Attribute
# Seq2Pat over 3 sequences
seq2pat = Seq2Pat(sequences=[["A", "A", "B", "A", "D"],
["C", "B", "A"],
["C", "A", "C", "D"]])
# Price attribute corresponding to each item
price = Attribute(values=[[5, 5, 3, 8, 2],
[1, 3, 3],
[4, 5, 2, 1]])
# Average price constraint
seq2pat.add_constraint(3 <= price.average() <= 4)
# Patterns that occur at least twice (A-D)
patterns = seq2pat.get_patterns(min_frequency=2)
@article{seq2pat,
title={Seq2Pat: Sequence-to-pattern generation to bridge pattern mining with machine learning},
author={Kad{\i}{\u{g}}lu, Serdar and Wang, Xin and Hosseininasab, Amin and van Hoeve, Willem-Jan},
journal={AI Magazine},
volume={44},
number={1},
pages={54--66},
year={2023},
publisher={Wiley Online Library}
}
Evaluation library for recommendation quality, including ranking, classification, and fairness metrics.
# Import binary and multi-class fairness metrics
from jurity.fairness import BinaryFairnessMetrics, MultiClassFairnessMetrics
# Data
binary_predictions = [1, 1, 0, 1, 0, 0]
multi_class_predictions = ["a", "b", "c", "b", "a", "a"]
multi_class_multi_label_predictions = [["a", "b"], ["b", "c"], ["b"], ["a", "b"], ["c", "a"], ["c"]]
memberships = [0, 0, 0, 1, 1, 1]
classes = ["a", "b", "c"]
# Metrics (see also other available metrics)
metric = BinaryFairnessMetrics.StatisticalParity()
multi_metric = MultiClassFairnessMetrics.StatisticalParity(classes)
# Scores
print("Metric:", metric.description)
print("Lower Bound: ", metric.lower_bound)
print("Upper Bound: ", metric.upper_bound)
print("Ideal Value: ", metric.ideal_value)
print("Binary Fairness score: ", metric.get_score(binary_predictions, memberships))
print("Multi-class Fairness scores: ", multi_metric.get_scores(multi_class_predictions, memberships))
print("Multi-class multi-label Fairness scores: ", multi_metric.get_scores(multi_class_multi_label_predictions, memberships))
@article{jurity,
title={Surrogate Modeling to Address the Absence of Protected Membership Attributes in Fairness Evaluation},
author={Kadio{\u{g}}lu, Serdar and Thielbar, Melinda},
journal={ACM Transactions on Evolutionary Learning},
volume={5},
number={3},
pages={1--25},
year={2025},
publisher={ACM New York, NY}
}
Explainable AI based on expressive Boolean formulas.
import numpy as np
from sklearn.metrics import balanced_accuracy_score
from boolxai import BoolXAI, Operator
# Create random toy data for binary classification. X and y must be binary!
rng = np.random.default_rng(seed=42)
X = rng.choice([0, 1], size=(100, 10))
y = rng.choice([0, 1], size=100)
# Rule classifier with maximum depth, complexity, possible operators
rule_classifier = BoolXAI.RuleClassifier(max_depth=3,
max_complexity=6,
operators=[Operator.And, Operator.Or, Operator.Choose, Operator.AtMost, Operator.AtLeast],
random_state=42)
# Learn the best rule
rule_classifier.fit(X, y)
# Best rule and best score
best_rule = rule_classifier.best_rule_
best_score = rule_classifier.best_score_
print(f"{best_rule=} {best_score=:.2f}")
# The depth of a rule is the number of edges in the longest path from the root to any leaf/literal.
# The complexity of a rule is the total number of operators and literals.
print(f"depth={best_rule.depth()} complexity={best_rule.complexity()}")
# Predict and score
y_pred = rule_classifier.predict(X)
score = balanced_accuracy_score(y, y_pred)
print(f"{score=:.2f}")
# It is also possible to plot the best rule --requires installing plot dependencies
best_rule.plot()
# or get a networkx.DiGraph representation of the rule --requires installing plot dependencies
G = best_rule.to_graph()
print(G)
@inproceedings{boolxai,
title={BoolXAI: Explainable AI Using Expressive Boolean Formulas},
author={Kad{\i}{\u{g}}lu, Serdar and Zhu, Elton Yechao and Rosenberg, Gili and Brubaker, John Kyle and Schuetz, Martin JA and Salton, Grant and Zhu, Zhihuai and Katzgraber, Helmut G},
booktitle={Proceedings of the AAAI Conference on Artificial Intelligence},
volume={39},
number={28},
pages={28900--28906},
year={2025}
}
Selected patents across recommender systems, fairness, optimization, predictive modeling, and reinforcement learning.
Talks, tutorials, community resources, and practical references for building recommender systems.