반응형

최신모델 State of the art Models

Transformers

  • BERT랑 GPT의 기반이 되는 네트워크
  • Seq2seq 모델을 약간 보정하기 위해 나온 것
  • Seq2seq model에서는 전체 Context 가 한번에 넘겨가지는 못하고, Attention이 있다고 하더라도 굳이 하나씩 보지말고 전체를 한번에 볼 수 있을까? 해서 만든 것이 Transformers Network

 

Seq2seq model

  • seq2seq 모델은 인코더-디코더 구조로 되어있음
  • 인코더는 입력 시퀀스를 하나의 벡터 표현으로 압축하고, 디코더는 이 벡터 표현을 통해서 출력 시퀀스를 생성

 

Seq2seq model

 

그렇다면 Attention으로 RNN을 보정하지 않고, 인코더 디코더안에 Attention을 넣어버린다면??
→ 트랜스포머 네트워크

 

 

Transformers Network

  • 트랜스포머 하나에 인코더와 디코더가 다 들어가있음 → 전체 입력이 트랜스포머 하나에 다 들어감

 

 

→ lstm이 느려서 그냥 인코더, 디코더 안에 Attention을 넣어버림

 

 

Transformers Network

  • 논문 (Attention is all you need. Vaswani et al. 2017) → 꼭 읽어봐야함
  • 구글 연구 팀이 공개한 딥러닝 아키텍쳐로 뛰어난 성능으로 주목 받았음
  • GPT, BERT 등의 모델의 기본 모델로 활용되고 있음

→ 인코더에도 디코더에도 임베딩을 전체를 넣어버리고, 인코더에서 애초에 Attention을 활용해서 인코더 전체에 대한 Self-Attention을 보고, 디코더에서도 디코더 전체에 대한 Self-Attention을 알아서 한번 본 다음에 인코더 디코더를 합친 Attention을 가지고 뭔가 하나를 분류하는 거

→ 인코더 디코더를 Layer를 여러개를 쌓을 때 층을 위에 계속 올린다

 

인코더 : 셀프 어텐션을 거치고 FeedForward 신경망(DNN)을 거쳐서 디코더로 보내줌

ADD : 원래값과 Attention을 거친 값을 넣어서 합한다음 Norm을 해준다 (Overfitting이 줄어듬)

FFNN (Feed Forward Network) : DNN - 매 token마다 아웃풋이 나옴

residual connection : FFNN을 안거치고 바로 ADD로 이동하는 부분

 

Transformers Network의 구조를 숙지해야 자연어처리를 할 수 있음

 

Positional Encoding

  • Transformer는 한번에 입력하다보니 lstm처럼 순서를 알수가 없음
    → lstm같은 경우에는 순서까지 고려되서 모델 생성이 되는데 트랜스포머 네트워크는 순서를 알수 없어서 순서를 알려주려고 하는 부분이 포지셔널 인코딩
  • 트랜스포머는 단어 입력을 순차적으로 받는 방식이 아니므로, 단어의 위치 정보를 다른 방식으로 알려줄 필요가 있음
  • 각 단어의 임베딩 벡터에 위치 정보들을 더하여 모델 입력으로 사용하며, 이를 포지셔널 인코딩이라 함

→ 위치가 2의 배수일 때는 sin 함수, 위치가 홀수일 때는 cos 함수를 해서 값을 더해줌

 

→ 포지셔널 인코딩에는 이와 같은 함수를 활용하여 위치 정보를 생성

 

 

셀프 어텐션 (Self Attention)

  • Key, Value, Query 값이 모두 같은 경우

멀티헤드 어텐션 (Multi-head Attention)

  • multi-head는 어텐션을 head 개수만큼 병렬로 수행하는 방법

트렌스포머의 어텐션 3가지

  • 3가지의 어텐션이 활용됨
  • Encoder Self-Attention : Query = Key = Value

 

각 attention은 multi-head attention이 적용이 되어 있음 !! 실제 transfomer의 attention은 dot product attention이 적용되어 있어서 그냥 matrix 곱셈 연산 정도만 수행한다고 생각하쟈

 

 

https://github.com/jadore801120/attention-is-all-you-need-pytorch/blob/master/transformer/Models.py

→ 트랜스포머 모델 구현하는 소스코드

 

 

 

GPT

  • (Improving language understanding by generative pre-training. Radford, Alec, et al. 2018)
  • Transformer의 디코더를 적층한 모델
  • 디코더를 활용하여, 생성 모델에 적합
  • Transformer를 활용한 pre-trained model의 시초

 

GPT 학습 방식

  • 일반적인 Language Model(언어 모델) 학습 방식을 활용하여, 대용량 코퍼스를 활용한 비지도 학습
GPT는 꽤 괜찮은 성능을 보여줬지만 전반적으로 성능이 엄청 좋지는 않음,, 기존의 모델들 보다는 우수한 성능을 내는것도 있고, 모자란 것도 있고, 비슷한 것도 있음

 

 

 

BERT

  • (BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding. Devlin et al. 2018)
  • Transformer의 인코더를 적층해서 맨 뒤에 Layer 하나만 붙임 (Fine-Tuning)
  • 잘 만들어진 BERT 언어모델 위에 1개의 classification layer만 부착하여 다양한 NLP task를 수행
  • 영어권에서 11개의 NLP task에 대해 state-of-the-art (SOTA) - 전부 최신성능 달성 !!

 

 

→ 인코더를 쌓고나서 원래 BERT는 Mask LM이랑 NSP 두가지를 학습을 했었는데 BERT Fine-Tuning은 여기에다가 Layer를 하나만 추가를 함,,

→ 원래 트랜스포머 인코더가 12개가 안에 적층이 쌓여있음! 근데 여기에다가 분류를 할때는 Classification Layer를 하나 추가해주고, 기계독해를 할때는 정답의 위치를 출력하는 Layer 추가해주고, 개체명인식은 각 토큰마다 벡터가 나오기 때문에 각 토큰마다 tagging 을 하도록 Layer를 추가함

 

BERT 학습 방식

BERT 이전의 GPT는 기존의 Language Model(언어 모델)을 그대로 학습을 함 → 각 토큰 순서대로 예측을 함

 

반응형

 

MLM (Masked Language Model)

  • 무슨 토큰이 나와야 할지 분류 !
  • 입력 문장에서 임의로 토큰을 masking 한 후에, 해당 토큰을 맞추는 학습

→ 토큰 하나하나 학습을 안하니까 학습 속도도 빠르고, 인코더를 사용하니까 lstm처럼 이전 토큰에 대한 연산을 기다리지 않아도 된다. lstm 층을 많이 쌓으면 엄청 느린데 BERT는 훨씬 빠르다.

→ 층을 아무리 쌓아도 lstm만큼 느려지지 않음

 

BERT에서는 Transformer Incoder를 기본적으로 12개 층을 쌓아서 만들었음

 

 

 

NSP (Next Sentence Prediction)

  • 두 문장이 주어졌을 때, 두 문장의 순서를 예측하는 방식
  • 순서가 맞는지 틀렸는지를 학습함
  • 학습데이터를 문서단위로 입력할 때 문서에서 다음 문장을 가지고 왔을 때는 1, 문장의 순서를 랜덤으로 바꾸고 가지고 올 때는 0으로 학습

→ Bi-classification만 학습을 하기 때문에 학습속도가 빠름

 

 

 

BERT vs GPT vs ELMo

  • BERT - 구글에서 만듬
    • MLM학습으로 인하여, 동시에 연산이 가능 → 속도가 빨라짐
  • GPT - OpenAI에서 만듬
    • Transformer의 디코더만 사용 - 언어모델 사용
    • LM(언어모델) 학습으로 인하여 결국 lstm처럼 앞의 토큰 예측이 끝나야 다음 토큰 예측 가능
      → BERT보다 속도는 훨씬 느림

 

BERT 이후의 모델들

  • BERT랑 RoBERT랑 차이가 별로 없음, 학습 파라미터 갯수(Size)가 똑같음
    → BERT는 GPU를 기준으로 V100을 8대를 12일동안 학습함, RoBERT는 1024개의 GPU를 붙여서 하루만에 학습함
    → 학습을 하면서 데이터를 정말 많이 늘렸고, NSP 을 빼고 학습을 함
  • DistilBERT : BERT에서 뭔가를 없앤것
    → BERT가 네트워크 Layer수를 많이 쌓으면서 많이 느린 문제 때문에 Layer를 12개를 사용하는데 Layer를 줄여서 속도를 빠르게 만듬
    → 다른거는 똑같음 (모델 사이즈를 줄임 110 → 66)
    → 성능이 조금 떨어졌지만 속도가 빨라짐
  • XLNet (permutation??순열??) 학습방법을 바꿈
    • BERT의 MLM 학습에서는 masking된 토큰이 여러개일 때, 하나의 토큰을 예측할 때 다른 토큰도 masking 되어있다.
    • XLNet 학습방법 - 처음 앞에 있는 토큰을 예측할 때는 두개의 토큰이 다 masking 되어 있지만 , 뒤의 토큰을 예측할 때는 앞에서 예측한 결과를 받아와서 활용함
    • LSTM이나 GPT 처럼 순서가 생기는 문제때문에 학습 속도가 느려짐

 

최신 모델들

 

 

최신모델 실습

https://github.com/huggingface/transformers

→ transformer기반의 최신 모델들의 쉽게 사용할 수 있도록 제공하는 라이브러리

→ transformers 라이브러리 쓸 때는 pytorch 사용하는 것을 권장

 

→ transformer기반의 모델이 엄청 많음

→ GPT가 시초임

 

 

→ GPT2 생성모델 "A long time ago"를 넣었을때 뒤에 말이되는 문장을 넣어줌

 

 

!pip install transformers

from transformers import GPT2Tokenizer, GPT2Model

tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
model = GPT2Model.from_pretrained('gpt2')
text = "Replace me by any text you'd like."
encoded_input = tokenizer(text, return_tensors='pt')
output = model(**encoded_input)

print(output)

결과

 

 

→ output이 숫자로 나오는데 나중에 반대로 다시 token으로 바꿔주면 끝 !!

→ token으로 바꿔주는 예제는 찾아보면 있을듯,,,

https://github.com/huggingface/transformers/tree/master/notebooks

→ colab으로 바로 실행할 수 있는 링크

 

https://github.com/huggingface/transformers/blob/master/examples/text-generation/run_generation.py

→ 서버가 셋팅이 되면 이걸 실행해봐도 됨

 

https://www.tensorflow.org/official_models/fine_tuning_bert

→ tensorflow를 활용해서 어떻게 해야하는지에 대한 설명부터 가이드가 다 있음

→ tensorflow를 활용해서 BERT 모델을 다운로드 받아서 BERT 모델을 가지고 어떻게 학습을 하는지까지 다 나와있음

→ colab에서 실행을 누르면 코드 그대로 colab으로 넘어옴 (colab은 번역이 안됨)

반응형
반응형

BPE (Byte Pair Encoding)

  • BERT 기반 모델들도 다 BPE를 사용을 하기 때문에 알아둬야 함

  • Word embedding

    → 단어 수가 많고, Out-of Vocabulary 문제가 빈번하게 발생함 (한국어로 word2vec을 했을 때 엄청 단어가 많이 나옴)

  • 의미 있는 단위로 단어를 자르면 OOV를 피하고 단어 수를 줄일 수 있음

  • 의미 있는 단위 판단 기준

    • 의미 있는 패턴(subword)은 자주 등장할 것,
    • 많이 나오는 패턴은 독립적인 의미를 가지는 subword일 것

    → 결국 word를 서브 워드로 잘라준다.

 

 

과정

 

 

 

 

Tokenize

  • Vocab을 만들기 위한 문서의 전처리 과정
  • 기준이 되는 문서를 character 단위로 분리

Merge Best Pair

  • Character 단위로 분리했을 때, 문서에서 가장 많이 등장하는 bi-gram pair를 찾아서 merge
  • Iteration을 정해놓고 주어진 횟수만큼 반복해서 수행

Building Vocab

  • 정의된 iteration을 모두 수행 후 vocab을 생성

 

예제

참고 사이트 - https://wikidocs.net/22592

 

sentence : aaabdaaabac

→ vacab : (a, b, c, d) - 초기에는 character 단위로 잘라줌

→ tokenize를 단위로 잘라줌 : (a a a b d a a a b a c)

→ 2글자 단위로 pair를 만든다 : (aa, aa, ab, bd, da, aa, aa, ab, ba, ac)

→ 제일 많이 등장한 token : aa(4번)

→ Iteration이 한번 끝나면서 vocab에 aa가 추가됨 : (a, b, c, d, aa)

→ 추가된 vocab으로 다시 tokenizing : (aa a b d aa a b a c)

→ 2번의 Iteration을 돌면 : (aa ab d aa ab a c)

→ 3번의 Iteration을 돌면 : (aaab d aaab a c) → vocab은 (a, b, c, d, aa, ab, aaab)

 

 

BPE 실습

import _collections
import re

dictionary = {'l o w <end>':5, 'l o w e r <end>':2, 'n e w e s t <end>':6, 'w i d e s t <end>':3}

def get_pair(dictionary):
    pairs = _collections.defaultdict(int)
    for word, freq in dictionary.items():
        symbols = word.split()
        for i in range(len(symbols)-1):
            pairs[symbols[i], symbols[i+1]] += freq

    print('현재 pair:', dict(pairs))
    return pairs

def merge_dictionary(pair, v_in):
    v_out = {}
    bigram = re.escape(' '.join(pair)) # e,s ->'e s' 공백을 확실히 넣어줌
    # 앞뒤가 공백인 것을 찾는다 -> 공백제거하고 텍스트찾기
    p = re.compile(r'(?<!\S)' + bigram + r'(?!\S)')
    for word in v_in:
        w_out = p.sub(''.join(pair), word)
        v_out[w_out] = v_in[word]

    return v_out

epochs = 10
for i in range(epochs):
    pairs = get_pair(dictionary)
    best = max(pairs, key=pairs.get)
    print('best pair:', best[0] + best[1])
    new_dictionary = merge_dictionary(best, dictionary)
    print('new_dictionary:', new_dictionary)

 

 

 

BERT 모델의 vocab

  • [PAD] → tokenizing 할 때 모자라는 파?? 에 대해서 padding을 해주는데 padding에 대한 TOKEN
  • [unused0]
    • 안쓰는것, 나중에 BERT 모델 바꾸고 싶을 때 단어를 추가하고 싶을 때 이걸 바꿔서 단어를 넣으면 그 단어가 tokenizing이 됨
    • 처음에 BERT 모델 학습 할때는 unused는 사용을 안 하고 학습함
  • 캐릭터 단위의 문자가 존재함, 특수문자, 일본어도 있음
  • 어절 첫번째에 오는 것과 중간에 오는 것을 구별하기 위해 ## 을 이용함
    • ex) lovely → love, ly → love, ##ly로 표현함 (나중에 쉽게 갖다 붙일 수 있게 만듬)
  • 단어가 분리되지 않고 통째로 나오는 단어들은 사람들이 정말 많이 쓰는 것들임
Vocab을 직접 만드는데 얼마나 잘 만들었는가에 따라서도 BERT 성능이 조금씩 바뀜

 

반응형
반응형

StanfordNLP

  • StanfordNLP는 Python으로 패키징 된 자연어 처리 라이브러리
  • Pytorch를 활용하며, LSTM 기반의 sequential tagging 모델을 활용 → lstm 다대다 모델을 활용하면 토큰마다 결과가 나오게 할 수 있는데 거기에 태그(tagging)를 달아준다.
  • CoNLL 2018 shared task에서 높은 점수를 획득

https://stanfordnlp.github.io/stanza/

https://stanfordnlp.github.io/stanza/performance.html

 

 

 

  • 기본적인 전처리를 다 할 수 있는 기능을 가지고 있음
https://stanfordnlp.github.io/stanza/available_models.html
→ 제공되는 모델들의 리스트를 확인할 수 있음, 성능에 대한 부분도 나와있음

 

 

→ 제공되는 것들

 

다른 언어를 사용할 때 활용하는 것을 추천 → 성능이 좋음

 

 

StanfordNLP 실습 - StanfordNLP.ipynb

https://stanfordnlp.github.io/stanza/#getting-startedhttps://stanfordnlp.github.io/stanza/#getting-started

!pip install stanza

# 학습되있는 모델을 다운 받는 부분
import stanza
stanza.download('en')

# initialization (초기화) 원하는 언어에 대한 것만 넣으면 됨
nlp = stanza.Pipeline('en')

https://stanfordnlp.github.io/stanza/pipeline.html

→ Pipeline을 쓰면 tokenize, mwt 등 기본적인 전처리가 돼서 결과가 나온다.

 

rersult = nlp('Barack Obama was born in Hawaii')

 

반응형
print(result)

 

 

딥러닝을 활용한 모델이다보니 CPU만 활용을 하면 너무 느려서 GPU를 사용할 수 있을 때는 GPU를 사용하게 내부적으로 구현이 돼있음

 

 

https://stanfordnlp.github.io/stanza/performance.html

 

→ 해당 dataset에 대한 성능이 나와있음

→ token, sentence, word, UPOS → 각 정확도들

 

 

 

 

→ 개체명 인식 같은 경우 원래 dataset에 포함이 안 돼있었음 그래서 별도로 WikiNER에서 dataset을 가지고 본인들이 따로 학습을 해서 같이 공개를 함

 

 

Khaiii (Kakao Hangul Analyzer III)

  • 카카오에서 공개한 한국어 형태소 분석기

https://github.com/kakao/khaiii

 

윈도우에서는 잘 동작을 안 한다.

 

Khaiii 실습 - khaiii.ipynb

  • Khaiii는 python을 사용을 할 수 있게 했지만 내부는 C언어로 돌아감 - 속도 때문에
  • 설치 명령어 - 다운로드하는데 시간이 꽤 걸린다.
!git clone https://github.com/kakao/khaiii.git
!pip install cmake
!mkdir build
!cd build && cmake /content/khaiii
!cd /content/build/ && make all
!cd /content/build/ && make resource
!cd /content/build && make install
!cd /content/build && make package_python
!pip install /content/build/package_python

→ 속도 때문에 C언어로 코드를 짜서 복잡함, 대신 속도가 빠름

→ 설치가 오래 걸리지만 서버에서는 한 번만 설치하면 편하게 쓸 수 있음

 

from khaiii import KhaiiiApi

api = KhaiiiApi()

sentence = "안녕, 세상."
tagged = api.analyze(sentence)

for word in tagged:
  print(word)

결과

→ 형태소 분석 결과 : 어절 단위로 자르고 어절마다 형태소가 tagging이 됐는지 결과가 나옴

 

Colab에서는 잘 되지만 내 서버에서는 잘 안될 수도 있음 → 그러면 깃허브에 빌드 및 설치 문서 참고

 

반응형
반응형

어텐션 메커니즘 (Attention Mechanism)

  • seq2seq 모델의 문제점 : 전체 문장에 대해서 context를 단 한 번에 넘겨줌
  • 매 순간마다 데이터를 다 넣어주기 위해 사용

 

Seq2seq model

  • 전체 문장에 대한 정보를 한꺼번에 벡터 하나로 만들어서 넘겨주니까 token에서의 정보는 하나도 안 남아있고, 전체 문장에 대한 Context만 넘어감

 

Attention은 Seq2seq model을 보완하고자 만든 것

 

 

Attention

  • 쿼리에 대해서 모든 키와의 유사도를 각각 구하고, 유사도를 키와 맵핑되어 있는 각각의 value에 반영
  • Query : t 시점의 디코더 셀에서의 은닉 상태
  • Keys : 모든 시점의 인코더 셀의 은닉 상태
  • Values : 모든 시점의 인코더 셀의 은닉 상태

 

→ Attention Value는 Query랑 Key가 들어가서 Value가 곱해져서 다 합쳐진 거임 (먼 소리야ㅎㅎ,,)

 

 

Dot-product Attention

  • 매 순간마다 앞에 있던 것을 다 같이 보겠다는 것
  • 기본적으로 곱하기임

 

→ softmax Layer에서 더 연관이 있는 단어에 weight를 더 많이 줘서 weight들을 전부 더해서 다음에 나올 단어를 예측한다.

 

그 외의 Attention 종류들

 

 

 

Attention 실습

  • 전에 만들었던 glove_lstm_classification.ipynb 에 attention을 추가해서 만듬
    • 다른 거는 똑같고, model 쪽에 attention 추가
from collections import Counter
import urllib.request
import pandas as pd
import numpy as np
import tensorflow as tf

import nltk
from nltk.tokenize import sent_tokenize
from nltk import WordPunctTokenizer

nltk.download('punkt')
from google.colab import drive
drive.mount('/content/drive')
# spam classification data loading
urllib.request.urlretrieve("https://raw.githubusercontent.com/mohitgupta-omg/Kaggle-SMS-Spam-Collection-Dataset-/master/spam.csv", filename="spam.csv")
data = pd.read_csv('spam.csv', encoding='latin-1')
data[:5]
texts = list(data['v2'])
labels = list(data['v1'])

print(texts[:5])
print(labels[:5])

print(Counter(labels))
# glove vector model initialize
glove = {}
with open('/content/drive/My Drive/Colab Notebooks/data/news_sample/glove.6B.50d.txt', 'r', encoding='utf-8') as fr:
    for line in fr.readlines():
        temp = line.strip().split()
        word = temp[0]
        vector = temp[1:]

        glove[word] = list(map(float, vector))
def tokenize(document):
  words = []
  sentences = sent_tokenize(document) # sentence tokenizing

  for sentence in sentences:
    words.extend(WordPunctTokenizer().tokenize(sentence)) # word tokenizing

  return [word.lower() for word in words] # case normalization

def get_vector(sentence):
  tokens = tokenize(sentence)
  vector = [glove[token] if token in glove.keys() else [0]*50 for token in tokens]

  while len(vector) < 256:
    vector.append([0] * 50)
  
  return vector[:256]
x = [get_vector(text) for text in texts]
x_train, x_test = np.array(x[:5000]), np.array(x[5000:])
y = [0 if label == 'spam' else 1 for label in labels]
y_train, y_test = np.array(y[:5000]), np.array(y[5000:])

 

반응형
  • 모델 생성
    • tf.keras.layers.LSTM(128) → 자동으로 LSTM이 다대일로 해서 제일 마지막 결과만 여기 layer에서 출력해준다.
    • (tf.keras.layers.LSTM(128, return_sequences=True))
      → 모든 칸에 대해서 반환
    • Query밖에 없고 Key와 Value가 필요한 경우
      → 세 개를 똑같은걸 넣어줄 때 salf-attention이라고 함
    • tf.keras.layers.Attention()([lstm_layer, lstm_layer, lstm_layer])
      → 원래는 Query, Key, Value 순서대로 넣어야 되는데 지금 Query 하나밖에 없기 때문에 똑같은 거를 넣어줌,, 이거를 salf-attention이라고 함
input_layer = tf.keras.layers.Input(shape=(x_train.shape[1], x_train.shape[2]))
lstm_layer = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(128, return_sequences=True))(input_layer)
attention = tf.keras.layers.Attention()([lstm_layer, lstm_layer, lstm_layer])
flatten = tf.keras.layers.Flatten()(attention)
dense1_layer = tf.keras.layers.Dense(64, activation = 'relu')(flatten)
dense2_layer = tf.keras.layers.Dense(2, activation = 'softmax')(dense1_layer)

model = tf.keras.Model(inputs=input_layer, outputs=dense2_layer)
model.summary()

 

model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
model.fit(x_train, y_train, epochs=2)

 

model.evaluate(x_test, y_test, verbose=2)

결과

 

glove 벡터를 가지고 넣어줬는데 모델 자체에 embedding Layer를 추가할 수 있음! 그럼 vocabulary 아이디만 넣어주고 모델을 활용할 수 있음

 

 

Embedding + lstmm + attention 분류 실습 - embedding_layer_lstm_attention_classification.ipynb

from collections import Counter
import urllib.request
import pandas as pd
import numpy as np
import tensorflow as tf

import nltk
from nltk.tokenize import sent_tokenize
from nltk import WordPunctTokenizer

nltk.download('punkt')
# spam classification data loading
urllib.request.urlretrieve("https://raw.githubusercontent.com/mohitgupta-omg/Kaggle-SMS-Spam-Collection-Dataset-/master/spam.csv", filename="spam.csv")
data = pd.read_csv('spam.csv', encoding='latin-1')
data[:5]
texts = list(data['v2'])
labels = list(data['v1'])

print(texts[:5])
print(labels[:5])

print(Counter(labels))

 

  • vocab을 만드는 코드 생성
  • vector는 vocab이라는 것을 받아서 vocab에 있으면 vocab에 token ID 만 줄 거임, 없을 때는 vocab에 unk을 넣어서 줄거임
  • vector가 256보다 작을 때는 <pad>라고 줄 거임
def tokenize(document):
  words = []
  sentences = sent_tokenize(document) # sentence tokenizing

  for sentence in sentences:
    words.extend(WordPunctTokenizer().tokenize(sentence)) # word tokenizing

  return [word.lower() for word in words] # case normalization

def make_vocab(documents):
  word2index = {'<unk>':0, '<pad>':1}
  for document in documents:
    tokens = tokenize(document)
    for token in tokens:
      if token not in word2index.keys():
        word2index[token] = len(word2index)

  return word2index

def get_vector(sentence, vocab):
  tokens = tokenize(sentence)
  vector = [vocab[token] if token in vocab.keys() else vocab['<unk>'] for token in tokens]

  while len(vector) < 256:
    vector.append(vocab['<pad>'])
  
  return vector[:256]

 

  • vocab을 어떻게 만들었는지 확인, tokenize에 있는 결과 확인, vector 결과 확인
vocab = make_vocab(texts)
print(vocab)
print(texts[0])
print(tokenize(texts[0]))
print(get_vector(texts[0], vocab))

→ get_vector(texts[0], vocab) : 19까지는 ID가 있고 나머지 뒤에 1들은 pad가 채워짐 (256보다 짧기 때문에)

 

  • x_train = 5000 * 256
x = [get_vector(text, vocab) for text in texts]
x_train, x_test = np.array(x[:5000]), np.array(x[5000:])
y = [0 if label == 'spam' else 1 for label in labels]
y_train, y_test = np.array(y[:5000]), np.array(y[5000:])
print(x_train.shape)

결과

 

 

  • 256개의 token에 대해서 embedding Layer가 붙어야 함
    • tf.keras.layers.Embedding(len(vocab), 100) → 100 : 사이즈 → 임베딩 테이블이 word2vec랑 똑같이 생김 → 각 vocab에 대해서 100차원짜리 임베딩 테이블 생성
    • tf.keras.layers.Dropout(0.5)(embedding_table(input_layer)) → 임베딩 테이블에 input이 들어가서 dropout
    • 이렇게 만들면 glove 벡터를 따로 불러서 넣어줄 필요 없이 자체적으로 word2vec 모델을 안에 내장해버림,, → word2vec 모델도 실시간으로 같이 한꺼번에 학습됨! 학습 속도는 느려질 수 있음
input_layer = tf.keras.layers.Input(shape=(x_train.shape[1]))
embedding_table = tf.keras.layers.Embedding(len(vocab), 100)
embedded_layer = tf.keras.layers.Dropout(0.5)(embedding_table(input_layer))
lstm_layer = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(128, return_sequences=True))(embedded_layer)
attention = tf.keras.layers.Attention()([lstm_layer, lstm_layer, lstm_layer])
flatten = tf.keras.layers.Flatten()(attention)
dense1_layer = tf.keras.layers.Dense(64, activation = 'relu')(flatten)
dense2_layer = tf.keras.layers.Dense(2, activation = 'softmax')(dense1_layer)

model = tf.keras.Model(inputs=input_layer, outputs=dense2_layer)
model.summary()

 

model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
model.fit(x_train, y_train, epochs=2)

model.evaluate(x_test, y_test, verbose=2)

 

반응형
반응형

Annoy

  • 임베딩 들은 similarity를 직접 구하는 게 많이 사용되는데 특히 추천 같은 경우에서는 다른 사람들의 리스트와 내 리스트가 비슷한 경우에는 제일 비슷한 사람의 리스트에서 내가 보지 않은 리스트들을 추천해줌 (예로 들어 넷플릭스)
  • 모든 유저랑 내 기록이랑 similarity 구하는데 유저가 많으면 너무 오래 걸리기 때문에 그런 문제를 해결하기 위해서 나온 Annoy 라이브러리

 

Nearest Neighbor (근접 이웃)

  • 새로운 데이터를 입력 받았을 때, 가장 가까이 있는 것이 무엇이냐를 중심으로 새로운 데이터의

 

 

 

→ 이제까지는 분류모델을 학습을 했는데 각각의 데이터가 있으면 새로 들어온 데이터의 제일 근접한 같은 분류를 하기도 함

 

Annoy (Approximate Nearest Neighbors Oh Yeah)

  • 빠르게 벡터 유사도 검색을 수행할 수 있는 라이브러리
  • 정확한 벡터보다는 유사한 벡터를 찾기 때문에 정확도는 조금 낮아질 수 있으나 속도가 매우 빨라짐
  • Tree를 활용하여 유사한 벡터를 검색
  • Tree를 만드는 과정을 build라고 하며, build 된 Tree는 수정이 불가능

https://github.com/spotify/annoy

 

Annoy 실습 예제

  • 전에 만들었던 use_dnn_classification.ipynb 을 수정해서 만듬
    • 모델 생성해주고 정의하는 부분까지 동일함
!pip install tensorflow_text
!pip install annoy

from annoy import AnnoyIndex
from collections import Counter
import urllib.request
import pandas as pd
import numpy as np

import tensorflow as tf
import tensorflow_hub as hub
import tensorflow_text
# spam classification data loading
urllib.request.urlretrieve("https://raw.githubusercontent.com/mohitgupta-omg/Kaggle-SMS-Spam-Collection-Dataset-/master/spam.csv", filename="spam.csv")
data = pd.read_csv('spam.csv', encoding='latin-1')
data[:5]

 

 

texts = list(data['v2'])
labels = list(data['v1'])
print(texts[:5])
print(labels[:5])
print(Counter(labels))

# 모델 가져오기
model_url = 'https://tfhub.dev/google/universal-sentence-encoder-multilingual/3'
model = hub.load(model_url)
x = model(texts)
x_train, x_test = np.array(x[:5000]), np.array(x[5000:])
y = [0 if label == 'spam' else 1 for label in labels]
y_train, y_test = np.array(y[:5000]), np.array(y[5000:])

 

반응형
  • 지금 vector_size가 512이기 때문에 AnnoyIndex에 넣어주고 어떤 방법을 활용해서 거리를 구 할 건지 설정 → 'dot' 이 기본
  • Tree인덱스를 만들고 Tree 인덱스에 5000개를 넣는다.
# annoy indexing
vector_size = 512
index = AnnoyIndex(vector_size, 'dot')
data = []
vectors = model(texts)
for idx in range(len(vectors)):
  data.append({'idx':idx, 'text':texts[idx], 'vector':vectors[idx], 'label':labels[idx]})
  if idx < 5000: #indexing only 5000 samples, the others will be used for evaluation.
    index.add_item(idx, vectors[idx])

index.build(50)
index.save('spam_mail.annoy')

결과

 

 

 

 

  • 5000부터 나머지 데이터에 대해서 비교
    • 검색하는 방법 : load_index.get_nns_by_vector(data[5000]['vector'], 100)
      → 100은 몇 개를 검색할지 지정
# evaluation. check accuracy with nearest label
load_index = AnnoyIndex(vector_size, 'dot')
load_index.load('spam_mail.annoy')

result = load_index.get_nns_by_vector(data[5000]['vector'], 100)
print(result)

 

 

→ 5000번에 대한 데이터에서 제일 가까운 순서대로 100개 출력

 

  • 4번 데이터의 text와 label , 5000번 데이터의 text와 label 확인
print(data[4]['text'], data[4]['label'])
print(data[5000]['text'], data[5000]['label'])

 

→ 5000번의 벡터를 넣어서 test를 했을 때 나온 text랑 제일 비슷한 text는 4번 text이다.

→ 라벨은 ham으로 동일하다는 것은 accuracy는 맞다고 할 수 있음

 

  • 5000개를 다 검사
    • idx에서 label이랑 result[0]에 있는 label이랑 같으면 count를 올리고, total은 그냥 올림
# evaluation. check accuracy with nearest label
load_index = AnnoyIndex(vector_size, 'dot')
load_index.load('spam_mail.annoy')

count = 0
total = 0
for idx in range(5000, len(data)):
  result = load_index.get_nns_by_vector(data[idx]['vector'], 100)
  if data[idx]['label'] == data[result[0]]['label']:
    count += 1
  total += 1

print(count/total)

결과

 

→ 분류를 굳이 하지 않아도 제일 유사한 문장의 label과 비교를 했을 때 성능이 98점이 나옴

→ 이렇게 검색을 하면 전체 검색보다 훨씬 빠르기 때문에 이런 식으로 서비스 사용하기도 함

 

왜 100개를 검색을 했는가?
Tree를 50개를 생성을 했는데 제대로 동작을 하려면 무조건 트리의 개수보다 많아야 함
→ 트리의 두세 배를 검색하게 되면 100개를 검색하면 각 트리에서 두배를 검색을 한 다음 그 100개를 가져와서 100개를 정렬하는 것임,,
→ 라이브러리 동작 방식이 100개를 검색하는 것과 1개를 검색하는 것이 성능이 너무 달라지기 때문에 100개를 검색을 하게 됨

각 트리에서 나눠져서 동시에 검색을 하기 때문에 Tree의 개수가 많으면 훨씬 빠르고 성능이 좋음

 

반응형

+ Recent posts