Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dictionary of tensors input for Keras Functional API TensorFlow 2.0

I have a dataset of dictionary of tensors, and the following model defined using the subclassing API:

class Model(tf.keras.Model):

  def __init__(self):
    super().__init__()
    self._movie_full_dense = tf.keras.layers.Dense(
        units=40, activation=tf.keras.layers.Activation("relu"))
    self._user_full_dense = tf.keras.layers.Dense(
        units=40, activation=tf.keras.layers.Activation("relu"))
    self._full_dense = tf.keras.layers.Dense(
        units=1, activation=tf.keras.layers.Activation("sigmoid"))

  def call(self, features):
    movie_embedding = self._movie_full_dense(features['movie'])
    user_embedding = self._user_full_dense(features['user'])
    combined = tf.concat([movie_embedding, user_embedding], 1)
    output = self._full_dense(combined)
    return output

I want to implement it using the functional API. but I do not know how to define the inputs? Namely, what is the functional equivalent for the following?

self._movie_full_dense(features['movie'])
like image 364
Mishael Rosenthal Avatar asked Oct 02 '19 21:10

Mishael Rosenthal


1 Answers

import tensorflow as tf

print(tf.version.VERSION)

toy_data = {'movie': [[0], [1], [0], [1]], 'user': [[10], [12], [12], [10]]}
dataset = tf.data.Dataset.from_tensor_slices(toy_data).batch(2)

for x in dataset:
    print(x)

def make_model():
    inp_movie = tf.keras.Input(shape=(1,))
    inp_user = tf.keras.Input(shape=(1,))
    movie_embedding = tf.keras.layers.Dense(
            units=40, activation=tf.keras.layers.Activation("relu"))(inp_movie)
    user_embedding = tf.keras.layers.Dense(
            units=40, activation=tf.keras.layers.Activation("relu"))(inp_user)
    combined = tf.concat([movie_embedding, user_embedding], 1)
    output = tf.keras.layers.Dense(
            units=1, activation=tf.keras.layers.Activation("sigmoid"))(combined)
    model = tf.keras.Model(inputs=[inp_movie, inp_user], outputs=output)
    return model

model = make_model()

for x in dataset:
    print(model(x))

This works. Beware that the iterable you pass to the inputs argument of the tf.keras.Model call has to be sorted in the same order as the dictionary you will use, which is sorted by its keys, movie then user. So using inputs={'a': inp_movie, 'b': inp_user} or inputs={'movie': inp_movie, 'user': inp_user} also works, while inputs=[inp_user, inp_movie] won't.

You can use this code to test this kind of interaction:

def make_test_model():
    inp_movie = tf.keras.Input(shape=(1,))
    inp_user = tf.keras.Input(shape=(1,))
    model = tf.keras.Model(inputs={'a': inp_movie, 'b': inp_user}, outputs=inp_movie)
    return model

def make_test_model_2():
    inp_movie = tf.keras.Input(shape=(1,))
    inp_user = tf.keras.Input(shape=(1,))
    model = tf.keras.Model(inputs=[inp_user, inp_movie], outputs=inp_movie)
    return model

model_test = make_test_model()
model_test_2 = make_test_model_2()

for x in dataset:
    print(model_test(x))
for x in dataset:
    print(model_test_2(x))

You can also name the Input layers using the keys of your dictonary, and give as the inputs argument a list of Input layers sorted by the layers names. This allows you to add or remove inputs in your model without having to worry about rewriting your inputs argument each time. So this is what I would do:

def make_model_2():
    input_list = []
    inp_movie = tf.keras.Input(shape=(1,), name='movie')
    input_list.append(inp_movie)
    inp_user = tf.keras.Input(shape=(1,), name='user')
    input_list.append(inp_user)
    movie_embedding = tf.keras.layers.Dense(
            units=40, activation=tf.keras.layers.Activation("relu"))(inp_movie)
    user_embedding = tf.keras.layers.Dense(
            units=40, activation=tf.keras.layers.Activation("relu"))(inp_user)
    combined = tf.concat([movie_embedding, user_embedding], 1)
    output = tf.keras.layers.Dense(
            units=1, activation=tf.keras.layers.Activation("sigmoid"))(combined)
    input_list.sort(key=lambda inp: inp._keras_history.layer.name)
    model = tf.keras.Model(inputs=input_list, outputs=output)
    return model

Here is a way to test that it works:

def make_test_model_3(boolean):
    input_list = []
    inp_movie = tf.keras.Input(shape=(1,), name='movie')
    inp_user = tf.keras.Input(shape=(1,), name='user')
    if boolean:
        input_list.append(inp_movie)
        input_list.append(inp_user)
    else:
        input_list.append(inp_user)
        input_list.append(inp_movie)
    input_list.sort(key=lambda inp: inp._keras_history.layer.name)
    model = tf.keras.Model(inputs=input_list, outputs=inp_movie)
    return model

model_test_3_0= make_test_model_3(True)
model_test_3_1= make_test_model_3(False)

for x in dataset:
    print(model_test_3_0(x))
for x in dataset:
    print(model_test_3_1(x))

Edit 2020-02-20:

make_model does not work with tf2.1.0, but make_model_2 still does. I have oppened an issue on GitHub about this backward incompatibility. Here is the link if you are interested. Recall that both functions work if you plan to stay on tf2.0.0.

like image 164
Guillermo Durand Avatar answered Oct 12 '22 23:10

Guillermo Durand