基于自定义数据的TensorFlow推荐系统推荐结果超数据集范围问题
问题描述
使用自定义数据基于TensorFlow Recommenders搭建推荐系统时,出现推荐结果超出数据集可选范围的问题:数据集仅包含item 1、2、3,但系统推荐出了1、4、6、9等不存在的item。
环境配置:
- Google Colab
- Python 3.7.14
- TensorFlow 2.10.0
- tensorflow_recommenders 0.7.2
问题原因分析
训练数据输入顺序完全错误
构建ratings数据集时使用了df[['item_id', 'user_id']].values,每个样本结构为[item_id, user_id],但模型compute_loss方法中错误地将features[0](实际是item_id)传给用户模型,features[1](实际是user_id)传给物品模型,导致模型训练的输入完全错位,学习到的embedding不符合预期。推荐索引未绑定原始item_id
调用index.index_from_dataset时仅传入了经过items_model处理后的embedding,未将原始item_id与embedding对应绑定。此时BruteForce层会把模型内部的词汇表索引(而非原始item_id)作为推荐结果返回,加上IntegerLookup默认的mask token(值为0)和OOV(未知)索引,就出现了不在数据集中的数值。IntegerLookup默认配置引入无效索引
IntegerLookup默认会添加mask token和OOV索引,导致词汇表大小大于实际item数量,这些额外索引会被当作有效item返回。
解决方案
1. 修正训练数据输入顺序
调整ratings数据集的列顺序为[user_id, item_id],并修正模型compute_loss中的特征提取逻辑:
# 修正ratings数据集列顺序 ratings = tf.data.Dataset.from_tensor_slices(df[['user_id', 'item_id']].values) # 修正模型compute_loss方法 def compute_loss(self, features: tf.Tensor, training=False) -> tf.Tensor: # features形状为(batch_size, 2),[:,0]是user_id,[:,1]是item_id user_embeddings = self.user_model(features[:, 0]) item_embeddings = self.item_model(features[:, 1]) return self.task(user_embeddings, item_embeddings)
2. 构建索引时绑定原始item_id
传入包含**(物品embedding, 原始item_id)**的数据集,确保返回结果为原始item_id:
# 构建包含embedding和原始item_id的索引数据集 index_dataset = items.batch(100).map(lambda x: (items_model(x), tf.squeeze(x, axis=1))) # 传入索引 index.index_from_dataset(index_dataset)
3. 配置IntegerLookup禁用无效索引
由于item_id是已知离散值,禁用OOV和mask token,避免引入额外无效索引:
user_id_vocabulary = tf.keras.layers.IntegerLookup(num_oov_indices=0, mask_token=None) # 仅传入user_id列适配词汇表 user_id_vocabulary.adapt(ratings.map(lambda x: x[0])) items_vocabulary = tf.keras.layers.IntegerLookup(num_oov_indices=0, mask_token=None) items_vocabulary.adapt(items)
完整修正代码
from typing import Dict, Text import numpy as np import pandas as pd import tensorflow as tf import tensorflow_datasets as tfds import tensorflow_recommenders as tfrs DATA_URL = "sample.csv" df = pd.read_csv(DATA_URL) # 修正训练数据集列顺序 ratings = tf.data.Dataset.from_tensor_slices(df[['user_id', 'item_id']].values) items = tf.data.Dataset.from_tensor_slices(df[['item_id']].values) # 配置IntegerLookup,禁用OOV和mask token user_id_vocabulary = tf.keras.layers.IntegerLookup(num_oov_indices=0, mask_token=None) user_id_vocabulary.adapt(ratings.map(lambda x: x[0])) items_vocabulary = tf.keras.layers.IntegerLookup(num_oov_indices=0, mask_token=None) items_vocabulary.adapt(items) class CustomModel(tfrs.Model): def __init__( self, user_model: tf.keras.Model, item_model: tf.keras.Model, task: tfrs.tasks.Retrieval): super().__init__() self.user_model = user_model self.item_model = item_model self.task = task def compute_loss(self, features: tf.Tensor, training=False) -> tf.Tensor: user_embeddings = self.user_model(features[:, 0]) item_embeddings = self.item_model(features[:, 1]) return self.task(user_embeddings, item_embeddings) users_model = tf.keras.Sequential([ user_id_vocabulary, tf.keras.layers.Embedding(user_id_vocabulary.vocabulary_size(),64) ]) items_model = tf.keras.Sequential([ items_vocabulary, tf.keras.layers.Embedding(items_vocabulary.vocabulary_size(),64) ]) task = tfrs.tasks.Retrieval(metrics=tfrs.metrics.FactorizedTopK( items.batch(128).map(lambda x: items_model(x)))) model = CustomModel(users_model, items_model, task) model.compile(optimizer=tf.keras.optimizers.Adagrad(0.5)) model.fit(ratings.batch(8), epochs=3) # 构建绑定原始item_id的索引数据集 index = tfrs.layers.factorized_top_k.BruteForce(model.user_model) index_dataset = items.batch(100).map(lambda x: (items_model(x), tf.squeeze(x, axis=1))) index.index_from_dataset(index_dataset) # 获取推荐结果 _, titles = index(np.array([1])) print(f"Top 3 recommendations for user 1: {titles[0, :3]}") # 打印所有推荐结果 titlesNumpy = titles.numpy() for i in titlesNumpy[0]: print(i)
内容的提问来源于stack exchange,提问作者Fian Julio

