1

Given a 2-dimensional tensor t, what's the fastest way to compute a tensor h where

h[i, :] = tf.histogram_fixed_width(t[i, :], vals, nbins)

I.e. where tf.histogram_fixed_width is called per row of the input tensor t?

It seems that tf.histogram_fixed_width is missing an axis parameter that works like, e.g., tf.reduce_sum's axis parameter.

Augustin
  • 2,444
  • 23
  • 24
fabian789
  • 8,348
  • 4
  • 45
  • 91

5 Answers5

3

tf.histogram_fixed_width works on the entire tensor indeed. You have to loop through the rows explicitly to compute the per-row histograms. Here is a complete working example using TensorFlow's tf.while_loop construct :

import tensorflow as tf

t = tf.random_uniform([2, 2])

i = 0
hist = tf.constant(0, shape=[0, 5], dtype=tf.int32)

def loop_body(i, hist):
  h = tf.histogram_fixed_width(t[i, :], [0.0, 1.0], nbins=5)
  return i+1, tf.concat_v2([hist, tf.expand_dims(h, 0)], axis=0)

i, hist = tf.while_loop(
              lambda i, _: i < 2, loop_body, [i, hist],
              shape_invariants=[tf.TensorShape([]), tf.TensorShape([None, 5])])

sess = tf.InteractiveSession()
print(hist.eval())
keveman
  • 8,427
  • 1
  • 38
  • 46
1

Inspired by keveman's answer and because the number of rows of t is fixed and rather small, I chose to use a combination of tf.gather to split rows and tf.pack to join rows. It looks simple and works, will see if it is efficient...

t_histo_rows = [
        tf.histogram_fixed_width(
            tf.gather(t, [row]),
            vals, nbins)
        for row in range(t_num_rows)]

t_histo = tf.pack(t_histo_rows, axis=0)
fabian789
  • 8,348
  • 4
  • 45
  • 91
  • I just want to point out, that the `tf.gather` solution doesn't work if `t_num_rows` is unknown on graph building time (what is very likely when using tensorflow and batching). – Spenhouet Jan 13 '19 at 15:36
1

I would like to propose another implementation. This implementation can also handle multi axes and unknown dimensions (batching).

def histogram(tensor, nbins=10, axis=None):
    value_range = [tf.reduce_min(tensor), tf.reduce_max(tensor)]

    if axis is None:
        return tf.histogram_fixed_width(tensor, value_range, nbins=nbins)
    else:
        if not hasattr(axis, "__len__"):
            axis = [axis]

        other_axis = [x for x in range(0, len(tensor.shape)) if x not in axis]
        swap = tf.transpose(tensor, [*other_axis, *axis])
        flat = tf.reshape(swap, [-1, *np.take(tensor.shape.as_list(), axis)])

        count = tf.map_fn(lambda x: tf.histogram_fixed_width(x, value_range, nbins=nbins), flat, dtype=(tf.int32))

        return tf.reshape(count, [*np.take([-1 if a is None else a for a in tensor.shape.as_list()], other_axis), nbins])

The only slow part here is tf.map_fn but it is still faster than the other solutions mentioned.

If someone knows a even faster implementation please comment since this operation is still very expensive.

Spenhouet
  • 6,556
  • 12
  • 51
  • 76
0

answers above is still slow running in GPU. Here i give an another option, which is faster(at least in my running envirment), but it is limited to 0~1 (you can normalize the value first). the train_equal_mask_nbin can be defined once in advance

def histogram_v3_nomask(tensor, nbins, row_num, col_num):
    #init mask
    equal_mask_list = []
    for i in range(nbins):
        equal_mask_list.append(tf.ones([row_num, col_num], dtype=tf.int32) * i)
    #[nbins, row, col]
    #[0, row, col] is tensor of shape [row, col] with all value 0
    #[1, row, col] is tensor of shape [row, col] with all value 1
    #....
    train_equal_mask_nbin = tf.stack(equal_mask_list, axis=0)

    #[inst, doc_len] float to int(equaly seg float in bins)
    int_input = tf.cast(tensor * (nbins), dtype=tf.int32)
    #input [row,col] -> copy N times, [nbins, row_num, col_num]
    int_input_nbin_copy = tf.reshape(tf.tile(int_input, [nbins, 1]), [nbins, row_num, col_num])
    #calculate histogram
    histogram = tf.transpose(tf.count_nonzero(tf.equal(train_equal_mask_nbin, int_input_nbin_copy), axis=2))
    return histogram
0

With the advent of tf.math.bincount, I believe the problem has become much simpler.

Something like this should work:

def hist_fixed_width(x,st,en,nbins):
  x=(x-st)/(en-st)
  x=tf.cast(x*nbins,dtype=tf.int32)
  x=tf.clip_by_value(x,0,nbins-1)
  return tf.math.bincount(x,minlength=nbins,axis=-1)
Jackson Loper
  • 525
  • 4
  • 10