1. 정수 인코딩(Integer Encoding)
컴퓨터는 텍스트보다는 숫자를 더 잘 처리 할 수 있습니다. 이를 위해 자연어 처리에서는 텍스트를 숫자로 바꾸는 여러가지 기법들이 있습니다. 그리고 그러한 기법들을 본격적으로 적용시키기 위한 첫 단계로 각 단어를 고유한 정수에 맵핑(mapping)시키는 전처리 작업이 필요할 때가 있습니다.
예를 들어 갖고 있는 텍스트에 단어가 5,000개가 있다면, 5,000개의 단어들 각각에 1번부터 5,000번까지 단어와 맵핑되는 고유한 정수, 다른 표현으로는 인덱스를 부여합니다. 가령, book은 150번, dog는 171번, love는 192번, books는 212번과 같이 숫자가 부여됩니다. 인덱스를 부여하는 방법은 여러 가지가 있을 수 있는데 랜덤으로 부여하기도 하지만, 보통은 전처리 또는 빈도수가 높은 단어들만 사용하기 위해서 단어에 대한 빈도수를 기준으로 정렬한 뒤에 부여합니다.
왜 이러한 작업이 필요한 지에 대해서는 뒤에서 원-핫 인코딩, 워드 임베딩 챕터 등에서 알아보기로 하고 여기서는 어떤 과정으로 단어에 정수 인덱스를 부여하는지에 대해서만 정리하겠습니다. 단어에 정수를 부여하는 방법 중 하나로 단어를 빈도수 순으로 정렬한 단어 집합(vocabulary set)을 만들고, 빈도수가 높은 순서대로 차례로 낮은 숫자부터 정수를 부여하는 방법이 있습니다. 이해를 돕기위해 단어의 빈도수가 적당하게 분포되도록 의도적으로 만든 텍스트 데이터를 가지고 실습해보겠습니다.
from nltk.tokenize import sent_tokenize
from nltk.tokenize import word_tokenize
from nltk.corpus import stopwords
text="A barber is a person. " \
"a barber is good person. " \
"a barber is huge person. " \
"he Knew A Secret! " \
"The Secret He Kept is huge secret. " \
"Huge secret. " \
"His barber kept his word. " \
"a barber kept his word. " \
"His barber kept his secret. " \
"But keeping and keeping such a huge secret to himself was driving the barber crazy. " \
"the barber went up a huge mountain."
text=sent_tokenize(text)
print(text)
"""
sentence_tokenize출력 :
['A barber is a person.',
'a barber is good person.',
'a barber is huge person.',
'he Knew A Secret!',
'The Secret He Kept is huge secret.',
'Huge secret.',
'His barber kept his word.',
'a barber kept his word.',
'His barber kept his secret.',
'But keeping and keeping such a huge secret to himself was driving the barber crazy.',
'the barber went up a huge mountain.']
"""
for i in text:
sentence=word_tokenize(i)
# 단어 토큰화를 수행합니다.
result = []
for word in sentence:
word=word.lower()
# 모든 단어를 소문자화하여 단어의 개수를 줄입니다.
if word not in stop_words:
# 단어 토큰화 된 결과에 대해서 불용어를 제거합니다.
if len(word) > 2:
# 단어 길이가 2이하인 경우에 대하여 추가로 단어를 제거합니다.
result.append(word)
if word not in vocab:
vocab[word] = 0
vocab[word] += 1
sentences.append(result)
print(sentences)
"""
cleaned & normalized sentence 출력 :
[['barber', 'person'],
['barber', 'good', 'person'],
['barber', 'huge', 'person'],
['knew', 'secret'],
['secret', 'kept', 'huge', 'secret'],
['huge', 'secret'],
['barber', 'kept', 'word'],
['barber', 'kept', 'word'],
['barber', 'kept', 'secret'],
['keeping', 'keeping', 'huge', 'secret', 'driving', 'barber', 'crazy'],
['barber', 'went', 'huge', 'mountain']]
"""
텍스트를 숫자로 바꾸는 단계라는 것은 본격적으로 자연어 처리 작업에 들어간다는 의미이므로, 단어가 텍스트일 때만 할 수 있는 최대한의 전처리를 끝내놓아야 합니다. 위의 코드를 보면, 동일한 단어가 대문자로 표기되었다는 이유로 서로 다른 단어로 카운트되는 일이 없도록 모든 단어를 소문자로 바꾸었습니다. 그리고 자연어 처리에서 크게 의미를 갖지 못하는 불용어와 길이가 짧은 단어를 제거하는 방법을 사용하였습니다. 현재 vocab에는 중복을 제거한 단어와 각 단어에 대한 빈도수가 기록되어져 있습니다. vocab을 출력해보겠습니다.
print(vocab)
"""
vocab 출력 :
{'barber': 8,
'person': 3,
'good': 1,
'huge': 5,
'knew': 1,
'secret': 6,
'kept': 4,
'word': 2,
'keeping': 2,
'driving': 1,
'crazy': 1,
'went': 1,
'mountain': 1}
"""
단어를 키(key)로, 단어에 대한 빈도수가 값(value)으로 저장되어져 있습니다. vocab에 단어를 입력하면 빈도수를 리턴합니다. 이제 빈도수가 높은 순서대로 정렬해보겠습니다.
vocab_sorted = sorted(vocab.items(), key=lambda x:x[1], reverse=True)
print(vocab_sorted)
"""
sorted vocab 출력 :
[('barber', 8),
('secret', 6),
('huge', 5),
('kept', 4),
('person', 3),
('word', 2),
('keeping', 2),
('good', 1),
('knew', 1),
('driving', 1),
('crazy', 1),
('went', 1),
('mountain', 1)]
"""
word_to_index={}
i=0
for (word, frequency) in vocab_sorted :
if frequency > 1 :
# 정제(Cleaning) 챕터에서 언급했듯이 빈도수가 적은 단어는 제외한다.
i=i+1
word_to_index[word]=i
print(word_to_index)
"""
word2idx 출력:
{'barber': 1,
'secret': 2,
'huge': 3,
'kept': 4,
'person': 5,
'word': 6,
'keeping': 7}
"""
1의 인덱스를 가진 단어가 가장 빈도수가 높은 단어가 됩니다. 그리고 이러한 작업을 수행하는 동시에 각 단어의 빈도수를 알 경우에만 할 수 있는 전처리인 빈도수가 적은 단어를 제외시키는 작업을 합니다. 등장 빈도가 낮은 단어는 자연어 처리에서 의미를 가지지 않을 가능성이 높기 때문입니다. 여기서는 빈도수가 1인 단어들은 전부 제외시켰습니다.
자연어 처리를 하다보면, 텍스트 데이터에 있는 단어를 모두 사용하기 보다는 빈도수가 가장 높은 n개의 단어만 사용하고 싶은 경우가 많습니다. 위 단어들은 빈도수가 높은 순으로 낮은 정수가 부여되어져 있으므로 빈도수 상위 n개의 단어만 사용하고 싶다고하면 vocab에서 정수값이 1부터 n까지인 단어들만 사용하면 됩니다. 여기서는 상위 5개 단어만 사용한다고 가정하겠습니다.
vocab_size=5
words_frequency = [w for w,c in word_to_index.items() if c >= vocab_size + 1]
# 인덱스가 5 초과인 단어 제거
for w in words_frequency:
del word_to_index[w] # 해당 단어에 대한 인덱스 정보를 삭제
print(word_to_index)
"""
word2idx 출력 :
{'barber': 1,
'secret': 2,
'huge': 3,
'kept': 4,
'person': 5}
"""
이제 word_to_index에는 빈도수가 높은 상위 5개의 단어만 저장되었습니다. 이제 word_to_index를 사용하여 단어 토큰화가 된 상태로 저장된 sentences에 있는 각 단어를 정수로 바꾸는 작업을 하겠습니다. 예를 들어 sentences에서 첫번째 문장은 ['barber', 'person']이었는데, 이 문장에 대해서는 [1, 5]로 인코딩합니다. 그런데 두번째 문장인 ['barber', 'good', 'person']에는 더 이상 word_to_index에는 존재하지 않는 단어인 'good'이라는 단어가 있습니다.
이처럼 훈련 데이터 또는 테스트 데이터에 대해서 단어 집합에 존재하지 않는 단어들을 Out-Of-Vocabulary(단어 집합에 없는 단어)의 약자로 'OOV'라고 합니다. word_to_index에 'OOV'란 단어를 새롭게 추가하고, 단어 집합에 없는 단어들은 'OOV'의 인덱스로 인코딩하겠습니다.
word_to_index['OOV']=len(word_to_index)+1
# 6번 단어로 'OOV' 추가
encoded=[]
for s in sentences:
temp = []
for w in s:
try:
temp.append(word_to_index[w])
except KeyError:
temp.append(word_to_index['OOV'])
# Vocab에 없어 에러가 날 경우 'OOV'로 대체
encoded.append(temp)
print(encoded)
"""
정수인코딩 출력 :
[[1, 5],
[1, 6, 5],
[1, 3, 5],
[6, 2],
[2, 4, 3, 2],
[3, 2],
[1, 4, 6],
[1, 4, 6],
[1, 4, 2],
[6, 6, 3, 2, 6, 1, 6],
[1, 6, 3, 6]]
"""
지금까지 파이썬의 dictionary 자료형으로 정수 인코딩을 진행해보았습니다. 그런데 이보다는 좀 더 쉽게 하기 위해서 Counter, FreqDist, enumerate 또는 케라스 토크나이저를 사용하는 것을 권장합니다.
2. Reference
'미분류' 카테고리의 다른 글
컴퓨터의 기원 (0) | 2020.01.18 |
---|---|
조건부 확률 (0) | 2020.01.15 |
Preprocessing - Ont-Hot Encoding (0) | 2020.01.08 |
Preprocessing - Stopword (0) | 2020.01.08 |
Preprocessing - Stemming & Lemmatization (0) | 2020.01.08 |
Preprocessing - Cleaning & Normalization (0) | 2020.01.08 |
Preprocessing - Tokenization (0) | 2020.01.08 |