본문 바로가기
네이버클라우드/AI

AI 5일차 (2023-05-12) 인공지능 기초 - Tokenizer 와 Embedding

by prometedor 2023. 5. 12.

Tokenizer

from keras.preprocessing.text import Tokenizer

# 예시 문장들
sentences = ['The cat sat on the chair.', 'The dog ate my food.']

# 토크나이저 인스턴스 생성 후, 텍스트 데이터에 대해 단어 인덱스 생성
tokenizer = Tokenizer()
tokenizer.fit_on_texts(sentences)

# 생성된 단어 인덱스 확인
print(tokenizer.word_index)
# {'the': 1, 'cat': 2, 'sat': 3, 'on': 4, 'chair': 5, 'dog': 6, 'ate': 7, 'my': 8, 'food': 9}

# 문장을 시퀀스로 변환
sequences = tokenizer.texts_to_sequences(sentences)
print(sequences)
# [[1, 2, 3, 4, 1, 5], [1, 6, 7, 8, 9]]

ㄴ Keras의 텍스트 전처리 도구

각 단어를 고유한 정수로 인코딩하고, 문장을 시퀀스로 변환

fit_on_texts() 메서드를 사용하여 주어진 텍스트 데이터에 대해 단어 인덱스를 만듦

texts_to_sequences() 메서드를 사용하여 문장을 시퀀스로 변환 --> 이러한 시퀀스는 모델 학습에서 입력으로 사용

 

tf19_tokenizer01.py

from keras.preprocessing.text import Tokenizer	# 텍스트 데이터를 단어 단위 또는 문자 단위로 분리해주는 기능

text = '나는 진짜 매우 매우 매우 매우 맛있는 밥을 엄청 엄청 많이 많이 많이 먹어서 매우 배가 부르다'

token = Tokenizer()
token.fit_on_texts([text])  # 주어진 텍스트 데이터를 학습하여 각 단어나 문자에 대한 인덱스를 만들고 이를 저장

# index = token.word_index
print(token.word_index)	
# {'매우': 1, '많이': 2, '엄청': 3, '나는': 4, '진짜': 5, '맛있는': 6, '밥을': 7, '먹어서': 8, '배가': 9, '부르다': 10}

x = token.texts_to_sequences([text])
print(x)


from keras.utils import to_categorical

x = to_categorical(x)   # onehotencoding 하면 index 수 + 1개로 만들어짐
print(x)    # 11 개 인 이유 : 빈칸을 만들어줌 --> 훈련 데이터에 없는 경우를 처리해 주기 위함
print(x.shape)  # (1, 17, 11)   --> 3차원으로 받아온다

ㄴ 텍스트 데이터를 토큰화하고, 각 단어를 정수로 인코딩한 후, 이를 one-hot encoding 한 것

 

 

tf19_tokenizer02.py

from keras.preprocessing.text import Tokenizer

text1 = '나는 진짜 매우 매우 매우 매우 맛있는 밥을 엄청 엄청 많이 많이 많이 먹어서 매우 배가 부르다'
text2 = '나는 딥러닝이 정말 재미있다. 재미있어하는 내가 너무 너무 너무 너무 멋있다. 또 또 또 얘기해 봐.'

token = Tokenizer()
token.fit_on_texts([text1, text2])  # fit on 하면서 index 생성 (주어진 텍스트 데이터에 대해 단어 인덱스를 만듦)

print(token.word_index)
# {'매우': 1, '너무': 2, '많이': 3, '또': 4, '나는': 5, '엄청': 6, '진짜': 7, 
#  '맛있는': 8, '밥을': 9, '먹어서': 10, '배가': 11, '부르다': 12, '딥러닝이': 13, 
#  '정말': 14, '재미있다': 15, '재미있어하는': 16, '내가': 17, '멋있다': 18, 
#  '얘기해': 19, '봐': 20}

x = token.texts_to_sequences([text1, text2])
print(x)
# [[5, 7, 1, 1, 1, 1, 8, 9, 6, 6, 3, 3, 3, 10, 1, 11, 12],  # text1
#  [5, 13, 14, 15, 16, 17, 2, 2, 2, 2, 18, 4, 4, 4, 19, 20]]    # text


from keras.utils import to_categorical	# 문장을 시퀀스로 변환 --> 모델 학습에서 입력으로 사용
x_new = x[0] + x[1]
print(x_new)    # 리스트 2개가 연결됨
'''
x = to_categorical(x_new)   # onehotencoding 하면 index 수 + 1개로 만들어짐
print(x)   
print(x.shape)  # (33, 21)
''' 


########## OneHotEncoder 수정 ##########
import numpy as np
from sklearn.preprocessing import OneHotEncoder
onehot_encoder = OneHotEncoder(
    categories='auto', sparse=False
    )

x = np.array(x_new)     # 배열 만들어주는 거
# x = x.reshape(-1, 11, 9)    # reshape : list로 해야됨
print(x.shape)  # (33,)
x = x.reshape(-1, 1)    # -1 : 차원 늘어남
print(x.shape)  # (33, 1)

onehot_encoder.fit(x)
x = onehot_encoder.transform(x)
print(x)
print(x.shape)  # (33, 20)  --> 0부터 시작하기 때문에 to_categorical 보다 1 큼

# AttributeError: 'list' object has no attribute 'reshape'

** 주의

OneHotEncoder는 정수형 인덱스를 0부터 시작하여 순서대로 one-hot 인코딩하므로 to_categorical  의 one-hot encoding 과 다름

= > to_categorical  의 one-hot encoding 시 x.shape --> (33, 21)

= > sklearn.preprocessing 의 OneHotEncoder 사용 시 x.shape --> (33, 20)

 

 

Embedding

ㄴ 자연어 처리에서 단어를 벡터로 변환하는 기술

ㄴ 각 단어는 고유한 벡터로 매핑되며, 이러한 벡터들은 단어 간의 유사도를 반영

ㄴ "강아지"와 "고양이"라는 두 단어가 유사하면 해당 단어들이 벡터 공간에서 가까이 위치

ㄴ 이를 통해 자연어 처리 모델은 문장 내의 단어들의 관계를 파악하여 자연어 처리를 수행

 

Embedding 감성 분류 모델

tf20_embedding01.py

import numpy as np
from keras.preprocessing.text import Tokenizer

# 1. 데이터
docs = ['재밌어요', '재미없다', '돈 아깝다', '숙면했어요', 
        '최고에요', '꼭 봐라', '세 번 봐라', '또 보고싶다',
        'n회차 관람', '배우가 잘 생기긴 했어요', '발연기에요', '추천해요',
        '최악', '후회된다', '돈 버렸다', '글쎄요', '보다 나왔다',
        '망작이다', '연기가 어색해요', '차라리 기부할걸',
        '다음편 나왔으면 좋겠다', '다른 거 볼걸', '감동이다']

# 긍정 1, 부정 0 라벨링
labels = np.array([1, 0, 0, 0,
                   1, 1, 1, 1,
                   1, 0, 0, 1,
                   0, 0, 0, 0, 0,
                   0, 0, 0, 1, 0, 1])

# Tokenizer
token = Tokenizer()
token.fit_on_texts(docs)
print(token.word_index)

# {'돈': 1, '봐라': 2, '재밌어요': 3, '재미없다': 4, '아깝다': 5, '숙면
# 했어요': 6, '최고에요': 7, '꼭': 8, '세': 9, '번': 10, '또': 11, '보고
# 싶다': 12, 'n회차': 13, '관람': 14, '배우가': 15, '잘': 16, '생기긴': 
# 17, '했어요': 18, '발연기에요': 19, '추천해요': 20, '최악': 21, '후회 
# 된다': 22, '버렸다': 23, '글쎄요': 24, '보다': 25, '나왔다': 26, '망작
# 이다': 27, '연기가': 28, '어색해요': 29, '차라리': 30, '기부할걸': 31, '다음편': 32, '나왔으면': 33, '좋겠다': 34, '다른': 35, '거': 36, '볼
# 걸': 37, '감동이다': 38}

x = token.texts_to_sequences(docs)
print(x)
# [[3], [4], [1, 5], [6], [7], [8, 2], [9, 10, 2], [11, 12], [13, 14], 
# [15, 16, 17, 18], [19], [20], [21], [22], [1, 23], [24], [25, 26], 
# [27], [28, 29], [30, 31], [32, 33, 34], [35, 36, 37], [38]]


# pad_sequences --> 길이를 동일하게 맞춰줌
# 1. 데이터
from keras_preprocessing.sequence import pad_sequences

pad_x = pad_sequences(x, padding='pre', maxlen=4)   # 출력한 단어에 모두 0을 채움
print(pad_x)
print(pad_x.shape)  # (23, 4) --> 23은 전체 개수(x 크기), 4는 maxlen

word_size = len(token.word_index)
print('word_size : ', word_size)    # word_size :  38  --> 단어 사전 개수


# 2. 모델
from keras.models import Sequential
from keras.layers import Dense, Embedding, LSTM

model = Sequential()
model.add(Embedding(input_dim = 39,     # word_size + 1 : 38 + 1
                    output_dim = 10,    # node 수
                    input_length=4))    # 문장의 길이(가장 긴 길이)
model.add(LSTM(32)) # 시간적인 개념, 3차원을 받아서 2차원을 보냄
model.add(Dense(1, activation='sigmoid'))   # 긍정, 부정 --> 이진분류

# model.summary()


# 3. 컴파일, 훈련
model.compile(loss='binary_crossentropy',
              optimizer='adam',
              metrics='accuracy')
model.fit(pad_x, labels, epochs=100, batch_size=16)


# 4. 평가, 예측
loss, acc = model.evaluate(pad_x, labels)
print('loss : ', loss)
print('acc : ', acc)
# loss :  0.04936698079109192
# acc :  1.0

 

 

 

tf20_embedding02_predict.py

 

import numpy as np
from keras.preprocessing.text import Tokenizer

#1. 데이터
docs = ['재밌어요', '재미없다', '돈 아깝다', '숙면했어요', '최고예요',
        '꼭 봐라', '세 번 봐라', '또 보고싶다', 'n회차 관람', '배우가 잘 생기긴 했어요',
        '발연기에요','추천해요','최악','후회된다','돈 버렸다','글쎄요', '보다 나왔다',
        '망작이다','연기가 어색해요','차라리 기부할걸','다음편 나왔으면 좋겠다',
        '다른 거 볼걸','감동이다']

# 긍정 1, 부정 0 라벨링
labels = np.array([1, 0, 0, 0,
                  1, 1, 1, 1,
                  1, 0, 0, 1, 
                  0, 0, 0, 0, 0,
                  0, 0, 0, 1, 0, 1])


# tokenizer
token = Tokenizer()
token.fit_on_texts(docs)
print(token.word_index)

# {'돈': 1, '봐라': 2, '재밌어요': 3, '재미없다': 4, '아깝다': 5, '숙면했어요': 6, '최고예요': 7, 
#  '꼭': 8, '세9': 9, '번': 10, '또': 11, '보고싶다': 12, 'n회차': 13, '관람': 14, '배우가': 15, '잘': 16, 
#  '생기긴': 17, '했어요': 18, '발연기에요': 1, '추천해요': 20, '최악': 21, '후회된다': 22, '버렸다': 23,
#    '글쎄요': 24, '보다': 25, '나왔다': 26, '망작이다': 27, '연기가': 28, '어색해요': 29, '차라리': 30, 
#    '기부할걸': 31, '다음편': 32, '나왔으면': 33, '좋겠다': 34, '다른': 35, '거': 36, '볼걸': 37, '감동이다': 38}

x = token.texts_to_sequences(docs)
print(x)
# [[3], [4], [1, 5], [6], [7], [8, 2], [9, 10, 2], [11, 12], [13, 14], [15, 16, 17, 18], [19], [20], [21],
#  [22], [1, 23], [24], [25, 26], [27], [28, 29], [30, 31], [32, 33, 34], [35, 36, 37], [38]]

# pad_sequences
from keras_preprocessing.sequence import pad_sequences

pad_x = pad_sequences(x, padding = 'pre', maxlen = 4)   # 출력한 단어에 모두 0을 채움
print(pad_x)
print(pad_x.shape)  # (23, 4) --> 23은 전체 개수(x 크기, 라벨링 개수), 4는 maxlen

word_size = len(token.word_index)   # word_size :  38  --> 단어 사전 개수
print('word size : ',word_size)     # word size :  38 

# 모델
from keras.models import Sequential
from keras.layers import Dense, Embedding, LSTM

model = Sequential()
model.add(Embedding(input_dim = 39, #word_size : 38 + 1
                    # 각 단어의 인덱스는 0부터 시작하기 때문에, 모든 단어를 벡터화하기 위해서는 
                    # 0을 포함한 총 단어의 개수(word_size + 1)만큼의 차원이 필요
                    output_dim = 10,    # node 수 
                    input_length=4))    # 문장의 길이(가장 긴 길이에 맞춰짐)

model.add(LSTM(32)) # 시간적인 개념, 3차원을 받아서 2차원을 보냄
model.add(Dense(32, activation='relu'))
model.add(Dense(16, activation='relu'))
model.add(Dense(1, activation='sigmoid'))   # 긍정, 부정 --> 이진분류

# model.summary()

# 컴파일, 훈련
model.compile(loss = 'binary_crossentropy',
              optimizer='adam',
              metrics = 'accuracy')

#model.fit(pad_x, labels, epochs=100, batch_size = 16 )
model.fit(pad_x, labels, epochs=10, batch_size = 1)
#4.평가, 예측
loss, acc = model.evaluate(pad_x, labels)
print('loss : ', loss)
print('acc : ', acc)


# loss :  0.08198674768209457
# acc :  1.0
####################################
# loss :  0.262984037399292
# acc :  1.0

############################### [predict] ##################################

# x_predict = '진짜 정말 후회된다 최악'
x_predict = '정말 정말 재미있고 최고예요'
#x_predict = "배우가 잘 생기긴 했네요"


# 1) tokenizer
token = Tokenizer()
x_predict = np.array([x_predict])
print(x_predict)     # 리스트 안에 들어갔는지 확인
# ['정말 정말 재미있고 최고예요']

token.fit_on_texts(x_predict)   # fit on 하면서 index 가 생성됨
x_pred = token.texts_to_sequences(x_predict)	# # max length 맞춰줘야 함
#print(token.word_index())
print(len(token.word_index))    
# 3

print(x_pred)
# [[1, 1, 2, 3]]

score =float(model.predict(x_pred))

# 2) pad_sequences
x_pred = pad_sequences(x_pred, padding='pre')
print(x_pred)
# [[1 1 2 3]]


# 3) model.predict
y_pred = model.predict(x_pred)
print(y_pred)
# [[0.719884]]


# [실습]
# 1. predict 결과 제대로 출력되도록
# 2. score 를 긍정과 부정으로 출력하라

score = float(model.predict(x_pred))

if y_pred > 0.5:
    print("{:.2f}% 확률로 긍정\n".format(score * 100))
else:
    print("{:.2f}% 확률로 부정\n".format((1-score) * 100))

# 71.99% 확률로 긍정