jam 블로그

Deep learning natural language processing nlp 1 본문

인공지능

Deep learning natural language processing nlp 1

kid1412 2019. 10. 20. 23:07
728x90

자연어 처리(Natural Language Processing, NLP)

스터디 용으로 Deep Learning from Scratch 2 책을 참고로 정리한 것입니다.

Word Embedding에 대해서 알아봅니다.

  • 시소러스를 활용한 기법
  • 통계 기반 기법
  • 추론 기반 기법(word2vec)
  • Latent semantic analysis
  • Brown clustering
  • Glove
  • fastText
  • Gensim

위와 같이 다양하게 있으며, 해당 책에서는 볼드로 된 3가지를 설명합니다.

시소러스

유의어 사전으로, '뜻이 같은 단어(동의어)'나 '뜻이 비슷한 단어(유의어)'가 한 그룹으로 분류
자연어 처리에 이용되는 시소러스는 단어 사이의 '상위, 하위' 또는 '전체, 부분' 과 같이 관계를 정의해둔 형식으로 사용합니다.

  • WordNet
    시소러스 종류 중 하나로 '단어 네트워크'를 이용할 수 있습니다. 또한 단어 사이의 유사도를 구할 수 있습니다.
    Python library에서는 NLTK가 있습니다.
from nltk.corpus import wordnet
print(wordnet.synsets('car'))
'''
[Synset('car.n.01'), Synset('car.n.02'), Synset('car.n.03'), Synset('car.n.04'), Synset('cable_car.n.01')]
위와 같이 결과가 나오며, 'car' 단어로 'n' 명사의 그룹의 서로 다른 동의어 그룹이 5개가 있다는 뜻입니다.
'''


car = wordnet.synset('car.n.01')
print(car.definition())
'''
a motor vehicle with four wheels; usually propelled by an internal combustion engine
여기서 나오는 문자은 동의어 그룹의 의미를 뜻하며, 컴퓨터 보다는 사람이 이해 할 때 쓰입니다.
'''

car = wordnet.synset('car.n.01')
novel = wordnet.synset('novel.n.01')
dog = wordnet.synset('dog.n.01')
motorcycle = wordnet.synset('motorcycle.n.01')

print(car.path_similarity(novel))
print(car.path_similarity(dog))
print(car.path_similarity(motorcycle))

'''
0.05555555555555555
0.07692307692307693
0.3333333333333333

car 단어와 novel, dog, motorcycle 셋 단어의 유사도를 구한 것이며 다른 두 단어에 비해 motorcycle이 높은 것을 확인 할 수 있습니다.
'''

시소러스의 단점으로는

  1. 새로운 단어가 생겨날 수록 사람이 레이블링 하는데에 있어서 갱신이 어렵기 때문에 시대 변화에 대응하기 어렵습니다.
  2. 위에 얘기했다시피 사람의 손을 거쳐야 하기 때문에 비용이 큽니다.
  3. 단어의 쓰임새에 따라 뜻이 달라지는 미묘한 차이를 표현할 수 없습니다.

통계 기반 기법

시소러스의 문제가 있어서 나왔습니다. 여기서는 말뭉치 Corpus를 이용합니다.
다음은 매우 작은 텍스트를 사용하여 전처리 Preprocessing을 진행합니다.

text = 'You say goodbye and I say hello.' # 말뭉치
text = text.lower() # 전부 소문자로 변경
text = text.replace('.', ' .') # 마침표를 한칸 띄우는걸로 교체
words = text.split(' ') # 띄어쓰기 기준으로 분할
print(words)

'''
['you', 'say', 'goodbye', 'and', 'i', 'say', 'hello', '.']
정규식을 사용하여 분할 할 수 도 있습니다.
'''

word_to_id = {}
id_to_word = {}

for word in words:
    if word not in word_to_id:
        new_id = len(word_to_id)
        word_to_id[word] = new_id
        id_to_word[new_id] = word

print(word_to_id)
print(id_to_word)

'''
{'you': 0, 'say': 1, 'goodbye': 2, 'and': 3, 'i': 4, 'hello': 5, '.': 6}
{0: 'you', 1: 'say', 2: 'goodbye', 3: 'and', 4: 'i', 5: 'hello', 6: '.'}
'''

import numpy as np

corpus = [word_to_id[w] for w in words]
corpus = np.array(corpus)
print(corpus)

'''
[0 1 2 3 4 1 5 6]
문장을 id 값으로 전처리한 상태입니다.
'''
  • 분산 표현 : 단어의 의미를 정확하게 파악할 수 잇는 벡터 표현
  • 분포 가설 : 단어의 의미는 주변 단어에 의해 형성된다.
    분포 가설의 주요 핵심은 단어 자체의 의미보다는 주변 단어 즉, 맥락이 의미를 형성한다 입니다.
  • 통계 기반 : 주변에 어떤 단어가 몇 번이나 등장하는지를 세어 집계하는 방법
  • 동시발생 행렬 : 모든 단어에 대해 동시 발생하는 단어를 표로 정리
import numpy as np

def create_co_matrix(corpus, vocab_size, window_size=1):
    corpus_size= len(corpus)
    co_matrix = np.zeros((vocab_size, vocab_size), dtype=np.int32)

    for idx, word_id in enumerate(corpus):
        for i in range(1, window_size + 1):
            left_idx = idx - i
            right_idx = idx + i

            if left_idx >= 0:
                left_word_id = corpus[left_idx]
                co_matrix[word_id, left_word_id] += 1

            if right_idx < corpus_size:
                right_word_id = corpus[right_idx]
                co_matrix[word_id, right_word_id] += 1

    return co_matrix

각 단어들의 벡터 사이의 유사도 측정 방식

  • 벡터의 내적
  • 유클리드 거리
  • 코사인 유사도

코사인 유사도는 다음과 같습니다.
예를 들어 두 벡터 $ x = (x_1, x_2, x_3, \cdots , x_n), y = (y_1, y_2, y_3, \cdots , y_n) $

$$
similarity(x, y) = \frac{x \cdot y}{|x||y|}
$$

식은 부자에는 벡터의 내적이, 분모에는 각 벡터의 노름(norm) 입니다. 여기서 노름은 L2 norm 이며, 벡터의 각 원소를 제곱해 더한 후 다시 제곱근을 구해 계산합니다.

def cos_similarity(x, y, eps=1e-8):
    nx = x / (np.sqrt(np.sum(x**2)) + eps)
    ny = y / (np.sqrt(np.sum(y**2)) + eps)
    return np.dot(nx, ny)
import numpy as np
import sys

def preprocess(text):
    text = text.lower()
    text = text.replace('.',' .')
    words = text.split(" ")
    word_to_id = {}
    id_to_word = {}
    for word in words:
        if word not in word_to_id:
            new_id = len(word_to_id)
            word_to_id[word] = new_id
            id_to_word[new_id] = word
    corpus = np.array([word_to_id[w] for w in words])
    return corpus, word_to_id, id_to_word

def create_co_matrix(corpus, vocab_size, window_size=1):
    corpus_size= len(corpus)
    co_matrix = np.zeros((vocab_size, vocab_size), dtype=np.int32)

    for idx, word_id in enumerate(corpus):
        for i in range(1, window_size + 1):
            left_idx = idx - i
            right_idx = idx + i

            if left_idx >= 0:
                left_word_id = corpus[left_idx]
                co_matrix[word_id, left_word_id] += 1

            if right_idx < corpus_size:
                right_word_id = corpus[right_idx]
                co_matrix[word_id, right_word_id] += 1

    return co_matrix

def most_similar(query, word_to_id, id_to_word, word_matrix, top=5):
    if query not in word_to_id:
        print("%s(을)를 찾을 수 없습니다." % query)
        return

    print('\n[query] ' + query)
    query_id = word_to_id[query]
    query_vec = word_matrix[query_id]
    vocab_size = len(id_to_word)
    similarity = np.zeros(vocab_size)

    for i in range(vocab_size):
        similarity[i] = cos_similarity(word_matrix[i], query_vec)

    count = 0
    for i in (-1 * similarity).argsort():
        if id_to_word[i] == query:
            continue
        print(' %s: %s' % (id_to_word[i], similarity[i]))

        count += 1
        if count >= top:
            return

def cos_similarity(x, y):
    nx = x/np.sqrt(np.sum(x**2))
    ny = y/np.sqrt(np.sum(y**2))
    return np.dot(nx, ny)

text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocess(text)
vocab_size = len(word_to_id)
C = create_co_matrix(corpus, vocab_size)

most_similar('you', word_to_id, id_to_word, C, top=5)
'''
 goodbye: 0.7071067811865475
 i: 0.7071067811865475
 hello: 0.7071067811865475
 say: 0.0
 and: 0.0
'''

위에서 단어를 벡터로 표현한 것을 좀 더 개선해 봅시다.
점별 상호정보량(Pointwise Mutual Information)이라는 척도를 사용합니다.
PMI,는 확률 변수 $ x, y $에 대해 다음 식으로 정의됩니다.

$$
PMI(x, y) = log_2\frac{P(x, y)}{P(x)P(y)}
$$

위 식은 $P(x)$는 $x$가 일어날 확률, $P(y)$는 $y$가 일어날 확률, $P(x, y)$는 $x, y$가 동시에 일어날 확률을 뜻하며, 높을 수록 관련성이 높습니다.
이 공식을 위에서 만들었던 동시발생 행렬 C로 변경하면 다음과 같습니다.

$$
PMI(x, y) = log_2\frac{P(x, y)}{P(x)P(y)} = log_2\frac{\frac{C(x, y)}{N}}{\frac{C(x)}{N}\frac{C(y)}{N}} = log_2\frac{C(x,y)\cdot N}{C(x)C(y)}
$$

단점은 동시 발생 횟수가 0이면 $ log_2 0 = -\infty$ 되기에 양의 상호정보량 (PPMI) $PPMI(x, y) = max(0, PMI(x,y,))$

차원감소(Dimensionality reduction) : 노이즈를 줄이면서 최대한 중요한 정보를 유지하는게 핵심

특잇값분해(Singular Value Decomposition, SVD) : 임의의 행렬 X를 U, S, V라는 세 행렬의 곱으로 분해하며, U, V는 직교행렬, S는 대각행렬입니다.
S 행렬에서 대각 성분에는 특잇값(Singular value)가 큰 순서로 나열되어 있으며, 중요하지 않는 부분을 줄여서 차원 감소를 시행합니다.

Comments