这里有一些问题:
- 在矩阵乘法完成后,你没有重置
result
矩阵,因此你会不断地添加更多的值;以及
- 你从未将
result
赋值回m
以执行下一个乘法代数。
朴素幂实现
我认为最好将矩阵乘法“封装”在一个单独的函数中,例如:
def matmul(a1, a2):
m, ka = a1.shape
kb, n = a2.shape
if ka != kb:
raise ValueError()
res = np.zeros((m, n))
for i in range(m):
for j in range(n):
d = 0.0
for k in range(ka):
d += a1[i,k] * a2[k,j]
res[i, j] = d
return res
然后,我们可以使用以下公式计算这个矩阵的幂:
m2 = m
for i in range(topow-1):
m = matmul(m, m2)
请注意,我们不能仅使用
m
作为矩阵。因为如果我们写
m = matmul(m, m)
,那么
m
现在是
m
2
。但这意味着如果我们再次执行乘法,我们得到的是
m
4
而不是
m
3
。
然后这将产生预期的结果:
>>> cross = np.array([[1,0,1],[0,1,0], [1,0,1]])
>>> matmul(cross, cross)
array([[2., 0., 2.],
[0., 1., 0.],
[2., 0., 2.]])
>>> matmul(cross, matmul(cross, cross))
array([[4., 0., 4.],
[0., 1., 0.],
[4., 0., 4.]])
>>> matmul(cross, matmul(cross, matmul(cross, cross)))
array([[8., 0., 8.],
[0., 1., 0.],
[8., 0., 8.]])
对数幂乘法
上述方法可以在O(n)(线性时间)内计算出Mn,但我们可以做得更好,可以在对数时间内计算出此矩阵:我们首先检查幂是否为1
,如果是,则直接返回矩阵;如果不是,则检查幂是否为偶数,如果是,则将矩阵乘以自身,并计算该矩阵的幂,但将幂除以二,因此M2 n=(M×M)n。如果幂是奇数,我们也差不多这样做,只不过要用原始值M将其乘以:M2 n + 1=M×(M×M)n。像这样:
def matpow(m, p):
if p <= 0:
raise ValueError()
if p == 1:
return m
elif p % 2 == 0:
return matpow(matmul(m, m), p // 2)
else:
return matmul(m, matpow(matmul(m, m), p // 2))
上述内容可以更加优雅地编写,但我将其留给你练习 :).
然而需要注意的是,使用numpy数组进行标量计算通常比使用矩阵乘法(和其他函数)效率低。这些被优化过,并且是非解释性的,通常比Python等效方法表现出色。因此,我真的建议你使用它们。numpy函数也经过测试,使得其中出现bug的可能性较小。