일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
29 | 30 | 31 |
- deep learning
- Charlse Severance
- dl
- convolution
- docker
- f1-score
- excel
- nn.Module
- 1x1 Convolution
- Multi-Layer Perceptron
- Python
- 선형대수
- Linear algebra
- skip-gram
- 분포 가설
- 상호 정보량
- Long Short Term Memory
- GoogLeNet
- pythonForEverybody
- pytorch
- CBOW
- Gated Recurrent Unit
- 차원 감소
- py4e
- 벡터 간 유사도
- 동시발생 행렬
- object detaction
- 파이썬
- 시소러스
- mlops
- Today
- Total
Tech & TIL
[NLP] 단어의 의미를 학습시키는 방법 본문
"밑바닥부터 시작하는 딥러닝 2"를 기반으로 정리한 내용입니다.
자연어를 컴퓨터에게 이해시키려면 "단어의 의미"를 이해시켜야 한다. 지금부터 컴퓨터에게 단어의 의미에 대해 학습시킬 수 있는 3가지 기법에 대해 알아보자.
- 시소러스를 활용한 기법
- 통계 기반 기법
- 추론 기반 기법(Word2Vec)
시소러스
시소러스(thesaurus)는 쉽게 말해 유의어 사전이다. 사람이 직접 단어의 의미를 정의하는 방식을 생각해볼 수 있다. 아래 그림을 보면 이해가 쉬울 것이다.
검색 엔진을 예로 들어, "automobile"과 "car"가 유의어임을 알고 있으면 "car"의 검색 결과에 "automobile"의 검색 결과도 포함시켜주는 것과 같은 기법이다. 그렇다면 시소러스의 문제점은 무엇이 있을까?
시소러스는 수많은 단어에 대한 동의어와 계층 구조 등의 관계가 정의돼 있다. 이 지식을 이용하면 '단어의 의미'를 간접적으로라도 컴퓨터에 전달할 수 있다. 하지만 이처럼 사람이 수작업으로 레이블링 하는 방식에는 크나큰 결점이 존재한다. 대표적으로 3가지 결점에 대해 알아보자.
- 시대의 변화에 대응하기 어렵다
시간이 지남에 따라 단어의 의미가 변경되거나 새로운 신조어가 등장한다. 이런 단어의 변화에 대응하려면 시소러스를 사람이 수작업으로 끊임없이 갱신해야 한다. - 사람을 쓰는 비용이 크다
시소러스를 만드는 데는 엄청난 인적 비용이 발생한다. 영어를 예로 들면, 현존하는 영어 단어의 수는 1,000만 개가 넘는다고 한다. 수많은 단어들을 일일이 시소러스로 만드는 데는 대규모 인력 자원이 필요 하교 여기서 발생하는 비용이 매우 크다. - 단어의 미묘한 차이를 표현할 수 없다
시소러스에서는 뜻이 비슷한 단어들을 묶는다. 그러나 실제로 비슷한 단어더라도 미묘한 차이가 있는 것을 시소러스는 표현할 수 없다. 예컨대 "빈티지"와 "레트로"는 의미가 같지만 용법은 다르다.
통계 기반 기법
통계 기반 기법은 말뭉치(Corpus)를 사용한다. 말뭉치란 간단히 말하면 대량의 텍스트 데이터이다. 다만 맹목적으로 수집된 텍스트 데이터가 아닌 자연어 처리 연구나 애플리케이션을 염두에 두고 수집된 텍스트 데이터를 일반적으로 '말뭉치'라고 한다. 통계 기반 기법의 목표는 이처럼 사람의 지식으로 가등한 말뭉치에서 자동으로, 그리고 효율적으로 그 핵심을 추출하는 것이다. 간단한 예시를 통해 알아보자.
import numpy as np
def preprocess(text):
text = text.lower()
text = text.replace('.', ' .')
words = text.split()
word2id = {}
id2word = {}
for word in words:
if word not in word2id:
new_id = len(word2id)
word2id[word] = new_id
id2word[new_id] = word
corpus = np.array([word2id[w] for w in words])
return corpus, word2id, id2word
text = 'I am a student and you are a teacher.'
corpus, word2id, id2word = preprocess(text)
print(corpus)
print(word2id)
print(id2word)
Output
[0 1 2 3 4 5 6 2 7 8]
{'i': 0, 'am': 1, 'a': 2, 'student': 3, 'and': 4, 'you': 5, 'are': 6, 'teacher': 7, '.': 8}
{0: 'i', 1: 'am', 2: 'a', 3: 'student', 4: 'and', 5: 'you', 6: 'are', 7: 'teacher', 8: '.'}
단어의 분산 표현
세상에는 다채로운 '색'이 존재한다. 빨간색, 파란색 등 여러 가지 색이 있고 이러한 색들에는 '코발트블루', '스페이스 그레이'같은 고유한 이름을 붙이는 것도 있다. 한편, RGB(red/green/blue)라는 세 가지 성분으로 표현하는 방법도 있다. 여기서 주목할 점은 RGB 같은 벡터 표현이 색을 더 정확하게 명시할 수 있다는 사실이다. 예컨대 '비색'이라고 하면 어떤 색인지는 몰라도 RGB=(170, 33, 22)라고 하면 빨강 계열에 가까운 색임을 알 수 있다.
'색'을 벡터로 표현하듯 '단어'도 벡터로 표현할 수 있다. NLP에서 우리가 원하는 것은 컴퓨터에게 '단어의 의미'를 정확하게 학습시키는 것이다. 이를 위해 '단어의 의미'를 정확하게 파악할 수 있는 벡터로 표현하는 기법이 사용되는데, 이를 자연어 처리 분야에서 분산 표현(distributional representation)이라 한다.
단어의 분산 표현은 단어를 고정 길이의 밀집 벡터(dense vector)로 표현한다. 밀집 벡터라 함은 대부분의 원소가 0이 아닌 실수인 벡터를 말한다. 예컨데 3차원 분산 표현은 [0.12, -0.24, 0.83]과 같은 모습이 된다.
분포 가설
단어의 의미는 주변 단어에 의해 형성된다
이를 분포 가설(distributional hypothesis)이라 하며, 단어를 벡터로 표현하는 대부분의 연구가 이 가설에 기초한다. 분포 가설이 말하고자 하는 매우 간단하다. 단어 자체에는 의미가 없고, 그 단어가 사용된 '맥락(context)'이 의미를 형성한다는 것이다. 예컨데 "I drink beer"와 "We drink wine"처럼 "drink"의 주변에는 주체와 음료가 등장하기 쉬울 것이다.
동시발생 행렬
분포 가설에 기초해 단어를 벡터로 나타내는 방법을 생각해보자. 어떤 단어에 주목했을 때, 그 주변에 어떤 단어가 몇 번이나 등장하는지를 세어 집계하는 방법이다. 이를 '통계적 기반(statistical based)'라고 한다. 아래 예를 통해 이해해보자.
text = 'The dog and the cat are cute.'
특정 단어의 주변 1개의 단어만 고려한다고 했을 때, window size = 1이라고 하며 공통된 단어는 제외한다.
"dog"를 특정 단어로 할 때, 아래와 같은 표가 만들어진다.
the | dog | and | cat | are | cute | . | |
dog | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
이를 모든 단어에 대해 반복하면 표가 만들어진다. 각 행은 해당 단어를 표현한 벡터가 된다. 이 표가 행렬의 형태를 띤다는 뜻에서 동시발생 행렬(co-occurrence matrix)라고 한다.
벡터 간 유사도
동시발생 행렬을 만들었다면 벡터 사이의 유사도를 측정하는 방법에 대해 알아보자. 벡터 간 유사도를 측정하는 방법은 다양하지만 대표적으로 벡터의 내적이나 유클리드 거리 등을 꼽을 수 있다. 많은 방법들 중 단어 벡터의 유사도를 나타낼 때는 코사인 유사도(cosine similarity)를 자주 이용한다.
두 벡터 $x = (x_{1},x_{2},x_{3}, ..., x_{n})$, $y = (y_{1},y_{2},y_{3}, ..., y_{n})$가 있다면 코사인 유사도는 다음 식으로 정의된다.
$$
similarity(x,y) = \frac{x \cdot y}{\left | x \right |\left | y \right |} = \frac{x_{1}y_{1} + ... + x_{n}y_{n}}{\sqrt{x_{1}^2 + ... + x_{n}^2} \sqrt{y_{1}^2 + ... + y_{n}^2}}
$$
코사인 유사도의 분자에는 벡터의 내적이, 분모에는 각 벡터의 노름(norm)이 등장한다. 여기에서는 L2 Norm을 사용한다. 위 식의 핵심은 벡터를 정규화하고 내적을 구하는 것이다. 코사인 유사도는 직관적으로 풀어보자면 두 벡터가 가리키는 방향이 얼마나 비슷한가이다. 두 벡터의 방향이 완전히 같다면 코사인 유사도가 1이 되며, 완전히 반대라면 -1이 된다.
코사인 유사도를 구할 때 주의해야 할 점이 있다. 코사인 유사도 식에서 분모가 0이 되면 Divide by Zero 오류가 발생한다. 따라서 분모에 아주 작은 값인 eps(epsilon)을 더해주면 결괏값에 영향을 주지 않고 오류를 예방할 수 있다.
지금까지 본 것처럼 동시발생 행렬(co-occurrence matrix)을 사용하면 단어를 벡터로 표현할 수 있다.
통계 기반 기법 개선하기
위 내용으로 단어를 벡터로 표현하는 데는 성공했지만, 동시발생 행렬에는 아직 개선할 점이 있다. 먼저 상호 정보량에 대해 알아보자.
상호 정보량
동시발생 행렬의 원소는 두 단어가 동시에 발생한 횟수를 나타낸다. 하지만 '발생 횟수'라는 것은 그렇게 좋은 특징이 아니다. 예컨대 말뭉치(corpus)에서 'the'와 'car'의 동시발생을 생각해보면 'car'과 'drive'보다 발생 횟수가 높을 것이다. 단순히 단어의 동시발생 횟수로만 따지면 'the'가 많이 나오므로 'the'와 'car'의 유사도가 높다고 판단할 것이다.
이것을 해결하기 위해 점별 상호 정보량(Pointwise Mutual Information, PMI)이라는 척도가 생겨난다. PMI는 확률 변수 x와 y에 대해 다음 식으로 정의된다.
$$
PMI(x, y) = log_{2} \frac{P(x,y)}{P(x)P(y)}
$$
분자는 x와 y가 동시에 일어날 확률, 분모는 x가 발생할 확률 곱하기 y가 발생할 확률이다. 예컨대 10,000개의 단어로 이루어진 말뭉치에 "the"가 100번 등장한다면 $P("the") = \frac{100}{10000} = 0.01$이 된다.
위 식을 동시발생 행렬을 사용한다고 했을 때의 식으로 다시 써보자. 여기서 C는 동시발생 행렬, C(x,y)는 x와 y가 동시에 발생하는 횟수, C(x)와 C(y)는 각각 단어의 등장 횟수이다. 이때 말뭉치에 포함된 단어 수를 N이라 하면 아래와 같은 식으로 도출된다.
$$
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)}
$$
N을 10,000이라 하고 "the"와 "car"과 "drive"가 각 1,000번, 20번, 10번 등장했다고 해보자. 그리고 "the"와 "car"의 동시발생 횟수는 10회, "car"과 "drive"의 동시발생 횟수는 5회라고 해보자.
$$
PMI("the","car") = 2.32
$$
$$
PMI("car","drive") = 7.97
$$
위 결과를 보면 알 수 있듯이 PMI를 사용하면 보다 논리적인 지표를 얻을 수 있다. 하지만 PMI도 한 가지 문제가 있는데, 두 단어의 동시발생 횟수가 0이면 $log_{2}0 = -\infty$가 된다는 점이다. 이를 해결하기 위해서는 양의 상호 정보량(Positive PMI)을 사용하면 된다.
PMI or PPMI를 사용하면 동시발생 행렬을 보다 논리적으로 구성할 수 있게 된다. 하지만 말뭉치의 어휘 수가 증가한다면 각 단어 벡터의 차원 수도 같이 증가하기 때문에 매우 큰 말뭉치에서는 하드웨어가 소화하지 못한다. 그리고 동시발생 행렬과 PMI 행렬은 대부분의 요소가 0으로 구성되기 때문에 공간이 낭비된다는 단점이 존재한다. 다시 말하자면 단어 벡터의 대부분의 원소가 중요하지 않다는 뜻이다. 이 문제점에 대처하고자 자주 수행하는 기법이 벡터의 차원 감소이다.
원소의 대부분이 0인 행렬 또는 벡터를 '희소 행렬(sparse matrix)' 또는 '희소 벡터(sparse vector)'라고 한다. 차원 감소의 결과는 원래의 희소 벡터의 원소 대부분이 0이 아닌 값을 갖는 '밀집 벡터(dense vector)'로 변환된다.
차원 감소
차원 감소(dimensionality reduction)는 벡터의 차원을 줄이는 방법을 말한다. 단순히 줄이는 것이 다가 아니라, '중요한 정보'는 최대한 유지하면서 줄이는 게 핵심이다. 차원 감소에 사용되는 기법 중 특이값 분해에 관해 알아보자.
SVD
SVD(Singular Value Decomposition)은 특이값 분해라고 하며 수식은 다음과 같다.
$$
X = USV^T
$$
위와 같이 SVD는 임의의 행렬 X를 U, S, V라는 세 행렬의 곱으로 분해한다. U와 V는 직교 행렬(orthogonal matrix)이고, 그 열 벡터는 서로 직교한다. 또한 S는 대각 행렬(diagonal matrix)이다.
위 그림 가운데 S에 해당하는 행렬의 흰색 부분은 0을 의미한다. S의 대각 원소들은 오름차순으로 정렬되어 있다. 예컨대 S의 대각 원소 중 2개만 사용한다면 U의 차원이 줄어들고 U의 행이 곧 단어 벡터를 나타내기 때문에 결과적으로 단어 벡터의 차원이 2차원으로 줄어든다고 이해하면 좋을 것 같다. 우리가 사용하는 값은 결국 SVD를 통해 나온 U가 된다. SVD의 자세한 설명은 어렵기 때문에 여기서는 생략하도록 하겠다.
정리
컴퓨터에 단어의 의미를 학습시키기 위한 기법 중 '시소러스를 활용한 기법'과 '통계 기반 기법'에 관해 알아보았다. 시소러스는 NLP 연구 초기에 나온 기법으로 사람이 직접 유의어 사전을 만들어서 컴퓨터에게 알려주는 방식이었고 단어에 어느 정도 의미를 부여할 수 있다는 특징이 있었지만 의미가 같지만 쓰이는 용도가 다른 언어에 대해 전혀 알 수 없다는 단점과 대규모의 사람들이 투입되어 비용이 매우 높다는 단점을 갖고 있었다.
통계 기반 기법은 말뭉치(corpus)를 사용한다. Corpus에서 동시발생 행렬과 코사인 유사도를 사용하여 단어 간 유사도를 측정했었지만 빈도수가 높으면 무조건 높은 유사도를 가지는 단점이 있었고, 이를 해결하기 위해 상호 정보량의 개념이 등장했다. 하지만 상호 정보량(PMI)을 사용하더라도 Corpus가 커지게 되면 단어 벡터의 차원이 무한하게 증가하기 때문에 SVD를 사용해서 차원을 감소시키는 기법이 적용되었다.
'NLP' 카테고리의 다른 글
BERT 논문 리뷰 (0) | 2021.10.01 |
---|---|
[NLP] Word2Vec (0) | 2021.09.23 |
[NLP] 자연어처리란? (0) | 2021.09.16 |