정수 인코딩
컴퓨터는 텍스트보다 숫자를 더 잘 처리하기 때문에 자연어 처리 시 텍스트를 숫자로 바꾼다.
각 단어를 고유한 정수에 매핑을 시키는데 보통 단어 등장 빈도수를 기준으로 정렬한 뒤 부여한다.
Dictionary
from nltk.tokenize import sent_tokenize
from nltk.tokenize import word_tokenize
from nltk.corpus import stopwords
raw_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."
기본적으로 위의 라이브러리와 텍스트를 사용할 것이다.
sentences = sent_tokenize(raw_text)
print(sentences)
'''
['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.']
'''
우선 문장 단위의 토큰화를 진행했다.
이후 정제 - 정규화 작업과 단어 토큰활르 수행할 것이다.
이 때 단어들을 소문자화 하여 단어의 개수를 통일하고, 불용어와 단어 길이가 2 이하인 단어는 제외히킨다.
vocab = {}
preprocessed_sentences = []
stop_words = set(stopwords.words('english'))
for sentence in sentences:
# 단어 토큰화
tokenized_sentence = word_tokenize(sentence)
result = []
for word in tokenized_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
preprocessed_sentences.append(result)
print(preprocessed_sentences)
'''
[['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']]
'''
각 문장마다 단어 정제 후 단어 토큰화를 진행한 결과다.
텍스트를 수치화하는 단계는 자연어 처리 작업에 들아간다는 의미다.
단어가 텍스트일 때만 할 수 있는 최대한의 전처리를 해야한다.
print('단어 집합 :',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}
'''
vocab에는 각 단어의 빈도수가 저장되어 있다.
vocab은 딕셔너리 형태로 단어가 키, 빈도수가 값이다.
값을 기준으로 딕셔너리를 정렬한 후 빈도수 순서대로 정수를 부여한다.
빈도수가 적은 단어는 제외하고 빈도수가 높은 단어부터 1번 ~ 을 부여한다.
# 정렬
vocab_sorted = sorted(vocab.items(), key = lambda x:x[1], reverse = True)
# 정수 부여
word_to_index = {}
i = 0
for (word, frequency) in vocab_sorted :
if frequency > 1 : # 빈도수가 작은 단어는 제외.
i = i + 1
word_to_index[word] = i
print(word_to_index)
'''
{'barber': 1, 'secret': 2, 'huge': 3, 'kept': 4, 'person': 5, 'word': 6, 'keeping': 7}
'''
다음으로 할 작업은 앞서 문장 토큰화를 했던 sentences에 있는 각 단어를 정수로 바꾼느 작업이다.
['barber', 'person'] -> [1, 5] 로 변환하는 작업이다.
그러나 ['barber', 'good', 'person'] 에는 정제 과정에서 걸러진 'good'이 존재한다.
이처럼 단어 집합에 존재하지 않는 단어가 생기는 상황을 Out-Of-Vocabulary 문제라고 한다.
이런 경우는 'OOV' : 8이라는 값을 추가해 존재하지 않는 단어의 경우 'OOV' 인덱스로 인코딩한다.
word_to_index['OOV'] = len(word_to_index) + 1
encoded_sentences = []
for sentence in preprocessed_sentences:
encoded_sentence = []
for word in sentence:
try:
# 단어 집합에 있는 단어라면 해당 단어의 정수를 리턴.
encoded_sentence.append(word_to_index[word])
except KeyError:
# 만약 단어 집합에 없는 단어라면 'OOV'의 정수를 리턴.
encoded_sentence.append(word_to_index['OOV'])
encoded_sentences.append(encoded_sentence)
print(encoded_sentences)
'''
[[1, 5],
[1, 8, 5],
[1, 3, 5],
[8, 2],
[2, 4, 3, 2],
[3, 2],
[1, 4, 6],
[1, 4, 6],
[1, 4, 2],
[7, 7, 3, 2, 8, 1, 8],
[1, 8, 3, 8]]
'''
다음과 같이 1~8의 정수로 텍스트가 변환되 것을 확인할 수 있다.
이 과정들을 Counter, FreqDist, enumerate, 케라스 토크나이저를 사용하면 쉽게 할 수 있다.
FreqDist (NLTK)
from nltk import FreqDist
import numpy as np
vocab = FreqDist(np.hstack(preprocessed_sentences))
vocab_size = 5
vocab = vocab.most_common(vocab_size) # 등장 빈도수가 높은 상위 5개의 단어만 저장
word_to_index = {word[0] : index + 1 for index, word in enumerate(vocab)}
print(word_to_index)
'''
{'barber': 1, 'secret': 2, 'huge': 3, 'kept': 4, 'person': 5}
'''
NLTK에서 제공하는 FreqDist() 함수를 이용해서 위와 같은 과정을 간결하게 진행할 수 있다.
enumerate() 함수를 이용해 인덱스를 쉽게 부여할 수 있다.
이 함수는 순서가 있는 자료형을 입력으로 받아 인덱스를 순차적으로 함께 리턴한다.
Keras
케라스에서도 기본적인 전처리 도구를 제공하고, 이 또한 단어 토큰화 까지 진행한 데이터를 사용한다.
from tensorflow.keras.preprocessing.text import Tokenizer
tokenizer = Tokenizer()
# fit_on_texts()안에 코퍼스를 입력으로 하면 빈도수를 기준으로 단어 집합을 생성.
tokenizer.fit_on_texts(preprocessed_sentences)
fit_on_texts() 함수는 단어 카운팅과 정렬, 인덱스 부여를 모두 수행한다.
print(tokenizer.word_index) # 인덱스 반환
print(tokenizer.word_counts) # 카툰트 수 반환
위와 같이 word_index와 word_counts로 인덱스와 카운트 수를 각각 확인할 수 있다.
print(tokenizer.texts_to_sequences(preprocessed_sentences))
'''
[[1, 5],
[1, 8, 5],
[1, 3, 5],
[9, 2],
[2, 4, 3, 2],
[3, 2],
[1, 4, 6],
[1, 4, 6],
[1, 4, 2],
[7, 7, 3, 2, 10, 1, 11],
[1, 12, 3, 13]]
'''
texts_to_sequences() 함수로 입력으로 들어온 코퍼스에 대해 각 단어를 인덱스로 변환한다.
당연히 이 과정에서는 정제를 하지 않았기 때문에 위와 결과가 다르다.
preprocessed_sentences2 = [['bbarber', 'person']]
print(tokenizer.texts_to_sequences(preprocessed_sentences2))
# [[5]]
위와 같이 인덱스가 부여되지 않은 단어가 들어온 경우를 테스트 해보았더니 변환이 되지 않는 걸 확인했다.
케라스에서도 정제를 위해 함수에 인자를 넣을 수 있다.
vocab_size = 5
tokenizer = Tokenizer(num_words = vocab_size + 1) # 상위 5개 단어만 사용
tokenizer.fit_on_texts(preprocessed_sentences)
num_words인자를 이용해 빈도수가 가장 높은 단어 n개만 사용할 수 있다.
이 때 +1을 해주는 이유는 0 ~ n - 1 번 단어를 보존하기 때문에 1 ~ n - 1 번의 단어만 보존한다.
0을 단어 집합 크기로 산정하는 이유는 패딩 작업 때문이다.
이렇게 num_words 인자를 설정해도 word_index나 word_counts를 확인해보면 모든 데이터가 다 반환된다.
그러나 texts_to_sequences를 사용할 때는 num_words가 적용된 채 결과를 반환한다.
tokenizer = Tokenizer(num_words = vocab_size + 2, oov_token = 'OOV')
인자에 oov_token을 이용해 단어 집합에 없는 단어들을 보존할 수도 있다.
케라스의 토크나이저는 기본적으로 oov_token의 인덱스를 1로 한다.
즉, 나머지 단어들은 1씩 뒤로 밀리게 된다.
'AI > NLP' 카테고리의 다른 글
[Wiki] 원-핫 인코딩 (0) | 2024.06.02 |
---|---|
[Wiki] 패딩 (0) | 2024.06.01 |
[Wiki] 불용어 (0) | 2024.06.01 |
[Wiki] 어간 추출 (0) | 2024.06.01 |
[Wiki] 데이터 전처리 (토큰화 - 정제 - 정규화) (0) | 2024.05.31 |