'Розумна' симетрична матриця Numpy


74

Чи існує розумна та економічно ефективна симетрична матриця в numpy, яка автоматично (і прозоро) заповнює позицію, [j][i]коли [i][j]в неї записується?

import numpy
a = numpy.symmetric((3, 3))
a[0][1] = 1
a[1][0] == a[0][1]
# True
print(a)
# [[0 1 0], [1 0 0], [0 0 0]]

assert numpy.all(a == a.T) # for any symmetric matrix

Автоматичний Ермітаж також був би непоганим, хоча мені це не знадобиться на момент написання статті.


Ви можете розглянути можливість позначення відповіді як прийнятої, якщо це вирішить вашу проблему. :)
Ерік Лебіго

Я хотів дочекатися кращої (тобто вбудованої та ефективної пам’яті) відповіді. Звісно, ​​у вашій відповіді немає нічого поганого, тому я все одно прийму її.
Debilski

Відповіді:


83

Якщо ви можете дозволити собі симетризувати матрицю безпосередньо перед проведенням обчислень, наступне повинно бути досить швидким:

def symmetrize(a):
    """
    Return a symmetrized version of NumPy array a.

    Values 0 are replaced by the array value at the symmetric
    position (with respect to the diagonal), i.e. if a_ij = 0,
    then the returned array a' is such that a'_ij = a_ji.

    Diagonal values are left untouched.

    a -- square NumPy array, such that a_ij = 0 or a_ji = 0, 
    for i != j.
    """
    return a + a.T - numpy.diag(a.diagonal())

Це працює за розумних припущень (таких, як не робити обох a[0, 1] = 42і суперечливих a[1, 0] = 123до запуску symmetrize).

Якщо вам дійсно потрібна прозора симетризація, ви можете розглянути підклас numpy.ndarray і просто перевизначити __setitem__:

class SymNDArray(numpy.ndarray):
    """
    NumPy array subclass for symmetric matrices.

    A SymNDArray arr is such that doing arr[i,j] = value
    automatically does arr[j,i] = value, so that array
    updates remain symmetrical.
    """

    def __setitem__(self, (i, j), value):
        super(SymNDArray, self).__setitem__((i, j), value)                    
        super(SymNDArray, self).__setitem__((j, i), value)                    

def symarray(input_array):
    """
    Return a symmetrized version of the array-like input_array.

    The returned array has class SymNDArray. Further assignments to the array
    are thus automatically symmetrized.
    """
    return symmetrize(numpy.asarray(input_array)).view(SymNDArray)

# Example:
a = symarray(numpy.zeros((3, 3)))
a[0, 1] = 42
print a  # a[1, 0] == 42 too!

(або еквівалент з матрицями замість масивів, залежно від ваших потреб). Цей підхід навіть обробляє більш складні завдання, наприклад a[:, 1] = -1, які правильно встановлюють a[1, :]елементи.

Зверніть увагу, що Python 3 усунув можливість написання def …(…, (i, j),…), тому перед запуском з Python 3 код повинен бути трохи адаптований: def __setitem__(self, indexes, value): (i, j) = indexes


5
Насправді, якщо ви підкласуєте його, вам не слід перезаписувати setitem , а скоріше getitem, щоб не створювати більше накладних витрат на створення матриці.
Markus

1
Це дуже цікава ідея, але писати це як еквівалент __getitem__(self, (i, j))не вдається, коли виконується простий printмасив екземпляра підкласу. Причина в тому, що printдзвінки __getitem__()з цілочисельним індексом, тому потрібно більше роботи навіть для простого print. Рішення з __setitem__()працює з print(очевидно), але страждає від подібної проблеми: a[0] = [1, 2, 3]не працює, з тієї ж причини (це не ідеальне рішення). __setitem__()Рішення має ту перевагу, що більш надійним, так як масив в пам'яті є правильним. Не дуже погано. :)
Ерік Лебіго

ваша пропозиція звучить як blog.sopticek.net/2016/07/24/… ... Ви підтверджуєте, що це майже те саме? Проблема полягає в тому, що це оптимізує використання пам'яті, а не обчислювальний час. Я шукаю методи python для прискорення деяких простих обчислень на симетричних матрицях. будь ласка, дайте мені знати, якщо у вас є інформація.
Стефан,

Ця відповідь не економить пам'ять, і тому вона сильно відрізняється від підходу в цитованому посиланні. Зараз економія часу за допомогою симетричних матриць зазвичай передбачає проходження спеціалізованих алгоритмів замість загальних, наприклад використання восьми () у NumPy замість eig ().
Ерік Лебіго

20

Більш загальне питання оптимального лікування симетричних матриць в numpy також мене хвилювало.

Подивившись на це, я думаю, що відповідь, мабуть, полягає в тому, що numpy дещо обмежений макетом пам'яті, підтримуваним базовими процедурами BLAS для симетричних матриць.

Хоча деякі підпрограми BLAS використовують симетрію, щоб пришвидшити обчислення на симетричних матрицях, вони все одно використовують ту ж структуру пам'яті, що і повна матриця, тобто n^2простір, а не n(n+1)/2. Просто їм повідомляють, що матриця симетрична і використовувати лише значення у верхньому чи нижньому трикутнику.

Деякі scipy.linalgпідпрограми приймають прапори (наприклад, sym_pos=Trueon linalg.solve), які передаються підпрограми BLAS, хоча було б непогано додатково підтримувати це в numpy, зокрема обгортки для підпрограм, таких як DSYRK (симетричне оновлення k), що дозволило б матрицю Грама для обчислення трохи швидше, ніж крапка (MT, M).

(Може здатися неприємним турбуватися про оптимізацію для 2x постійного коефіцієнта часу та / або простору, але це може змінити цей поріг наскільки великою проблемою ви можете управляти на одній машині ...)


Питання полягає в тому, як автоматично створити симетричну матрицю шляхом присвоєння одного запису (а не в тому, як BLAS можна доручити використовувати симетричні матриці у своїх розрахунках або як симетричні матриці в принципі можуть зберігатися ефективніше).
Eric O Lebigot

3
Питання також стосується космічної ефективності, тому питання BLAS є актуальними.
jmmcd

@EOL, питання не в тому, як автоматично створити симетричну матрицю шляхом призначення одного запису.
Олексій

Звичайно, "створення" можна доречніше замінити на "оновлене". Тепер, оскільки питання явно стосується прозорого встановлення M_ji, коли встановлено M_ji, і ця відповідь не про це, ви розумієте, що це по суті питання, яке я підняв. Питання полягає в тому, як ефективно це зробити (а не в тому, щоб ефективно обробляти симетричні матриці, хоча це може бути правильним питанням: щось краще поставити в коментарях або дати як відповідь, яка вирішує більш загальну проблему, а не просто обговорювати її) ).
Ерік Лебігот

7

Існує ряд добре відомих способів зберігання симетричних матриць, тому їм не потрібно займати n ^ 2 елементів зберігання. Більше того, можливо переписати загальні операції для доступу до цих переглянутих засобів зберігання. Остаточна робота - Голуб і Ван Лоан, Матричні обчислення , 3-е видання 1996 р., Університет Джонса Гопкінса, розділи 1.27-1.2.9. Наприклад, цитуючи їх із форми (1.2.2), у симетричній матриці потрібно лише зберігати A = [a_{i,j} ]для i >= j. Тоді, припускаючи , що вектор , що утримує матрицю позначимо V, і що А є п-по-п, покласти a_{i,j}в

V[(j-1)n - j(j-1)/2 + i]

Це передбачає 1-індексацію.

Голуб та Ван Позика пропонують алгоритм 1.2.3, який показує, як отримати доступ до такого збереженого V для розрахунку y = V x + y.

Голуб і Ван Лоан також забезпечують спосіб зберігання матриці в діагональній домінантній формі. Це не економить пам’ять, але підтримує готовий доступ для деяких інших видів операцій.


1
Існує також Прямокутна пам'ять (RFP), наприклад, Lapack ZPPTRF використовує її. Чи підтримується Numpy?
isti_spl

@isti_spl: Ні, але ти міг би реалізувати обгортку, яка це робить
Ерік

1

Це звичайний python, а не numpy, але я просто зібрав рутину, щоб заповнити симетричну матрицю (і тестову програму, щоб переконатися, що вона правильна):

import random

# fill a symmetric matrix with costs (i.e. m[x][y] == m[y][x]
# For demonstration purposes, this routine connect each node to all the others
# Since a matrix stores the costs, numbers are used to represent the nodes
# so the row and column indices can represent nodes

def fillCostMatrix(dim):        # square array of arrays
    # Create zero matrix
    new_square = [[0 for row in range(dim)] for col in range(dim)]
    # fill in main diagonal
    for v in range(0,dim):
        new_square[v][v] = random.randrange(1,10)

    # fill upper and lower triangles symmetrically by replicating diagonally
    for v in range(1,dim):
        iterations = dim - v
        x = v
        y = 0
        while iterations > 0:
            new_square[x][y] = new_square[y][x] = random.randrange(1,10)
            x += 1
            y += 1
            iterations -= 1
    return new_square

# sanity test
def test_symmetry(square):
    dim = len(square[0])
    isSymmetric = ''
    for x in range(0, dim):
        for y in range(0, dim):
            if square[x][y] != square[y][x]:
                isSymmetric = 'NOT'
    print "Matrix is", isSymmetric, "symmetric"

def showSquare(square):
    # Print out square matrix
    columnHeader = ' '
    for i in range(len(square)):
        columnHeader += '  ' + str(i)
    print columnHeader

    i = 0;
    for col in square:
        print i, col    # print row number and data
        i += 1

def myMain(argv):
    if len(argv) == 1:
        nodeCount = 6
    else:
        try:
            nodeCount = int(argv[1])
        except:
            print  "argument must be numeric"
            quit()

    # keep nodeCount <= 9 to keep the cost matrix pretty
    costMatrix = fillCostMatrix(nodeCount)
    print  "Cost Matrix"
    showSquare(costMatrix)
    test_symmetry(costMatrix)   # sanity test
if __name__ == "__main__":
    import sys
    myMain(sys.argv)

# vim:tabstop=8:shiftwidth=4:expandtab

0

Питонічно заповнювати, [i][j]якщо [j][i]заповнено, тривіально . Питання про зберігання є трохи цікавішим. Можна збільшити клас масиву numpy за допомогою packedатрибута, який корисний як для економії пам’яті, так і для подальшого зчитування даних.

class Sym(np.ndarray):

    # wrapper class for numpy array for symmetric matrices. New attribute can pack matrix to optimize storage.
    # Usage:
    # If you have a symmetric matrix A as a shape (n,n) numpy ndarray, Sym(A).packed is a shape (n(n+1)/2,) numpy array 
    # that is a packed version of A.  To convert it back, just wrap the flat list in Sym().  Note that Sym(Sym(A).packed)


    def __new__(cls, input_array):
        obj = np.asarray(input_array).view(cls)

        if len(obj.shape) == 1:
            l = obj.copy()
            p = obj.copy()
            m = int((np.sqrt(8 * len(obj) + 1) - 1) / 2)
            sqrt_m = np.sqrt(m)

            if np.isclose(sqrt_m, np.round(sqrt_m)):
                A = np.zeros((m, m))
                for i in range(m):
                    A[i, i:] = l[:(m-i)]
                    A[i:, i] = l[:(m-i)]
                    l = l[(m-i):]
                obj = np.asarray(A).view(cls)
                obj.packed = p

            else:
                raise ValueError('One dimensional input length must be a triangular number.')

        elif len(obj.shape) == 2:
            if obj.shape[0] != obj.shape[1]:
                raise ValueError('Two dimensional input must be a square matrix.')
            packed_out = []
            for i in range(obj.shape[0]):
                packed_out.append(obj[i, i:])
            obj.packed = np.concatenate(packed_out)

        else:
            raise ValueError('Input array must be 1 or 2 dimensional.')

        return obj

    def __array_finalize__(self, obj):
        if obj is None: return
        self.packed = getattr(obj, 'packed', None)

``


0

Щоб побудувати матрицю NxN, симетричну вздовж головної діагоналі, і з нулями на головній діагоналі, ви можете зробити:

a = np.array([1, 2, 3, 4, 5])
b = np.zeros(shape=(a.shape[0], a.shape[0]))
upper = np.triu(b + a)
lower = np.tril(np.transpose(b + a))
D = (upper + lower) * (np.full(a.shape[0], fill_value=1) - np.eye(a.shape[0]))

Це свого роду особливий випадок, але нещодавно я використовував такий тип матриці для представлення суміжності мережі.

Сподіваюся, що це допомагає. Ура.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.