Gerar uma matriz "aleatória" de determinada classificação sobre um conjunto fixo de elementos
Eu gostaria de gerar matrizes de tamanhom
xn
e classificarr
, com elementos provenientes de um conjunto finito especificado, e.{0,1}
ou{1,2,3,4,5}
. Eu quero que eles sejam "aleatórios" em algum sentido muito frouxo dessa palavra, ou seja, eu quero obter uma variedade de saídas possíveis do algoritmo com distribuição vagamente semelhante à distribuição de todas as matrizes sobre esse conjunto de elementos com a classificação especificada.
Na verdade, eu realmente não me importo que tenha classificaçãor
, só que éperto para uma matriz de classificaçãor
(medido pela norma Frobenius).
Quando o conjunto em questão é o real, venho fazendo o seguinte, que é perfeitamente adequado para as minhas necessidades: gerar matrizesU
de tamanhom
xr
eV
don
xr
, com elementos independentemente amostrados de, e. Normal (0, 2) EntãoU V'
é umm
xn
matriz de classificaçãor
(bem,<= r
, mas eu acho que ér
com alta probabilidade).
Se eu fizer isso e depois passar para binário / 1-5, a classificação aumenta.
Também é possível obter uma aproximação de classificação mais baixa para uma matriz fazendo um SVD e obtendo o primeiror
valores singulares. Esses valores, no entanto, não estarão no conjunto desejado, e arredondá-los aumentará novamente a classificação.
Essa questão está relacionado, mas a resposta aceita não é "aleatória", e a outra resposta sugere SVD, o que não funciona aqui como observado.
Uma possibilidade que eu pensei é fazerr
vetores de linha ou coluna linearmente independentes do conjunto e depois obter o restante da matriz por combinações lineares desses. Não estou muito claro, no entanto, em como obter vetores lineares aleatórios "aleatórios" ou como combiná-los de maneira quase aleatória depois disso.
(Não que seja super relevante, mas estou fazendo isso numpy.)
Atualizar: Eu tentei a abordagem sugerida pelo EMS nos comentários, com esta implementação simples:
<code>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) </code>
Funciona rapidamente para pequenas matrizes, mas desmorona por ex. 10x10 - parece ficar preso no rank 6 ou 7, pelo menos por centenas de milhares de iterações.
Parece que isso pode funcionar melhor com uma função objetiva melhor (ou seja, menos plana), mas não sei o que isso seria.
Eu também tentei um método simples de rejeição para construir a matriz:
<code>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 </code>
Isso funciona bem, por exemplo, Matrizes binárias 10x10 com qualquer classificação, mas não para matrizes 0-4 ou binários muito maiores com classificação inferior. (Por exemplo, obter uma matriz binária de 20x20 de nível 15 me deu 42.000 rejeições; com 20x20 de classificação 10, foram necessários 1,2 milhão.)
Isto é claramente porque o espaço ocupado pela primeirar
linhas é uma parte muito pequena do espaço do qual estou amostrando, por exemplo{0,1}^10
, nesses casos.
Queremos a intersecção do espaço do primeiror
linhas com o conjunto de valores válidos. Assim, poderíamos tentar amostragem a partir do intervalo e procurar por valores válidos, mas como o intervalo envolve coeficientes reais que nunca nos encontrarão vetores válidos (mesmo se normalizarmos de modo que, por exemplo, o primeiro componente esteja no conjunto válido).
Talvez isso possa ser formulado como um problema de programação inteira, ou algo assim?