Extract max-sum submatrices - python

I've a 2D NxN matrix that has elements from a set of real numbers. I need to identify top n DxD sub-matrices from it such that their sum is maximum and return top left index of the sub-matrices. I need to do it in Tensorflow.
For example I have following 4x4 matrix:
[1 1 4 4]
[1 1 4 4]
[3 3 2 2]
[3 3 2 2]
I need to identify 2 sub-matrices that have the largest sum and return their top left index. In above case, 2 sub-matrices that have the largest and second largest sum are:
[[4 4] [[3 3]
[4 4]] & [3 3]]
I need to return [[0,2],[2,0]], the top left indices to both the matrices. Thanks.

You can get that with the following snippet. The idea is to build a tensor holding the row and column indices of each element of each submatrix, then sum the submatrices and find the largest sums.
import tensorflow as tf
# Input data
input = tf.placeholder(tf.int32, [None, None])
# Submatrix dimension
dims = tf.placeholder(tf.int32, [2])
# Number of top submatrices to find
k = tf.placeholder(tf.int32, [])
# Sizes
input_shape = tf.shape(input)
rows, cols = input_shape[0], input_shape[1]
d_rows, d_cols = dims[0], dims[1]
subm_rows, subm_cols = rows - d_rows + 1, cols - d_cols + 1
# Index grids
ii, jj = tf.meshgrid(tf.range(subm_rows), tf.range(subm_cols), indexing='ij')
d_ii, d_jj = tf.meshgrid(tf.range(d_rows), tf.range(d_cols), indexing='ij')
# Add indices
subm_ii = ii[:, :, tf.newaxis, tf.newaxis] + d_ii
subm_jj = jj[:, :, tf.newaxis, tf.newaxis] + d_jj
# Make submatrices tensor
subm = tf.gather_nd(input, tf.stack([subm_ii, subm_jj], axis=-1))
# Add submatrices
subm_sum = tf.reduce_sum(subm, axis=(2, 3))
# Use TopK to find top submatrices
_, top_idx = tf.nn.top_k(tf.reshape(subm_sum, [-1]), tf.minimum(k, tf.size(subm_sum)))
# Get row and column
top_row = top_idx // subm_cols
top_col = top_idx % subm_cols
result = tf.stack([top_row, top_col], axis=-1)
# Test
with tf.Session() as sess:
mat = [
[1, 1, 4, 4],
[1, 1, 4, 4],
[3, 3, 2, 2],
[3, 3, 2, 2],
]
print(sess.run(result, feed_dict={input: mat, dims: [2, 2], k: 2}))
Output:
[[0 2]
[1 2]]
Note that the output in this case is [0, 2] and [1, 2], but not [2, 0]. That's because the submatrix starting at [1, 2] sums the same amount as the one at [2, 0], and it is before in the matrix, if you iterate it by rows. If you pass k: 3 in the test you would get [2, 0] too in the result.

Related

How to construct a matrix that contains all pairs of rows of a matrix in tensorflow

I need to construct a matrix z that would contain combinations of pairs of rows of a matrix x.
x = tf.constant([[1, 3],
[2, 4],
[0, 2],
[0, 1]], dtype=tf.int32)
z=[[[1,2],
[1,0],
[1,0],
[2,0],
[2,0],
[0,0]],
[3,4],
[3,2],
[3,1],
[4,2],
[4,1],
[2,1]]]
It pairs each value with the rest of the values on that row.
I could not find any function or come up with a good idea to do that.
Update 1
So I need the final shape be 2*6*2 like the z above.
Unfortunately, it's a bit more complex than one would like using tensorflow operators only. I would go with creating the indices for all combinations with a while_loop then use tf.gather to collect values:
import tensorflow as tf
x = tf.constant([[1, 3],
[2, 4],
[3, 2],
[0, 1]], dtype=tf.int32)
m = tf.constant([], shape=(0,2), dtype=tf.int32)
_, idxs = tf.while_loop(
lambda i, m: i < tf.shape(x)[0] - 1,
lambda i, m: (i + 1, tf.concat([m, tf.stack([tf.tile([i], (tf.shape(x)[0] - 1 - i,)), tf.range(i + 1, tf.shape(x)[0])], axis=1)], axis=0)),
loop_vars=(0, m),
shape_invariants=(tf.TensorShape([]), tf.TensorShape([None, 2])))
z = tf.reshape(tf.transpose(tf.gather(x, idxs), (2,0,1)), (-1, 2))
# <tf.Tensor: shape=(12, 2), dtype=int32, numpy=
# array([[1, 2],
# [1, 3],
# [1, 0],
# [2, 3],
# [2, 0],
# [3, 0],
# [3, 4],
# [3, 2],
# [3, 1],
# [4, 2],
# [4, 1],
# [2, 1]])>
This should work in both TF1 and TF2.
If the length of x is known in advance, you don't need the while_loop and could simply precompute the indices in python then place them in a constant.
Here is a way to do that without a loop:
import tensorflow as tf
x = tf.constant([[1, 3],
[2, 4],
[0, 2],
[0, 1]], dtype=tf.int32)
# Number of rows
n = tf.shape(x)[0]
# Grid of indices
ri = tf.range(0, n - 1)
rj = ri + 1
ii, jj = tf.meshgrid(ri, rj, indexing='ij')
# Stack together
grid = tf.stack([ii, jj], axis=-1)
# Get upper triangular part
m = ii < jj
idx = tf.boolean_mask(grid, m)
# Get values
g = tf.gather(x, idx, axis=0)
# Rearrange result
result = tf.transpose(g, [2, 0, 1])
print(result.numpy())
# [[[1 2]
# [1 0]
# [1 0]
# [2 0]
# [2 0]
# [0 0]]
#
# [[3 4]
# [3 2]
# [3 1]
# [4 2]
# [4 1]
# [2 1]]]

Create tensor with random index to be zero

Now I have a tensor random_row, and I want to create a new tensor, whose shape is known 2-dim, and its random_row to be all zeros, and all other rows to be all ones, e.g.
random_row = [1, 3] # random_row is a tensor itself
# new_tensor needs to be another tensor whose row in random_row to be all zeros
# we already know new_tensor's shape to be (4, 2)
new_tensor = [[1, 1], [0, 0], [1, 1], [0, 0]]
How can I achieve that? Really appreciate it if someone can help!
You can do that with a function like his:
import tensorflow as tf
def zero_rows(x, idx)
# Turn row indices into a boolean mask
n = tf.shape(x)[0]
m = tf.scatter_nd(tf.expand_dims(idx, 1), tf.ones_like(idx, dtype=tf.bool), [n])
# Select zeros where the indices are or the data elsewhere
return tf.where(m, tf.zeros_like(x), x)
# Test
with tf.Graph().as_default(), tf.Session() as sess:
y = zero_rows(x, idx)
print(sess.run(y, feed_dict={x: [[1, 2], [3, 4], [5, 6], [7, 8]], idx: [1, 3]}))
# [[1 2]
# [0 0]
# [5 6]
# [0 0]]

How to reduce the number of row repetitions in a numpy array

I want to clean my data reducing the number of duplicates. I do not want to delete ALL duplicates.
How can I get a numpy array with certain number of duplicates?
Suppose, I have
x = np.array([[1,2,3],[1,2,3],[5,5,5],[1,2,3],[1,2,3]])
and I set number of duplicates as 2.
And the output should be like
x
>>[[1,2,3],[1,2,3],[5,5,5]]
or
x
>>[[5,5,5],[1,2,3],[1,2,3]]
It does not meter in my task
Even though using list appending as an intermediate step is not always a good idea when you already have numpy arrays, in this case it is by far the cleanest way to do it:
def n_uniques(arr, max_uniques):
uniq, cnts = np.unique(arr, axis=0, return_counts=True)
arr_list = []
for i in range(cnts.size):
num = cnts[i] if cnts[i] <= max_uniques else max_uniques
arr_list.extend([uniq[i]] * num)
return np.array(arr_list)
x = np.array([[1,2,3],
[1,2,3],
[1,2,3],
[5,5,5],
[1,2,3],
[1,2,3],])
reduced_arr = n_uniques(x, 2)
This was kind of tricky, but you can actually do that without loops and preserving the relative order in the original array with something like this (in this case the first repetitions are preserved):
import numpy as np
def drop_extra_repetitions(x, max_reps):
# Find unique rows
uniq, idx_inv, counts = np.unique(x, axis=0, return_inverse=True, return_counts=True)
# Compute number of repetitions of each different row
counts_clip = np.minimum(counts, max_reps)
# Array alternating between valid unique row indices and -1 ([0, -1, 1, -1, ...])
idx_to_repeat = np.stack(
[np.arange(len(uniq)), -np.ones(len(uniq), dtype=int)], axis=1).ravel()
# Number of repetitions for each of the previous indices
idx_repeats_clip = np.stack([counts_clip, counts - counts_clip], axis=1).ravel()
# Valid unique row indices are repetead at most max_reps,
# extra repetitions are filled with -1
idx_clip_sorted = np.repeat(idx_to_repeat, idx_repeats_clip)
# Sorter for inverse index - that is, sort the indices in the input array
# according to their corresponding unique row index
sorter = np.argsort(idx_inv)
# The final inverse index is the same as the original but with -1 on extra repetitions
idx_inv_final = np.empty(len(sorter), dtype=int)
idx_inv_final[sorter] = idx_clip_sorted
# Return the array reconstructed from the inverse index without the positions with -1
return uniq[idx_inv_final[idx_inv_final >= 0]]
x = [[5, 5, 5], [1, 2, 3], [1, 2, 3], [5, 5, 5], [1, 2, 3], [1, 2, 3]]
max_reps = 2
print(drop_extra_repetitions(x, max_reps))
# [[5 5 5]
# [1 2 3]
# [1 2 3]
# [5 5 5]]
If you do not need to preserve the order at all, then you can simply do:
import numpy as np
def drop_extra_repetitions(x, max_reps):
uniq, counts = np.unique(x, axis=0, return_counts=True)
# Repeat each unique row index at most max_reps
ret_idx = np.repeat(np.arange(len(uniq)), np.minimum(counts, max_reps))
return uniq[ret_idx]
x = [[5, 5, 5], [1, 2, 3], [1, 2, 3], [5, 5, 5], [1, 2, 3], [1, 2, 3]]
max_reps = 2
print(drop_extra_repetitions(x, max_reps))
# [[1 2 3]
# [1 2 3]
# [5 5 5]
# [5 5 5]]

How to efficiently extract all slices of given length using tensorflow

I am trying to extract all slices of length 4 along 0th axis of a 2-dim tensor. So far I can do it mixing pure Python with tensorflow.
r = test.shape[0] # test should be a tensor
n = 4
a_list = list(range(r))
the_list = np.array([a_list[slice(i, i+n)] for i in range(r - n+1)])
test_stacked = tf.stack(tf.gather(test, the_list))
What would be an efficient way of doing that without using pure Python? Note that the "test" array is actually supposed to be a tensor, thus its shape isn't known before I execute the first part of the graph.
A full vanilla example:
array = np.array([[0, 1],[1, 2],[2, 3],[3, 4],[4, 5],[5, 6]])
array.shape # (6,2)
r = array.shape[0]
n = 4
a_list = list(range(r))
the_list = np.array([a_list[slice(i, i+n)] for i in range(r - n+1)])
result = array[the_list] # all possible slices of length 4 of the array along 0th axis
result.shape # (3, 4, 2)
result:
[[[0 1]
[1 2]
[2 3]
[3 4]]
[[1 2]
[2 3]
[3 4]
[4 5]]
[[2 3]
[3 4]
[4 5]
[5 6]]]
You may want to try the more general tf.extract_image_patches.
import tensorflow as tf
a = tf.constant([[0, 1],[1, 2],[2, 3],[3, 4],[4, 5],[5, 6]])
# tf.extract_image_patches requires a [batch, in_rows, in_cols, depth] tensor
a = a[None, :, :, None]
b = tf.extract_image_patches(a,
ksizes=[1, 4, 2, 1],
strides=[1, 1, 1, 1],
rates=[1, 1, 1, 1],
padding='VALID')
b = tf.reshape(tf.squeeze(b), [-1, 4, 2])
sess = tf.InteractiveSession()
print(b.eval())
I believe gather_nd is what you are looking for.
# a is a tensor of size (6, 2)
def get_indices(l, d):
return [[[j] for j in range(i, i + d)] for i in range(l - d + 1)]
b = tf.gather_nd(a, get_indices(6, 4))
# b is a tensor of shape (3, 4, 2)

Tensorflow: stack all row pairs from a tensor

Given a tensor t=[[1,2], [3,4]], I need to produce ts=[[1,2,1,2], [1,2,3,4], [3,4,1,2], [3,4,3,4]]. That is, I need to stack together all row pairs.
Important: the tensor has dimension [None, 2], ie. the first dimension is variable.
I have tried:
Using a tf.while_loop to generate a list of indices idx=[[0, 0], [0, 1], [1, 0], [1, 1]], then tf.gather(ts, idx). This works but is messy and I don't know what to do about gradients.
2 for loops iterating over tf.unstack(t), adding stacked rows to a buffer, then tf.stack(buffer). This does not work if the first dimension is variable.
To look for inspiration in broadcasting. For instance, given x=t.expand_dims(t, 0), y=t.expand_dims(t, 1), s=tf.reshape(tf.add(x, y), [-1, 2]) s will be [[2, 4], [4, 6], [4, 6], [6, 8]], ie. the sum of every row combination. But how can I do stacking instead of sum? I've been failing for 2 days :)
Solution with tf.meshgrid() and some reshaping:
import tensorflow as tf
import numpy as np
t = tf.placeholder(tf.int32, [None, 2])
num_rows, size_row = tf.shape(t)[0], tf.shape(t)[1] # actual dynamic dimensions
# Getting pair indices using tf.meshgrid:
idx_range = tf.range(num_rows)
pair_indices = tf.stack(tf.meshgrid(*[idx_range, idx_range]))
pair_indices = tf.transpose(pair_indices, perm=[1, 2, 0])
# Finally gathering the rows accordingly:
res = tf.reshape(tf.gather(t, pair_indices), (-1, size_row * 2))
with tf.Session() as sess:
print(sess.run(res, feed_dict={t: np.array([[1,2], [3,4], [5,6]])}))
# [[1 2 1 2]
# [3 4 1 2]
# [5 6 1 2]
# [1 2 3 4]
# [3 4 3 4]
# [5 6 3 4]
# [1 2 5 6]
# [3 4 5 6]
# [5 6 5 6]]
Solution using cartesian product:
import tensorflow as tf
import numpy as np
t = tf.placeholder(tf.int32, [None, 2])
num_rows, size_row = tf.shape(t)[0], tf.shape(t)[1] # actual dynamic dimensions
# Getting pair indices by computing the indices cartesian product:
row_idx = tf.range(num_rows)
row_idx_a = tf.expand_dims(tf.tile(tf.expand_dims(row_idx, 1), [1, num_rows]), 2)
row_idx_b = tf.expand_dims(tf.tile(tf.expand_dims(row_idx, 0), [num_rows, 1]), 2)
pair_indices = tf.concat([row_idx_a, row_idx_b], axis=2)
# Finally gathering the rows accordingly:
res = tf.reshape(tf.gather(t, pair_indices), (-1, size_row * 2))
with tf.Session() as sess:
print(sess.run(res, feed_dict={t: np.array([[1,2], [3,4], [5,6]])}))
# [[1 2 1 2]
# [1 2 3 4]
# [1 2 5 6]
# [3 4 1 2]
# [3 4 3 4]
# [3 4 5 6]
# [5 6 1 2]
# [5 6 3 4]
# [5 6 5 6]]
Can be achieved by:
tf.concat([tf.tile(tf.expand_dims(t,1), [1, tf.shape(t)[0], 1]), tf.tile(tf.expand_dims(t,0), [tf.shape(t)[0], 1, 1])], axis=2)
Detailed steps:
t = tf.placeholder(tf.int32, shape=[None, 2])
#repeat each row of t
d = tf.tile(tf.expand_dims(t,1), [1, tf.shape(t)[0], 1])
#Output:
#[[[1 2] [1 2]]
# [[3 4] [3 4]]]
#repeat the entire input t
e = tf.tile(tf.expand_dims(t,0), [tf.shape(t)[0], 1, 1])
#Output:
#[[[1 2] [3 4]]
# [[1 2] [3 4]]]
#concat
f = tf.concat([d, e], axis=2)
with tf.Session() as sess:
print(sess.run(f, {t:np.asarray([[1,2],[3,4]])}))
#Output
#[[[1 2 1 2]
#[1 2 3 4]]
#[[3 4 1 2]
#[3 4 3 4]]]

Categories