easy_rec/python/model/dropoutnet.py (179 lines of code) (raw):
# -*- encoding:utf-8 -*-
# Copyright (c) Alibaba, Inc. and its affiliates.
import tensorflow as tf
from easy_rec.python.layers import dnn
from easy_rec.python.loss.pairwise_loss import pairwise_loss
from easy_rec.python.model.easy_rec_model import EasyRecModel
from easy_rec.python.protos.loss_pb2 import LossType
from easy_rec.python.utils.proto_util import copy_obj
from easy_rec.python.protos.dropoutnet_pb2 import DropoutNet as DropoutNetConfig # NOQA
from easy_rec.python.loss.softmax_loss_with_negative_mining import softmax_loss_with_negative_mining # NOQA
from easy_rec.python.protos.dropoutnet_pb2 import DropoutNet as DropoutNetConfig # NOQA
if tf.__version__ >= '2.0':
tf = tf.compat.v1
losses = tf.losses
def cosine_similarity(user_emb, item_emb):
user_item_sim = tf.reduce_sum(
tf.multiply(user_emb, item_emb), axis=1, name='cosine')
return user_item_sim
def bernoulli_dropout(x, rate, training=False):
if rate == 0.0 or not training:
return x
keep_rate = 1.0 - rate
dist = tf.distributions.Bernoulli(probs=keep_rate, dtype=x.dtype)
mask = dist.sample(sample_shape=tf.stack([tf.shape(x)[0], 1]))
return x * mask / keep_rate
class DropoutNet(EasyRecModel):
def __init__(self,
model_config,
feature_configs,
features,
labels=None,
is_training=False):
super(DropoutNet, self).__init__(model_config, feature_configs, features,
labels, is_training)
self._losses = self._model_config.losses
assert self._model_config.WhichOneof(
'model'
) == 'dropoutnet', 'invalid model config: %s' % self._model_config.WhichOneof(
'model')
self._model_config = self._model_config.dropoutnet
assert isinstance(self._model_config, DropoutNetConfig)
# copy_obj so that any modification will not affect original config
self.user_content_layers = copy_obj(self._model_config.user_content)
self.user_preference_layers = copy_obj(self._model_config.user_preference)
self.user_tower_layers = copy_obj(self._model_config.user_tower)
self.user_content_feature, self.user_preference_feature = None, None
if self._input_layer.has_group('user_content'):
self.user_content_feature, _ = self._input_layer(self._feature_dict,
'user_content')
if self._input_layer.has_group('user_preference'):
self.user_preference_feature, _ = self._input_layer(
self._feature_dict, 'user_preference')
assert self.user_content_feature is not None or self.user_preference_feature is not None, 'no user feature'
# copy_obj so that any modification will not affect original config
self.item_content_layers = copy_obj(self._model_config.item_content)
self.item_preference_layers = copy_obj(self._model_config.item_preference)
self.item_tower_layers = copy_obj(self._model_config.item_tower)
self.item_content_feature, self.item_preference_feature = None, None
if self._input_layer.has_group('item_content'):
self.item_content_feature, _ = self._input_layer(self._feature_dict,
'item_content')
if self._input_layer.has_group('item_preference'):
self.item_preference_feature, _ = self._input_layer(
self._feature_dict, 'item_preference')
assert self.item_content_feature is not None or self.item_preference_feature is not None, 'no item feature'
def build_predict_graph(self):
num_user_dnn_layer = len(self.user_tower_layers.hidden_units)
last_user_hidden = self.user_tower_layers.hidden_units.pop()
num_item_dnn_layer = len(self.item_tower_layers.hidden_units)
last_item_hidden = self.item_tower_layers.hidden_units.pop()
assert last_item_hidden == last_user_hidden, 'the last hidden layer size of user tower and item tower must be equal'
# --------------------------build user tower-----------------------------------
with tf.name_scope('user_tower'):
user_features = []
if self.user_content_feature is not None:
user_content_dnn = dnn.DNN(self.user_content_layers, self._l2_reg,
'user_content', self._is_training)
content_feature = user_content_dnn(self.user_content_feature)
user_features.append(content_feature)
if self.user_preference_feature is not None:
user_prefer_feature = bernoulli_dropout(
self.user_preference_feature, self._model_config.user_dropout_rate,
self._is_training)
user_prefer_dnn = dnn.DNN(self.user_preference_layers, self._l2_reg,
'user_preference', self._is_training)
prefer_feature = user_prefer_dnn(user_prefer_feature)
user_features.append(prefer_feature)
user_tower_feature = tf.concat(user_features, axis=-1)
user_dnn = dnn.DNN(self.user_tower_layers, self._l2_reg, 'user_dnn',
self._is_training)
user_hidden = user_dnn(user_tower_feature)
user_tower_emb = tf.layers.dense(
inputs=user_hidden,
units=last_user_hidden,
kernel_regularizer=self._l2_reg,
name='user_dnn/dnn_%d' % (num_user_dnn_layer - 1))
# --------------------------build item tower-----------------------------------
with tf.name_scope('item_tower'):
item_features = []
if self.item_content_feature is not None:
item_content_dnn = dnn.DNN(self.item_content_layers, self._l2_reg,
'item_content', self._is_training)
content_feature = item_content_dnn(self.item_content_feature)
item_features.append(content_feature)
if self.item_preference_feature is not None:
item_prefer_feature = bernoulli_dropout(
self.item_preference_feature, self._model_config.item_dropout_rate,
self._is_training)
item_prefer_dnn = dnn.DNN(self.item_preference_layers, self._l2_reg,
'item_preference', self._is_training)
prefer_feature = item_prefer_dnn(item_prefer_feature)
item_features.append(prefer_feature)
item_tower_feature = tf.concat(item_features, axis=-1)
item_dnn = dnn.DNN(self.item_tower_layers, self._l2_reg, 'item_dnn',
self._is_training)
item_hidden = item_dnn(item_tower_feature)
item_tower_emb = tf.layers.dense(
inputs=item_hidden,
units=last_item_hidden,
kernel_regularizer=self._l2_reg,
name='item_dnn/dnn_%d' % (num_item_dnn_layer - 1))
user_emb = tf.nn.l2_normalize(user_tower_emb, axis=-1)
item_emb = tf.nn.l2_normalize(item_tower_emb, axis=-1)
cosine = cosine_similarity(user_emb, item_emb)
self._prediction_dict['similarity'] = cosine
self._prediction_dict['float_user_emb'] = user_emb
self._prediction_dict['float_item_emb'] = item_emb
self._prediction_dict['user_emb'] = tf.reduce_join(
tf.as_string(user_emb), axis=-1, separator=',')
self._prediction_dict['item_emb'] = tf.reduce_join(
tf.as_string(item_emb), axis=-1, separator=',')
return self._prediction_dict
def build_loss_graph(self):
labels = list(self._labels.values())[0]
logits = self._prediction_dict['similarity']
for loss in self._losses:
if loss.loss_type == LossType.SOFTMAX_CROSS_ENTROPY_WITH_NEGATIVE_MINING:
assert self._model_config.HasField(
'softmax_loss'), '`softmax_loss` must be configured'
user_emb = self._prediction_dict['float_user_emb']
item_emb = self._prediction_dict['float_item_emb']
loss_value = softmax_loss_with_negative_mining(
user_emb,
item_emb,
labels,
self._model_config.softmax_loss.num_negative_samples,
embed_normed=True,
weights=self._sample_weight,
margin=self._model_config.softmax_loss.margin,
gamma=self._model_config.softmax_loss.gamma,
t=self._model_config.softmax_loss.coefficient_of_support_vector)
self._loss_dict['softmax_loss'] = loss_value * loss.weight
elif loss.loss_type == LossType.PAIR_WISE_LOSS:
loss_value = pairwise_loss(labels, logits)
self._loss_dict['pairwise_loss'] = loss_value * loss.weight
elif loss.loss_type == LossType.CLASSIFICATION:
loss_value = tf.losses.sigmoid_cross_entropy(labels, logits,
self._sample_weight)
self._loss_dict['sigmoid_loss'] = loss_value * loss.weight
return self._loss_dict
def build_metric_graph(self, eval_config):
from easy_rec.python.core.easyrec_metrics import metrics_tf as metrics
metric_dict = {}
labels = list(self._labels.values())[0]
sim_score = self._prediction_dict['similarity']
prob = tf.nn.sigmoid(sim_score)
predict = tf.greater(prob, 0.5)
for metric in eval_config.metrics_set:
if metric.WhichOneof('metric') == 'auc':
metric_dict['auc'] = metrics.auc(
labels, prob, weights=self._sample_weight)
elif metric.WhichOneof('metric') == 'accuracy':
metric_dict['accuracy'] = metrics.accuracy(
tf.cast(labels, tf.bool), predict, weights=self._sample_weight)
elif metric.WhichOneof('metric') == 'precision':
metric_dict['precision'] = metrics.precision(
labels, predict, weights=self._sample_weight)
elif metric.WhichOneof('metric') == 'recall':
metric_dict['recall'] = metrics.recall(
labels, predict, weights=self._sample_weight)
else:
ValueError('invalid metric type: %s' % str(metric))
return metric_dict
def get_outputs(self):
return ['similarity', 'user_emb', 'item_emb']