Repository: GPflow/GPflow

Issue:
ValueError in multiclass likelihood
Hi,  when I run line 5 of the multiclass notebook:

``` python
m.kern.white.variance.fixed = True
m.Z.fixed = True
_ = m.optimize()
```

I get the following error (stacktrace):

```
python
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-5-e4eebd086840> in <module>()
      1 m.kern.white.variance.fixed = True
      2 m.Z.fixed = True
----> 3 _ = m.optimize()

/Users/danmarthaler/GPflow/GPflow/model.pyc in optimize(self, method, tol, callback, maxiter, **kw)
    207 
    208         if type(method) is str:
--> 209             return self._optimize_np(method, tol, callback, maxiter, **kw)
    210         else:
    211             return self._optimize_tf(method, callback, maxiter, **kw)

/Users/danmarthaler/GPflow/GPflow/model.pyc in _optimize_np(self, method, tol, callback, maxiter, **kw)
    265         """
    266         if self._needs_recompile:
--> 267             self._compile()
    268 
    269         options = dict(disp=True, maxiter=maxiter)

/Users/danmarthaler/GPflow/GPflow/model.pyc in _compile(self, optimizer)
    127         with self.tf_mode():
    128             f = self.build_likelihood() + self.build_prior()
--> 129             g, = tf.gradients(f, self._free_vars)
    130 
    131         self._minusF = tf.neg(f, name='objective')

/usr/local/lib/python2.7/site-packages/tensorflow/python/ops/gradients.pyc in gradients(ys, xs, grad_ys, name, colocate_gradients_with_ops, gate_gradients, aggregation_method)
    476                 # If grad_fn was found, do not use SymbolicGradient even for
    477                 # functions.
--> 478                 in_grads = _AsList(grad_fn(op, *out_grads))
    479               else:
    480                 # For function call ops, we add a 'SymbolicGradient'

/usr/local/lib/python2.7/site-packages/tensorflow/python/ops/math_grad.pyc in _ProdGrad(op, grad)
    128   reduced = math_ops.cast(op.inputs[1], dtypes.int32)
    129   idx = math_ops.range(0, array_ops.rank(op.inputs[0]))
--> 130   other, _ = array_ops.listdiff(idx, reduced)
    131   perm = array_ops.concat(0, [reduced, other])
    132   reduced_num = math_ops.reduce_prod(array_ops.gather(input_shape, reduced))

/usr/local/lib/python2.7/site-packages/tensorflow/python/ops/gen_array_ops.pyc in list_diff(x, y, name)
   1199     idx: A `Tensor` of type `int32`. 1-D. Positions of `x` values preserved in `out`.
   1200   """
-> 1201   result = _op_def_lib.apply_op("ListDiff", x=x, y=y, name=name)
   1202   return _ListDiffOutput._make(result)
   1203 

/usr/local/lib/python2.7/site-packages/tensorflow/python/framework/op_def_library.pyc in apply_op(self, op_type_name, name, **keywords)
    701           op = g.create_op(op_type_name, inputs, output_types, name=scope,
    702                            input_types=input_types, attrs=attr_protos,
--> 703                            op_def=op_def)
    704           outputs = op.outputs
    705           return _Restructure(ops.convert_n_to_tensor(outputs),

/usr/local/lib/python2.7/site-packages/tensorflow/python/framework/ops.pyc in create_op(self, op_type, inputs, dtypes, input_types, name, attrs, op_def, compute_shapes, compute_device)
   2310                     original_op=self._default_original_op, op_def=op_def)
   2311     if compute_shapes:
-> 2312       set_shapes_for_outputs(ret)
   2313     self._add_op(ret)
   2314     self._record_op_seen_by_control_dependencies(ret)

/usr/local/lib/python2.7/site-packages/tensorflow/python/framework/ops.pyc in set_shapes_for_outputs(op)
   1702       raise RuntimeError("No shape function registered for standard op: %s"
   1703                          % op.type)
-> 1704   shapes = shape_func(op)
   1705   if shapes is None:
   1706     raise RuntimeError(

/usr/local/lib/python2.7/site-packages/tensorflow/python/ops/array_ops.pyc in _ListDiffShape(op)
   1979   """Shape function for the ListDiff op."""
   1980   op.inputs[0].get_shape().assert_has_rank(1)
-> 1981   op.inputs[1].get_shape().assert_has_rank(1)
   1982   # TODO(mrry): Indicate that the length falls within an interval?
   1983   return [tensor_shape.vector(None)] * 2

/usr/local/lib/python2.7/site-packages/tensorflow/python/framework/tensor_shape.pyc in assert_has_rank(self, rank)
    619     """
    620     if self.ndims not in (None, rank):
--> 621       raise ValueError("Shape %s must have rank %d" % (self, rank))
    622 
    623   def with_rank(self, rank):

ValueError: Shape () must have rank 1
```

Can anyone replicate this?



Discussion:
Nope, works for me.

Which versions of GPflow and tensorflow are you using?

GPflow 0.2.1
TensorFlow 0.10.0rc0

I git pulled from latest sphinx branch and ran it again with the same error.

On Sat, Aug 13, 2016 at 8:32 AM, James Hensman notifications@github.com
wrote:

> Nope, works for me.
> 
> Which versions of GPflow and tensorflow are you using?
> 
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> https://github.com/GPflow/GPflow/issues/161#issuecomment-239626477, or mute
> the thread
> https://github.com/notifications/unsubscribe-auth/ACQPhW7CpM6ZawvoIrSamRc6Q597E-3Eks5qfeN6gaJpZM4JiqSf
> .

I'll try building tensorflow from source and see...

Dan

> On Aug 13, 2016, at 08:32, James Hensman notifications@github.com wrote:
> 
> Nope, works for me.
> 
> Which versions of GPflow and tensorflow are you using?
> 
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub, or mute the thread.

I suspect a tensorflow upgrade issue. I'm running 0.9.0, I'll try with the 10.0rc. Could you try with 0.9.0 just to make sure it's not a machine-specific issue?

Confirming this as a bug, here's the minimal code to reproduce

``` python
import GPflow
import tensorflow as tf
mu, var = tf.placeholder(tf.float64), tf.placeholder(tf.float64)
Y = tf.placeholder(tf.int32)
lik = GPflow.likelihoods.MultiClass(3)
ve = lik.variational_expectations(mu, var, Y)
tf.gradients(tf.reduce_sum(ve), mu)
```

python 3.5, tensorflow 0.10rc0, osx. The tests all pass except one, noted in #163.

This is coming from reduce_prod op which has changed recently in TensorFlow. It is either a TensorFlow bug or has revealed something strange about our tensor shapes that it was previously willing to tolerate. My guess is that it is a bug with TensorFlow.

In particular the gradient of reduce_prod was the code that changed. 

@jameshensman I will finish triaging this one. 

Okay @alexggmatthews good plan.


Resolving patch:
diff --git a/GPflow/likelihoods.py b/GPflow/likelihoods.py
--- a/GPflow/likelihoods.py
+++ b/GPflow/likelihoods.py
@@ -387,7 +387,7 @@ def prob_is_largest(self, Y, mu, var, gh_x, gh_w):
         cdfs = cdfs * tf.expand_dims(oh_off, 2) + tf.expand_dims(oh_on, 2)
 
         # take the product over the latent functions, and the sum over the GH grid.
-        return tf.matmul(tf.reduce_prod(cdfs, 1), tf.reshape(gh_w/np.sqrt(np.pi), (-1, 1)))
+        return tf.matmul(tf.reduce_prod(cdfs, reduction_indices=[1]), tf.reshape(gh_w/np.sqrt(np.pi), (-1, 1)))
 
 
 class MultiClass(Likelihood):


Test patch:
diff --git a/testing/test_likelihoods.py b/testing/test_likelihoods.py
--- a/testing/test_likelihoods.py
+++ b/testing/test_likelihoods.py
@@ -12,26 +12,27 @@ def __init__(self, likelihood, Y, tolerance):
             six.get_unbound_function(GPflow.likelihoods.Likelihood.predict_density)
 
 
-def getTestSetups(includeMultiClass=True,addNonStandardLinks=False):
+def getTestSetups(includeMultiClass=True, addNonStandardLinks=False):
     test_setups = []
     rng = np.random.RandomState(1)
     for likelihoodClass in GPflow.likelihoods.Likelihood.__subclasses__():
-        if likelihoodClass!=GPflow.likelihoods.MultiClass:
-            test_setups.append( TestSetup( likelihoodClass() , rng.rand(10,2) , 1e-6 ) )
+        if likelihoodClass != GPflow.likelihoods.MultiClass:
+            test_setups.append(TestSetup(likelihoodClass(), rng.rand(10, 2), 1e-6))
         elif includeMultiClass:
-            sample = rng.randn(10,2)
-            #Multiclass needs a less tight tolerance due to presence of clipping.
+            sample = rng.randn(10, 2)
+            # Multiclass needs a less tight tolerance due to presence of clipping.
             tolerance = 1e-3
-            test_setups.append( TestSetup( likelihoodClass(2) ,  np.argmax(sample, 1).reshape(-1,1) , tolerance )  )
-    
-    if addNonStandardLinks:        
-        test_setups.append( TestSetup( GPflow.likelihoods.Poisson(invlink=tf.square) , rng.rand(10,2) , 1e-6 ) )
-        test_setups.append( TestSetup( GPflow.likelihoods.Exponential(invlink=tf.square) , rng.rand(10,2) , 1e-6 ) )
-        test_setups.append( TestSetup( GPflow.likelihoods.Gamma(invlink=tf.square) , rng.rand(10,2) , 1e-6 ) )
-        sigmoid = lambda x : 1./(1 + tf.exp(-x))
-        test_setups.append( TestSetup( GPflow.likelihoods.Bernoulli(invlink=sigmoid) , rng.rand(10,2) , 1e-6 ) )
+            test_setups.append(TestSetup(likelihoodClass(2),  np.argmax(sample, 1).reshape(-1, 1), tolerance))
+
+    if addNonStandardLinks:
+        test_setups.append(TestSetup(GPflow.likelihoods.Poisson(invlink=tf.square), rng.rand(10, 2), 1e-6))
+        test_setups.append(TestSetup(GPflow.likelihoods.Exponential(invlink=tf.square), rng.rand(10, 2), 1e-6))
+        test_setups.append(TestSetup(GPflow.likelihoods.Gamma(invlink=tf.square), rng.rand(10, 2), 1e-6))
+        sigmoid = lambda x: 1./(1 + tf.exp(-x))
+        test_setups.append(TestSetup(GPflow.likelihoods.Bernoulli(invlink=sigmoid), rng.rand(10, 2), 1e-6))
     return test_setups
 
+
 class TestPredictConditional(unittest.TestCase):
     """
     Here we make sure that the conditional_mean and contitional_var functions
@@ -41,29 +42,33 @@ class TestPredictConditional(unittest.TestCase):
     def setUp(self):
         tf.reset_default_graph()
         self.test_setups = getTestSetups(addNonStandardLinks=True)
-        
+
         self.x = tf.placeholder('float64')
         for test_setup in self.test_setups:
             test_setup.likelihood.make_tf_array(self.x)
 
         self.F = tf.placeholder(tf.float64)
         rng = np.random.RandomState(0)
-        self.F_data = rng.randn(10,2)
+        self.F_data = rng.randn(10, 2)
 
     def test_mean(self):
         for test_setup in self.test_setups:
             l = test_setup.likelihood
             with l.tf_mode():
-                mu1 = tf.Session().run(l.conditional_mean(self.F), feed_dict={self.x: l.get_free_state(), self.F:self.F_data})
-                mu2, _ = tf.Session().run(l.predict_mean_and_var(self.F, self.F * 0), feed_dict={self.x: l.get_free_state(), self.F:self.F_data})
+                mu1 = tf.Session().run(l.conditional_mean(self.F),
+                                       feed_dict={self.x: l.get_free_state(), self.F: self.F_data})
+                mu2, _ = tf.Session().run(l.predict_mean_and_var(self.F, self.F * 0),
+                                          feed_dict={self.x: l.get_free_state(), self.F: self.F_data})
             self.assertTrue(np.allclose(mu1, mu2, test_setup.tolerance, test_setup.tolerance))
 
     def test_variance(self):
         for test_setup in self.test_setups:
             l = test_setup.likelihood
             with l.tf_mode():
-                v1 = tf.Session().run(l.conditional_variance(self.F), feed_dict={self.x: l.get_free_state(), self.F:self.F_data})
-                v2 = tf.Session().run(l.predict_mean_and_var(self.F, self.F * 0)[1], feed_dict={self.x: l.get_free_state(), self.F:self.F_data})   
+                v1 = tf.Session().run(l.conditional_variance(self.F),
+                                      feed_dict={self.x: l.get_free_state(), self.F: self.F_data})
+                v2 = tf.Session().run(l.predict_mean_and_var(self.F, self.F * 0)[1],
+                                      feed_dict={self.x: l.get_free_state(), self.F: self.F_data})
             self.assertTrue(np.allclose(v1, v2, test_setup.tolerance, test_setup.tolerance))
 
     def test_var_exp(self):
@@ -75,10 +80,12 @@ def test_var_exp(self):
             l = test_setup.likelihood
             y = test_setup.Y
             with l.tf_mode():
-                r1 = tf.Session().run(l.logp(self.F, y), feed_dict={self.x: l.get_free_state(), self.F:self.F_data})
-                r2 = tf.Session().run(l.variational_expectations(self.F, self.F * 0,test_setup.Y), feed_dict={self.x: l.get_free_state(), self.F:self.F_data})   
+                r1 = tf.Session().run(l.logp(self.F, y), feed_dict={self.x: l.get_free_state(), self.F: self.F_data})
+                r2 = tf.Session().run(l.variational_expectations(self.F, self.F * 0, test_setup.Y),
+                                      feed_dict={self.x: l.get_free_state(), self.F: self.F_data})
             self.assertTrue(np.allclose(r1, r2, test_setup.tolerance, test_setup.tolerance))
 
+
 class TestQuadrature(unittest.TestCase):
     """
     Where quadratre methods have been overwritten, make sure the new code
@@ -89,12 +96,12 @@ def setUp(self):
 
         self.rng = np.random.RandomState()
         self.Fmu, self.Fvar, self.Y = self.rng.randn(3, 10, 2)
-        self.Fvar = 0.01 * self.Fvar **2
+        self.Fvar = 0.01 * self.Fvar ** 2
         self.test_setups = getTestSetups(includeMultiClass=False)
 
     def test_var_exp(self):
-        #get all the likelihoods where variational expectations has been overwritten
-                
+        # get all the likelihoods where variational expectations has been overwritten
+
         for test_setup in self.test_setups:
             if not test_setup.is_analytic:
                 continue
@@ -103,46 +110,47 @@ def test_var_exp(self):
             x_data = l.get_free_state()
             x = tf.placeholder('float64')
             l.make_tf_array(x)
-            #'build' the functions
+            # 'build' the functions
             with l.tf_mode():
                 F1 = l.variational_expectations(self.Fmu, self.Fvar, y)
                 F2 = GPflow.likelihoods.Likelihood.variational_expectations(l, self.Fmu, self.Fvar, y)
-            #compile and run the functions:
+            # compile and run the functions:
             F1 = tf.Session().run(F1, feed_dict={x: x_data})
             F2 = tf.Session().run(F2, feed_dict={x: x_data})
             self.assertTrue(np.allclose(F1, F2, test_setup.tolerance, test_setup.tolerance))
 
     def test_pred_density(self):
-        #get all the likelihoods where predict_density  has been overwritten.
-        
+        # get all the likelihoods where predict_density  has been overwritten.
+
         for test_setup in self.test_setups:
             if not test_setup.is_analytic:
                 continue
             l = test_setup.likelihood
             y = test_setup.Y
             x_data = l.get_free_state()
-            #make parameters if needed
+            # make parameters if needed
             x = tf.placeholder('float64')
             l.make_tf_array(x)
-            #'build' the functions
+            # 'build' the functions
             with l.tf_mode():
                 F1 = l.predict_density(self.Fmu, self.Fvar, y)
                 F2 = GPflow.likelihoods.Likelihood.predict_density(l, self.Fmu, self.Fvar, y)
-            #compile and run the functions:
+            # compile and run the functions:
             F1 = tf.Session().run(F1, feed_dict={x: x_data})
             F2 = tf.Session().run(F2, feed_dict={x: x_data})
             self.assertTrue(np.allclose(F1, F2, test_setup.tolerance, test_setup.tolerance))
 
+
 class TestRobustMaxMulticlass(unittest.TestCase):
     """
     Some specialized tests to the multiclass likelihood with RobustMax inverse link function.
     """
     def setUp(self):
         tf.reset_default_graph()
-    
+
     def testSymmetric(self):
         """
-        This test is based on the observation that for 
+        This test is based on the observation that for
         symmetric inputs the class predictions must have equal probability.
         """
         nClasses = 5
@@ -151,20 +159,38 @@ def testSymmetric(self):
         epsilon = 1e-3
         F = tf.placeholder(tf.float64)
         x = tf.placeholder('float64')
-        F_data = np.ones( (nPoints, nClasses) )
+        F_data = np.ones((nPoints, nClasses))
         l = GPflow.likelihoods.MultiClass(nClasses)
         l.invlink.epsilon = epsilon
         rng = np.random.RandomState(1)
-        Y = rng.randint( nClasses, size = (nPoints,1) )
+        Y = rng.randint(nClasses, size=(nPoints, 1))
         with l.tf_mode():
-            mu, _ = tf.Session().run(l.predict_mean_and_var(F,F), feed_dict={x: l.get_free_state(), F:F_data})  
-            pred = tf.Session().run(l.predict_density(F,F,Y), feed_dict={x: l.get_free_state(), F:F_data})  
-            variational_expectations = tf.Session().run(l.variational_expectations(F,F,Y), feed_dict={x: l.get_free_state(), F:F_data}) 
-        self.assertTrue( np.allclose( mu , np.ones((nPoints,nClasses))/nClasses, tolerance, tolerance ) )
-        self.assertTrue( np.allclose( pred , np.ones((nPoints,1))/nClasses, tolerance, tolerance ) )
-        validation_variational_expectation = 1./nClasses * np.log( 1.- epsilon ) + (1. - 1./nClasses ) * np.log( epsilon / (nClasses - 1) )
-        self.assertTrue( np.allclose( variational_expectations , np.ones((nPoints,1))*validation_variational_expectation, tolerance, tolerance ) )
-            
+            mu, _ = tf.Session().run(l.predict_mean_and_var(F, F), feed_dict={x: l.get_free_state(), F: F_data})
+            pred = tf.Session().run(l.predict_density(F, F, Y), feed_dict={x: l.get_free_state(), F: F_data})
+            variational_expectations = tf.Session().run(l.variational_expectations(F, F, Y),
+                                                        feed_dict={x: l.get_free_state(), F: F_data})
+        self.assertTrue(np.allclose(mu, np.ones((nPoints, nClasses))/nClasses, tolerance, tolerance))
+        self.assertTrue(np.allclose(pred, np.ones((nPoints, 1))/nClasses, tolerance, tolerance))
+        validation_variational_expectation = 1./nClasses * np.log(1. - epsilon) + \
+            (1. - 1./nClasses) * np.log(epsilon / (nClasses - 1))
+        self.assertTrue(np.allclose(variational_expectations,
+                                    np.ones((nPoints, 1)) * validation_variational_expectation,
+                                    tolerance, tolerance))
+
+
+class TestMulticlassIndexFix(unittest.TestCase):
+    """
+    A regression test for a bug in multiclass likelihood.
+    """
+    def setUp(self):
+        tf.reset_default_graph()
+
+    def testA(self):
+        mu, var = tf.placeholder(tf.float64), tf.placeholder(tf.float64)
+        Y = tf.placeholder(tf.int32)
+        lik = GPflow.likelihoods.MultiClass(3)
+        ve = lik.variational_expectations(mu, var, Y)
+        tf.gradients(tf.reduce_sum(ve), mu)
+
 if __name__ == "__main__":
     unittest.main()
-