[텍스트분석3.2] BOW피쳐 벡터화 - TfidfVectorizer
요즘 추천시스템이랑 텍스트분석쪽에 관심이 생겨서 책을 찾아보았다.
사실 이미 한번 프로젝트(?)로 수행했지만 훑어보고 간단하게 넘어간것 같아서..
다시한번 짚어보는 목적! 튼튼한 기본이 단단한 기초가 중요하니까!
아래의 내용은 파이썬 머신러닝 완벽가이드의 책의 [8.텍스트분석] 읽고 정리한 내용입니다
이전에 살펴본 텍스트분석 프로세스에 따라 두번째 단계인 피쳐 벡터화 작업! 를 살펴본다.
- 텍스트 사전준비 (텍스트 전처리) : 텍스트를 벡터로 만들기 전에 토큰화작업
- 피쳐 벡터화/추출 : 가공된 텍스트에서 피쳐를 추출하고, 여기에 벡터값을 할당하는 작업
- ML모델 수립 및 학습/예측/평가 : 피쳐 벡터화된 데이터세트에 ML모델을 적용
이전에 BOW피쳐에 알아본 BOW피쳐 벡터화 첫번째 방법인 Count기반에 이어서 tf-idf기반 살펴보기
2021.09.05 - [텍스트분석3.1] BOW피쳐 벡터화 - CountVectorizer
BOW 피쳐 벡터화 방법2- TF-IDF기반의 벡터화
카운트기반 벡터화의 문제를 보완하는 방식이 TF-IDF 방식
개별문서에서 자주 나타나는 단어에 높은 가중치를 주죄,
모든 문서에서 전반적으로 자주 나타는 단어에 대해서는 패널티를 주는 방식으로 값을 부여하는 방식.
만일 어떤 문서에서 특정 단어가 자주 나타난다면 그 단어는 해당문서를 특정짓는 중요단어일수도 있다.
하지만 그 단어가 다른문서에도 자주 나타나면 그냥 언어 특성상 범용적으로 자주사용되는 단어일수도 있기 때문이다.
모든 문서에서 반복적으로 자주 발생하는 단어에 대해서 패널티를 부여하는 방식으로 단어에 대한 가중치를 맞춘다.
그래서 실질적으로 중요한 단어인지 검사할 수 있는 방식.
문서마다 텍스트가 길고, 문서의 개수가 많은경우 카운트보다 TF-IDF사용하는것이 더 좋은 에측성능 보장
Sklearn의 TfidfVectorizer로 구현
import nltk
from sklearn.feature_extraction.text import TfidfVectorizer
corpus = [
'This is the first document.',
'This is the second second document.',
'And the third one.',
'Is this the first document?',
'The last document?',
]
vector = TfidfVectorizer()
print(vector.fit_transform(corpus).toarray()) # TF-IDF 값을 기록한다.
'''
[[0. 0.38947624 0.55775063 0.4629834 0. 0. 0. 0.32941651 0. 0.4629834 ]
[0. 0.24151532 0. 0.28709733 0. 0. 0.85737594 0.20427211 0. 0.28709733]
[0.55666851 0. 0. 0. 0. 0.55666851 0. 0.26525553 0.55666851 0. ]
[0. 0.38947624 0.55775063 0.4629834 0. 0. 0. 0.32941651 0. 0.4629834 ]
[0. 0.45333103 0. 0. 0.80465933 0. 0. 0.38342448 0. 0. ]]
'''
print(vector.vocabulary_)
'''
{'this': 9, 'is': 3, 'the': 7, 'first': 2, 'document': 1, 'second': 6,
'and': 0, 'third': 8, 'one': 5, 'last': 4} ': 4}
'''
이해하기 쉽게 데이터프레임으로 변환하면 아래와 같다.
각 문장마다 각각의 단어들의 tf-idf값을 알 수 있다.
## CountVectorizer -> DataFrame
countvect_df = pd.DataFrame(vector.fit_transform(corpus).toarray(), columns = sorted(vector.vocabulary_))
print(countvect_df)
'''
and document first is last one second the third this
0 0.000000 0.389476 0.557751 0.462983 0.000000 0.000000 0.000000 0.329417 0.000000 0.462983
1 0.000000 0.241515 0.000000 0.287097 0.000000 0.000000 0.857376 0.204272 0.000000 0.287097
2 0.556669 0.000000 0.000000 0.000000 0.000000 0.556669 0.000000 0.265256 0.556669 0.000000
3 0.000000 0.389476 0.557751 0.462983 0.000000 0.000000 0.000000 0.329417 0.000000 0.462983
4 0.000000 0.453331 0.000000 0.000000 0.804659 0.000000 0.000000 0.383424 0.000000 0.000000
'''
TfidfVectorizer 파라미터
이전 countVectorizer와 같다 >> https://pearlluck.tistory.com/682?category=969365
그리고 tf-idf의 원리?를 알아보려면 여기참고 > >https://chan-lab.tistory.com/24
희소행렬
이렇게 텍스트를 피쳐단위로 벡터화해 변환하면 CSR형태의 희소행렬이 된다.
희소행렬의 문제점
희소행렬은 행렬의 대부분 값이 0으로 이루어진 행렬인데, 너무 많은 불필요한 0값 때문에 메모리공간이 낭비된다.
거기에 행렬의 크기도 커서 연산시 데이터액세스를 위한 시간도 오래걸린다.
그래서 이러한 문제점을 해결하기 위해
희소행렬을 물리적으로 적은 메모리공간을 차지할 수 있도록 COO형식 또는 CSR형식으로 변환해야한다
해결방법 : COO형식 변환
COO(Coordinate: 좌표)형식은 좌표형식으로 0이 아닌 데이터만 별도의 데이터배열에 저장하고,
그 데이터가 가리키는 행과 열의 위치를 별도의 배열로 저장하는 방식.
사이파이(Scipy)의 coo_matrix()를 이용해서 변환할 수 있다.
예를 들어, 아래와 같은 2 x 3 행렬이 있다면, 0이 아닌 값은 [3, 1, 2]입니다.
3 | 0 | 1 |
0 | 2 | 0 |
3의 행과 열의 위치는 (0, 0)이고,
1의 행과 열의 위치는 (0, 2)이며,
2의 행과 열의 위치는 (1, 1)이다.
-> 행 위치 값만 모으면 [0, 0, 1], 열 위치 값만 모으면 [0, 2, 1]입니다.
결국 0이 아닌 값 [3,1,2] , 행의 위치 값 [0,0,1], 열의 위치 값 [0,2,1]을 가지고 COO형식을 계산한다.
from scipy import sparse
import numpy as np
# 0 이 아닌 데이터 추출
dense2 = np.array([[0,0,1,0,0,5],
[1,4,0,3,2,5],
[0,6,0,3,0,0],
[2,0,0,0,0,0],
[0,0,0,7,0,8],
[1,0,0,0,0,0]])
# 0 이 아닌 데이터 추출
data2 = np.array([1, 5, 1, 4, 3, 2, 5, 6, 3, 2, 7, 8, 1])
# 행 위치와 열 위치를 각각 array로 생성
row_pos = np.array([0, 0, 1, 1, 1, 1, 1, 2, 2, 3, 4, 4, 5])
col_pos = np.array([2, 5, 0, 1, 3, 4, 5, 1, 3, 0, 3, 5, 0])
# COO희소행렬 생성
sparse_coo = sparse.coo_matrix((data2, (row_pos,col_pos)))
#print(type(sparse_coo)) # scipy.sparse.coo.coo_matrix
print(sparse_coo)
'''
(0, 2) 1
(0, 5) 5
(1, 0) 1
(1, 1) 4
(1, 3) 3
(1, 4) 2
(1, 5) 5
(2, 1) 6
(2, 3) 3
(3, 0) 2
(4, 3) 7
(4, 5) 8
(5, 0) 1
'''
그리고 다시 toarray()를 가지고 밀집행렬(원본)로 복구할 수도 있다.
# 다시 밀접행렬 형태로 변환
print(sparse_coo.toarray())
'''
[[0 0 1 0 0 5]
[1 4 0 3 2 5]
[0 6 0 3 0 0]
[2 0 0 0 0 0]
[0 0 0 7 0 8]
[1 0 0 0 0 0]]
'''
해결방법 : CSR형식 변환
하지만 COO방식은 행과 열의 위치를 나타낼 행렬을 알고 있어야한다.
그래서 이를 해결하기 위한 방법이 CSR(Compressed Sparse Row)형식
행 위치값을 그대로 사용하는게 아니라 배열의 고유한 값들의 시작 위치 인덱스를 배열로 생성해서 사용한다.
from scipy import sparse
import numpy as np
# 0 이 아닌 데이터 추출
dense2 = np.array([[0,0,1,0,0,5],
[1,4,0,3,2,5],
[0,6,0,3,0,0],
[2,0,0,0,0,0],
[0,0,0,7,0,8],
[1,0,0,0,0,0]])
# 0 이 아닌 데이터 추출
data2 = np.array([1, 5, 1, 4, 3, 2, 5, 6, 3, 2, 7, 8, 1])
# 행 위치와 열 위치를 각각 array로 생성
row_pos = np.array([0, 0, 1, 1, 1, 1, 1, 2, 2, 3, 4, 4, 5])
col_pos = np.array([2, 5, 0, 1, 3, 4, 5, 1, 3, 0, 3, 5, 0])
# CSR희소행렬의 행위치 고유값
row_pos_ind = np.array([0, 2, 7, 9, 10, 12, 13])
# CSR희소행렬 생성
sparse_csr = sparse.csr_matrix((data2, col_pos, row_pos_ind))
print(type(sparse_csr)) # scipy.sparse.csr.csr_matrix
print(sparse_csr)
'''
(0, 2) 1
(0, 5) 5
(1, 0) 1
(1, 1) 4
(1, 3) 3
(1, 4) 2
(1, 5) 5
(2, 1) 6
(2, 3) 3
(3, 0) 2
(4, 3) 7
(4, 5) 8
(5, 0) 1
'''
그리고 마찬가지로 다시 toarray()를 가지고 밀집행렬(원본)로 복구할 수도 있다.
# 다시 밀접행렬 형태로 변환
print(sparse_coo.toarray())
'''
[[0 0 1 0 0 5]
[1 4 0 3 2 5]
[0 6 0 3 0 0]
[2 0 0 0 0 0]
[0 0 0 7 0 8]
[1 0 0 0 0 0]]
'''
희소행렬 해결방법 정리
coo = sparse.coo_matrix(dense2)
csr = sparse.csr_matrix(dense2)
print(coo) # type : scipy.sparse.coo.coo_matrix
print(csr) # type : scipy.sparse.csr.csr_matrix
그런데 어차피 사이킷런의 CountVectorizer나 TfidfVectorizer로 변환시킨 피쳐와 행렬은 모두 사이파이의 CSR형태.
참고