创建稀疏零均值随机矩阵

Create a Sparse Zero Mean Random Matrix

有没有人有创建稀疏矩阵的经验,非零值遵循 [-0.5, 0.5] 的均匀分布并且在 python 中具有零均值(零中心)(例如使用 Scipy.sparse)?

我知道 scipy.sparse 包提供了一些创建随机稀疏矩阵的方法,比如 'rand' 和 'random'。但是我无法用这些方法实现我想要的。例如,我试过:

import numpy as np
import scipy.sparse as sp

s = np.random.uniform(-0.5,0.5)
W=sp.random(1024, 1024, density=0.01, format='csc', data_rvs=s)

具体说明我的想法: 假设我想要上面提到的非稀疏矩阵或密集矩阵,我将通过以下方式创建它:

dense=np.random.rand(1024,1024)-0.5

'np.random.rand(1024,1024)' 将创建一个密集的均匀矩阵,其值在 [0,1] 中。为了使其均值为零,我通过减去 0.5 来使矩阵居中。

但是,如果我创建一个稀疏矩阵,假设:

sparse=sp.rand(1024,1024,density=0.01, format='csc')

矩阵将在统一 [0,1] 中具有非零值。但是,如果我想使矩阵居中,我不能简单地执行 'sparse-=0.5' 这将导致所有最初为零的条目在减法后都为非零。

那么,对于稀疏矩阵上的密集矩阵,我该如何实现与上述示例相同的效果?

感谢大家的帮助!

data_rvs 参数期望 "callable" 需要一个大小。这在文档中并不十分明显。这可以使用 lambda 来完成,如下所示:

import numpy as np
import scipy.sparse as sp

W = sp.random(1024, 1024, density=0.01, format='csc', 
              data_rvs=lambda s: np.random.uniform(-0.5, 0.5, size=s))

然后 print(W) 给出:

  (243, 0)  -0.171300809713
  (315, 0)  0.0739590145626
  (400, 0)  0.188151369316
  (440, 0)  -0.187384896218
    :   :
  (1016, 0) 0.29262088084
  (156, 1)  -0.149881296136
  (166, 1)  -0.490405135834
  (191, 1)  0.188167190147
  (212, 1)  0.0334533020488
  : :
  (411, 1)  0.122330200832
  (431, 1)  -0.0494334160833
  (813, 1)  -0.0076379249885
  (828, 1)  0.462807265425
  : :
  (840, 1021)   0.456423017883
  (12, 1022)    -0.47313075329
   :    :
  (563, 1022)   -0.477190349161
  (655, 1022)   -0.460942546313
  (673, 1022)   0.0930207181126
  (676, 1022)   0.253643616387
   :    :
  (843, 1023)   0.463793903168
  (860, 1023)   0.454427252782

对于新手来说,lambda 可能看起来很奇怪 - 这只是一个未命名的函数。 sp.random 函数接受一个可选参数 data_rvs,默认为 None。指定后,它应该是一个函数,它接受一个大小参数和 returns 个随机数。执行此操作的一个简单函数是:

def generate_n_uniform_randoms(n):
    return np.uniform(-0.5, 0.5, n)

我不知道 API 的来源,但不需要形状,因为 sp.random 大概首先弄清楚哪些索引将是非零的,然后它只需要计算这些索引的随机值,这是一组已知大小。

lambda 只是语法糖,它允许我们根据其他函数调用定义内联函数。我们可以改写

W = sp.random(1024, 1024, density=0.01, format='csc', 
              data_rvs=generate_n_uniform_randoms)

实际上,这可以是一个 "callable" - 一些对象 f,其中 f(n) returns n 个随机变量。这可以是一个函数,但也可以是实现 __call__(self, n) 函数的 class 的对象。例如:

class ufoo(object):

    def __call__(self, n):
        import numpy
        return numpy.random.uniform(-0.5, 0.5, n)

W = sp.random(1024, 1024, density=0.01, format='csc', 
              data_rvs=ufoo())

如果您需要均值恰好为零(当然在舍入范围内),这可以通过从非零值中减去均值来完成,正如我上面提到的:

W.data -= np.mean(W.data)

然后:

W[idx].mean()

-2.3718641632430623e-18

在我看来,您的要求仍然不完整(见下文提到的缺点)。

下面是我在评论中概述的简单构造的一些实现:

import numpy as np
import scipy.sparse as sp

M, N, NNZ = 5, 5, 10
assert NNZ % 2 == 0
flat_dim = M*N

valuesA = np.random.uniform(-0.5, 0.5, size=NNZ // 2)
valuesB = valuesA * -1
values = np.hstack((valuesA, valuesB))
positions_flat = np.random.choice(flat_dim, size=NNZ, replace=False)
positions_2d = np.unravel_index(positions_flat, dims=(M, N))
mat = sp.coo_matrix((values, (positions_2d[0], positions_2d[1])), shape=(M, N))
print(mat.todense())
print(mat.data.mean())

输出:

[[ 0.          0.          0.          0.0273862   0.        ]
 [-0.3943963   0.          0.         -0.04134932  0.        ]
 [-0.10121743  0.         -0.0273862   0.          0.04134932]
 [ 0.3943963   0.          0.          0.          0.        ]
 [-0.24680983  0.          0.24680983  0.10121743  0.        ]]
0.0

优势

  • 稀疏
  • 零均值
  • 来自均匀分布的条目

潜在劣势:

  • 对于矩阵中的每个值 x,都可以在某处找到 -x!
    • 意思是:在更广泛的联合分布意义上它是不均匀的
    • 如果那是伤害只有你能说
    • 如果是:可以轻松修改上述构造以使用来自某个分布的任何居中值,因此您的问题会分解为这个稍微小一点(但不一定更容易的问题)

现在关于那个相关的问题:我在这里猜测,但我不会惊讶地看到使用约束 mean(x)=0 统一采样 x 值是 NP-hard。

请记住,正如另一个答案中所建议的那样,非零值的后验居中会改变基础分布(即使对于 simple distributions)。在某些情况下甚至使边界无效(离开间隔 -0.5, 0.5)。

这意味着:这个问题是关于形式化 objective 有多重要 并以某种方式平衡它们。

sparse.random 做了两件事 - 随机分布非零值,并生成随机统一值。

In [62]: M = sparse.random(10,10,density=.2, format='csr')
In [63]: M
Out[63]: 
<10x10 sparse matrix of type '<class 'numpy.float64'>'
    with 20 stored elements in Compressed Sparse Row format>
In [64]: M.data
Out[64]: 
array([ 0.42825407,  0.51858978,  0.8084335 ,  0.08691635,  0.13210409,
        0.61288928,  0.39675205,  0.58242891,  0.5174367 ,  0.57859824,
        0.48812484,  0.13472883,  0.82992478,  0.70568697,  0.45001632,
        0.52147305,  0.72943809,  0.55801913,  0.97018861,  0.83236235])

您可以在不改变稀疏分布的情况下廉价地修改 data 值:

In [65]: M.data -= 0.5
In [66]: M.A
Out[66]: 
array([[ 0.        ,  0.        ,  0.        , -0.07174593,  0.        ,
         0.        ,  0.        ,  0.        ,  0.        ,  0.        ],
       [ 0.01858978,  0.        ,  0.        ,  0.3084335 , -0.41308365,
         0.        ,  0.        ,  0.        ,  0.        , -0.36789591],
       [ 0.        ,  0.        ,  0.        ,  0.        ,  0.11288928,
        -0.10324795,  0.        ,  0.        ,  0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
         0.        ,  0.        ,  0.08242891,  0.0174367 ,  0.        ],
       [ 0.        ,  0.        ,  0.07859824,  0.        ,  0.        ,
         0.        ,  0.        ,  0.        ,  0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
         0.        , -0.01187516,  0.        ,  0.        , -0.36527117],
       [ 0.        ,  0.        ,  0.32992478,  0.        ,  0.        ,
         0.        ,  0.        ,  0.        ,  0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        ,  0.        ,  0.20568697,
         0.        ,  0.        , -0.04998368,  0.        ,  0.        ],
       [ 0.02147305,  0.        ,  0.22943809,  0.05801913,  0.        ,
         0.        ,  0.        ,  0.        ,  0.        ,  0.        ],
       [ 0.        ,  0.47018861,  0.33236235,  0.        ,  0.        ,
         0.        ,  0.        ,  0.        ,  0.        ,  0.        ]])
In [67]: np.mean(M.data)
Out[67]: 0.044118297661574338

或用一组新值替换非零值:

In [69]: M.data = np.random.randint(-5,5,20)
In [70]: M
Out[70]: 
<10x10 sparse matrix of type '<class 'numpy.int32'>'
    with 20 stored elements in Compressed Sparse Row format>
In [71]: M.A
Out[71]: 
array([[ 0,  0,  0,  4,  0,  0,  0,  0,  0,  0],
       [-1,  0,  0,  1,  2,  0,  0,  0,  0, -4],
       [ 0,  0,  0,  0,  0,  4,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  0,  0,  0, -5, -5,  0],
       [ 0,  0,  2,  0,  0,  0,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  0,  0, -3,  0,  0,  3],
       [ 0,  0, -1,  0,  0,  0,  0,  0,  0,  0],
       [ 0,  0,  0,  0, -4,  0,  0, -1,  0,  0],
       [-1,  0, -5, -2,  0,  0,  0,  0,  0,  0],
       [ 0,  3,  1,  0,  0,  0,  0,  0,  0,  0]])
In [72]: M.data
Out[72]: 
array([ 4, -1,  1,  2, -4,  0,  4, -5, -5,  2, -3,  3, -1, -4, -1, -1, -5,
       -2,  3,  1])