Search code examples
tensorflow2.0tensorflow-probability

Learning a Categorical Variable with TensorFlow Probability


I would like to use TFP to write a neural network where the output are the probabilities of a categorical variable with 3 classes, and train it using the negative log-likelihood.

As I'm moving my first steps with TF and TFP, I started with a toy model where the input layer has only 1 unit receiving a null input, and the output layer has 3 units with softmax activation function. The idea is that the biases should learn (up to an additive constant) the log of the probabilities.

Here below is my code, true_p are the true parameters I use to generate the data and I would like to learn, while learned_p is what I get from the NN.

import numpy as np
import tensorflow as tf
from tensorflow import keras
from functions import nll

from tensorflow.keras.optimizers import SGD
import tensorflow.keras.layers as layers
import tensorflow_probability as tfp
tfd = tfp.distributions

# params
true_p = np.array([0.1, 0.7, 0.2])
n_train = 1000

# training data
x_train = np.array(np.zeros(n_train)).reshape((n_train,))
y_train = np.array(np.random.choice(len(true_p), size=n_train, p=true_p)).reshape((n_train,))

# model
input_layer = layers.Input(shape=(1,))
p_layer = layers.Dense(len(true_p), activation=tf.nn.softmax)(input_layer)
p_y = tfp.layers.DistributionLambda(tfd.Categorical)(p_layer)

model_p = keras.models.Model(inputs=input_layer, outputs=p_y)
model_p.compile(SGD(), loss=nll)

# training
hist_p = model_p.fit(x=x_train, y=y_train, batch_size=100, epochs=3000, verbose=0)

# check result
learned_p = np.round(model_p.layers[1].call(tf.constant([0], shape=(1, 1))).numpy(), 3)
learned_p

With this setup, I get the result:

>>> learned_p
array([[0.005, 0.989, 0.006]], dtype=float32)

I over-estimate the second category, and can't really distinguish between the first and the third one. What's worst, if I plot the probabilities at the end of each epoch, it looks like they are converging monotonically to the vector [0,1,0], which doesn't make sense (it seems to me the gradient should push in the opposite direction once I start to over-estimate).

I really can't figure out what's going on here, but have the feeling I'm doing something plain wrong. Any idea? Thank you for your help!

For the record, I also tried using other optimizers like Adam or Adagrad playing with the hyper-params, but with no luck.

I'm using Python 3.7.9, TensorFlow 2.3.1 and TensorFlow probability 0.11.1


Solution

  • I believe the default argument to Categorical is not the vector of probabilities, but the vector of logits (values you'd take softmax of to get probabilities). This is to help maintain precision in internal Categorical computations like log_prob. I think you can simply eliminate the softmax activation function and it should work. Please update if it doesn't!

    EDIT: alternatively you can replace the tfd.Categorical with

    lambda p: tfd.Categorical(probs=p)

    but you'll lose the aforementioned precision gains. Just wanted to clarify that passing probs is an option, just not the default.