| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- # Copyright (c) 2018. All rights reserved.
- #
- # 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.
- #
- # -----------------------------------------------------------------------
- #
- # Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved.
- #
- # 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 tensorflow as tf
- import horovod.tensorflow as hvd
- def float32_variable_storage_getter(getter, name, shape=None, dtype=None,
- initializer=None, regularizer=None,
- trainable=True,
- *args, **kwargs):
- """
- Custom variable getter that forces trainable variables to be stored in
- float32 precision and then casts them to the half-precision
- """
- storage_dtype = tf.float32 if trainable else dtype
- variable = getter(name, shape, dtype=storage_dtype,
- initializer=initializer, regularizer=regularizer,
- trainable=trainable,
- *args, **kwargs)
- if trainable and dtype != tf.float32:
- variable = tf.cast(variable, dtype)
- return variable
- def neural_mf(users,
- items,
- model_dtype,
- nb_users,
- nb_items,
- mf_dim,
- mf_reg,
- mlp_layer_sizes,
- mlp_layer_regs,
- dropout_rate,
- sigmoid=False):
- """
- Constructs the model graph
- """
- # Check params
- if len(mlp_layer_sizes) != len(mlp_layer_regs):
- raise RuntimeError('u dummy, layer_sized != layer_regs')
- if mlp_layer_sizes[0] % 2 != 0:
- raise RuntimeError('u dummy, mlp_layer_sizes[0] % 2 != 0')
- nb_mlp_layers = len(mlp_layer_sizes)
- # Embeddings
- user_embed = tf.get_variable(
- "user_embeddings",
- shape=[nb_users, mf_dim + mlp_layer_sizes[0] // 2],
- initializer=tf.initializers.random_normal(mean=0.0, stddev=0.01))
- item_embed = tf.get_variable(
- "item_embeddings",
- shape=[nb_items, mf_dim + mlp_layer_sizes[0] // 2],
- initializer=tf.initializers.random_normal(mean=0.0, stddev=0.01))
- # Matrix Factorization Embeddings
- xmfu = tf.nn.embedding_lookup(user_embed[:, :mf_dim], users, partition_strategy='div')
- xmfi = tf.nn.embedding_lookup(item_embed[:, :mf_dim], items, partition_strategy='div')
- # MLP Network Embeddings
- xmlpu = tf.nn.embedding_lookup(user_embed[:, mf_dim:], users, partition_strategy='div')
- xmlpi = tf.nn.embedding_lookup(item_embed[:, mf_dim:], items, partition_strategy='div')
- # Enforce model to use fp16 data types when manually enabling mixed precision
- # (Tensorfow ops will use automatically use the data type of the first input)
- if model_dtype == tf.float16:
- xmfu = tf.cast(xmfu, model_dtype)
- xmfi = tf.cast(xmfi, model_dtype)
- xmlpu = tf.cast(xmlpu, model_dtype)
- xmlpi = tf.cast(xmlpi, model_dtype)
- # Matrix Factorization
- xmf = tf.math.multiply(xmfu, xmfi)
- # MLP Layers
- xmlp = tf.concat((xmlpu, xmlpi), 1)
- for i in range(1, nb_mlp_layers):
- xmlp = tf.layers.Dense(
- mlp_layer_sizes[i],
- activation=tf.nn.relu,
- kernel_initializer=tf.glorot_uniform_initializer()
- ).apply(xmlp)
- xmlp = tf.layers.Dropout(rate=dropout_rate).apply(xmlp)
- # Final fully-connected layer
- logits = tf.concat((xmf, xmlp), 1)
- logits = tf.layers.Dense(
- 1,
- kernel_initializer=tf.keras.initializers.lecun_uniform()
- ).apply(logits)
- if sigmoid:
- logits = tf.math.sigmoid(logits)
- # Cast model outputs back to float32 if manually enabling mixed precision for loss calculation
- if model_dtype == tf.float16:
- logits = tf.cast(logits, tf.float32)
- return logits
- def compute_eval_metrics(logits, dup_mask, val_batch_size, K):
- """
- Constructs the graph to compute Hit Rate and NDCG
- """
- # Replace duplicate (uid, iid) pairs with -inf
- logits = logits * (1. - dup_mask)
- logits = logits + (dup_mask * logits.dtype.min)
- # Reshape tensors so that each row corresponds with a user
- logits_by_user = tf.reshape(logits, [-1, val_batch_size])
- dup_mask_by_user = tf.cast(tf.reshape(logits, [-1, val_batch_size]), tf.bool)
- # Get the topk items for each user
- top_item_indices = tf.math.top_k(logits_by_user, K)[1]
- # Check that the positive sample (last index) is in the top K
- is_positive = tf.cast(tf.equal(top_item_indices, val_batch_size-1), tf.int32)
- found_positive = tf.reduce_sum(is_positive, axis=1)
- # Extract the rankings of the positive samples
- positive_ranks = tf.reduce_sum(is_positive * tf.expand_dims(tf.range(K), 0), axis=1)
- dcg = tf.log(2.) / tf.log(tf.cast(positive_ranks, tf.float32) + 2)
- dcg *= tf.cast(found_positive, dcg.dtype)
- return found_positive, dcg
- def ncf_model_ops(users,
- items,
- labels,
- dup_mask,
- params,
- mode='TRAIN'):
- """
- Constructs the training and evaluation graphs
- """
- # Validation params
- val_batch_size = params['val_batch_size']
- K = params['top_k']
- # Training params
- learning_rate = params['learning_rate']
- beta_1 = params['beta_1']
- beta_2 = params['beta_2']
- epsilon = params['epsilon']
- # Model params
- fp16 = False
- nb_users = params['num_users']
- nb_items = params['num_items']
- mf_dim = params['num_factors']
- mf_reg = params['mf_reg']
- mlp_layer_sizes = params['layer_sizes']
- mlp_layer_regs = params['layer_regs']
- dropout = params['dropout']
- sigmoid = False #params['sigmoid']
- loss_scale = params['loss_scale']
- model_dtype = tf.float16 if fp16 else tf.float32
- # If manually enabling mixed precision, use the custom variable getter
- custom_getter = None if not fp16 else float32_variable_storage_getter
- # Allow soft device placement
- with tf.device(None), \
- tf.variable_scope('neumf', custom_getter=custom_getter):
- # Model graph
- logits = neural_mf(
- users,
- items,
- model_dtype,
- nb_users,
- nb_items,
- mf_dim,
- mf_reg,
- mlp_layer_sizes,
- mlp_layer_regs,
- dropout,
- sigmoid
- )
- logits = tf.squeeze(logits)
- if mode == 'INFERENCE':
- return logits
- # Evaluation Ops
- found_positive, dcg = compute_eval_metrics(logits, dup_mask, val_batch_size, K)
- # Metrics
- hit_rate = tf.metrics.mean(found_positive, name='hit_rate')
- ndcg = tf.metrics.mean(dcg, name='ndcg')
- eval_op = tf.group(hit_rate[1], ndcg[1])
- if mode == 'EVAL':
- return hit_rate[0], ndcg[0], eval_op, None
- # Labels
- labels = tf.reshape(labels, [-1, 1])
- logits = tf.reshape(logits, [-1, 1])
- # Use adaptive momentum optimizer
- optimizer = tf.train.AdamOptimizer(
- learning_rate=learning_rate,
- beta1=beta_1, beta2=beta_2,
- epsilon=epsilon)
- loss = tf.losses.sigmoid_cross_entropy(
- labels,
- logits,
- reduction=tf.losses.Reduction.MEAN)
- # Apply loss scaling if manually enabling mixed precision
- if fp16:
- if loss_scale is None:
- loss_scale_manager = tf.contrib.mixed_precision.ExponentialUpdateLossScaleManager(2**32, 1000)
- else:
- loss_scale_manager = tf.contrib.mixed_precision.FixedLossScaleManager(loss_scale)
- optimizer = tf.contrib.mixed_precision.LossScaleOptimizer(optimizer, loss_scale_manager)
- # Horovod wrapper for distributed training
- optimizer = hvd.DistributedOptimizer(optimizer)
- # Update ops
- global_step = tf.train.get_global_step()
- train_op = optimizer.minimize(loss, global_step=global_step)
- return hit_rate[0], ndcg[0], eval_op, train_op
|