본문 바로가기
machine learning

Word2Vec porting

by 유주원 2016. 9. 30.

python이나 spark 등에서 word2vec을 사용해보기만 했지.. 내가 실제로 소스를 들여다 볼 일은 없을거라고 생각했었는데.. 실제 소스를 까서 porting 작업을 진행해야 하는 우려 사항이 발생했다.


우선 word2vec에 대해 간략하게 설명을 하자면, 가장 단순하게 말해서 word를 vector로 바꿔서 표현해 주는 것이다.


아니 왜???


그냥 word를 쓰면 안돼??


실생활에서는 물론 그냥 word를 쓰는게 당연하다. 하지만 컴퓨터는 해당 word에 대한 의미를 전혀 알지 못하기 때문에, 컴퓨터에게 의미 있는 어떤 수치로 알려주기 위해서는 vector 표현으로 주는 게 바람직하다.


그런데 이게.. 아무 벡터나 막 만들어서 주는게 아니라, 비슷한 단어끼리는 비슷한 벡터가 될 수 있도록 학습을 시킨 다음에 그 결과를 컴퓨터에 주고, 컴퓨터에서는 그것을 바탕으로 단어 간의 유사도를 확인할 수가 있다.


지금까지는 word2vec의 기본 개념이고, 그럼 이제 어떻게 단어간의 유사도를 확인해서 유사한 벡터로 만들어 주는가가 핵심이 될 수 있는데..


핵심 아이디어는 co-occurrence 즉, 단어의 동시 발생 빈도이다. 동시에 함께 출현하는 단어의 빈도수가 커질 수록 그 두 단어의 유사도는 높다고 판단하는 것이다.



그래서 w: 타켓 단어가 발생했을 때, c: 같은 문맥에서의 다른 단어가 발생할 확률의 곱이 최대가 될 수 있도록 만드는 것이 이 아이디어의 핵심이다. 물론 이 아이디어는 기존에 bengio라는 교수가 NNLM(neural net language model)을 발표하며 등장했었지만, word2vec 같은 경우에는 이 기존 NNLM에 속도 개선 측면을 더 부각시킨거라고 보면 될 것 같다.



The cat purrs.

This cat hunts mice.


이렇게 두 문장이 있다고 치자.

word2vec에 따르면 cat은 the, purrs, this, hunts, mice 등을 통해 유추해 낼수 있어야 한다.


만약 위와 유사하게,


the kitty purrs.

this kitty hunts mice.


이런 문장이 추가가 되었다면 어떨까??


결과적으로는 cat과 kitty는 유사도가 매우 높게 나타날 것이다. 왜냐하면 cat과 kitty로 인해 동반 등장하면 co-ocurrance 단어들이 상당히 유사하기 때문이다.


바로 이런 논리로 word2vec 에서는 단어간의 유사도를 판별해 내고 있다.


word2vec에서는 cbow 기법과 skipgram 기법 이렇게 두 가지 기법을 제시하는데, cbow는 주변 단어들을 통해 target 단어를 찾아내는 방법이고 skipgram은 target 단어가 주어지면 주변 단어들을 찾아내는 방법이다.


데이터가 클 경우에는 skipgram이 더 유용하다고 설명이 되어졌지만, 나는 그냥 cbow에 negative sampling을 써서 구현했다.


negative sampling과 hierarchical softmax 이란 용어도 등장하는데, 일단은 결과 확률을 효율적으로 계산하기 위한 기법들이다. 

뉴런 연산이 끝난 후 뉴런 마지막 부분에서는 이제 가중치 계산된 결과를 확률로 나타내 주기 위해 softmax를 실행해야 하고 softmax의 결과를 가지고 정답과의 차이만큼을 다시 back-propagation을 진행하는 것이다.


근데 softmax는 모든 결과 단어에 대한 확률을 계산하는 것인 만큼 많은 시간이 소요되고 계산량도 많아진다.


이를 보다 줄이기 위해 나타난 방법이 바로 negative sampling과 hierarchical softmax이다.


negative sampling은 '머 단어 전체에 대해 다 할 필요 있어??' 라는 생각을 기본으로 해서 정답 단어 1개와 나머지 오답 단어의 샘플을 선택한 다음에 샘플된 단어에 대한 확률 분포만 계산하는 것이다.

훈련 데이터가 많을 경우에는 2~5개의 negative sampling이 적합하고, 적은 훈련 데이터에서는 5 ~20 개의 훈련 데이터가 적합하다고 한다.




hierarchical softmax는 softmax를 계층화 해서 보다 효율적으로 확률 계산을 하는 방법이라고 하는데 이건 잘 모르겠다.


어찌되었건 cbow와 negative sampling을 활용해서 구현을 완료했다.


소스가 길진 않은데 수학적 기법들이 조금 들어가 있어서 처음 소스를 봤을 때 아리송하던 부분들만 살펴보자면,


for(i = 0; i < EXP_TABLE_SIZE; i++){

    expTable[i] = exp((i / (double)EXP_TABLE_SIZE * 2 - 1) * MAX_EXP);

    expTable[i] = expTable[i] / (expTable[i] + 1);

}


아마 처음 코드를 살펴보면 막히는 부분일 것 같다.


지금도 정확히는 모르겠지만, 대충 결과를 보면 0 ~ 1 사이의 소수 값들이 연달아 나타나는 배열이 결과로 나타난다.

단어에 대한 가중치 결과를 계산한 후 그 계산 결과를 expTable index로 보냄으로써 보다 빠르게 확률값을 얻기 위해 사전에 만든 작업인 것 같다.


http://stackoverflow.com/questions/21501973/precomputing-exponent-exp-table




두 번째 부분은 word2vec에서는 분명 window size를 정한 후 해당하는 window size 내에 있는 단어일 경우에만 같은 context로 두는데, 이 window size가 코드 내에서 랜덤으로 변화되도록 짜여져 있는 것이 의아한 부분이었다.


b = next_random % window_size;


for(a = b; a < window * 2 + 1 - b; a++){

    ....

}


이 부분에 대한 설명은 아래에 링크에 보다 자세히 나와있는데, 간추려서 설명하자면 wor2vec 내에서도 context간의 거리가 가까운 단어는 더 높은 유사도를, 거리가 먼 단어는 낮은 유사도를 주기 위해서 window_size로 모듈러 연산을 해서 제약을 두려고 한 것 같다.


https://groups.google.com/forum/#!topic/word2vec-toolkit/0eQMX2OgJEQ





subsampling이라고 설명한 방법에 대해서, 구현 원리에 대해 이해를 못해서 그냥 주석 처리 했다. 아래의 로직이 'the', 'a' 등의 빈도수는 많지만 relavant하지는 않은 단어들을 필터링 해주는 역할을 확률적으로 휴리스틱하게 걸러 줄 수 있다고 하는데 이해 못하는 소스를 무작정 쓸 순 없어서.... 그냥 주석 처리했다.

어떤 사람이 쓴 글에도 subsampling보다도 그냥 금칙어 사전을 만들어서 처리하는게 더 성능이 좋게 나타난다고 한다.


if (sample > 0) {

    double ran = (sqrt(vocab[word].cn / (sample * train_words)) + 1) * (sample * train_words) / vocab[word].cn;

    next_random = next_random * (unsigned int)1103515245 + 12345;

    if(ran < ((next_random & 0xFFFF) / (double)65536)){

        continue;

    }

}


porting 결과



위의 most_similar, dissimilar, words_nearest, similarity는 각 벡터간의 코사인 유사도를 측정해서 값을 계산 한 후 그 결과를 리턴하는 함수를 만든 것이다. word2vec에서는 지원해 주지 않기 때문에 따로 만들어야 한다.


most_similar 같은 경우는 query가 입력될 때마다 매번 모든 단어에 대한 코사인 유사도를 계산하기 때문에, 사전에 k-means 등을 활용하여 clustering을 해주는게 어떨까 하는 생각도 해본다.