Skip to content

Support dynamically added variables #310

@skydoorkai

Description

@skydoorkai

Question:
Do we need to support the two types of keras model (Graph network from Keras Functional API, or subclass model), or just one?

There are two types of keras model:

  1. Graph network from Keras Functional and Sequential APIs.
  2. Subclassed networks are used when a user subclasses the Model class.

More Keras features are supported with graph networks, such as serialization, whole model saving, etc.

Example of functional API:

inputs = tf.keras.Input(shape=(10,))
x = keras.layers.Dense(1)(inputs)
outputs = tf.nn.relu(x)
network = tf.keras.Model(inputs, outputs)

inputs and outputs can be a tensor, or a list of tensors.

Example of subclass model:

class MyModel(keras.Model):
  def __init__(self):
    super(MyModel, self).__init__(name='my_model', dynamic=False)
    self.layer1 = keras.layers.Dense(10, activation='relu')
  def call(self, inputs):
    return self.layer1(inputs)

Functional API knows the input shape, while subclass model does not. In order to use model.call() for subclass model, model.build(input_shapes) need to be called in advance to let keras model knows the input shape. `input_shapes' is a shape or a list of shapes.

ElasticDL user-defined class for keras model definition

Functional API

We used to use functional API for keras model, such as in worker_test.py

class TestModel(object):
    def __init__(self):
        input1 = tf.keras.layers.Input(shape=(1,))
        x1 = tf.keras.layers.Dense(1)(input1)
        self._model = tf.keras.models.Model(input1, x1)

    def get_keras_model(self):
        return self._model

    def output(self, data):
        return self._model.call(data['x'])

    @staticmethod
    def loss(output, data):
        return tf.reduce_mean(tf.square(output - data['y']))

    @staticmethod
    def input_fn(records):
        x_list = []
        y_list = []
        # deserialize
        for r in records:
            parsed = np.frombuffer(r, dtype='float32')
            x_list.append([parsed[0]])
            y_list.append([parsed[1]])
        # batching
        batch_size = len(x_list)
        xs = np.concatenate(x_list, axis=0)
        xs = np.reshape(xs, (batch_size, 1))
        ys = np.concatenate(y_list, axis=0)
        ys = np.reshape(xs, (batch_size, 1))
        return {'x': xs, 'y': ys}

    @staticmethod
    def optimizer(lr=0.1):
        return tf.train.GradientDescentOptimizer(lr)

Subclass model

Later, we changed to subclass model worker_test.py:

class TestModel(tf.keras.Model):
    def __init__(self):
        super(TestModel, self).__init__(name='test_model')
        self.dense = tf.keras.layers.Dense(1)

    def call(self, inputs):
        return self.dense(inputs)

    @staticmethod
    def input_shapes():
        return (1, 1)

    @staticmethod
    def input_names():
        return ['x']

    @staticmethod
    def loss(outputs, labels):
        return tf.reduce_mean(tf.square(outputs - labels['y'])) 

    @staticmethod
    def input_fn(records):
        x_list = []
        y_list = []
        # deserialize
        for r in records:
            parsed = np.frombuffer(r, dtype='float32')
            x_list.append([parsed[0]])
            y_list.append([parsed[1]])
        # batching
        batch_size = len(x_list)
        xs = np.concatenate(x_list, axis=0)
        xs = np.reshape(xs, (batch_size, 1))
        ys = np.concatenate(y_list, axis=0)
        ys = np.reshape(xs, (batch_size, 1))
        return {'x': xs, 'y': ys}

    @staticmethod
    def optimizer(lr=0.1):
        return tf.train.GradientDescentOptimizer(lr)

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions