uding9 2021. 5. 18. 11:42
반응형

https://wikidocs.net/31766 참고하여 공부한 내용 정리

06) 정수 인코딩(Integer Encoding)

텍스트를 숫자로 바꾸는 여러 기법들 중에서 각 단어를 고유한 정수에 매핑(mapping)시키는 전처리 작업

인덱스를 부여하는 방법은 랜덤으로 부여, 단어 빈도수 기준으로 정렬한 후 부여하는 방법이 있음

1. 정수 인코딩(Integer Encoding)


단어에 정수를 부여하는 방법 중 하나로 단어를 빈도수 순으로 정렬한 단어 집합을 만들고 빈도수가 높은 순서대로 인덱스를 낮은 숫자부터 정수를 부여하는 방법

1) dictionary 사용하기

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)
['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.']

문장 토큰화 수행 결과. 다음은 단어 토큰화 수행

vocab={}
sentences=[]
stop_words=set(stopwords.words('english'))

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)
[['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에는 중복 제거한 단어와 각 단어에 대한 빈도수가 기록되어 있음. 단어를 key로 빈도수를 value로 저장되어 있음

vocab_sorted=sorted(vocab.items(), key = lambda x:x[1], reverse=True)
print(vocab_sorted)
[('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: # 빈도수가 적은 단어는 제외함
        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}

빈도수가 높은 단어일수록 낮은 정수 인덱스를 부여함. 1 인덱스를 가진 단어가 가장 빈도수가 높은 단어임

vocab_size=5
# 빈도수가 5를 초과하는 경우(ex. 6, 7)를 저장
words_frequency=[w for w, c in word_to_index.items() if c>=vocab_size+1]

for w in words_frequency:
    del word_to_index[w] # 빈도수가 5를 초과하는 경우 삭제함
print(word_to_index)
{'barber': 1, 'secret': 2, 'huge': 3, 'kept': 4, 'person': 5}

텍스트 데이터에 있는 모든 단어를 사용하는 것이 아닌 빈도수가 높은 상위 n개의 단어만 사용함. 이 경우 빈도수가 높은 상위 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
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']) # 단어 사전에 단어가 존재하지 않아 '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]]

정수 인코딩 완료!

2) Counter 사용하기

from collections import Counter
print(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']]

sentences에는 단어 토큰화된 결과가 저장되어 있음

단어 집합을 만들기 위해 단어들을 하나의 리스트로 만듦

words=sum(sentences, [])
print(words)
['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']

Counter() 사용하면 중복 제거하고 단어의 빈도수를 계산할 수 있음

vocab=Counter(words)
print(vocab)
Counter({'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})

빈도수가 높은 상위 5개 단어만 저장

vocab_size=5
vocab=vocab.most_common(vocab_size)
print(vocab)
[('barber', 8), ('secret', 6), ('huge', 5), ('kept', 4), ('person', 3)]

빈도수가 높을수록 낮은 정수 인덱스를 부여함

word_to_index={}
i=0
for (word, frequency) in vocab:
    i+=1
    word_to_index[word]=i
print(word_to_index)
{'barber': 1, 'secret': 2, 'huge': 3, 'kept': 4, 'person': 5}

3) NLTK의 FreqDist 사용하기

FreqDist()는 빈도수 계산 도구임

from nltk import FreqDist
import numpy as np

np.hstack으로 여러 리스트를 하나의 array로 만든 후 FreqDist()를 이용해 빈도수를 반환함

vocab=FreqDist(np.hstack(sentences))
vocab_size=5
vocab=vocab.most_common(vocab_size)
print(vocab)
[('barber', 8), ('secret', 6), ('huge', 5), ('kept', 4), ('person', 3)]

빈도수 상위 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}

enumerate을 사용하여 인덱스 부여

4) enumerate 이해하기

test=['a', 'b', 'c', 'd', 'e']
for index, value in enumerate(test):
    print('value: {}, index: {}'.format(value, index))
value: a, index: 0
value: b, index: 1
value: c, index: 2
value: d, index: 3
value: e, index: 4

입력 순서대로 인덱스를 순차적으로 부여함

2. 케라스의 텍스트 전처리


from tensorflow.keras.preprocessing.text import Tokenizer

fit_on_texts는 입력 텍스트로부터 빈도수가 높은 순으로 낮은 정수 인덱스를 부여함

tokenizer=Tokenizer()
tokenizer.fit_on_texts(sentences)

word_index 사용해 각 단어에 인덱스가 어떻게 부여되었는지 확인할 수 있음

print(tokenizer.word_index)
{'barber': 1, 'secret': 2, 'huge': 3, 'kept': 4, 'person': 5, 'word': 6, 'keeping': 7, 'good': 8, 'knew': 9, 'driving': 10, 'crazy': 11, 'went': 12, 'mountain': 13}

word_counts사용해 각 단어가 카운트를 수행하였을 때의 결과를 확인

print(tokenizer.word_counts)
OrderedDict([('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)])

texts_to_sequences()는 입력에 대해 각 단어를 정해진 인덱스로 변환

print(tokenizer.texts_to_sequences(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]]

빈도수가 높은 상위 5개의 단어만 사용

vocab_size=5
tokenizer=Tokenizer(num_words=vocab_size+1) # num_words는 숫자를 0부터 카운트하기 때문에 1을 더해줌
tokenizer.fit_on_texts(sentences)

다시 word_index를 확인

print(tokenizer.word_index)
{'barber': 1, 'secret': 2, 'huge': 3, 'kept': 4, 'person': 5, 'word': 6, 'keeping': 7, 'good': 8, 'knew': 9, 'driving': 10, 'crazy': 11, 'went': 12, 'mountain': 13}

상위 5개의 단어만 사용하겠다고 선언했는데 여전히 13개의 단어가 모두 출력됨. word_counts 확인

print(tokenizer.word_counts)
OrderedDict([('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)])

word_counts도 마찬가지로 13개의 단어가 모두 출력됨. 사실 실제 적용은 texts_to_sequences를 사용할 때 적용이 됨

print(tokenizer.texts_to_sequences(sentences))
[[1, 5], [1, 5], [1, 3, 5], [2], [2, 4, 3, 2], [3, 2], [1, 4], [1, 4], [1, 4, 2], [3, 2, 1], [1, 3]]

상위 5개만 사용한다고 지정했으므로 1번 단어부터 5번 단어까지만 보존되고 나머지 단어들은 제거된 것을 확인할 수 있음

만약 word_indexword_counts에서도 지정된 num_words만큼 단어를 남기고 싶으면 아래의 코드를 이용

tokenizer=Tokenizer()
tokenizer.fit_on_texts(sentences)
vocab_size=5
words_frequency=[w for w, c in tokenizer.word_index.items() if c>=vocab_size+1]

for w in words_frequency:
    del tokenizer.word_index[w]
    del tokenizer.word_counts[w]
print(tokenizer.word_index)
print(tokenizer.word_counts)
print(tokenizer.texts_to_sequences(sentences))
{'barber': 1, 'secret': 2, 'huge': 3, 'kept': 4, 'person': 5}
OrderedDict([('barber', 8), ('person', 3), ('huge', 5), ('secret', 6), ('kept', 4)])
[[1, 5], [1, 5], [1, 3, 5], [2], [2, 4, 3, 2], [3, 2], [1, 4], [1, 4], [1, 4, 2], [3, 2, 1], [1, 3]]

모두 1~5번 까지만 저장된 것을 확인할 수 있음

케라스 토크나이저는 기본적으로 단어 집합에 없는 단어인 OOV에 대해서는 인코딩 과정에서 아예 단어를 제거한다는 특징이 있음. 단어 집합에 없는 단어들을 OOV로 간주하여 보존하고 싶다면 Tokenizer의 인자 oov_token을 사용함

vocab_size=5
# 빈도수 상위 5개만 사용, 숫자 0과 oov 고려해서 단어 집합의 크기는 +2 해줌
tokenizer=Tokenizer(num_words=vocab_size+2, oov_token='OOV')
tokenizer.fit_on_texts(sentences)

oov_token 사용하면 케라스 토크나이저는 기본적으로 'OOV'의 인덱스를 1로 함

print('단어 OOV의 인덱스: {}'.format(tokenizer.word_index['OOV']))
단어 OOV의 인덱스: 1

코퍼스에 대해 정수 인코딩 진행

print(tokenizer.texts_to_sequences(sentences))
[[2, 6], [2, 1, 6], [2, 4, 6], [1, 3], [3, 5, 4, 3], [4, 3], [2, 5, 1], [2, 5, 1], [2, 5, 3], [1, 1, 4, 3, 1, 2, 1], [2, 1, 4, 1]]

빈도수 상위 5개의 단어는 2~6까지의 인덱스를 가지고 그 외 단어 집합에 없는 단어는 'OOV'의 인덱스인 1로 인코딩됨

반응형