728x90
자연어와 단어의 분산표현¶
- 자연어란 ? 우리가 평소 쓰는 말, Natual Language
- 자언어 처리, Natual Language Processing은 우리의 말을 컴퓨터에게 이해시키기 위한 기술 분야
In [1]:
# !pip install IPython
from IPython.display import Image
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings(action='ignore')
Thesaurus¶
- 유의어 사전, 뜻이 같은 단어 (동의어)
모든 단어의 유의어 집합을 만든 다음, 단어들의 관계를 그래프로 표현하여 단어 사의의 연결의 정의
문제점
- 시대 변화에 대응하기 어렵다
- 사람을 쓰는 비용이 크다
- 단어의 미요한 차이를 표현할 수 없다.
통계 기반 기법¶
- 말뭉치(corpus): 대량의 텍스트 데이터
- 통계 기반 기법의 목표는 사람의 지식으로 가득한 말뭉치에서 자동으로 효율적으로 그 핵심을 추출하는 것
말뭉치 전처리( corpus preprocessing)¶
In [2]:
def preprocess(text):
text = text.lower()
text = text.replace('.',' .') # 단어와 '.'를 분리하기 위해서
words = text.split()# 공백을 기준으로 분리
word_to_id ={} # word에 id 부여
id_to_word ={} #각id에 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 # 말뭉치, word_to_id, id_to_word dict data return
In [3]:
text = 'You say goodbye and I say Hello.'
corpus, word_to_id, id_to_word = preprocess(text)
corpus, word_to_id, id_to_word
Out[3]:
(array([0, 1, 2, 3, 4, 1, 5, 6]), {'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: '.'})
단어의 분산 표현, 분포가설¶
- 분산표현, distributional representation: 단어를 고정길이의 밀집벡터 (dence vector)로 표현
- 분포가설, distributional hypothesis: 단어의 의미는 주변 단어에 의해 형성된다. 즉 단어자체에는 의미가 없고 그 단어가 사용된 맥락(context)이 의미를 형성
ex) "I drink beer", "We drink wine" <=> "I guzzle beer", "We guzzle wine", "drink"와 "guzzle은 같은 맥락에서 사용됨
- 맥락(context): 주목하는 단어(target) 주변에 놓인 단어, 즉 특정 단어를 중심에 둔 주변단어
- window size: 맥락의 크기
- 동시발생 행렬(co-occurence matrix): 각 행에 target단어 가 있고 그 행을 중심으로 맥락열을 구성하는 matrix 즉 각 행은 해당 단어를 표현한 벡터
ex) "You say goodbye and i say hello."에서 say의 맥락에서는 you, goodbye, i, hello가 포함된다. 즉, you say goodbye and i hello . 열에서 => 1, 0, 1, 0, 1, 1, 0으로 나타난다
In [4]:
def create_to_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
In [5]:
C = create_to_matrix(corpus, len(corpus))
C
Out[5]:
array([[0, 1, 0, 0, 0, 0, 0, 0], [1, 0, 1, 0, 1, 1, 0, 0], [0, 1, 0, 1, 0, 0, 0, 0], [0, 0, 1, 0, 1, 0, 0, 0], [0, 1, 0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]])
In [6]:
C[word_to_id['goodbye']]
Out[6]:
array([0, 1, 0, 1, 0, 0, 0, 0])
벡터간 유사도¶
- Cosine similarity
similarity(x, y) = $$ \frac{X\dot Y}{\lVert X \rVert \lVert Y \rVert} =\frac{x_1y_1+...+x_n y_n}{\sqrt{x_1^2+...x_n^2}\sqrt{y_1^2+...y_n^2}}$$
- 분자에 내적, 분모에 L2-norm
- 두 벡터가 가르키는 방향이 얼마나 비슷한가, 두 벡터의 방향이 완전히 같다면 코사인 유사도는 1이 되고, 완전히 반대라면 -1이 됨
In [7]:
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)
In [8]:
c0 = C[word_to_id['you']]
c1 = C[word_to_id['i']]
cos_similarity(c0, c1)
Out[8]:
0.7071067691154799
코사인 유사도는 -1 과 1사이의 값을 가지므로 0.70은 비교적 높은 값이다( 유사성이 크다)
인수가 0인 제로 벡터가 들어오면 0으로 나누게 되는 'divide by zero'오류가 발생하기 때문에 eps을 더해준다
유사 단언 랭킹 표시¶
- 비슷한 단어를 유사도 순으로 출력하는 함수
In [9]:
x= np.array([100, -20, 2])
x.argsort() # 오름차순으로 배열원소 정렬
Out[9]:
array([1, 2, 0], dtype=int64)
In [10]:
(-x).argsort() # 내림차순으로 정렬됨
Out[10]:
array([0, 2, 1], dtype=int64)
In [11]:
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
In [12]:
most_similar('you', word_to_id, id_to_word, C)
[query]you goodbye:0.7071067691154799 i:0.7071067691154799 hello:0.7071067691154799 say:0.0 and:0.0
통계기반 기법 개선¶
상호 정보량¶
- 점별 상호정보량(Pointwise Mutual Information)
$$ PMI(x, y) = log_2 \frac{P(x,y)}{P(x)P(y)}=log_2 \frac{\frac{C(x,y)}{N}}{\frac{P(x)}{N}\frac{P(y)}{N}}=log_2 \frac{C(x,y)N}{C(x)C(y)}$$
- $P(x)$ : x가 일어날 확률
- $P(y)$ : y가 일어날 확률
- $P(x,y)$ : x, y가 동시에 일어날 확률
- PMI가 높을수록 관련성이 높다는 의미
- 말뭉치에 포함된 단어수 N
- C:동시발생 행렬 $C(x, y)$ 는 x, y가 동시에 발생하는 횟수, $C(x)$,$C(y)$는 x,y의 등장 횟수
- ex) N=10,000등장 횟수 : the = 1000, car=20, drive=10, the & car = 10, car & drive =5일때
PMI(the, car) = $log_2\frac{10x10000}{1000x20}\approx 2.32$
PMI(car, drive) = $log_2\frac{5x10000}{20x10}\approx 7.97$- 이런 결과가 나온것은 단어가 단독으로 출연하는 횟수가 고려되었기 때문
- PMI의 문제점
- 두단어의 동시발생 횟수가 0 이면 $log_20=-\inf$
- PMI를 실제로 구현할 때는 양의 상호정보량(PPMI, Positive Pointwise Mutual Information)을 사용
$$PPMI(x,y) = max(0,PMI(x, y))$$
- PMI가 음수일때 0으로 취급
- PPMI문제점
- 말뭉치의 어휘수가 증가함에 따라 각 단어 벡터의 차원 수도 증가
- 행렬의 원소 대부분이 0인 희소행렬(sparce matrix) 즉 각 원소의 중요도가 낮음 -> 해결책으로 나온 기법이 백터의 차원감소
In [13]:
def ppmi(C, verbose=False, eps=1e-8):
"""
C:동시발행 행렬
verbose: 진행상활 출력여부 flag
eps: 0으로 나누어 에러가 발생하지 않도록
"""
M = np.zeros_like(C, dtype=np.float32)
N = np.sum(C)
S = np.sum(C, axis=0)
total = C.shape[0] * C.shape[1]
cnt = 0
for i in range(C.shape[0]):
for j in range(C.shape[1]):
pmi = np.log2(C[i, j] * N /(S[j]* S[i])+ eps)
M[i, j] = max(0, pmi)
if verbose:
cnt+=1
if cnt%(total//100+1) ==0:
print('%.f%%완료'%(100*(cnt/total)))
return M
In [14]:
W = ppmi(C)
np.set_printoptions(precision=3) #유효자릿수를 세자리로 표시
print('동시발생행렬')
print(C)
print('-'*50)
print('PPMI')
print(W)
동시발생행렬 [[0 1 0 0 0 0 0 0] [1 0 1 0 1 1 0 0] [0 1 0 1 0 0 0 0] [0 0 1 0 1 0 0 0] [0 1 0 1 0 0 0 0] [0 1 0 0 0 0 1 0] [0 0 0 0 0 1 0 0] [0 0 0 0 0 0 0 0]] -------------------------------------------------- PPMI [[0. 1.807 0. 0. 0. 0. 0. 0. ] [1.807 0. 0.807 0. 0.807 0.807 0. 0. ] [0. 0.807 0. 1.807 0. 0. 0. 0. ] [0. 0. 1.807 0. 1.807 0. 0. 0. ] [0. 0.807 0. 1.807 0. 0. 0. 0. ] [0. 0.807 0. 0. 0. 0. 2.807 0. ] [0. 0. 0. 0. 0. 2.807 0. 0. ] [0. 0. 0. 0. 0. 0. 0. 0. ]]
차원감소( Dimension Reduction)¶
- 벡터의 차원을 줄이는 방법
- 중요한 정보는 최대한 유지하면서 줄이는게 핵심
- 특잇값분해(Singular Value Decomposition)
$X = USV^T$
- U, V: 직교행렬(Orthogonal matix)
- S: 대각행렬(Diagonal Matrix), 특잇값이 큰 순서로 나열 (특잇값-> 해당 축의 중요도)
- U: 단어공간
In [15]:
# SVD
U, S, V = np.linalg.svd(W)
print(C[0]) # 동시발생행렬
print(W[0]) # PPMI행렬
print(U[0]) #SVD
print(U[0,:2]) #2차원 벡터
[0 1 0 0 0 0 0 0] [0. 1.807 0. 0. 0. 0. 0. 0. ] [ 3.409e-01 -1.110e-16 -3.886e-16 -1.205e-01 -9.323e-01 0.000e+00 0.000e+00 1.958e-17] [ 3.409e-01 -1.110e-16]
In [16]:
for word, word_id in word_to_id.items():
plt.annotate(word, (U[word_id,0], U[word_id, 1]))
plt.scatter(U[:,0], U[:,1], alpha=0.5)
plt.show()
Summary
- WordNet등 Thesaurus를 이용하면 유의어를 얻거나 단어사이의 유사도를 측정하는 등 유용한 작업을 할수 있다.
- Thesaurus기법의 문제는 작성하는데 엄청난 인적 자원이 든다거나 새로운 단어에 대응하기 어렵다는 문제가 있다.
- 현재는 말뭉치를 이용해 단어를 벡터화하는 방식이 주로 쓰인다
- 최근 단어 벡터화 기법들은 대부분 단어 의미는 주변 단어에 의해 형성되다는 분포가설에 기초한다.
- 통계기반 기법은 말뭉치 안의 각 단어에 대해서 그 단어의 주변 단어의 빈도를 집계한다 (동시발생행렬)
- 동시발생행렬을 PPMI행렬로 변환하고 다시 차원을 감소시킴으로 거대한 희소벡터를 작은 밀집벡터로 변환 할수 있다.
- 단어 벡터 공간에서는 의미가 가까운 단어는 그 거리도 가까울 것으로 기대된다.
728x90
'Data Science > Deep Learning' 카테고리의 다른 글
[Deep Learning from Scratch 2] chapter 4. word2vec 속도개선 (0) | 2023.04.26 |
---|---|
[Deep Learning from Scratch 2] chapter 3. word2vec (0) | 2023.04.26 |
[Deep Learning from Scratch] chapter 8-4. 딥러닝 NIC, DCGAN (0) | 2023.04.25 |
[Deep Learning from Scratch] chapter 8-3. 딥러닝 FCN (0) | 2023.04.25 |
[Deep Learning from Scratch] chapter 8-2. 딥러닝 R-CNN (0) | 2023.04.25 |