Python/Scipy

[Scipy] 파이썬 희소 행렬 변환(CSR, COO, DOK), 배열 역변환, 연산 방법 정리

jimmy_AI 2022. 11. 24. 15:24
반응형

Python Sparse Matrix

파이썬에서 0의 비중이 높은 행렬인 희소 행렬을 효율성을 위하여 3가지 대표 방법인

CSR, COO, DOK 방법으로 변환할 수 있는 Scipy의 기능을 살펴보고

배열 역변환 및 연산에 관한 예제도 같이 정리해보도록 하겠습니다.

 

 

희소 행렬 변환 방법(CSR, COO, DOK)

먼저, CSR, COO, DOK의 3가지 희소 행렬 표현 방법에 대해서 간략하게 설명하겠습니다.

 

CSR : 각 row별로 0이 아닌 개수의 누적합을 이용해서 어느 행에 속하는지를 추적하고,

인덱스 번호(열의 위치)는 따로 저장하여 각 데이터가 저장된 위치를 찾는 방식입니다.

참고로, CSR은 연산이 빠르다는 장점 덕분에 많이 사용되는 방식입니다.

 

COO : 0이 아닌 데이터가 저장된 위치의 row, column 번호를 각각 순서대로 저장하여

각 데이터가 저장된 위치를 찾는 방식입니다.

 

DOK : 딕셔너리와 유사한 방식으로 (row, column) pair를 key로,

해당 데이터의 값을 value로 저장하여 희소 행렬을 표현하는 방식입니다.

참고로, DOK은 수정 과정에 유리하다는 특징을 가지고 있습니다.

이제 파이썬 Scipy를 활용하여 위의 희소 행렬 표현 방식들을

다음과 같은 행렬 A를 기준으로 나타내는 방법을 살펴보겠습니다.

import numpy as np

A = np.array([[0, 0, 0, 1],
              [0, 0, 0, 0],
              [3, 0, 0, 0],
              [0, 0, 2, 1]])

 

 

CSR 변환

csr_matrix 메소드를 통하여 아래처럼 쉽게 변환이 가능합니다.

각 행 기준 누적합, 열 위치 인덱스, 저장된 데이터는 각각

indptr, indices, data 속성으로 조회가 가능합니다.

from scipy.sparse import csr_matrix

A_csr = csr_matrix(A)

print(f'indptr: {A_csr.indptr}')
print(f'indices: {A_csr.indices}')
print(f'data: {A_csr.data}')

# 출력 결과
indptr: [0 1 1 2 4]
indices: [3 0 2 3]
data: [1 3 2 1]

 

CSR의 경우 값 접근 및 값 수정을 배열 인덱싱 방법과 유사하게 진행할 수 있습니다.

print(A_csr[0, 0]) # 0
print(A_csr[0, 3]) # 1

A_csr[1, 0] = 0

 

반응형

 

COO 변환

COO도 coo_matrix 메소드로 마찬가지 변환이 쉽게 가능합니다.

여기서는 row, col, data 속성으로 각 데이터가 저장된 행, 열, 값 정보를

가져올 수 있습니다.

from scipy.sparse import coo_matrix

A_coo = coo_matrix(A)

print(f'row: {A_coo.row}')
print(f'col: {A_coo.col}')
print(f'data: {A_coo.data}')

# 출력 결과
row: [0 2 3 3]
col: [3 0 2 3]
data: [1 3 2 1]

 

다만, COO 방법의 경우는 인덱싱을 통한 값 접근 등이 어렵습니다.

print(A_coo[0, 0]) # TypeError: 'coo_matrix' object is not subscriptable

 

 

DOK 변환

DOK의 경우도 dok_matrix 메소드로 같은 방식으로 변환이 가능합니다.

여기서는 딕셔너리와 유사하게 keys(), values() 메소드로

저장된 값들의 위치 키각 위치에 저장된 값들의 목록을 가져올 수 있습니다.

from scipy.sparse import dok_matrix

A_dok = dok_matrix(A)

print(f'keys: {A_dok.keys()}')
print(f'values: {A_dok.values()}')

# 출력 결과
keys: dict_keys([(0, 3), (2, 0), (3, 2), (3, 3)])
values: dict_values([1, 3, 2, 1])

 

 

희소 행렬 -> 넘파이 배열로 역변환 방법

희소 행렬을 numpy array로 역변환 하는 방법은 매우 간단합니다.

위의 세 방법 모두 .toarray() 메소드를 적용하면 넘파이 배열 역변환이 가능합니다.

# A1, A2, A3 모두 기존 넘파이 배열 A와 동일
A1 = A_csr.toarray()
A2 = A_coo.toarray()
A3 = A_dok.toarray()

 

 

희소 행렬 표현의 연산(행렬합, 행렬곱 등)

희소 행렬 표현에 대하여 행렬합, 행렬곱 등 연산 방법은

넘파이 배열과 유사하게 진행이 가능합니다.

 

참고로, CSR, COO, DOK 표현 간의 연산도 자유롭게 가능한 편이며,

희소 행렬 표현과 일반 넘파이 배열 간의 연산도 제약이 크지 않습니다.

 

먼저, 서로 다른 희소 행렬 표현끼리의 행렬합 예시입니다.

(여기서는 B의 결과가 CSR 희소 행렬 표현으로 반환됩니다.)

B = A_csr + A_coo + A_dok
B.toarray()
"""
array([[0, 0, 0, 3],
       [0, 0, 0, 0],
       [9, 0, 0, 0],
       [0, 0, 6, 3]])"""

 

이번에는 CSR 희소 행렬 표현과 일반 넘파이 배열 간의 행렬곱 예시입니다.

(여기서는 C의 결과 배열이 넘파이 배열로 반환됩니다.)

C = A_csr @ A
C
"""
array([[0, 0, 2, 1],
       [0, 0, 0, 0],
       [0, 0, 0, 3],
       [6, 0, 2, 1]])"""