Skip to content

Commit eb58278

Browse files
authored
TensorFlow v1 to v2 migration for NCF models (#668)
1 parent cb2a0b4 commit eb58278

File tree

6 files changed

+328
-326
lines changed

6 files changed

+328
-326
lines changed

cornac/models/ncf/backend_tf.py

Lines changed: 84 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,8 @@
1313
# limitations under the License.
1414
# ============================================================================
1515

16-
import warnings
1716

18-
# disable annoying tensorflow deprecated API warnings
19-
warnings.filterwarnings("ignore", category=UserWarning)
20-
21-
import tensorflow.compat.v1 as tf
22-
23-
tf.logging.set_verbosity(tf.logging.ERROR)
24-
tf.disable_v2_behavior()
17+
import tensorflow as tf
2518

2619

2720
act_functions = {
@@ -35,88 +28,98 @@
3528
}
3629

3730

38-
def loss_fn(labels, logits):
39-
cross_entropy = tf.reduce_mean(
40-
tf.nn.sigmoid_cross_entropy_with_logits(labels=labels, logits=logits)
41-
)
42-
reg_loss = tf.losses.get_regularization_loss()
43-
return cross_entropy + reg_loss
44-
45-
46-
def train_fn(loss, learning_rate, learner):
31+
def get_optimizer(learning_rate, learner):
4732
if learner.lower() == "adagrad":
48-
opt = tf.train.AdagradOptimizer(learning_rate=learning_rate, name="optimizer")
33+
return tf.keras.optimizers.Adagrad(learning_rate=learning_rate)
4934
elif learner.lower() == "rmsprop":
50-
opt = tf.train.RMSPropOptimizer(learning_rate=learning_rate, name="optimizer")
35+
return tf.keras.optimizers.RMSprop(learning_rate=learning_rate)
5136
elif learner.lower() == "adam":
52-
opt = tf.train.AdamOptimizer(learning_rate=learning_rate, name="optimizer")
37+
return tf.keras.optimizers.Adam(learning_rate=learning_rate)
5338
else:
54-
opt = tf.train.GradientDescentOptimizer(
55-
learning_rate=learning_rate, name="optimizer"
56-
)
57-
58-
return opt.minimize(loss)
59-
60-
61-
def emb(
62-
uid, iid, num_users, num_items, emb_size, reg_user, reg_item, seed=None, scope="emb"
63-
):
64-
with tf.variable_scope(scope):
65-
user_emb = tf.get_variable(
66-
"user_emb",
67-
shape=[num_users, emb_size],
68-
dtype=tf.float32,
69-
initializer=tf.random_normal_initializer(stddev=0.01, seed=seed),
70-
regularizer=tf.keras.regularizers.L2(reg_user),
39+
return tf.keras.optimizers.SGD(learning_rate=learning_rate)
40+
41+
42+
class GMFLayer(tf.keras.layers.Layer):
43+
def __init__(self, num_users, num_items, emb_size, reg_user, reg_item, seed=None, **kwargs):
44+
super(GMFLayer, self).__init__(**kwargs)
45+
self.num_users = num_users
46+
self.num_items = num_items
47+
self.emb_size = emb_size
48+
self.reg_user = reg_user
49+
self.reg_item = reg_item
50+
self.seed = seed
51+
52+
# Initialize embeddings
53+
self.user_embedding = tf.keras.layers.Embedding(
54+
num_users,
55+
emb_size,
56+
embeddings_initializer=tf.keras.initializers.RandomNormal(stddev=0.01, seed=seed),
57+
embeddings_regularizer=tf.keras.regularizers.L2(reg_user),
58+
name="user_embedding"
7159
)
72-
item_emb = tf.get_variable(
73-
"item_emb",
74-
shape=[num_items, emb_size],
75-
dtype=tf.float32,
76-
initializer=tf.random_normal_initializer(stddev=0.01, seed=seed),
77-
regularizer=tf.keras.regularizers.L2(reg_item),
78-
)
79-
80-
return tf.nn.embedding_lookup(user_emb, uid), tf.nn.embedding_lookup(item_emb, iid)
81-
82-
83-
def gmf(uid, iid, num_users, num_items, emb_size, reg_user, reg_item, seed=None):
84-
with tf.variable_scope("GMF") as scope:
85-
user_emb, item_emb = emb(
86-
uid=uid,
87-
iid=iid,
88-
num_users=num_users,
89-
num_items=num_items,
90-
emb_size=emb_size,
91-
reg_user=reg_user,
92-
reg_item=reg_item,
93-
seed=seed,
94-
scope=scope,
60+
61+
self.item_embedding = tf.keras.layers.Embedding(
62+
num_items,
63+
emb_size,
64+
embeddings_initializer=tf.keras.initializers.RandomNormal(stddev=0.01, seed=seed),
65+
embeddings_regularizer=tf.keras.regularizers.L2(reg_item),
66+
name="item_embedding"
9567
)
68+
69+
def call(self, inputs):
70+
user_ids, item_ids = inputs
71+
user_emb = self.user_embedding(user_ids)
72+
item_emb = self.item_embedding(item_ids)
9673
return tf.multiply(user_emb, item_emb)
9774

9875

99-
def mlp(uid, iid, num_users, num_items, layers, reg_layers, act_fn, seed=None):
100-
with tf.variable_scope("MLP") as scope:
101-
user_emb, item_emb = emb(
102-
uid=uid,
103-
iid=iid,
104-
num_users=num_users,
105-
num_items=num_items,
106-
emb_size=int(layers[0] / 2),
107-
reg_user=reg_layers[0],
108-
reg_item=reg_layers[0],
109-
seed=seed,
110-
scope=scope,
76+
class MLPLayer(tf.keras.layers.Layer):
77+
def __init__(self, num_users, num_items, layers, reg_layers, act_fn, seed=None, **kwargs):
78+
super(MLPLayer, self).__init__(**kwargs)
79+
self.num_users = num_users
80+
self.num_items = num_items
81+
self.layers = layers
82+
self.reg_layers = reg_layers
83+
self.act_fn = act_fn
84+
self.seed = seed
85+
86+
# Initialize embeddings
87+
self.user_embedding = tf.keras.layers.Embedding(
88+
num_users,
89+
int(layers[0] / 2),
90+
embeddings_initializer=tf.keras.initializers.RandomNormal(stddev=0.01, seed=seed),
91+
embeddings_regularizer=tf.keras.regularizers.L2(reg_layers[0]),
92+
name="user_embedding"
11193
)
112-
interaction = tf.concat([user_emb, item_emb], axis=-1)
113-
for i, layer in enumerate(layers[1:]):
114-
interaction = tf.layers.dense(
115-
interaction,
116-
units=layer,
117-
name="layer{}".format(i + 1),
118-
activation=act_functions.get(act_fn, tf.nn.relu),
119-
kernel_initializer=tf.initializers.lecun_uniform(seed),
120-
kernel_regularizer=tf.keras.regularizers.L2(reg_layers[i + 1]),
94+
95+
self.item_embedding = tf.keras.layers.Embedding(
96+
num_items,
97+
int(layers[0] / 2),
98+
embeddings_initializer=tf.keras.initializers.RandomNormal(stddev=0.01, seed=seed),
99+
embeddings_regularizer=tf.keras.regularizers.L2(reg_layers[0]),
100+
name="item_embedding"
101+
)
102+
103+
# Define dense layers
104+
self.dense_layers = []
105+
for i, layer_size in enumerate(layers[1:]):
106+
self.dense_layers.append(
107+
tf.keras.layers.Dense(
108+
layer_size,
109+
activation=act_functions.get(act_fn, tf.nn.relu),
110+
kernel_initializer=tf.keras.initializers.LecunUniform(seed=seed),
111+
kernel_regularizer=tf.keras.regularizers.L2(reg_layers[i + 1]),
112+
name=f"layer{i+1}"
113+
)
121114
)
115+
116+
def call(self, inputs):
117+
user_ids, item_ids = inputs
118+
user_emb = self.user_embedding(user_ids)
119+
item_emb = self.item_embedding(item_ids)
120+
interaction = tf.concat([user_emb, item_emb], axis=-1)
121+
122+
for layer in self.dense_layers:
123+
interaction = layer(interaction)
124+
122125
return interaction

cornac/models/ncf/recom_gmf.py

Lines changed: 39 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -111,55 +111,45 @@ def __init__(
111111
########################
112112
## TensorFlow backend ##
113113
########################
114-
def _build_graph_tf(self):
115-
import tensorflow.compat.v1 as tf
116-
from .backend_tf import gmf, loss_fn, train_fn
117-
118-
self.graph = tf.Graph()
119-
with self.graph.as_default():
120-
tf.set_random_seed(self.seed)
121-
122-
self.user_id = tf.placeholder(shape=[None], dtype=tf.int32, name="user_id")
123-
self.item_id = tf.placeholder(shape=[None], dtype=tf.int32, name="item_id")
124-
self.labels = tf.placeholder(
125-
shape=[None, 1], dtype=tf.float32, name="labels"
126-
)
127-
128-
self.interaction = gmf(
129-
uid=self.user_id,
130-
iid=self.item_id,
131-
num_users=self.num_users,
132-
num_items=self.num_items,
133-
emb_size=self.num_factors,
134-
reg_user=self.reg,
135-
reg_item=self.reg,
136-
seed=self.seed,
137-
)
138-
139-
logits = tf.layers.dense(
140-
self.interaction,
141-
units=1,
142-
name="logits",
143-
kernel_initializer=tf.initializers.lecun_uniform(self.seed),
144-
)
145-
self.prediction = tf.nn.sigmoid(logits)
146-
147-
self.loss = loss_fn(labels=self.labels, logits=logits)
148-
self.train_op = train_fn(
149-
self.loss, learning_rate=self.lr, learner=self.learner
150-
)
151-
152-
self.initializer = tf.global_variables_initializer()
153-
self.saver = tf.train.Saver()
154-
155-
self._sess_init_tf()
156-
157-
def _score_tf(self, user_idx, item_idx):
158-
feed_dict = {
159-
self.user_id: [user_idx],
160-
self.item_id: np.arange(self.num_items) if item_idx is None else [item_idx],
161-
}
162-
return self.sess.run(self.prediction, feed_dict=feed_dict)
114+
def _build_model_tf(self):
115+
import tensorflow as tf
116+
from .backend_tf import GMFLayer
117+
118+
# Define inputs
119+
user_input = tf.keras.layers.Input(shape=(1,), dtype=tf.int32, name="user_input")
120+
item_input = tf.keras.layers.Input(shape=(1,), dtype=tf.int32, name="item_input")
121+
122+
# GMF layer
123+
gmf_layer = GMFLayer(
124+
num_users=self.num_users,
125+
num_items=self.num_items,
126+
emb_size=self.num_factors,
127+
reg_user=self.reg,
128+
reg_item=self.reg,
129+
seed=self.seed,
130+
name="gmf_layer"
131+
)
132+
133+
# Get embeddings and element-wise product
134+
gmf_vector = gmf_layer([user_input, item_input])
135+
136+
# Output layer
137+
logits = tf.keras.layers.Dense(
138+
1,
139+
kernel_initializer=tf.keras.initializers.LecunUniform(seed=self.seed),
140+
name="logits"
141+
)(gmf_vector)
142+
143+
prediction = tf.keras.layers.Activation('sigmoid', name="prediction")(logits)
144+
145+
# Create model with both logits and prediction outputs
146+
model = tf.keras.Model(
147+
inputs=[user_input, item_input],
148+
outputs=prediction,
149+
name="GMF"
150+
)
151+
152+
return model
163153

164154
#####################
165155
## PyTorch backend ##

cornac/models/ncf/recom_mlp.py

Lines changed: 39 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -116,60 +116,45 @@ def __init__(
116116
########################
117117
## TensorFlow backend ##
118118
########################
119-
def _build_graph_tf(self):
120-
import tensorflow.compat.v1 as tf
121-
from .backend_tf import mlp, loss_fn, train_fn
122-
123-
self.graph = tf.Graph()
124-
with self.graph.as_default():
125-
tf.set_random_seed(self.seed)
126-
127-
self.user_id = tf.placeholder(shape=[None], dtype=tf.int32, name="user_id")
128-
self.item_id = tf.placeholder(shape=[None], dtype=tf.int32, name="item_id")
129-
self.labels = tf.placeholder(
130-
shape=[None, 1], dtype=tf.float32, name="labels"
131-
)
132-
133-
self.interaction = mlp(
134-
uid=self.user_id,
135-
iid=self.item_id,
136-
num_users=self.num_users,
137-
num_items=self.num_items,
138-
layers=self.layers,
139-
reg_layers=[self.reg] * len(self.layers),
140-
act_fn=self.act_fn,
141-
seed=self.seed,
142-
)
143-
logits = tf.layers.dense(
144-
self.interaction,
145-
units=1,
146-
name="logits",
147-
kernel_initializer=tf.initializers.lecun_uniform(self.seed),
148-
)
149-
self.prediction = tf.nn.sigmoid(logits)
150-
151-
self.loss = loss_fn(labels=self.labels, logits=logits)
152-
self.train_op = train_fn(
153-
self.loss, learning_rate=self.lr, learner=self.learner
154-
)
155-
156-
self.initializer = tf.global_variables_initializer()
157-
self.saver = tf.train.Saver()
158-
159-
self._sess_init_tf()
160-
161-
def _score_tf(self, user_idx, item_idx):
162-
if item_idx is None:
163-
feed_dict = {
164-
self.user_id: np.ones(self.num_items) * user_idx,
165-
self.item_id: np.arange(self.num_items),
166-
}
167-
else:
168-
feed_dict = {
169-
self.user_id: [user_idx],
170-
self.item_id: [item_idx],
171-
}
172-
return self.sess.run(self.prediction, feed_dict=feed_dict)
119+
def _build_model_tf(self):
120+
import tensorflow as tf
121+
from .backend_tf import MLPLayer
122+
123+
# Define inputs
124+
user_input = tf.keras.layers.Input(shape=(1,), dtype=tf.int32, name="user_input")
125+
item_input = tf.keras.layers.Input(shape=(1,), dtype=tf.int32, name="item_input")
126+
127+
# MLP layer
128+
mlp_layer = MLPLayer(
129+
num_users=self.num_users,
130+
num_items=self.num_items,
131+
layers=self.layers,
132+
reg_layers=[self.reg] * len(self.layers),
133+
act_fn=self.act_fn,
134+
seed=self.seed,
135+
name="mlp_layer"
136+
)
137+
138+
# Get MLP vector
139+
mlp_vector = mlp_layer([user_input, item_input])
140+
141+
# Output layer
142+
logits = tf.keras.layers.Dense(
143+
1,
144+
kernel_initializer=tf.keras.initializers.LecunUniform(seed=self.seed),
145+
name="logits"
146+
)(mlp_vector)
147+
148+
prediction = tf.keras.layers.Activation('sigmoid', name="prediction")(logits)
149+
150+
# Create model
151+
model = tf.keras.Model(
152+
inputs=[user_input, item_input],
153+
outputs=prediction,
154+
name="MLP"
155+
)
156+
157+
return model
173158

174159
#####################
175160
## PyTorch backend ##

0 commit comments

Comments
 (0)