일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- rnn
- 기초통계
- 최소자승법
- 밑바닥부터 시작하는 딥러닝
- 히스토그램
- 감성분석
- numpy
- 자연어 처리
- 가설검정
- F분포
- 파이썬 pandas
- 텍스트 분류
- student t분포
- 오래간만에 글쓰네
- 결정계수
- 모두의 딥러닝
- 차원축소
- Django
- 밑바닥부터 시작하는 딥러닝2
- 코사인 유사도
- 머신러닝
- 텐서플로2와 머신러닝으로 시작하는 자연어처리
- 기술통계학
- 구글 BERT의 정석
- Pandas
- 군집화
- 회귀분석
- word2vec
- 은준아 화이팅
- 다층 퍼셉트론
- Today
- Total
데이터 한 그릇
자연어와 단어의 분산 표현 본문
자연어 처리란?
한국어와 영어 등 우리가 사용하는 언어를 자연어(Natural Language) 라고 부름.
자연어 처리 : Natural Language Proccessing(NLP)
단어의 의미
문장안에 여러 단어들이 포함되어 있음.
단어는 의미의 최소 단위.
(이번장)
***"컴퓨터에게 단어의 의미 이해시키기" , 다른 말로 정확히 말하면 "컴퓨터가 단어의 의미를 잘 파악하는 표현 방법"***
구체적인 이번장과 다음장에서 알아볼 기술
(컴퓨터에게 단어의 의미를 이해시키는 방법 3가지)
- 시소러스를 활용한 기법(이번)
- 통계 기반 기법 (이번)
- 추론 기반 기법(word2vec) (다음)
시소러스
시소러스 방법(표현)을 이용해서 자연어 단어를 컴퓨터가 알아들을 수 있게 표현.
- 시소러스
- 유의어 사전
- 기본적으로 뜻이 같은 단어나, 유사한 단어들이 하나의 그룹으로 분류됨.
- 위계를 가지는 시소러스
- object > motor vehicle >(car, go-kart, truck) -> car에서 분파되는 가지 (SUV, compact, hatch-back)
모든 단어에 대해서 유의어 집합을 만들고, 단어들의 관계를 그래프로 표현하여 단어 사이의 연결을 정의할 수 있다.
즉, 단어 네트워크(그래프) 를 통해서 단어간의 연결과 관계를 정의 내릴 수 있다.
이를 컴퓨터에게 가르치면 단어의 의미를 이해시켰다고 할 수 있다.
wordNet
자연어 처리에서 가장 유명한 시소러스는 wordNet.
wordNet을 사용하면 유의어를 얻거나, 단어 네트워크를 이용해 단어간 유사도를 구할 수 있다.
시소러스의 문제점
- 시대 변화에 따라서 단어의 의미가 새로 생기거나 변화한다.
- 영어의 heavy 는 과거에 무겁다라는 의미만 있었고 현재는 어렵다라는 의미를 표현하기도 한다.
- 사람을 쓰는 비용이 크다.
- 단어의 미묘한 차이를 표현할 수 없다.
- 빈티지와 레트로는 의미는 동일하지만 미묘한 표현 차이가 있다. 시소러스는 이를 표현할 수 없다.
시소러스의 이러한 문제점들을 해결하기 위해서 '통계 기반 기법'과 '신경망을 사용한 추론 기반 기법'을 알아볼 것.
- 시소러스의 문제점을 타파하기 위한 두 가지 방법
- 통계 기반 기법
- 추론 기반 기법(신경망 이용)
이 두 기법은 대량의 텍스트 데이터로부터 "단어의 의미" 를 자동으로 추출한다.
이 덕에 사람이 순수 레이블링 하는 중노동에서 해방.(시소러스 해방)
통계 기반 기법
말뭉치(Corpus) 사용
- 자연어 처리를 목적으로 수집한 대량의 텍스트 데이터
- 혹은 문서들의 집합이라고 표현
통계 기반 방법은 말뭉치로부터 자연어에 대한 지식을 자동으로 추출한다.
(말뭉치는 사람들이 적은 데이터이기 때문에, 자연어에 대한 정보가 자동으로 많이 담겨있다.)
*말뭉치 ->(추출) -> 자연어 지식 ※자동화*
파이썬으로 말뭉치 전처리하기
자연어 기본적인 전처리 : 텍스트 데이터에서 단어들을 추출하고, 그 단어들 하나하나에 ID를 부여하고 그 값들을 LIST로 변환하는 작업
*Text -> 단어1,단어2,단어3.... -> 단어1_id, 단어2_id, 단어3_id,..... -> [단어2_id, 단어3_id,.....]*
#소문자로 전환
text = 'You say goodbye and I say hello'
text = text.lower()
#단어 단위로 나누기
words = text.split(' ')
words
단어 단위로 나누어져서 다루기 쉬워진 것은 맞지만 있는 그대로 사용하기란 불편하다.
따라서 각 단어에 ID 를 부여한다.
그리고 ID의 리스트로 이용할 수 있도록 리스트에 그 값들을 담아준다.
#단어에 ID 붙이기
#각 단어에 id를 붙이고
#id가 value고 단어가 key 값인 딕셔너리를 만들고
#id가 key고 단어가 value인 딕셔너리를 만든다.
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
#word to id 딕셔너리를 array로 전환 (key : word, value는 id인 딕셔너리임)
import numpy as np
corpus = [word_to_id[w] for w in words]
corpus = np.array(corpus)
corpus
위의 과정을 한 번에 볼 수 있게 함수화 해보겠다.
#text lower
#text split으로 단어만 추출
#단어 하나하나에 id부여하기
#부여한 단어id들을 배열로 담아두기
def preprocessing(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 = [word_to_id[word] for word in words]
corpus = np.array(corpus)
return corpus, word_to_id, id_to_word
이 함수를 이용해서 한 번에 return 값 받기
text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocessing(text)
print(corpus)
print(word_to_id)
print(id_to_word)
분포 가설
단어를 벡터화 시키려는 노력은 계속 있어왔다.
중요한 기법의 거의 모두가 단 하나의 `간단한 아이디어` 에 뿌리를 두고 있다.
단어의 의미는 주변 단어에 의해 형성된다.
이를 `분포 가설(distribution hyphothesis)` 라고 부른다.
단어 자체에는 의미가 없고, 그 단어가 사용된 맥락(context)이 의미를 형성한다.
다시 의미를 이해하기 위해서 타이핑하면서 적어보면
단어 그 자체에는 의미가 없으며, 단어가 사용이된 맥락이 단어의 의미를 부여한다.
물론 의미가 같은 단어들은 같은 맥락에서 더 많이 등장.(선 맥락, x에 그 맥락에 따라 같은 단어 똑같이 더 많이 등장)
ex) I drink coffee, we drink beer
drink 가 사용이 됐는데, drink 단어 주변에는 음료가 등장하기 쉽다.
즉, x 빈칸이 있고 그 주변에 액체가 있으면 drink 가 등장할 가능성이 커진다.
그리고
ex) we guzzle beer, we guzzle wine
똑같은 맥락에서 drink 말고 guzzle 이 사용이 됐는데, 이럴 시 우리는 guzzle 과 drink 가 비슷한 단어라는 걸 알 수 있다.
즉, 주변 맥락에 따라서 어떤 단어가 나올 수 있는데, 똑같은 맥락에서 어떤 단어들이복수 사용 가능하면, 그 단어들 끼리는 유사성이 있음을 알 수 있다.
같은 맥락에서 사용된 단어들은 서로 유사성이 있다.
앞으로 이 책에서는 맥락 이라고 하면 주변 단어들을 일컫는다.
다시, 맥락이란 특정 단어를 중심으로 둔 주변 단어들.
동시발생 행렬
이런 분포 가설을 기초해서 단어를 벡터화 하는 방법을 생각해보자.
(분포가설 : 맥락에 따라서 단어의 의미가 정해진다.)
어떤 단어에 집중했을 때, 그 주변에 어떤 단어가 몇 번이나 등장하는지 세어 집계하는 방법
집계해서 행렬을 만들면 이를 `co-occurrence-matrix` 라고 칭한다.
"You say goodbye and i say hello"
가 있다고 할 때,
You 주변 단어는 say 하나 뿐. 그리고 1 빈도를 기록
say 주변 단어는 you, goodbye, I, hello 이고 you : 1, goodbye : 1, I : 1, hello : 1 빈도 기록
#동시행렬 만드는 자동화 함수
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 = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10),
y = (y1,y2,y3,y4,y5,y6,y7,y8,y9,y10) 가 있다고 가정했을 때, `similarity(x,y) =` 공식은 다음과 같다.
두 벡터의 내적이 분자가 되고(각 원소 위치에 맞게 곱하고 그 값들을 더하기)
각 벡터 별로 원소 각각에 제곱을 하고 더한 이후에 루트를 씌우고 그 값들을 곱한 값이 분모가 된다.
이를 노름(norm) 이라고 부른다. 노름은 벡터의 크기를 나타낸다. 여기선 L2 노름을 계산한다.
L2노름(벡터의 크기) : 벡터의 각 원소를 제곱하고 더한 후 제곱근을 구한 값
결국, (벡터의 내적) / (x벡터의 크기)(y벡터의 크기)
코사인 유사도를 직관적으로 풀면, 두 벡터가 향하는 방향이 얼마나 비슷한가다.
두 벡터의 방향이 완전히 같다면 코사인 유사도가 1이 되며 완전히 반대라면 -1
import numpy as np
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)
text = "You say goodbye and I say hello"
corpus, word_to_id, id_to_word = preprocessing(text)
print(corpus)
print(word_to_id)
print(id_to_word
)
vocab_size = len(word_to_id)
print(vocab_size)
C = create_co_matrix(corpus, vocab_size)
print(C[0])
print(C[1])
c_you = C[word_to_id['you']]
c_i = C[word_to_id['i']]
print(cos_similarity(c_you,c_i))
상위 5개의 유사도가 높은 단어 추출
#query : 검색단어
#word_matrix : 각 행에는 대응하는 단어들의 벡터들이 저장되어 있다고 가정
#top : 상위 몇 개까지 출력할지 설정
def most_similar(query, word_to_id, id_to_word, word_matrix, top=5):
if query not in word_to_id:
print("%s(을)를 찾을 수 없습니다.")
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) #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
통계 기반 기법 개선하기
앞장에서는, 단어간 동시발생빈도를 이용해서 동시발생행렬을 만들었다.
이를 통해서 단어를 벡터화 하는 건 성공했다.
상호정보량
두 단어가 동시에 발생한 횟수를 기반으로 단어를 벡터화 하는 건 사실 그리 좋은 기반이 아니다.
the car 라고 많이 표현되기 때문에 the, car 는 관련성이 높다고 나올 것.
car 와 drive 는 확실히 연관이 깊을 것, 하지만 the 와 car가 더 관련성이 높다고 나올 것.
왜냐하면 단순 등장 횟수만을 고려했기 때문에
**즉, car 와 drive가 훨씬 더 관련이 깊어보이지만, the 와 car 가 동시에 등장한 횟수가 더 많기 때문에 더 관련이 깊은 것으로 컴퓨터가 판별할 것**
`점별 상호정보량(Pointwise Mutal Information)` 척도를 사용할 것. (일명, PMI)
확률 변수 x와 y에 대해 다음 식으로 정의.
`PMI(x,y) = log(P(x,y) / P(x) * P(y))`
PMI 값이 높을수록 관련성이 높음을 의미한다. => x, y의 관련성이 높음을 의미한다.
만일 10000개의 단어로 이루어진 corpus 에서 단어 "the" 가 나온 횟수가 100일 때, P(x) = 100 / 10000,
"car" 와 "the" 가 동시에 나온 횟수가 10이면 P(x,y) = 10 / 10000 이다.
동시발생행렬을 기준으로 위의 식을 재바꿈한다면,
CORPUS 내에 출현한 단어의 개수 = N
P(x,y) = C(x,y) / N
P(x) = C(x) / N * C(y) / N
`log2( C(x,y) / N / C(x) / N * C(y) / N)` 가 `log2(C(x,y) * N) / C(x) * C(y)` 가 된다. (동시행렬을 기준으로 PMI 계산)
구체적인 수치 계산하기
예)
N = 10000,
(the, car, drive) = 1000,20,10,
C(the, car) = 10,
C(car, drive) = 5
(the, car) pmi 지수 :
`log2(10 * 10000 / 1000 * 20) = log2(100000 / 20000) ~~ 2.32`
2.32 값 도출된다.
(car, drive) pmi 지수 :
7.97
PMI 지수를 이용하면 car와 drive 가 더 유사성이 깊은걸로 도출된다.
본래 동시발생을 기준으로 관련성을 비교하면 the와 car 의 동시발생 빈도가 car, drive 보다 더 많기 때문에 더 관련성이 높다고 판정하지만, PMI 지수를 이용하면 car 와 drive 가 관련성이 더 깊음을 잘 보여준다.
PMI 도 issue 발생:
동시발생 횟수가 0이면 마이너스 무한대로 향한다는 것.
타파하기 위해서 **양의 상호정보량** 사용.(상호정보량 -> 양의 상호정보량)
`PPMI(x,y) = max(0,PMI(x,y))`
만일 PMI 지수가 나왔는데 음수가 나오면 0으로 취급한다.
이제 두 단어 사이의 관련성을 0이상의 실수로 나타낼 수 있게 됐다.(PPMI 사용)
PPMI 행렬을 만들어보자 (x, y 단어의 관련성 수치로 채운)
#C = 동시발생행렬
#verbose 진행상활 출력 여부
#log2(0) 이 음의 무한대로 가는 걸 방지하기 위해서 eps
def ppmi(C , verbose = False, eps = 1e-8):
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("%.1f%% 완료" % (100 * cnt/total))
return M
text = 'You say goodbye and I say hello'
corpus, word_to_id, id_to_word = preprocessing(text)
vocab_size = len(word_to_id)
C = create_co_matrix(corpus, vocab_size)
W = ppmi(C)
np.set_printoptions(precision = 3)
print('동시발생 행렬')
print(C)
print('-' * 50)
print(W)
차원 감소
PPMI 행렬의 문제
동시발생행렬의 형식을 이용해서 PPMI를 만들었는데, 문제점은 공간 낭비가 심하다는 점.
예를 들어서 어떤 CORPUS 가 단어가 10만개라고 했을 때 10만 * 10만의 행렬이 만들어지는데 이를 분석하기란 현실적이지 않다.
또한, 0 값이 많은데, 이 또한 공간낭비의 주범. 대부분의 원소가 중요하지 않음을 의미한다.
이런 issue 를 해결하기 위한 방법이 차원 감소 (decompositionality reduction).
문자 그대로 벡터의 차원을 줄이는 방법. 단순히 줄이는 게 아니라 중요한 정보를 유지한 상태에서 차원을 축소한다.
ex) 이차원 좌표 x,y 에 점이 찍혀있다고 가정, ax 방향으로 plot 들이 찍혀있을 때, 1차원 직선으로 좌표에 그림을 그려서 예측할 수 있다.
특잇값분해
차원을 감소하는 방식 중 특잇값분해(singular value decomposition, SVD) 사용.
특잇값분해는 임의의 행렬을 세 개의 행렬 곱으로 분해한다.
`X = USV^t`
이 식을 통해서 U,S,V 행렬들로 X행렬을 분해한 걸 알 수 있다.
여기서 U와 V는 `직교행렬` 이고, 그 열벡터는 서로 직교한다.
그리고 S는 `대각행렬`.
U : 직교행렬
S : 대각행렬
V : 직교행렬
대각행렬의 대각 값들은 특잇값, 이 특잇값은 해당 축의 중요도를 의미한다.
대각행렬의 대각 특잇값을 이용해서 본래 X 행렬이 차원 축소가 된다.
text = 'You say goodbye and I say hello'
corpus, word_to_id, id_to_word = preprocessing(text)
vocab_size = len(id_to_word)
C =create_co_matrix(corpus, vocab_size, window_size = 1)
w = ppmi(C)
U,S,V = np.linalg.svd(w)
print('차원축소 전 ppmi 행렬은 여전히 희소행렬 : ',w[0])
print('차원축소 후 행렬을 분해하고 U 의 벡터를 확인하면 밀집행렬 : ',U[0])
print(U[:,0:2])
정리
지금까지 자연어 처리 기초에 대해서 살펴봤다.
자연어 처리란, 우리의 자연어를 컴퓨터가 이해시킬 수 있게 하는 과정을 의미한다.
이번 장에서는 컴퓨터에게 단어의 의미를 이해할 수 있게 단어를 표현하는 방식에 대해서 살펴봤다.
먼저 시소러스 방식이 있는데, 이는 단어의 유사어를 위계를 지어서 나타내어 컴퓨터에게 그 의미를 전달하는 방식이다. 시소러스 방식은 수작업이라는 점에서 문제점을 가지고 있다.
따라서 그 대안으로 통계 분석 기법이 사용되는데, 이 기법의 기본적인 원리는 하나의 단어의 의미는 그 자체로 형성되지 않고 그 주변 맥락에 의해서 형성이 된다는 것이다.
따라서 CORPUS 가 들어오면 그 CORPUS를 단어별로 나누고 동시행렬 C를 만든다.
동시행렬 C를 만들게 되면 단어를 동시발생 빈도기반의 벡터로 표현할 수 있다.
그렇다면 단어간 유사도를 계산하기 위해서 코사인 유사도를 사용할 수 있다.
코사인 유사도는 두 벡터간의 유사도를 측정하는 방식인데, 벡터의 방향이 같은지 다른지에 따라서 그 값이 변화한다.
그런데 문제가 발생한다. 만일 동시발생빈도기반으로 행렬을 만들고 두 단어의 관련성을 따지면, THE, CAR, DRIVE 셋 단어 중에서 THE와 CAR가 더 관련이 깊은 것으로 컴퓨터가 이해한다. 이는 오직 빈도를 기준으로만 따졌기 때문에 나타난 현상이다.
따라서 그 대안으로 PMI 기법이 나왔고, PMI 기법이 0 미만의 값에 대해서 -무한의 값을 기록하기 때문에 PPMI 기법이 나왔다.
상호정보량을 기반으로한 PPMI 행렬의 문제점은 희소행렬이라는 점과 차원이 너무 클 수 있다는 점이다.
따라서 이러한 문제를 해결하기 위해,차원축소를 하여 차원을 줄이게 된다.
`동시발생 행렬 -> PPMI -> 차원축소`
'NLP > 밑바닥부터 시작하는 딥러닝' 카테고리의 다른 글
어텐션(Attention) (0) | 2022.01.25 |
---|---|
게이트가 추가된 RNN (0) | 2022.01.21 |
순환신경망(RNN) (0) | 2022.01.20 |
word2vec 개선 (4) | 2022.01.19 |
word2vec (0) | 2022.01.18 |