모두야
CH2) 자연어와 단어의 분산 표현 - (1) 본문
자연어 처리 -> 컴퓨터가 우리가 하는 말을 알아듣게(이해하게) 만드는 것이다.
- 고전적인 기법 (딥러닝 이전)
- 딥러닝 기법 (신경망)
- 파이썬으로 텍스트를 다루는 연습 - 텍스트를 단어로 분할하는 처리, 단어를 단어 ID로 변환하는 처리
앞으로 텍스트 처리를 위한 사전 준비!!!
자연어 (Natural Language)
: 한국어, 영어 등 평소에 우리가 쓰는 말
자연어 처리(Natural Language Processing)
: 자연어를 처리하는 분야
: 우리의 말을 컴퓨터에게 이해 시키기 위한 기술
: 사람의 말을 컴퓨터가 이해하도록 만들어서, 컴퓨터가 우리에게 도움이 되는 일을 수행하도록 하는 것
컴퓨터 이해 (프로그래밍 언어 : 딱딱한 언어)
평소 사용하는 말(자연어 : 부드러운 언어- 똑같은 의미의 문장을 여러 형태로 표현하고, 문장의 뜻이 애매할 땐 의미나 형태가 유연하게 바뀌는 것)
자연어 처리 기술 예시
번역, 질의응답 시스템, IME(입력기 전환), 문장 자동 요약, 감정 분석 등
말(문자로 구성)
말의 의미(단어로 구성) -> 단어는 의미의 최소 단위
=> 컴퓨터에게 "단어의 의미"를 이해시키는 것이 중요하다.
1. 시소러스 기법 : 유의어 사전
2. 통계 기반 기법
3. 추론 기반 기법(word2vec)
사람이 직접 단어의 의미를 정의하는 방식 -> [표준국어대사전] 처럼 단어를 정의하자.
1. 시소러스 기법
: 유의어 사전
뜻이 같은 단어(동의어) / 뜻이 비슷한 단어(유의어)
> WordNet
자연어 처리 분야에서 가장 유명한 시소러스이다.
WordNet을 사용하면 유의어를 얻거나 '단어 네트워크'를 이용할 수 있다. 단어 사이의 유사도를 구할 수 있다.
부록B 참고
문제점
- 시대 변화에 대응하기 어렵다.
: 새로운 단어가 생기고, 사라짐을 사람이 수작업으로 끊임없이 갱신하기 어렵다.
- 사람 쓰는 비용이 크다.
: 시소러스는 만드는 인적 비용이 엄청나다. 단어 사이의 관계를 정의해주어야 한다.
- 단어의 미묘한 차이를 알 수 없다.
: 비슷한 단어라고 미묘한 차이가 있다. (빈티지 & 레트로)
2. 통계 기반 기법
말뭉치(corpus) : 대량의 텍스트 데이터
> 사람의 지식이 가득한 말뭉치에서 자동으로, 효율적으로 핵심을 추출한다.
사람의 지식이 가득하다
: 문장 쓰는 법, 단어를 선택하는 법, 단어의 의미 등 사람이 알고 있는 지식이 포함 됨
# 말뭉치 전처리 함수
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])# 단어 ID, 넘파이 배열로 변환
return corpus, word_to_id, id_to_word
단어의 분산 표현 (distributional representation)
색을 RGB 값의 벡터로 표현하듯, 단어도 벡터로 표현하면 단어의 의미를 더 정확하게 파악할 수 있다.
분포 가설 (distrivutional hypothesis)
단어의 의미는 주변 단어에 의해 형성된다.
= 단어가 사용된 맥락(context)가 의미를 형성한다.
I drink beer.
We drink wine.
I guzzle beer.
drink 뒤에는 음료.
guzzle(폭음하다)= drink
맥락 : 단어 주변에 놓인 단어
윈도우 크기 (window size) : 주변 단어를 몇 개나 포함 할 것인지
통계 기반 기법 (statistical based)
어떤 단어를 주목했을 때, 그 주변에 어떤 단어가 몇 번이나 등장하는지 세어 집계하는 방법
you | say | goodbye | and | i | hello | . | |
you | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
say | 1 | 0 | 1 | 0 | 1 | 1 | 0 |
goodbye | 0 | 1 | 0 | 1 | 0 | 0 | 0 |
and | 0 | 0 | 1 | 0 | 1 | 0 | 0 |
i | 0 | 1 | 0 | 1 | 0 | 0 | 0 |
hello | 0 | 1 | 0 | 0 | 0 | 0 | 1 |
. | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
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) # 0으로 채워진 2차원 배열로 초기화
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 # 행렬에 1추가
if right_idx < corpus_size: #오른쪽 윈도우
right_word_id = corpus[right_idx]
co_matrix[word_id,right_word_id] += 1 # 행렬에 1추가
return co_matrix #행렬
벡터 사이의 유사도
- 벡터의 내적
- 유클리드 거리
- 코사인 유사도 (cosine similarity) : 두 벡터가 가리키는 방향이 얼마나 비슷한가
: 방향이 같으면 1, 완전히 반대면 -1
= 벡터의 내적 / 벡터의 노름
= 노름 : 벡터의 크기를 나타낸 것
=> L2 노름 : 벡터의 각 원소를 제곱해 더한 후 다시 제곱근을 구하여 계산한다.
: 벡터를 정규화하고, 내적을 구하는 것이다. -> 각도가 중요!
# 코사인 유사도
def cos_similarity(x,y,eps=1e-8):
nx = x/np.sqrt(np.sum(x**2)+eps) # x의 정규화
ny = y/np.sqrt(np.sum(y**2)+eps) # y의 정규화
return np.dot(nx,ny)
# 단어 벡터 유사도 구하기
# you와 i의 유사도 구하기
text2 = "You say goodbye and I say hello."
corpus, word_to_id, id_to_word = preprocess(text2)
vocab_size = len(word_to_id)
C = create_co_matrix(corpus,vocab_size)
c0 = C[word_to_id['you']] # 'you'의 단어 벡터
c1 = C[word_to_id['i']] # 'i'의 단어 벡터
print(cos_similarity(c0,c1))
>> 0.7071067758832467 # 유사성이 높다
유사 단어 랭킹 표시
: 검색어 입력 후 그 검색어와 비슷한 단어를 유사도 순으로 출력한다.
- 검색어의 단어 벡터를 꺼낸다.
- 검색어의 단어 벡터와 다른 모든 단어 벡터와의 코사인 유사도를 각각 구한다.
- 계산한 코사인 유사도 결과를 기준으로 값이 높은 순서대로 출력한다.
def most_similar(query,word_to_id,id_to_word,word_matrix,top=5):
# 1. 검색어를 꺼낸다.
if query not in word_to_id:
print('%s(을)를 찾을 수 없습니다.' %query)
return
print('[검색어]:'+query +'\n')
query_id = word_to_id[query] # 검색 단어의 id 지정
query_vec = word_matrix[query_id] # 유사도 행렬의 id의 행
# 2. 코사인 유사도 계산
vocab_size = len(id_to_word)
similarity = np.zeros(vocab_size) # 0벡터 초기화
for i in range(vocab_size):
similarity[i] = cos_similarity(word_matrix[i],query_vec) # 각 단어와의 코사인 유사도 계산
# 3. 코사인 유사도를 기준으로 내림차순으로 출력
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
# 검색어 you와 유사도가 높은 순서
text2 = "You say goodbye and I say hello."
corpus, word_to_id, id_to_word = preprocess(text2)
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)
#결과
[검색어:]you
goodbye: 0.7071067758832467
i: 0.7071067758832467
hello: 0.7071067758832467
say: 0.0
and: 0.0
통계 기반 기법 개선하기
문제점
동시발생 행렬 : 두 단어가 동시엥 발생한 횟수 = 고빈도 단어에서 문제가 생긴다!
ex) the car >> car drive : car 이 drive보다 the와 더 많이 붙어 있는 경우가 많아 관련성이 강하다고 나타난다.
점별 상호 정보량 (PMI : Pointwise Mutual Information)
P(x) : x가 일어날 확률
P(y) : y가 일어날 확률
P(x,y) : x와 y가 동시에 일어날 확률
=> PMI 값이 높을수록 관련성이 높다.
동시발생 행렬 (행렬의 원소를 동시발생한 단어의 횟수) 를 이용하여 PMI를 완성 시켜보자.
C : 동시발생 행렬
C(x,y) : 단어 x와 y가 동시발생 하는 횟수
C(x) : 단어 x의 등장 횟수
C(y) : 단어 y의 등장 횟수
N : 말뭉치에 포함된 단어 수
Ex)
'the'는 1000번, 'car'은 20번, 'drive'는 10번 등장하였을 때,
1) 'the'와 'car'의 동시발생 수가 10회라면 PMI 결과는 다음과 같다.
2) 'car'와 'drive'의 동시발생 수가 5라면 PMI 결과는 다음과 같다.
동시발생 횟수로만 보면 the와 car의 관계가 더 높지만, PMI 기준으로는 car과 drive의 관련성이 더 높다.
=> 단어가 단독으로 출현하는 횟수가 고려 되었기 때문이다.
-> the는 단독으로도 나타나는 경우가 많음!!(흔하다..)
동시발생 횟수가 0일 경우 log2(0)이 되므로 양의 상호정보량 (PPMI : Positive PMI) 를 이용한다.
PMI가 음수일 때는 0으로 취급 된다.
def ppmi(C,verbose=False, eps=1e-8): #동시발생 행렬
#verbose=진행사항 출력 여부 플래그 - 큰 말뭉치에서 True하면 중간중간 진행 상황 알려줌
M = np.zeros_like(C,dtype=np.float32) #행렬
N = np.sum(C) # 총 단어수
S = np.sum(C,axis=0) # C 동시발생행렬
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) #pmi
M[i,j] = max(0,pmi) #ppmi
if verbose:
cnt +=1
if cnt%(total//100)==0:
print('%.1f%% 완료' % (100*cnt/total))
return M
text2 = "You say goodbye and I say hello."
corpus, word_to_id, id_to_word = preprocess(text2)
vocab_size = len(word_to_id)
C = create_co_matrix(corpus,vocab_size)
W = ppmi(C) # return M
np.set_printoptions(precision=3) # 유효 자릿수 3자리 표시
print("동시발생 행렬")
print(C)
print('-'*50)
print("PPMI")
print(W)
동시발생 행렬
[[0 1 0 0 0 0 0]
[0 0 1 0 1 1 0]
[0 1 0 1 0 0 0]
[0 0 1 0 1 0 0]
[0 1 0 1 0 0 0]
[0 1 0 0 0 0 1]
[0 0 0 0 0 1 0]]
--------------------------------------------------
PPMI
[[0. inf 0. 0. 0. 0. 0. ]
[0. 0. 0.7 0. 0.7 0.7 0. ]
[0. 0.7 0. 1.7 0. 0. 0. ]
[0. 0. 1.7 0. 1.7 0. 0. ]
[0. 0.7 0. 1.7 0. 0. 0. ]
[0. 0.7 0. 0. 0. 0. 2.7]
[0. 0. 0. 0. 0. 2.7 0. ]]
PPMI 문제점
말뭉치 어휘 수가 증가하면, 각 단어 벡터의 차원 수가 증가한다.
말뭉치 어휘 수가 10만 개이면, 10만 차원의 벡터를 다뤄야한다ㅠㅠ
행렬을 살펴보면 대부분이 0이고, 각 원소의 중요도가 낮다. 이러한 경우 노이즈가 약하고 견고하지 못하다.
=> 벡터의 차원 감소가 필요하다.
차원 감소 (demensionality reduction)
중요한 정보는 유지하며 벡터의 차원을 줄인다.
=> 데이터 분포를 고려하여 중요한 '축'을 찾는다.
희소행렬(희소벡터) : 원소 대부분이 0인 행렬(벡터)
-> 밀집벡터 : 중요한 축을 찾아내어 더 작은 차원으로 다시 표현하여 원소 대부분이 0이 아니다.
차원 감소 방법
특잇값 분해 (SVD : Singular Value Decomposition)
행렬을 3개의 행렬 곱으로 분해한다.
U와 V는 직교행렬, U(T) = V(T) 이다. S는 대각행렬(대각 성분 외에는 모두 0인 행렬)이다.
행렬 S에서 특잇값이 작다면 중요도가 낮다. 단어공간 직교행렬인 U에서 차원이 감소 된 단어 벡터를 만들 수 있다.
?????
코드 오류..
PTB 데이터셋
텍스트 파일로 된 PTB 말뭉치는 한 문장이 하나의 줄로 저장되어 있다.
<unk>,<eos>
'밑.시.딥 > 2권' 카테고리의 다른 글
CH3) word2vec - (2) (0) | 2021.09.20 |
---|---|
CH3) word2vec - 간단ver (1) (0) | 2021.09.19 |
CH1) 신경망 복습 -(3) 신경망의 학습(역전파) 계층 구현 (0) | 2021.08.27 |
CH1) 신경망 복습 -(2) 신경망의 학습(역전파) (0) | 2021.08.24 |
CH1) 신경망 복습 -(1) 신경망의 추론(순전파) (0) | 2021.08.20 |