본문 바로가기
Study/머신러닝

[6] 마르코프 체인과 LSTM으로 문장 생성하기

by 투말치 2020. 8. 15.

목차

    반응형

    마르코프 체인(Markov Chain)이란?

    마르코프 체인은 마르코프 특성을 가지고 이산적인 경우만 고려하는 것을 말한다.

    마르코프 특성은 과거 상태와 현재 상태가 주어졌을 때, 미래 상태가 과거 상태와 무관하게 현재 상태에 의해서만 결정되는 것을 말한다.  

     

    마르코프 체인으로 문장 생성하기

    마르코프 체인으로 문장을 만드는 과정은 다음과 같다.

    1. 문장을 단어로 분할(형태소 분석)합니다.
    2. 단어의 전후 연결을 딕셔너리에 등록합니다.
    3. 사전을 사용해 임의의 문장을 생성합니다.

    사전은 단어 하나씩이 아니라 단어를 몇 개씩 묶어서 사전으로 등록한다.

    예를 들면 "나는 커피를 마신다."라는 문장이 있으면 아래와 같이 사전으로 등록한다.

    나|는|커피

    는|커피|를

    커피|를|마신다

     

    <실습 1 - 마르코프 체인으로 문장 만들기>

    import os
    import codecs
    from bs4 import BeautifulSoup
    from konlpy.tag import Twitter
    import urllib.request
    import os, re, json, random
    
    #네이버 맞춤법 검사 요청에 user-agent 헤더 추가
    import requests
    
    
    # 마르코프 체인 딕셔너리 만들기 --- (※1)
    def make_dic(words):
        tmp = ["@"]
        dic = {}
        for word in words:
            tmp.append(word)
            if len(tmp) < 3: continue
            if len(tmp) > 3: tmp = tmp[1:]
            set_word3(dic, tmp)
            if word == ".":
                tmp = ["@"]
                continue
        return dic
    # 딕셔너리에 데이터 등록하기 --- (※2)
    def set_word3(dic, s3):
        w1, w2, w3 = s3
        if not w1 in dic: dic[w1] = {}
        if not w2 in dic[w1]: dic[w1][w2] = {}
        if not w3 in dic[w1][w2]: dic[w1][w2][w3] = 0
        dic[w1][w2][w3] += 1
    # 문장 만들기 --- (※3)
    def make_sentence(dic):
        ret = []
        if not "@" in dic: return "no dic" 
        top = dic["@"]
        w1 = word_choice(top)
        w2 = word_choice(top[w1])
        ret.append(w1)
        ret.append(w2)
        while True:
            w3 = word_choice(dic[w1][w2])
            ret.append(w3)
            if w3 == ".": break
            w1, w2 = w2, w3
        ret = "".join(ret)
        # 띄어쓰기
        params = urllib.parse.urlencode({
            "_callback": "",
            "q": ret
        })
        # 네이버 맞춤법 검사기를 사용합니다.
        # data = urllib.request.urlopen("https://m.search.naver.com/p/csearch/ocontent/util/SpellerProxy?" + params)
        data = urllib.request.urlopen("https://m.search.naver.com/p/csearch/ocontent/spellchecker.nhn?" + params)
        data = data.read().decode("utf-8")[1:-2]
        data = json.loads(data)
        data = data["message"]["result"]["html"]
        data = soup = BeautifulSoup(data, "html.parser").getText()
        # 리턴
        return data
    
    def word_choice(sel):
        keys = sel.keys()
        return random.choice(list(keys))
    # 문장 읽어 들이기 --- (※4)
    toji_file = "toji.txt"
    dict_file = "markov-toji.json"
    if not os.path.exists(dict_file):
        # 토지 텍스트 파일 읽어 들이기
        fp = codecs.open("BEXX0003.txt", "r", encoding="utf-16")
        soup = BeautifulSoup(fp, "html.parser")
        body = soup.select_one("body > text")
        text = body.getText()
        text = text.replace("…", "") # 현재 koNLPy가 …을 구두점으로 잡지 못하는 문제 임시 해결
        # 형태소 분석
       # twitter = Twitter()
        twitter=Twitter()
        malist = twitter.pos(text, norm=True)
        words = []
        for word in malist:
            # 구두점 등은 대상에서 제외(단 마침표는 포함)
            if not word[1] in ["Punctuation"]:
                words.append(word[0])
            if word[0] == ".":
                words.append(word[0])
        # 딕셔너리 생성
        dic = make_dic(words)
        json.dump(dic, open(dict_file,"w", encoding="utf-8"))
    else:
        dic = json.load(open(dict_file,"r"))
    # 문장 만들기 --- (※6)
    for i in range(3):
        s = make_sentence(dic)
        print(s)
        print("---")
    

     

     

     

    LSTM이란?

    LSTM은 RNN을 개선한 알고리즘이다.

    RNN은 Recurrent Neral Network의 약자로 스스로 반복하면서 이전 단계에서 얻은 정보를 지속하는 신경망을 말한다.

    RNN은 바로 전의 데이터박에 기억하지 못한다는 단점이 있어서 장기적으로 기억할 수 있는 기능을 추가한 LSTM이 만들어진 것이다.

     

     

    LSTM으로 문장 생성하기

    import codecs
    from bs4 import BeautifulSoup
    from keras.models import Sequential
    from keras.layers import Dense, Activation, Dropout
    from keras.layers import LSTM
    from keras.optimizers import RMSprop
    from keras.utils.data_utils import get_file
    import numpy as np
    import random, sys
    fp = codecs.open("./BEXX0003.txt", "r", encoding="utf-16")
    soup = BeautifulSoup(fp, "html.parser")
    body = soup.select_one("body")
    text = body.getText() + " "
    print('코퍼스의 길이: ', len(text))
    # 문자를 하나하나 읽어 들이고 ID 붙이기
    chars = sorted(list(set(text)))
    print('사용되고 있는 문자의 수:', len(chars))
    char_indices = dict((c, i) for i, c in enumerate(chars)) # 문자 → ID
    indices_char = dict((i, c) for i, c in enumerate(chars)) # ID → 문자
    # 텍스트를 maxlen개의 문자로 자르고 다음에 오는 문자 등록하기
    maxlen = 20
    step = 3
    sentences = []
    next_chars = []
    for i in range(0, len(text) - maxlen, step):
        sentences.append(text[i: i + maxlen])
        next_chars.append(text[i + maxlen])
    print('학습할 구문의 수:', len(sentences))
    print('텍스트를 ID 벡터로 변환합니다...')
    X = np.zeros((len(sentences), maxlen, len(chars)), dtype=np.bool)
    y = np.zeros((len(sentences), len(chars)), dtype=np.bool)
    for i, sentence in enumerate(sentences):
        for t, char in enumerate(sentence):
            X[i, t, char_indices[char]] = 1
        y[i, char_indices[next_chars[i]]] = 1
    # 모델 구축하기(LSTM)
    print('모델을 구축합니다...')
    model = Sequential()
    model.add(LSTM(128, input_shape=(maxlen, len(chars))))
    model.add(Dense(len(chars)))
    model.add(Activation('softmax'))
    optimizer = RMSprop(lr=0.01)
    model.compile(loss='categorical_crossentropy', optimizer=optimizer)
    # 후보를 배열에서 꺼내기
    def sample(preds, temperature=1.0):
        preds = np.asarray(preds).astype('float64')
        preds = np.log(preds) / temperature
        exp_preds = np.exp(preds)
        preds = exp_preds / np.sum(exp_preds)
        probas = np.random.multinomial(1, preds, 1)
        return np.argmax(probas)
    # 학습시키고 텍스트 생성하기 반복
    for iteration in range(1, 60):
        print()
        print('-' * 50)
        print('반복 =', iteration)
        model.fit(X, y, batch_size=128, nb_epoch=1) # 
        # 임의의 시작 텍스트 선택하기
        start_index = random.randint(0, len(text) - maxlen - 1)
        # 다양한 다양성의 문장 생성
        for diversity in [0.2, 0.5, 1.0, 1.2]:
            print()
            print('--- 다양성 = ', diversity)
            generated = ''
            sentence = text[start_index: start_index + maxlen]
            generated += sentence
            print('--- 시드 = "' + sentence + '"')
            sys.stdout.write(generated)
            # 시드를 기반으로 텍스트 자동 생성
            for i in range(400):
                x = np.zeros((1, maxlen, len(chars)))
                for t, char in enumerate(sentence):
                    x[0, t, char_indices[char]] = 1.
                # 다음에 올 문자를 예측하기
                preds = model.predict(x, verbose=0)[0]
                next_index = sample(preds, diversity)
                next_char = indices_char[next_index]
                # 출력하기
                generated += next_char
                sentence = sentence[1:] + next_char
                sys.stdout.write(next_char)
                sys.stdout.flush()
            print()

     

     

     

     

     

     

     

     

     

    반응형