Word2Vec 만들어 보기 - BD-SEARCH/MLtutorial GitHub Wiki

Python을 이용해 한국어 Word2Vec을 만들어 봅시다. 대략적인 과정은 아래와 같습니다.

  1. 한국어로 된 글 모으기
  2. 형태소 분석 등 전처리하기
  3. gensim을 통해 Word2Vec 만들기

아래 튜토리얼을 따라하려면 아래와 같은 것이 설치되어 있어야 합니다.

  • Python 2.7, 3.3 이상
  • python의 pip으로 필요한 라이브러리 설치
    • python -m pip install gensim konlpy

한국어로 된 글 모으기

웹 상에서 손쉽게 구할 수 있는 한국어로 된 글은 아래와 같습니다.

덤프들이 다양한 포맷으로 되어 있습니다. plain text로 바꿔 주면 word2vec을 만들기 쉽습니다. 각 덤프별로 plain text로 변환하는 방법은 아래와 같습니다.

Wikipedia

Wikiepdia 덤프는 MediaWiki 포맷으로 되어 있습니다. 이 포맷을 plain text로 바꾸는 Tool은 많이 있지만, 여기서는 wikiextractor를 이용하겠습니다. 이 Tool을 이용하면 json/xml 형태로 제목, 링크와 본문을 저장합니다. 여기서는 json 포맷으로 저장합니다.

  1. attardi/wikiextractor로 이동해서, WikiExtractor.py를 다운로드합니다.
  2. 위 파일을 임의의 디렉토리에 저장합니다. 그 디렉토리에 output이라는 디렉토리를 만듧니다.
  3. 아래와 같은 명령어를 입력합니다.
python WikiExtractor.py [덤프 파일 이름] -o step1 --json -b 50M -q

위 명령어의 의미는 아래와 같습니다.

  • -o step1: step1 디렉토리에 plain text로 변환된 파일을 저장합니다.
  • --json: 출력 파일은 JSON 포맷입니다.
  • -b 50M: 파일 한 개의 크기를 50MB로 제한합니다. 출력이 50MB를 초과한다면 새 파일을 만듧니다.
  • -q: 덤프에 있는 문서 목록을 출력하지 않습니다.

전처리

특히 한국어 자연어 처리를 할 때, 전처리가 매우 중요합니다. 이 예제에서 사용하는 전처리는 아래와 같습니다.

  • Konlpy의 Hannanum 라이브러리를 이용해서 문장을 품사별로 분리
  • 관계언과 특수문자 제거

전처리에 따라 Word2Vec의 품질이 달라질 수 있으므로 다양한 시도를 해 보는 것도 좋습니다.

아래 코드는 step1 디렉토리에 저장된 plain text를 읽어서 품사별로 분리한 뒤, 관계언과 특수문자를 제거하고 그 결과를 step2 디렉토리에 저장하는 예제입니다.

# -*- coding: utf-8 -*-
import os
import re
import multiprocessing

from konlpy.tag import Hannanum

# tag reference: http://semanticweb.kaist.ac.kr/research/morph/


def extract_keywords(han, sentence):
    """
    품사 분석을 진행한 뒤 관계언(조사 등)이나 기호를 제거한다.
    """
    tagged = han.pos(sentence)

    result = []
    
    # 관계언 제거 (조사 등)
    for word, tag in tagged:
        if tag in ['F', 'N', 'P', 'M', 'I', 'X']: # 관계언 or 기호 제외
            result.append(word)
    return result


def worker(data):
    han = Hannanum()
    remove_special_char = re.compile(r'[^가-힣^A-z^0-9^.^,^?^!^ ]') # 한글, 영어, 기본 기호를 제외한 문자들
    
    path, file_name = data
    print("process file: {}".format(file_name))
    with open(os.path.join(path, file_name), 'rt', encoding='utf-8') as input:
        with open(os.path.join(os.getcwd(), 'step2', file_name), 'wt', encoding='utf-8') as output:
            # step1에 있는, plain text를 읽어는다
            i = 0
            for input_line in input:
            
                # 진행률을 출력하기 위한 부분
                i += 1
                if i % 100 == 0:
                    print("{}] {} finished".format(file_name, i))
                  
                # 특수 문자 제거 후 품사 분석 진행, 파일에 기록
                text = remove_special_char.sub(' ', input_line)
                keyword = extract_keywords(han, text)
                output.write(' '.join(keyword))
                output.write('\n')
                
                
if __name__ == '__main__':
    pool = multiprocessing.Pool(processes=4)
    print('loading multiprocessing pool...')

    data = []
    for path, dirs, files in os.walk('step1/'):
        for file_name in files:
            data.append( (path, file_name) )
    pool.map(worker, data)
    pool.close()
    pool.join()

Word2Vec 만들기

같은 디렉토리에서 아래 script를 실행시키면 word2vec이 만들어집니다.

아래 script는 http://blog.theeluwin.kr/post/146591096133/%ED%95%9C%EA%B5%AD%EC%96%B4-word2vec 를 참고하여 만들었습니다.

# -*- coding: utf-8 -*-

import multiprocessing
import os

import gensim

class SentenceLoader(object):
    def __init__(self, source_dir):
        self.source_dir = source_dir
        
    def __iter__(self):
        for path, dirs, files in os.walk(self.source_dir):
            for file in files:
                with open(os.path.join(path, file), 'rt', encoding='utf-8') as f:
                    for line in f:
                        yield line.replace('\\n', '').replace(',', '').split(' ')
                        
sentences_vocab = SentenceLoader('step2/')
sentences_train = SentenceLoader('step2/')

print("sentence loader loaded.")

config = {
    'min_count': 5,  # 등장 횟수가 5 이하인 단어는 무시
    'size': 350,  # 300차원짜리 벡터스페이스에 embedding
    'sg': 1,  # 0이면 CBOW, 1이면 skip-gram을 사용한다
    'batch_words': 10000,  # 사전을 구축할때 한번에 읽을 단어 수
    'iter': 10,  # 보통 딥러닝에서 말하는 epoch과 비슷한, 반복 횟수
    'workers': multiprocessing.cpu_count(),
}

model = gensim.models.Word2Vec(**config) # Word2vec 모델 생성
model.build_vocab(sentences_vocab) # corpus 개수를 셈
print('model.corpus_count: {}'.format(model.corpus_count))
model.train(sentences_train, total_examples=model.corpus_count, epochs=config['iter']) # Word2Vec training
model.save('model') # 모델을 'model' 파일에 저장

Word2Vec 사용하기

from gensim.models import Word2Vec

# 모델 로딩
model = Word2Vec.load('model')

print(model.vw['컴퓨터']) # 컴퓨터의 word vector 출력
print(model.most_similar(positive=['서울', '일본'], negative=['한국']))
# "서울 - 한국 + 일본" vector와 가장 가까운 단어 출력