разреженная 3d матрица / массив в Python? - PullRequest
60 голосов
/ 07 октября 2011

В scipy мы можем построить разреженную матрицу, используя scipy.sparse.lil_matrix () и т. Д. Но матрица находится в 2d.

Мне интересно, существует ли существующая структура данных для разреженной 3d матрицы / массива (тензора) в Python?

p.s. У меня есть много разреженных данных в 3D и мне нужен тензор для хранения / выполнения умножения. Любые предложения для реализации такого тензора, если нет существующей структуры данных?

Ответы [ 4 ]

14 голосов
/ 11 октября 2011

Рад предложить (возможно, очевидную) реализацию этого, которая может быть сделана на чистом 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 - реализация на 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

Хотя вам всегда нужно будет свернуть это вручную для решения проблемы, потому что (как упомянуто в комментарии к коду) вам нужно будет определить, по каким показателям вы суммируете, и быть осторожнымl о длине массива, иначе вещи не будут работать!

РЕДАКТИРОВАТЬ 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 на производительность, прежде чем беспокоиться об этом.

6 голосов
/ 21 сентября 2012

Взгляните на sparray - разреженные n-мерные массивы в Python (автор Ян Эрик Солем).Также доступно на github .

4 голосов
/ 09 декабря 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>
3 голосов
/ 08 мая 2016

Лучше, чем писать все новое с нуля, возможно, использовать редкий модуль scipy, насколько это возможно.Это может привести к (намного) лучшей производительности.У меня была несколько похожая проблема, но мне нужно было только эффективно обращаться к данным, а не выполнять над ними какие-либо операции.Более того, мои данные были скудны только в двух из трех измерений.

Я написал класс, который решает мою проблему и может (насколько я думаю) легко расширяться для удовлетворения потребностей ОП.Тем не менее, он все еще может иметь некоторый потенциал для улучшения.

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