m
サイズxn
とランクの行列を生成したいと思います。r
要素は指定された有限集合({0,1}
または)から取得されます{1,2,3,4,5}
。私はそれらをその単語の非常に緩い意味で「ランダム」にしたい、つまり、指定されたランクの要素のセット全体のすべての行列の分布に漠然と似た分布で、アルゴリズムからさまざまな可能な出力を取得したい。
実際、私はそれがランクを持っていることを実際には気にしませんr
、ただそれがランクの行列(フロベニウスノルムによって測定される)に近いということだけです。r
手元のセットが実数である場合、私は次のことを行っています。これは私のニーズに完全に適しています。たとえば、Normal(0、2)から独立してサンプリングされた要素を使用して、サイズU
xおよびxの行列をm
生成r
します。次に、ランクのx行列です(まあ、、、しかし私はそれが高い確率であると思います)。V
n
r
U V'
m
n
r
<= r
r
ただし、それを実行してから2進数/ 1-5に丸めると、ランクが上がります。
r
SVDを実行し、最初の特異値を取得することにより、行列の低ランク近似を取得することもできます。ただし、これらの値は目的のセットには含まれず、丸めるとランクが再び上がります。
この質問は関連していますが、受け入れられた回答は「ランダム」ではなく、他の回答はSVDを示唆しています。これは、前述のようにここでは機能しません。
私が考えた1つの可能性は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では、120万が必要でした。)
これは明らかに、最初の行にまたがるスペースが、たとえばこれらの場合にr
サンプリングしているスペースの一部として小さすぎるためです。{0,1}^10
最初の行のスパンとr
有効な値のセットの共通部分が必要です。したがって、スパンからサンプリングして有効な値を探すこともできますが、スパンには実数値の係数が含まれているため、有効なベクトルを見つけることはできません(たとえば、最初のコンポーネントが有効なセットに含まれるように正規化した場合でも)。
多分これは整数計画問題か何かとして定式化することができますか?