recommended-item-search/softmax_model.py (102 lines of code) (raw):

#!/usr/bin/python # # Copyright 2019 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import pickle import tensorflow as tf # tf.enable_eager_execution() def get_feature_columns(metadata_path, embeddings_dim): def _get_num_bucket(): with tf.io.gfile.GFile(metadata_path, 'rb') as f: metadata = pickle.load(f) return metadata['N'] categorical_col = tf.feature_column.categorical_column_with_identity( key='movie_ids', num_buckets=_get_num_bucket()) feature_columns = [ # movie_ids tf.feature_column.embedding_column( categorical_column=categorical_col, dimension=embeddings_dim, combiner='mean') ] return feature_columns def get_activation_fn(activation): if activation == 'relu': return tf.nn.relu else: return None def build_network( inputs, hidden_dims, activation_fn, scope='user_embeddings'): """Build a forward network graph.""" with tf.variable_scope(scope): input_dim = inputs.shape[1].value for i, output_dim in enumerate(hidden_dims): layer = tf.layers.Dense(output_dim, activation=activation_fn) outputs = layer(inputs) input_dim = output_dim inputs = outputs return outputs def generate_labels(features): def _select_random(x): """Selectes a random elements from each row of x.""" def to_float(x): return tf.cast(x, tf.float32) def to_int(x): return tf.cast(x, tf.int64) batch_size = tf.shape(x)[0] rn = tf.range(batch_size) nnz = to_float(tf.count_nonzero(x >= 0, axis=1)) rnd = tf.random_uniform([batch_size]) ids = tf.stack([to_int(rn), to_int(nnz * rnd)], axis=1) return to_int(tf.gather_nd(x, ids)) return _select_random(features['movie_ids']) def softmax_loss(user_embeddings, movie_embeddings, labels): """Calculate loss with sampled movie id.""" user_embedding_size = user_embeddings.shape[1].value movie_embedding_size = movie_embeddings.shape[1].value if user_embedding_size != movie_embedding_size: raise ValueError( "The user embedding dimension %d should match the movie embedding " "dimension % d" % (user_embedding_size, movie_embedding_size)) logits = tf.matmul(user_embeddings, movie_embeddings, transpose_b=True) loss = tf.losses.sparse_softmax_cross_entropy(labels, logits) return loss def serving_input_fn(): receiver_tensor = {'input': tf.placeholder(shape=[None, None], dtype=tf.int64)} features = {'movie_ids': receiver_tensor['input']} return tf.estimator.export.ServingInputReceiver(features, receiver_tensor) def model_fn(features, labels, mode, params): """A recommendation model for movielens dataset. Args: features: sequences of movie_ids (batch_size x sequence_size) labels: None """ # fill a difference between training and prediction input. if not isinstance(features, dict): features = {'movie_ids': features} # create user_embeddings feature_columns = get_feature_columns( metadata_path=params.metadata_path, embeddings_dim=params.hidden_dims[-1]) user_input = tf.feature_column.input_layer( features=features, feature_columns=feature_columns) user_embeddings = build_network( inputs=user_input, hidden_dims=params.hidden_dims, activation_fn=get_activation_fn(params.activation_name)) # extract movie_embeddings with tf.variable_scope('input_layer', reuse=True): movie_embeddings = tf.get_variable('movie_ids_embedding/embedding_weights') # generate labels from features['movie_ids'] labels = generate_labels(features) loss = softmax_loss(user_embeddings, movie_embeddings, labels) estimator_spec = None if mode == tf.estimator.ModeKeys.PREDICT: predictions = { 'user_embeddings': user_embeddings } export_outputs = { 'predictions': tf.estimator.export.PredictOutput(predictions) } estimator_spec = tf.estimator.EstimatorSpec( mode=mode, predictions=predictions, export_outputs=export_outputs) if mode == tf.estimator.ModeKeys.TRAIN: global_step = tf.train.get_global_step() learning_rate = tf.train.exponential_decay( learning_rate=params.learning_rate, global_step=global_step, decay_steps=params.lr_decay_steps, decay_rate=params.lr_decay_rate) optimizer = tf.train.AdagradOptimizer(learning_rate) train_op = optimizer.minimize(loss, global_step=global_step) estimator_spec = tf.estimator.EstimatorSpec( mode=mode, loss=loss, train_op=train_op) if mode == tf.estimator.ModeKeys.EVAL: predictions = tf.matmul( user_embeddings, movie_embeddings, transpose_b=True) eval_metric_ops = { 'precision_at_10': tf.metrics.precision_at_k( labels=labels, predictions=predictions, k=10) } estimator_spec = tf.estimator.EstimatorSpec( mode=mode, loss=loss, eval_metric_ops=eval_metric_ops) return estimator_spec