在固定元素集合上生成特定秩的“随机”矩阵

9
我想生成大小为mxn且秩为r的矩阵,其中元素来自指定的有限集,例如{0,1}{1,2,3,4,5}。在某些非常宽泛的意义上,我希望它们是“随机”的,即我希望从算法中获得各种可能的输出,其分布与具有指定秩的元素集合上的所有矩阵的分布模糊地类似。
事实上,我并不关心它的秩是否为r,只要它接近秩为r的矩阵即可(由弗罗贝尼乌斯范数度量)。
当处理的集合为实数时,我一直在做以下操作,这对我的需求完全足够:生成大小为mxr的矩阵U和大小为nxr的矩阵V,其元素独立采样,例如从Normal(0, 2)中。然后UV'是一个秩为rmxn矩阵(好吧,小于等于r,但我认为它具有高概率的r)。
但是如果只做到这一步,然后将其二进制/ 1-5四舍五入,秩就会增加。
还可以通过进行SVD并取前r个奇异值来获得矩阵的较低秩近似值。但是,这些值不会位于所需集合中,并且将它们舍入将再次增加秩。
与此相关的问题,但接受的答案不是“随机”的,另一个答案建议使用SVD,在此处无法使用。
我想到的一个可能性是从集合中制作r个线性独立的行向量或列向量,然后通过这些的线性组合来得到矩阵的其余部分。然而,我并不清楚如何以“随机”方式获取线性独立向量,也不清楚在此之后如何以准随机的方式将它们组合起来。
(虽然这并不是特别相关,但我正在numpy中进行此操作。)
更新: 我已尝试EMS在评论中提出的方法,使用以下简单实现:
real = np.dot(np.random.normal(0, 1, (10, 3)), np.random.normal(0, 1, (3, 10)))
bin = (real > .5).astype(int)
rank = np.linalg.matrix_rank(bin)
niter = 0

while rank > des_rank:
    cand_changes = np.zeros((21, 5))
    for n in range(20):
        i, j = random.randrange(5), random.randrange(5)
        v = 1 - bin[i,j]
        x = bin.copy()
        x[i, j] = v
        x_rank = np.linalg.matrix_rank(x)
        cand_changes[n,:] = (i, j, v, x_rank, max((rank + 1e-4) - x_rank, 0))
    cand_changes[-1,:] = (0, 0, bin[0,0], rank, 1e-4)

    cdf = np.cumsum(cand_changes[:,-1])
    cdf /= cdf[-1]
    i, j, v, rank, score = cand_changes[np.searchsorted(cdf, random.random()), :]
    bin[i, j] = v
    niter += 1
    if niter % 1000 == 0:
        print(niter, rank)

对于小矩阵,它的运行速度很快,但是对于例如10x10的矩阵,它会崩溃--至少在数十万次迭代中似乎会卡在第6或第7个秩。

看起来使用更好(即不那么平坦)的目标函数可能效果更好,但我不知道应该使用什么样的目标函数。


我还尝试了一种简单的拒绝方法来构建矩阵:

def fill_matrix(m, n, r, vals):
    assert m >= r and n >= r
    trans = False
    if m > n: # more columns than rows I think is better
        m, n = n, m
        trans = True

    get_vec = lambda: np.array([random.choice(vals) for i in range(n)])

    vecs = []
    n_rejects = 0

    # fill in r linearly independent rows
    while len(vecs) < r:
        v = get_vec()
        if np.linalg.matrix_rank(np.vstack(vecs + [v])) > len(vecs):
            vecs.append(v)
        else:
            n_rejects += 1
    print("have {} independent ({} rejects)".format(r, n_rejects))

    # fill in the rest of the dependent rows
    while len(vecs) < m:
        v = get_vec()
        if np.linalg.matrix_rank(np.vstack(vecs + [v])) > len(vecs):
            n_rejects += 1
            if n_rejects % 1000 == 0:
                print(n_rejects)
        else:
            vecs.append(v)
    print("done ({} total rejects)".format(n_rejects))

    m = np.vstack(vecs)
    return m.T if trans else m

这对于任意秩的10x10二进制矩阵都能正常工作,但不适用于0-4矩阵或秩较低的更大二进制矩阵。 (例如,获取一个15秩的20x20二进制矩阵需要拒绝42,000次;对于10秩的20x20,需要1.2百万次。)

显然,这是因为前r行所张成的空间在这些情况下与样本空间(例如{0,1}^10)相比太小了。

我们希望第一行到第r行张成的空间与有效值集合的交集。因此,我们可以尝试从张成空间中进行采样并寻找有效值,但由于张成空间涉及实数系数,即使我们进行归一化以使得第一个分量在有效集合中,也永远无法找到有效向量。

也许可以将其表述为整数规划问题或其他什么问题吗?


@EMS:如果您使用 {0,1,2,3,4}(对于我的目的来说完全等效),那么标准基向量 e_1,...,e_10 就包含在这 5^10 个向量的集合中,因此您必须需要 10 个向量来跨越它。不过,这是个好观点 -- 我应该使用 0..4 而不是 1..5。 - Danica
当然,那是因为你包括了底层域的零元素。如果不包括它,那么它就变成了仿射空间,事情就会改变。所以仅仅通过移动来包括0,就会大大改变子空间的整体特征。 - ely
我认为在这个问题上可以取得进展,但这绝不是一个容易的问题。这让我想起了对具有固定行和列和的二进制矩阵进行采样的工作,关于这个问题高度评价的研究论文仍在发表。我正在思考你的有限集合中的向量的哪些倍数可能会产生有限集合中的其他向量。这取决于元素是否包括0,它们是否都可被有限集合中的公共数字整除等等。我怀疑已经存在一个简单、快速的解决方案。 - ely
1
实际上,尝试对此进行采样的一种非常好的方法可能是使用Metropolis-Hastings算法。创建一个随机的初始矩阵,其中条目仅来自您的有限集,并且在每次迭代中,选择要更改的随机位置(将其条目与有限集中的另一个条目交换)。计算某些测量线性独立性的变化度量,并按比例接受该变化。您会经常拒绝,但经过许多迭代后,您将获得非常接近所需结果的东西。 - ely
显然,这可能需要一些修改。但我相信这就是最好的现代算法从二进制固定行和矩阵中进行采样的背后思想。 - ely
显示剩余12条评论
3个回答

2
像这样怎么样?
rank = 30
n1 = 100; n2 = 100
from sklearn.decomposition import NMF
model = NMF(n_components=rank, init='random', random_state=0)
U = model.fit_transform(np.random.randint(1, 5, size=(n1, n2)))
V = model.components_
M = np.around(U) @ np.around(V)

这是一个有趣的方法,但是(a)它给出了一堆太大的元素(对于一个运行np.bincount(M.astype(int).ravel()),得到[1496, 3057, 2973, 1720, 597, 134, 21, 2],其中最后一个数字表示有两个“7”),而且(b)排名最终变成了29而不是30。 - Danica
无论如何,我已经过了六年的项目期限,我还是想要这个功能。 - Danica
嗯,我明白了。感谢您的反馈。 - diadochos

2
我不确定这个解决方案有多大用处,但是你可以构造一个矩阵,让你能够在另一个仅包含0和1的矩阵中搜索解。如果你在二进制矩阵上随机搜索,相当于随机修改最终矩阵的元素,但是可能有一些规则可以比随机搜索更好。
如果你想要生成一个元素集为E,元素为ei(其中0<=i
显然,这个矩阵的秩为m。现在,你可以构造另一个矩阵B,在某些位置上有1,以选择来自集合E的元素。这个矩阵的结构如下:
每个Bi都是一个k x n矩阵。因此,AB的大小是m x n,rank(AB)是min(m, rank(B))。如果我们希望输出矩阵仅包含集合E中的元素,则Bi的每列都必须恰好设置为1个元素,并且其余元素设置为0。
如果你想要随机搜索B的某个特定秩,你需要从具有最大秩的有效B开始,并将随机Bi的随机列j旋转一定角度。这相当于将A*B的第i行第j列更改为集合中的随机元素,因此它不是非常有用的方法。
然而,你可以对矩阵进行某些技巧。例如,如果k为2,并且B0和B1的第一行没有重叠,你可以通过添加这两个子矩阵的第一行来生成线性相关行。第二行也将是这两个矩阵的行的线性相关。我不确定这是否容易推广到k大于2,但我相信你可以使用其他技巧。
例如,一种生成最多秩为k(当mk+1时)的简单方法是获取一个随机有效的B0,保持旋转该矩阵的所有行以获得B1Bm-2,将Bm-1的第一行设为全1,其余行设为全0。假设n > k,则秩不能小于k,因为B_0列恰好有1个非零元素。矩阵的其余行都是这些行的线性组合(实际上对于几乎所有子矩阵都是精确拷贝)。最后一个子矩阵的第一行是第一个子矩阵的所有行的和,其余行都是0。对于更大的m值,您可以使用B0的行排列而不是简单旋转。
一旦您生成了满足秩约束条件的矩阵,您可以通过随机打乱其行和列来生成其他矩阵。

2

我的朋友丹尼尔·约翰逊在上面发表了评论,提出了一个想法,但我看到他从未发布过。这个想法还不够成熟,但你可能能够调整它。

如果 A 是 m×r 的,B 是 r×n 的,并且两者的秩都是 r,则 AB 的秩为 r。现在,我们只需要选择 AB,使得 AB 的值仅在给定集合中。最简单的情况是 S = {0,1,2,...,j}

一种选择是使 A 二进制,具有适当的行/列和,保证正确的秩,B 具有列和不超过 j(以便乘积中的每个项都在 S 中),并且行和选择使秩为 r(或至少鼓励它,因为可以使用拒绝方法)。

我认为我们可以提出两个独立的抽样方案来处理 AB,比试图一次性攻击整个矩阵要简单和快速。不幸的是,我所有的矩阵抽样代码都在另一台电脑上。我知道它很容易推广到允许输入一个比 {0,1}(即 S)更大的集合,但我不记得计算如何随着 m*n 的增加而扩展。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接