2016년 7월 18일 월요일

Word2Vec Vector Algebra Comparison - Python(Gensim) VS Scala(Spark)

요즘 각종 내외부 Text 데이타들이, 새롭게 재 조명 되며, 다양한 형태로 분석 활용 되고 있다. 대표적인 예가 Text Classification , Sentiment Analysis ,  Opinion Mining 등 일것이다. (이 용어들 또한 많이 겹치는 의미들 이다.) 이러한 기법들을 이용하면, 고객센터나 이메일로 접수되는 내용들을 자동분류 하고, 실시간 Dash Board 화 하여, 문제가 되는 부분을 빠르게 파악 하고 대처할 수 있다. 또한, SNS나 게시판, 댓글등을 분석하여, 평판분석을 할 수 있고, 문제 상황을 좀더 빨리 인지하여 대응 할 수 있으며, 경쟁사의 평판과도 비교해 볼 수도 있다. 좀더 적극적으로는 Chat봇 이나 토픽 검색, 추천 고도화 등으로 확장되어 활용되는 사례도 많이 보이고 있다.

오늘 정리해본 내용은 그러한 Text 처리에 있어, 전처리 Word Embedding 기법 중 간단하면서도 뛰어난 효율을 보이는 Word2Vec 에 대한 내용이다. 

  1. Word2Vec 알고리즘에 대하여?
    1. 정의는 Learning4j 에서의 정의를 인용하였다.
      1. "데이터의 양이 충분하면 Word2vec은 단어의 의미를 꽤 정확하게 파악합니다. 그리고 이를 이용하면 단어의 뜻 뿐만 아니라 여러 단어의 관계를 알아냅니다. 예를 들어 단어의 관계를 이용해 ‘남자’:’소년’ = ‘여자’:x 같은 관계식을 주면 x=’소녀’라는 답을 구할 수 있습니다. 단어 뿐만 아니라 더 큰 단위의 텍스트인 문장이나 문서를 분류하는데에도 Word2vec을 사용합니다. 예를 들어 문서를 군집화한 뒤에 결과를 이용하면 검색 엔진에서 문서의 분야별 검색(과학, 법률, 경제 등)이나 문장의 감정 분석, 추천 시스템을 만들 수 있습니다."
    2. Word2Vec 은 텍스트를 처리하는 인공 신경망이며, 두 개의 층으로 구성되어 있다. 심층 신경망은 아니지만, 심층 신경망의 전처리 단계로 많이 쓰인다. 
      1. 인공 신경망 작동 방식은 크게 위 2가지로 나뉜다.
    3. Word2Vec 알고리즘에 대하여 좀더 자세한 내용을 알고싶어 하는 분들은 이 글을 참고하면 될것 같다.
      1. 자연어 기계학습의 혁명적 진화! (나프다에 기재된 글)
      2. http://www.moreagile.net/2014/11/word2vec.html 


오늘 내가 정리하는 내용은 Word2Vec 을 실제 구현하는 2가지 큰 방법( Python vs Scala )에 대하여 비교 분석한 내용이다.


  1. 구현방법 1
    1. Python 계열로 구현해 보기.
      1. Gensim 으로 구현하면, Word2Vec 구현하기가 너무 쉽다.
      2. 게다가 다음과 같은 장점이 있다.
        1. Python 계열은 우선 설치도 간단하다.
          1. Anaconda 를 설치하자. 이거 하나로 모든게 해결된다.
          2. https://docs.continuum.io/anaconda/install
        2. Word2Vec 에서 생각해볼 수 있는 거의 모든 내용이, 매우 쉽게 사용가능한, 잘 정리된 메소드로, 거의 다 만들어져 있다.
        3. 사용예, 셈플도 매우 많다.
        4. 한글의 경우 좀 쓸만한 형태소 분석기들은 죄다, R이나 Python 계열이다. 때문에, 자연어 처리에 있어, Python으로의 선택은 이런 모듈들과 궁합이 좋아, 우선 가장 안전한 접근 일 수 있다.
          1. 특히, Konlp 나  Konlpy 가 좋다.
        5. 요즘 Python 은 GPU 를 쓰기도 좋고, CPython  등으로 인하여 속도도 매우 빠르다.
      3. 물론 다음과 같은 단점도 있다.
        1. 초 대용량으로 가는 경우 답이 없다.
        2. 기본적으로 아직 까지는 Single  머신으로 돌리는 것이 Default 이다. 물론 병렬로 돌리기 위한 몇몇 마이너한 방법들이 있으나, 아직 까지 그 길은 최적화 되어 있지 못하다.
      4. 기본적인 코드들은 인터넷에 널려 있다.
        1. 참고로 너무 쉽고, 매우 짧다.
        2. 살짝 드려다 보자면...
      5. 학습을 위해 준비한 데이타.
        1. 나무위키 데이타, 위키피디아 데이타, 그리고 모사의 블로그 크롤링 데이타 등이 사용되었다.
        2. 데이타를 조사제거하고, 의미없이 너무 긴단어 제거하고, 몇몇 전처리를 해 줘야 하는데, 이 역시 Konlpy 등을 이용하면, 어렵지 않게 전처리 가능하다. (물론 Data가 매우 매우 클 때는 Python 으로는 매우 오래 걸리는 작업이긴 하지만..)
      6. 대표적인 Vector Algebra 인 King:Man = Queen:Woman 예제를 돌려 보자. 
        1. 참고로 이 예제는 한글에서는 잘 맞지 않다. 그 이유는 왕 이라는 단어가, 한글에서는 동음 이의어가 존재하기 때문이다. (왕서방, 왕짜장, 왕 좋다. 킹 왕짱 등등 때문에 그렇다.)
      7. 그래서 중국:북경 = 대한민국:서울 의 Algebra로 실험을 해 보았다.
        중국 - 북경 = X - 서울 요렇게 표기하는 것도 가능할것이다.답은 대한민국이 나와야 한다. X 를 구하기 위해 위 식을 아래처럼 다시 바꾸어 보았다.
        X = 중국 - 북경 + 서울
    2. Algebra 수식 부분 수행코드는 아래와 같다.
      1. 코드는 허무하게시리 간단하다.
      2. 위 결과에서처럼 대한민국과 한국이 1, 2등 이다. 놀랍지 아니한가??? 나는 처음 이 결과 봤을때 깜짝 놀랐었는데...
  2. 구현방법2
    1. Scala On Spark 로 구현해 보기.
      1. Spark 로 구현해 보자. Spark ML 안에 Word2Vec 이 있으니, 망설일 필요가 없다.
      2. Spark 는 Mesos Cluster 로 구동하였고, 모든 데이타는 Hadoop 내 공유 저장소에 존재한다.
      3. 하지만, 막상 구현을 해보니, 일단 학습하고, 저장하고, 로드하는 Spark 가 제공하는 Example 외에, 실무에서 활용할 만한 Sample 이 너무 너무 없다.게다가, 메뉴얼이 매우 빈약하다.
        1. 여튼 코드는 간단하다. Sample을 찾긴 힘들지만...
      4. Algebra 예제는 2시간을 Goggling 해도 제대로 수행된는 Code를 찾기가 힘들다. 몇몇 Code가 stackoverflow 사이트 등에 메뉴얼 Base, 답변 글 형태로 Code가 존재하긴 하는데 작동하지 않는다. (아래에서 좀더 상세 설명)
      5. API Document 를 보고 Algebra 구현을 시도해 보았다.
        1. 1차 시도
          1. 메뉴얼만 보면, 위처럼 하면 되야만 한다. 
          2. 하지만 위처럼 하면, model.getVectors() 쪽에서 인자가 없다고 에러가 난다. 분명 API Doc 에는 getVectors() 가 인자 없는 메소드 인데 말이다.
          3. () 를 하지 말고 그냥 가져오면 되긴 한다. 
            1. val w2v_map = model.getVectors   # 메뉴얼상의 getVectors() 메소드는 존재하진 않는다.
            2. 소스를 까보면 getVectors 가 Map[String, Array[Float]] 형태의 자료 구조이다. 즉, 뒤에서 다시 언급하겠지만, 그냥 Map 이라고 생각하고 getVectors(key:String) 으로 변수를 끄집어 낼수는 있지만, getVectors()(key:String)는 에러가 난다.
            3. 위에서 getVectors()를 getVectors 로 고치면 위 에러는 해결 되지만, 사칙연산이 안되는 새로운 에러가 발생한다. 이는 뒤에서 다시 언급...
        2. 2차 시도.
          1. 메뉴얼을 보면 이렇게 해도 되야 될거 같은데, 일단, 에러...
          2. Vectors 를 사칙연산 가능하게 breeze Vector로 형변환 해주면 되긴 한다. 맨 뒤에서 다시 언급...
        3. IDE 로 가 보았다. (개인적으로 서버에서 Scala 코딩은 zepplin 이나 sbt 로 바로 코딩 하고 확인 하는걸 선호 하는데... 이런 경우는 IDE에서의 확인이 필요하다.)
          1. 확인 결과 getVectors 는 메소드가 아니었다. 공식 API Document 에는 getter method 처럼 되어 있음에도 말이다...
        4. Github 의 소스 코드를 직접 확인해 보니 아래와 같다.
          1. getVectors 의 형이 요런식이다. 확실히 메소드가 아니다.TT
        5. 3차시도
          1. getVectors 자체에 args(N)을 인자로 주고, vector algebra 연산을 해보았다.
          2. 역시나.... algebra 연산에서 에러가 난다. Python 이 아니니까...
          3. linear Algebra 를 위한 breeze Vector 로 형변환을 하고 시도하였다. 성공!
            1. findSynonmys 안에 넣을때는 다시 dense 로 형변환이 필요하다. Python 이 얼마나 편한지... 비교 되는 부분이다. 
        6. Algebra 를 수행한 결과는 아래와 같다. Python 의 경우와 Data Set 이 살짝 달라서 결과도 약간 다르다.(시점 차이로..) 여튼 요런식으로 결과가 나온다.

최종 결론은 다음과 같다.


  1. Word2Vec 을 구현하기 위해
    1. Gensim + Python
      1. 구현을 하기 위한 가장 손쉽고 빠른 접근 방법이다.
      2. 왠만한 모든 가공 처리가 일사천리로 진행 가능하다..막히는 부분이 거의 없다.
      3. 데이타 전처리 부 또한 걸출한 Open 모듈들이 매우 많다.
      4. 단, 데이타 크기가 수십기가가 넘어가면, 단일머신에서는 고사양이 아닌 경우 급격히 느려 진다.
      5. GPU 를 사용하여 한번 더 성능을 점프 업 할 수 있으나, 이 역시 한계가 있다.
      6. 최고사양의 GPU 머신이라 할지라도 수백기가가 넘는 텍스트는 불가능 한 것으로 보여진다. (각종 변태 같은 다른 방법은 고려하지 않음.)
    2. Spark + Scala (or Python) : (실험에 사용한 환경은 Spark + Mesos + Hadoop)
      1. Spark 의 MLlib 를 이용하는 것이므로, Scala 가 아닌 Python 으로도 가능하다. 
      2. Python on Spark 인 경우 형태소 분리 등 전처리는 Python 의 모듈을 그대로 활용 할 수 있으며, Python 형태소 분리 등의 전처리를 Gensim 과 달리 병렬 처리 구현 하기 수훨해 진다.
      3. 단일 머신에서 돌릴때, Gensim 에 비하여 매우 느리다. 일단, Spark 는 분산처리를 위한 소잡는 칼이기 때문일 것으로 위안을 삼아 본다.
      4. gensim 고사양 1대 vs Spark 고사양 5대를 비교 했는데, 극한의 Spark 튜닝을 하기 전에는 기본 비교 시 gensim 이 좀 더 빨랐다.
      5. 하지만 여러가지 튜닝을 해주니, 중사양 Spark 20대가 고사양 Gensim 1대보다 5배 정도 빠른 결과를 내 주었다. (이 부분은 별도로 Posting 을 해보겠다. 속도를 끌어 올리는데 매우 많은 실험과 극한의 config 설정 튜닝이 필요하였다.)
      6. 무엇보다, Gensim 고성능 서버 1대에서 절대 돌지 않는 양 (수백 GB 이상)의 데이타가 Spark Dev 클러스터 중사양 20대에서는 그런데로의 속도로 도는 것이 가능하였다. ( 이 부분도 별도로 Posting 해 보도록 하겠다. 각종 Memory 관련 이슈가 등장하는데, 이 부분을 해결하는것 또 한 매우 많은 실험과 극한의 설정 튜닝이 필요하였다. 속도도 속도 지만, 대 용량을 Spark ML 로 돌릴때에는 Memory 의 제약으로 인하여 각종 Trick 이 필요한데. 이 부분의 작업은 매우 지난한 작업 이었다.)
      7. 수 ~ 수십 TB 단위의 데이타로의 실험을 해보진 않았는데, 돌지 않을지도 모른다는 생각이 든다, 실험결과 Spark Submit  수행시 주는 옵션인 Driver Memory가 학습할 Text 데이타의 크기에 비례하여 많이 필요하였기 때문이다.  
    3. 결론
      1. 작은 양의 데이타는 Gensim + Python Win!
      2. 중간 이상 크기의 데이타는 어쩔 수 없이 Spark!
      3. 그 이상 크기의 데이타는, 곧 훨씬 더 큰 데이타와 훨씬 고사양의 훨씬 많은 Spark 노드에서 다시 한번 테스트 해볼 예정이지만, 위 2-7의 가설이 맞다면, 어쩔 수 없이... deeplearning4j! (물론 deeplearning4j 도 내부에서 Spark를 이용하고는 있지만..)
      4. 어느정도 이상의 데이타는 도메인을 나누어 학습을 시켜도 될 듯 싶다.
        1. 어느정도 이상의 데이타는 표본이 전수를 대표하는 성격을 가질 것이기 때문이다. 굳이 이세상의 모든 데이타를 하나의 model 로 합쳐서 학습할 필요가 없다는 생각이 든다.
        2. 백과 사전 몇개를 학습하여, 일반어 간의 거리를 관리하는 모델을 하나 만들어 놓고, 이후부터는 특정 도메인 별로 데이타를 수집하여 별개로 학습해도 될 것 같다.
          1. 예를 들어, 네이버 요리 카테고리 블로그를 따로 쭉 학습해서 요리 단어에 대한 학습을 하고 나면, 요리 박사가 될 수 있다.
          2. 상품명을 가지고 크롤링 하여 쭉 학습을 하고 나면 또 상품 박사가 될 수 있다.
          3. 결국은 중간 Classification 분류 대표명이 중요하다. 중분류 대표명이 있다면, 세상의 모든 단어에 해당하는 백과사전 Model 과 특정 분야에 특화된 Domain 사전 간에 중분류 대표 단어를 매개로 거리 Mapping 을 통하여, 다양한 조합 쿼리가 가능하기 때문이다. (단, 방향성 연산은 이 시나리오가 통하지 않는다. 복수 모델을 조합할때는 similarity 연산만 유용하다.)
          4. 백과사전 Model 로 부터 다양한 형태의 자연어 대화 질문을 Classification 대표 질문으로 분류해주고, Classfication 대표 질문으로 부터 해당 Class 에 맞는 Domain 특화 model 에 질의 하여 정확한 전문 용어를 끄집어 내는 것이 가능 할 것이기 때문이다.