본문 바로가기
프로그램

[파이썬] NLP-Word2Vec로 단어 및 문장 유사도 분석

by 오디세이99 2022. 8. 24.
728x90
반응형

여러 방법으로 Word2Vec을 만들 수 있습니다.

여기서는 gensim package를 활용해서 Word2Vec을 만들겠습니다.

Word2Vec을 사용해 보면 놀라울 정도로 잘 동작한다는 것으로 알 수 있습니다.

더 정확성을 높이려면 학습량을 늘리면 될 것입니다. 대신 시간이 오래 걸리죠.

 

다음과 같은 순서로 진행 합니다.

 

1) 한글 데이터 받기

   1-1) Wiki 한글 데이터를 받습니다.

   1-2) 받은 bz2 압축파일을 풀어서 Text 파일로 만듭니다.

2) 데이터 전처리

- 특수문자 제거 및 품사 처리

3) Word2Vec 학습

4) Word2Vec 사용

 

 

1-1) Wiki 한글 데이터 받기

- 아래 정리된 방법으로 받습니다.

[파이썬] 위키피디아(Wiki) 한글 자료 Dump (tistory.com)

 

[파이썬] 위키피디아(Wiki) 한글 자료 Dump

Wiki 데이터를 전부 Dump 받으려고 시도했습니다. WikiExtractor를 사용하는 방법이 있는데, 제 PC에서는 에러가 발생해서 사용할 수가 없었습니다. 그래서 다음과 같이 작업을 진행했습니다. 1) 아래 Wi

question99.tistory.com

-  Wiki Dump 파일 : kowiki-20220820-pages-articles-multistream1.xml-p1p82407.bz2 (약 70 MB)

 

1-2) bz2 파일을 Text 파일로 변환합니다.

- step01/wiki_data.txt  (약 300 MB) 만들어집니다

import bz2

# bz2 파일 읽기
with open("kowiki-20220820-pages-articles-multistream1.xml-p1p82407.bz2", "rb") as f:
    data = f.read()
    decom_data = bz2.decompress(data).decode()
    print(decom_data[:500])  # 내용 확인 하기

# text 파일로 저장
f = open('wiki_data.txt', 'w', encoding='UTF-8')
f.write(decom_data)
f.close()

 

 

2) 데이터 전처리

- step02/wiki_data.txt(약 232 MB) 만들어집니다(소요시간 5시간 - PC 성능에 따라 달라지겠죠)

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

from konlpy.tag import Hannanum

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


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(), 'E:/RnD/NLP/wikiextractor/step02', file_name), 'wt', encoding='utf-8') as output:
            # step1에 있는, plain text를 읽어는다
            i = 0
            for input_line in input:
                if not input_line:
                    break
                print('run start=',i)
                # 진행률을 출력하기 위한 부분
                i += 1
                if i % 100 == 0:
                    print('[{}] {} finished'.format(file_name, i))

                # 특수 문자 제거 후 품사 분석 진행, 파일에 기록
                print('for =',1)
                text = remove_special_char.sub(' ', input_line)
                chk = text.replace(' ','')
                # text = text.replace('\n','')
                print('for =',2,' / han=',han,' / text=',text,' /len(text)=',len(text))
                if len(chk) == 0:
                    continue
                keyword = extract_keywords(han, text)
                print('for =',3)
                output.write(' '.join(keyword))
                print('for =',4)
                output.write('\n')
                print('run end=',i)


if __name__ == '__main__':
    pool = multiprocessing.Pool(processes=4)
    print('loading multiprocessing pool...')

    data = []
    for path, dirs, files in os.walk('E:/RnD/NLP/wikiextractor/step01/'):
        for file_name in files:
            data.append( (path, file_name) )
        worker(data[-1])
    pool.map(worker, data)
    pool.close()
    pool.join()
    print('End!')

 

 

3) Word2Vec 학습 (약 15분 소요)

- - model                     8,512 KB
  - model.syn1neg.npy       355,454 KB
  - model.wv.vectors.npy    355,454 KB

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('E:/RnD/NLP/wikiextractor/step02/')
sentences_train = SentenceLoader('E:/RnD/NLP/wikiextractor/step02/')

print('### sentence loader loaded.')

config = {
    'min_count': 5,  # 등장 횟수가 5 이하인 단어는 무시
    # 'size': 350,     # 300차원짜리 벡터스페이스에 embedding
    'vector_size': 350,     # 300차원짜리 벡터스페이스에 embedding
    'sg': 1,         # 0이면 CBOW, 1이면 skip-gram을 사용한다
    'batch_words': 10000,  # 사전을 구축할때 한번에 읽을 단어 수
    # 'iter': 10,      # 보통 딥러닝에서 말하는 epoch과 비슷한, 반복 횟수
    'epochs': 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.train(sentences_train, total_examples=model.corpus_count, epochs=config['epochs']) # Word2Vec training
model.save('model')                       # 모델을 'model' 파일에 저장

 

4) Word2Vec 사용

### Word2Vec 사용하기
from gensim.models import Word2Vec

# 모델 로딩
model = Word2Vec.load('E:/RnD/NLP/wikiextractor/model')

 

- 김치와 관련이 있는 단어 찾기. positive로 지정 합니다.

# 김치와 관련이 있느 단어
rtn1 = model.wv.most_similar(positive=['김치'])

print(rtn1)

-

[('싸먹', 0.6364246010780334), ('풋고추', 0.6320699453353882), ('반찬', 0.6305665373802185), ('깍두기', 0.6203721165657043), ('[[피클]]', 0.6132300496101379), ('찌개', 0.6129357814788818), ('양념장', 0.6110677123069763), ('부침개', 0.6106680035591125), ('고추장', 0.6103342175483704), ('불고기', 0.6088910698890686)]

 

- 김치와 관련이 없는 단어 찾기. negative로 지정합니다.

# 김치와 관련이 없느 단어
rtn1 = model.wv.most_similar(negative=['김치'])

print(rtn1)

-

[('FlashPt\n', 0.15908798575401306), ('유자광\n', 0.1489071547985077), ('동물생명과학', 0.1481540948152542), ('doctoral_students\n', 0.14059396088123322), ('회심\n', 0.1364152878522873), ('정상순\n', 0.13528765738010406), ('지도너비\n', 0.13396240770816803), ('지도_크\n', 0.13077586889266968), ('list7\n', 0.13057848811149597), ('삼중점K\n', 0.12981094419956207)]

 

- 여러 단어를 지정할 수 있습니다.

rtn1 = model.wv.most_similar(positive=['서울','대한민국','특별시'])

print(rtn1)

 

[('강동구]]', 0.6355381011962891), ('강서구청장\n', 0.6267351508140564), ('중랑구\n', 0.6261865496635437), ('서울특별자유시', 0.626021146774292), ('숭인동]]', 0.6239762306213379), ('측백나무\n', 0.6221975684165955), ('중랑구]]', 0.6213183403015137), ('강동구\n', 0.6201136112213135), ('종로구\n', 0.6167482733726501), ('[[서울시의회]]', 0.6147496104240417)]

 

- positive와 negative를 같이 사용할 수 있습니다.

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

 

[('[[도쿄]]', 0.4093792736530304), ('도쿄', 0.3986055850982666), ('교토', 0.393641859292984), ('보성중학교]]', 0.3928932249546051), ('인천', 0.39085716009140015), ('[[요코하마]]', 0.3900267481803894), ('성수중학교', 0.3894706666469574), ('[[보성중학교', 0.3870285749435425), ('양천구', 0.3832933306694031), ('동작구', 0.3815532624721527)]

 

 

print(model.wv.most_similar(positive=['서울', '중국'], negative=['한국']))

 

[('인천', 0.4297114610671997), ('남경', 0.42820557951927185), ('시안시', 0.41377440094947815), ('김포국제공항\n', 0.4102281630039215), ('베이징', 0.40923938155174255), ('[[톈진]]', 0.40780940651893616), ('뤄양', 0.3996354937553406), ('[[베이징]]', 0.3989080786705017), ('강동구', 0.39803001284599304), ('종로구', 0.39606156945228577)]

 

> 비슷한 단어 찾기

# 단어 비슷한 의미 단어 찾기
word1 = model.wv.n_similarity(['김치', '음식'], ['한국', '나라'])

word2 = model.wv.n_similarity(['김치', '음식'], ['빵', '음식'])


print(word1,'/',word2)

- 김치-음식/한국-나라는 0.2 이고, 김치-음식/빵-음식 은 0.8로 더 가깝게 판단합니다.

0.29520512 / 0.80048954

 

> 문장의 유사성 검사

# 문장의 유사성
s1 = '서울은 한국의 수도이다'
s2 = '서울은 한국의 수도이다' #corrected variable name

# s1.split() : ['서울은', '한국의', '수도이다.']
distance1 = model.wv.n_similarity(s1.split(), s2.split())

s1 = '서울은 한국의 수도이다'
s2 = '한국의 수도 서울이다'
distance2 = model.wv.n_similarity(s1.split(), s2.split())

s1 = '서울은 한국의 수도이다'
s2 = '도쿄는 일본의 수도이다' #corrected variable name
distance3 = model.wv.n_similarity(s1.split(), s2.split())

s1 = '제주도는 아름다운 섬 입니다'
s2 = '독도는 아름다운 섬 입니다' #corrected variable name
distance4 = model.wv.n_similarity(s1.split(), s2.split())

print(distance1,'/', distance2, '/', distance3, '/',distance4)

- 동일한 문장은 1 입니다.

- 단어의 위치만 바꾼 문장의 0.75,

- 서울-한국/도쿄-일본 은 0 인데, 이건 더 학습이 필요할 듯

- 제주도/독도는 1 로 나옵니다.

1.0 / 0.75130904 / 0.0 / 1.0
728x90
반응형

댓글