8.2: Розріджені формати матриць
- Page ID
- 79578
8.2.1 Список списків (LIL)
Формат розрідженої матриці List of Lists (який з невідомих причин скорочено як LIL, а не LOL) є одним з найпростіших розріджених форматів матриць. Він схематично зображений на малюнку нижче. Кожен ненульовий рядок матриці представлений елементом у своєрідній структурі списку, яка називається «зв'язаним списком». Кожен елемент зв'язаного списку записує номер рядка та дані стовпця для записів матриці в цьому рядку. Дані стовпця складаються зі списку, де кожен елемент списку відповідає ненульовому елементу матриці, і зберігає інформацію про (i) номер стовпця та (ii) значення елемента матриці.

Очевидно, цей формат досить ефективний з пам'яттю. Список рядків повинен бути лише таким довгим, як кількість ненульових рядків матриці; решта не вказані. Аналогічним чином, кожен список даних стовпців повинен бути лише такою довгою, як кількість ненульових елементів у цьому рядку. Загальний обсяг необхідної пам'яті пропорційний кількості ненульових елементів незалежно від розміру самої матриці.
У порівнянні з іншими розрідженими форматами матриць, які ми обговоримо, доступ до окремого елемента матриці у форматі LIL є відносно повільним. Це пов'язано з тим, що пошук заданого матричного індексу\((i,j)\) вимагає кроку через список рядків, щоб знайти елемент з індексом рядка\(i\); і якщо він знайдений, крокуючи рядок стовпця, щоб знайти індекс\(j\). Так, наприклад, пошук елемента в діагональній\(N\times N\) матриці в форматі LIL вимагає\(O(N)\) часу! Як ми побачимо, CSR та CSC формати набагато ефективніші при доступі до елементів. З цієї ж причини матрична арифметика в форматі LIL дуже неефективна.
Однією з переваг формату LIL, однак, є те, що змінити «структуру розрідженості» матриці відносно легко. Щоб додати новий ненульовий елемент, потрібно просто перейти до списку рядків і (i) вставити новий елемент у зв'язаний список, якщо рядок раніше не був у списку (ця вставка вимагає\(O(1)\) часу), або (ii) змінити список стовпців (який зазвичай дуже короткий, якщо матриця дуже розріджена).
З цієї причини формат LIL є кращим, якщо вам потрібно побудувати розріджену матрицю, де ненульові елементи не розподіляються жодним корисним шаблоном. Один із способів - створити порожню матрицю, а потім заповнити елементи по черзі, як показано в наступному прикладі. Матриця LIL створюється функцією lil_matrix
, яка забезпечується модулем scipy.sparse
.
Ось приклад програми, яка будує матрицю LIL і друкує її:
from scipy import * import scipy.sparse as sp A = sp.lil_matrix((4,5)) # Create empty 4x5 LIL matrix A[0,1] = 1.0 A[1,1] = 2.0 A[1,2] = -1.0 A[3,0] = 6.6 A[3,4] = 1.4 ## Verify the matrix contents by printing it print(A)
Коли ми запускаємо вищевказану програму, вона відображає ненульові елементи розрідженої матриці:
(0, 1) 1.0 (1, 1) 2.0 (1, 2) -1.0 (3, 0) 6.6 (3, 4) 1.4
Ви також можете перетворити розріджену матрицю в звичайний 2D масив, використовуючи метод toarray
. Припустимо, ми замінюємо рядок print (A)
у вищевказаній програмі на
B = A.toarray() print(B)
В результаті виходить:
[[ 0. 1. 0. 0. 0. ] [ 0. 2. -1. 0. 0. ] [ 0. 0. 0. 0. 0. ] [ 6.6 0. 0. 0. 1.4]]
Примітка: будьте обережні при виклику toarray
в фактичному коді. Якщо матриця величезна, 2D масив з'їсть необгрунтовані обсяги пам'яті. Нерідкі випадки роботи з розрідженими матрицями з розмірами на замовлення, які можуть займати менше 1 Мб пам'яті в розрідженому форматі, але близько 80 Гб пам'яті в форматі масиву!
8.2.2 Діагональне сховище (DIA)
Формат Diagonal Storage (DIA) зберігає вміст розрідженої матриці уздовж її діагоналей. Він використовує 2D масив, який ми позначаємо даними
, і 1D цілого масиву, який ми позначаємо зміщеннями
. Типовий приклад показаний на наступному малюнку:

Кожен рядок масиву даних
зберігає одну з діагоналей матриці, а зміщення [i]
записує діагональ, якій відповідає цей рядок даних
, зі зміщенням 0, відповідним основній діагоналі. Наприклад, у наведеному вище прикладі рядок\(0\) даних
містить записи [6.6,0,0,0,0]
), а зміщення [0]
містить значення, що вказує на те\(-3\), що запис\(6.6\) відбувається вздовж\(-3\) піддіагоналі, у стовпці\(0\). (Додаткові елементи в цьому рядку даних
лежать за межами матриці і ігноруються.) Діагоналі, що містять тільки нуль, опущені.
Для розріджених матриць з дуже мало ненульових діагоналей, таких як діагональні або тридіагональні матриці, формат DIA дозволяє виконувати дуже швидкі арифметичні операції. Його основне обмеження полягає в тому, що пошук кожного елемента матриці вимагає виконання сліпого пошуку через масив зсувів
. Це нормально, якщо не нульових діагоналей дуже мало, так як зсуви
будуть невеликими. Але якщо кількість ненульових діагоналей стає великим, продуктивність стає дуже поганою. У найгіршому випадку антидіагональної матриці пошук елементів вимагає\(O(N)\) часу!
Створити розріджену матрицю в форматі DIA можна за допомогою функції dia_matrix
, яка забезпечується модулем scipy.sparse
. Ось приклад програми:
from scipy import * import scipy.sparse as sp N = 6 # Matrix size diag0 = -2 * ones(N) diag1 = ones(N) A = sp.dia_matrix(([diag1, diag0, diag1], [-1,0,1]), shape=(N,N)) ## Verify the matrix contents by printing it print(A.toarray())
Тут першим входом в dia_matrix
є кортеж форми (дані, зсуви)
, де дані
і зсуви є
масивами описаного вище сорту. Це повертає розріджену матрицю у форматі DIA з вказаним вмістом (елементи даних
, які лежать за межами матриці, ігноруються). У цьому прикладі матриця тридіагональна з -2 по головній діагоналі і 1 по діагоналям +1 і -1. Запуск вищевказаної програми виводить наступне:
[[-2. 1. 0. 0. 0. 0.] [ 1. -2. 1. 0. 0. 0.] [ 0. 1. -2. 1. 0. 0.] [ 0. 0. 1. -2. 1. 0.] [ 0. 0. 0. 1. -2. 1.] [ 0. 0. 0. 0. 1. -2.]]
Інший спосіб створення матриці DIA - спочатку створити матрицю в іншому форматі (наприклад, звичайний 2D масив), і надати це як вхід для dia_matrix
. Це повертає розріджену матрицю з однаковим вмістом у форматі DIA.
8.2.3 Стиснутий розріджений рядок (CSR)
Формат стисненого розрідженого рядка (CSR) являє собою розріджену матрицю з використанням трьох масивів, які ми позначаємо даними
, індексами
та indptr
. Приклад показаний на наступному малюнку:

Масив, що позначається даними
, зберігає значення ненульових елементів матриці, в послідовному порядку зліва направо уздовж кожного ряду, потім з верхнього ряду в нижній. Масив, що позначається індексами
, записує індекс стовпця для кожного з цих елементів. У наведеному вище прикладі дані [3] зберігають
значення\(6.6\), а індекси [3]
мають значення\(0\), що вказує на те, що елемент матриці зі значенням\(6.6\) зустрічається в стовпці\(0\). Ці два масиви мають однакову довжину, рівну кількості ненульових елементів в розрідженій матриці.
Масив, що позначається indptr
(що розшифровується як «покажчик індексу») забезпечує зв'язок між індексами рядків і елементами матриці, але непрямим чином. Його довжина дорівнює кількості рядків матриці (включаючи нульові рядки). Для кожного рядка\(i\), якщо рядок ненульовий, indptr [i]
записує індекс у масивах даних
та індексів
, що відповідають першому ненульовому елементу на рядку\(i\). (Для нульового рядка indptr
записує індекс наступного ненульового елемента, що зустрічається в матриці.)
Наприклад, розглянемо пошук індексу\((1,2)\) на наведеному вище малюнку. Індекс рядка дорівнює 1, тому ми вивчаємо indptr [1]
(чиє значення\(1\)) і indptr [2]
(чиє значення\(3\)). Це означає, що ненульові елементи для\(1 \le n < 3\) рядків матриці\(1\) відповідають індексам масивів даних
та індексів
. Ми шукаємо індекси [1]
та індекси [2]
, шукаємо індекс стовпця\(2\). Це знайдено в індексах [2]
, тому ми шукаємо в даних [2]
значення елемента матриці, який є\(-1.0\).
Зрозуміло, що пошук окремого елемента матриці дуже ефективний. На відміну від формату LIL, де нам потрібно пройти через зв'язаний список, у форматі CSR масив indptr
дозволяє нам перейти прямо до даних для відповідного рядка. З цієї ж причини формат CSR є ефективним для операцій розрізання рядків (наприклад, A [4,:]),
а також для матрично-векторних добутків типу\(\mathbf{A}\vec{x}\) (який передбачає взяття добутку кожного рядка матриці з вектором\(\vec{x}\)).
Формат CSR має кілька мінусів. Нарізка стовпців (наприклад, A [:,4]
) неефективна, оскільки вимагає пошуку по всіх елементах масиву індексів
для відповідного індексу стовпця. Зміни в структурі розрідженості (наприклад, вставлення нових елементів) також дуже неефективні, оскільки всі три масиви повинні бути перевпорядковані.
Для створення розрідженої матриці в форматі CSR використовуємо функцію csr_matrix
, яка забезпечується модулем scipy.sparse
. Ось приклад програми:
from scipy import * import scipy.sparse as sp data = [1.0, 2.0, -1.0, 6.6, 1.4] rows = [0, 1, 1, 3, 3] cols = [1, 1, 2, 0, 4] A = sp.csr_matrix((data, [rows, cols]), shape=(4,5)) print(A)
Тут першим входом до csr_matrix
є кортеж форми (data, idx),
де data
є 1D масивом із зазначенням ненульових елементів матриці, idx [0,:]
вказує індекси рядків, а idx [1,:]
вказує індекси стовпців. Запуск програми дає очікувані результати:
(0, 1) 1.0 (1, 1) 2.0 (1, 2) -1.0 (3, 0) 6.6 (3, 4) 1.4
Функція csr_matrix
з'ясовує і генерує три CSR масиви автоматично; вам не потрібно розробляти їх самостійно. Але якщо вам подобається, ви можете оглянути вміст CSR масивів безпосередньо:
>>> A.data array([ 1. , 2. , -1. , 6.6, 1.4]) >>> A.indices array([1, 1, 2, 0, 4], dtype=int32) >>> A.indptr array([0, 1, 3, 3, 5], dtype=int32)
(Існує додатковий кінцевий елемент\(5\) у масиві indptr
. Для простоти ми не згадували про це в вищезгаданому обговоренні, але ви повинні бути в змозі зрозуміти, чому це там.)
Іншим способом створення CSR-матриці є створення матриці в іншому форматі (наприклад, звичайний 2D масив або розріджена матриця у форматі LIL), і надати це як вхід до csr_matrix
. Це повертає вказану матрицю у форматі CSR. Наприклад:
>>> A = sp.lil_matrix((6,6)) >>> A[0,1] = 4.0 >>> A[2,0] = 5.0 >>> B = sp.csr_matrix(A)
8.2.4 Стиснутий розріджений стовпець (CSC)
Формат стисненого розрідженого стовпця (CSC) дуже схожий на формат CSR, за винятком того, що роль рядків і стовпців міняється місцями. Масив даних
зберігає ненульові елементи матриці в послідовному порядку зверху вниз уздовж кожного стовпця, потім від крайнього лівого стовпця до крайнього правого. Масив індексів
зберігає індекси рядків, і кожен елемент масиву indptr
відповідає одному стовпцю матриці. Приклад наведено нижче:

Формат CSC ефективний при пошуку матриці, операціях розрізу стовпців (наприклад, A [:,4]
) та таких векторно-матричних продуктів, як\(\vec{x}^{\,T} \mathbf{A}\) (що передбачає взяття добутку вектора\(\vec{x}\) з кожним стовпцем матриці). Однак це неефективно для нарізки рядків (наприклад, A [4,:]
) та для зміни структури розрідженості.
Для створення розрідженої матриці в форматі CSC використовуємо функцію csc_matrix
. Це аналогічно функції csr_matrix
для CSR матриць. Наприклад,
>>> from scipy import * >>> import scipy.sparse as sp >>> data = [1.0, 2.0, -1.0, 6.6, 1.4] >>> rows = [0, 1, 1, 3, 3] >>> cols = [1, 1, 2, 0, 4] >>> >>> A = sp.csc_matrix((data, [rows, cols]), shape=(4,5)) >>> A <4x5 sparse matrix of type '<class 'numpy.float64'>' with 5 stored elements in Compressed Sparse Column format>