From 65a215646c653ab808170c8b8c10de2945262613 Mon Sep 17 00:00:00 2001 From: Yu-Yang Date: Sat, 8 Apr 2017 02:41:59 +0800 Subject: [PATCH] Fix in_top_k() for Theano when identical values appear in predictions (#6133) * Fix in_top_k() for Theano when identical values appear in predictions * Add test and update docstrings for in_top_k() --- keras/backend/tensorflow_backend.py | 9 ++++--- keras/backend/theano_backend.py | 33 +++++++++++++++++------- tests/keras/backend/backend_test.py | 40 +++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/keras/backend/tensorflow_backend.py b/keras/backend/tensorflow_backend.py index 7a9d4c224..13c48920e 100644 --- a/keras/backend/tensorflow_backend.py +++ b/keras/backend/tensorflow_backend.py @@ -2730,13 +2730,14 @@ def in_top_k(predictions, targets, k): """Returns whether the `targets` are in the top `k` `predictions`. # Arguments - predictions: A tensor of shape `batch_size` x classes and type `float32`. - targets: A tensor of shape batch_size and type `int32` or `int64`. + predictions: A tensor of shape `(batch_size, classes)` and type `float32`. + targets: A 1D tensor of length `batch_size` and type `int32` or `int64`. k: An `int`, number of top elements to consider. # Returns - A tensor of shape `batch_size` and type `bool`. `output_i` is `True` if - `targets_i` is within top-k values of `predictions_i` + A 1D tensor of length `batch_size` and type `bool`. + `output[i]` is `True` if `predictions[i, targets[i]]` is within top-`k` + values of `predictions[i]`. """ return tf.nn.in_top_k(predictions, targets, k) diff --git a/keras/backend/theano_backend.py b/keras/backend/theano_backend.py index a86a15249..9bf4a07cd 100644 --- a/keras/backend/theano_backend.py +++ b/keras/backend/theano_backend.py @@ -1494,20 +1494,35 @@ def l2_normalize(x, axis): def in_top_k(predictions, targets, k): - """Returns whether the `targets` are in the top `k` `predictions` + """Returns whether the `targets` are in the top `k` `predictions`. # Arguments - predictions: A tensor of shape batch_size x classess and type float32. - targets: A tensor of shape batch_size and type int32 or int64. - k: An int, number of top elements to consider. + predictions: A tensor of shape `(batch_size, classes)` and type `float32`. + targets: A 1D tensor of length `batch_size` and type `int32` or `int64`. + k: An `int`, number of top elements to consider. # Returns - A tensor of shape batch_size and type int. output_i is 1 if - targets_i is within top-k values of predictions_i + A 1D tensor of length `batch_size` and type `bool`. + `output[i]` is `True` if `predictions[i, targets[i]]` is within top-`k` + values of `predictions[i]`. """ - predictions_top_k = T.argsort(predictions)[:, -k:] - result, _ = theano.map(lambda prediction, target: any(equal(prediction, target)), sequences=[predictions_top_k, targets]) - return result + # handle k < 1 and k >= predictions.shape[1] cases to match TF behavior + if k < 1: + # dtype='bool' is only available since Theano 0.9.0 + try: + return T.zeros_like(targets, dtype='bool') + except TypeError: + return T.zeros_like(targets, dtype='int8') + + if k >= int_shape(predictions)[1]: + try: + return T.ones_like(targets, dtype='bool') + except TypeError: + return T.ones_like(targets, dtype='int8') + + predictions_k = T.sort(predictions)[:, -k] + targets_values = predictions[T.arange(targets.shape[0]), targets] + return T.ge(targets_values, predictions_k) # CONVOLUTIONS diff --git a/tests/keras/backend/backend_test.py b/tests/keras/backend/backend_test.py index b851b1258..a9192a77a 100644 --- a/tests/keras/backend/backend_test.py +++ b/tests/keras/backend/backend_test.py @@ -618,6 +618,46 @@ class TestBackend(object): check_single_tensor_operation('l2_normalize', (4, 3), axis=-1) check_single_tensor_operation('l2_normalize', (4, 3), axis=1) + def test_in_top_k(self): + batch_size = 20 + num_classes = 10 + + # Random prediction test case + predictions = np.random.random((batch_size, num_classes)).astype('float32') + targets = np.random.randint(num_classes, size=batch_size, dtype='int32') + + predictions_th = KTH.variable(predictions, dtype='float32') + targets_th = KTH.variable(targets, dtype='int32') + predictions_tf = KTF.variable(predictions, dtype='float32') + targets_tf = KTF.variable(targets, dtype='int32') + + for k in range(1, num_classes + 1): + res_th = KTH.eval(KTH.in_top_k(predictions_th, targets_th, k)) + res_tf = KTF.eval(KTF.in_top_k(predictions_tf, targets_tf, k)) + + assert res_th.shape == res_tf.shape + assert_allclose(res_th, res_tf, atol=1e-05) + + # Identical prediction test case: + # randomly set half of the predictions to an identical value + num_identical = num_classes // 2 + for i in range(batch_size): + idx_identical = np.random.choice(num_classes, size=num_identical, replace=False) + predictions[i, idx_identical] = predictions[i, 0] + targets = np.zeros(batch_size, dtype='int32') + + predictions_th = KTH.variable(predictions, dtype='float32') + targets_th = KTH.variable(targets, dtype='int32') + predictions_tf = KTF.variable(predictions, dtype='float32') + targets_tf = KTF.variable(targets, dtype='int32') + + for k in range(1, num_classes + 1): + res_th = KTH.eval(KTH.in_top_k(predictions_th, targets_th, k)) + res_tf = KTF.eval(KTF.in_top_k(predictions_tf, targets_tf, k)) + + assert res_th.shape == res_tf.shape + assert_allclose(res_th, res_tf, atol=1e-05) + def test_conv2d(self): # TF kernel shape: (rows, cols, input_depth, depth)