Quantcast
Channel: Machine Learning | Towards AI
Viewing all articles
Browse latest Browse all 786

Using LLMs to Build Explainable Recommender Systems

$
0
0
Author(s): Hang Yu Originally published on Towards AI. Photo by Sean Benesh on Unsplash In recent days, there’s no doubt that LLMs have become the most eye-catching superhero in the spotlight. While this powerful technique has raised a range of ethical concerns, there are more benefits and value being explored to either enhance the existing use cases or discover new opportunities. In this blog, I’ll demonstrate how LLMs can be used to improve recommender systems in two ways at the same time: Increase the predictive power. This is achieved by ranking the candidates generated by the upstream model. Provide explainability. Leveraging the rich knowledge compressed, each recommendation will have a context-based explanation. This will largely benefit use cases that need to interpret model behaviors. For the sake of clarity, only the most essential code is displayed in this article. However, this experiment can be reproduced and extended based on the code hosted on Github. Recommender system First, let’s recap what a personalized recommender system looks like from an architectural perspective. As depicted below, it’s generally a multi-stage funnel whereby each stage has a decreased number of candidate items with an increased relevance. It consists of two essential stages, which are recall (or matching) and ranking [1]. For a target person, the recall stage retrieves the top items that are potentially of interest from a wide range of channels, including interactions, promotions, etc., and those candidates are then ordered by the ranking stage to prioritize the best ones. The ranking stage is usually a model trained based on the fine-grained user and item knowledge. In modern days, the funnel can optionally have modules like the pre-ranking and re-ranking stages before and after ranking, respectively, to accommodate large candidate sets, high model complexity, and business-specific rules based on the needs of platforms. A multi-stage recommender system U+007C Image by author LLM-based recommender system This work mainly focuses on applying LLM in the ranking stage because of the following reasons: LLMs’ rich external knowledge, which is complementary to the original dataset, would better identify the relative relationships among items. LLMs are limited by the token lengths. The ranking stage has a smaller number of candidates to be processed, so it would mitigate the impact of the token limitation. Briefly, the system is a two-stage architecture whereby the recall stage is implemented using Matrix Factorization (MF) followed by an LLM-based ranking module generating ranks and explanations. Next, I’ll describe the dataset used, and how the recall and ranking stages are implemented and evaluated. Dataset The dataset used is the publicly available MovieLens 100k. This popular benchmark dataset contains 100k movie ratings from 1000 users and 1700 movies. The ratings are then transformed to binary implicit feedback as these are more common in the real world. ratings['like'] = ratings['rating'] > 3 The processed dataset is shown below. The new column ‘like’ is transformed implicit signal from the original ratings. A sample of MovieLens 100k Next, the dataset is split into a training set and a test set with a split ratio of 0.9. train_ratio = 0.9train_size = int(len(ratings)*train_ratio)ratings_train = ratings.sample(train_size, random_state=42)ratings_test = ratings[~ratings.index.isin(ratings_train.index)] Now, we have the data prepared. Let’s build the recall stage! Recall stage The recall stage is implemented using the widely adopted Matrix Factorization, which is a type of collaborative filtering. The basic idea is to project users and items into the same embedding space and model the similarity based on the binary user-item interactions. In terms of implementation, a fast version that is Alternating Least Squares is adopted. Firstly, the data needs to be transformed into a sparse matrix with the row indices representing user ID and column indices representing item ID. It’s worth noting that the “ID minus 1” operation transforms actual IDs to indices that start from 0. from scipy.sparse import csr_matrixn_users = ratings_train['user id'].max()n_item = ratings_train['item id'].max()ratings_train_pos = ratings_train[ratings_train['like']]ratings_test_pos = ratings_test[ratings_test['like']]row=ratings_train_pos['user id'].values - 1col=ratings_train_pos['item id'].values - 1data=np.ones(len(ratings_train_pos))user_item_data = csr_matrix((data, (row, col)), shape=(n_users, n_item)) Now we simply use the interaction matrix to train the MF model using the default parameters. import implicit# initialize a modelmodel = implicit.als.AlternatingLeastSquares(factors=50, random_state=42)# train the model on a sparse matrix of user/item/confidence weightsmodel.fit(user_item_data) As shown by the following function, retrieving the top-N candidate items for a given user is pretty simple. Here, the movies already watched are filtered out as we aim to recommend new ones. def recall_stage(model, user_id, user_item_data, ratings_train, N): filter_items = ratings_train[ratings_train['user id']==user_id]['item id'].values filter_items = filter_items - 1 user_id = user_id - 1 recs, scores = model.recommend(user_id, user_item_data[user_id], filter_items=filter_items, N=N_recall) recs = recs.flatten() + 1 return recs Ranking stage Now, it’s time to build the ranking stage using LLM. Briefly, the idea is that we let LLM tell us if the user likes each item returned by the recall stage based on the user preference in the training data and its own knowledge. This is implemented as a typical few-shot prompt shown below. from langchain.chat_models import ChatOpenAIfrom langchain.prompts import ChatPromptTemplatefrom langchain.chains import LLMChainimport openaiimport osfrom google.colab import userdataif "OPENAI_API_KEY" not in os.environ: os.environ["OPENAI_API_KEY"] = userdata.get('OPENAI_API_KEY')llm_model = "gpt-3.5-turbo"llm = ChatOpenAI(temperature=0.0, model=llm_model)prompt = ChatPromptTemplate.from_template("""The person has a list of liked movies: {movies_liked}. \The person has a list of disliked movies: {movies_disliked}. \Tell me if this person likes each of the candidate movies: {movies_candidates}.\Return a list of boolean values and explain why the person likes or dislikes.<< FORMATTING >>Return a markdown code snippet with a list of JSON object formatted to look like:{{ "title": string \ the name of the movie in candidate movies "like": boolean \ true or false "explanation": string \ explain why the person likes or dislikes the candidate movie}}REMEMBER: Each boolean and explanation for each element in candidate movies.REMEMBER: The explanation must relate to the person's liked and disliked movies.""")chain = LLMChain(llm=llm, prompt=prompt) This prompt requires three input parameters: movies_liked: a list of titles of the movies liked by the user. movies_disliked: a list of titles of the movies disliked by the user. movies_candidates: a list of titles of the candidate movies to be judged by the LLM […]

Viewing all articles
Browse latest Browse all 786

Trending Articles