web-dev-qa-db-fra.com

matrice / tableau 3D clairsemé en Python?

En scipy, nous pouvons construire une matrice clairsemée en utilisant scipy.sparse.lil_matrix () etc. Mais la matrice est en 2d.

Je me demande s'il existe une structure de données existante pour une matrice/matrice 3D clairsemée (tenseur) en Python?

p.s. J'ai beaucoup de données éparses en 3D et j'ai besoin d'un tenseur pour stocker/effectuer une multiplication. Des suggestions pour implémenter un tel tenseur s'il n'y a pas de structure de données existante?

60
zhongqi

Heureux de suggérer une implémentation (peut-être évidente) de cela, qui pourrait être faite en pur Python ou C/Cython si vous avez le temps et l'espace pour de nouvelles dépendances et que vous en avez besoin pour être plus rapide .

Une matrice clairsemée en N dimensions peut supposer que la plupart des éléments sont vides, nous utilisons donc un dictionnaire basé sur des tuples:

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

et vous l'utiliseriez comme ceci:

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

Vous pouvez rendre cette implémentation plus robuste en vérifiant que l'entrée est en fait un tuple et qu'elle ne contient que des entiers, mais cela ne fera que ralentir les choses, donc je ne m'inquiéterais pas, sauf si vous publiez votre code dans le monde plus tard.

EDIT - une implémentation Cython du problème de multiplication matricielle, en supposant qu'un autre tenseur est un tableau N Dimensional NumPy (numpy.ndarray) pourrait ressembler à ceci:

#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

Bien que vous ayez toujours besoin de le faire à la main pour le problème à résoudre, car (comme mentionné dans le commentaire de code), vous devrez définir les indices que vous additionnez et faire attention aux longueurs de tableau ou les choses ne fonctionneront pas. !

EDIT 2 - si l'autre matrice est également clairsemée, vous n'avez pas besoin de faire la boucle à trois voies:

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

Ma suggestion pour une implémentation C serait d'utiliser une structure simple pour contenir les indices et les valeurs:

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

vous aurez alors besoin de certaines fonctions pour allouer et maintenir un tableau dynamique de ces structures, et les rechercher aussi rapidement que vous le souhaitez; mais vous devez tester l'implémentation Python en place pour les performances avant de vous soucier de ce genre de choses.

14
tehwalrus

Jetez un oeil à sparray - tableaux n-dimensionnels clairsemés en Python (par Jan Erik Solem). Également disponible sur github .

6
eldad-a

Une autre réponse à partir de cette année est le package sparse . Selon le package lui-même, il implémente des tableaux multidimensionnels clairsemés au-dessus de NumPy et scipy.sparse en généralisant le scipy.sparse.coo_matrix disposition.

Voici un exemple tiré de la documentation:

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

Plus agréable que d'écrire tout ce qui est nouveau à partir de zéro peut être d'utiliser autant que possible le module clairsemé de scipy. Cela peut conduire à (beaucoup) de meilleures performances. J'ai eu un problème quelque peu similaire, mais je n'ai eu qu'à accéder efficacement aux données, sans effectuer aucune opération sur celles-ci. De plus, mes données n'étaient que rares dans deux dimensions sur trois.

J'ai écrit un cours qui résout mon problème et pourrait (autant que je pense) être facilement étendu pour répondre aux besoins du PO. Il pourrait cependant encore présenter un certain potentiel d'amélioration.

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