how to produce a tensor containing the indices of nonzero elements?

Viewed 306

I have a tensor like

 [[0, 0, 0, 1, 0, 0],
  [0, 3, 0, 0, 0, 1],
  [0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0],
  [0, 0, 0, 2, 0, 0],
  [0, 0, 0, 0, 0, 0]]

How do i produce a tensor containing only the non-zero indices of the former?

[[0,3],[1,1],[1,5],[4,3]]

I'm on tfjs so it doesn't appear to have boolean_mask or some other helpful functions others seem to use in indexing. Is this possible without using the .data() escape hatch and map/filtering over the array? (i'm just using tensorflow for its fast linear algebra methods so that wouldn't be the worst, right?)

2 Answers

True that currently there are no operators one can use to define a partition using a mask. You can consider this answer as a workaround. But even using a mask, the issue here cannot be solved easily because creating a partition supposes that one knows beforehand the indexes to retrieve.

To solve the issue, one might use the logicalAnd between the input tensor and a tensor of the same shape with all the elements equal to 1. It is usefull because it will return a tensor of the same shape than the input shape with values 0 if the initial element was 0 and 1 if not.Using argMax, one can retrieve the index of the elements that are equal to 1

let t = tf.tensor2d([[0, 0, 0, 1, 0, 0], [0, 3, 0, 0, 0, 1], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 2, 0, 0], [0, 0, 0, 0, 0, 0]])
let a = t.as1D()

const b = tf.cast(a, 'bool')
let helper = b.logicalAnd(tf.cast(tf.ones([a.size]), 'bool'))
const n = helper.sum().dataSync()[0]
const noNull = []
for(let i = 0; i < n ; i++) {
  let ind = tf.argMax(helper)
  let s = ind.dataSync()[0]
  noNull.push(tf.argMax(helper).dataSync()[0])
  if (s === 0) {
     const [x, y] = helper.split([1, helper.size - 1])
     helper = tf.concat(tf.tensor1d([0]), y)
  } else if (s === helper.size) {
    const [x, y] = helper.split([helper.size -1, 1])
    helper = tf.concat(x, tf.tensor1d([0]))
  } else {
    const [x, _, y] = helper.split([s, 1, helper.size - s - 1])
    helper = tf.concat([x,tf.tensor1d([0]), y])
  }
}

const indexToCoords = (index, shape) => {
  const pseudoShape = shape.map((a, b, c) => c.slice(b + 1).reduce((a, b) => a * b, 1))
  let coords = []
  let ind = index
  for (let i = 0; i < shape.length; i++) {
    coords.push(Math.floor(ind / pseudoShape[i]))
    ind = ind % pseudoShape[i]
  }
  return coords
}

const coords = noNull.map(e => indexToCoords(e, t.shape))

console.log(coords)
<html>
  <head>
    <!-- Load TensorFlow.js -->
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@0.13.0"> </script>
  </head>

  <body>
  </body>
</html>

Following this thread, there is a much simpler way to achieve the same thing

(async function() {
  const x = tf.tensor2d(
    [[0, 0, 0, 1, 0, 0],
    [0, 3, 0, 0, 0, 1],
    [0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0],
    [0, 0, 0, 2, 0, 0],
    [0, 0, 0, 0, 0, 0]]);

  const mask = x.greater([0]).asType('bool');
  const coords = await tf.whereAsync(mask);

  coords.print();
}());
<html>
  <head>
    <!-- Load TensorFlow.js -->
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@0.13.0"> </script>
  </head>

  <body>
  </body>
</html>

You can use tf.whereAsync(condition) to return the indices. See the docs

Example:

(await tf.whereAsync(tf.tensor1d([0,1,2,0,1]).notEqual(0))).print();
==> Tensor
    [[1],
     [2],
     [4]]
Related