Python中的稀疏3d矩阵/数组?

时间:2011-10-07 09:08:40

标签: python numpy scipy sparse-matrix

在scipy中,我们可以使用scipy.sparse.lil_matrix()等构造一个稀疏矩阵。但矩阵在2d。

我想知道Python中是否存在稀疏3d矩阵/数组(张量)的现有数据结构?

P.S。我在3d中有很多稀疏数据,需要张量来存储/执行乘法。如果没有现有的数据结构,是否有任何建议来实现这样的张量?

6 个答案:

答案 0 :(得分:14)

很高兴建议一个(可能是显而易见的)实现,如果你有新的依赖项的时间和空间,可以在纯Python或C / Cython中实现,并且需要它更快。

N维中的稀疏矩阵可以假设大多数元素都是空的,因此我们使用键入元组的字典:

class NDSparseMatrix:
  def __init__(self):
    self.elements = {}

  def addValue(self, tuple, value):
    self.elements[tuple] = value

  def readValue(self, tuple):
    try:
      value = self.elements[tuple]
    except KeyError:
      # could also be 0.0 if using floats...
      value = 0
    return value

你可以像这样使用它:

sparse = NDSparseMatrix()
sparse.addValue((1,2,3), 15.7)
should_be_zero = sparse.readValue((1,5,13))

你可以通过验证输入实际上是一个元组,并且它只包含整数来使这个实现更加健壮,但这只会减慢速度,所以我不担心,除非你将代码发布到世界之后。

编辑 - 矩阵乘法问题的Cython实现,假设其他张量是N维NumPy数组(numpy.ndarray)可能如下所示:

#cython: boundscheck=False
#cython: wraparound=False

cimport numpy as np

def sparse_mult(object sparse, np.ndarray[double, ndim=3] u):
  cdef unsigned int i, j, k

  out = np.ndarray(shape=(u.shape[0],u.shape[1],u.shape[2]), dtype=double)

  for i in xrange(1,u.shape[0]-1):
    for j in xrange(1, u.shape[1]-1):
      for k in xrange(1, u.shape[2]-1):
        # note, here you must define your own rank-3 multiplication rule, which
        # is, in general, nontrivial, especially if LxMxN tensor...

        # loop over a dummy variable (or two) and perform some summation:
        out[i,j,k] = u[i,j,k] * sparse((i,j,k))

  return out

虽然你总是需要手头解决这个问题,因为(如代码注释中所述)你需要定义你正在总结的索引,并且要小心数组长度或赢得的东西干嘛!

编辑2 - 如果其他矩阵也稀疏,那么您不需要进行三向循环:

def sparse_mult(sparse, other_sparse):

  out = NDSparseMatrix()

  for key, value in sparse.elements.items():
    i, j, k = key
    # note, here you must define your own rank-3 multiplication rule, which
    # is, in general, nontrivial, especially if LxMxN tensor...

    # loop over a dummy variable (or two) and perform some summation 
    # (example indices shown):
    out.addValue(key) = out.readValue(key) + 
      other_sparse.readValue((i,j,k+1)) * sparse((i-3,j,k))

  return out

我对C实现的建议是使用一个简单的结构来保存索引和值:

typedef struct {
  int index[3];
  float value;
} entry_t;

然后你需要一些函数来分配和维护这些结构的动态数组,并根据需要快速搜索它们;但是你应该在担心这些东西之前测试Python实现的性能。

答案 1 :(得分:6)

看看sparray - sparse n-dimensional arrays in Python(Jan Erik Solem)。也可在github上找到。

答案 2 :(得分:4)

今年的另一个答案是sparse包。根据包本身,它通过推广scipy.sparse布局在NumPy和scipy.sparse.coo_matrix之上实现稀疏的多维数组。

以下是从文档中获取的示例:

import numpy as np
n = 1000
ndims = 4
nnz = 1000000
coords = np.random.randint(0, n - 1, size=(ndims, nnz))
data = np.random.random(nnz)

import sparse
x = sparse.COO(coords, data, shape=((n,) * ndims))
x
# <COO: shape=(1000, 1000, 1000, 1000), dtype=float64, nnz=1000000>

x.nbytes
# 16000000

y = sparse.tensordot(x, x, axes=((3, 0), (1, 2)))

y
# <COO: shape=(1000, 1000, 1000, 1000), dtype=float64, nnz=1001588>

答案 3 :(得分:3)

比从头开始编写所有新内容更好,可能是尽可能使用scipy的稀疏模块。这可能会导致(更多)更好的性能。我有一个类似的问题,但我只需要有效地访问数据,而不是对它们执行任何操作。此外,我的数据在三个维度中只有两个是稀疏的。

我写了一个解决我问题的课程,可以(据我认为)轻松扩展以满足OP的需求。不过,它可能仍有一些改进的潜力。

import scipy.sparse as sp
import numpy as np

class Sparse3D():
    """
    Class to store and access 3 dimensional sparse matrices efficiently
    """
    def __init__(self, *sparseMatrices):
        """
        Constructor
        Takes a stack of sparse 2D matrices with the same dimensions
        """
        self.data = sp.vstack(sparseMatrices, "dok")
        self.shape = (len(sparseMatrices), *sparseMatrices[0].shape)
        self._dim1_jump = np.arange(0, self.shape[1]*self.shape[0], self.shape[1])
        self._dim1 = np.arange(self.shape[0])
        self._dim2 = np.arange(self.shape[1])

    def __getitem__(self, pos):
        if not type(pos) == tuple:
            if not hasattr(pos, "__iter__") and not type(pos) == slice: 
                return self.data[self._dim1_jump[pos] + self._dim2]
            else:
                return Sparse3D(*(self[self._dim1[i]] for i in self._dim1[pos]))
        elif len(pos) > 3:
            raise IndexError("too many indices for array")
        else:
            if (not hasattr(pos[0], "__iter__") and not type(pos[0]) == slice or
                not hasattr(pos[1], "__iter__") and not type(pos[1]) == slice):
                if len(pos) == 2:
                    result = self.data[self._dim1_jump[pos[0]] + self._dim2[pos[1]]]
                else:
                    result = self.data[self._dim1_jump[pos[0]] + self._dim2[pos[1]], pos[2]].T
                    if hasattr(pos[2], "__iter__") or type(pos[2]) == slice:
                        result = result.T
                return result
            else:
                if len(pos) == 2:
                    return Sparse3D(*(self[i, self._dim2[pos[1]]] for i in self._dim1[pos[0]]))
                else:
                    if not hasattr(pos[2], "__iter__") and not type(pos[2]) == slice:
                        return sp.vstack([self[self._dim1[pos[0]], i, pos[2]]
                                          for i in self._dim2[pos[1]]]).T
                    else:
                        return Sparse3D(*(self[i, self._dim2[pos[1]], pos[2]] 
                                          for i in self._dim1[pos[0]]))

    def toarray(self):
        return np.array([self[i].toarray() for i in range(self.shape[0])])

答案 4 :(得分:0)

我还需要3D稀疏矩阵来求解2D热方程(2个空间维是密集的,但时间维是对角线加上一个负对角线。)我发现this链接可以指导我。诀窍是创建一个数组Number,该数组将2D稀疏矩阵映射到1D线性向量。然后通过构建数据和索引列表来构建2D矩阵。后来,Number矩阵用于将答案排列回2D数组。

[编辑],这是我最初发帖后发生的,可以通过使用.reshape(-1)方法来更好地解决。经过研究,reshape方法比flatten更好,因为它可以将新视图返回到原始数组中,但是flatten复制了该数组。该代码使用原始的Number数组。我稍后会尝试更新。[结束编辑]

我通过创建一维随机矢量并求解第二个矢量来对其进行测试。然后将其乘以稀疏2D矩阵,得到相同的结果。

注意:我使用完全相同的矩阵 M 在循环中重复多次,因此您可能认为解决{{1} } M inverse(。但是 M 的倒数不是 稀疏的,所以我认为(但尚未测试)使用)是更好的解决方案。 “最佳”可能取决于您使用的矩阵大小。

spsolve

答案 5 :(得分:0)

我需要x,y,z的3d查找表,并提出了此解决方案。
为什么不使用其中一个维度作为第三维的除数?即。使用x和yz作为矩阵尺寸

例如。如果x具有80个潜在成员,则y具有100个潜在成员,而z具有20个潜在成员 您将2000年的稀疏矩阵设为80(即xy = 100x20)
x尺寸照常
yz尺寸:前100个元素代表z = 0,y = 0到99
.........第二个100代表z = 2,y = 0到99等
所以位于(x,y,z)的给定元素将位于(x,z * 100 + y)的稀疏矩阵中
如果需要使用负数,请在矩阵平移中设计一个算术偏移量。如果需要,解决方案可以扩展到n个维度
from scipy import sparse
m = sparse.lil_matrix((100,2000), dtype=float)

def add_element((x,y,z), element):
    element=float(element)
    m[x,y+z*100]=element

def get_element(x,y,z):
    return m[x,y+z*100]

add_element([3,2,4],2.2)
add_element([20,15,7], 1.2)
print get_element(0,0,0)
print get_element(3,2,4)
print get_element(20,15,7)
print "  This is m sparse:";print m

====================
OUTPUT:
0.0
2.2
1.2
  This is m sparse:
  (3, 402L) 2.2
  (20, 715L)    1.2
====================