ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [밑시딥2] 자연어와 단어의 분산 표현
    A.I/Study 2023. 6. 17. 02:14

    자연어 처리 개요

    자연어 (Natural Language): 한국어나 영어와 같이 사람이 일반적으로 사용하는 말

    자연어 처리(NLP; Natural Language Processing): 컴퓨터가 우리의 말을 이해하게 만드는데 목적을 둔 연구분야

    자연어 특성

    • 같은 의미의 문장이 여러형태로 표현 가능하다
    • 문장의 뜻이 애매할 수 있다
    • 그 의미나 형태가 유연하게 바뀐다.
    • 세월에 따라 새로운 말이 생기거나 사라기지도 한다.

    단어의 의미

    문자로 구성되고, 말의 의미단어로 구성된다.

    즉, 단어는 의미의 최소 단위라고 볼 수 있다.

    단어의 의미를 표현하는 방법

    • 시소러스(thesaurus) 활용: 유의어 사전 기반
    • 통계 기반 기법: 통계 정보로부터 단어를 표현
    • 추론 기반 기법: 신경망을 활용

    시소러스

    단어의 의미를 인력을 동원해 정의한 사례

    시소러스 (thesaurus) 형태를 사용하여 '뜻이 같은 단어 (동의어)' 또는 '뜻이 비슷한 단어 (유의어)'를 한 그룹으로 분류한다.

    car에 대한 유의어 그룹 예시

    자연어 처리를 위해 사용되는 시소러스에서는 단어 사이의 '상위와 하위' 혹은 '전체와 부분' 등 세세한 관계까지 정의해둔 경우가 있다.

    단어의 상/하위 관계를 이용한 그래프 표현

    위 다이어그램과 같이, 모든 단어에 대한 유의어 집합을 만든 다음, 단어들의 관계를 그래프로 표현하여, 단어 사이의 연결을 정의할 수 있다.

    이것을 단어 네트워크라고 볼 수 있는데, 이를 통해 컴퓨터에게 단어 사이의 관계를 가르칠 수 있다.

    WordNet

    자연어 분야에서 유명한 시소러스 중 하나이며, 프린스턴 대학교에서 1985년부터 구축하기 시작한 전통있는 시소러스이다.

    시소러스 문제점

    1. 시대 변화에 대응하기 어렵다.
    2. 사람을 쓰는 비용이 크다.
    3. 단어의 미묘한 차이를 표현할 수 없다.

    통계 기반 기법

    통계 기반 기법에서는 말뭉치 (corpus) 라는 시소러스와 다른 데이터를 사용한다.

    이는 자연어 처리 연구나 애플리케이션을 염두해 두고 수집된 텍스트 데이터이며, 쉽게 말해 대량의 텍스트 데이터이다.

    일반적인 텍스트 데이터셋과 다르게, 예를 들어 문장을 쓰는 방법 또는 단어를 선택하는 방법 등, 자연어에 대한 사람의 '지식'이 충분히 담겨 있다고 불 수 있다.

    사람의 지식으로 가득한 말뭉치에서 자동으로, 그리고 효율적으로 그 핵심을 추출한다.

    파이썬으로 말뭉치 전처리

    책에는 다음과 같이 간단한 텍스트를 전처리하는 예시가 있다.

    전처리 과정에서는 공백을 기준으로 분리하고, 단어들에 ID를 부여한다.

    text = 'You say goodbye and I say hello.'
    
    def preprocess(text):
        # 소문자로 변환, Capitalized 단어도 동일하게 처리하기 위함
        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:
                # 새로운 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

    위의 전처리 과정을 통해 단어를 백터로 표현할 수 있다.

    단어의 분산 표현

    '단어의 의미'를 정확하게 파악할 수 있는 벡터 표현을 단어의 분산 표현 (distributional representation)이라고 한다.

    분포 가설

    분포 가설 (distributional hypothesis)는 '단어의 의미는 주변 단어에 의해 형성된다'는 아이디어를 기반으로한 가설이다.

    단어 자체에는 의미가 없지만, 그 단어가 사용된 '맥락 (context)'이 의미를 형상한다는 의미이다.

    맥락이란 특정 단어를 중심에 둔 그 주변 단어를 의미하며, 맥락의 크기(주변 단어를 몇 개나 포함할지)를 '윈도우 크기(window size)'라고 한다.

    동시발생 행렬

    분포 가설에 기초해 단어를 벡터로 나타내는 방법

    통계 기반 (statistical based) 기법어떤 단어에 주목했을 때, 그 주변에 어떤 단어가 몇 번이나 등장하는지를 세어 집계하는 방법이다.

    예시문장 "you say goodbye and i say hello . "에서 각 단어의 맥락에 해당하는 단어의 빈도를 확인해보자.

      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

    위 표는 모든 단어에 대한 동시발생하는 단어를 표에 정리한 것이다.

    표가 행렬의 형태를 띈다는 의미에서 동시발생 행렬 (co-occurrence matrix)라고 불린다.

    이러한 행렬 표현을 응용하여 단어를 벡터로 나타낼 수 있다.

    def create_co_matrix(corpus, vocab_size, window_size=1):
        corpus_size = len(corpus)
        # 0으로 채워진 2차원 배열로 초기화
        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

    벡터 간 유사도

    벡터 사이의 유사도를 측정하는 방법

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

    단어 벡터의 유사도를 나타낼 때는 코사인 유사도를 주로 사용한다.

    코사인 유사도 수식

    분자에는 벡터의 내적이, 분모에는 각 벡터의 노름 (norm)이 있다. 
    여기서 노름은 L2 노름이다. 
    핵심은 벡터를 정규화하고 내적을 구한다.
    코사인 유사도는 '두 벡터가 가리키는 방향이 얼마나 비슷한가'라고 해석할 수 있다.
    벡터의 방향이 완전히 같다면 코사인 유사도가 1이되며, 완전히 반대라면 -1이 된다.

    # 분모가 0이되는 것을 막기 위해, eps(epsilon)을 추가한다.
    def cos_similarity(x, y, eps=1e-8):
    	nx = x / (np.sqrt(np.sum(x ** 2)) + eps)
        ny = y / (np.sqrt(np.sum(x ** 2)) + eps)
        return np.dot(nx, ny)

    유사 단어 랭킹 표시

    지금까지 구현한 함수들을 활용하면, 단어가 주어졌을 때, 해당 단어와의 유사도 순으로 출력하는 함수를 구현할 수 있다.

    def most_similar(query, word_to_id, id_to_word, word_matrix, top=5):
    	# 검색어를 꺼낸다.
        if query not in word_to_id:
        	print(f'{query}를 찾을 수 없음')
    		return
        
        print(f'\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(f'{id_to_word[i]}: {similarity[i]}')
            
            count += 1
            if count >= top:
            	return

    여기서 배열의 원소 순서대로 정렬하기 위해 argsort()가 사용되었다.
    파이썬의 sort와 다르게 직접 변환하기 보다는 인덱스 값들을 담은 배열을 반환한다.
    단, 큰 수부터 나열하기 위해 -1이 곱해졌다.

    통계 기반 기법 개선하기

    상호정보량

    동시발생 행렬의 원소는 두 단어가 동시에 발생한 횟수를 나타낸다.

    그러나 이 발생 횟수라는 것은 좋은 특징이 아니다.

    the와 car가 동시발생을 생각해보면, 실재로는 car와 drive가 연관성이 높겠지만, the가 고빈도라서 car와 강한 관련성을 갖는다고 평가한다.

    책에서는 이 문제를 해결하기 위한 방법으로 점별 상호정보량 (PMI; Pointwise Mutual Information)이 사용됬다.

    PMI는 확률변수 x와 y에 대해 다음과 같이 표현 가능하다.

    PMI 수식

    동시발생 행렬로부터 PMI를 구하는 것은 다음의 수식으로 구할 수 있다.

    C는 동시발생 행렬, C(x,y)는 동시 발행하는 횟수, C(x) 단어의 등장 횟수, N이 말뭉치에 포함된 단어의 수라고 할 때, 다음과 같이 표현 가능하다.

    동시 발생 행렬을 이용한 PMI

    단어가 단독으로 출현하는 횟수가 고려되어 어느정도 문제가 해결됬다고 볼 수 있지만, 두 단어의 동시발생 횟수가 0일 때, 음의 무한이 되는 문제가 있다.

    이를 해결하기 위해 양의 상호정보량 (Positive PMI)를 사용한다.

    PPMI 수식 표현

    코드로 표현하면 다음과 같다.

    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

    위의 예시를 사용하면 아래와 같은 행렬이 나온다.

    0. 1.807 0. 0. 0. 0. 0.
    1.807 0. 0.807 0. 0.807 0.807 0.
    0. 0.807 0. 1.807 0. 0. 0.
    0. 0. 1.807 0. 1.807 0. 0.
    0. 0.807 0. 1.807 0. 0. 0.
    0. 0.807 0. 0. 0. 0. 2.807
    0. 0. 0. 0. 0. 2.807 0.

    앞서 언급한대로 단어가 단독으로 출현하는 횟수도 고려됬고, 음수가 발생하는 문제도 해결됬다.

    그럼에도 불구하고 여기에는 몇몇의 문제점이 여전히 존재한다.

    • 말뭉치의 어휘수가 증가함에따라 차원 수도 같이 증가한다.
    • 원소의 대부분이 0이다. 즉, 중요도가 낮다.
    • 노이즈에 약하고 견고하지 못하다.

    차원 감소

    차원 감소 (dimensionality reduction)은 문자 그대로 벡터의 차원을 줄이는 방법을 의미한다.

    책에 첨부된 다이어그램

    위 다이어그램과 마찬가지로 데이터의 분포를 고려해 중요한 '축'을 찾는 일을 수행한다.
    결과적으로 2차원 데이터를 좌표축 하나만으로 표현할 수 있게 됬다.

    특잇값분해 (SVD; Singular Value Decomposition) 는 세 행렬의 곱으로 분해한다.

    X에 SVD를 사용한 수식 표현

    여기서 U와 V는 직교행렬 (orthogonal matrix)이고, 그 열벡터는 서로 직교하며, S는 대각행렬(diagonal matrix)이다.

    U는 직교행렬로, 어떠한 공간의 축을 형성하며, 단어 공간으로 취급할 수 있다.
    S는 대각행렬이며, 그 대각성분에 특잇값(singular value)이 큰 순서대로 나열되어 있다.
    여기서 특잇값은 해당 축의 중요도라고 볼 수있다.

    파이썬 코드로는 다음과 같이 표현할 수 있다.

    U, S, V = np.linalg.svd(W) # W: ppmi

    SVD의 경우 O(N^3)의 시간 복잡도를 가져서 현실적으로 어려움으로 Truncated SVD 같은 더 빠른 기법을 사용한다.

    레퍼런스

    밑바닥부터 시작하는 딥러닝 2 (사이토 고키 지음, 개앞맵시 옮김)

    개앞맵시님의 밑시딥2 저장소: WegraLee/deep-learning-from-scratch-2: 『밑바닥부터 시작하는 딥러닝 ❷』(한빛미디어, 2019) (github.com)

    'A.I > Study' 카테고리의 다른 글

    [밑시딥2] 순환 신경망 (RNN)  (0) 2023.07.13
    [Math for Deeplearning] Linear Algebra  (0) 2023.07.08
    [밑시딥2] word2vec 속도 개선  (0) 2023.07.02
    [Math for Deeplearning] Probability  (0) 2023.07.01
    [밑시딥2] word2vec  (0) 2023.06.26
Designed by Tistory.