tensorflow / tensorflow/recommenders

How to build a retrieval task with Keras data generators

Open
#313 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
2k
Forks
300
PR merge metrics
No merged PRs in 30d

Description

I've recently started working with the package to build recommender systems and so far I've successfully built a Ranking task that takes the inputs from a Keras Data Generator. However, I could not make the same pipeline work for the Retrieval tasks, because the recommended approach to instantiate such a task involves passing a tf.data.Dataset as follows.

    self.task = tfrs.tasks.Retrieval(
        metrics=tfrs.metrics.FactorizedTopK(
            candidates=movies.batch(128).map(self.candidate_model),
        ),
    )

After looking into the documentation and the source code, I found out also a factorized_top_k layer can also be passed. So, I tried the following:

class RetrievalModel(tfrs.Model):
    def __init__(self, unique_user_ids, unique_item_ids, 
                 time_features=[],
                 customer_features=[],
                 item_features=[]):

        super().__init__()  
        
        user_embedding_dim = item_embedding_dim = embedding_dim

        # The two-tower model needs user and item towers of the same size
        if len(item_features) - len(customer_features) > 0:
            user_embedding_dim = 2 * embedding_dim + 13

        # Compute embeddings for users.
        self.user_model = UserModel2D(unique_user_ids,
                                    customer_features=customer_features,
                                    time_features=time_features,
                                    embedding_dim=user_embedding_dim,
                                    )

        # Compute embeddings for items.
        self.item_model = ItemModel2D(unique_item_ids,
                                    item_features=item_features,
                                    embedding_dim=item_embedding_dim,
                                    )
    
        self.candidate_layer = tfrs.layers.factorized_top_k.ScaNN(self.user_model)
        metrics= tfrs.metrics.FactorizedTopK(candidates=self.candidate_layer)
        self.task = tfrs.tasks.Retrieval(metrics=metrics)

    def call(self, inputs):
        user_embedding = self.user_model(inputs)
        item_embedding = self.item_model(inputs)
        self.candidate_layer.index(candidates=item_embedding)

        return user_embedding, item_embedding

    def compute_loss(self, inputs, training=False):
        user_embedding, item_embedding = self(inputs)
        return self.task(user_embedding, item_embedding, compute_metrics=not training)

But it did not work either. I was just wondering how you can create a Retrieval task without using tf.data.Dataset. I'd appreciate any feedback here. Thanks a lot for taking the time!

Here are some outputs, and debugging prints. I simplified some parts to make it a bit easier to read and hid confidential information.

--- init ---
item_features: 4
customer_features: 3
time_features: 3
user_embedding_dim: 67
item_embedding_dim: 27
user: len(unique_user_ids) 135447
user: len(customer_features) 3
user: len(time_features) 3
item: len(unique_item_ids) 504
item: len(customer_features) 4
candidate_layer: <tensorflow_recommenders.layers.factorized_top_k.ScaNN object at 0x7fc6ea5b5240>
metrics: <tensorflow_recommenders.metrics.factorized_top_k.FactorizedTopK object at 0x7fc6ea5b5518>
task: <tensorflow_recommenders.tasks.retrieval.Retrieval object at 0x7fc6ea561c88>
train_datagenerator: <deep_learning.helpers.DataGenerator object at 0x7fc88ac82f60>

--- call ---
inputs: {'user_id': <tf.Tensor: shape=(1024,), dtype=int32, numpy=
array([158429273, 460546163, 144561824, ..., 130676640,  17285232,
       111347467], dtype=int32)>, 'item_id': <tf.Tensor: shape=(1024,), dtype=int32, numpy=
array([ 10903699, 484336382, 459214922, ..., 945589400, 484336382,
       303642080], dtype=int32)>,  ... }
user: user_embedding.shape (1024, 67)
user: context_embedding.shape (1024, 12)
user: after align user_embedding.shape (1024, 79)
user: user_embedding.shape (1024, 79)
user: context_embedding.shape (1024, 3)
user: after align user_embedding.shape (1024, 82)
user_embedding.shape (1024, 82)
item: item_embedding.shape (1024, 27)
item: context_embedding.shape (1024, 28)
item: after align context item_embedding.shape (1024, 55)
item: item_embedding.shape (1024, 55)
item: context_embedding.shape (1024, 27)
item: after align context item_embedding.shape (1024, 82)
item_embedding.shape (1024, 82)

--- compute loss ---
inputs: {'user_id': <tf.Tensor 'IteratorGetNext:0' shape=(None,) dtype=int32>, 'item_id': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int32>, ...}

--- call ---
inputs: {'user_id': <tf.Tensor 'IteratorGetNext:0' shape=(None,) dtype=int32>, 'item_id': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=int32>, ...}

user: user_embedding.shape (None, 67)
user: context_embedding.shape (None, 12)
user: after align user_embedding.shape (None, 79)
user: user_embedding.shape (None, 79)
user: context_embedding.shape (None, 3)
user: after align user_embedding.shape (None, 82)
user_embedding.shape (None, 82)

item: item_embedding.shape (None, 27)
item: context_embedding.shape (None, 28)
item: after align context item_embedding.shape (None, 55)
item: item_embedding.shape (None, 55)
item: context_embedding.shape (None, 27)
item: after align context item_embedding.shape (None, 82)
item_embedding.shape (None, 82)

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-6-7b4e907381cd> in <module>
     11                              batch_size=batch_size,
     12                              use_implicit_feedback=use_implicit_feedback,
---> 13                              use_all_features=use_all_features)

~.../deep_learning/helpers.py in hyperparameter_tune_deep_learning(project_path, data_folder_name, split_suffix, study_name, n_trials, trial_epochs, batch_size, use_implicit_feedback, use_all_features)
    741                                            use_all_features, monitor), 
    742                    n_trials=n_trials,
--> 743                    callbacks=[tensorboard_callback])
    744 
    745     study_dir = project_path + '/data/hyperparameter_tuning_studies/'

~/workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/optuna/study.py in optimize(self, func, n_trials, timeout, n_jobs, catch, callbacks, gc_after_trial, show_progress_bar)
    313             callbacks=callbacks,
    314             gc_after_trial=gc_after_trial,
--> 315             show_progress_bar=show_progress_bar,
    316         )
    317 

~/workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/optuna/_optimize.py in _optimize(study, func, n_trials, timeout, n_jobs, catch, callbacks, gc_after_trial, show_progress_bar)
     63                 reseed_sampler_rng=False,
     64                 time_start=None,
---> 65                 progress_bar=progress_bar,
     66             )
     67         else:

~/workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/optuna/_optimize.py in _optimize_sequential(study, func, n_trials, timeout, catch, callbacks, gc_after_trial, reseed_sampler_rng, time_start, progress_bar)
    154 
    155         try:
--> 156             trial = _run_trial(study, func, catch)
    157         except Exception:
    158             raise

~/workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/optuna/_optimize.py in _run_trial(study, func, catch)
    187 
    188     try:
--> 189         value = func(trial)
    190     except exceptions.TrialPruned as e:
    191         # Register the last intermediate value if present as the value of the trial.

~.../deep_learning/helpers.py in <lambda>(trial)
    739     study.optimize(lambda trial: objective(trial, train_datagenerator, val_datagenerator, 
    740                                            time_features, customer_features, item_features,
--> 741                                            use_all_features, monitor), 
    742                    n_trials=n_trials,
    743                    callbacks=[tensorboard_callback])

~.../deep_learning/helpers.py in objective(trial, train_datagenerator, val_datagenerator, time_features, customer_features, item_features, use_all_features, monitor)
    632                   validation_data=val_datagenerator, validation_freq=1,
    633                   callbacks=[reduce_lr, trial_pruner],
--> 634                   verbose=1)
    635 
    636     return history.history[monitor][-1]

~/workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/tensorflow/python/keras/engine/training.py in fit(self, x, y, batch_size, epochs, verbose, callbacks, validation_split, validation_data, shuffle, class_weight, sample_weight, initial_epoch, steps_per_epoch, validation_steps, validation_batch_size, validation_freq, max_queue_size, workers, use_multiprocessing)
   1181                 _r=1):
   1182               callbacks.on_train_batch_begin(step)
-> 1183               tmp_logs = self.train_function(iterator)
   1184               if data_handler.should_sync:
   1185                 context.async_wait()

~/workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/tensorflow/python/eager/def_function.py in __call__(self, *args, **kwds)
    887 
    888       with OptionalXlaContext(self._jit_compile):
--> 889         result = self._call(*args, **kwds)
    890 
    891       new_tracing_count = self.experimental_get_tracing_count()

~/workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/tensorflow/python/eager/def_function.py in _call(self, *args, **kwds)
    931       # This is the first call of __call__, so we have to initialize.
    932       initializers = []
--> 933       self._initialize(args, kwds, add_initializers_to=initializers)
    934     finally:
    935       # At this point we know that the initialization is complete (or less

~/workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/tensorflow/python/eager/def_function.py in _initialize(self, args, kwds, add_initializers_to)
    762     self._concrete_stateful_fn = (
    763         self._stateful_fn._get_concrete_function_internal_garbage_collected(  # pylint: disable=protected-access
--> 764             *args, **kwds))
    765 
    766     def invalid_creator_scope(*unused_args, **unused_kwds):

~/workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/tensorflow/python/eager/function.py in _get_concrete_function_internal_garbage_collected(self, *args, **kwargs)
   3048       args, kwargs = None, None
   3049     with self._lock:
-> 3050       graph_function, _ = self._maybe_define_function(args, kwargs)
   3051     return graph_function
   3052 

~/workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/tensorflow/python/eager/function.py in _maybe_define_function(self, args, kwargs)
   3442 
   3443           self._function_cache.missed.add(call_context_key)
-> 3444           graph_function = self._create_graph_function(args, kwargs)
   3445           self._function_cache.primary[cache_key] = graph_function
   3446 

~/workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/tensorflow/python/eager/function.py in _create_graph_function(self, args, kwargs, override_flat_arg_shapes)
   3287             arg_names=arg_names,
   3288             override_flat_arg_shapes=override_flat_arg_shapes,
-> 3289             capture_by_value=self._capture_by_value),
   3290         self._function_attributes,
   3291         function_spec=self.function_spec,

~/workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/tensorflow/python/framework/func_graph.py in func_graph_from_py_func(name, python_func, args, kwargs, signature, func_graph, autograph, autograph_options, add_control_dependencies, arg_names, op_return_value, collections, capture_by_value, override_flat_arg_shapes)
    997         _, original_func = tf_decorator.unwrap(python_func)
    998 
--> 999       func_outputs = python_func(*func_args, **func_kwargs)
   1000 
   1001       # invariant: `func_outputs` contains only Tensors, CompositeTensors,

~/workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/tensorflow/python/eager/def_function.py in wrapped_fn(*args, **kwds)
    670         # the function a weak reference to itself to avoid a reference cycle.
    671         with OptionalXlaContext(compile_with_xla):
--> 672           out = weak_wrapped_fn().__wrapped__(*args, **kwds)
    673         return out
    674 

~/workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/tensorflow/python/framework/func_graph.py in wrapper(*args, **kwargs)
    984           except Exception as e:  # pylint:disable=broad-except
    985             if hasattr(e, "ag_error_metadata"):
--> 986               raise e.ag_error_metadata.to_exception(e)
    987             else:
    988               raise

ValueError: in user code:

    /home/.../workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/tensorflow/python/keras/engine/training.py:855 train_function  *
        return step_function(self, iterator)
    /home/.../deep_learning/models.py:457 call  *
        index = self.candidate_layer.index(candidates=item_embedding)
    /home/.../workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/tensorflow_recommenders/layers/factorized_top_k.py:491 index  *
        identifiers = tf.range(candidates.shape[0])
    /home/.../workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/tensorflow/python/util/dispatch.py:206 wrapper  **
        return target(*args, **kwargs)
    /home/.../workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/tensorflow/python/ops/math_ops.py:1908 range
        limit = ops.convert_to_tensor(limit, dtype=dtype, name="limit")
    /home/.../workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/tensorflow/python/profiler/trace.py:163 wrapped
        return func(*args, **kwargs)
    /home/.../workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/tensorflow/python/framework/ops.py:1566 convert_to_tensor
        ret = conversion_func(value, dtype=dtype, name=name, as_ref=as_ref)
    /home/.../workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/tensorflow/python/framework/constant_op.py:339 _constant_tensor_conversion_function
        return constant(v, dtype=dtype, name=name)
    /home/.../workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/tensorflow/python/framework/constant_op.py:265 constant
        allow_broadcast=True)
    /home/.../workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/tensorflow/python/framework/constant_op.py:283 _constant_impl
        allow_broadcast=allow_broadcast))
    /home/.../workspace/conda/envs/tf_gpu_py36/lib/python3.6/site-packages/tensorflow/python/framework/tensor_util.py:445 make_tensor_proto
        raise ValueError("None values not supported.")

    ValueError: None values not supported.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the tfrs.tasks.Retrieval and tfrs.layers.factorized_top_k.ScaNN entry points, then reproduce the failure with the shown Keras DataGenerator and model call. Done would be a documented or verified way to use Retrieval with this input pipeline, or a clearly reported incompatibility and its expected handling.

Written by the indexing model from the issue text.

Assessment

Tech stack
keras, python, tensorflow
Domain
machine-learning
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.