ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 정수 인코딩(Integer Encoding)
    Natural Language Processing/딥 러닝을 이용한 자연어 처리 입문 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로 인코딩됨

    반응형

    댓글

Designed by Tistory.