본문 바로가기

활용/NLP

Word2vec 만들어보기

반응형

관련글: Word2Vec 개념


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

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

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

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

한국어로 된 글 모으기

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

  • Wikipedia: link
    • pages-articles.xml.bz2 파일을 받으면 된다.
    • (참고) 한국어판: 다운로드
  • 나무위키: link
  • 기타 등등..

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: 덤프에 있는 문서 목록을 출력하지 않는다.
    {"id": "", "revid": "", "url":"", "title": "", "text": "..."}

위와 같이 파싱이 되게 되는데, 이 json 파일을 한번 더 읽어서 plain text로 만들 수 있다.

전처리

이 예제에서 사용하는 전처리는 아래와 같다.

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

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

  • Hannanum에서 사용하는 품사 정보: link

아래 코드는 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는 한국어 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' 파일에 저장

gensim을 이용해서 word2vec을 만들었다. gensim이 자체적으로 처리해 주는 일은 아래와 같다.

  • 문서를 자동으로 로딩한다.
  • 단어들을 자동으로 One-hot encoding으로 바꿔준다.
  • CBOW 기법을 이용해서 Word2Vec을 학습한다. (옵션 지정시 Skip-gram 가능)
    • CBOW는 주변의 맥락(중심 단어 양옆에 있는 단어)을 보고 타깃 단어(중심 단어)를 학습하는 방법이다.
    • 학습 방법은 Word2Vec 문서에서 확인이 가능하다.
  • 단어들을 One-hot encoding된 index로, index를 원래 단어로 상호 변환한다.
  • Word2Vec 벡터값을 출력하고, 벡터 간의 연산을 수행하고, 특정 단어와 가장 가까운 벡터를 출력한다.

이와 같이, Word2Vec을 학습하고 사용하는 데 있어서 필요한 대부분의 기능을 코드 몇 줄만으로 처리할 수 있다.

Word2Vec 사용하기

from gensim.models import Word2Vec

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

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

 

반응형

'활용 > NLP' 카테고리의 다른 글

TextCNN  (0) 2020.04.12
Natural Language Processing  (0) 2020.04.12
Word2vec  (0) 2020.04.12
Text Embedding  (0) 2020.04.12