인공지능/자연어 처리

자연어 처리 python 실습 - 한국어 Word2Vec 임베딩 만들기

이게될까 2024. 3. 17. 22:25
728x90
728x90

한국어 워드 임베딩 구축

Introduction

Chapter 4. 단어 임베딩 만들기 강의의 첫 번째 실습 강의인 한국어 워드 임베딩 구축 강의입니다.

강의에서 배웠던 단어 임베딩의 대표적인 방법인 Word2Vec을 활용하여 영어와 한국어 워드 임베딩을 직접 구축해보고 임베딩의 품질을 평가합니다.

1. 영어 워드 임베딩 구축

한국어 워드 임베딩을 만들기 전에 영어 워드 임베딩을 구축하며 임베딩 구축에 대한 감을 익혀봅시다.
간단한 실습을 위해, scikit-learn 패키지에서 제공하는 20 newsgroup를 활용합니다.

import pandas as pd
import numpy as np

from sklearn.datasets import fetch_20newsgroups

dataset = fetch_20newsgroups(shuffle=True, random_state=1, remove=('headers', 'footers', 'quotes'))
dataset = dataset.data
dataset[0]

"Well i'm not sure about the story nad it did seem biased. What\nI disagree with is your statement that the U.S. Media is out to\nruin Israels reputation. That is rediculous. The U.S. media is\nthe most pro-israeli media in the world. Having lived in Europe\nI realize that incidences such as the one described in the\nletter have occured. The U.S. media as a whole seem to try to\nignore them. The U.S. is subsidizing Israels existance and the\nEuropeans are not (at least not to the same degree). So I think\nthat might be a reason they report more clearly on the\natrocities.\n\tWhat is a shame is that in Austria, daily reports of\nthe inhuman acts commited by Israeli soldiers and the blessing\nreceived from the Government makes some of the Holocaust guilt\ngo away. After all, look how the Jews are treating other races\nwhen they got power. It is unfortunate.\n"

데이터 하나에 뉴스 하나가 들어가 있습니다.

print(f"데이터셋 총 개수 : {len(dataset)}")

데이터셋 총 개수 : 11314

뉴스가 엄청 많다.

news_df = pd.DataFrame({'document':dataset})
news_df

전처리가 필요해 보인다.

sklearn 패키지의 datasets 메소드에서 가져온 fetch_20newgroups 데이터셋의 총 개수는 11,314개입니다.

1.1. 데이터셋 전처리

데이터셋의 전처리를 진행할수록 워드 임베딩의 성능이 향상될 것이므로, 불필요한 토큰을 제거하고, 소문자화를 통한 정규화와 같은 간단한 전처리 작업을 진행합니다.

# 먼저 데이터셋 내에 결측값이 있는지 확인합니다
news_df.replace("", float("NaN"), inplace=True)
print(news_df.isnull().values.any()) # true 빈 값 있는지 확인

news_df = news_df.dropna().reset_index(drop=True) # 빈 값들을 지우고 인덱스를 없앤다.
print(f"필터링된 데이터셋 총 개수 : {len(news_df)}")

필터링된 데이터셋 총 개수 : 11096

news_df
# 열을 기준으로 중복제거
processed_news_df = news_df.drop_duplicates(['document']).reset_index(drop=True)
processed_news_df

len(processed_news_df.iloc[0][0]) # 852
processed_news_df.iloc[0][0]

중복된 애들 3개가 사라졌다.

"Well i'm not sure about the story nad it did seem biased. What\nI disagree with is your statement that the U.S. Media is out to\nruin Israels reputation. That is rediculous. The U.S. media is\nthe most pro-israeli media in the world. Having lived in Europe\nI realize that incidences such as the one described in the\nletter have occured. The U.S. media as a whole seem to try to\nignore them. The U.S. is subsidizing Israels existance and the\nEuropeans are not (at least not to the same degree). So I think\nthat might be a reason they report more clearly on the\natrocities.\n\tWhat is a shame is that in Austria, daily reports of\nthe inhuman acts commited by Israeli soldiers and the blessing\nreceived from the Government makes some of the Holocaust guilt\ngo away. After all, look how the Jews are treating other races\nwhen they got power. It is unfortunate.\n"

# 데이터셋 내 특수 문자를 제거합니다.
processed_news_df['document'] = processed_news_df['document'].str.replace("[^a-zA-Z]", " ")
processed_news_df

# 문서 내 길이가 너무 짧은 단어를 제거합니다. (단어의 길이가 2 이하)
processed_news_df['document'] = processed_news_df['document'].apply(lambda x: ' '.join([token for token in x.split() if len(token) > 2]))
processed_news_df # 길이가 너무 짧은 단어는 정보량이 적다라는 가정으로 2개가 넘어야 남기낟.

# 전체 길이가 200 이하이거나 전체 단어 개수가 5개 이하인 데이터를 필터링합니다.
processed_news_df = processed_news_df[processed_news_df.document.apply(lambda x: len(str(x)) <= 200 and len(str(x).split()) > 5)].reset_index(drop=True)
processed_news_df  # 많은 애들이 필터링 된다. 긴 뉴스들이 필터링 되었다.(여기선 시간 때문에)

# 전체 단어에 대한 소문자 변환 (정규화)
processed_news_df['document'] = processed_news_df['document'].apply(lambda x: x.lower())
processed_news_df  # 이름에서 대문자는 중요하지만 일단 오늘은 띄어쓰기 단위로 만들 것이기 때문에 이렇게 진행

import nltk

from nltk.corpus import stopwords

nltk.download('stopwords')

stop_words = stopwords.words('english')


print(len(stop_words)) # 179
print(stop_words[:10])

['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', "you're"]

# 데이터들의 불용어를 제외하여 띄어쓰기 단위로 문장을 분리합니다.
tokenized_doc = processed_news_df['document'].apply(lambda x: x.split())
tokenized_doc = tokenized_doc.apply(lambda x: [s_word for s_word in x if s_word not in stop_words])
tokenized_doc

0 [diet, evangelist, good, term, fits, atkins, g...
1 [title, says, need, know, rom, versions]
2 [move, political, bickering, appropriate, grou...
3 [texas, well, corpus, christi, anyway, pick, p...
4 [yes, coast, portland, really, nice, way, sure...
...
2580 [anybody, know, polygon, reduction, algorithm,...
2581 [mark, juric, programs, mjuric, uga, edu, univ...
2582 [sunroof, leaks, always, thought, things, roya...
2583 [danny, rubenstein, israeli, journalist, speak...
2584 [agree, home, runs, clemens, always, memorable...
Name: document, Length: 2585, dtype: object

tokenized_doc = tokenized_doc.to_list()
print(len(tokenized_doc)) # 2585

 

1.2. 영어 워드 임베딩 구축을 위한 단어 토큰화

이번 실습에서는 Word2Vec의 skip-gram 방법을 사용하여 워드 임베딩을 구축합니다.

Word2Vec의 Skip-gram을 사용하려면 데이터에 2개 이상의 단어가 있어야 합니다.

그래야만 중심 단어, 주변 단어의 관계가 성립하며 그렇지 않으면 샘플을 구성할 수 없어 에러가 발생합니다.

추가적인 전처리 과정에서 이를 만족하지 않는 샘플들을 제거해야 합니다.

# 단어가 1개 이하인 샘플의 인덱스를 찾아서 저장하고, 해당 샘플들은 제거.
drop_train = [index for index, sentence in enumerate(tokenized_doc) if len(sentence) <= 1]
tokenized_doc = np.delete(tokenized_doc, drop_train, axis=0)

print(len(tokenized_doc)) # 2585

print(f"최종 학습 데이터셋 수 : {len(tokenized_doc)}") # 2585

#단어 집합을 생성하고, 단어 토큰화(정수 인코딩)를 진행합니다.

from tensorflow.keras.preprocessing.text import Tokenizer

tokenizer = Tokenizer()
tokenizer.fit_on_texts(tokenized_doc)

word2idx = tokenizer.word_index
idx2word = {value : key for key, value in word2idx.items()}
encoded = tokenizer.texts_to_sequences(tokenized_doc)

#생성된 단어 사전의 크기를 확인합니다.

vocab_size = len(word2idx) + 1 
print(f"단어 사전의 크기 : {vocab_size}") # 9519
print(encoded[0])

단어 사전이 엄청 크다.

[1927, 4113, 14, 1928, 1220, 4114, 742, 859, 1010, 1011, 1012, 1013, 1014, 743, 1015, 860, 10, 861, 666, 265]

 

1.3. Word2Vec과 네거티브 샘플링을 활용한 데이터셋 구축

이전 강의에서 학습한대로, 간단한 정제(cleaning), 정규화(normalization), 토큰화(tokenization) 과정의 전처리 과정을 간단히 실습해봤습니다.

이제 Word2Vec 모델에 네거티브 샘플링을 활용할 수 있도록 데이터셋을 구성해보겠습니다.

네거티브 샘플링을 위해서 keras에서 제공하는 전처리 도구인 skipgrams를 사용합니다.


from tensorflow.keras.preprocessing.sequence import skipgrams

#모든 데이터셋에 대해 임베딩 구축을 시도하기에 앞서, 일부 데이터만 사용하여 작업이 잘 진행되는지에 대해서 확인해보겠습니다.
# 네거티브 샘플링

skip_grams = [skipgrams(sample, vocabulary_size=vocab_size, window_size=10) for sample in encoded[:5]] # 보켑 사이즈랑 윈도우 사이즈 꼭 넣어줘야 하는 것 생각하기
print(f"전체 샘플 수 : {len(skip_grams)}") # 5

# 첫번째 샘플인 skip_grams[0] 내 skipgrams로 형성된 데이터셋 확인
pairs, labels = skip_grams[0][0], skip_grams[0][1]
print(f"first 3 pairs: {pairs[:3]}")
print(f"first 3 labels: {labels[:3]}")

first 3 pairs: [[1014, 5110], [1015, 3051], [1015, 1010]]
first 3 labels: [0, 0, 1]

윈도우 사이즈 내에 있으면 1, 없으면 0이다.

# 첫번째 뉴스그룹 샘플에 대해서 생긴 pairs와 labels의 개수
print(len(pairs)) # 580
print(len(labels)) # 580
#아래 예시처럼 윈도우 크기 내에서 중심 단어, 주변 단어의 관계를 가지는 경우(**positive sample**)에는 1의 레이블을 갖도록 하고, 그렇지 않은 경우(**negative sample**)는 0의 레이블을 가지도록 하여 데이터셋을 구성합니다.

#이 과정은 각각의 데이터에 대해서 동일한 프로세스로 작동합니다..
for i in range(5):
  print("({:s} ({:d}), {:s} ({:d})) -> {:d}".format(
    idx2word[pairs[i][0]], pairs[i][0], 
    idx2word[pairs[i][1]], pairs[i][1], 
    labels[i])
  )

(geb (1014), dialin (5110)) -> 0
(dsl (1015), materials (3051)) -> 0
(dsl (1015), jxp (1010)) -> 1
(intellect (1013), surrender (666)) -> 1
(pitt (860), offense (1989)) -> 0

예시가 잘 생성됨을 확인했으니, 해당 작업을 더 많음 뉴스그룹 샘플에 대해서 수행합니다.

(학습 데이터셋의 규모가 크므로, 1000개 데이터로만 학습합니다)

training_dataset = [skipgrams(sample, vocabulary_size=vocab_size, window_size=10) for sample in encoded[:1000]]

print(len(training_dataset))# 1000

1.4. Skip-Gram with Negative Sampling(SGNS)으로 워드 임베딩 구축

from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.layers import Embedding, Reshape, Activation, Input
from tensorflow.keras.layers import Dot
from tensorflow.keras.utils import plot_model
from IPython.display import SVG

#임베딩 벡터의 차원은 100으로 정하고, 두 개의 임베딩 층을 추가

embedding_dim = 100

# 중심 단어를 위한 임베딩 테이블
w_inputs = Input(shape=(1, ), dtype='int32')
word_embedding = Embedding(vocab_size, embedding_dim)(w_inputs)

# 주변 단어를 위한 임베딩 테이블
c_inputs = Input(shape=(1, ), dtype='int32')
context_embedding  = Embedding(vocab_size, embedding_dim)(c_inputs)

#각각의 임베딩 행렬은 중심 단어와 주변 단어를 위한 임베딩 행렬입니다.

#데이터셋 내 각각의 단어가 임베딩 행렬을 거쳐서 나온 벡터를 활용하여 두 단어 벡터 간 내적을 계산합니다.

#내적 값의 결과는 1 또는 0을 예측해야 하므로 시그모이드 함수를 활성화 함수로 거쳐 최종 예측값을 얻습니다.

dot_product = Dot(axes=2)([word_embedding, context_embedding])
dot_product = Reshape((1,), input_shape=(1, 1))(dot_product)
output = Activation('sigmoid')(dot_product) # 1아니면 0임을 맞춰야 함으로 시그모이드

model = Model(inputs=[w_inputs, c_inputs], outputs=output)
model.summary()
model.compile(loss='binary_crossentropy', optimizer='adam')
plot_model(model, to_file='model3.png', show_shapes=True, show_layer_names=True, rankdir='TB')

Model: "model"


Layer (type) Output Shape Param # Connected to

input_1 (InputLayer) [(None, 1)] 0 []

input_2 (InputLayer) [(None, 1)] 0 []

embedding (Embedding) (None, 1, 100) 951900 ['input_1[0][0]']

embedding_1 (Embedding) (None, 1, 100) 951900 ['input_2[0][0]']

dot (Dot) (None, 1, 1) 0 ['embedding[0][0]',
'embedding_1[0][0]']

reshape (Reshape) (None, 1) 0 ['dot[0][0]']

activation (Activation) (None, 1) 0 ['reshape[0][0]']

==================================================================================================
Total params: 1,903,800
Trainable params: 1,903,800
Non-trainable params: 0

모델 학습은 10 에폭만 진행할게요!

for epoch in range(10):
  loss = 0
  for _, elem in enumerate(skip_grams):
    first_elem = np.array(list(zip(*elem[0]))[0], dtype='int32')
    second_elem = np.array(list(zip(*elem[0]))[1], dtype='int32')
    labels = np.array(elem[1], dtype='int32')
    X = [first_elem, second_elem]
    Y = labels
    loss += model.train_on_batch(X,Y)  
  print('Epoch :',epoch + 1, 'Loss :',loss)

Epoch : 1 Loss : 3.4657832384109497
Epoch : 2 Loss : 3.4539295434951782
Epoch : 3 Loss : 3.442515194416046
Epoch : 4 Loss : 3.4304059743881226
Epoch : 5 Loss : 3.4170904755592346
Epoch : 6 Loss : 3.4021273851394653
Epoch : 7 Loss : 3.3851250410079956
Epoch : 8 Loss : 3.365738332271576
Epoch : 9 Loss : 3.343664824962616
Epoch : 10 Loss : 3.318642258644104

 

1.5. 임베딩 품질 확인

우리는 학습된 모델의 결과를 확인해볼 수 있습니다.

학습된 임베딩 벡터들을 vector.txt에 저장하고, 이를 gensim의 models.KeyedVectors.load_word2vec_format()으로 로드하면 단어 벡터 간 유사도를 구할 수 있습니다!

 

import gensim

f = open('vectors.txt' ,'w')
f.write('{} {}\n'.format(vocab_size-1, embedding_dim))
vectors = model.get_weights()[0]
for word, i in tokenizer.word_index.items():
  f.write('{} {}\n'.format(word, ' '.join(map(str, list(vectors[i, :])))))
f.close()

# 모델 로드
w2v = gensim.models.KeyedVectors.load_word2vec_format('./vectors.txt', binary=False)

w2v.most_similar(positive=['apple'])

[('cursor', 0.3650397062301636),
('republicans', 0.33872145414352417),
('sampling', 0.33657264709472656),
('accept', 0.33537811040878296),
('laying', 0.3275332450866699),
('wee', 0.32318687438964844),
('viewing', 0.32082104682922363),
('hasan', 0.32066336274147034),
('thanks', 0.3180510997772217),
('fundraising', 0.31026703119277954)]

가장 유사한 애들을 가지고 온 것이다.

w2v.most_similar(positive=['doctor'])

[('heckling', 0.36563640832901),
('izzat', 0.34239625930786133),
('trackballs', 0.339799165725708),
('summary', 0.3346317410469055),
('autotracing', 0.33384624123573303),
('venuti', 0.330957293510437),
('hurricanes', 0.31608933210372925),
('board', 0.3141782283782959),
('james', 0.3138725459575653),
('orbit', 0.3122623562812805)]

w2v.most_similar(positive=['engine'])

[('suprafaxmodem', 0.3571285009384155),
('sixties', 0.35429346561431885),
('alpha', 0.34206321835517883),
('represent', 0.33519864082336426),
('sealed', 0.32704854011535645),
('aldrin', 0.3193432092666626),
('hardly', 0.311809241771698),
('callously', 0.3108622133731842),
('rubberband', 0.30534628033638),
('inode', 0.304291695356369)]

학습이 잘 되었다면 유사한 단어가 잘 나올 것이다.

 

2. 한국어 워드 임베딩 구축

위에서 실습한 내용을 바탕으로 한국어 워드 임베딩을 구축해봅시다!

2.1. 데이터셋 수집

이번에는 AI Hub에 공개되어있는 한국어 도서 요약 데이터셋을 활용해볼게요!

전처리가 완료된 데이터셋은 링크에서 받으실 수 있으며,

해당 데이터셋에 대한 이용 약관은 여기에서 확인할 수 있습니다.

해당 파일의 용량이 매우 크므로, 이번 강의에서는 위 데이터셋의 일부를 발췌한 데이터셋을 사용할게요.

data = open('/content/korean_word_embedding.txt', 'r', encoding='utf-8')
lines = data.readlines()
passages = []
summaries = []

for line in lines:
    line = line.strip()
    if len(line.split('\t')) < 2:
        print(line)
        break
    passages.append('<s>' + line.split('\t')[0] + '</s>')
    summaries.append('<s>' + line.split('\t')[1] + '</s>')

data.close()

print("passages:", passages[0])
print("summaries:", summaries[0])

passages: 자신의 생각을 불명확하게 표현하는 사람들은 생각도 불명확할 가능성이 높다. 따라서 생각을 명료하게 정리하는 것이 명확한 글쓰기의 시작이다. 존 케네스 갈브레이드(John Kenneth Galbraith) 교수는 “아무리 복잡하고 어려운 주제라고 명쾌한 언어로 설명이 가능하다. 하지만 필자가 그 주제에 대해 완전히 이해하지 못하면 절대로 명확하게 쓸 수 없다.”고 했다. 명료한 글은 명료한 생각에서 나온다. 그렇지만 글을 고치다보면 생각도 명료하게 된다. 명료하게 쓰려면 내용을 단순화해야 한다. 많은 것을 전달하려는 욕심을 버리고 한두가지로 선택하고 거기 집중해야 한다. 앞에서 이야기한 간결성은 명확성에 도움이 된다. 명료함은 복잡한 설득기법을 사용하는 것보다 더 중요하다. 판사는 이해할 수 없는 사실관계 진술로는 설득되지 않는다. 설득기법이 명료함을 해치는 때에는 그 기법을 사용하지 않는다.
summaries: 주제가 아무리 복잡하고 어려울 지라도 필자가 주제에 대해 완벽하게 이해하고 있다면 명확한 글쓰기는 당연 가능하다. 명확한 글쓰기를 할 때 가장 중요한 점은 내용의 단순화 및 간결성이고 많은 것을 전달하려는 욕심을 버리는 것이다.

summary_dataset = passages + summaries

#전처리가 완료된 데이터셋이므로, 바로 사용할 수 있습니다.

kor_tokenized_doc = [text.split() for text in summary_dataset]
print(len(kor_tokenized_doc)) # 500

2.2. 한국어 워드 임베딩 구축을 위한 단어 토큰화

from tensorflow.keras.preprocessing.text import Tokenizer

tokenizer = Tokenizer()
tokenizer.fit_on_texts(kor_tokenized_doc)

word2idx = tokenizer.word_index
idx2word = {value : key for key, value in word2idx.items()}
encoded = tokenizer.texts_to_sequences(kor_tokenized_doc)

#생성된 단어 사전의 크기를 확인합니다.

vocab_size = len(word2idx) + 1 
print(f"단어 사전의 크기 : {vocab_size}") # 15519

print(encoded[0])

[4793, 1339, 4794, 2234, 1340, 2235, 4795, 144, 342, 32, 1339, 1341, 4796, 23, 343, 4797, 4798, 4799, 4800, 4801, 4802, 4803, 4804, 4805, 1342, 905, 4806, 4807, 2236, 4808, 218, 96, 2237, 10, 2238, 34, 537, 4809, 4810, 906, 538, 4811, 1, 1343, 344, 2239, 2240, 2239, 4812, 4813, 4814, 667, 4815, 2235, 1341, 123, 1341, 4816, 417, 4817, 14, 76, 24, 2241, 2242, 4818, 4819, 4820, 4821, 4822, 14, 668, 4823, 4824, 4825, 539, 123, 4826, 1344, 4827, 418, 4828, 44, 151, 4829, 907, 1, 77, 4830, 4831, 4832, 419, 4833, 4834, 4835, 292, 10, 4836, 2243, 2244]

2.3. Word2Vec과 네거티브 샘플링을 활용한 데이터셋 구축

앞선 실습에서처럼 Word2Vec 모델에 네거티브 샘플링을 활용할 수 있도록 데이터셋을 구성해보겠습니다.

from tensorflow.keras.preprocessing.sequence import skipgrams
# 네거티브 샘플링
skip_grams = [skipgrams(sample, vocabulary_size=vocab_size, window_size=10) for sample in encoded[:5]]
print(f"전체 샘플 수 : {len(skip_grams)}") # 5

# 첫번째 샘플인 skip_grams[0] 내 skipgrams로 형성된 데이터셋 확인
pairs, labels = skip_grams[0][0], skip_grams[0][1]

# 첫번째 뉴스그룹 샘플에 대해서 생긴 pairs와 labels의 개수
print(len(pairs))#3780
print(len(labels)) #3780

for i in range(5):
  print("({:s} ({:d}), {:s} ({:d})) -> {:d}".format(
    idx2word[pairs[i][0]], pairs[i][0], 
    idx2word[pairs[i][1]], pairs[i][1], 
    labels[i])
  )

(가능하다. (218), 명쾌한 (4807)) -> 1
(복잡한 (1344), 수 (1)) -> 1
(진술로는 (4831), 합니다. (14624)) -> 0
(도움이 (539), 된다. (123)) -> 1
(이해할 (907), 없는 (77)) -> 1

확인이 끝났으니 데이터셋을 구축합니다.

training_dataset = [skipgrams(sample, vocabulary_size=vocab_size, window_size=10) for sample in encoded]

2.4. Skip-Gram with Negative Sampling(SGNS)으로 워드 임베딩 구축

from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.layers import Embedding, Reshape, Activation, Input
from tensorflow.keras.layers import Dot
from tensorflow.keras.utils import plot_model
from IPython.display import SVG

embedding_dim = 100

# 중심 단어를 위한 임베딩 테이블
w_inputs = Input(shape=(1, ), dtype='int32')
word_embedding = Embedding(vocab_size, embedding_dim)(w_inputs)

# 주변 단어를 위한 임베딩 테이블
c_inputs = Input(shape=(1, ), dtype='int32')
context_embedding  = Embedding(vocab_size, embedding_dim)(c_inputs)

dot_product = Dot(axes=2)([word_embedding, context_embedding])
dot_product = Reshape((1,), input_shape=(1, 1))(dot_product)
output = Activation('sigmoid')(dot_product)

model = Model(inputs=[w_inputs, c_inputs], outputs=output)
model.summary()
model.compile(loss='binary_crossentropy', optimizer='adam')
plot_model(model, to_file='model3.png', show_shapes=True, show_layer_names=True, rankdir='TB')

Model: "model_1"


Layer (type) Output Shape Param # Connected to

input_3 (InputLayer) [(None, 1)] 0 []

input_4 (InputLayer) [(None, 1)] 0 []

embedding_2 (Embedding) (None, 1, 100) 1551900 ['input_3[0][0]']

embedding_3 (Embedding) (None, 1, 100) 1551900 ['input_4[0][0]']

dot_1 (Dot) (None, 1, 1) 0 ['embedding_2[0][0]',
'embedding_3[0][0]']

reshape_1 (Reshape) (None, 1) 0 ['dot_1[0][0]']

activation_1 (Activation) (None, 1) 0 ['reshape_1[0][0]']

==================================================================================================
Total params: 3,103,800
Trainable params: 3,103,800
Non-trainable params: 0

for epoch in range(10):
  loss = 0
  for _, elem in enumerate(skip_grams):
    first_elem = np.array(list(zip(*elem[0]))[0], dtype='int32')
    second_elem = np.array(list(zip(*elem[0]))[1], dtype='int32')
    labels = np.array(elem[1], dtype='int32')
    X = [first_elem, second_elem]
    Y = labels
    loss += model.train_on_batch(X,Y)  
  print('Epoch :',epoch + 1, 'Loss :',loss)

Epoch : 1 Loss : 3.4659515023231506
Epoch : 2 Loss : 3.458793520927429
Epoch : 3 Loss : 3.451488673686981
Epoch : 4 Loss : 3.443537175655365
Epoch : 5 Loss : 3.434665262699127
Epoch : 6 Loss : 3.4245815873146057
Epoch : 7 Loss : 3.4129945039749146
Epoch : 8 Loss : 3.399620532989502
Epoch : 9 Loss : 3.3841899037361145
Epoch : 10 Loss : 3.36644846200943

2.5. 임베딩 품질 확인

f = open('kor_vectors.txt' ,'w')
f.write('{} {}\n'.format(vocab_size-1, embedding_dim))
vectors = model.get_weights()[0]
for word, i in tokenizer.word_index.items():
  f.write('{} {}\n'.format(word, ' '.join(map(str, list(vectors[i, :])))))
f.close()
# 모델 로드
w2v = gensim.models.KeyedVectors.load_word2vec_format('./kor_vectors.txt', binary=False)

w2v.most_similar(positive=['주제'])

[('제조업이', 0.37587451934814453),
('수립되어야', 0.34317490458488464),
('노동쟁의', 0.3290068209171295),
('의견만으로는', 0.3227134644985199),
('인식하여야', 0.3224392831325531),
('뭐라고', 0.32067131996154785),
('수입선', 0.31716734170913696),
('덕분에', 0.31061339378356934),
('사업이었고,', 0.30967408418655396),
('이루어진다.', 0.3080037832260132)]

w2v.most_similar(positive=['명쾌한'])

[('인센티브', 0.3752218186855316),
('주제라고', 0.3686476945877075),
('kenneth', 0.35046812891960144),
('발생되는', 0.34564584493637085),
('제출할', 0.34299370646476746),
('처와', 0.3421686887741089),
('방식을', 0.33675259351730347),
('가능하다.', 0.3364788591861725),
('낙인으로', 0.3357722759246826),
('것이다.”라고', 0.33342456817626953)]

728x90