List을 통해 구현해본 Sparse Matrix


Linked-List 개념과 코드 구현 : 링크

이번 글에서는 리스트를 응용해서 Sparse Matrix에 대해 이해하고 구현해 본 것을 소개드리고자 합니다.

Sparse Matrix란?

행렬의 값이 대부분인 0인 경우를 가리키는 표현

위키백과

텍스트 데이터를 다루시는분들이라면 한번쯤 보셨을 만한 TDM(Term Document Matrix)가 바로 희소행렬을 활용한 대표적인 예입니다.

출처 : xwMOOC

문서를 vectorization연산이 가능하게 하기 위한 구조로 변환 하기위한 과정입니다.

주로 이용하는는 방법은 한 document안에 위 단어가 있는지 혹은 얼마나 있는지를 행렬로 표현하는데요

Document가 늘어나면 쓰이는 단어도 많아져서 위의 그림을 보시게 되면 컬럼이 많아 질 것이고 0인 값을 가지는 셀도 많아질 것입니다.

결국 DTM(희소행렬)을 저장 할 때, 0도 값(cost)이기 때문에 메모리를 차지합니다.

이를 해결 하기위해서 TF-IDF와 같은 cut-off을 설정하여 column수를 줄이거나 n-gram을 활용하는 방법들이 주로 사용됩니다.

그럼에도 불구하고, 0인 값들을 저장하는 것은 효율적이지 않으므로 이를 효율적으로 저장하기 위한 자료구조를 생각해봅니다.

  • SparseMatrix 자료구조
    • init : 행렬의 dimension m, n 을 입력받아 희소행렬 리스트를 [[m,n,0]]로 초기 설정한다.
    • append method: [i, j, value] 형태의 리스트를 희소행렬 리스트에 추가한다.
    • shape method: 희소행렬 dimension을 리턴한다.
    • getValue method: i, j 행렬값을 리턴한다.
    • print method: 희소행렬을 일반적 행렬 형태로 프린트한다. (0으로 구성된 m,n array를 만들고 i,j 행렬값을 업데이트한다.)
class SparseMatrix:
	def __init__(self, m, n):
		self.s = [[m, n, 0]]
	    self.m = m
	    self.n = n
        
    def append(self,i,j,val):
        if val != 0: 
            self.s.append([i,j,val])
        # s[0][2]에는 DTM에 몇 개의 값을 가진 셀이 있는 지 보여줌
        self.s[0][2] = len(self.s) - 1
        
    
    def shape(self):
        return (self.m, self.n)
    
    def getValue(self, i, j):
        val = 0
        for k in range(1,len(self.s)):
            if self.s[k][0] == i and self.s[k][1] == j:
                return self.s[k][2]
        return 0
            
    
    def print(self):
        import numpy as np
        _tmp = np.zeros((self.m, self.n))
        for i in range(1,len(self.s)):
            _tmp[self.s[i][0]-1,self.s[i][1]-1] = self.s[i][2]
        print(_tmp)


EX) 3*3행렬 생성


a = SparseMatrix(3,3)
b = SparseMatrix(3,3)

a.append(1,1,1)
a.append(2,2,2)
a.append(3,3,3)
b.append(1,1,4)
b.append(1,2,7)
b.append(2,3,2)
b.append(3,3,1)

a.print()
b.print()

print(a.getValue(3,3))

  • 희소행렬의 전치행렬 알고리즘 : 희소행렬의 행 [i,j,val] 을 [j, i, val] 로 바꿔 리턴한다.

  • 두 희소행렬의 덧셈 알고리즘:

    • 덧셈 결과를 저장할 빈 희소행렬 c를 만든다.
    • 두 희소행렬 a, b의 unique 한 (i,j) 쌍의 집합을 u 라고 하자.
    • u에서 원소를 하나씩 꺼내면서 a, b의 해당 위치 값을 구해 더한다.
    • 위에서 구한 값이 0이 아니면 c에 추가한다.
def transpose(a):
#행과 열의 값을 바꿔서 저장
    t_a = SparseMatrix(a.n,a.m)
    
# 첫번째 값은 그대로 몇개가 들어있는지 
# 그외에는 행과 열의 값을 바꿔서!
    for i in range(len(a.s)):
        if i == 0:
            t_a.s[0][2] = a.s[0][2]
        else:
            t_a.append(a.s[i][1],a.s[i][0],a.s[i][2])

    return t_a

# EX

t_b = transpose(b)
t_b.print()


def add(a, b):
    if a.shape() != b.shape():
        print("두 행렬의 차원이 달라 더할 수 없습니다.")
    else:
        c = SparseMatrix(a.shape()[0], a.shape()[1])
        u = set()
        for i in range(1,len(a.s)):
            u.add((a.s[i][0], a.s[i][1]))
        for i in range(1,len(b.s)):
            u.add((b.s[i][0], b.s[i][1]))
        for item in u:
            _tmp = a.getValue(item[0], item[1]) + b.getValue(item[0], item[1])
            if _tmp != 0:
                c.append(item[0], item[1], _tmp) 
    return c

# EX

a.print()
b.print()
c = add(a, b)
c.print()

위와 같이 Sparse Matrix을 구현해봤습니다.

TDM은 어떻게 구현이 되는 지 보고싶은 분들은 sklearn의 CountVectorizer source에서_count_vocab을 참고하시면 좋을 것 같습니다!




© 2018. by Gangmin

Powered by zzsza