본문 바로가기

AI/PyTorch

PyTorch #원-핫 인코딩 #워드 임베딩

원-핫 인코딩

 

1. 원-핫 인코딩이란?

표현하고 싶은 단어의 인덱스에 1의 값, 나머지 인덱스에는 0을 부여하는 벡터 표현 방식이며, 이렇게 표현된 벡터를 원-핫 벡터라고 부름

 

2. 원-핫 인코딩 프로세스

3. 원-핫 인코딩의 한계

- 단어 개수가 늘어날 수록 벡터를 저장하기 위한 공간이 늘어남(저장 공간 및 연산 측면에서 비효율)

- 단어의 유사도를 표현할 수 없음

 

4. 한국어 문장을 원-핫 벡터로 생성하는 예시

 

문장: 오늘 아침 커피는 맛있습니다.

 

 

- 한국어 형태소 분석을 위한 konlpy 설치

 

# 형태소 분석을 위한 konlpy 설치
!pip install konlpy

 

 

- 형태소 분석기로 문장 토큰화

 

# 형태소 분석기로 문장을 형태소 단위로 나눔(문장 토큰화)
from konlpy.tag import Okt  
okt = Okt()  
token = okt.morphs("오늘 아침 커피는 맛있습니다")  
print(token)

 

 

- 각 토큰에 인덱스 부여

 

word2index = {}
for voca in token:
     if voca not in word2index.keys():
       word2index[voca] = len(word2index)
print(word2index)

 

 

- 원-핫 벡터를 생성하는 함수 정의

 

# 토큰을 입력하면 해당 토큰에 대한 원-핫 벡터를 생성하는 함수
def one_hot_encoding(word, word2index):
       one_hot_vector = [0]*(len(word2index))
       index = word2index[word]
       one_hot_vector[index] = 1
       return one_hot_vector

 

 

- 단어 입력 후 출력 확인

 

one_hot_encoding("커피",word2index)

 

 

- 전체 원-핫 벡터 확인

 

for i in word2index:
  token_vector = one_hot_encoding(i, word2index)
  print(i, token_vector)

 

 

 


워드 임베딩(Word Embedding)

 

- 워드 임베딩은 단어를 밀집 표현으로 변환해 벡터로 표현하는 기법

- 워드 임베딩 과정에서 나온 밀집 벡터를 임베딩 벡터(embedding vector)라고 함

- 워드 임베딩 방법론에는 LSA, Word2Vec, Fasttext, Glove 등이 있음

- 파이토치의 워드 임베딩 도구는 nn.embedding()이 있으며 단어를 랜덤한 값을 가지는 밀집 벡터로 변환한 뒤, 인공 신경망의 가중치를 학습하는 것과 비슷하게 단어 벡터를 학습하는 방법을 사용함

 

 희소 표현(Sparse Representation) 밀집 표현(Dense Representation)
- 원-핫 인코딩 처럼 벡터나 행렬의 값이 대부분 0으로 표현
- 단어의 개수가 늘어나면 벡터의 차원이 커지는 단점
- 단어간 유사도 표현 불가(자연어 처리에서 치명적) 
- 0과 1만 가진 값이 아니라 실수 값을 가짐
- 벡터의 차원을 단어 집합 크기로 상정하지 않음
- 사용자가 설정한 값으로 모든 단어의 벡터 표현을 맞춤
ex) 강아지 = [00001000...]  ex) 강아지 = [0.2, 1.8, 1.1, -2.1....]

 

  원-핫 벡터 임베딩 벡터
차원 고차원(단어 집합의 크기) 저차원
다른 표현 희소 벡터의 일종 밀집 벡터의 일종
표현 방법 수동 훈련 데이터로부터 학습
값의 타입 1과 0 실수

 

 


워드투벡터(Word2Vec)

 

단어간 유사도를 반영할 수 있도록 단어의 의미를 벡터화 하는 방법 중 하나

단어의 의미가 벡터화 되면 다음과 같은 연산 가능

ex) 전화기 + 컴퓨터 = 스마트폰

 

1. 희소 표현(Sparse Representation)

- 벡터나 행렬의 값이 대부분 0으로 표현되는 방법

 

2. 분산 표현(Distribued Representation)

- 단어간 유사성을 표현할 수 없는 희소 표현의 단점을 해결하기 위한 표현 방법

- '비슷한 위치에서 등장하는 단어들은 비슷한 의미를 가진다' 라는 분포 가설을 가정함

- 분산 표현으로 표현된 벡터들은 벡터의 차원이 단어 집합의 크기일 필요가 없어 벡터의 차원이 상대적으로 저차원으로 줄어듬

- 분산 표현의 대표적인 학습 방법이 워드투벡터 임

 

3. 워드투벡터의 방식

- CBOW(Continuous Bag of Words) : 주변에 있는 단어를 가지고 중간에 있는 단어를 예측

- Skip-Gram : 중간에 잇는 단어로 주변 단어를 예측

- 워드투벡터는 주로 Skip-Gram + 네거티브 샘플링을 사용

 

4. 네거티브 샘플링(Negative Sampling)

- Word2Vec 모델의 단점은 속도, 단어 집합의 크기가 수백만에 달하면 굉장히 무거워짐

- 이를 해결하기 위해 랜덤으로 주변 단어가 아닌 상관없는 단어들의 일부를 가져와 마지막 단계를 이진 분류 문제로 수행

- 연산량에 있어 매우 효율적

 

 


글로브(Global Vecors for Word Representation, GloVe)

 

- 카운트 기반과 예측 기반을 모두 사용

- 2014년 스탠포드 대학에서 개발

- 카운트 기반의 LSA(Latent Semantic Analysis)와 예측 기반의 Word2Vec의 단점을 보완하기 위한 목적으로 개발

- 현재까지 Word2Vec과 GloVe 중 어떤 것이 더 뛰어나다고 말하기 어려움

 

LSA Word2Vec
각 간어의 빈도수를 카운트한 행렬이라는 전체적 통계정보를 입력으로 받아 차원을 축소하여 잠재된 의미를 예측 실제값과 예측값에 대한 오차를 손실함수를 통해 줄여나가며 학습하는 예측 기반 방법론
단점: 코퍼스의 전체적인 통계 정보를 고려하지만
왕:남자 = 여왕: ? 같은 단어 의미의 유추 작업에는 성능이 떨어짐 
단점: 유추 작업은 LSA보다 뒤어나지만, 임베딩 벡터가 윈도우 크기 내의 주변단어들만 고려하기 때문에 코퍼스의 전체적인 통계 정보를 반영하지 못함

 


파이토치의 nn.Embedding()

 

파이토치에서 임베딩 벡터를 사용하는 방법은 크게 두 가지로 구성

방법 1. 임베딩 층을 만들어 훈련 데이터로부터 처음부터 임베딩 벡터를 학습

방법 2. 미리 사전에 훈련된 임베딩 벡터를 가져와 사용

 

 

방법 1. 처음부터 임베딩 벡터를 학습하는 방법

 

# 임의의 문장으로 단어 집합 생성 및 단어에 정수 부여

train_data = 'you need to know how to code'

# 중복을 제거한 단어들의 집합인 단어 집합 생성.
word_set = set(train_data.split())

# 단어 집합의 각 단어에 고유한 정수 맵핑.
vocab = {tkn: i+2 for i, tkn in enumerate(word_set)}
vocab['<unk>'] = 0
vocab['<pad>'] = 1
# nn.Embedding()을 사용해 학습 가능한 임베딩 테이블 생성
# nn.Embedding의 인자
# num_embeddings : 임베딩을 할 단어들의 개수. 다시 말해 단어 집합의 크기
# embedding_dim : 임베딩 할 벡터의 차원입니다. 사용자가 정해주는 하이퍼파라미터
# padding_idx : 선택적으로 사용하는 인자입니다. 패딩을 위한 토큰의 인덱스


import torch.nn as nn
embedding_layer = nn.Embedding(num_embeddings=len(vocab), 
                               embedding_dim=3,
                               padding_idx=1)
print(embedding_layer.weight)

 

 

 

방법 2. 토치텍스트를 사용한 사전 훈련된 워드 임베딩(Pretrained Word Embedding)

 

토치 텍스트는 아래와 같은 영어 단어들의 사전 훈련된 임베딩 벡터를 제공

  • fasttext.en.300d
  • fasttext.simple.300d
  • glove.42B.300d
  • glove.840B.300d
  • glove.twitter.27B.25d <== 이넘을 사용
  • glove.twitter.27B.50d
  • glove.twitter.27B.100d
  • glove.twitter.27B.200d
  • glove.6B.50d
  • glove.6B.100d
  • glove.6B.200d
  • glove.6B.300d

 

IMDB 리뷰 데이터에 존재하는 단어들을 토치텍스트가 제공하는 사전 훈련된 임베딩 벡터의 값으로 초기화 예

 

- IMDB 데이터 준비

 

# 도구 임포트
from torchtext.legacy import data, datasets

# Text와 Label 객체 정의
TEXT = data.Field(sequential=True, batch_first=True, lower=True)
LABEL = data.Field(sequential=False, batch_first=True)

# 데이터 준비
# torch.legacy.datasets를 사용해 IMDB 데이터셋 다운로드 및 학습 데이터셋과 테스트 데이터셋으로 나눔
trainset, testset = datasets.IMDB.splits(TEXT, LABEL)

# 훈련 데이터의 샘플 갯수 확인
print('훈련 데이터의 크기 : {}' .format(len(trainset)))
# >>> 훈련 데이터의 크기 : 25000

# 훈련 데이터의 첫번째 샘플 출력
print(vars(trainset[0]))
# >>> {'text': ['paul', 'hennessy', 'and', 'his', 'wife,', 'cate', 'must', 'deal', 'with', 'their', 'two', 'teenage', 'daughters', 'and', 'weird', 'son...but', 'after', 'the', 'untimely', 'passing', 'of', 'john', 'ritter,', 'the', 'show', 'became', 'more', 'about', 'coping', 'with', 'the', 'loss', 'of', 'a', 'loved', 'one...<br', '/><br', '/>i', 'found', 'this', 'show,', 'passing', 'through', 'the', 'channels', 'one', 'afternoon', 'and', 'i', 'have', 'to', 'say', 'i', 'was', 'laughing', 'myself', 'till', 'my', 'ribs', 'ached,', 'simply', 'at', 'the', 'range', 'of', 'characters;', 'the', 'witty', 'lines', 'and', 'the', 'situation', 'paul', 'would', 'find', 'himself', 'dealing', 'mostly', 'with', 'his', 'daughters...from', 'then', 'on,', 'i', 'caught', 'the', 'rest', 'of', 'the', 'show', 'when', 'i', 'was', 'free', 'and', 'i', 'have', 'to', 'say', 'the', 'writing', 'was', 'very', 'good..but', 'then', 'i', 'read', 'about', 'john', "ritter's", 'death...shortly', 'afterwards', 'i', 'watched', "'goodbye'", 'part', '2', 'and', 'i', 'have', 'to', 'say', 'i', 'was', 'nearly', 'in', 'tears,', 'watching', 'the', 'emotions', 'of', 'the', 'characters,', 'losing', 'a', 'loved', 'one...how', 'rory', 'punches', 'a', 'wall', 'in', 'anger', 'and', 'frustration...how', 'cate', 'deals', 'with', 'having', 'to', 'sleep', 'in', 'her', 'bed', 'all', 'alone....briget', 'and', 'kerry', 'talking', 'about', 'what', 'they', 'should', 'have', 'done.<br', '/><br', '/>but', 'the', 'show', 'does', 'move', 'on,', 'bringing', 'with', 'it', 'jim', 'egan', 'and', 'cj', 'barnes', 'who', 'provide', 'great', 'laughs,', 'as', "cate's", 'father', 'tries', 'to', 'protect', 'his', 'family', 'and', 'give', "'man", 'issue', "talks'", 'to', 'rory...but', 'the', 'true', 'gem', 'is', 'cj...who', 'is', 'absolutely', 'hilarious', 'as', 'the', 'wild', 'cousin.<br', '/><br', '/>it', 'will', 'always', 'be', 'john', "ritter's", 'masterpiece.'], 'label': 'pos'}

 

 

- 토치텍스트의 glove.twitter.27B.25d(사전 훈련된 임베딩 벡터)를 불러와 IMDB 리뷰 데이터의 단어를 초기화

 

# 도구 임포트
import torch
import torch.nn as nn
from torchtext.vocab import GloVe

# Field 객체의 build_vocab을 통해 사전 훈련된 임베딩 벡터 다운 가능
# max_size : 단어 집합의 크기 제한, min_freq : 등장 빈도수가 10번 이상인 단어만 허용
TEXT.build_vocab(trainset, vectors=GloVe(name='twitter.27B', dim=25), max_size=10000, min_freq=10)
LABEL.build_vocab(trainset)

# 현재 집합의 단어와 맵핑된 고유한 정수 출력
print(TEXT.vocab.stoi)
# >>>defaultdict(<bound method Vocab._default_unk_index of <torchtext.legacy.vocab.Vocab object at 0x7f119a15e3d0>>, {'<unk>': 0, '<pad>': 1, 'the': 2, 'a': 3, 'and': 4, 'of': 5, 'to': 6, 'is': 7, 'in': 8, 'i': 9, 'this': 10, 'that': 11, 'it': 12, '/><br': 13, 'was': 14, 'as': 15, 'for': 16, 'with': 17, 'but': 18, 'on': 19, 'movie': 20, 'his': 21, 'are': 22, 'not': 23, 'film': 24, 'you': 25,...<생략>

# 임베딩 벡터의 개수와 각 벡터의 차원 확인
print('임베딩 벡터의 개수와 차원 : {} '.format(TEXT.vocab.vectors.shape))
# >>> 임베딩 벡터의 개수와 차원 : torch.Size([10002, 25]) 

# <unk>의 임베딩 벡터값 확인
print(TEXT.vocab.vectors[0]) # <unk>의 임베딩 벡터값
# >>> tensor([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.])
        
# <pad>의 임베딩 벡터값 확인
print(TEXT.vocab.vectors[1]) # <pad>의 임베딩 벡터값
# >>>
# tensor([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.])

# 'this'의 임베딩 벡터값 확인
print(TEXT.vocab.vectors[10]) # this의 임베딩 벡터값
# >>>
# tensor([-0.1789,  0.3841,  0.0730, -0.3236, -0.0924, -0.4077,  2.1000, -0.1136,
#         -0.5878, -0.1703, -0.6433,  0.7239, -5.7839, -0.1041,  0.5215, -0.1131,
#          0.5955, -0.4759, -0.4551,  0.0844, -0.4582, -0.1673,  0.5459,  0.0355,
#         -0.1607])

# 'self-indulgent'의 임베딩 벡터값 확인
# 사전 훈련된 임베딩 벡터에는 해당 단어가 존재하지 않기 때문에 0으로 초기화
print(TEXT.vocab.vectors[9999]) # seeing의 임베딩 벡터값

# >>>
# tensor([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.])


# 이 임베딩 벡터들이 저장되어져 있는 TEXT.vocab.vectors를 nn.Embedding()의 초기화 입력으로 사용
embedding_layer = nn.Embedding.from_pretrained(TEXT.vocab.vectors, freeze=False)

embedding_layer(torch.LongTensor([10])) # 단어 this의 임베딩 벡터값

 

 


네이버 영화 리뷰 Word2Vec 생성 후 테스트

 

- 데이터 로드 후 전처리(결측값 제거 및 한글, 띄어쓰기 외 문자 제거)

!pip install konlpy # 형태소 분석기 설치
# 도구 임포트
import pandas as pd
import matplotlib.pyplot as plt
import urllib.request
from gensim.models.word2vec import Word2Vec
from konlpy.tag import Okt
from tqdm import tqdm # 현재 for문이 얼마나 실행되었는지를 알려주는 라이브러리
# 네이버 영화 리뷰 데이터 다운로드
urllib.request.urlretrieve("https://raw.githubusercontent.com/e9t/nsmc/master/ratings.txt", filename="ratings.txt")
# 리뷰 데이터를 데이터프레임으로 로드
train_data = pd.read_table('ratings.txt')
# 결측값 유무 확인
# NULL 값 존재 유무
print(train_data.isnull().values.any())
# 결측값이 존재하는 행 제거
train_data = train_data.dropna(how = 'any') # Null 값이 존재하는 행 제거
# 정규 표현식을 통한 한글 외 문자 제거
train_data['document'] = train_data['document'].str.replace("[^ㄱ-ㅎㅏ-ㅣ가-힣 ]","")
# 학습 시에 사용하지 않는 불용어 제거, 형태소 분석기를 사용해 각 문장에 대해 토큰화 수행
# 불용어 정의
stopwords = ['의','가','이','은','들','는','좀','잘','걍','과','도','를','으로','자','에','와','한','하다']

# 형태소 분석기 OKT를 사용한 토큰화 작업 (다소 시간 소요)
okt = Okt()

tokenized_data = []
for sentence in tqdm(train_data['document'][:5000]):	# 5000개만 사용
    tokenized_sentence = okt.morphs(sentence, stem=True) # 토큰화
    stopwords_removed_sentence = [word for word in tokenized_sentence if not word in stopwords] # 불용어 제거
    tokenized_data.append(stopwords_removed_sentence)
# 각리뷰의 길이 분포 확인
# 리뷰 길이 분포 확인
print('리뷰의 최대 길이 :',max(len(review) for review in tokenized_data))
print('리뷰의 평균 길이 :',sum(map(len, tokenized_data))/len(tokenized_data))
plt.hist([len(review) for review in tokenized_data], bins=50)
plt.xlabel('length of samples')
plt.ylabel('number of samples')
plt.show()

 

 

- Word2Vec 으로 토큰화 된 네이버 영화 리뷰 데이터 벡터화(학습)

 

# 모델 정의
from gensim.models import Word2Vec

model = Word2Vec(sentences = tokenized_data, size = 100, window = 5, min_count = 5, workers = 4, sg = 0)

Word2Vec 의 하이퍼파라미터 값

  • size : 워드 벡터의 특징 값, 임베딩 된 벡터의 차원
  • window : 컨텍스트 윈도우 크기
  • min_count : 단어 최소 빈도 수 제한(빈도가 적은 타이틀은 학습하지 않음)
  • workers = 학습을 위한 프로세스 수
sg : 0 은 CBOW, 1은 Skip-gram

 

# Word2Vec 임베딩 행렬 크기 확인
model.wv.vectors.shape # 총 1592개의 단어 존재 및 각 단어는 100차원으로 구성

100차원을 가진 1592개 단어

 

# '최민식'과 유사한 단어 추출
print(model.wv.most_similar("최민식"))

학습량이 적어서 그런가..

 


AI-HUB 일반 상식 데이터 Word2Vec 생성 후 테스트

데이터: ko_wiki_v1_squad.json

 

 

1. 형태소 분석기 설치

 

!pip install konlpy

 

 

2. 도구 임포트

 

# 도구 임포트
import pandas as pd
import matplotlib.pyplot as plt
import urllib.request
from gensim.models.word2vec import Word2Vec
from konlpy.tag import Okt
from tqdm import tqdm # 현재 for문이 얼마나 실행되었는지를 알려주는 라이브러리
import json

 

 

3. json 내용 확인

 

# 코렙에 구글 드라이브 권한 부여 #01
from google.colab import drive
drive.mount('/content/gdrive')

# pprint를 통한 데이터 확인
from pprint import pprint 
with open('/content/gdrive/My Drive/Colab Notebooks/ko_wiki_v1_squad.json') as data_file:
  local = json.load(data_file)

pprint(local) # 출력 생략

 

 

4. json 데이터의 첫번째 문장 확인

 

# 불러온 json 데이터의 첫번째 문장 확인
local['data'][0]['paragraphs'][0]['context']

 

 

5. json 데이터의 context를 train_data에 담음

 

train_data = []
for i in tqdm(local['data']):
  context = i['paragraphs'][0]['context']
  train_data.append(context)

 

 

6. train_data 1000개를 데이터프레임에 담음

 

pd_data = pd.DataFrame()
pd_data['context'] = train_data[:1000]

 

 

7. 전처리 수행

 

# 정규 표현식을 통해 한글이 아닌경우 제거하는 전처리 수행
# 정규 표현식을 통한 한글 외 문자 제거
pd_data['context'] = pd_data['context'].str.replace("[^ㄱ-ㅎㅏ-ㅣ가-힣 ]","")

# 학습 시에 사용하지 않는 불용어 제거, 형태소 분석기를 사용해 각 문장에 대해 토큰화 수행
# 불용어 정의
stopwords = ['의','가','이','은','들','는','좀','잘','걍','과','도','를','으로','자','에','와','한','하다']

# 형태소 분석기 OKT를 사용한 토큰화 작업 (다소 시간 소요)
okt = Okt()

tokenized_data = []
for sentence in tqdm(pd_data['context']):
    tokenized_sentence = okt.morphs(sentence, stem=True) # 토큰화
    stopwords_removed_sentence = [word for word in tokenized_sentence if not word in stopwords] # 불용어 제거
    tokenized_data.append(stopwords_removed_sentence)

 

 

8. 모델 정의 및 저장

 

# 모델 정의
from gensim.models import Word2Vec
model = Word2Vec(sentences = tokenized_data, size = 100, window = 5, min_count = 5, workers = 4, sg = 0)

# 모델 저장
from gensim.models import KeyedVectors
model.wv.save_word2vec_format('aihub_wiki_w2v') 

# 코렙에 모델 저장
# torch.savq 메소스 사용
import torch
model_save_name = 'aihub_wiki_w2v'
path = F"/content/gdrive/My Drive/Colab Notebooks/{model_save_name}" 
torch.save(model, path)

 

 

9. Word2Vec 임베딩 행렬 크기 확인

 

# Word2Vec 임베딩 행렬 크기 확인
model.wv.vectors.shape # 총 3940개의 단어 존재 및 각 단어는 100차원으로 구성

 

 

10. 테스트

 

# '우승'과 유사한 단어 추출
print(model.wv.most_similar("우승"))

 

 

11. 결과 파악

- 데이터셋의 문장의 도메인이 일관성이 없음. 크게 유의미한 결과를 얻기 힘듬

- 데이터가 중요

 

 

 


참고:

https://wikidocs.net/book/2788

 

PyTorch로 시작하는 딥 러닝 입문

이 책은 딥 러닝 프레임워크 PyTorch를 사용하여 딥 러닝에 입문하는 것을 목표로 합니다. 이 책은 2019년에 작성된 책으로 비영리적 목적으로 작성되어 출판 ...

wikidocs.net

 

반응형

'AI > PyTorch' 카테고리의 다른 글

PyTorch #다대다 RNN  (0) 2022.04.06
PyTorch #순환 신경망(RNN)  (0) 2022.04.06
PyTorch #자연어 데이터 전처리  (0) 2022.04.04
PyTorch #합성곱 신경망(CNN)  (0) 2022.04.01
PyTorch #인공 신경망(ANN)  (0) 2022.03.30