(02강) Word Embedding

210906

3. Word Embedding : Word2Vec, GloVe

워드 임베딩은 자연어가 단어들을 정보의 기본 단위로 해서 각 단어들을 특정 공간에 한점으로 나타내는 벡터로 변환해주는 기법이다.

고양이를 의미하는 cat과 어린 고양이를 의미하는 kitty는 의미가 유사하므로 각 점은 가까이 위치하고 hamburger와는 멀리 위치하게 된다.

Word2Vec

워드 임베딩을 하는 방법 중 대표적인 방법. 같은 문장에서 나타난 인접한 단어들 간에 의미가 비슷할 것이라는 가정을 사용한다. "The cat purrs" 와 " This cat hunts mice" 라는 문장에서 cat이라는 단어는 The, purrs, This, hunts, mice 와 관련이 있다.

어떠한 단어가 주변의 등장하는 단어를 통해 그 의미를 알 수 있다는 사실에 착안한다. 주어진 학습 데이터를 바탕으로 cat 주변에 나타나는 주변 단어들의 확률 분포를 예측하게 된다. 보다 구체적으로는 cat을 입력단어로 주고 주변단어를 숨긴채 예측하도록 하는 방식으로 Word2Vec의 학습이 진행된다.

구체적인 학습 방법은 다음과 같다.

  • 처음에는 "I study math" 라는 문장이 주어진다

  • word별로 tokenization이 이루어지고 유의미한 단어를 선별해 사전을 구축한다

  • 이 후 사전에 있는 단어들은 사전의 사이즈만큼의 차원을 가진 ont-hot vector로 표현된다.

  • 이후 sliding window라는 기법을 적용해서 한 단어를 중심으로 앞뒤로 나타나는 단어들 각각과의 입출력 단어 쌍을 구성하게된다.

    • 예를 들어 window size가 3이면 앞뒤로 하나의 단어만을 보게된다.

    • 중심 단어가 I 라면 (I, study) 라는 쌍을 구할 수 있게 된다.

    • 중심 단어가 study 라면 (study, I) 와 (study, math) 라는 쌍을 얻을 수 있다.

    • 즉, (중심 단어, 주변 단어) 라는 관계를 가진 쌍을 window size에 따라 만들어 낼 수 있게된다.

  • 이렇게 만들어진 입출력 단어 쌍들에 대해 예측 Task를 수행하는 Two layer를 만들게 된다.

    • 입력과 출력노드의 개수는 Vocab의 사이즈와 같다.

    • 가운데에 있는 Hidden layer의 노드 수는 사용자가 정하는 하이퍼 파라미터이며, 워드임베딩을 수행하는 차원 수와 동일한 값으로 주로 결정한다.

  • 만약 (study, math) 쌍을 학습한다고 하자. 그러면 input값으로 study를 의미하는 [0, 1, 0] 이 입력된다.

    • study : [0, 1, 0]

    • math : [0, 0, 1]

    • I : [1, 0, 0]

  • Input layer는 3차원 Hidden layer는 2차원이므로 W는 3 * 2의 형태를 가져야 하며 실제 X와 곱해질 때의 2 * 3의 모양으로 곱해진다.

  • Output layer는 3차원이므로 W는 3 * 2의 모양으로 곱해진다.

  • 이후, Softmax를 적용해서 확률분포 벡터를 얻게된다. 그리고 이를 Ground Truth와의 거리가 제일 가까워지게 하는 Softmax Loss를 적용함으로써 W1과 W2를 학습하게된다.

  • 여기서 W1과 X를 내적하게 되는데, X의 특성상 특정 인덱스만 1이고 나머지는 다 0이다 보니, W1의 특정 인덱스 값만 뽑아온다고 볼 수 있다.

    • 그래서 코드로 구현할 때에도 내적을 구현하지 않고 X에서 1이 존재하는 인덱스를 가지고 W1에서 가져오게된다.

  • 마찬가지로 W2와 W1*X를 내적할 때도 Ground Truth값인 Y에서 1이 존재하는 인덱스에 해당하는 값만 확인하면 된다. 따라서 W2는 Y에서 1이 존재하는 인덱스에서의 값만 뽑아온다.

    • 예측값과 실제값이 가까워지려면 W2 * W1 * X의 값은 정답에 해당하는 인덱스의 값은 무한대에 가까워야 하고 그외의 값들은 음의 무한대에 가까워야 한다.

    • 그래야 Softmax를 적용했을 때 양의 무한대에 대해서만 1을 얻고 나머지 위치에서는 0을 얻기 때문

위 링크에 들어가면 워드 임베딩을 시각적인 상태로 볼 수 있다. 예시로는 8개의 단어를 사용했으며 hidden size는 5이다.

또, 다음과 같이 W1과 W2 행렬을 시각적으로 확인할 수 있다.

  • W1은 Transpose를 통해 W2와 사이즈가 같도록 나타냈다.

  • 푸른색은 음수, 붉은색은 양수이다.

  • 현재는 Random Initialization 된 상태이다.

각 단어의 임베딩 된 좌표평면도 다음과 같다.

  • W1과 W2의 차원은 5개지만 PCA를 통해서 2-Dimension으로 차원 축소를 한 뒤 Scatter plot의 형태로 각각의 벡터들을 시각화 한 결과이다.

Trainin data는 다음과 같다.

eat|apple
eat|orange
eat|rice
drink|juice
drink|milk
drink|water
orange|juice
apple|juice
rice|milk
milk|drink
water|drink
juice|drink

이후, 300번의 학습을 진행하게 된 후의 결과를 확인해보자.

  • juice의 input vector는 drink의 output vector와는 유사한 벡터를 가지게 되는 것을 볼 수 있다.

    • 이 두 단어벡터의 내적값은 커지게 된다.

    • 또한 milk와 water와도 유사하다.

  • eat과 apple도 유사한 벡터를 가진다.

    • orange와도 유사하다.

입력 단어와 출력 단어의 두 개의 벡터를 최종적으로 얻을 수 있고 둘 중에 어느것을 워드 임베딩의 아웃풋으로 사용해도 상관이 없으나 통상적으로 입력 단어의 벡터를 사용하게 된다.

이렇게 학습된 Word2Vec은 Words간의 의미론적 관계를 Vector Embedding로 잘 표현할 수 있다.

다음 그림은 Word2Vec으로 학습된 단어들의 임베딩 벡터를 표현한 것이다.

MAN에서 WOMAN으로의 벡터나 KING에서 QUEEN 벡터가 남성에서 여성으로의 변화를 의미하는 벡터의 관계가 워드임베딩 관계에서 잘 학습된 것을 볼 수 있다.

위 링크에서 Word2Vec을 한글 데이터를 통해 학습한 결과를 볼 수 있다.

꽤? 똑똑한 것 같군

또, 위 링크에서는 여러 단어들이 주어졌을 때 나머지 단어와 가장 의미가 상이한 단어를 찾아내는 작업을 할 수 있다

  • 엄마, 아빠, 할아버지, 할머니, 이웃사촌 => 이웃사촌

  • 각 벡터 사이의 평균 거리를 구해서 평균 거리가 가장 큰 단어를 반환한다.

  • math, shopping, reading, science => shopping

Word2Vec은 단어 자체의 의미를 파악하는 Task 이외에도 다양한 자연어 처리 Task에서 자연어를 Word단위의 벡터로 나타내어 Task의 입력으로 제공되는 형태로 많이 사용된다.

  • 기계 번역 : 같은 의미를 지닌 단어가 align 될 수 있도록 한다.

  • 감정 분석 : 각 단어들의 긍/부정의 의미를 보다 용이하게 파악할 수 있도록 하는 워드 임베딩을 제공한다.

  • Image Captioning : 이미지의 상황을 잘 이해하고 이에대한 설명글을 자연어의 형태로 생성하는 것

  • PoS tagging, 고유명사인식 등

GloVe

Word2Vec과 더불어 많이 쓰이는 또다른 워드 임베딩 방법이다. Word2Vec과의 큰 차이점은 다음과 같다.

  • 각 입력 및 출력 쌍들에 대해서 학습 데이터에서 두 단어가 한 윈도우 내에서 몇번 등장했는지를 사전에 미리 계산한다.

  • 다음 수식처럼 입력벡터와 출력벡터의 내적값에서 두 단어가 한 윈도우 내에서 동시에 몇번 등장했는지에 대한 값에 log를 취한 값을 뺀 값을 Loss Function으로 사용한다.

    • 그래서, 두 내적값이 P에 fit되도록 한다.

  • Word2Vec에서 자주 등장하는 단어는 자주 학습됨으로써 워드 임베딩의 내적값이 그에 비례해서 커지게 되는데, GloVe에서는 단어쌍이 동시에 등장하는 횟수를 미리 계산하고 이에 대한 log값을 Ground Truth로 사용했다는 점에서 중복되는 계산을 줄여줄 수 있다는 장점이 존재한다. 그래서 학습이 Word2Vec보다 더 빠르게 되며 더 적은 데이터로도 학습이 잘 된다.

  • 세부적으로는 더 많은 차이점이 있지만 큰 틀에서는 이정도의 차이가 있다.

  • 두 방법은 주어진 학습데이터에 기반해서 워드임베딩을 학습하는 동일한 알고리즘이고 실제로 Task에 적용했을 때 성능도 비등비등하게 나온다.

또, GloVe 모델은 추천시스템에 많이 활용되는 알고리즘인 Co-occurrence matrix의 low rank matrix factorization의 Task로서 선형대수의 관점에서 이해할 수 있다.

특정 관점(또는 기준)에서 차이가 있는 단어들의 벡터관계를 살펴보면 비슷한 것을 알 수 있다.

man / woman

company / ceo

단어들간의 의미를 고려해야 하는 관계 뿐만 아니라 형용상의 원형과 비교급, 최상급의 관계를 가지고 있는 단어들 사이에도 이러한 문법적인 관계까지도 GloVe가 잘 학습했다고 할 수 있다.

GloVe는 Open Source로 사용할 수 있고 위키피디아 2014년도 버전 + Gigaword 5를 학습한 pretrained 된 모델을 사용할 수 있다.

  • 60억개의 토큰(또는 단어)이 있다.

  • 중복된 단어를 제거하고 사전으로 구성된 단어는 40만개이다.

  • uncased는 대소문자를 구분하지 않았다라는 뜻

    • he와 He를 같은 단어로 취급했다는 뜻

  • 50d, 100d, 200d, 300d는 GloVe 알고리즘을 적용할 때 결정한 Target Dimension의 크기이다.

    • 입력 단어와 출력 단어의 벡터의 크기이다.

실습

필요 패키지와 데이터 전처리는 이전 실습과 매우 비슷하므로 설명은 생략한다.

필요 패키지

!pip install konlpy
from tqdm import tqdm
from konlpy.tag import Okt
from torch import nn
from torch.nn import functional as F
from torch.utils.data import Dataset, DataLoader
from collections import defaultdict

import torch
import copy
import numpy as np

데이터 전처리

train_data = [
  "정말 맛있습니다. 추천합니다.",
  "기대했던 것보단 별로였네요.",
  "다 좋은데 가격이 너무 비싸서 다시 가고 싶다는 생각이 안 드네요.",
  "완전 최고입니다! 재방문 의사 있습니다.",
  "음식도 서비스도 다 만족스러웠습니다.",
  "위생 상태가 좀 별로였습니다. 좀 더 개선되기를 바랍니다.",
  "맛도 좋았고 직원분들 서비스도 너무 친절했습니다.",
  "기념일에 방문했는데 음식도 분위기도 서비스도 다 좋았습니다.",
  "전반적으로 음식이 너무 짰습니다. 저는 별로였네요.",
  "위생에 조금 더 신경 썼으면 좋겠습니다. 조금 불쾌했습니다."       
]

test_words = ["음식", "맛", "서비스", "위생", "가격"]
tokenizer = Okt()

def make_tokenized(data):
  tokenized = []
  for sent in tqdm(data):
    tokens = tokenizer.morphs(sent, stem=True)
    tokenized.append(tokens)

  return tokenized
  
  train_tokenized = make_tokenized(train_data)
word_count = defaultdict(int)

for tokens in tqdm(train_tokenized):
  for token in tokens:
    word_count[token] += 1
    
word_count = sorted(word_count.items(), key=lambda x: x[1], reverse=True)
print(list(word_count))
[('.', 14), ('도', 7), ('이다', 4), ('좋다', 4), ('별로', 3), ('다', 3), ('이', 3), ('너무', 3), ('음식', 3), ('서비스', 3), ('하다', 2), ('방문', 2), ('위생', 2), ('좀', 2), ('더', 2), ('에', 2), ('조금', 2), ('정말', 1), ('맛있다', 1), ('추천', 1), ('기대하다', 1), ('것', 1), ('보단', 1), ('가격', 1), ('비싸다', 1), ('다시', 1), ('가다', 1), ('싶다', 1), ('생각', 1), ('안', 1), ('드네', 1), ('요', 1), ('완전', 1), ('최고', 1), ('!', 1), ('재', 1), ('의사', 1), ('있다', 1), ('만족스럽다', 1), ('상태', 1), ('가', 1), ('개선', 1), ('되다', 1), ('기르다', 1), ('바라다', 1), ('맛', 1), ('직원', 1), ('분들', 1), ('친절하다', 1), ('기념일', 1), ('분위기', 1), ('전반', 1), ('적', 1), ('으로', 1), ('짜다', 1), ('저', 1), ('는', 1), ('신경', 1), ('써다', 1), ('불쾌하다', 1)]
w2i = {}
for pair in tqdm(word_count):
  if pair[0] not in w2i:
    w2i[pair[0]] = len(w2i)
    
print(train_tokenized)
print(w2i)    
[['정말', '맛있다', '.', '추천', '하다', '.'], ['기대하다', '것', '보단', '별로', '이다', '.'], ['다', '좋다', '가격', '이', '너무', '비싸다', '다시', '가다', '싶다', '생각', '이', '안', '드네', '요', '.'], ['완전', '최고', '이다', '!', '재', '방문', '의사', '있다', '.'], ['음식', '도', '서비스', '도', '다', '만족스럽다', '.'], ['위생', '상태', '가', '좀', '별로', '이다', '.', '좀', '더', '개선', '되다', '기르다', '바라다', '.'], ['맛', '도', '좋다', '직원', '분들', '서비스', '도', '너무', '친절하다', '.'], ['기념일', '에', '방문', '하다', '음식', '도', '분위기', '도', '서비스', '도', '다', '좋다', '.'], ['전반', '적', '으로', '음식', '이', '너무', '짜다', '.', '저', '는', '별로', '이다', '.'], ['위생', '에', '조금', '더', '신경', '써다', '좋다', '.', '조금', '불쾌하다', '.']]
{'.': 0, '도': 1, '이다': 2, '좋다': 3, '별로': 4, '다': 5, '이': 6, '너무': 7, '음식': 8, '서비스': 9, '하다': 10, '방문': 11, '위생': 12, '좀': 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, '상태': 39, '가': 40, '개선': 41, '되다': 42, '기르다': 43, '바라다': 44, '맛': 45, '직원': 46, '분들': 47, '친절하다': 48, '기념일': 49, '분위기': 50, '전반': 51, '적': 52, '으로': 53, '짜다': 54, '저': 55, '는': 56, '신경': 57, '써다': 58, '불쾌하다': 59}

데이터셋 클래스

CBOWSkipGram 두 가지 방식에 대한 데이터셋을 정의한다.

  • CBOW : Continuous Bag of Words의 약어로 주변에 있는 단어들을 가지고 중간 단어를 예측 하는 방법이다.

  • SkipGram : CBOW와 반대로 중간 단어를 가지고 주변 단어를 예측하는 방법이다.

CBOW

class CBOWDataset(Dataset):
  def __init__(self, train_tokenized, window_size=2):
    self.x = []
    self.y = []

    for tokens in tqdm(train_tokenized):
      token_ids = [w2i[token] for token in tokens]
      for i, id in enumerate(token_ids):
        if i-window_size >= 0 and i+window_size < len(token_ids):
          self.x.append(token_ids[i-window_size:i] + token_ids[i+1:i+window_size+1])
          self.y.append(id)

    self.x = torch.LongTensor(self.x)  # (전체 데이터 개수, 2 * window_size)
    self.y = torch.LongTensor(self.y)  # (전체 데이터 개수)

  def __len__(self):
    return self.x.shape[0]

  def __getitem__(self, idx):
    return self.x[idx], self.y[idx]
  • 중심 단어를 기준으로 윈도우 사이즈만큼의 주변 단어를 x로, 중심 단어를 y로 설정한다.

  • 이 때(window_size = 2 기준) 처음 두 단어와 마지막 두 단어는 학습 데이터에 포함되지 못하는건가?

SkipGram

class SkipGramDataset(Dataset):
  def __init__(self, train_tokenized, window_size=2):
    self.x = []
    self.y = []

    for tokens in tqdm(train_tokenized):
      token_ids = [w2i[token] for token in tokens]
      for i, id in enumerate(token_ids):
        if i-window_size >= 0 and i+window_size < len(token_ids):
          self.y += (token_ids[i-window_size:i] + token_ids[i+1:i+window_size+1])
          self.x += [id] * 2 * window_size

    self.x = torch.LongTensor(self.x)  # (전체 데이터 개수)
    self.y = torch.LongTensor(self.y)  # (전체 데이터 개수)

  def __len__(self):
    return self.x.shape[0]

  def __getitem__(self, idx):
    return self.x[idx], self.y[idx]
  • 주변 단어를 y로, 중심 단어를 x로 설정한다. 이 때 x의 개수를 주변 단어의 개수와 통일시켜준다.

cbow_set = CBOWDataset(train_tokenized)
skipgram_set = SkipGramDataset(train_tokenized)

모델 클래스

CBOW

class CBOW(nn.Module):
  def __init__(self, vocab_size, dim):
    super(CBOW, self).__init__()
    self.embedding = nn.Embedding(vocab_size, dim, sparse=True)
    self.linear = nn.Linear(dim, vocab_size)

  # B: batch size, W: window size, d_w: word embedding size, V: vocab size
  def forward(self, x):  # x: (B, 2W)
    embeddings = self.embedding(x)  # (B, 2W, d_w)
    embeddings = torch.sum(embeddings, dim=1)  # (B, d_w)
    output = self.linear(embeddings)  # (B, V)
    return output
  • CBOW 모델을 선언한다. 그리고 nn.Module의 속성을 사용하기 위해 super() 구문을 선언한다.

    • 나는 몰랐었는데, 새로 알게되어 공유한다. 다음의 두 경우는 똑같은 의미를 지니며 전자는 2.x 파이썬 버전에서 사용하던 문법이고 후자는 3.x 파이썬 버전에서 사용하던 문법이다. 호환성을 위해 2.x 문법도 인정해주는 것이라고 한다.

    • super(CBOW, self).__init__(**kwargs)

    • super().__init__(**kwargs)

  • nn.Embedding(num_embeddings, embedding_dim, sparse)

    • num_embeddings : dictionary의 사이즈를 의미한다. vocab에 등록된 총 단어 수

    • embedding_dim : 임베딩 벡터의 차원

    • sparse : 만약 True라면 0이 있는 행렬을 모두 제거하고 0이 아닌 값을 가지고 있는 행렬만 가지고 있게 된다. 실제로도 0인 부분을 업데이트 할 필요가 없으므로 주로 True로 설정한다. False인 경우에는 0을 포함한 행렬로 가지게된다.

      • 희소 행렬이란 행렬의 값이 대부분 0인 경우를 가리킨다.

  • forward

    • 각각의 중심단어에 대한 주변단어가 (1, 2W) 의 모양으로 존재한다. 윈도우 사이즈 W만큼 양쪽으로 가지고 있으므로 2W 이다.

    • 이후 데이터로더에서 배치만큼 나오므로 (B, 2W)의 모양으로 존재한다. 이해를 쉽게 하기 위해 배치 사이즈는 고려하지 않고 설명한다.

    • embedding : 각각의 주변단어가 임베딩 벡터꼴로 바뀌면서 (2W, d_w)의 형태로 바뀐다. 이 과정이 INPUT에서 HIDDEN으로 가는 과정이다.

    • torch.sum : embeddings의 원소를 모두 더하는데 이 때의 축이 dim=1 이다. 이때 잘 생각해보면 실제로 embeddings의 차원은 (B, 2W, d_w) 이다. 따라서 2W를 기준으로 다 더하라는 뜻. 결국 아래와 같이 된다. (물론 임베딩 벡터는 한 리스트에 한 개의 1값만 있을 수 있다)

    • 이후, Linear를 거쳐 Hidden에서 Output으로 네트워크가 구성된다.

class SkipGram(nn.Module):
  def __init__(self, vocab_size, dim):
    super(SkipGram, self).__init__()
    self.embedding = nn.Embedding(vocab_size, dim, sparse=True)
    self.linear = nn.Linear(dim, vocab_size)

  # B: batch size, W: window size, d_w: word embedding size, V: vocab size
  def forward(self, x): # x: (B)
    embeddings = self.embedding(x)  # (B, d_w)
    output = self.linear(embeddings)  # (B, V)
    return output
  • CBOW의 설명과 동일하다. 다만 중심단어에서 주변단어로의 방향이기 때문에 CBOW와 같은 torch.sum 은 없다.

cbow = CBOW(vocab_size=len(w2i), dim=256)
skipgram = SkipGram(vocab_size=len(w2i), dim=256)
  • 인스턴스를 생성한다. vocab size는 만들어둔 vocab의 길이이며 임베딩 벡터의 차원은 256으로 정했다.

batch_size=4
learning_rate = 5e-4
num_epochs = 5
device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')

cbow_loader = DataLoader(cbow_set, batch_size=batch_size)
skipgram_loader = DataLoader(skipgram_set, batch_size=batch_size)
  • 하이퍼 파라미터를 세팅하고 데이터 로더 객체를 생성한다.

cbow.train()
cbow = cbow.to(device)
optim = torch.optim.SGD(cbow.parameters(), lr=learning_rate)
loss_function = nn.CrossEntropyLoss()

for e in range(1, num_epochs+1):
  print("#" * 50)
  print(f"Epoch: {e}")
  for batch in tqdm(cbow_loader):
    x, y = batch
    x, y = x.to(device), y.to(device) # (B, W), (B)
    output = cbow(x)  # (B, V)
 
    optim.zero_grad()
    loss = loss_function(output, y)
    loss.backward()
    optim.step()

    print(f"Train loss: {loss.item()}")

print("Finished.")
skipgram.train()
skipgram = skipgram.to(device)
optim = torch.optim.SGD(skipgram.parameters(), lr=learning_rate)
loss_function = nn.CrossEntropyLoss()

for e in range(1, num_epochs+1):
  print("#" * 50)
  print(f"Epoch: {e}")
  for batch in tqdm(skipgram_loader):
    x, y = batch
    x, y = x.to(device), y.to(device) # (B, W), (B)
    output = skipgram(x)  # (B, V)

    optim.zero_grad()
    loss = loss_function(output, y)
    loss.backward()
    optim.step()

    print(f"Train loss: {loss.item()}")

print("Finished.")
  • CBOW와 Skip Gram을 학습한다.

테스트

for word in test_words:
  input_id = torch.LongTensor([w2i[word]]).to(device)
  emb = cbow.embedding(input_id)

  print(f"Word: {word}")
  print(emb.squeeze(0))
Word: 음식
tensor([-0.2270,  0.6425, -1.8639,  2.0074,  1.0602,  0.2280, -1.8116,  0.8747,
         1.0573, -0.5521,  0.9199, -0.6088,  0.1038, -1.3149, -0.7779, -0.1262,
        -0.2844, -0.8965,  1.0262, -1.0729,  0.9233, -0.3065, -0.4321,  0.6775,
        -0.6982, -0.7818, -1.2061, -0.5342,  0.1569, -0.3796, -0.2084,  0.2658,
         0.0634, -0.8452,  0.2186,  1.7930, -0.9043, -1.4288,  0.1343, -0.1323,
        -0.0827,  2.4206,  1.2419,  0.2111,  0.8264,  1.0360,  0.4485, -1.1690,
         1.3214,  0.3526,  0.1401, -0.7342,  0.4352, -0.2940,  0.7282,  1.1189,
         1.5770,  1.1922, -1.5995, -1.1725,  0.0383, -0.0336,  0.3315, -0.1366,
         0.1023, -1.1581, -0.4394,  2.3015, -1.1541, -1.9957,  0.6316,  1.1867,
         0.7656,  0.4047,  1.0780,  1.6586, -0.9893,  1.5444, -0.7219, -1.2783,
         1.1337, -1.0382, -2.2678,  0.4427,  0.5249, -0.3099,  1.0815,  0.3609,
        -0.7238, -0.2766,  1.9665, -0.2601, -1.0646, -1.6778, -1.4640, -0.0491,
         1.5413,  1.7797, -1.0758, -1.1144, -0.2286,  0.0801, -0.8882,  0.7936,
        -0.9803, -1.3373,  0.2506, -0.3887, -0.3695,  0.5422, -1.4766,  0.6960,
         1.0853,  1.6725, -0.1421,  0.2922, -0.4651, -0.7007,  0.4225, -0.4785,
         0.1668,  0.1869,  1.1163,  0.8795, -0.4107, -0.4094, -0.6185, -1.0438,
        -0.8743, -0.1753, -1.2997, -0.3147,  0.6688,  1.2124,  0.3117, -0.6259,
         0.3318, -0.3269,  0.3592,  0.8215,  0.3237, -0.5968,  0.0490, -0.6924,
         0.9793,  0.2978,  0.4555,  1.2372, -1.2608, -0.1196, -0.2401,  3.0968,
         0.3112,  1.4220, -0.4118,  0.6844,  2.5612, -0.1734,  0.2485,  0.3059,
        -0.6804, -1.7223, -1.0865,  0.4444, -1.1373, -2.0967, -0.0566, -0.2483,
         0.2775,  0.2334,  0.0348,  0.7561, -0.2670, -1.8109,  1.0936, -1.2397,
         1.5405,  1.0400,  1.6530,  1.3015,  0.7623, -0.5298,  0.7347,  0.7972,
        -0.9365, -0.2704,  0.1107, -0.5784,  0.3181, -0.4907, -1.3453,  1.7972,
         0.3025, -1.3448,  0.2850, -1.2486, -0.6892,  1.4329,  0.2077, -0.5875,
         0.2315, -0.0705,  0.5190,  0.9477,  0.4642,  1.1244,  1.6203, -0.6578,
         0.3903,  0.3586,  0.2026, -0.5119,  0.9532, -0.2923,  0.4737, -0.1401,
        -0.3620,  0.1771, -1.4056, -0.6920, -0.2073,  0.5479,  2.5050, -0.2251,
        -0.1477, -0.8426, -1.0569,  1.1574, -0.9174,  0.9778, -0.4568, -1.1284,
         0.3200, -0.6528,  0.8150, -0.8982, -1.2987, -0.8387, -0.1911, -0.1043,
        -1.1814,  1.5886,  0.0527,  0.5757, -0.3675,  0.0131, -0.6479,  1.2956,
        -0.7678, -0.0297,  0.0950,  1.3812, -2.3011,  1.2982, -0.7013,  0.9153],
       device='cuda:0', grad_fn=<SqueezeBackward1>)
Word: 맛
tensor([-1.0472,  0.0974, -0.8682, -1.1996,  0.2108,  0.1986,  0.0686, -0.0737,
        -1.3722,  0.5095, -0.4904, -0.2565, -0.5316, -0.4625,  0.9563, -0.2709,
         0.0844, -0.7378, -0.6514,  0.2506,  0.8878,  0.9990,  0.5927, -1.0165,
        -0.0710, -0.2383, -0.0516,  0.6606,  0.4938, -0.0647, -0.3309,  1.9562,
        -0.1541, -0.5692, -0.6306, -0.7136,  0.1331, -1.3353, -0.3859,  0.0787,
        -0.3642, -1.0149, -1.3493, -1.1551,  0.2465,  1.0351, -0.6229,  0.5483,
         0.7147,  2.0509, -0.3774, -0.1519, -0.8314, -1.5689, -0.2741, -0.6017,
         0.4783,  1.0759,  0.0212, -0.3583, -1.4920, -1.4609, -0.7354, -0.9537,
         0.6405, -1.2083, -0.1385, -0.1988,  0.5321,  0.1500,  0.3514,  0.5592,
        -0.0134, -1.6319, -1.9174,  0.7925,  0.7038, -1.9603, -0.3082, -1.0190,
        -0.9457, -0.6549, -2.3182,  0.9935, -0.9646, -0.2284, -1.5698,  1.6264,
         1.9855,  0.7877,  0.2498, -0.4655, -1.8867,  0.0782, -2.1022, -0.9665,
         0.1089,  1.0033, -0.7092, -0.6666,  0.0724, -0.1201,  1.0299,  1.1822,
        -0.5133, -0.0656,  1.6838,  0.5656, -1.6196, -1.3678, -1.9246, -0.2582,
        -1.2313, -0.5565, -1.4278, -1.6979,  0.2416, -0.2073, -0.9860,  0.4333,
        -0.7303, -1.8909, -0.4977, -0.3817, -0.1278, -1.2833, -0.1096,  0.5392,
         0.0872,  0.1551,  0.6277,  0.0262,  0.2213,  0.2394,  0.7892, -0.6257,
         0.2233, -0.4631, -1.1916, -0.4870, -0.8276,  0.8618, -1.3837,  0.8283,
        -1.8022,  0.1711, -1.1377,  0.1900,  1.1042, -0.0955, -0.8679, -0.2167,
         0.8381,  0.0165,  0.1233,  0.3171,  1.0855,  0.5779, -1.1795, -0.7091,
         1.5232,  0.3740,  0.4528,  1.2556,  0.1404,  0.2895,  0.1765, -0.9980,
         0.9362,  0.8846,  0.0243, -0.6571,  0.2311,  0.7219, -0.8362,  0.5606,
        -1.0710, -0.9607,  1.3773, -1.7947, -1.0652,  2.0063,  0.4961,  1.4650,
        -0.4460, -1.5360, -1.8555, -0.1169,  1.3857, -0.8016,  1.4880, -0.2983,
         0.3895, -0.1311,  0.3588,  0.1798, -0.5869,  0.0665,  0.1506,  0.8748,
         0.3882,  0.3710,  1.4076, -1.2311,  1.6294, -1.1121,  0.5917,  1.3581,
        -1.7429,  1.5666,  2.1108,  0.6751, -0.2842,  0.4098, -0.9852,  0.7692,
        -0.0995,  0.9608,  0.0525,  1.4533, -0.0367,  0.5078, -0.4264, -0.8814,
         1.5054,  0.7343,  0.8897,  0.6645, -0.6002, -1.6571,  1.3604, -1.2209,
         0.0410,  0.8878, -0.5121,  0.2544,  1.5048,  1.4095,  0.1942, -0.9450,
        -0.4449, -0.8279, -0.5602,  0.0912, -0.6250,  0.4974, -1.3092, -0.2586,
        -0.8915, -1.0231,  1.4395, -1.6217, -0.2441,  0.2061,  0.8649,  0.0635],
       device='cuda:0', grad_fn=<SqueezeBackward1>)
Word: 서비스
tensor([-0.0787, -1.1450, -0.7564, -2.4116, -0.0838, -0.7901,  1.7528, -0.7519,
        -1.1036,  1.2519,  1.9637,  0.4373, -1.6533, -0.5308,  0.4822,  0.7537,
         0.6932,  0.2260, -0.7731, -1.0282,  1.0214, -2.6137,  1.8673,  1.6458,
        -0.8776, -1.5505,  0.3739,  0.6621,  1.6348,  0.2010,  0.6087,  1.1409,
         0.2191, -0.2075,  0.6529, -1.1824,  0.4042, -0.4010, -1.3660,  0.1026,
         0.1692, -1.9065, -0.0668, -1.0470,  1.1856, -1.0677, -1.2867,  0.8814,
         1.6265,  0.2371, -0.3964,  0.1474,  1.0457,  0.4961, -0.2712,  0.3565,
        -0.9721,  0.2956,  0.1070, -0.3823,  1.4254, -1.3427,  0.5211,  1.2268,
         1.4122,  0.6131, -0.6106, -0.5293,  0.8121, -0.1082, -0.8560, -2.1471,
         0.9212,  0.3901,  0.0973, -0.7380, -0.1620, -0.3767,  0.3537,  0.6173,
        -0.4539,  1.0900, -1.4832,  0.4236,  1.9819,  1.0959,  0.6926, -0.5225,
         0.7654, -0.5251,  1.9103,  0.8923, -0.4214, -0.3404,  0.6262, -0.2463,
         0.6839,  0.8577,  0.1911,  1.1948, -1.4529, -1.4004, -1.5048,  0.5155,
         0.1009,  0.4357,  1.8159,  0.8558,  1.3723, -0.5532,  0.8792,  0.2843,
        -0.3140,  0.1011,  0.1138, -1.3283, -0.6232, -0.8752, -0.9700,  0.3494,
        -0.1527, -2.0688,  0.9206,  0.5911,  0.6741, -0.1734,  0.8589, -1.3126,
        -2.5574, -1.6329, -1.0171,  0.6409,  0.1215, -0.1397, -0.6283,  1.2531,
        -1.3375,  1.1464, -0.7267, -1.4634, -0.1921, -1.2586, -0.8526,  0.2546,
        -0.9211, -0.9668,  0.0244,  1.2189, -0.2882, -0.1984,  0.7868,  0.8291,
         0.9296,  1.1139,  2.2364,  0.3595, -1.1315, -0.1057,  1.3962,  1.3667,
        -0.2853, -0.4472, -0.1790, -0.8108, -0.0603, -0.4816, -0.3196,  0.1857,
         2.1967, -2.0003, -0.9249,  0.2484,  0.2902,  1.5240, -0.4971,  0.5436,
         0.8831, -2.4488, -0.6193, -0.0385,  0.5773,  0.6528,  1.0862, -0.8144,
        -1.8830, -0.4851, -0.6876, -0.5086,  0.5674, -0.6843, -0.7577, -0.1941,
        -0.3187, -1.3989, -0.2202,  0.4376, -1.4670, -2.1252, -0.9923, -1.2067,
        -0.0418,  0.7270, -1.7223,  0.3808,  0.0361,  0.2602,  1.1497,  0.1551,
        -0.4484, -0.3199,  0.5271,  1.1065,  0.8989,  0.5388,  2.8208,  1.5697,
        -1.5519, -0.5708,  1.2299, -0.1646,  0.0899,  1.3712,  0.7240, -0.6646,
         2.5825, -1.6390,  0.5282, -0.5932, -0.2457,  1.0970, -0.9602, -1.7764,
        -0.2803, -0.8997, -0.7232,  1.2771,  0.3938, -1.3284,  0.5469, -1.4847,
        -2.1790,  0.7954,  1.1309, -1.3554, -1.1986,  1.9054, -0.0211,  1.5022,
         0.3509,  0.4151,  1.0703,  0.3781, -0.3361, -0.8350,  0.8844, -0.8835],
       device='cuda:0', grad_fn=<SqueezeBackward1>)
Word: 위생
tensor([ 1.4635,  0.0472, -0.2920,  2.2415,  0.7345, -0.0266,  2.3534,  0.4149,
        -0.4027, -0.8227,  0.2078, -0.0614,  1.3888,  1.5814,  1.2698,  0.7780,
         1.0784, -0.5726,  0.3691, -0.3784,  1.7578,  0.6682,  1.1343,  1.1348,
        -0.7508, -0.6819, -0.2814,  1.6518,  0.9614,  0.2049,  1.6817,  1.1591,
        -1.4652,  1.7127,  1.3486,  0.8572, -0.0781,  2.1295,  1.8364, -1.1327,
        -1.5336, -0.2378, -0.4603,  0.4570, -1.5193, -0.8845,  0.2861, -0.2050,
        -1.1469,  0.7168,  1.6596,  0.5842,  1.0214, -0.4324, -0.8226, -1.6118,
        -0.7537,  1.8058,  0.2632, -0.5004, -0.5509, -0.9169, -0.4003,  0.5948,
         1.2915, -2.5165,  0.4802, -1.1447,  0.2535, -1.5039, -0.6419, -0.7702,
        -0.1178, -1.1140,  0.6543,  1.5395, -0.7481,  0.4947, -0.0842,  0.4924,
         1.6079,  1.2196,  0.1097,  1.0841, -1.3099, -1.0873, -1.6422,  1.0077,
        -0.5648, -0.1174, -0.5233, -0.3610,  0.6528, -1.6939,  1.3660,  0.9235,
        -0.9617,  0.1454,  0.0946,  1.3863,  0.5336,  0.7604, -1.1961,  0.9660,
        -1.4058, -1.7446,  0.4522, -1.2946, -1.0239,  1.0128, -0.5896, -0.0522,
        -1.2036,  0.2604, -0.9780,  0.8232,  1.8128, -0.4069,  0.7757, -0.3126,
        -1.3977,  0.8309, -1.4586,  2.5585, -0.1508,  0.5422,  1.2602,  2.3163,
         1.1662, -0.0363,  1.9558, -0.5463, -1.1020,  0.9395,  0.1261,  0.3191,
         1.0380,  0.9197, -2.3953, -0.0254,  0.7378,  1.4489,  1.0960,  1.0448,
        -0.3338,  0.8813,  0.3805,  1.4281, -0.6788,  0.6769,  0.2370, -0.0375,
         0.4547,  0.5599, -0.4717,  1.8094, -0.0208,  0.9473,  0.0324,  1.1140,
         0.3397, -0.9161, -0.4724,  0.7157, -0.2371, -0.1056,  0.4809, -1.7507,
        -0.0878, -1.1405,  0.2465,  0.6495, -0.6020, -0.5262, -0.0185,  0.0338,
        -1.3123, -1.0556,  2.6045, -1.6214, -0.4730, -0.1740,  0.1088, -0.3643,
         0.9992, -0.4736,  0.9494, -0.2128,  0.0932, -0.0634, -0.2361,  0.0767,
         0.2880,  0.7311,  1.0265, -0.7716,  0.8375, -1.0687, -0.5387, -0.4072,
         0.3035, -0.3796,  1.0750, -1.3545, -0.1934,  0.1279, -0.1400,  0.1826,
         0.7236,  0.0869, -2.5555,  1.4435,  1.2680, -1.1072,  0.2390, -0.6227,
        -0.6019,  0.2923, -0.3315, -0.9888,  0.0680, -0.6145,  1.4524, -0.6144,
         0.3811, -1.0752, -0.1378, -1.1781, -1.3308,  0.1508,  0.6268,  0.7951,
         0.0076, -0.1099, -1.7573, -0.9509,  0.7146,  1.0960, -0.1482, -0.3525,
         2.6867, -0.0765, -0.1901,  0.8058,  1.2801,  1.2046, -2.4593, -0.6246,
         1.6271, -0.1134, -0.0778, -0.0460, -0.2295, -0.1353,  1.7088,  2.2059],
       device='cuda:0', grad_fn=<SqueezeBackward1>)
Word: 가격
tensor([ 0.8071, -1.3313, -0.3625, -1.5106,  0.6003,  0.2000, -1.8236, -0.4199,
        -0.2933,  2.0132,  2.0983, -1.4669, -1.0168,  1.6880,  0.6606, -0.0546,
         1.1678,  0.6888, -0.7090,  0.8981, -1.1845, -0.4427,  1.1653, -0.7461,
         0.5615,  0.1164, -0.0094,  1.2941,  0.4426, -0.9336,  0.0255,  1.3787,
        -1.1152, -0.0392, -0.8120, -1.5373,  1.1159, -0.9863,  0.4286, -1.7930,
         0.4408, -0.4924,  0.0201, -1.2696,  0.3612,  0.5594,  1.2853,  0.1080,
        -0.8028,  0.1369, -0.3349,  0.9132, -1.1569,  0.6054,  1.1370, -0.9736,
         0.8449,  0.3383, -0.5378, -0.1810,  1.1856,  1.3018, -0.2890,  0.0199,
         0.3393, -0.8456, -0.2945, -1.6360,  1.2111, -1.6976, -0.3300,  0.5007,
         0.1409, -0.0166,  1.5618, -1.5591, -1.8889,  0.4192,  0.5738, -0.3916,
         0.5963, -0.2358,  0.1130, -0.4391, -1.1202,  1.0349, -0.3725,  1.4162,
        -1.1179, -0.2070,  0.5687,  0.9898,  0.5919, -0.7255, -0.3884, -1.4880,
         0.8851, -1.0258, -2.1898, -1.2771,  1.8130, -0.5130, -0.4104, -2.4358,
        -0.3272,  1.9936, -0.5004, -1.2736, -1.0434,  0.8440, -0.8625,  0.7908,
        -0.2088, -0.4829,  2.0142, -0.2008,  1.1509,  0.0038,  0.1059,  0.0373,
        -0.7908, -1.0695, -2.3989, -1.0499, -2.5601,  1.0675, -1.0300, -0.4214,
        -0.2884, -0.1907, -0.7646, -0.7620, -1.3060,  0.1957, -0.4128, -0.3409,
        -0.9520,  0.3340,  0.8779,  0.0572, -0.7208, -0.1062, -2.1561,  0.5127,
         0.1051,  0.7469,  0.0130, -0.0913, -0.8858,  2.8809, -0.1937,  0.2497,
        -0.1365,  1.3323,  0.6656,  0.3979,  0.8870,  0.9509,  0.8877,  1.6644,
        -0.0219,  0.1815,  0.8389,  0.7634,  0.3560, -0.2789,  0.7645,  0.3617,
        -0.4780,  0.1113,  0.0688,  1.1077, -1.0212, -1.0837, -0.1043,  0.0270,
         1.1620,  1.1493, -0.2896,  0.3576, -0.3277,  1.6700, -0.0992, -0.5844,
        -0.9020, -0.1732, -1.8760,  0.7676,  1.1106, -0.6111,  0.9906,  0.2238,
        -0.5576, -0.8923,  0.5638,  0.5147,  0.3834, -1.0289,  0.2768, -0.4992,
        -0.6802,  1.4443,  2.2874, -1.7116, -1.2088, -0.7768,  0.0621,  0.2715,
         1.8747, -0.6866, -0.1533,  0.4955,  2.2433, -0.9108, -0.1007, -0.4541,
         0.4291, -0.1669,  0.2096, -0.2491, -1.1363,  0.1701,  0.2329,  1.3489,
        -0.6611, -0.4267, -1.2714,  0.0092,  0.7429,  0.2841, -1.6789, -0.5273,
         0.3177, -0.5025, -1.1413, -0.2910,  0.2159, -0.5251, -0.0195, -1.2398,
         0.2083, -0.7559, -0.9942, -1.7577,  1.0292,  1.9885,  0.1919,  0.1370,
        -1.6130,  0.0330,  0.7853, -1.7331,  2.1818, -1.5653,  0.5580,  1.6604],
       device='cuda:0', grad_fn=<SqueezeBackward1>)
for word in test_words:
  input_id = torch.LongTensor([w2i[word]]).to(device)
  emb = skipgram.embedding(input_id)

  print(f"Word: {word}")
  print(max(emb.squeeze(0)))
Word: 음식
tensor(2.3452, device='cuda:0', grad_fn=<UnbindBackward>)
Word: 맛
tensor(2.4317, device='cuda:0', grad_fn=<UnbindBackward>)
Word: 서비스
tensor(3.0386, device='cuda:0', grad_fn=<UnbindBackward>)
Word: 위생
tensor(2.9022, device='cuda:0', grad_fn=<UnbindBackward>)
Word: 가격
tensor(2.2546, device='cuda:0', grad_fn=<UnbindBackward>)

Last updated

Was this helpful?