Numpy“智能”对称矩阵
Posted
技术标签:
【中文标题】Numpy“智能”对称矩阵【英文标题】:Numpy ‘smart’ symmetric matrix 【发布时间】:2011-02-04 02:24:39 【问题描述】:在 numpy 中是否有一个智能且节省空间的对称矩阵,当 [i][j]
被写入时,它会自动(并且透明地)填充 [j][i]
的位置?
import numpy
a = numpy.symmetric((3, 3))
a[0][1] = 1
a[1][0] == a[0][1]
# True
print(a)
# [[0 1 0], [1 0 0], [0 0 0]]
assert numpy.all(a == a.T) # for any symmetric matrix
自动 Hermitian 也不错,尽管在撰写本文时我不需要它。
【问题讨论】:
如果可以解决您的问题,您可以考虑将答案标记为已接受。 :) 我想等待一个更好的(即内置和内存效率高的)答案的出现。当然,你的回答没有问题,所以我还是会接受。 【参考方案1】:如果您有能力在进行计算之前使矩阵对称,那么以下应该相当快:
def symmetrize(a):
"""
Return a symmetrized version of NumPy array a.
Values 0 are replaced by the array value at the symmetric
position (with respect to the diagonal), i.e. if a_ij = 0,
then the returned array a' is such that a'_ij = a_ji.
Diagonal values are left untouched.
a -- square NumPy array, such that a_ij = 0 or a_ji = 0,
for i != j.
"""
return a + a.T - numpy.diag(a.diagonal())
这在合理的假设下有效(例如在运行 symmetrize
之前不同时执行 a[0, 1] = 42
和矛盾的 a[1, 0] = 123
)。
如果您真的需要透明的对称化,您可以考虑继承 numpy.ndarray 并简单地重新定义 __setitem__
:
class SymNDArray(numpy.ndarray):
"""
NumPy array subclass for symmetric matrices.
A SymNDArray arr is such that doing arr[i,j] = value
automatically does arr[j,i] = value, so that array
updates remain symmetrical.
"""
def __setitem__(self, (i, j), value):
super(SymNDArray, self).__setitem__((i, j), value)
super(SymNDArray, self).__setitem__((j, i), value)
def symarray(input_array):
"""
Return a symmetrized version of the array-like input_array.
The returned array has class SymNDArray. Further assignments to the array
are thus automatically symmetrized.
"""
return symmetrize(numpy.asarray(input_array)).view(SymNDArray)
# Example:
a = symarray(numpy.zeros((3, 3)))
a[0, 1] = 42
print a # a[1, 0] == 42 too!
(或使用矩阵代替数组的等价物,具体取决于您的需要)。这种方法甚至可以处理更复杂的赋值,例如 a[:, 1] = -1
,它正确设置了 a[1, :]
元素。
请注意,Python 3 消除了编写 def …(…, (i, j),…)
的可能性,因此在使用 Python 3 运行之前必须稍微修改代码:def __setitem__(self, indexes, value): (i, j) = indexes
...
【讨论】:
实际上,如果你对它进行子类化,你不应该覆盖 setitem,而应该覆盖 getitem,这样你就不会在创建矩阵。 这是一个非常有趣的想法,但是当在子类实例数组上执行简单的print
时,将其写为等效的__getitem__(self, (i, j))
会失败。原因是print
使用整数索引调用__getitem__()
,因此即使是简单的print
也需要做更多的工作。 __setitem__()
的解决方案适用于print
(显然),但遇到类似的问题:a[0] = [1, 2, 3]
不起作用,出于同样的原因(这不是一个完美的解决方案)。 __setitem__()
解决方案的优点是更健壮,因为内存中的数组是正确的。还不错。 :)
你的建议听起来像blog.sopticek.net/2016/07/24/…...你确认它几乎一样吗?麻烦的是这优化了内存使用,而不是计算时间。我正在寻找 python 方法来加速对称矩阵上的一些简单计算。如果您有信息,请告诉我。
这个答案不会节省内存,因此与引用链接中的方法有很大不同。现在,使用对称矩阵节省时间通常涉及使用专用算法而不是通用算法,例如在 NumPy 中使用 eigh() 而不是 eig()。【参考方案2】:
在 numpy 中优化对称矩阵的更普遍问题也困扰着我。
在研究之后,我认为答案可能是 numpy 在某种程度上受到对称矩阵的底层 BLAS 例程支持的内存布局的限制。
虽然一些 BLAS 例程确实利用对称性来加速对称矩阵的计算,但它们仍然使用与完整矩阵相同的内存结构,即 n^2
空间而不是 n(n+1)/2
。只是他们被告知矩阵是对称的,并且只能使用上三角形或下三角形中的值。
一些scipy.linalg
例程确实接受标志(如linalg.solve
上的sym_pos=True
),这些标志会传递给BLAS 例程,尽管在numpy 中对此提供更多支持会很好,特别是像DSYRK 这样的例程的包装器(对称秩 k 更新),这将允许 Gram 矩阵的计算比 dot(MT, M) 快一点。
(担心在时间和/或空间上优化 2 倍常数因子似乎有些挑剔,但它可能会影响您在单台机器上可以处理多大问题的阈值...)
【讨论】:
问题是关于如何通过分配单个条目来自动创建对称矩阵(而不是关于如何指示 BLAS 在其计算中使用对称矩阵或原则上如何存储对称矩阵更有效)。 这个问题也是关于空间效率的,所以 BLAS 问题是热门话题。 @EOL,问题不在于如何通过单个条目的分配自动创建对称矩阵。 当然,“创建”可以更恰当地替换为“更新”。现在,由于问题明确是关于在设置 M_ji 时透明地设置 M_ji ,而这个答案与此无关,你明白这本质上就是我提出的观点。问题是关于如何有效地做到这一点(而不是有效地处理对称矩阵,即使这可能是正确的问题:更好地放在 cmets 上,或者作为解决更普遍问题的答案而不是仅仅讨论它)。【参考方案3】:有许多众所周知的存储对称矩阵的方法,因此它们不需要占用 n^2 个存储元素。此外,重写常用操作以访问这些修改后的存储方式是可行的。权威著作是 Golub 和 Van Loan,矩阵计算,1996 年第 3 版,约翰霍普金斯大学出版社,第 1.27-1.2.9 节。例如,从表格(1.2.2)中引用它们,在对称矩阵中只需要存储A = [a_i,j ]
fori >= j
。然后,假设保存矩阵的 vector 表示为 V,并且 A 是 n×n,将 a_i,j
放入
V[(j-1)n - j(j-1)/2 + i]
这假定为 1 索引。
Golub 和 Van Loan 提供了一个算法 1.2.3,它展示了如何访问这样一个存储的 V 来计算 y = V x + y
。
Golub 和 Van Loan 还提供了一种以对角占优形式存储矩阵的方法。这不会节省存储空间,但支持某些其他类型的操作的就绪访问。
【讨论】:
还有 Rectangular Full Packed storage (RFP),例如 Lapack ZPPTRF 使用它。 numpy 支持吗? @isti_spl:不,但你可以实现一个包装器【参考方案4】:这是普通的python而不是numpy,但我只是拼凑了一个例程来填充 一个对称矩阵(以及一个确保它正确的测试程序):
import random
# fill a symmetric matrix with costs (i.e. m[x][y] == m[y][x]
# For demonstration purposes, this routine connect each node to all the others
# Since a matrix stores the costs, numbers are used to represent the nodes
# so the row and column indices can represent nodes
def fillCostMatrix(dim): # square array of arrays
# Create zero matrix
new_square = [[0 for row in range(dim)] for col in range(dim)]
# fill in main diagonal
for v in range(0,dim):
new_square[v][v] = random.randrange(1,10)
# fill upper and lower triangles symmetrically by replicating diagonally
for v in range(1,dim):
iterations = dim - v
x = v
y = 0
while iterations > 0:
new_square[x][y] = new_square[y][x] = random.randrange(1,10)
x += 1
y += 1
iterations -= 1
return new_square
# sanity test
def test_symmetry(square):
dim = len(square[0])
isSymmetric = ''
for x in range(0, dim):
for y in range(0, dim):
if square[x][y] != square[y][x]:
isSymmetric = 'NOT'
print "Matrix is", isSymmetric, "symmetric"
def showSquare(square):
# Print out square matrix
columnHeader = ' '
for i in range(len(square)):
columnHeader += ' ' + str(i)
print columnHeader
i = 0;
for col in square:
print i, col # print row number and data
i += 1
def myMain(argv):
if len(argv) == 1:
nodeCount = 6
else:
try:
nodeCount = int(argv[1])
except:
print "argument must be numeric"
quit()
# keep nodeCount <= 9 to keep the cost matrix pretty
costMatrix = fillCostMatrix(nodeCount)
print "Cost Matrix"
showSquare(costMatrix)
test_symmetry(costMatrix) # sanity test
if __name__ == "__main__":
import sys
myMain(sys.argv)
# vim:tabstop=8:shiftwidth=4:expandtab
【讨论】:
【参考方案5】:要构造一个沿主对角线对称且主对角线上为 0 的 NxN 矩阵,您可以这样做:
a = np.array([1, 2, 3, 4, 5])
b = np.zeros(shape=(a.shape[0], a.shape[0]))
upper = np.triu(b + a)
lower = np.tril(np.transpose(b + a))
D = (upper + lower) * (np.full(a.shape[0], fill_value=1) - np.eye(a.shape[0]))
这是一种特殊情况,但最近我使用这种矩阵来表示网络邻接关系。
希望对您有所帮助。 干杯。
【讨论】:
【参考方案6】:如果填写[j][i]
,用Python 方式填写[i][j]
很简单。存储问题有点意思。可以使用packed
属性来扩充 numpy 数组类,该属性对于节省存储空间和以后读取数据都很有用。
class Sym(np.ndarray):
# wrapper class for numpy array for symmetric matrices. New attribute can pack matrix to optimize storage.
# Usage:
# If you have a symmetric matrix A as a shape (n,n) numpy ndarray, Sym(A).packed is a shape (n(n+1)/2,) numpy array
# that is a packed version of A. To convert it back, just wrap the flat list in Sym(). Note that Sym(Sym(A).packed)
def __new__(cls, input_array):
obj = np.asarray(input_array).view(cls)
if len(obj.shape) == 1:
l = obj.copy()
p = obj.copy()
m = int((np.sqrt(8 * len(obj) + 1) - 1) / 2)
sqrt_m = np.sqrt(m)
if np.isclose(sqrt_m, np.round(sqrt_m)):
A = np.zeros((m, m))
for i in range(m):
A[i, i:] = l[:(m-i)]
A[i:, i] = l[:(m-i)]
l = l[(m-i):]
obj = np.asarray(A).view(cls)
obj.packed = p
else:
raise ValueError('One dimensional input length must be a triangular number.')
elif len(obj.shape) == 2:
if obj.shape[0] != obj.shape[1]:
raise ValueError('Two dimensional input must be a square matrix.')
packed_out = []
for i in range(obj.shape[0]):
packed_out.append(obj[i, i:])
obj.packed = np.concatenate(packed_out)
else:
raise ValueError('Input array must be 1 or 2 dimensional.')
return obj
def __array_finalize__(self, obj):
if obj is None: return
self.packed = getattr(obj, 'packed', None)
```
【讨论】:
以上是关于Numpy“智能”对称矩阵的主要内容,如果未能解决你的问题,请参考以下文章