[자연어처리] one-hot encoding 구현
0. 들어가면서
기본적으로 자연어 처리에 대한 기초 기념에 대해서는 아래의 블로그를 참고하자. 지금은 one-hot encoding 구현에 중점을 주고 적어보겠다.
원-핫 인코딩(one-hot encoding)은 컴퓨터가 text를 이해할 수 있도록 수치화하는 가장 기초적인 방법 중 하나이다. 단어 하나당 전부에 정수 인덱스를 부여하는 방식이다. 코드를 통해 더 좀 더 알아보자.
1. one-hot encoding 구현
우리는 총 4가지 방법으로 one-hot encoding을 구현할 것이다.
1.1. 단어를 기준으로 one-hot 인코딩하기
#word. one-hot encoding
import numpy as np
samples = ['나는 오늘도 어제처럼 자연어처리 공부를 한다', '한파이와 함께라면 자연어처리 공부를 쉽게 한다']
token_index = {}
# print('numpy 확인')
for sample in samples:
for word in sample.split(): # split로 띄어쓰기 기준으로 토큰을 나눈다
# print(word)
if word not in token_index:
token_index[word] = len(token_index) + 1 # 0 인덱스는 사용하지 않는다.
# print(token_index) # //{'나는': 1, '오늘도': 2, '어제처럼': 3, '자연어처리': 4, '공부를': 5, '한다': 6, '한파이와': 7, '함께라면': 8, '쉽게': 9}
max_length = 10 # 각 샘플의 단어는 max_length까지만 허용한다. 즉, 여기서는 한 문장에 10개의 단어까지만 허용 할 수 있다는 이야기다.
results = np.zeros(shape=(len(samples),
max_length,
max(token_index.values()) + 1))
# print(token_index.values()) # dict_values([1, 2, 3, 4, 5, 6, 7, 8, 9])
print(results) # 결과를 저장할 배열을 만든거다.
for i, sample in enumerate(samples): # enumerate는 인덱스부분을 i에서 같이 뽑을 수 있다.
for j, word in list(enumerate(sample.split()))[:max_length]: # 리스트 안에서 enumerate를 하면 튜플로 인덱스 0부터 묶이고, 딕셔너리 안에서 enumerate를 하면 인덱스와 값이 key, value로 들어간다.
index = token_index.get(word) # word 부분이 token_index의 딕셔너리의 key로 들어가서 index에 value인 숫자가 들어간다.
results[i, j, index] = 1. # i는 문장의 인덱스, j는 문장안에서 단어의 인덱스, index는 toekn_index의 value값
# print(index)
print(results)
최종으로 나오는 results는 아래와 같다.
[[[0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]
[[0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]
[0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]]
풀어서 이야길 해보면 리스트 안에 2개의 문장이 있고, 각각의 문장 안에는 max_length가 10개이므로 10개의 단어까지 표현 가능하게 되어 있고, 각각의 단어마다 등록된 index에 1이 표현되어 있다.
1.2. 문자를 기준으로 one-hot 인코딩하기
import string
samples = ['The cat ate my homework', 'The dog is not hotdog'] # 여기서는 영어로만 가능
characters = string.printable # 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ // 출력가능한 모든 아스키 문자
# print(characters)
token_index = dict(zip(characters, range(1, len(characters) + 1))) # key가 characters고 value가 1부터 숫자로 들어간다.
print(token_index)
max_length = 50
results = np.zeros((len(samples), max_length, max(token_index.values()) + 1))
for i, sample in enumerate(samples):
for j, character in enumerate(sample):
index = token_index.get(character)
results[i, j, index] = 1
print(results)
1.3. 케라스를 활용한 one-hot 인코딩하기
from keras.preprocessing.text import Tokenizer
# print('import keras')
samples = ["The youngham sat on th mat.", "The dog ate my homework."]
tokenizer = Tokenizer(num_words=1000) # 빈도가 가장 높은 순서로 1000개만 선택하도록 Tokenizer 객체를 만든 것이다.
# print(tokenizer)
tokenizer.fit_on_texts(samples) # 단어 인덱스 구축
# print(tokenizer)
sequences = tokenizer.texts_to_sequences(samples) # 문자열을 정수 인덱스의 리스트로 변환
# print(sequences) #// [[1, 2, 3, 4, 5, 6], [1, 7, 8, 9, 10]]
one_hot_results = tokenizer.texts_to_matrix(samples, mode='binary') # 직접 원-핫 이진 벡터 표현 얻기. 원-핫 인코딩 외 다른 벡터화방법도 제공한다
# print(one_hot_results)
word_index = tokenizer.word_index #계산된 중복을 제외한 단어 인덱스 구하기
# print(word_index) #{'the': 1, 'youngham': 2, 'sat': 3, 'on': 4, 'th': 5, 'mat': 6, 'dog': 7, 'ate': 8, 'my': 9, 'homework': 10}
위의 방식 처럼 Tokenizer를 import 해서 쓰면 빈도가 높은 N개의 단어만 선택하여 벡터 공간이 너무 커지지 않게 하고 특수문자도 제거해 주기 때문에 굳이 전부다 구현하지 않고 편하게 사용 가능하다.
+ text_to_matrix()
text_to_matrix() 메서드는 텍스트를 시권스 리스트로 바꿔 주는 texts_to_sequences() 메서드와 시퀀스 리스트를 넘파이 배열로 바꿔주는 sequences_to_matrix() 메서드를 차례로 호출하여 준다.
+text_to_matrix(data, mode='binary')
text_to_matrix()의 인자인 mode의 기본값은 binary다. 이 외에는 count( 단어의 출현 횟수를 사용 ), freq( 출현 횟수를 전체 시퀀스의 길이로 나누어 정규화 ), tfidf( TF-IDF 방식 )를 넣을 수 있다.
1.4. 해싱 기법을 사용한 단어 one-hot hashing
import numpy as np
# 메모리 절약 인코딩
samples = ["The youngham sat on th mat.", "The dog ate my homework."]
dimensionality = 1000
max_length = 10
results = np.zeros((len(samples), max_length, dimensionality))
for i, sample in enumerate(samples):
for j, word in list(enumerate(sample.split()))[:max_length]:
index = abs(hash(word)) % dimensionality # 단어를 해싱하여 0과 1000 사이의 랜덤한 정수 인덱스로 변환
results[i, j, index] = 1.
print(results)
이 방식은 어휘 사전에 있는 고유한 토큰의 수가 커서 다루기 힘들 때 사용한다. 딕셔너리에 값을 저장하는 대신 단어를 해식하여 고정된 크기의 벡터로 변환한다. 이 방식의 장점은 명시적인 단어 인덱스가 필요 없어서 메모리를 절약할 수 있다. 그러나 해시 충돌이 일어나서 여러 단어가 같은 해시를 만든다면, 머신 러닝 모델은 단어들의 차이를 인식하지 못한다.