web-dev-qa-db-ja.com

Pythonでスパース3D行列/配列?

Scipyでは、scipy.sparse.lil_matrix()などを使用してスパース行列を作成できます。しかし、行列は2次元です。

Pythonのスパース3Dマトリックス/配列(テンソル)に既存のデータ構造があるかどうか疑問に思っていますか?

追伸3Dのスパースデータがたくさんあり、乗算を保存/実行するためにテンソルが必要です。既存のデータ構造がない場合、そのようなテンソルを実装するための提案はありますか?

60
zhongqi

純粋な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))

入力が実際にタプルであり、整数のみを含むことを確認することにより、この実装をより堅牢にすることができますが、それは単に遅くなりますので、後でコードを世界にリリースしない限り心配しません。

[〜#〜] edit [〜#〜]-他のテンソルがN次元NumPy配列であると仮定した場合の行列乗算問題のCython実装(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

(コードのコメントで述べたように)合計するインデックスを定義する必要があり、配列の長さや動作しないことに注意する必要があるため、手元の問題のためにこれを常に手で回す必要があります!

EDIT 2-他のマトリックスもスパースである場合、3方向ループを実行する必要はありません。

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実装をテストする必要があります。

14
tehwalrus

sparray-Pythonのスパースn次元配列 (by Jan Erik Solem)をご覧ください。 github でも利用可能です。

6
eldad-a

今年の別の答えは、 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>
4
TomCho

ゼロから新しいものをすべて書くよりも、scipyのスパースモジュールを可能な限り使用する方がいいかもしれません。これにより、(はるかに)パフォーマンスが向上する場合があります。多少似たような問題がありましたが、データに効率的にアクセスするだけで、操作を実行する必要はありませんでした。さらに、私のデータは3次元のうち2次元でのみスパースでした。

私は自分の問題を解決するクラスを書いており、(私が思う限り)簡単に拡張して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])])
3
Samufi