在给定特定块大小的情况下稀疏矩阵的函数

时间:2021-02-23 01:39:31

标签: python numpy matrix

问题说明

我正在尝试编写一个函数,该函数将在给定目标稀疏度和一个名为 block_shape 的参数的情况下稀疏矩阵,该参数定义矩阵中零块的最小大小。目标不一定要完美实现,但要尽可能接近。

例如,给定以下参数,

>>> matrix = [
  [1, 1, 1, 1],
  [1, 1, 1, 1],
  [1, 1, 1, 1],
  [1, 1, 1, 1]
]
>>> target = 0.5
>>> block_shape = (2, 2)

50% 稀疏度的有效输出可能是

>>> sparse_matrix = sparsify(matrix, target, block_shape)
>>> sparse_matrix
[
  [1, 1, 0, 0],
  [1, 1, 0, 0],
  [0, 0, 1, 1],
  [0, 0, 1, 1]
]

>>> sparse_matrix = sparsify(matrix, target, block_shape)
>>> sparse_matrix
[
  [1, 0, 0, 1],
  [1, 0, 0, 1],
  [0, 0, 1, 1],
  [0, 0, 1, 1]
]

请注意,输入可能有多个有效的稀疏版本。唯一的标准是尽可能多地到达目标。约束之一是只有形状 block_size 的零被认为是稀疏的。

例如,给定参数

,下面的矩阵的稀疏度为0%
>>> sparse_matrix = sparsify(matrix, target, block_shape)
>>> sparse_matrix
[
  [1, 0, 0, 1],
  [1, 1, 0, 0],
  [0, 1, 1, 1],
  [0, 0, 0, 0]
]

到目前为止我所拥有的

目前,我有以下一段代码

import numpy as np

def sparsify(matrix, target, block_shape=None):
    if block_shape is None or block_shape == 1 or block_shape == (1,) or block_shape == (1, 1):
        # 1x1 is just bernoulli with p=target
        probs = np.random.uniform(size=matrix.shape)
        mask = np.zeros(matrix.shape)
        mask[probs >= target] = 1.0
    else:
        if isinstance(block_shape, int):
            block_shape = (block_shape, block_shape)
        if len(block_shape) == 1:
            block_shape = (block_shape[0], block_shape[0])
        mask = np.ones(matrix.shape)
        rows, cols = matrix.shape
        for row in range(rows):
            for col in range(cols):
                submask = mask[row:row+block_shape[0], col:col+block_shape[1]]
                if submask.shape != block_shape:
                    # we don't care about the edges, cannot partially sparsify
                    continue
                if (submask == 0).any():
                    # If current (row, col) is already in the sparsified area, skip
                    continue
                prob = np.random.random()
                if prob < target:
                    submask[:, :] = np.zeros(submask.shape)
    return matrix * mask, mask

上面代码的问题是,如果块大小不是(1, 1)

>>> matrix = np.random.randn(100, 100)
>>> matrix, mask = sparsify(matrix, target=0.5, block_shape=(2, 2))

>>> print((matrix == 0).mean())
0.73
>>> print((mask == 0).mean())
0.73

差异的原因(我认为)

我不知道为什么我没有得到我期望的目标,但我认为这与我检查每个元素的概率而不是整个块的概率有关。但是,我的代码中有跳过条件,所以我认为应该涵盖它

编辑

编辑 1 -- 附加示例

再举一些例子。

示例 1:给定不同的块大小

>>> sparse_matrix = sparsify(matrix, 0.25, (3, 3))
>>> sparse_matrix
[
  [0, 0, 0, 1],
  [0, 0, 0, 1],
  [0, 0, 0, 1],
  [1, 1, 1, 1]
]

上面的例子是一个有效的稀疏矩阵,虽然稀疏度不是 25%,但另一个有效的结果可能是一个全 1 的矩阵。

示例 2:给定不同的块大小和目标

>>> sparse_matrix = sparsify(matrix, 0.6, (1, 2))
>>> sparse_matrix
[
  [0, 0, 0, 0],
  [1, 0, 0, 1],
  [0, 0, 1, 1],
  [1, 1, 0, 0]
]

注意所有零都可以放在(1, 2)的块中,并且稀疏度= 60%

编辑 2 -- 忘记约束

另一个我忘记提及的约束,但尝试将其合并到我的代码中是零块必须不重叠。

示例 1:以下结果无效

>>> sparse_matrix = sparsify(matrix, 0.5, (2, 2))
>>> sparse_matrix
[
  [0, 0, 1, 1],
  [0, 0, 0, 1],
  [1, 0, 0, 1],
  [1, 1, 1, 1]
]

虽然从索引 (0, 0)(1, 1) 开始的块具有有效的零形状,但结果不符合要求。原因是这些块中只有一个可以被认为是有效的。如果我们将零块标记为 z0z1,那么这个矩阵是这样的:

[
  [z0, z0,  1, 1],
  [z0, z0, z1, 1],
  [ 1, z1, z1, 1],
  [ 1,  1,  1, 1]
]
(1, 1) 处的

元素可被视为属于 z0z1。这意味着只有一个稀疏块,这使得稀疏度为 25%(不是 ~44%)。

1 个答案:

答案 0 :(得分:1)

变成 0 的概率并不完全相等。

例如:block_shape (2, 2), matrix(0, 0) 变为 0 的概率为 target,因为循环只通过一次。 matrix(1, 0) 的概率大于 target,因为循环通过它两次。类似地,matrix(1, 1) 的概率大于 (1, 0),因为循环在 (0, 0), (1, 0), (0, 1), (1, 1).< /p>

由于先前的循环操作,这也会发生在矩阵的中间。

所以影响结果的主要变量是block_shape。

我一直在摆弄一些,这里有一种使用 while 循环而不是 for 循环的替代方法。模拟直到您在 err 内达到目标概率。由于 err 太小,您只需要注意 inf 循环。

import numpy as np

def sparsify(matrix, target, block_shape=None):
    if block_shape is None or block_shape == 1 or block_shape == (1,) or block_shape == (1, 1):
        # 1x1 is just bernoulli with p=target
        probs = np.random.uniform(size=matrix.shape)
        mask = np.zeros(matrix.shape)
        mask[probs >= target] = 1.0
    else:
        if isinstance(block_shape, int):
            block_shape = (block_shape, block_shape)
        if len(block_shape) == 1:
            block_shape = (block_shape[0], block_shape[0])
        mask = np.ones(matrix.shape)
        rows, cols = matrix.shape

        # vars for probability check
        total = float(rows * cols)
        zero_cnt= total - np.count_nonzero(matrix)
        err = 0.005  # .5%

        # simulate until we reach target probability range
        while not target - err < (zero_cnt/ total) < target + err:

            # pick a random point in the matrix
            row = np.random.randint(rows)
            col = np.random.randint(cols)

            # submask = mask[row:row + block_shape[0], col:col + block_shape[1]]
            submask = matrix[row:row + block_shape[0], col:col + block_shape[1]]

            if submask.shape != block_shape:
                # we don't care about the edges, cannot partially sparsify
                continue
            if (submask == 0).any():
                # If current (row, col) is already in the sparsified area, skip
                continue

            # need more 0s to reach target probability range
            if zero_cnt/ total < target - err:  
                matrix[row:row + block_shape[0], col:col + block_shape[1]] = 0

            # need more 1s to reach target probability range
            else:                               
                matrix[row:row + block_shape[0], col:col + block_shape[1]] = 1

            # update 0 count
            zero_cnt = total - np.count_nonzero(matrix)

    return matrix * mask, mask

注意。

  1. 未检查任何优化或代码重构。
  2. 未使用 mask 变量。直接处理矩阵。
matrix = np.ones((100, 100))
matrix, mask = sparsify(matrix, target=0.5, block_shape=(2, 2))
print((matrix == 0).mean())
# prints somewhere between target - err and target + err
# likely to see a lower value in the range since we're counting up (0s)
相关问题