Word Embedding, Factorization, and Personalization
여러 가지 개념이 혼재돼있습니다. 단어의 원래 뜻과 다르게 해석한 여지가 있습니다. 이 글에서 설명한 것이 절대적으로 맞다고 생각하면 위험합니다. 저는 제게 필요한 것으로 아전인수격으로 정의, 사용했을 개연성이 높음을 미리 경고합니다. 한글화된 용어를 별로 좋아하지 않지만 필요에 따라서 (국내에서 통상적으로 사용하는 경우) 일부 용어는 한글화했습니다.
2017년은 나름 공부하는 해로 정하고 그동안 미뤄놨던 논문들을 읽기 시작했습니다. 벌써 4주차가 됐는데도 여전히 논문을 읽고 있으니 지금의 흐름은 나름 오래 갈 것 같습니다. 한동안은 업무에 필요하거나 주목받은 논문 한두편을 짧게 읽은 적은 있지만, 연구실에 있을 때처럼 그냥 손에 잡히는 논문이나 엮인 논문들을 계속 읽어가는 것은 참 오랜만입니다. 아무래도 딥러닝이나 AI와 관련된 논문들을 많이 읽고 또 오픈소스나 라이브러리를 내려받아서 실행해보는 것을 한동안 계속할 예정입니다.
최근에 관심있게 보고 있는 논문은 워드 임베딩 Word Embedding 관련 논문입니다. 더 구체적으로 2013년에 처음 소개된 후로 많은 연구자들과 개발자들의 주목을 받은 Word2Vec과 후속의 관련 논문들입니다. Word2Vec은 딥러닝 커뮤니티에서 자주 언급되는데, 학습 구조상으로 인공신경망 ANN을 닮았지만 엄밀히 딥러닝의 범주에 넣기에는 구조가 매우 간단합니다. 사전 (또는 vocabulary) 사이즈의 one-hot 인코딩을 인풋으로 받기 때문에 통상의 딥러닝의 인풋에 견줄만하지만 은닉층 hidden layer이 하나뿐인 매우 간단한 shallow 네트워크입니다. 그리고 인닉층의 activation function도 통상의 sigmoid나 ReLU 등의 비선형 함수가 아닌 그냥 선형함수를 사용하는 가장 심플한 MLP의 구조를 따르는, 그리고 딥러닝에서 pre-training에 많이 사용하는 auto-encoder를 많이 닮았습니다.
Word2Vec은 CBOW continuous bag of words나 skip-gram으로 학습을 하는데, 이것에 대한 자세한 설명은 생략합니다 (다음의 링크 참조). 그리고 word2vec의 메커니즘을 자세히 알고 싶은 분들은 word2vec을 처음 제안했던 Mikolov의 2013년 논문을 찾아보기 보다는 이후에 나왔던 Xin Rong의 'word2vec Parameter Learning Explained http://www-personal.umich.edu/~ronxin/pdf/w2vexp.pdf'를 찾아서 읽어볼 것을 권합니다. word2vec의 구조와 parameter update 방법/수식 등을 친절히 설명해놨습니다. (배경 지식없이 Mikolov의 논문을 보면 좀 욕 나옴.)
word2vec이 word embedding을 효과적으로 구현해서 많은 이들의 주목을 받은 후에, GloVe라는 알고리즘도 등장했습니다. (참고. GloVe: Global Vectors for Word Representation http://www-nlp.stanford.edu/pubs/glove.pdf) 자료를 찾아보면 GloVe가 단어를 벡터로 표현하는데 word2vec보다 낫다는 평도 있습니다. 일반적인 입장에서 볼 때 word2vec과 glove는 큰 차이는 없는 것같고, 필요하다면 둘 다 테스트해보고 자신의 문제에 더 맞는 것을 선택하면 됩니다. 이 둘이 어떤 차이가 있을까?를 조사하면서 'don't count, predict! http://www.aclweb.org/anthology/P14-1023'라는 논문을 읽었는데, 이 논문에서 word embedding 또는 word representation의 두가지 방식을 잘 설명해줍니다. (논문의 결론은 GloVe의 counting보다는 word2vec의 predictive가 더 낫다고...)
전통적으로 word embedding은 counting에 기반합니다. 보통 VSM vector space model로 알려진 N x M의 word-document matrix나 word co-occurrence를 표현한 N x N matrix를 만드는 것은 counting방식입니다. NM행렬은 문서로 단어를 표현하고, NN 행렬은 다른 단어로 단어를 벡터로 표현한 것입니다. 그런데, 이런 단어벡터는 보통 수십만 이상의 고차원으로 정의되기 때문에 데이터를 저장한다거나 벡터 연산을 하는데 실효성/실용성이 떨어집니다. 그래서 dimension reduction 기법을 사용해서 고차원 벡터의 의미를 최대한 유지하면서 저장공간도 줄이고 연산도 쉽게하는 저차원 벡터로 만듭니다. 그렇게 저차원의 단어 벡터가 통상 말하는 word embedding입니다.
저차원으로 줄이는 방식은 단어를 특정 카테고리로 매핑해서 카테고리 벡터로 표현하는 syntactic 방식이 생각하기에 가장 쉬운 방식일 듯합니다. 하지만 인위적으로 카테고리를 정하는 것이 만만치가 않고, 또 단어를 카테고리로 매핑하는 방식도 쉽다고는 말하기 어렵습니다. 물론 분류 classification 알고리즘을 사용하면 된다라고 설명하면 되지만... 서비스를 기획/개발하면서 카테고리 작업을 해보신 분들은 카테고리를 잘 정의하면 여러모로 좋다는 것은 잘 알지만, 그걸 깔끔하게 잘 만들기가 매우 어렵다는 것을 압니다. 처음에는 카테고리를 잘 정의했다고 생각하지만, 나중에 이상한 새로운 데이터가 출현했을 때 기존 카테고리와 맞지 않는 문제도 있고, 카테고리를 어느만큼 세밀하게 정의할 것인가 등의 많은 이슈들이 터져나옵니다.
Supervised 분류가 어렵다면, unsupervised를 고려할 수 있습니다. 그래서 클러스터 방식을 사용해서 군집화합니다. 그렇게 만들어진 클러스터1부터 클러스터c까지 매핑하면 c차원의 벡터를 만들 수 있습니다. 하지만, 클러스터 방식은 만들어진 벡터의 robustness에 의문이 생깁니다. 학습데이터가 바뀔 때마다 각각의 데이터가 다른 클러스터로 군집되면 같은 데이터가 다른 벡터로 표현될 수가 있다는 의미입니다. 그래서 기존의 클러스터 구조를 유지하면서 새로운 데이터를 수용할 수 있는 방식이 필요합니다. 그리고 클러스터는 분류와 개념상 더 비슷한/연결고리가 있는 것이라서 먼저 설명했지만, 가장 일반적인 방식은 PCA principle component analysis입니다. PCA는 행렬에서 고유값/고유벡터를 찾아서 분해하는 SVD singular value decomposition 메커니즘을 기본적으로 따릅니다. 이 SVD를 텍스트 마이닝에 바로 적용한 것이 보통 LSA/LSI Latent Semantic Analysis/Indexing입니다. PCA에서 principle과 SVD애서 singular, LSA의 latent, 그리고 행렬의 eigen(고유)이 결국 같은 걸 의미합니다. 이를 통해서 고차원의 행렬을 저차원의 행렬들로 분해합니다.
SVD와 같이 행렬을 저차원으로 행렬의 곱으로 분해하는 것을 보통 matrix factoriztion이라고 합니다. 즉 행렬을 인수분해하는 것입니다. SVD는 3개의 행렬로 분해하지만, 최근에는 그냥 2개의 저차원 행렬로 분해하는 여러 방식도 많이 제안됐습니다. 대표적으로 Non-negative Matrix Factorization인데, 이는 원래 고차원 행렬과 저차원 행렬 모두 음수가 아닌 값으로 채워지도록 분해하기 때문에 붙여진 이름입니다. 어쨌든 NMF를 사용하면 고차원 행렬을 2개의 저차원 행렬의 곱으로 표현할 수 있습니다. Word-document matrix를 SVD로 인수분해하든 NMF로 인수분해하든 word 쪽의 저차원 매트릭스를 단어 벡터로 볼 수 있습니다. 이렇게 여러 가지 방법으로 저차원의 단어벡터를 만들 수 있습니다.
다시 counting vs predictve로 돌아가서, 이상에서 길게 설명한 word-document matrix 또는 word-co-occurrence matrix를 factorization하든 카테고리로 매핑하든 클러스터로 묶든 이런 co-occurrence 데이터로 단어벡터를 만드는 것이 counting 방식입니다. 반대로 word2vec은 알고 싶은 단어의 주변 context 단어들의 벡터로 표현하는 것이 predictive 방식입니다. 즉 컨텍스트로 의미를 유추/예측한다는 뜻입니다. 그런데, counting 방식에서 co-occurrence의 카운팅을 문서나 문장 단위가 아니라, word2vec처럼 로컬의 컨텍스트 단어로 window를 축소해서 계산한다면 counting방식이 predictve방식과 큰 차이가 날까?라는 의심이 듭니다. Co-occurrence를 카운팅할 때 global하게 볼 것인가 아니면 local로 볼 것인가의 차이인 듯도 한데... 실제 co-occurrence matrix를 로컬의 context만으로 표현할 수 있습니다. Window 사이즈를 어떻게 잡느냐에 따라서 로컬 정보만을 취할 수도 있고 글로벌 정보를 취할 수도 있습니다. 이건 k-NN에서 k의 값을 어떻게 잡느냐에 따라서 민감도가 달라지는 것과 크게 달라보이지 않습니다. 물론 연산방식이 달라서 결과가 다르게 나올 것 같지만, word2vec에서 사용하는 context만으로 co-occurrence를 구해서 MF든 GloVe든 구하면 결과가 얼마나 많이 다를지 살짝 의문이 듭니다.
... 제가 이런 고차원의 단어 벡터를 저차원의 단어 벡터로 표현하는 것에 관심을 가지는 것은 본격적으로 NLP나 텍스트 마이닝을 하겠다는 것보다는 예전부터 계속 해왔던 추천 그리고 광고랭킹에 이런 기술을 적용해서 효과를 낼 수 있다는 가능성 때문입니다. 단순히 사용자가 봤던 상품/컨텐츠 ID의 시퀀스를 word2vec이든 GloVe 알고리즘에 인풋으로 넣으면 상품/컨텐츠ID의 벡터가 나오고, 그 벡터의 유사도 계산을 통해서 관련상품 또는 관련 컨텐츠는 쉽게 추천해줄 수 있습니다. 기존에 사용하든 CF나 MF 방식도 결국 이걸 구현했던 것이니 별반 차이가 없습니다. 컨텐츠ID를 벡터로 표현해서 바로 적용하는 것도 있지만, 사용자의 벡터화를 통해서 개인화 추천에 바로 적용할 수도 있습니다. 첫째는 유사 사용자를 벡터 연산으로 바로 찾아낼 수도 있고, 사용자 벡터와 컨텐츠 벡터의 관계를 Neural Translation에서 사용한 것과 유사한 방식으로 찾아낼 수도 있습니다. 그리고, MF 추천 방식에서 원래 사용자 벡터와 컨텐츠 벡터의 곱으로 연관성을 측정했습니다.
현재 추천이나 광고랭킹 시스템이 고도화됐더라도 한 사용자의 모든 활동 이력을 raw data 수준으로 활용하는 곳은 별로 없다고 봅니다. 성별이나 연령, 또는 관심사 등으로 뭉뚱그려서 (앞서 설명한 카테고리 방식) 개인을 설명합니다. 그러다 보면 추정이 잘못된 경우도 빈번하고 카테고리에서 설명했듯이 개인의 히스토리가 온전히 카테고리로 매핑되지도 않습니다. 그리고 역으로 raw 데이터를 그대로 활용해서 추천에 이용하는 것이 가능하더라도 raw 데이터에는 outlier가 포함돼서 어느 정도는 데이터를 뭉게는 과정이 필요합니다. 사용자의 이력을 엄청나게 긴 벡터라고 생각한다면 이를 적절히 짧은 벡터로 만들 수 있다면 활용성은 무궁무진해집니다.
사람이든 객체든 그걸 설명하는 데이터를 앞으로 엄청나게 많아 질 것입니다. 하지만 데이터가 많아진다고 해서 그걸 모두 적절히 활용한다는 것은 아닙니다. 빅데이터라는 신드롬에 편승해서 데이터가 모이기만 하면 가치가 만들어지는 것 같은 신기루에 빠졌습니다. 하지만 모든 데이터는 적절히 관리돼고 또 처리될 수 있는 수준으로 압출돼야 비로소 가치적 행위로 이어집니다. 물론 고차원의 데이터를 그대로 활용할 수 있다면 문제가 없겠지만, 아직은 많은 곳에서는 적절한 양으로 요약해야 제대로 사용할 수 있습니다. 고차원의 데이터가 손실없이 저차원으로 표현된다면... 그런 측면에서 계속 word embedding 기술도 탐독했고 각종 factorization이나 dimension reduction 기술을 공부하고 있습니다. (하지만, 전 그걸 코드화하는 것에 참 부족해서... 그냥 늘 머리 속으로 공부만... 다행히 요즘은 오픈소스가 잘 돼있어서 직접 구현할 필요가 많이 줄어들었습니다. 하지만 가끔은 그걸 이용하는 게 더 귀찮을 때도... 최근 움직임 중 하나는 클라우드를 AIaaS로 활용하는 것도 있지만 좀 경계는해야 할 듯...)
오랫동안 여러 논문들을 읽으면서 여러 번 글을 적고 싶었지만 오늘 다양한 주제를 하나의 글로 만들었습니다. 여러 개념들이 일반적으로 통용되는 것과 다른 식으로 표현돼거나 또는 곡해한 부분이 있을 수 있습니다. 제 나름의 시각에서 해석한 것들이 많기 때문에, 이걸 원래 가지고 있던 의미를 다시 확인해보거나 자신의 문제나 시각에 맞도록 재해석하는 것이 필요합니다. 그것까지 제가 해줄 수는 없습니다. 물론 관련해서 도움을 요청한다면 또 다른 저의 시각에서 조언은 해줄 수는 있을지도...
word2vec을 다루는 많은 글들이 있습니다. 하지만 참 부족하다고 느꼈습니다. 단순히 word2vec의 결과로 벡터 연산을 할 수 있다느니 아니면 단순히 word2vec 오픈소스를 사용하는 방법만을 설명해놓은 중복된 정보가 너무 많습니다. 제가 늘 아쉬웠던 것은 실제 word2vec이 어떤 메커니즘/수식을 통해서 도출되는지였는데, 다행히 앞서 언급했던 논문 (word2vec explained)에서 많은 궁금증을 해결했습니다. 뿐만 아니라, word2vec을 통해서 단순히 단어의 벡터 연산 이상으로 어떤 곳에 어떻게 활용했는지를 정리한 글도 별로 보지 못했습니다. 인터넷이라는 공간이 긍정적인 면도 많지만, 때로는 너무 한쪽에 치우친 정보만이 넘쳐나는 공간이 돼는 듯도 해서 안타까움도 있습니다. 제가 많이 알지도 못하고 글재주도 부족하지만 조금은 다른 측면에서 글을 적으려는 이유도 이런 안타까움이 한몫했습니다.
다시 경고하지만, 제가 틀린 것을 적었을 수도 있습니다. 제가 이해한 것을 적은 것이니 잘못된 것은 걸러들으시고 또 알려주시면 감사하겠습니다.
===
반응형