разреженная трехмерная матрица / массив в Python?

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

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

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


person zhongqi    schedule 07.10.2011    source источник
comment
этот пост может помочь stackoverflow.com/questions/4490961/   -  person jayunit100    schedule 09.10.2011
comment
... но, к сожалению, не редкость.   -  person Steve Tjoa    schedule 10.10.2011
comment
Что вы имеете в виду под матрицей в 2D? Если вы имеете в виду матрицу, представляющую двумерное линейное преобразование, то вы говорите о матрице 2x2 вещественных значений (аппроксимированных значениями с плавающей запятой) с определителем 1 для жесткого вращения. Если вы хотите также представить перевод, вы встраиваете матрицу 2x2 в матрицу 3x3, и если вы хотите разрешить сдвиг или расширение / сжатие, вы можете ослабить требование детерминанта, но даже в этом случае это всего 9 значений с плавающей запятой. Почему вы хотите / нуждаетесь в разреженном представлении?   -  person Peter    schedule 12.10.2011
comment
@Peter матрица в 2D означает матрицу в 2 измерениях. Единица в 2d-матрице может быть представлена ​​как (x, y, r), где x & y - координаты, а r - значение, хранящееся в (x, y). Мне нужно разреженное представление, потому что, когда x и y очень большие, скажем, x ‹10 ^ 5, y‹ 10 ^ 4, И только очень мало данных хранится в матрице, скажем 10 ^ 4. numpy предоставляет разреженную матрицу для 2-й матрицы. Но очень часто нам нужны 3d или даже n-d. Думаю, случай n-d слишком общий. Так что любые решения для 3d мне подходят.   -  person zhongqi    schedule 12.10.2011
comment
Спасибо - меня смутил постскриптум. в вашем вопросе (мне показалось, что вы хотели умножить кучу евклидовых кортежей на матрицу в стиле линейной алгебры). Но если вы говорите о матрицах m x n x o, то похоже, что ваша разреженная реализация должна будет предоставить какой-то интерфейс итератора, чтобы вы реализовали (поэлементно) умножение.   -  person Peter    schedule 12.10.2011


Ответы (6)


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

person tehwalrus    schedule 11.10.2011
comment
Проблема в математических операциях, а не в контейнере данных ... Я никогда не слышал об алгоритмах для эффективных разреженных N-мерных тензорных произведений. Посмотрите scipy.sparse.dok_matrix. Это то, что вы здесь описываете, только в 2D. Достаточно легко расширить его для хранения данных N-D, но как вы работаете с данными? (При этом ваш ответ вполне разумен ...) - person Joe Kington; 11.10.2011
comment
ах, я неправильно понял? Итак, этот вопрос больше касается реализации операции умножения матриц, совместимой с scipy? Конечно, это должно быть относительно легко реализовать, поскольку все, что вам действительно нужно для этого, - это запрос в цикле для значения по индексу, который я предоставил. Я все же посмотрю на scipy спецификации. - person tehwalrus; 11.10.2011
comment
Что ж, возможно, я тоже неправильно понял. В любом случае, моя точка зрения заключалась в том, что вы не пользуетесь структурой разреженности при выполнении операций. То, что вы описали в своем редактировании, рассматривает его как плотный массив. (Что, безусловно, работает! Ваш ответ решает возникшую проблему.) Библиотеки разреженных матриц используют преимущества разреженности массива и избегают таких вещей, как зацикливание каждого элемента массива, независимо от разреженности. В этом суть использования разреженной матрицы. Операции примерно масштабируются с учетом количества плотных элементов, а не габаритных размеров матрицы. - person Joe Kington; 11.10.2011
comment
@tehwalrus Спасибо за ответ. Но я боюсь, что умножение на предложенную вами структуру данных может быть не очень эффективным ... - person zhongqi; 12.10.2011
comment
@JoeKington В любом случае вам придется перебирать каждый элемент в неразреженном массиве (u в данном случае)? Если только оба не являются редкими, в этом случае я еще больше не понял. В этом случае вы можете просто перебрать ключи в словаре и извлечь индексы из кортежа. В любом случае, я не разбираюсь в разреженной алгебре, не говоря уже об информатике, стоящей за оптимизацией алгоритмов по этой теме. Извини, @zhongqi! - person tehwalrus; 12.10.2011
comment
Если только оба не редкие ‹- Это идея! :) Извините, я не стал яснее. Я имел в виду операции между двумя разреженными массивами. (Что обычно имеет место, когда вы имеете дело с ситуацией, когда использование разреженных матриц является хорошим решением.) Умножение между двумя разреженными массивами является наиболее распространенным вариантом использования библиотеки разреженных массивов, поэтому для этого нужно приложить много усилий. оптимизируя эту конкретную ситуацию. Реализация эффективного разреженного тензордота (т. Е. Умножения для N-D) нетривиальна. Опять же, ваше решение в порядке. (+1) Это непрактично для больших разреженных массивов, хотя - person Joe Kington; 12.10.2011
comment
@JoeKington edit 2 может помочь, но я не уверен. Однако в какой-то момент вам придется иметь дело с данными, которые там есть, что означает цикл по вашим фиктивным индексам ... - person tehwalrus; 12.10.2011

Альтернативным ответом на 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>
person TomCho    schedule 09.12.2017
comment
@JayShin Может быть, но, честно говоря, я думаю, тебе нужно это проверить. - person TomCho; 23.02.2018
comment
Я рекомендую писать с 2017 года, а не с этого года - person MrMartin; 22.08.2018
comment
Любая причина указывать на эту вилку, а не на исходный (github.com/pydata/sparse источник вилка) одна? Связанный здесь не обновляется с 2018 года, а тот, который я связал, обновлен недавно (4 января 2021 года). - person 0xc0de; 14.02.2021
comment
@ 0xc0de Причина в том, что я не уделял достаточно внимания! Исправил по оригинальной ссылке. Спасибо - person TomCho; 15.02.2021

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

person eldad-a    schedule 20.09.2012

Лучше, чем писать все новое с нуля, может быть, насколько это возможно, использование разреженного модуля 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])])
person Samufi    schedule 07.05.2016
comment
Я в такой же ситуации, и это очень полезно. Я думаю, что с небольшой дополнительной работой это можно было бы реализовать в модуле разреженных массивов scipy. Вы обдумывали это? - person TomCho; 09.12.2017
comment
@TomCho Спасибо! Я не рассматривал возможность реализации этого в разреженном модуле scipy. Я думаю, что реализация в scipy должна поддерживать все стандартные функции матрицы numpy. Это было бы выполнимо, но потребовало бы приличной работы. Кроме того, я думаю, что добавление реализаций C для операций с этими матрицами было бы намного более эффективным и более подходящим для scipy. - person Samufi; 11.12.2017

Мне также нужна разреженная трехмерная матрица для решения двумерных уравнений теплопроводности (2 пространственных измерения плотные, но временное измерение - диагональ плюс и минус одна недиагональ.) Я нашел эта ссылка, которая поможет мне. Уловка состоит в том, чтобы создать массив Number, который отображает двумерную разреженную матрицу на одномерный линейный вектор. Затем постройте 2D-матрицу, построив список данных и индексов. Позже матрица Number используется для упорядочивания ответа обратно в двумерный массив.

[edit] После моего первого сообщения мне пришло в голову, что с этим можно было бы лучше справиться, используя метод .reshape(-1). После исследования метод reshape лучше, чем flatten, потому что он возвращает новое представление в исходный массив, но flatten копирует массив. В коде используется исходный массив Number. Я постараюсь обновить позже. [конец редактирования]

Я тестирую это, создавая одномерный случайный вектор и решая второй вектор. Затем умножьте его на разреженную 2D-матрицу, и я получу тот же результат.

Примечание. Я повторяю это много раз в цикле с точно такой же матрицей M, поэтому вы можете подумать, что было бы более эффективно решить для inverse( M ). Но обратное к 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}")
person Prof Huster    schedule 11.12.2019

Мне нужна была трехмерная справочная таблица для x, y, z, и я нашел это решение ..
Почему бы не использовать одно из измерений в качестве делителя третьего измерения? т.е. используйте x и yz в качестве размеров матрицы

eg. if x has 80 potential members, y has 100 potential' and z has 20 potential' you make the sparse matrix to be 80 by 2000 (i.e. xy=100x20)
x dimension is as usual
yz dimension: the first 100 elements will represent z=0, y=0 to 99
..............the second 100 will represent z=2, y=0 to 99 etc
so given element located at (x,y,z) would be in sparse matrix at (x, z*100 + y)
if you need to use negative numbers design a aritrary offset into your matrix translation. the solutio could be expanded to n dimensions if necessary
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
====================
person Sujit Vasanth    schedule 15.11.2020