본문 바로가기

Deep Learning(강의 및 책)/밑바닥부터 시작하는 딥러닝 2

2. 자연어와 단어의 분산 표현

728x90

밑바닥부터 시작하는 딥러닝 2를 바탕으로 제작하였습니다.

 

2.1. 자연어 처리란

한국어와 영어 등 우리가 평소에 쓰는 말을 자연어(natural language)라고 합니다. 자연어 처리(Natural Language Processing, NLP)를 문자 그대로 해석하면 '자연어를 처리하고 분야'이고, 알기 쉽게 풀어보면 '우리의 말을 컴퓨터에게 이해시키기 위한 기술(분야)'입니다. 그래서 자연어 처리가 추구하는 목표는 사람의 말을 컴퓨터가 이해하도록 만들어서, 컴퓨터가 우리에게 도움이 되는 일을 수행하게 하는 것입니다.

 

 ■ 단어의 의미

우리의 말은 '문자'로 구성되며, 말의 의미는 '단어'로 구성됩니다. 단어는 말하자면 의미의 최소 단위가 됩니다. 그래서 자연어를 컴퓨터에게 이해시키는 데는 무엇보다 '단어의 의미'를 이해시키는 게 중요합니다.

 

2.2. 시소러스

시소러스란 유의어 사전으로, '뜻이 같은 단어(동의어)'나 '뜻이 비슷한 단어(유의어)'가 한 그룹으로 분류되어 있습니다.

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

단어의 관계 그래프를 그리면 이와 같이 그릴 수 있습니다. "car"의 상위 개념으로 "motor vehicle"이라는 단어가 존재합니다. 한편 "car"의 하위 개념으로는 "SUV(스포츠 유틸리티 자동차)", "compact(소형차)", "hatch-back(해치백)" 등 더 구체적인 차종이 있음을 알려줍니다. 이처럼 모든 단어에 대한 유의어 집합을 만든 다음, 단어들의 관계를 그래프로 표현하여 단어 사이의 연결을 정의할 수 있고 이러한 '단어 네트워크'를 이용하여 컴퓨터에게 단어 사이의 관계를 가르칠 수 있습니다.

 

 ■ WordNet

자연어 처리 분야에서 가장 유명한 시소러스는 WordNet입니다. WordNet은 프린스턴 대학교에서 1985년부터 구축하기 시작한 오래된 시소러스로, 지금까지 많은 연구와 다양한 자연어 처리 애플리케이션에서 활용되고 있습니다. 이러한 WordNet을 사용하면 유의어를 얻거나 '단어 네트워크'를 이용할 수 있고 '단어 네트워크'를 이용해 단어 사이의 유사도를 구할 수도 있습니다.

 

 ■ 시소러스의 문제점

시소러스는 사람이 수작업으로 레이블링하는 방식인데 큰 문제점들이 존재합니다.

 - 시대 변화에 대응하기 어렵다.

새로운 단어가 생성되기도 하고 시대에 따라 언어의 의미가 변하기도, 사라지기도 합니다. 이런 단어의 변화에 대응하려면 시소러스를 사람이 수작업으로 끊임없이 갱신해야만 합니다.

 - 사람을 쓰는 비용은 크다.

시소러스를 만드는 데는 엄청난 인적 비용이 발생합니다. 영어를 예로 들면, 현존하는 영어 단어의 수는 1000만 개가 넘습니다. 따라서 이 무수히 많은 단어 사이의 관계를 정의하기 위해서는 엄청난 인적 비용이 발생합니다.

 - 단어의 미묘한 차이를 표현할 수 없다.

시소러스에서는 뜻이 비슷한 단어들을 묶습니다. 그러나 실제로 비슷한 단어들이라도 미묘한 차이가 있습니다. 시소러스에서는 미묘한 차이를 표현할 수 없습니다.

 

2.3. 통계 기반 기법

통계 기반 기법을 살펴보면서 우리는 말뭉치를 사용할 것입니다. 말뭉치란 간단히 말해 대량의 텍스트 데이터입니다. 다만, 맹목적으로 수집된 텍스트 데이터가 아닌 자연어 처리 연구나 애플리케이션을 염두에 두고 수집된 텍스트 데이터를 일반적으로 '말뭉치'라 합니다. 결국 말뭉치란 텍스트 데이터 지나지 않습니다만, 그 안에 담긴 문장들은 사람이 쓴 글이고 자연어에 대한 사람의 '지식'이 충분히 담겨 있다고 볼 수 있습니다.

 

 ■ 파이썬으로 말뭉치 전처리하기

텍스트 데이터를 단어로 분할하고 그 분할된 단어들을 단어 ID 목록으로 변환하는 일을 '말뭉치 전처리'라고 합니다.

이와 같이 문장이 존재할 때 모든 글자를 소문자로 변환하고 split을 통해 공백을 기준으로 분할했습니다. 이제 원래의 문장을 단어 목록 형태로 이용할 수 있게 되었습니다.

 

 

 

이번에는 딕셔너리를 사용해 ID와 단어를 짝지어주는 대응표를 작성해보겠습니다.

이와 같이 코드를 작성할 수 있습니다. 단어 ID에서 단어로의 변환은 id_to_word가 담당하며(키가 ID, 값이 단어), 단어에서 단어 ID로의 변환은 word_to_id가 담당합니다.

 

 

 

 

 

마지막으로 단어 목록을 단어 ID 목록으로 변경해보겠습니다. 단어 목록에서 단어 ID 목록으로 변환한 다음, 다시 넘파이 배열로 변환하는 코드를 작성하겠습니다. 여태까지 내용을 한 번에 preprocess라는 함수로 만들어보겠습니다.

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])

    return corpus, word_to_id, id_to_word

이와 같이 작성할 수 있으며 예시로 사용하면

text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocess(text)
print(corpus)
print(word_to_id)
print(id_to_word)

실행결과는

이와 같이 작성됩니다.

 

 

 

 ■ 분포 가설

'단어의 의미는 주변 단어에 의해 형성된다'는 것을 분포 가설이라 하며, 단어를 벡터로 표현하는 최근 연구도 대부분 이 가설에 기초합니다. 분포 가설이 말하고자 하는 바는 매우 간단합니다. 단어 자체에는 의미가 없고, 그 단어가 사용된 '맥락(context)'이 의미를 형성한다는 것입니다.

사진과 같이 goodbye를 기준으로 좌우의 두 단어씩이 '맥락'에 해당합니다. 여기서 맥락의 크기는 '윈도우 크기'라고 하고 윈도우 크기가 2이기 때문에 좌우 두 단어씩을 맥락에 해당했습니다.

 

 ■ 동시발생 행렬

분포 가설에 기초해 단어를 벡터로 나타내는 방법을 생각해보면 어떤 단어에 주목했을 때, 그 주변에 어떤 단어가 몇 번이나 등장하는지를 세어 집계하는 방법이 떠오를 것인데 이를 '통계 기반'기법이라고 합니다.

아까 작성한 코드를 통해 각 단어의 맥락에 해당하는 단어의 빈도를 세어보겠습니다. 윈도우 크기는 1로 하고, 단어 ID가 0인 'you'부터 시작해보겠습니다.

'you'의 경우 맥락은 'say'라는 단어 하나뿐입니다. 표는 'you'라는 단어의 맥락으로써 동시에 발생하는 단어의 빈도를 나타낸 것입니다. 이를 바탕으로 벡터로 표현하면

[0, 1, 0, 0, 0, 0, 0]이 됩니다.

이번에는 ID = 1인 'say'라는 단어에 대해서 빈도를 확인해보겠습니다.

'say'의 경우 맥락은 'you', 'goodbye', 'i', 'hello'라는 단어가 있습니다. 표는 'say'를 기준으로 맥락으로써 동시에 발생하는 단어의 빈도를 나타낸 것입니다. 벡터로 표현한다면

[1, 0, 1, 0, 1, 1, 0]이 됩니다.

이렇게 모든 단어에 대해서 표를 작성해보면

이와 같이 각 단어에 대해서 표로 작성할 수 있습니다.

 

 ■ 벡터 간 유사도

동시발생 행렬을 활용해 단어를 벡터로 표현하는 방법을 알아봤고 이번에는 벡터 사이의 유사도를 측정하는 방법을 살펴보겠습니다. 벡터 사이의 유사도를 측정하는 방법은 다양한데 대표적으로 벡터의 내적이나 유클리드 거리 등을 꼽을 수 있습니다. 그 외에도 다양하지만, 단어 벡터의 유사도를 나타낼 때는 코사인 유사도를 자주 이용합니다.

두 벡터 x, y가 존재할 때

이와 같은 식으로 유사도를 구하는 것을 코사인 유사도라 합니다. 분자에는 두 벡터의 내적, 분모에는 각 벡터의 노름(norm)이 등장합니다. 노름은 벡터의 크기를 나타낸 것으로, 여기에서는 'L2노름'을 계산합니다(L2노름은 벡터의 각 원소를 제곱해 더한 후 다시 제곱근을 구해 계산합니다). 위 식의 핵심은 벡터를 정규화하고 내적을 구한다는 것입니다.

이러한 코사인 유사도를 구현해보면

def cos_similarity(x, y, eps=1e-8):
    '''코사인 유사도 산출

    :param x: 벡터
    :param y: 벡터
    :param eps: '0으로 나누기'를 방지하기 위한 작은 값
    :return:
    '''
    nx = x / (np.sqrt(np.sum(x ** 2)) + eps)
    ny = y / (np.sqrt(np.sum(y ** 2)) + eps)
    return np.dot(nx, ny)

이와 같이 구현할 수 있습니다. 이 코드는 x, y 둘 다 넘파이 배열이라고 가정하고 작성한 코드입니다. 이 함수는 먼저 벡터 x와 y를 정규화한 후 두 벡터의 내적을 구했습니다. 만약 인수로 제로 벡터(모든 원소가 0인 벡터)가 들어오면 '0으로 나누는 오류'가 발생합니다. 이를 해결하기 위해 매우 작은 값 eps를 더했습니다.

이와 같이 작성한 코드를 사용해 'you'와 'i'의 유사도를 계산해보겠습니다.

# coding: utf-8
import sys
sys.path.append('..')
from common.util import preprocess, create_co_matrix, cos_similarity


text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocess(text)
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))

 실행 결과

이와 같이 코사인 유사도를 구할 수 있습니다. 코사인 유사도 값은 -1부터 1 사이이므로, 이 값은 비교적 유사성이 크다고 말할 수 있습니다.

 

 

 

 ■ 유사 단어의 랭킹 표시

어떤 단어가 검색어로 주어지면, 그 검색어와 비슷한 단어를 유사도 순으로 출력하는 함수를 구현해보겠습니다.

def most_similar(query, word_to_id, id_to_word, word_matrix, top=5):
    '''유사 단어 검색

    :param query: 쿼리(텍스트)
    :param word_to_id: 단어에서 단어 ID로 변환하는 딕셔너리
    :param id_to_word: 단어 ID에서 단어로 변환하는 딕셔너리
    :param word_matrix: 단어 벡터를 정리한 행렬. 각 행에 해당 단어 벡터가 저장되어 있다고 가정한다.
    :param top: 상위 몇 개까지 출력할 지 지정
    '''
    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

이와 같은 코드로 구현할 수 있습니다. 이 코드는 검색어의 단어 벡터를 꺼내고 검색어의 단어 벡터와 다른 모든 단어 벡터와의 코사인 유사도를 각각 구하고 결과를 기준으로 높은 순서대로 출력을 하는 순서로 진행됩니다.

이렇게 작성한 함수를 이용해 'you'라는 단어를 기준으로 순위를 나타내 보겠습니다.

# coding: utf-8
import sys
sys.path.append('..')
from common.util import preprocess, create_co_matrix, most_similar


text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocess(text)
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'와 유사한 순서대로 단어 5개를 출력한 결과입니다. 'i'와 'you' 모두 인칭대명사이므로 둘이 비슷하다는 건 납득이 됩니다. 하지만 'goodbye'와 'hello'는 지금 말뭉치의 크기가 너무 작아서 나온 결과입니다. 말뭉치가 커진다면 좀 더  올바른 결과를 얻을 수 있습니다.

 

 

 

2.4. 통계 기반 기법 개선하기

 ■ 상호정보량

이 전에 본 동시발생 행렬의 원소는 두 단어가 동시에 발생한 횟수를 나타냅니다. 그러나 이 '발생' 횟수는 그다지 좋은 특징이 아닙니다. 예를 들어 'the'와 'car'라는 단어는 동시발생 횟수가 클 것입니다. 한편, 'car'와 'drive'는 확실히 관련이 깊습니다. 하지만 단순히 등장 횟수만을 본다면 'car'는 'drive'보다 the'랑 더 관련성이 높다고 나올 것입니다.

이러한 문제를 해결하기 위해서 점별 상호정보량(Pointwise Mutual Information, PMI)이라는 척도를 사용합니다. PMI는 확률 변수 x, y를 이용해 다음 식으로 정의됩니다.

P(x)는 x가 일어날 확률 , P(y)는 y가 일어날 확률, P(x, y)는 x와 y가 동시에 일어날 확률을 의미합니다. 이 PMI 값이 높다면 관련성이 높다는 의미입니다.

이 식을 다시 정리해

이와 같이 작성할 수 있습니다. 여기서 C는 동시발생 행렬, C(x, y)는 단어 x와 y가 동시 발생하는 횟수, C(x)와 C(y)는 각각 단어 x와 y의 등장 횟수를 의미합니다. N은 말뭉치에 포함된 단어 수를 의미합니다. 이렇게 동시발생 행렬로부터 PMI를 구할 수 있습니다.

이 식을 이용해 경우를 살펴보겠습니다. 만약 1000번 등장한 'the', 20번 등장한 'car'와 10번 등장한 'drive'를 계산해보겠습니다.

먼저 'the'와 'car'의 동시발생 수가 10회라면 PMI의 결과는

이와 같이 구할 수 있습니다.

이번에는 'car'와 'drive'의 동시발생 수가 5라면 PMI의 결과는

이와 같이 구할 수 있습니다. 이 경우 'car'와 'drive'의 관계성이 더 크다고 나옵니다. 이러한 PMI에도 한 가지 문제가 있습니다. 만약 두 단어의 동시발생 횟수가 0이면, log0 = -∞가 됩니다. 이러한 문제를 해결하기 위해 양의 상호정보량(PPMI)을 사용합니다.

이와 같은 식으로 표현할 수 있으며 이 식에 따라 PMI가 음수일 때는 0으로 취급하여 단어 사이의 관련성을 0 이상의 실수로 나타낼 수 있습니다. 이러한 PPMI를 구현해보겠습니다.

# coding: utf-8
import sys
sys.path.append('..')
import numpy as np
from common.util import preprocess, create_co_matrix, cos_similarity, ppmi


text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocess(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('PPMI')
print(W)

실행 결과

이와 같이 결과를 얻습니다. 여기서 C는 동시발생 행렬, verbose는 진행상황 출력 여부를 결정하는 플래그입니다. log(0)이 되는 경우를 막기 위해 eps라는 작은 값을 사용했습니다.

이것으로 동시발생 행렬을 PPMI 행렬로 변환하는 법을 알아봤습니다. 이때 PPMI 행렬의 각 원소는 0 이상의 실수입니다. 만약 말뭉치의 크기가 매우 커진다면 벡터의 차원도 매우 커집니다. 예를 들어 말뭉치의 어휘수가 10만 개라면 그 벡터의 차원 수도 똑같이 10만이 됩니다. 또한, 이 행렬의 대부분 값들이 0인 것을 알 수 있습니다. 이 말은 벡터의 원소 대부분이 중요하지 않다는 뜻입니다. 다르게 표현하면 각 원소의 '중요도'가 낮다는 뜻입니다. 더구나 이런 벡터는 노이즈에 약하고 견고하지 못하다는 약점도 존재합니다. 이러한 문제를 해결하는 방법은 '벡터의 차원 감소'입니다.

 

 

 

 ■ 차원 감소

차원 감소(dimensionality reduction)는 문자 그대로 벡터의 차원을 줄이는 방법입니다. 중요한 정보를 최대한 유지하면서 줄이는 게 핵심입니다.

그림과 같이 데이터의 분포를 고려해 중요한 '축'을 찾는 일을 수행합니다. 왼쪽은 데이터 점들을 2차원 좌표에 표시한 모습입니다. 그리고 오른쪽은 새로운 축을 도입하여 똑같은 데이터를 좌표축 하나만으로 표시했습니다. 여기서 중요한 것은 가장 적합한 축을 찾아내는 일로, 1차원 값만으로 데이터의 본질적인 차이를 구별할 수 있어야 합니다.

차원을 감소하는 방법 중 하나인 특잇값분해(Singular Value Decomposition , SVD)를 사용하겠습니다. SVD는 임의의 행렬을 세 행렬의 곱으로 분해하며, 수식은 다음과 같습니다.

SVD는 임의의 행렬 X를 U, S, V라는 세 행렬의 곱으로 분해합니다. U와 V는 직교행렬이고 열벡터는 시로 직교합니다. S는 대각행렬(대각성분 외에는 모두 0인 행렬)입니다. 수식을 시각적으로 표현하면

이와 같이 나타낼 수 있습니다. 여기서 U는 직교행렬이고 이는 어떠한 공간의 축을 형성합니다. 지금 이 U행렬을 '단어 공간'으로 취급할 수 있고, S는 대각 행렬로, 대각성분에는 '특잇값'이 큰 순서로 나열되어 있습니다. 특잇값은 쉽게 말해 '해당 축'의 중요도라고 간주할 수 있습니다. 그래서 중요도가 낮은 원소를 깎아내는 방법을 생각할 수 있고 이를 그림으로 나타내면

이와 같이 나타낼 수 있습니다. 행렬 S에서 특잇값이 작다면 중요도가 낮다는 뜻이고, 행렬 U에서 여분의 열벡터를 깎아내어 원래의 행렬을 근사할 수 있습니다

 

■ SVD에 의한 차원 감소

이제 SVD를 파이썬 코드로 작성해보겠습니다.

# coding: utf-8
import sys
sys.path.append('..')
import numpy as np
from common.util import most_similar, create_co_matrix, ppmi
from dataset import ptb


window_size = 2
wordvec_size = 100

corpus, word_to_id, id_to_word = ptb.load_data('train')
vocab_size = len(word_to_id)
print('동시발생 수 계산 ...')
C = create_co_matrix(corpus, vocab_size, window_size)
print('PPMI 계산 ...')
W = ppmi(C, verbose=True)

print('calculating SVD ...')
try:
    # truncated SVD (빠르다!)
    from sklearn.utils.extmath import randomized_svd
    U, S, V = randomized_svd(W, n_components=wordvec_size, n_iter=5,
                             random_state=None)
except ImportError:
    # SVD (느리다)
    U, S, V = np.linalg.svd(W)

word_vecs = U[:, :wordvec_size]

querys = ['you', 'year', 'car', 'toyota']
for query in querys:
    most_similar(query, word_to_id, id_to_word, word_vecs, top=5)

실행 결과

이와 같은 결과를 얻을 수 있습니다. 'goodbye'와 'hello', 'you'와 'i'가 제법 가까이 있음을 알 수 있습니다. 그래프에서 'i'는 'goodbye'와 겹쳐있습니다. 지금은 말뭉치가 작기 때문에 PTB라는 더 큰 말뭉치를 사용해 진행해보겠습니다.

 

 ■ PTB 데이터셋

펜 트리뱅크(Penn Treebank, PTB)는 너무 크기도, 그렇다고 작지도 않은 적당한 크기의 데이터셋입니다. 이 데이터셋을 이용해 지금까지 했던 과정을 진행해보겠습니다.

# coding: utf-8
import sys
sys.path.append('..')
from dataset import ptb


corpus, word_to_id, id_to_word = ptb.load_data('train')

print('말뭉치 크기:', len(corpus))
print('corpus[:30]:', corpus[:30])
print()
print('id_to_word[0]:', id_to_word[0])
print('id_to_word[1]:', id_to_word[1])
print('id_to_word[2]:', id_to_word[2])
print()
print("word_to_id['car']:", word_to_id['car'])
print("word_to_id['happy']:", word_to_id['happy'])
print("word_to_id['lexus']:", word_to_id['lexus'])

실행 결과

총 말뭉치 크기는 929589가 되고 corpus에는 단어 ID 목록이 저장됩니다. id_to_word는 단어 ID에서 단어로 변환하는 딕셔너리이고, word_to_id는 단어에서 단어 ID로 변환하는 딕셔너리입니다.

 

 ■ PTB 데이터셋 평가

PTB 데이터셋에 통계 기반 기법을 적용해보겠습니다. 이번에는 데이터가 전보다 크기 때문에 sklearn 모듈을 이용해 진행하겠습니다.

# coding: utf-8
import sys
sys.path.append('..')
import numpy as np
from common.util import most_similar, create_co_matrix, ppmi
from dataset import ptb


window_size = 2
wordvec_size = 100

corpus, word_to_id, id_to_word = ptb.load_data('train')
vocab_size = len(word_to_id)
print('동시발생 수 계산 ...')
C = create_co_matrix(corpus, vocab_size, window_size)
print('PPMI 계산 ...')
W = ppmi(C, verbose=True)

print('calculating SVD ...')
try:
    # truncated SVD (빠르다!)
    from sklearn.utils.extmath import randomized_svd
    U, S, V = randomized_svd(W, n_components=wordvec_size, n_iter=5,
                             random_state=None)
except ImportError:
    # SVD (느리다)
    U, S, V = np.linalg.svd(W)

word_vecs = U[:, :wordvec_size]

querys = ['you', 'year', 'car', 'toyota']
for query in querys:
    most_similar(query, word_to_id, id_to_word, word_vecs, top=5)

실행 결과

sklearn의 사용 유무에 따라 진행 방식을 두 가지로 나눴습니다. SVD를 수행하는 데 sklearn의 randomized_svd() 메서드를 이용했습니다. 이 메서드는 무작위 수를 사용한 Truncated SVD로, 특잇값이 큰 것들만 계산하여 기본적인 SVD보다 훨씬 빠릅니다.

결과를 보면 'you'라는 검색어에서는 인칭대명사인 'i'와 'we'가 상위를 차지했음을 알 수 있습니다. 영어 문장에서 관용적으로 자주 같이 나오는 단어이기 때문입니다. 또, 'year'의 연관어로는 'month'와 'quarter'가, 'car'의 연관어로는 'auto'와 'vehicle' 등이 뽑혔습니다. 이처럼 단어의 의미 혹은 문법적인 관점에서 비슷한 단어들이 가까운 벡터로 나타났습니다.

'Deep Learning(강의 및 책) > 밑바닥부터 시작하는 딥러닝 2' 카테고리의 다른 글

7. RNN을 사용한 문장 생성  (0) 2022.03.05
6. 게이트가 추가된 RNN  (0) 2022.03.03
5. 순환 신경망(RNN)  (0) 2022.03.02
4. word2vec 속도 개선  (0) 2022.02.26
3. word2vec  (0) 2022.02.23