Word2Vec #2, 기본적인 모델 구현(생성)하기

    우선 워드투벡터(Word2Vec)를 구현하기에 앞서 이 포스팅에 사용된 예제는 네이버 영화 리뷰 데이터이다. 네이버 영화 리뷰 데이터는 직접 github에 들어가서 데이터를 다운로드 받아서 특정 폴더에 저장한 후 사용하는 방법과 파이썬을 실행 하는것과 동시에 다운로드를 받는 2가지 방법이 존재한다. 여기서는 github에 있는 데이터를 파이썬을 실행할 때 접근하여 가져오는 방식을 사용하도록 한다.

     

    네이버 영화 리뷰 데이터

     

    e9t/nsmc

    Naver sentiment movie corpus. Contribute to e9t/nsmc development by creating an account on GitHub.

    github.com

     

    위 링크에 들어가면 네이버 영화 리뷰에 대한 자세한 설명이 있으며, 샘플로 보면 다음과 같다.

    id      document        label
    9976970 아 더빙.. 진짜 짜증나네요 목소리        0
    3819312 흠...포스터보고 초딩영화줄....오버연기조차 가볍지 않구나        1
    10265843        너무재밓었다그래서보는것을추천한다      0
    9045019 교도소 이야기구먼 ..솔직히 재미는 없다..평점 조정       0
    6483659 사이몬페그의 익살스런 연기가 돋보였던 영화!스파이더맨에서 늙어보이기만 했던 커스틴 던스트가 너무나도 이뻐보였다  1
    5403919 막 걸음마 뗀 3세부터 초등학교 1학년생인 8살용영화.ㅋㅋㅋ...별반개도 아까움.     0
    7797314 원작의 긴장감을 제대로 살려내지못했다.  0
    9443947 별 반개도 아깝다 욕나온다 이응경 길용우 연기생활이몇년인지..정말 발로해도 그것보단 낫겟다 납치.감금만반복반복..이드라마는 가족도없다 연기못하는사람만모엿네       0
    7156791 액션이 없는데도 재미 있는 몇안되는 영화 1
    

     

    id는 말그대로 아이디이며, document는 리뷰 내용, label은 1인 경우 긍정적, 0은 부정적인 내용이다. 즉 기본적으로 감정 분석에 최적화된 데이터셋이라 보면 된다.

     

    형태소 분석기

    영어는 공백을 기준으로 품사들이 완벽히 분리되어 별도의 형태소 분석기를 사용할 필요가 없지만 한글은 품사들을 공백 기준으로 분류할 수 없기 때문에 형태소 분석기를 별도로 사용해야 한다.

     

    여기서는 Okt 형태소 분석기(트위터 형태소 분석기)를 사용했는데 이건 어디까지나 속도에 민간한 취향차이라 생각하면 되고 본인이 선호하는 형태소 분석기(ex: komoran, kkma 등)가 있으면 그걸 사용하면 된다. 형태소 분석기에 대해서 사용한 적이 없다면 아래 형태소 분석기 포스팅을 먼저 보고 오는 것을 추천한다.

     

    형태소 분석의 개념과 konlpy로 사용 하기

    형태소 분석기, Okt(Open Korean Text) (구)트위터 형태소분석기

     

    Gensim 라이브러리

    워드투벡터를 구현하기 위해서는 Tensorflow와 같은 딥러닝 라이브러리를 활용하는 경우도 있지만, Gensim을 사용하게 되면 매우 편리하게 구현이 가능하다. 

     

    Gensim

     

    Microsoft Windows [Version 10.0.19042.867]
    (c) 2020 Microsoft Corporation. All rights reserved.
    
    (saibog) C:\project\saibog>pip install gensim
    Collecting gensim
      Downloading gensim-4.0.1-cp38-cp38-win_amd64.whl (23.9 MB)
         |████████████████████████████████| 23.9 MB 406 kB/s
    Requirement already satisfied: scipy>=0.18.1 in c:\anaconda3\envs\saibog\lib\site-packages (from gensim) (1.4.1)
    Requirement already satisfied: numpy>=1.11.3 in c:\anaconda3\envs\saibog\lib\site-packages (from gensim) (1.18.5)
    Collecting Cython==0.29.21
      Downloading Cython-0.29.21-cp38-cp38-win_amd64.whl (1.7 MB)
         |████████████████████████████████| 1.7 MB 1.6 MB/s
    Collecting smart-open>=1.8.1
      Downloading smart_open-5.0.0-py3-none-any.whl (56 kB)
         |████████████████████████████████| 56 kB 2.0 MB/s
    Installing collected packages: smart-open, Cython, gensim
    Successfully installed Cython-0.29.21 gensim-4.0.1 smart-open-5.0.0
    

     

     

    Word2Vec Source Code

    import urllib.request
    from gensim.models import Word2Vec
    from konlpy.tag import Okt
    
    urllib.request.urlretrieve("https://raw.githubusercontent.com/e9t/nsmc/master/ratings.txt", filename="ratings.txt")

     

    여기서는 파일을 실행할 때 내리는 것을 하기 때문에 urllib로 github의 ratings.txt 데이터를 직접 요청받아서 실행한다. 일단 파일 여부를 체크하기 때문에 한번 저장을 하면 2번째 실행할 때에는 저장을 할 필요가 없다. gensim을 실행할 때 아래와 같은 에러가 발생하면 라이브러리를 설치해야 된다.

     

    C:\Anaconda3\envs\saibog\lib\site-packages\gensim\similarities\__init__.py:15: UserWarning: The gensim.similarities.levenshtein submodule is disabled, because the optional Levenshtein package <https://pypi.org/project/python-Levenshtein/> is unavailable. Install Levenhstein (e.g. `pip install python-Levenshtein`) to suppress this warning. warnings.warn(msg)

    위와 같은 메세지가 발생 할 경우 pip install python-Levenshtein을 설치해도록 한다. Levenshtein은 다음을 위한 것이다.

     

    The Levenshtein Python C extension module contains functions for fast computation of

    • Levenshtein (edit) distance, and edit operations
    • string similarity
    • approximate median strings, and generally string averaging
    • string sequence and set similarity

    It supports both normal and Unicode strings.

     

    문자열의 연산을 위한 것으로 여기서의 문자열의 유사성(string similiarity)이 word2vec의 similarity에서 활용 되는 걸로 보인다.

     

     

    # 리뷰 데이터를 가져온다
    def set_review_data(file_nm):
        with open(file_nm, "r", encoding="utf8") as f:
            data = [line.split('\t') for line in f.read().splitlines()]
        return data[1:]

    리뷰데이터를 가져오기 위한 펑션이다. 리뷰데이터는 utf8로 되어 있기 때문에 encoding="utf8"을 지정해야 한다. ratings.txt 데이터는 탭(t) 기준으로 값들이 저장되어 있기 때문에 매라인별 \t로 split하여 data에 저장하며, 최종 return은 헤더값을 제외한 데이터를 리턴한다.

     

    # 시작 시간 세팅
    start_time = time.time()
    
    print('[BEGIN] 리뷰 데이터를 읽는다.')
    review_data = set_review_data('ratings.txt')
    print('review_data size->', len(review_data))
    print('[END] 리뷰 데이터를 읽는다. (', time.time() - start_time, ')sec')

    시간을 측정하기 위해서 시작 시간을 세팅하고, 첫번째로 urllib로 저장한 ratings.txt 파일을 set_review_data 펑션을 통해 읽으며, review_data에 저장한다.

     

    print('[BEGIN] 형태소 분석을 시작하여 명사를 추출한다')
    okt = Okt()
    nouns = [okt.nouns(datas[1]) for datas in review_data]
    print('nouns size->', len(nouns))
    print('[END] 형태소 분석을 시작하여 명사를 추출한다. (', time.time() - start_time, ')sec')

    Okt를 생성하고, review_data의 document 즉 리뷰 부분을 명사를 추출하는 메소드인 okt.nouns으로 실행하여 nouns 변수에 명사 리스트를 저장한다.

     

    t('[BEGIN] Word2Vec 학습 시작')
    model = Word2Vec(sentences=nouns, vector_size=200, window=4, hs=1, min_count=2, sg=1, workers=2)
    print('[END] Word2Vec 학습 시작 (', time.time() - start_time, ')sec')

    벡터 크기는 200개, 윈도우 크기는 4개, 소프트맥스(soft)를 사용하며, 단어는 최소 3개 이상 나온 것만 사용하고, skip-gram으로 모델을 만들며 학습을 돌리는데 패러럴을 2로 잡는다.

     

    print('[BEGIN] Word2Vec 학습 모델 저장')
    model.save('word2vec_okt.model')
    print('[END] Word2Vec 학습 모델 저장 (', time.time() - start_time, ')sec')
    
    print("말뭉치 개수 ->", model.corpus_count)
    print("말뭉치 내 전체 단어수->", model.corpus_total_words)

    학습을 수행하면 최종적으로 word2vec_okt.model이라는 파일로 저장되며 현재 모델의 말뭉치 개수와 말뭉치 안에 전체 단어수를 체크한다. 전체 단어수는 형태소 분석기의 성능에 따라서 값들이 달라진다.

     

     

    최종 실행결과

    [BEGIN] 리뷰 데이터를 읽는다.
    review_data size-> 200000
    [END] 리뷰 데이터를 읽는다. ( 0.47908735275268555 )sec
    [BEGIN] 형태소 분석을 시작하여 명사를 추출한다
    nouns size-> 200000
    [END] 형태소 분석을 시작하여 명사를 추출한다. ( 1094.9519219398499 )sec
    [BEGIN] Word2Vec 학습 시작
    [END] Word2Vec 학습 시작 ( 1135.609759092331 )sec
    [BEGIN] Word2Vec 학습 모델 저장
    [END] Word2Vec 학습 모델 저장 ( 1136.358758687973 )sec
    말뭉치 개수 -> 200000
    말뭉치 내 전체 단어수-> 1197110

    전체 돌리는데 1136초 라는 꽤 오랜 시간이 걸렸는데 대부분 명사를 추출하는 로직에서 오래 걸린 것을 확인할 수 있다. 동일한 테스트를 Komoran으로도 해보았는데

     

    [BEGIN] 리뷰 데이터를 읽는다.
    review_data size-> 200000
    [END] 리뷰 데이터를 읽는다. ( 0.39893341064453125 )sec
    [BEGIN] 형태소 분석을 시작하여 명사를 추출한다
    nouns size-> 200000
    [END] 형태소 분석을 시작하여 명사를 추출한다. ( 152.7668285369873 )sec
    [BEGIN] Word2Vec 학습 시작
    [END] Word2Vec 학습 시작 ( 192.36932706832886 )sec
    [BEGIN] Word2Vec 학습 모델 저장
    [END] Word2Vec 학습 모델 저장 ( 194.08514833450317 )sec
    말뭉치 개수 -> 200000
    말뭉치 내 전체 단어수-> 1076896

    코모란의 경우 명사 추출 속도가 Okt보다 훨씬 빠른 속도를 보여줬는데 문제는 전체 단어수이다. Okt의 경우 1197110개이고 코모란은 1076896개라는 생각보다 단어수의 차이가 존재하였다. 아무래도 Komoran의 경우 분석을 제대로 못할 경우 NA로 퉁 치는 경우가 있는데 이 경우가 많아서 생기는 것이 아닐까 싶다. 

     

    예를 들어 아래는 OKT와 Komoran의 분석 결과이다.

    ['8112052', '어릴때보고 지금다시봐도 재밌어요ㅋㅋ', '1']
    # 코모란
    [('어리', 'VA'), ('ㄹ', 'ETM'), ('때', 'NNG'), ('보고', 'JKB'), ('지금', 'MAG'), ('다시', 'MAG'), ('보', 'VV'), ('아도', 'EC'), ('재밌어요ㅋㅋ', 'NA')]
    
    # OKT
    [('어릴', 'Verb'), ('때', 'Noun'), ('보고', 'Noun'), ('지금', 'Noun'), ('다시', 'Noun'), ('봐도', 'Verb'), ('재밌어요', 'Adjective'), ('ㅋㅋ', 'KoreanParticle')]

    형태소 분석 내용을 보면 OKT의 경우 부사 등도 Noun으로 하는 경우가 있는 것으로 보이지만, 재밌어요ㅋㅋ의 경우 Komoran은 아예 해당 단어를 분석하지 못하지만 Okt의 경우 재밌어요와 ㅋㅋ를 분리하는 놀라운 분석 능력을 가지고 있다. 사실 취향 차이이고, 한번 형태소 분석을 했으면 데이터를 저장하여 불러오는 방식 등을 사용하면 되기 때문에 서비스에서 Komoran와 Okt의 경우는 형태소 분석 스타일이 얼만큼 서비스에 맞는지를 보면 될 것 같다.

     

    다음 포스팅은 생성된 Komoran과 Okt의 워드투벡 모델을 기반으로 사용하는 방법을 다뤄볼 예정이다.

     

    연관 포스팅

    Word2Vec #1, (개념, CBOW와 Skip-gram)

    Word2Vec #3, 모델 활용(사용)하기

    댓글

    Designed by JB FACTORY