본문 바로가기
자연어 처리

[딥러닝을 이용한 자연어 처리 입문] 패딩과 원-핫인코딩

by 나연하야 2023. 2. 27.

오늘은 자연어 처리 전 input data를 만들 때 필요한 패딩과 원-핫인코딩에 대해서 학습해보겠습니다.

패딩(Padding)

- 자연어 처리를 하다보면 각 문장(또는 문서)의 길이가 서로 다른 경우가 있는데 기계는 길이가 동일한 문서들을 하나의 행렬로 보고 한꺼번에 병렬로 처리함. 따라서, 길이가 다른 문장들을 동일하게 맞춰주는 작업이 필요함.

- 이때, 데이터에 특정 값을 채워서 데이터 크기를 조정하는 것을 패딩이라고 하며, 숫자 0을 사용하면 제로 패딩이라고 함.

- 아래는 길이가 다른 문장을 가장 길이가 긴 문장에 맞추어 0으로 제로 패딩한 예시임.

import numpy as np
from tensorflow.keras.preprocessing.text import Tokenizer
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']]

tokenizer=Tokenizer()
tokenizer.fit_on_texts(preprocessed_sentences) # 단어 빈도수가 높은 순으로 낮은 정수 인덱스 부여
encoded=tokenizer.texts_to_sequences(preprocessed_sentences)
print(encoded)
[[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]]
max_len=max(len(item) for item in encoded)
print('최대 길이:', max_len)
최대 길이: 7
for sentence in encoded:
  while len(sentence) < max_len:
    sentence.append(0)

padded_np = np.array(encoded)
padded_np
array([[ 1,  5,  0,  0,  0,  0,  0],
       [ 1,  8,  5,  0,  0,  0,  0],
       [ 1,  3,  5,  0,  0,  0,  0],
       [ 9,  2,  0,  0,  0,  0,  0],
       [ 2,  4,  3,  2,  0,  0,  0],
       [ 3,  2,  0,  0,  0,  0,  0],
       [ 1,  4,  6,  0,  0,  0,  0],
       [ 1,  4,  6,  0,  0,  0,  0],
       [ 1,  4,  2,  0,  0,  0,  0],
       [ 7,  7,  3,  2, 10,  1, 11],
       [ 1, 12,  3, 13,  0,  0,  0]])

 

- 위의 과정을 케라스에서는 pad_sequences()를 사용하여 좀 더 간단하게 할 수 있음.

encoded=tokenizer.texts_to_sequences(preprocessed_sentences)
print(encoded)
[[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]]
from tensorflow.keras.preprocessing.sequence import pad_sequences
padded=pad_sequences(encoded)
padded
array([[ 0,  0,  0,  0,  0,  1,  5],
       [ 0,  0,  0,  0,  1,  8,  5],
       [ 0,  0,  0,  0,  1,  3,  5],
       [ 0,  0,  0,  0,  0,  9,  2],
       [ 0,  0,  0,  2,  4,  3,  2],
       [ 0,  0,  0,  0,  0,  3,  2],
       [ 0,  0,  0,  0,  1,  4,  6],
       [ 0,  0,  0,  0,  1,  4,  6],
       [ 0,  0,  0,  0,  1,  4,  2],
       [ 7,  7,  3,  2, 10,  1, 11],
       [ 0,  0,  0,  1, 12,  3, 13]], dtype=int32)

 

- 위의 실행 결과에서 알 수 있듯이, pad_sequences는 문서의 앞에 0을 채우는데 뒤에 0을 채우고 싶으면 padding='post'를 추가해주면 됨.

padded=pad_sequences(encoded, padding='post')
padded
array([[ 1,  5,  0,  0,  0,  0,  0],
       [ 1,  8,  5,  0,  0,  0,  0],
       [ 1,  3,  5,  0,  0,  0,  0],
       [ 9,  2,  0,  0,  0,  0,  0],
       [ 2,  4,  3,  2,  0,  0,  0],
       [ 3,  2,  0,  0,  0,  0,  0],
       [ 1,  4,  6,  0,  0,  0,  0],
       [ 1,  4,  6,  0,  0,  0,  0],
       [ 1,  4,  2,  0,  0,  0,  0],
       [ 7,  7,  3,  2, 10,  1, 11],
       [ 1, 12,  3, 13,  0,  0,  0]], dtype=int32)

 

원-핫 인코딩(One-Hot Encoding)

- 원-핫 인코딩을 하기 위해서는 먼저 단어 집합에 고유한 정수를 부여하는 정수 인코딩을 진행할 필요가 있음. 그 이후에 표현하고 싶은 단어의 고유한 정수를 인덱스로 간주하고 해당 위치에 1을 부여하고, 다른 단어의 인덱스의 위치에는 0을 부여하면 됨.

from konlpy.tag import Okt

okt=Okt()
tokens=okt.morphs("나는 자연어 처리를 배운다")
print(tokens)
['나', '는', '자연어', '처리', '를', '배운다']
word_to_index={word:index for index, word in enumerate(tokens)}
print('단어집합:', word_to_index)
단어집합: {'나': 0, '는': 1, '자연어': 2, '처리': 3, '를': 4, '배운다': 5}
def one_hot_encoding(word, word_to_index):
  one_hot_vector=[0]*(len(word_to_index))
  index=word_to_index[word]
  one_hot_vector[index]=1
  return one_hot_vector
one_hot_encoding("자연어", word_to_index)
[0, 0, 1, 0, 0, 0]

 

- 위와 같이 원-핫 인코딩을 위해 직접 코드를 작성할 수도 있지만 케라스의 to_categorical()을 이용하여 좀 더 간단하게 원-핫인코딩을 수행할 수 있음.

from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.utils import to_categorical

text = "나랑 점심 먹으러 갈래 점심 메뉴는 햄버거 갈래 갈래 햄버거 최고야"

tokenizer = Tokenizer()
tokenizer.fit_on_texts([text])
print('단 어 집 합 :',tokenizer.word_index)
단 어 집 합 : {'갈래': 1, '점심': 2, '햄버거': 3, '나랑': 4, '먹으러': 5, '메뉴는': 6, '최고야': 7}
sub_text="점심 먹으러 갈래 메뉴는 햄버거 최고야"
encoded=tokenizer.texts_to_sequences([sub_text])[0]
print(encoded)
[2, 5, 1, 6, 3, 7]

 

one_hot=to_categorical(encoded)
print(one_hot)
[[0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 1. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 1. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 1.]]

 

- 원-핫 인코딩의 경우 단어의 개수가 늘어날수록 벡터의 차원이 늘어나야 하므로 저장 공간 측면에서 매우 비효율적인 방법임.

- 또한, 단어들의 유사도를 표현하지 못한다는 단점이 있음.

 

출처: 유원준/안상준, 딥러닝을 이용한 자연어 처리 입문-1권, p95-p103.