https://www.youtube.com/watch?v=hR8Rvp-YNGg&list=PL7ZVZgsnLwEEoHQAElEPg7l7T6nt25I3N&index=7
https://colab.research.google.com/drive/1mBJgcpLrcyJeytlgarr2Gftff8JROPd8?usp=sharing
임베딩(Embedding)
- 워드 임베딩은 단어를 컴퓨터가 이해하고, 효율적으로 처리할 수 있도록 단어를 벡터화하는 기술
- 워드 임베딩은 단어의 의미를 잘 표현해야만 하며, 현재까지도 많은 표현 방법이 연구
- 워드 임베딩을 거쳐 잘 표현된 단어 벡터들은 계산이 가능하며, 모델 투입도 가능
인코딩(Encoding)
- 기계는 자연어(영어, 한국어 등)을 이해할 수 없음
- 데이터를 기계가 이해할 수 있도록 숫자 등으로 변환해주는 작업이 필요
- 이러한 작업을 인코딩이라고 함
- 텍스트 처리에서는 주로 정수 인코딩, 원 핫 인코딩을 사용
정수 인코딩
dictionary를 이용한 정수 인코딩
- 각 단어와 정수 인덱스를 연결하고, 토큰을 변환해주는 정수 인코딩
text = "평생 살 것처럼 꿈을 꾸어라. 그리고 내일 죽을 것처럼 오늘을 살아라."
tokens = [x for x in text.split(' ')]
unique = set(tokens) # 토큰이 중복되지 않도록
unique = list(unique)
token2idx = {}
for i in range(len(unique)):
token2idx[unique[i]] = i
encode = [token2idx[x]for x in tokens]
encode # [5, 9, 7, 6, 3, 8, 1, 4, 7, 0, 2]
keras를 이용한 정수 인코딩
- 정수 인코딩은 단어에 정수로 레이블을 부여
- dictionary, nltk 패키지를 이용한 방법들도 있지만, keras에서는 텍스트 처리에 필요한 도구들을 지원
- 해당 도구는 자동으로 단어 빈도가 높은 단어의 인덱스는 낮게끔 설정
from tensorflow.keras.preprocessing.text import Tokenizer
text = "평생 살 것처럼 꿈을 꾸어라. 그리고 내일 죽을 것처럼 오늘을 살아라."
t = Tokenizer()
t.fit_on_texts([text]) # Tokenizer().fit_on_texts()에는 텍스트로 이루어진 리스트를 넣어야 함
print(t.word_index) # 딕셔너리로 표현됨 : {'것처럼': 1, '평생': 2, '살': 3, '꿈을': 4, '꾸어라': 5, '그리고': 6, '내일': 7, '죽을': 8, '오늘을': 9, '살아라': 10}
encoded = t.texts_to_sequences([text])[0]
print(encoded) # [2, 3, 1, 4, 5, 6, 7, 8, 1, 9, 10]
원 핫 인코딩(One-Hot Encoding)
정수 인코딩보다는 원 핫 인코딩을 더 자주 씀.
조건문과 반복문을 이용한 원 핫 인코딩
- 원 핫 인코딩은 정수 인코딩한 결과를 벡터로 변환한 인코딩
- 원 핫 인코딩은 전체 단어 개수 만큼의 길이를 가진 배열에 해당 정수를 가진 위치는 1, 나머지는 0을 가진 벡터로 변환
import numpy as np
one_hot = []
for i in range(len(encoded)):
temp = []
for j in range(max(encoded)):
if j == (encoded[i] - 1): # encoded의 값은 1부터 시작. for문은 0부터 시작
temp.append(1)
else :
temp.append(0)
one_hot.append(temp)
np.array(one_hot)
keras를 이용한 원 핫 인코딩
- keras에서는 정수 인코딩을 원 핫 인코딩을 간단하게 변환해주는 to_categorical() 함수를 제공
from tensorflow.keras.utils import to_categorical
one_hot = to_categorical(encoded)
one_hot
IMDB 데이터
- 인터넷 영화 데이터베이스(Internet Movie Database)
- 양극단의 리뷰 5만개로 이루어진 데이터셋
- 훈련데이터: 25,000개
- 테스트데이터 : 25,000개
- https://www.imdb.com/interfaces/
1. module import
from tensorflow.keras.datasets import imdb
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, Dense, Flatten
2. 데이터 로드
num_words = 1000
max_len = 20 # 조금만 끊어서 연습해보장
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words = num_words)
print(x_train.shape) # (25000,)
print(y_train.shape) # (25000,)
print(x_test.shape) # (25000,)
print(y_test.shape) # (25000,)
3. 데이터 확인
- 긍정: 1
- 부정: 0
print(x_train[0]) # 이렇게 임베딩된 데이터는
print(y_train[0]) # 긍정이당!
for i in range(10):
if y_train[i] == 0:
label = '부정'
else:
label = '긍정'
print('{}\n{}'.format(x_train[i], label)) # 첫 10개 리뷰에 대해서, 임베딩된 정수 리스트와 함께 긍정인지 부정인지 출력
4. 데이터 전처리
- 모든 데이터를 같은 길이로 맞추기
- pad_sequence()
- 데이터가 maxlen보다 길면 데이터를 자름
- 데이터가 길면 padding 설정
- pre: 데이터 앞에 0으로 채움
- post: 데이터 뒤에 0으로 채움
- pad_sequence()
- 모든 데이터(문장 하나하나)가 같은 길이로 맞추어야 Embedding 레이어를 사용할 수 있음
from tensorflow.keras.preprocessing.sequence import pad_sequences
max_len = 100
pad_x_train = pad_sequences(x_train, maxlen=max_len, padding='pre')
pad_x_test = pad_sequences(x_test, maxlen=max_len, padding='pre')
# 전처리 확인하기
print(len(x_train[5])) # 43
print(len(pad_x_train[5])) # 100
# 길이가 잘 맞춰진 것을 알 수 있음 (나머지 57개는 맨 앞에 0 57개로 채워짐)
print(len(x_train[1])) # 189
print(len(pad_x_train[1])) # 100
# 이 경우에는 데이터가 잘림
5. 모델 구성
model = Sequential()
model.add(Embedding(input_dim=num_words, output_dim=32,input_length=max_len))
model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))
model.summary()
6. 모델 컴파일 및 학습
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['acc'])
history=model.fit(pad_x_train, y_train,
epochs=10,
batch_size=32,
validation_split=0.2) # 오버피팅됨. 모델 안에 일반화를 시킬 수 있는 장치가 없음..ㅜ
7. 시각화
import matplotlib.pyplot as plt
plt.style.use('seaborn-white')
hist_dict = history.history
hist_dict.keys() # dict_keys(['loss', 'acc', 'val_loss', 'val_acc'])
plt.plot(hist_dict['loss'], 'b-', label = 'Train Loss')
plt.plot(hist_dict['val_loss'], 'r:', label = "Validation Loss")
plt.legend()
plt.grid()
plt.figure()
plt.plot(hist_dict['acc'], 'b-', label = 'Train Accuracy')
plt.plot(hist_dict['val_acc'], 'r:', label = "Validation Accuracy")
plt.legend()
plt.grid()
plt.show()
오버피팅이 되었다는 것을 알 수 있음
8. 모델 평가
model.evaluate(pad_x_test, y_test) # [0.486984521150589, 0.8037199974060059] 각각 loss, 정확도
9. 단어의 수를 늘린 후 재학습
# 조건 다르게 다시 가볼게요
num_words = 2000
max_len=400
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words = num_words)
# 전처리
pad_x_train2 = pad_sequences(x_train, maxlen=max_len, padding='pre')
pad_x_test2 = pad_sequences(x_test, maxlen=max_len, padding='pre')
# 모델 설계
model = Sequential()
model.add(Embedding(input_dim=num_words, output_dim=32,input_length=max_len))
model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))
# 모델 컴파일
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['acc'])
# 모델 학습
history=model.fit(pad_x_train2, y_train,
epochs=10,
batch_size=32,
validation_split=0.2)
# 시각화
import matplotlib.pyplot as plt
plt.style.use('seaborn-white')
hist_dict = history.history
hist_dict.keys() # dict_keys(['loss', 'acc', 'val_loss', 'val_acc'])
plt.plot(hist_dict['loss'], 'b-', label = 'Train Loss')
plt.plot(hist_dict['val_loss'], 'r:', label = "Validation Loss")
plt.legend()
plt.grid()
plt.figure()
plt.plot(hist_dict['acc'], 'b-', label = 'Train Accuracy')
plt.plot(hist_dict['val_acc'], 'r:', label = "Validation Accuracy")
plt.legend()
plt.grid()
plt.show()
model.evaluate(pad_x_test2, y_test) # [0.49533307552337646, 0.8497200012207031] 각각 loss, 정확도
위의 결과도 나쁘지 않으나 과적합이 되는 이유
- 단어간 관계나 문장 구조 등 의미적 연결을 고려하지 않음
- 시퀀스 전체를 고려한 특성을 학습하는 것은 Embedding층 위에 RNN층이나 1D 합성곱을 추가하는 것이 좋음
Word2Vec
- 2013년, Mikolov 와 동료들이 제안한 모형
- 분류 등과 같이 별도의 레이블이 없이 텍스트 자체만 있어도 학습이 가능
- Word2Vec의 방식
주변 단어의 관계를 이용- 방식1 : CBOW(continuous bag-of-words)
- 주변단어의 임베딩을 더해서 대상단어를 예측
- 방식2 : Skip-Gram
- 대상 단어의 임베딩으로 주변단어를 예측
- 일반적으로 CBOW보다 성능이 좋은 편
- 한번에 여러 단어를 예측해야하기 때문에 비효율적
-> 최근에는 negative sampling이라는 방법을 사용
- 방식1 : CBOW(continuous bag-of-words)
T-SNE
- T-SNE(t-Stochastic Neighbor Embedding)은 고차원의 벡터들의 구조를 보존하며 저차원으로 사상하는 차원 축소 알고리즘
- 단어 임베딩에서도 생성된 고차원 벡터들을 시각화하기 위해 이 T-SNE 알고리즘을 많이 이용
- t-sne는 가장 먼저 원 공간의 데이터 유사도 pij와 임베딩 공간의 데이터 유사도 qij를 정의
- xi에서 xj 간의 유사도 pj|i 는 다음과 같이 정의
- 설명하자면 pj|i는 xi와 xj 간의 거리에서 가중치 σ2i로 나눈 후, 이를 negative exponential을 취해 모든 점 간의 거리의 합과 나누어준 값으로 두 점 간의 거리가 가까울 수록 큰 값을 가짐
- 또한 임베딩 공간에서의 yi에서 yj 간의 유사도 qj|i 는 다음과 같이 정의
- qj|i는 xi와 xj 간의 거리에서 1을 더해준 후 역수를 취한 값과 전체 합산 값과 나누어 유사도를 정의
- T-SNE의 학습은 pj|i와 비슷해지도록 qj|i의 위치를 조정하는 것이라고 할 수 있음
1. 데이터 준비
from sklearn.datasets import fetch_20newsgroups
dataset = fetch_20newsgroups(shuffle=True, random_state=1,
remove=('headers', 'footers', 'quotes'))
documents = dataset.data
print(len(documents))
documents[2]
2. 전처리
import re
import nltk
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
nltk.download('stopwords')
nltk.download('punkt')
def clean_text(d):
pattern = r'[^a-zA-Z\s]'
text = re.sub(pattern, '', d)
return text
# 댓글 참고해서 함수 수정
def clean_stopword(d):
stop_words = stopwords.words('english')
return ' '.join(w.lower() for w in d.split() if w.lower() not in stop_words and len(w)>3)
def tokenize(d):
return word_tokenize(d)
import pandas as pd
news_df = pd.DataFrame({'article':documents})
len(news_df) # 11314
news_df.replace("", float('NaN'), inplace=True)
news_df.dropna(inplace=True)
print(len(news_df)) # 11096
news_df['article'] = news_df['article'].apply(clean_text)
# news_df['article']
news_df['article'] = news_df['article'].apply(clean_stopword)
# news_df['article']
tokenized_news = news_df['article'].apply(tokenize)
tokenized_news = tokenized_news.to_list()
import numpy as np
drop_news = [index for index, sentence in enumerate(tokenized_news) if len(sentence) <= 1] # 불필요한 데이터 (제거 대상)
news_texts = np.delete(tokenized_news, drop_news, axis = 0)
print(len(news_texts)) # 10939
Gensim을 이용한 Word2Vec
1. CBOW
from gensim.models import Word2Vec
# gensim 버전이 다른 것인지 유튜브와는 달리 size 대신 vector_size 사용함
model = Word2Vec(sentences=news_texts,
window=4, # 고려할 앞 뒤 폭
vector_size=100, # 벡터 크기
min_count=5, # 사용할 단어의 최소 빈도 (5회 이하 단어 무시)
workers=4, # 동시에 처리할 작업의 수
sg=0) # 0 : cbow, 1 : skip-gram
model.save('word2vec.model') # 저장하기
model = Word2Vec.load('word2vec.model') # 불러오기
# model.wv['film']
model.wv.similarity('movie', 'film') # 0.98277336
model.wv.most_similar(positive=['woman'])
# [('mother', 0.9907262921333313),
('brought', 0.9883763194084167),
('dead', 0.9873701930046082),
('wife', 0.9823874235153198),
('azerbaijanis', 0.9766591787338257),
('neighbors', 0.973651111125946),
('lived', 0.9733042120933533),
('parents', 0.9723166823387146),
('daughter', 0.9703890681266785),
('apartment', 0.9700297117233276)]
model.wv.most_similar(positive=['woman', 'girl'], negative='soldier')
# [('face', 0.9797027111053467),
('neighbors', 0.9784392714500427),
('coming', 0.9781953692436218),
('karina', 0.9771144390106201),
('shouting', 0.975655198097229),
('alive', 0.974787175655365),
('wife', 0.9734463691711426),
('mamma', 0.9732183218002319),
('apartment', 0.97276771068573),
('parents', 0.9713296294212341)]
2. Skip-gram
# skip-gram
from gensim.models import Word2Vec
# gensim 버전이 다른 것인지 유튜브와는 달리 size 대신 vector_size 사용함
model = Word2Vec(sentences=news_texts,
window=4, # 고려할 앞 뒤 폭
vector_size=100, # 벡터 크기
min_count=5, # 사용할 단어의 최소 빈도 (5회 이하 단어 무시)
workers=4, # 동시에 처리할 작업의 수
sg=1) # 0 : cbow, 1 : skip-gram
model.save('word2vec.model2') # 저장하기
model = Word2Vec.load('word2vec.model2') # 불러오기
# model.wv['film']
model.wv.similarity('movie', 'film') # 0.92668915
model.wv.most_similar(positive=['woman'])
# [('husband', 0.9329707026481628),
('marina', 0.9309569597244263),
('sister', 0.9188961386680603),
('igor', 0.9188531637191772),
('shagen', 0.914876401424408),
('naked', 0.913983166217804),
('corpse', 0.9133069515228271),
('lyuda', 0.9116402268409729),
('shouted', 0.9090188145637512),
('daughters', 0.9089158773422241)]
model.wv.most_similar(positive=['woman', 'girl'], negative='soldier')
# [('mamma', 0.8757262825965881),
('marina', 0.8475387096405029),
('karina', 0.843293309211731),
('papa', 0.8417982459068298),
('lyuda', 0.8405961990356445),
('drove', 0.839897871017456),
('igor', 0.8369525671005249),
('sister', 0.8334420919418335),
('shouting', 0.8330908417701721),
('threw', 0.8318761587142944)]
임베딩 벡터 시각화
- metadata.tsv와 tensor.tsv 데이터 생성
from gensim.models import KeyedVectors
model.wv.save_word2vec_format('news_w2v')
!python -m gensim.scripts.word2vec2tensor -i news_w2v -o news_w2v
- Embedding Projector: https://projector.tensorflow.org/
위 사이트에 들어가서 두 개 tsv 파일 업로드 ('Load' 버튼 누르고) 가능!
짜잔!
'Computer > ML·DL·NLP' 카테고리의 다른 글
[이수안컴퓨터연구소] 합성곱 신경망 Convolution Neural Network (0) | 2022.01.11 |
---|---|
[이수안컴퓨터연구소] 순환 신경망 Recurrent Neural Network (0) | 2022.01.10 |
[이수안컴퓨터연구소] 토픽 모델링 Topic Modeling (0) | 2022.01.08 |
[스크랩] XGBoost 뿌수기! (0) | 2021.10.25 |
[이수안컴퓨터연구소] 의미 연결망 분석 Semantic Network Analysis (0) | 2021.08.07 |
댓글