랜덤 텍스트 생성하기

한국어로 랜덤 텍스트를 생성해봅시다. 어떻게 할 수 있을까요?

가장 간단하게는 고양이 한 마리가 키보드를 밟고 지나가게 할 수 있습니다. 그렇게 하면 이와 유사한 결과가 나오겠죠:

ㅕ키ㅔ츄ㅑㅋ응아 ㅝㄴㅇ 쿄ㅛㅍㄹㅎ튜허ㅓㅎㅠㅅㄴ마

하지만 이렇게 글자를 무작위로 나열하는 것은 아무런 의미를 가지지 않습니다. 일반적으로 한국어 텍스트는 초성, 중성, 종성으로 구성된 낱개의 음절들이 모여 단어를 이루고 [1] 이 단어들이 다시 모여 문장을 이루는데, 글자들을 랜덤으로 타이핑해서는 음절이나 단어를 이룰 수 있는 확률은 극히 희박하겠죠. 그래서 이번에는 고양이에게 음절을 타이핑하는 방법을 (어떻게든) 가르쳐봅니다:

로켘불됴약 니앙굷 췡바이꿻노기

이렇게 하고 보니, 한국어에서 일반적으로 보기 힘든 음절들이 자주 등장했다는 사실을 알게 됩니다. 보통 한국어에서는 ‘이’가 ‘앙’보다는, 특히 ‘꿻’이나 ‘굷’ 같은 음절보다 훨씬 빈번하게 등장하죠. 이번에는 일반적인 사용 빈도가 높은 음절들을 더 자주 등장시키도록 고양이를 다시 교육시킵니다:

다이는가 고다하에지 요그이데습

그렇게 해도 얻은 문자열이 온전한 문장이 되지는 않습니다. 그렇다면 각의 음절을 독립적으로(independently) 생성하지 말고, 선행하는 음절이 무엇이냐에 따라 현재의 음절을 생성해보면 어떨까요? 그러면 ‘하’ 다음에는 ‘다’가 자주 등장할 것이고, ‘그’ 다음에는 ‘리’와 ‘고’가 연속적으로 등장할 가능성이 높아질테니 우리가 실제로 쓰는 단어도 나올 수 있지 않을까요? 이런 과정을 수학적 용어로는 마코프 체인 이라고 합니다:

국의의하되고인정부가는 요구한 대통령은 2조 사면 기밀과 헌법률로 위하의 위하며

직전의 음절 하나가 무엇이냐에 따라 현재의 음절을 생성한 위의 “문장”은 “bigram” 또는 “2-그램”으로 생성된 것이지만, 만일 더 말이 되는 문장을 만들고 싶다면 직전 음절 여러개를 보는 “3-그램” 또는 “4-그램”을 시도해볼 수도 있습니다. 또는, 음절 단위보다 한 단계 높은 형태소 단위에서 문장을 생성해보는 것도 가능합니다. 실제 코드로 한 번 해보죠.

경고

아래 코드는 파이썬3로 작동하고 파이썬2에서는 작동하지 않습니다. 터미널에서 python3 generate.py 를 입력하면 실행할 수 있습니다.

#! /usr/bin/python3
# -*- coding: utf-8 -*-

import bisect
import itertools
import random

import nltk
from konlpy.corpus import kolaw
from konlpy.tag import Mecab # MeCab tends to reserve the original form of morphemes


def generate_sentence(cfdist, word, num=15):
    sentence = []

    # Generate words until we meet a period
    while word!='.':
        sentence.append(word)

        # Generate the next word based on probability
        choices, weights = zip(*cfdist[word].items())
        cumdist = list(itertools.accumulate(weights))
        x = random.random() * cumdist[-1]
        word = choices[bisect.bisect(cumdist, x)]

    return ' '.join(sentence)


def calc_cfd(doc):
    # Calculate conditional frequency distribution of bigrams
    words = [w for w, t in Mecab().pos(doc)]
    bigrams = nltk.bigrams(words)
    return nltk.ConditionalFreqDist(bigrams)


if __name__=='__main__':
    nsents = 5 # Number of sentences
    initstr = u'국가' # Try replacing with u'국가', u'대통령', etc

    doc = kolaw.open('constitution.txt').read()
    cfd = calc_cfd(doc)

    for i in range(nsents):
        print('%d. %s' % (i, generate_sentence(cfd, initstr)))
  • 출력 결과:

    0. 국민 은 법률 로 인한 배상 은 특별 한 영장 을 청구 할 수 있 어서 최고 득표자 가 제출 한 유일 한 때 에 의하 여 는 경우 를 선거 관리 할 수 없 을 포함 한 사항 은 청구 할 수 있 다
    1. 국민 투표 의 범죄 에 의하 여 발언 하 지 아니한 회의 는 요건 은 1988 년 으로 대통령 이 의결 한다
    2. 국민 경제 자문 기구 를 타파 하 기 위하 여 긴급 한 형태 로 정한다
    3. 국민 은 이 정하 는 헌법 시행 당시 의 심사 할 수 있 다
    4. 국민 의 기본 질서 를 진다
    

아직 많이 부족하지만 처음에 고양이가 타이핑했던 것보다는 훨씬 나아졌네요! 특히 형태소와 형태소 사이에 공백이 있어서 덜 예뻐보이지만, 그 정도는 충분히 개선할 수 있을 것 같아요. 게다가 이 모델은 단 하나의 문서를 기반으로 구축한 것이지만, 보다 큰 말뭉치를 이용하는 경우에는 굳이 형태소 분석도 필요하지 않을테고요. 그 외에도 이 모델을 발전시킬 수 있는 여지는 무궁무진하게 많습니다! 상상력을 발휘하여 다양하게 실험해보세요.

텍스트 생성에 대한 더 자세한 설명은 존 벤틀리의 생각하는 프로그래밍 (15장 3절) 의 내용을 참고해주세요.

한 걸음 더 나아가기 위해서는 언어 모델 을 이용해 생성한 랜덤 텍스트가 통계적인 관점에서 얼마나 말이 되는지 평가해볼 수 있습니다.

[1]

유니코드 문자 코드표

comments powered by Disqus
Fork me on GitHub

Translations