반응형

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의 개수가 많으면 훨씬 빠르고 성능이 좋음

 

반응형
반응형

임베딩에 관련된 설명 및 예제 참고 ↓

[AI] Word2Vec, GloVe, FastText, ELMo 기본 설명 및 실습

 

Embedding + LSTM 분류 실습

  • 초반에 LSTM 분류를 원핫인코딩으로 했던걸, 글로브 벡터 입력으로 받도록 해서 수정
from collections import Counter
import urllib.request # spam 메일 데이터 받아오려고 씀
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)) # ham 갯수, spam 갯수 출력

 

  • glove 벡터 불러오기 → 구글 드라이브에 올려서 가져오기
# 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))

 

  • get_vector 함수
    • 문장을 받아서 tokenize를 해주고 token마다 돌면서 이 토큰이 glove 벡터 모델에 있으면 glove에서 벡터 모델을 가지고 오고, 없으면 0 *50 개를 넣는다.
def tokenize(document):
  words = []
	# sentence tokenizing : 구문을 문장 단위로 분리
  sentences = sent_tokenize(document) 

  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]

 

  • texts들을 벡터로 다 가지고 오게 함!
    • 5000개 기준으로 나눠서 numpy로 감싸줌
  • y는 labels가 spam이랑 ham이 있기때문에 spam일 때는 0, 아닐때는 1
    • y도 마찬가지로 5000개를 기준으로 나눠줌
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:])

 

  • 분류를 2가지로 분류하기 때문에 softmax를 2로 줌
model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Input(shape=(x_train.shape[1], x_train.shape[2])))
model.add(tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(128)))
model.add(tf.keras.layers.Dense(64, activation = 'relu'))
model.add(tf.keras.layers.Dense(2, activation = 'softmax'))

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가 word2vec 중에서 제일 학습이 잘되있고 성능이 잘나오다보니 glove랑 lstm을 가지고 문장을 분류하는 것을 굉장히 많이 활용이 된다.
→ 지금은 BERT를 많이 이용한다.
반응형
반응형

단어표현방법(word representation) 및 Word2Vec에 대한 설명 포스팅 참고 ↓

[AI] 단어표현방법 (Bag of Words, Word2Vec, One-hot Vector 등) 설명 및 실습

 

 

word2vec 실습

다운로드

https://dumps.wikimedia.org/kowiki/latest/

kowiki-latest-pages-articles.xml.bz2 다운로드 받고 Zip 파일 압축 풀지 않고 바로 붙여 넣기

wikimedia 데이터를 받은 후 바로 사용이 불가능하다. → 데이터 전처리를 위해 아래의 github 주소의 코드를 다운로드 (wikimedia 데이터를 전처리, 추출하기 위한 소스코드)

https://github.com/MouhamadAboShokor/wikiextractor

다운로드 ZIP → 압축 풀고 폴더 복붙

 

파이참에서 터미널 열기

  • day3 폴더로 이동 → cd C:\Python\nlp2\day3
  • python -m wikiextractor.WikiExtractor kowiki-latest-pages-articles.xml.bzs 입력
    → parsing 파싱 시작! (시간이 꽤 걸림)
  • wikiextractor 라이브러리를 Install 한다.

 

cmd창

  • day3의 text 폴더로 이동 → cd C:\Python\nlp2\day3\text
  • txt 파일 복사 → copy AA\wiki* wikiAA.txt
  • wikiextractor-master 폴더 안에 생성된 wikiAA.txt 파일을 day3 폴더로 이동시킨다.

 

data_preprocessing.py

  • 필요 없는 라인 <doc 으로 시작하거나 </doc>으로 시작하거나 빈문장은 넘어감
with open('wiki_preprocessed.txt','w',encoding='utf-8') as fw:
    with open('wikiAA.txt','r',encoding='utf-8') as fr:
        for line in fr.readlines():
            if line.startswith('<doc') or line.startswith('</doc>') or len(line.strip())==0:
                continue
            fw.write(line)
print('done')

→ 깨끗하게 preprocessing 된 wiki_preprocessed.txt 파일이 생성됨

 

word2vec.py

from gensim.models import Word2Vec

data = []

with open('wiki_preprocessed.txt', 'r', encoding='utf-8') as fr:
    for line in fr.readlines():
        words = line.strip().split()
        data.append(words)

#print(data)

print('Word2Vex training start')
# data : should 2D list.(list of sentences) size : vector size, min-count : 5번이상나온것만 데이터를 만듬 , workers : 변결처리, sg가 1일때 skip-gram 0이면 CBOW
model = Word2Vec(data, size=100, window=5, min_count=5, workers=4, sg=1)

model.save('word2vec.model')

 

word2vec_test.py

from gensim.models import Word2Vec

model = Word2Vec.load('word2vec.model')

print(model.wv.vocab.keys())

 

문장 단위로 말고 형태소 분석으로 한번 다시 해보자 ↓

 

word2vec.py

from konlpy.tag import Okt

from gensim.models import Word2Vec

okt = Okt()

data = []

with open('wiki_preprocessed.txt', 'r', encoding='utf-8') as fr:
    for line in fr.readlines():
        morphs = okt.pos(line.strip())
        data.append([morph[0] for morph in morphs])

print('Word2vec training start')
model = Word2Vec(data, size=100, window=5, min_count=5, workers=4, sg=1) #size: vector size, data: should 2D list.(list of sentences)

model.save('word2vec_morph.model')

print('done')

 

word2vec_test.py

import numpy as np
from gensim.models import Word2Vec

model = Word2Vec.load('word2vec.model')

# print(model.wv.vocab.keys())

def is_oov(token):
    if token not in model.wv.vocab.keys(): # 단어가 있는가? 있으면 False 없으면 True
        return True
    else:
        return False

# # OOV 테스트
print(is_oov('대한민국')) 
print(is_oov('서울'))
print(is_oov('일본'))
print(is_oov('도쿄'))

# # 유사도 테스트
print(model.similarity("대한민국", "일본"))
print(model.similarity("서울", "도쿄"))
print(model.similarity("강아지", "고양이"))
print(model.similarity("강아지", "서울"))

# most similar
print(model.most_similar("강아지"))

# analogy test
# 대한민국 + 일본 - 서울
print(model.most_similar(positive=["대한민국", "일본"], negative=["서울"]))

wikidata 같은 경우 많이 활용됨

 

반응형

GloVe (Global Vectors for Word Represintation)

  • 카운트 기반과 예측 기반을 모두 사용하는 방법 → Word2Vec은 예측만 사용
  • 임베딩 된 중심 단어와 주변 단어 벡터의 내적이 전체 코퍼스에서의 동시 등장 확률이 되도록 만드는 것

https://nlp.stanford.edu/projects/glove/

→ Glove에 대한 자세한 정보, pre-trained 된 모델들도 다운로드할 수 있음

  • 영어권에서는 제일 많이 활용되는 Word2Vec 모델 중 하나
  • OOV에 문제가 있음
학습을 할 때 동시 등장 행렬을 따로 계산을 해준다
동시 등장 행렬 : 특정 단어가 동시 등장 횟수를 카운트를 하고 특정 단어가 등장했을 때 다른 단어가 등장할 조건부 확률

 

glove 실습

https://github.com/stanfordnlp/GloVe

glove.6B.zip 파일 다운로드하고 압축을 풀면 txt 파일이 4개가 생성이 되는데 이걸 파이참 glove 폴더 안에 복사 붙여 넣기

파이참에서 glove_python 라이브러리 Install 하기!!

 

nlp2 > day3 > glove 폴더 > glove_loading.py

  • vector = temp[1:]에 50차원의 실수가 들어있는데 str로 읽혔기 때문에 float로 바꿔야 함 → glove[word] = list(map(float, vector))
import numpy as np

def cosine_similarity(A, B):
    return np.dot(A, B) / (np.linalg.norm(A) * np.linalg.norm(B))

glove = {}

with open('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))

print('glove vector loaded')

# vector = glove['dog']
# print(cosine_similarity(vector, vector))
print(cosine_similarity(glove['dog'], glove['cat']))

glove 벡터를 사용할 때 코사인 유사도를 직접 만들어서 사용하면 됨
Glove는 학습이 되게 잘 돼있어서 성능이 잘 나옴

 

FastText

  • 위에서 Glove모델에서 OOV의 문제를 해결하기 위해 만들 Word 임베딩 모델
  • 단어를 n-gram으로 나눠서 학습
    • n-gram은 글자 개수로 나눠서 토큰을 만든다.
    • n-gram의 범위가 2-5로 설정한 경우 : assumption = {as, ss, su, …, ass, ssu, sum, …, mptio, ption, assumption}
    • 학습할 때 없던 단어가 들어왔을 때도 n-gram에 대한 단어는 웬만하면 있어서 커버 칠 수 있음
  • 실제 사용 시에는, 입력 단어가 사전에 있을 경우 해당 단어의 벡터를 곧바로 리턴하고 사전에 없는 경우 (OOV, Out-of-Vocabulary) 입력 단어의 n-gram vector를 합산하여 반환

https://research.fb.com/fasttext/ → github 주소도 있음

사람들이 오타를 냈을 때도 각 n-gram의 벡터의 합을 반환하기 때문에 비슷한 단어가 나올 수 있음

 

fasttext 실습

nlp2 > day3 > fasttext

  • gensim에서는 word2vec 로딩을 위한 함수를 제공함
    → 이미 tokenize가 되어 있어야 함
import gensim
from gensim.models.fasttext import FastText

path = '../word2vec/wiki_preprocessed.txt'

# gensim에서는 word2vec 로딩을 위한 함수를 제공함
sentences = gensim.models.word2vec.Text8Corpus(path)

model = FastText(sentences, min_count=5, size=100, window=5)

model.save('fasttext_model')

saved_model = FastText.load('fasttext_model')

word_vector = saved_model['이순신']
print(word_vector)

print(saved_model.similarity('이순신', '이명박'))
print(saved_model.similarity('이순신', '원균'))

print(saved_model.similar_by_word('이순신'))
print(saved_model.similar_by_word('조선'))

saved_model.most_similar(positive=['대한민국', '도쿄'], negative=['서울'])

 

import gensim
from gensim.models.fasttext import FastText

path = '../word2vec/wiki_preprocessed.txt'

# gensim에서는 word2vec 로딩을 위한 함수를 제공함
sentences = gensim.models.word2vec.Text8Corpus(path)

model = FastText(sentences, min_count=5, size=100, window=5)

model.save('fasttext_model')

saved_model = FastText.load('fasttext_model')

word_vector = saved_model['이순신']
print(word_vector)

print(saved_model.similarity('이순신', '이명박'))
print(saved_model.similarity('이순신', '원균'))

print(saved_model.similar_by_word('이순신'))
print(saved_model.similar_by_word('조선'))

saved_model.most_similar(positive=['대한민국', '도쿄'], negative=['서울'])

 

 

ELMo (Embeddings from Language Model)

  • 위의 word2vec 모델들 같은 경우에는 윈도우 사이즈가 있다보니까 window 내에 있는 단어들에 대해서만 학습을 하는데 밖에 있어도 문맥을 반영을 하고 싶고, 글자가 같은 단어도 다른 뜻을 가지는 경우가 있는데 그런 문제를 해결하기 위해 고안한 모델
  • Pre-trained 모델의 시작

→ 위의 그림처럼 단어의 뜻이 다른데도 벡터가 하나밖에 없으면 문제이기 때문에 해결하고, 문장의 전체 문맥까지 고려할 수 있는 임베딩 기법

 

  • Elmo는 언어모델인데, lstm을 가지고 순방향 하나 역방향 하나 만들어서 합친것
  • 입력이 문장으로 들어가고, 나올때도 문장 기준으로 벡터가 나옴
    • 한칸의 한칸의 벡터들이 워드에 대한 벡터이기는 하지만 문맥을 고려해서 벡터를 입력을 받기 때문에 문장을 입력했을 때 성능이 훨씬 잘나옴

 

 

 

 

반응형
반응형

단어표현방법(word representation)

  • DTM: Document-Term Matrix (문서 단어 행렬)
  • LSA: Latent Semantic Analysis (잠재 의미 분석)

  • N-gram : N(숫자)가 2라면 2글자씩 토큰으로 만든다.
  • Glove : Count랑 Word2Vec이랑 합친것
  • LSA : 문서 전체에서 count하는 방법

 

Bag of Words (BoW)

  • 단어들의 순서는 전혀 고려하지 않고, 단어들의 출현 빈도를 활용하는 단어 표현 방법
  • 단어들의 가방이라는 뜻으로 모든 단어를 가방에 넣어서 표현한다는 의미
  • BoW를 만드는 과정
    1. 각 단어에 고유한 정수 인덱스를 부여
      → one hot vector에서 처음에 vocab을 생성했던 부분과 동일
    2. 각 인덱스의 위치에 단어 토큰의 등장 횟수를 기록한 벡터를 생성

예시

 

Bag of Words 실습

  • 형태소 분석기 활용
!pip install konlpy

from konlpy.tag import Kkma

import nltk
from nltk.tokenize import sent_tokenize

nltk.download('punkt')

class Tokenizer:
  def __init__(self):
    self.kkma = Kkma()

  def make_vocab(self, documents):
    word2index = {'<unk>':0}
    for document in documents:
      tokens = self.tokenize(document)
      for voca in tokens:
        if voca not in word2index.keys(): # voca가 key 리스트에 없으면 리스트에 추가
          word2index[voca] = len(word2index)
    self.vocab = word2index

	# 형태소 분석
  def tokenize(self, document):
    morphs = []
    sentences = sent_tokenize(document)

    for sentence in sentences:
      sentence_morphs = self.kkma.pos(sentence)
      morphs.extend([morph[0] + '/' + morph[1] for morph in sentence_morphs])

    print(morphs)
    return morphs
  
	# BOW
  def bag_of_words(self, sentence):
    morphs = self.tokenize(sentence)
    vector = [0] * len(self.vocab)
    for morph in morphs:
      if morph not in self.vocab.keys():
        morph = '<unk>'
      vector[self.vocab[morph]] += 1
    
    return vector

→ 형태소 분석까지 다 한 다음

 

tokenizer = Tokenizer()
texts = ['안녕하세요', '안녕하십니까', '오늘은 날씨가 좋네요', '기분이 좋아요']
tokenizer.make_vocab(texts) # texts 리스트로 vocab을 만듬

print(tokenizer.vocab) # vocab 출력

tokenizer.bag_of_words('오늘은 날씨가 어떨 것 같으세요') # 문장의 벡터가 됨

→ 0 인덱스의 unk : 5개 , 6 인덱스의 '오늘' : 1개 , 7 인덱스의 '은' : 5개 , 8 인덱스의 '날씨' : 1개 , 9 인덱스의 '가' : 1개

 

 


DTM (Document Term Matrix)

  • 다수의 문서에서 등장하는 각 단어들의 빈도를 행렬로 표현한 것
  • 각 문서에 대한 BoW를 하나의 행렬로 만든 것과 동일

 

TF-IDF (Term Frequency-Inverse Document Frequency)

  • 단어의 빈도와 역 문서 빈도를 사용하여 각 단어들마다 중요한 정도를 가중치로 주는 방법
  • tf(d, t) : 특정 문서 d에서의 특정 단어 t의 등장 횟수
  • df(t) : 특정 단어 t가 등장한 문서의 수
  • idf(d, t) : df(t) 에 반비례하는 수

n : 총 문서 수

 

  1. DTM을 생성
  2. TF 는 각 문서에서 특정 단어 등장 횟수
    tf(문서1, 사과) → 1
    df(사과) → 1
    idf(d, 사과) → log(4/2) → log2
  3. tf(문서1, 사과) → 1
    tf(문서3, 바나나) → 2
    df(사과) → 1
    df(바나나) → 2
  4. '과일이' idf = log(4/1+1)
    '바나나' idf = log(4/1+2)
  5. TF-IDF = 각 문서에서 특정 단어 등장 횟수 (tf 값) 에 idf 값을 곱한 값

 

Tf-idf 실습

from sklearn.feature_extraction.text import TfidfVectorizer

documents = {
    'you knew I want your love',
    'I like you',
    'what should I do',
}

vectorizer = TfidfVectorizer().fit(documents)

print(vectorizer.transform(documents).toarray())

 

  • 검색할때 TF IDF 스코어를 활용을 하기 때문에 TF IDF 같이 나옴
DTM에서 TF-IDF의 차이는? 
DTM : Bag of Words 를 행렬로 나타낸 것
→ 모든 문서에서 등장한 단어는 특징이 없다고 의미될 수 있음
→ idf 값을 곱해줌으로써 자주 나오는 단어는 점수를 낮추고, 드물게 나오는 단어는 점수를 높이는 효과

DTM : 순수 단어 카운팅 TF-IDF : 모든 문서에서 등장하는 단어는 가중치를 낮추고, 드물게 나오는 단어는 가중치를 높이는 알고리즘

 

반응형

임베딩

희소표현 (Sparse Representation)

  • 원핫인코딩(one-hot encoding 방식) , tf-idf
  • 엄청 큰 메트릭스지만 특정부분에 하나씩만 사용
    → 숫자가 나오는것 자체가 희소하기 때문에 희소표현이라고 부름

밀집표현 (Dense Representation)

  • word2vec
  • 차원을 줄일때 사용자 설정값으로 줄이고, 벡터가 조밀해졌다고 해서 밀집벡터이라고 부름
  • 0보다는 거의 모든 케이스에 값이 다 들어있다

워드 임베딩

  • 밀집표현으로 벡터를 표현하는 방법
  • 임베딩 벡터 - 워드 임베딩 과정을 통해 나온 벡터
  • Word2Vec , Glove , FastText

 

Word2Vec

  • 비슷한 위치에서 등장하는 단어들은 비슷한 의미를 가진다라는 가정
    • ‘강아지’와 ‘고양이’는 주변에 서로 비슷한 단어가 나올 것
    • ‘강아지’와 ‘자연어’는 주변에 전혀 다른 단어가 나올 것
  • 중심 단어와 주변 단어로 학습하므로 라벨링이 필요 없음
    • 문장이 들어왔을때 tokenizer 한 다음에 해당 token들을 가지고 '강아지'라는 단어가 있을때 앞이나 뒤에 있는 단어들을 보고 학습
    • → 비지도학습(unsupervised learning)
  • CBOW(Continuous Bag of Words), Skip-gram 방식

 

CBOW (Continuous Bag of Words)

  • 주변에 있는 단어로부터 중심 단어를 예측하는 방법
  • 중심 단어를 예측하기 위해서 앞, 뒤로 몇 개의 단어를 볼지에 대한 범위를 윈도우(window)라고 함

→ Projection layer 모델은 복잡할 수 있음

→ set 이라는 단어를 예측하기 위해서는 fat , cat , on , the 라는 단어들을 가지고 예측을 한다.

→ 윈도우가 3개면 앞에 3단어 , 뒤에 3단어로 학습

 

Skip-gram

  • CBOW와 반대되는 개념 - 입력과 아웃풋이 반대
  • 중심단어에서 주변단어를 예측하는 방식

 

네거티브 샘플링 (Negative Sampling)

  • one-hot encoding으로 모든 단어에 대해서 cross entropy를 계산하고 weight를 조정하려면 단어 개수에 따라서 무거운 작업이 될 수 있음

→ 만약 단어가 10만개가 있다면 10만에 대한 cross entropy를 계산을 해야하는데 너무 오래 걸릴 수가 있다. 단어 갯수가 많아질수록 무거워진다.

→ 그래서 sampling 샘플링을 해준다.

  • 주변 단어-중심 단어 관계를 가지고 지정한 윈도우 사이즈 내에 존재하면 1, 그렇지 않으면 0으로 이진분류 문제로 변경하여 학습하면 더 빠르게 학습할 수 있음
    → cross entropy는 binary에 대해서만 계산하면 되고 , weight도 두개만 업데이트하면 됨
  • 전체 단어가 아니라, 일부에 대해서만 학습하도록 샘플링
Word2Vec 같은 경우 비지도 학습이다 보니 방대한 양의 데이터 학습 가능
→ 데이터가 너무 많기 때문에 네거티브 샘플링처럼 샘플링을 해서 연관이 있는지 학습

 

코사인 유사도 (Cosine Similarity)

  • 두 특성 벡터간의 유사정도를 코사인 값으로 표현한 것
  • 코사인 유사도는 -1에서 1까지의 값을 가지며, -1은 서로 완전히 반대, 0은 서로 독립, 1은 서로 같은 경우를 의미

그 외의 유사도 평가 방법 유클리드 거리(Euclidean Distance) , 자카드 유사도(Jaccard Similarity)

 

Word Analogy

  • 유추를 통한 평가로 유추에 대한 데이터가 존재해야 테스트를 할 수 있음
  • 명사뿐만이 아니라 형용사, 동명사에서도 똑같이 비슷하게 나오기도 함

→ man - woman + king = queen

→ 한국 - 서울 = 일본 - 도쿄

반응형
반응형

RNN - 시계열데이터 (자연어처리, 센서데이터, 주식데이터)

순환신경망 (Recurrent Neural Network)

  • DNN은 시퀀스에 대한 부분이 처리가 안되어 이를 위해 고안된 모델이 RNN
  • 앞에서 계산한 가중치를 가지고 가중치를 계산할 때 참고를 하다보니 시계열데이터가 더 잘됨
  • 현재 셀에서 이전 셀의 값을 받아서 현재 셀의 상태를 규정
  • 이전 셀이 계산이 되어야만 현재 셀이 계산이 되므로 동작 속도가 느리다.

RNN이라고 이미지를 사용하면 안되는건 없고, 이미지든 자연어든 결국 다 숫자로 이루어져 있기 때문에 결국 다 적용가능한데 좀 더 특화된 부분이 자연어처리나 시계열데이터라는 것 뿐임 
→ 자연언어 처리에서도 CNN을 활용을 해서 분류를 하기도 한다. 
→ CNN보다 RNN이 더 느린데, 왜냐하면 앞에를 계산해야만 뒤에를 계산을 할 수 있음

 

  • Application에 따라서 다양한 방식으로 활용할 수 있음
  • 원래는 다대다처럼 모델은 다 있는데 일대다에서는 한개만 넣고 나머지는 빈값만 넣어준다.
  • 이미지 캡셔닝 → 이미지에 대한 캡션을 넣고 예측을 하도록 만듬

RNN의 경우, 입력이 길어질수록 앞의 정보가 뒤로 충분히 전달되지 못하는 문제 발생 
→ 장기 의존성 문제(Long-Term Dependencies problem) 
문장에서는 제일 처음에 온 단어가 중요한 역할을 할 수도 있음

 

 

LSTM(Long Short Term Memory)

  • RNN의 문제를 해결하기 위해 고안된 모델
  • 제일 초반에 들어온 토큰에 대한 정보도 잘 보관을 하고, 최근에 들어온 토큰의 정보도 잘 보관을 할 수 있도록 고안된 모델
  • 불필요한 기억은 지우고 기억해야할 것들을 정한다.

→ 이런식으로 디자인 되어있다.

  • 장기 상태는 뭔가 곱해지지 않고 위의 Ct-1에서 보면 그냥 그대로 넘어오는 부분때문에 장기상태가 기억이 된다.

 

bi-directional LSTM

  • LSTM 같은 경우 앞에서부터 뒤로 가면서 왼쪽에서부터 오른쪽으로만 연산을 했는데 그렇게 하는것보다는 언어라는 자체가 뒤에서부터 앞으로 오는것도 의미가 있어서 역방향도 같이 사용을 하는 것이 bi-directional LSTM이다.
  • LSTM을 역방향이랑 정방향이랑 같이 쓰고 각각 출력을 붙여서 활용을 하면 병렬처리를 할 수 있어서 속도는 동일한데 성능을 높일 수 있다.
bi-directional은 꼭 LSTM만 해당이 되는 것은 아님. RNN도 bi-directional으로 똑같이 사용 가능하지만 LSTM을 가장 많이 사용하기 때문에 LSTM기준으로 보려고 bi-directional LSTM이라고 함.

 

GRU(Gated Recurrent Unit)

  • LSTM이랑 비슷하지만 학습하는 가중치가 적어서 학습이 조금 더 빠르다.
  • 데이터 양이 적을 때는, 매개 변수의 양이 적은 GRU가 조금 더 낫고, 데이터 양이 많을 때는, 매개 변수의 양이 많은 LSTM이 더 낫다고 알려져 있음

 

RNN기반 분류 실습

  • Colab을 이용한 실습

    • → 내 컴퓨터에 있는 파일을 Colab에서 불러오게 하려면 특별한 동작을 해줘야 함.
    • 구글 드라이브랑 연결이 되서 자동 저장됨
    • Colab은 내 컴퓨터가 아닌 구글에서 제공하는 서버에 접속해서 동작한다.

⇒ test.tsv , train.tsv 파일을 내 드라이브 Colab 폴더에 data 디렉토리를 만들어서 붙여 놓는다.

 

 

from google.colab import drive
drive.mount('/content/drive')

이런 화면이 뜨면 저 URL로 들어가서 계정 선택 후 code 를 복사해서 붙여넣고 엔터키를 누른다.

 

 

 

 

→ Mounted 가 나오면 Colab이랑 드라이브랑 연결이 됬다는 뜻 (드라이브에 있는 것에 접근 가능함)

 

 

 

 

 

 

fr = open('/content/drive/My Drive/Colab Notebooks/data/news_sample/train.tsv', 'r', encoding='utf-8') 
print(fr.readline())

→ 파일이 제대로 읽어지는 지 확인하는 코드

/content/drive/My Drive 까지가 내 드라이브의 경로

반응형
  • 데이터를 읽어서 저장하는 코드
    •  
    • 한 라인에 뉴스기사 하나가 저장이 되게 데이터가 만들어져 있다.
    •  
    • strip() : 양쪽의 공백제거 → 뉴스기사 하나를 읽으면 \n가 있어서 다음 기사로 넘어가는데 그것을 없애주기 위한 작업
    •  
    • tsv는 tab으로 나눠서 엑셀처럼 쓰는 함수!! → tab으로 split('\t')을 해서 나눈 후 1번째는 label , 3번째 들어있는 것을 title , 4번째 들어있는 것을 content로 나눠서 가져온다.
  • if문 부분
    1. label_dic 이라는 것을 만든다. → 새로운 label이 들어올때마다 label_dic 을 만든다.
    2. 코드 아래에 만들어진 label_dic 캡처본 확인
    3. 실제 train_labels 에 넣을때는 숫자로 들어가게 된다.
    4. train_text 는 title이랑 content를 넣을 때 그 사이에 개행문자를 넣는다.
  • label이 지금은 세개 다 텍스트로 들어가 있는데 전부 숫자로 바꿔주는 작업
label_dic = {}
train_texts, train_labels = [], []
test_texts, test_labels  = [], []

# 데이터를 읽어서 저장하는 코드
# 450건
with open('/content/drive/My Drive/Colab Notebooks/data/news_sample/train.tsv', 'r', encoding='utf-8') as fr:
  lines = fr.readlines()
  for line in lines:
    line = line.strip().split('\t')
    label = line[1]
    title = line[3]
    content = line[4]
    
    if label not in label_dic.keys():
      label_dic[label] = len(label_dic)

    train_texts.append(title + '\n' + content)
    train_labels.append(label_dic[label])

# 50건
with open('/content/drive/My Drive/Colab Notebooks/data/news_sample/test.tsv', 'r', encoding='utf-8') as fr:
  lines = fr.readlines()
  for line in lines:
    line = line.strip().split('\t')
    label = line[1]
    title = line[3]
    content = line[4]
  
    test_texts.append(title + '\n' + content)
    test_labels.append(label_dic[label])

 

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

import numpy as np
import tensorflow as tf

nltk.download('punkt') # wordTokenizer 쓸때 필요한 리소스

 

  • 전처리 과정 Tokenizer
    •  
    • one_hot_encoding 함수까지는 그저께 만든 코드랑 동일

    • → 딥러닝에서는 전체길이가 똑같아야 되기 때문에 행렬 연산은 사이즈가 똑같아야 연산이 가능
      → 256보다 큰 경우에는 0 벡터를 붙여주고 256 까지만 벡터를 반환
    • get_vector 함수에서 while문은 전체 길이를 256까지만 맞춰주는 코드
class Tokenizer:
  def make_vocab(self, documents):
    word2index = {'<unk>':0}
    for document in documents:
      tokens = self.tokenize(document)
      for voca in tokens:
        if voca not in word2index.keys():
          word2index[voca] = len(word2index)
    self.vocab = word2index

  def tokenize(self, document):
    words = []
    sentences = sent_tokenize(document)

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

    return words

  def one_hot_encoding(self, word):
    one_hot_vector = [0] * len(self.vocab)
    if word not in self.vocab:
      word = '<unk>'
    index = self.vocab[word]
    one_hot_vector[index] = 1
    return one_hot_vector
  
  def get_vector(self, sentence):
    tokens = self.tokenize(sentence)
    vector = [self.one_hot_encoding(token) for token in tokens]

    while len(vector) < 256:
      vector.append([0] * len(self.vocab))
    
    return vector[:256]
tokenizer = Tokenizer()
tokenizer.make_vocab(train_texts)

→ 전체 학습 데이터로 vocab을 생성해준다.

 

 

  • get_vector를 하면 안에서 tokenize를 한 다음에 알아서 256개까지의 벡터를 반환해준다.

 

  • train_text 를 숫자로 바꾸는 코드

    • → np.array 로 감싸주는 이유는 그래야 tensorflow 에서 활용을 할 수 있음
    • get_vector를 쓰면 된다.
x_train = np.array([tokenizer.get_vector(text) for text in train_texts])
x_test = np.array([tokenizer.get_vector(text) for text in test_texts])

# train_labels은 숫자로는 되어있는데 numpy로는 안되있어서 array로 감싸준다.
y_train = np.array(train_labels) 
y_test = np.array(test_labels)

print(x_train.shape, x_test.shape)

결과

→ 256개 까지만 사용하기 때문에 256

→ 340은 원핫벡터의 크기, 원핫벡터가 vocab의 사이즈만큼 생성

→ 원핫인코딩은 vocab 사이즈만큼 벡터에서 아닌것만 1로 표시되기 때문에 이런식으로 결과가 나온다.

 

 

  • 모델 생성
model = tf.keras.models.Sequential()
# 입력에 대한 사이즈 생성 - 입력 layer라서 학습 파라미터는 없음
model.add(tf.keras.layers.Input(shape=(x_train.shape[1], x_train.shape[2])))
# LSTM을 Bidirectional로 감싸줘야함
# model.add(tf.keras.layers.LSTM(128)) -> 이렇게 써도된다.
# 자동으로 LSTM이 다대일로 해서 제일 마지막 결과만 여기 layer에서 출력을 해준다.
model.add(tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(128)))
# 위에서 출력된 결과를 relu로 분류하는 네트워크를 붙여준다.
model.add(tf.keras.layers.Dense(64, activation = 'relu'))
# label이 세개니까 3을 넣어서 softmax layer를 추가
model.add(tf.keras.layers.Dense(3, activation = 'softmax'))

파라미터 갯수 확인

 

model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])

model.fit(x_train, y_train, epochs=20)

→ compile 해준 후 , fit 함수로 학습

학습

 

  • 학습을 하면 evaluate도 할 수 있다.
model.evaluate(x_test, y_test, verbose=2)

모델을 만드는 부분은 조금 다르지만 cnn 이미지분류를 할때랑 똑같이 compile해주고 , 학습하는 방법(fit)도 똑같다.
이전 포스팅 참고 - 2020/12/15 - [Ai] - [AI] CNN(Convolutional Neural Network)의 이해 및 실습

 

형태소 분석기 사용

  • konlpy 라이브러리 다운
!pip install konlpy

from konlpy.tag import Kkma
kkma = Kkma()
print(train_texts[0])
print(kkma.pos(train_texts[0]))

 

 

https://konlpy.org/en/latest/
→ 가장 간단한 형태소 분석기 참고

 


Seq2seq model

  • 번역기에서 대표적으로 사용되는 모델
  • 앞에는 LSTM을 쓰는데 다 0으로 쓴다.
    → 여기에 뭐가 나오든 무시하고 버림. 처음의 state가 넘어가고 넘어가다가 state를 새로운 LSTM에 넣는데 여기에서는 <sos> : start of sentence 를 넣었을때 나오는 단어를 그다음부터 입력을 함
  • I am a student 를 넣었을 때 스페인어로 번역되서 나오는 모델

  • 인코더 셀은 모든 단어를 입력받은 뒤에, 마지막 시점의 은닉 상태를 디코더 셀로 넘겨주며, 이 벡터를 context vector라고 한다.
  • 디코더는 초기 입력으로 문장의 시작을 의미하는 심볼이 들어가고, 다음에 등장할 확률이 높은 단어를 예측
반응형

+ Recent posts