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


73

在scipy中,我们可以使用scipy.sparse.lil_matrix()等构造一个稀疏矩阵。但是该矩阵是二维的。

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

ps我在3d中有很多稀疏数据,需要张量来存储/执行乘法。如果没有现有的数据结构,是否有实现此类张量的建议?



……但是很稀疏。
史蒂夫·乔阿

“二维矩阵”是什么意思?如果您要表示一个表示2D线性变换的矩阵,那么您是在说一个2x2的实数值矩阵(由浮点值近似),其中行列式1为刚性旋转。如果还要表示平移,则可以将2x2矩阵嵌入3x3矩阵中,如果要允许剪切或扩展/收缩,则可以放宽行列式要求-但即使这样总共有9个浮点值。为什么要/需要稀疏表示?
彼得

@Peter“二维矩阵”表示二维矩阵。2d矩阵中的单位可以表示为(x,y,r),其中x&y是坐标,r是存储在(x,y)的值。我需要一个稀疏表示,因为当x&y非常大时,例如x <10 ^ 5,y <10 ^ 4,并且只有很少的数据存储在矩阵中,例如10 ^ 4。numpy为2d矩阵提供稀疏矩阵。但是很多时候,我们需要3d甚至nd。我猜nd情况太笼统了。因此,任何3d解决方案对我来说都足够好。
zhongqi

谢谢-我对您的问题感到困惑(听起来像是您想将一堆欧几里得元组乘以矩阵,线性代数样式)。但是,如果您在谈论mxnxo矩阵,那么听起来您的“稀疏”实现将需要提供某种迭代器接口,以便您实现(逐元素)乘法。
彼得

Answers:


15

很高兴为您建议一个(可能很明显)的实现,如果您有时间和空间来建立新的依赖关系,并且需要更快的速度,可以用纯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))

您可以通过验证输入实际上是一个元组并且仅包含整数来使此实现更健壮,但是这样做只会减慢速度,因此除非您稍后将代码发布给外界,否则我不会担心。

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

#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实现的性能。


4
问题是数学运算,而不是数据容器...我从未听说过有效稀疏Nd张量积的算法。看看吧scipy.sparse.dok_matrix。这就是您在此处描述的内容,仅限于2D。扩展它以容纳ND数据很容易,但是您如何处理这些数据呢?(话虽这么说,您的回答是完全合理的……)
乔·肯顿

啊,我误会了吗?那么这个问题是在询问更多关于scipy兼容矩阵乘法运算的实现吗?当然,这应该相对容易实现,因为您真正需要的只是对索引中值的循环查询,这是我提供的。我会看看scipy规范。
tehwrus 2011年

3
好吧,可以说,我也误会了。无论哪种方式,我的意思是您在执行操作时没有利用稀疏结构。您在编辑中描述的内容将其视为密集数组。(这当然可行!您的答案解决了当前的问题。)稀疏矩阵库利用了数组的稀疏性,避免了像“稀疏”一样遍历数组的每个元素之类的事情。那是使用稀疏矩阵的要点。运算大致取决于“密集”元素的数量,而不是矩阵的整体尺寸。
乔·肯顿

@tehwalrus感谢您的答复。但是,恐怕与您建议的数据结构相乘可能不是非常有效……
zhongqi

@JoeKington您肯定要遍历非稀疏数组中的每个元素(u在这种情况下),确定吗?除非两者都很稀疏,否则在这种情况下我会被误解。在这种情况下,您只需在字典中的键上循环,然后从元组中提取索引。无论如何,我不能跟上稀疏代数的发展,更不用说优化该主题算法的计算机科学了。抱歉,@ zhongqi!
tehwrus


7

截至2017年的另一种答案是sparse一揽子计划。根据程序包本身,它在NumPy之上并scipy.sparse通过概括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>

@JayShin也许,但老实说,我认为您必须进行测试。
TomCho

1
我建议写“截至2017年”而不是“截至今年”
MrMartin '18

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])])

我处于相同的情况,这非常有用。我认为,只需做一些额外的工作,就可以将其实施到scipy稀疏数组模块中。你考虑过了吗?
TomCho

@TomCho谢谢!我尚未考虑将其实现到scipy的稀疏模块中。我认为scipy中的实现应支持所有标准的numpy矩阵功能。那是可行的,但需要大量的工作。另外,我认为为这些矩阵上的操作添加C实现会更高效,更适合scipy。
萨努菲'17

0

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

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

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

注意:我在一个循环中使用完全相同的矩阵M重复了很多次,因此您可能会认为求解inverse(M会更有效)。但逆中号不是稀疏的,所以我想用(但没有测试)spsolve是一个更好的解决方案。“最佳”可能取决于您使用的矩阵大小。

#!/usr/bin/env python3
# testSparse.py
# profhuster

import numpy as np
import scipy.sparse as sM
import scipy.sparse.linalg as spLA
from array import array
from numpy.random import rand, seed
seed(101520)

nX = 4
nY = 3
r = 0.1

def loadSpNodes(nX, nY, r):
    # Matrix to map 2D array of nodes to 1D array
    Number = np.zeros((nY, nX), dtype=int)

    # Map each element of the 2D array to a 1D array
    iM = 0
    for i in range(nX):
        for j in range(nY):
            Number[j, i] = iM
            iM += 1
    print(f"Number = \n{Number}")

    # Now create a sparse matrix of the "stencil"
    diagVal = 1 + 4 * r
    offVal = -r
    d_list = array('f')
    i_list = array('i')
    j_list = array('i')
    # Loop over the 2D nodes matrix
    for i in range(nX):
        for j in range(nY):
            # Recall the 1D number
            iSparse = Number[j, i]
            # populate the diagonal
            d_list.append(diagVal)
            i_list.append(iSparse)
            j_list.append(iSparse)
            # Now, for each rectangular neighbor, add the 
            # off-diagonal entries
            # Use a try-except, so boundry nodes work
            for (jj,ii) in ((j+1,i),(j-1,i),(j,i+1),(j,i-1)):
                try:
                    iNeigh = Number[jj, ii]
                    if jj >= 0 and ii >=0:
                        d_list.append(offVal)
                        i_list.append(iSparse)
                        j_list.append(iNeigh)
                except IndexError:
                    pass
    spNodes = sM.coo_matrix((d_list, (i_list, j_list)), shape=(nX*nY,nX*nY))
    return spNodes


MySpNodes = loadSpNodes(nX, nY, r)
print(f"Sparse Nodes = \n{MySpNodes.toarray()}")
b = rand(nX*nY)
print(f"b=\n{b}")
x = spLA.spsolve(MySpNodes.tocsr(), b)
print(f"x=\n{x}")
print(f"Multiply back together=\n{x * MySpNodes}")

0

我需要一个三维的查找表中的x,y,z和与此解决方案..想出了
为什么不使用其中一个维度是第三个维度的除数?即。使用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
====================
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.