function encoder(opts) {
  const {originalDim, intermediateDim, latentDim} = opts;

  const inputs = tf.input({shape: [originalDim], name: 'encoder_input'});
  const x = tf.layers.dense({units: intermediateDim, activation: 'relu'})
                .apply(inputs);
  const zMean = tf.layers.dense({units: latentDim, name: 'z_mean'}).apply(x);
  const zLogVar =
      tf.layers.dense({units: latentDim, name: 'z_log_var'}).apply(x);

  const z =
      new ZLayer({name: 'z', outputShape: [latentDim]}).apply([zMean, zLogVar]);

  const enc = tf.model({
    inputs: inputs,
    outputs: [zMean, zLogVar, z],
    name: 'encoder',
  });

  // console.log('Encoder Summary');
  // enc.summary();
  return enc;
}
The encoder portion of the model.

@param {object} opts encoder configuration, includnig the following fields:
  - originaDim {number} Length of the input flattened image.
  - intermediateDim {number} Number of units of the intermediate (i.e.,
    hidden) dense layer.
  - latentDim {number} Dimensionality of the latent space (i.e,. z-space).
@param {number} opts.originalDim number of dimensions in the original data.
@param {number} opts.intermediateDim number of dimensions in the bottleneck.
@param {number} opts.latentDim number of dimensions in latent space.
@returns {tf.LayersModel} the encoder model.