젠심-텍스트 벡터화, 변환 및 N-그램
본 포스팅은 바라가브 스리니바사 디지칸, 『자연어처리와 컴퓨터언어학』, 마창수 역, 에이콘을 참고하여 만들어졌습니다.
http://acornpub.co.kr/book/nlp-computational-linguistics
1. 젠심 소개
벡터는 더 나은 머신 러닝 분석을 위한 데이터를 준비하고 전처리하는 단계에 해당하는 내용이다. 텍스트와데이터를 준비하는 과정에 집중하는 것처럼 느껴질 수 있지만 이전에 말했듯이 "쓰레기가 입력되면 쓰레기가 출력된다"는 것을 기억하자. 텍스트를 머신러닝의 입력으로 사용하기 위해 텍스트를 숫자로 표현하는 방법, 특히 문자열을 벡터로 전환하는 방법을 알아본다.
표현(representations)과 변환에 대해 이야기할 때 BOW, TF-IDF, LSI 및 최근 인기를 끌고 있는 word2vec과 같이 문자를 벡터 형태로 표현하는 몇 가지 방법이 있는데 젠심은 이 모든 내용을 처리하기 위한 메서드를 제공한다. 변환된 벡터는 머신 러닝을 위해 사이킷런의 메서드와 쉽게 연동할 수 있다.
젠심은 스트림 데이터를 처리를 위해 파이썬의 내장 제너레이터와 반복자를 사용해 확장성을 관리하기 때문에 모든 데이터 세트가 메모리상에 상주하지 않아도 된다. 대부분 IR 알고리즘은 행렬 곱을 포함한 행렬 분해 연산을 수행한다. 이런 연산은 FORTRAN/C로 구현된 넘파이를 통해 수행되며 수학 연산에 최적화 돼있다. 이런 무거운 데이터가 낮은 수준의 BLAS 라이브러리에 전달되므로 젠심은 파이썬을 통해 쉽게 C의 강력함을 이용할 수 있다.
젠심의 주요 특징은 메모리 독립성, 잠재 의미 분석, 랜덤 투영, HDP, Word2Vec 딥러닝의 멀티코어 구현을 지원하는 것과 LDA의 클러스터 컴퓨팅을 지언하는 것이다, 또한 파이썬의 과학 컴퓨팅 생태계와 원활하게 연결돼 다른 벡터 고간 알고리즘의 확장이 가능하다. 젠심이 제공해주는 주피터 노트북 사이트는 젠심에 대한 대부분의 사용자를 위해 지침서와 문서로 활용하는 중요한 자료이다.
2. 벡터의 이해와 필요성
이제 텍스트 분석에서 머신 러닝을 수행하는 부분을 다루려고 한다. 이는 단어보다 숫자를 더 비중 있게 다룬다는 의미다. 젠심은 벡터를 LDA나 LSI와 같은 IR 알고리즘에 입력으로 전달하면 된다. 주로 내부에서 수행되는 작업은 행렬을 포함한 수학적 연산이다. 이는 이전에 문자열이었던 것을 벡터르 표현한다는 의미이고 이런 종류의 표현이나 모델을 벡터 공간 모델이라 부른다.
머신 러닝 알고리즘은 이 벡터를 이용해 예측을 수행한다. 머신 러닝은 수학 알고리즘의 집합체이며 그 알고리즘에 대한 연구라고 할 수 있다. 알고리즘의 목적은 제공된 데이터를 이용해 학습하고 예측 오류를 줄이는 것이다. 이것은 꽤 넙은 분야이다. 여기서는 이 중 특정한 머신러닝 알고리즘에 대해서만 설명한다.
(1) Bag Of Words (BOW)
BOW 모델은 문장을 벡터로 표현하기 가장 쉬운 유형이다. BOW를 이용하여 벡터로 표현하려면 먼저 어휘 사전을 구성해야 한다. 이는 문장에서 발견되는 유일한 던어들의 집합이다. 문장을 표현해야 할 벡터의 길이가 n이라면 벡터의 도메인도 n차원이라고 할 수 있다. 또한 각 단어를 우리가 가진 어휘 목록에 있는 각 단어의 숫자와 매칭하는 것을 생각할 수 있는데 이를 사전(Dictionary)라고 부른다.
BOW 모댈은 벡터를 만들기 위해 단어 빈도 정보를 활용한다. 그렇다면 문장은 어떻게 보여질까?
#문장을 입력받아 전처리 이후 다음과 같은 리스트로 토큰화되었다고 생각해보자!
s1 = ['dog', 'sat', 'mat'] #"The dog sat bt the mat."
s2 = ['cat', 'love', 'dog',] #"The cat loves the dog."
Vocab = ['dog','sat', 'mat', 'love', 'cat']
v1 = [1, 1, 1, 0, 0] #s1에 대한 vector표현
v2 = [1, 0, 0, 1, 1] #s2d에 대한 vector표현
이해하기 어렵지 않다. 첫 번째 문장에서는 dog은 어휘 목록의 첫 번째이고 한 번 발생했다. love는 0번 발새했다. 적절한 인덱스 값은 단어 빈도를 바탕으로 한다.
기억해야 할 것은 BOW모델의 중요한 특징 중 하나는 문서 표현에서 순서는 의미가 없고 단지 단어의 발견 횟수에만 의미를 둔다는 것이다. 위 예제에서도 알 수 있듯이 결과 문장 벡터는 어떤 단어가 먼저 왔는지 표현하지 않는ㄷ. 이는 공간 정보와 의미 정보의 손실을 초래한다. 하지만 많은 IR 알고리즘에서 단어의 순서는 그다지 중요하지 않으므로 단어 발생 빈도 자체만으로도 시작하기에는 충분하다.
(2) TF-IDF
TF-IDF는 IR 엔진에서 쿼리에 기초해 유사 문서를 찾을 때 많이 사용되는 문장을 벡터로 변환하는 다소 직관적인 접근 방법이다. 이름에서 알 수 있듯이 TF-IDF는 두 가지 정보, 즉 용어 빈도와 역문서 번역도 정보를 이용해 인코딩한다. 여기서 용어 빈도는 문서 내에서 단어가 발견된 횟수다.
역문서 빈도는 문서 내에서 중요한 단어를 이해하는데 도움이 된다. 단어룰 포함하고 있는 문서의 로그 스케일 역분수를 계산하고(총 문서 수를 단어가 포함하고 있는 문서 수로 나눈 값), 인용구의 로그를 보면 문서에서 그 단어가 얼마나 자주 또는 드물게 출현하는지 측정할 수 있다.
TF-IDF는 단순히 TF와 IDF라는 두 가지 요소의 결합이다. 단지 BOW 모델의 벡터 표현과 같이 단지 단어 수를 사용하는 대신 벡터 표현을 위해 더 많은 정보를 내포한다. TF-IDF는 보기 드문 단어를 더 두드러지게 하고 is, of, that과 같이 많이 출현하는 단어들은 중요하지 않은 단어처럼 무시하게 한다.
(3) 다른 벡터 표현
표현법을 확장할 수도 있다. 워드 벡터(word vector)는 흥미로운 단어의 표현이다. 단어를 벡터로 표현하기 위해 단순히 2개의 층으로 이뤄진 얕은 신경망을 훈련시킨다. 각 특징들은 단어의 의미 해석이다.
2. 젠심을 이용한 벡터 변환
벡터 변환이 무엇인지 알아봤으므로 이제 이것들을 생성하고 사용하는 방법에 익숙해져야 한다. 여기서는 젠심을 이용해 변환을 수행하지만 사이킷런으로도 가능하다.사이킷런을 사용하는 방법은 나중에 알아본다.
우선 말뭉치(corpus)를 생성해보자. 앞에서 말뭉치는 문서들의 집합이라고 했다. 예제에서 각 문서는 단 하나의 문장이지만 대부분의 실제 사례에서는 그렇지 않을 것이다. 일다 ㄴ전처리가 수행되면 구두점이 모두 제거된다. 여기서 문서는 하나의 문장이고 벡터 표현의 대상이 된다.
마이클 리의 뉴스 말뭉치를 이용하여 젠심을 이용한 벡터변환을 살펴보자. 마이클 리의 뉴스 말뭉치를 가져오는 방법은 아래 노트북을 확인하자.
github.com/bhargavvader/personal/blob/master/notebooks/text_analysis_tutorial/topic_modelling.ipynb
bhargavvader/personal
Contains Jupyter Notebooks of stuff I am working on. - bhargavvader/personal
github.com
#모듈 import 및 데이터 로딩
import matplotlib.pyplot as plt
import gensim
import numpy as np
import spacy
from gensim.models import CoherenceModel, LdaModel, LsiModel, HdpModel
from gensim.models.wrappers import LdaMallet
from gensim.corpora import Dictionary
import pyLDAvis.gensim
import os, re, operator, warnings
warnings.filterwarnings('ignore') # Let's not pay heed to them right now
%matplotlib inline
test_data_dir = '{}'.format(os.sep).join([gensim.__path__[0], 'test', 'test_data'])
lee_train_file = test_data_dir + os.sep + 'lee_background.cor'
lee_text = open(lee_train_file).read()
nlp = spacy.load("en")
#전처리 및 토큰화
documents = []
documents.append(lee_text)
texts = []
for document in documents:
text = []
doc = nlp(document)
for w in doc:
if not w.is_stop and not w.is_punct and not w.like_num:
text.append(w.lemma_)
texts.append(text)
print(texts)
print(len(text))
# 단어사전 구성
dictionary = gensim.corpora.Dictionary(texts)
print(dictionary.token2id)
#tfidf 모델 생성
tfidf = gensim.models.TfidfModel(corpus)
3. N그램
텍스트 데이터를 이용해 작업하 때는 문맥(context)의 정보를 유지하는 게 매우 중요하다. 앞에서 논의한 것과 같이 각 단어 수만 알고 있는 벡터 표현에서는 문맥을 잃어버리기 쉽다. N-그램, 특히 바이그램은 적어도 이러한 문맥 소실 문제를 푸는 데 도움이 많이 된다.
N-그램은 텍스트에서 n개 항목의 연속을 나타낸다. 우리는 단어를 항목으로 다룰 예정이지만 문자나 음절, 음성인식의 경우에는 음소 단위가 될 수도 있다. 바이그램은 n=2인 n-그램을 의미한다.
텍스트에서 바이그램을 계산하는 한 가지 방법은 앞에 위치하는 토큰에 대해 뒤에 이어지는 토큰의 조건부 확률을 계산하는 것이다 서로 옆에 위치하는 단어를 선택해 계산할 수도 있지만 하나의 쌍인 바이그램을 사용하는 것이 좀 더 유용하다. 이런 바이 그램은 다른 말로 연어(collocation)라고 부른다. 이는 주변에 자주 위치하고 있는 단어의 쌍을 찾는다는 의미다. 예를 들어 New York이나 Machine Leanring은 바이 그램으로 생성할 수 있는 쌍이 될 수 있다. 다시 말해 학습 데이터에 기반을 두고 New라는 단어 뒤에는 York 단어가 있을 가능성이 높기 때문에 New York을 하나의 단어로 고려하는 게ㅔ 더 합리적이다. 말뭉치로 바이그램 모델을 생성하기 전에 의미 없는 바이그램이 될 수 있는 불용어를 미리 조심스럽게 제거해야 한다. 젠심 바이그램 모델의 기본적인 구현은 연어를 식별하는 과정인 것이다.
이것은 확실히 유용하다. 말뭉치에서 구문을 선택할 수 있는데 New라는 단어와 York이라는 단어를 따로 보는 것보다 New York이라는 구문은 더 많은 정보를 제공한다. 이 과정은 전처리 파이프라인에 추가될 수 있다.
젠심은 바이그램으로 두 토큰의 확률을 계산하기 위해 간단히 밑줄을 추가해 처리한다. new와 york의 토큰은 new_york으로 바뀐다. TF-IDF 모델과 유사하게 바이그램은 젠심 모델의 Phrases 메서드를 이용해 생성할 수 있다.
#text를 bigram으로 변환
bigram = gensim.models.Phrases(lee_text)
#텍스트의 각 문장에서 가능한 모든 바이그램이 생성
texts = [bigram[line] for line in lee_text]