티스토리 뷰

시작하며

본 포스트는 Unsupervised Text Summarization using Sentence Embeddings(Kushal Chauhan)을 번역한 글입니다.

(참고) 일부 내용은 원본과 다를 수 있습니다. (무단 전재/재배포 금지)

 

  • From Kushal Chauhan,
    안녕하세요 독자 여러분! 저는 정보 기술을 전공하고 있는 학석사 통합 과정 마지막 학년을 다니고 있는 학생입니다. 저는 현재 NLP Research 인턴을 Jatana.ai에서 하고 있습니다. 이 글에서는 파이썬으로 문서를 요약하는데 사용한 접근방법에 대해 소개하고자 합니다. 이 일은 Jatana에서 일하는 제 멘토가 제게 배정해준 멋진 업무 중 하나였습니다.

문서 요약이란?

문서 요약은 원본 자료(source)로 부터 가장 중요한 정보를 정제(distil)하는 과정입니다. 그 결과로 특정 사용자/독자와 업무를 위한 요약된 형태의 텍스트가 생성됩니다.

Advances in Automatic Text Summarization, 1999 (페이지 1)

사람은 일반적으로 요약을 잘하는 편입니다. 사람은 텍스트 문서의 의미를 이해하는 능력을 가지고 있으며, 핵심적인(sailent) 특정을 뽑아 자신의 언어로 문서를 요약할 수 있기 때문입니다. 하지만 자동적인 텍스트 요약 기법은 최근에 특히 중요해졌습니다. 데이터가 너무나 많아졌는데, 사람이 이러한 데이터를 모두 파악하고 이해하기에는 시간과 역량이 충분하지 않기 때문입니다. 다음은 왜 자동화된 문서 요약이 필요한지에 대한 이유입니다.

  1. 요약은 읽는 시간을 줄여준다.
  2. 여러 문서를 조사할 때 요약된 정보는 선택 과정을 더욱 쉽게 해준다.
  3. 자동화된 문서 요약은 자료 탐색(indexing)에 더욱 효과적이다.
  4. 자동화된 문서 요약 알고리즘은 사람이 직접 요약하는 것보다 덜 편향적이다.
  5. 개인화된 정보를 제공할 수 있기 때문에 질문 답변 시스템에서 개인화된 요약이 유용하다.
  6. 자동/반자동 요약 시스템은 상업적 요약(abstract) 서비스에서 처리할 수 있는 텍스트 문서의 수를 늘려준다.

텍스트 요약 방법론의 종류

텍스트 요약 기법은 여러 종류로 분류할 수 있습니다.

<사진>

입력 형식에 따라

  1. 길이가 짧은 단일 문서가 입력 문서인 경우. (초기에 등장한 여러 요약 기법은 이러한 단일 문서 요약을 다루었습니다.)
  2. 어느정도 긴 텍스트를 가진 다중 문서가 입력 문서인 경우.

목적에 따라

  1. 일반 목적(Generic), 요약할 문서의 내용이나 분야에 대한 어떠한 가정도 없는 모형을 만드는 것으로 모든 입력 문서를 동일한 관점에서 다룹니다. 대부분의 문서 요약 기법은 문서를 일반적인 목적으로 요약하고 있습니다.
  2. 분야 한정 목적(Domain-specific), 더욱 정확한 요약을 위해 특정 분야의 사전 지식(정보)를 사용하는 모형을 만듭니다. 예를 들어서 특정 분야의 연구 논문(생체의학 분야 논문 요약 등)을 요약하는 것을 말합니다.
  3. 쿼리 기반 목적(Query-based), 입력된 텍스트의 자연어 질의에 응답(answer)하는 정보만 포함하는 요약 모형을 만듭니다.

출력 형식에 따라

  1. 추출 방식(Extractive). 입력된 텍스트로부터 중요한 문장을 추출하여 요약문을 만듭니다. 대부분의 요약 기법은 본질적으로 추출 방식을 사용하고 있습니다.
  2. 초록 방식(Abstractive). 논리 정연한(coherent) 요약문을 제공하기 위해 사람처럼 직접 구(phrase)나 문장(sentence)을 만듭니다. 이 접근방법은 확실히 더 매력적입니다만 추출 방식보다 구현하기가 훨씬 어렵습니다.

여기서 수행할 것

파이썬을 이용하여 작성된 언어(영어, 네덜란드어, 프랑스어 등등)에 따라 이메일을 요약하는 것이 이 포스트의 목표였습니다. 텍스트 요약을 위해 가장 널리 알려진 데이터셋은 문서나 아티클입니다. 이러한 데이터는 짧은 이메일과 확연히 다르기 때문에 지도 학습으로 구현된 모델은 제 성능을 발휘하지 못할 것입니다(may suffer from poor domain adaptation). 그러므로 저는 요약문을 편향없이 예측(unbiased prediction)하기 위해 비지도 학습 방법을 찾고자 하였습니다.

이제 모델 파이프라인을 구성하는 단계들에 대해 이해해보도록 하겠습니다.

문서 요약 모델 파이프라인

제가 도입한 문서 요약 접근 방법은 이 논문에서 영감을 받았습니다.

텍스트 요약 과정을 나누어보면 아래 그림과 같습니다.

<<그림>>

1단계) 메일 정제

이 단계를 왜 해야하는지 알기 위해 전형적인 이메일 문서 예제를 보도록 하겠습니다.

  • 영문 메일 예제:
Hi Jane,

Thank you for keeping me updated on this issue. I'm happy to hear that the issue got resolved after all and you can now use the app in its full functionality again. 
Also many thanks for your suggestions. We hope to improve this feature in the future. 

In case you experience any further problems with the app, please don't hesitate to contact me again.

Best regards,

John Doe
Customer Support

1600 Amphitheatre Parkway
Mountain View, CA
United States
  • 노르웨이어 메일 예제:
Hei

Grunnet manglende dekning på deres kort for månedlig trekk, blir dere nå overført til årlig fakturering.
I morgen vil dere motta faktura for hosting og drift av nettbutikk for perioden 05.03.2018-05.03.2019.
Ta gjerne kontakt om dere har spørsmål.

Med vennlig hilsen
John Doe - SomeCompany.no
04756 | johndoe@somecompany.no

Husk å sjekk vårt hjelpesenter, kanskje du finner svar der: https://support.somecompany.no/
  • 이탈리아어 메일 예제:
Ciao John, 

Grazie mille per averci contattato! Apprezziamo molto che abbiate trovato il tempo per inviarci i vostri commenti e siamo lieti che vi piaccia l'App. 

Sentitevi liberi di parlare di con i vostri amici o di sostenerci lasciando una recensione nell'App Store!

Cordiali saluti, 

Jane Doe
Customer Support

One Infinite Loop
Cupertino
CA 95014

위 예제에서 볼 수 있듯이 인사말(salutation)과 서명은 이메일의 처음과 끝 부분에서 볼 수 있습니다. 이 부분은 요약문 생성에 아무런 도움이 되지 않습니다. 그렇기 때문에 이러한 내용을 지워야만 합니다. 그래야 요약문을 만들 때 반영이 되지 않기 때문입니다. 이는 입력 텍스트를 더 간결하게 만드므로 모델이 더 잘 동작하게 됩니다.

하지만 인사말과 서명란은 메일마다 다르고 작성된 언어마다 상이합니다. 따라서 이렇게 불필요한 내용을 지우기 위해서는 정규표현식(Regular Expression)으로 찾을 필요가 있습니다. 이 모듈을 사용하기 위해 저는 Mailgun Talon Github repository에서 찾은 코드를 약간 수정하여 사용했습니다. 이 모듈에서도 역시 개행 문자(\n)를 삭제하고 있습니다.

# clean() is a modified version of extract_signature() found in bruteforce.py in the GitHub repository linked above
cleaned_email, _ = clean(email)

lines = cleaned_email.split('\n')
lines = [line for line in lines if line != '']
cleaned_email = ' '.join(lines)

코드를 수정하여 직접 원하는 crean() 함수를 만드는 대신에 아래 코드를 이용하면 됩니다.

from talon.signature.bruteforce import extract_signature
cleaned_email, _ = extract_signature(email)

정제를 거친 예제 메일은 다음과 같습니다.

  • 영어 버전
Thank you for keeping me updated on this issue. I'm happy to hear that the issue got resolved after all and you can now use the app in its full functionality again. Also many thanks for your suggestions. We hope to improve this feature in the future. In case you experience any further problems with the app, please don't hesitate to contact me again.
  • 노르웨이어 버전
Grunnet manglende dekning på deres kort for månedlig trekk, blir dere nå overført til årlig fakturering. I morgen vil dere motta faktura for hosting og drift av nettbutikk for perioden 05.03.2018-05.03.2019. Ta gjerne kontakt om dere har spørsmål.
  • 이탈리아어 버전
Grazie mille per averci contattato! Apprezziamo molto che abbiate trovato il tempo per inviarci i vostri commenti e siamo lieti che vi piaccia l'App. Sentitevi liberi di parlare di con i vostri amici o di sostenerci lasciando una recensione nell'App Store.

전처리 과정이 끝나면 다음 단계로 넘어갑니다.

2단계) 언어 감지

이메일 내용이 어떠한 언어로도 잘 요약되도록 하기 위해서 가장 먼저 해야할 것은 어떤 언어로 작성되었는지를 판단하는 것입니다. 머신 러닝 기법을 사용하여 텍스트내에 있는 언어를 판별하도록 하는 파이썬 라이브러리가 많이 공개되어 있는데 대표적으로 polyglot, langdetect, textblob이 있습니다. 이 포스트에서는 langdetect를 사용했습니다. 이 라이브러리는 55개 언어를 지원하고 있습니다. 언어 감지는 간단하게 함수로 하나로 구현할 수가 있습니다.

from langdetect import detect
lang = detect(cleaned_email) # lang = 'en' for an English email

3단계) 문장 분절

모든 메일 문서의 언어 식별이 끝나고 나면, 이 정보를 사용하여 메일 문서를 구성하는(constituent) 문장으로 분절할 수가 있게됩니다. 각 언어마다 구분자(delimiter)가 다르기 때문에 언어 식별 정보가 필요합니다. NLTK 라이브러리의 tokenizer를 이용하면 쉽게 문장을 분절할 수가 있습니다.

from nltk.tokenize import sent_tokenize
sentences = sent_tokenize(email, language = lang)

4단계) Skip-Thought 인코더

다음으로는 가지고 있는 데이터(메일 문서)의 각 문장을 고정된 길이의 벡터로 바꾸어야 합니다. 즉, 문장에 대한 고정된 길이의 벡터 표현을 만들어야 합니다. 이러한 방식은 대응되는 문장의 표상적 의미(meaning)와 내재된 의미(inherent semantics)을 인코드할 수 있어야 합니다. 이러한 방법으로 잘 알려진 것이 Skip-Gram Word2Vec 기법입니다. 이 기법은 우리 모델의 어휘에 존재하는 개별 단어들에 대한 단어 임베딩을 생성해줍니다. (여러가지 더 멋진 접근방법들은 모델 어휘에 속해있지 않은 단어에 대한 임베딩을 생성하기도 합니다. 이 경우에는 보조 단어 정보(subword information)를 사용합니다.)

 

문장 임베딩을 하기 위한 가장 쉬운 방법으로는 문장 내에 포함된 단어의 단어 벡터에 가중합(weighted sum)을 취하는 것이 있습니다. 여기에서는 가중합을 도입하려 하는데 그 이유는 and, to 또는 the와 같은 자주 등장하는 단어들은 그 단어가 속한 문장에 대한 정보를 거의 가지고 있지 않기 때문입니다. 반면 거의 등장하지 않는 어떤 단어들은 일부 문장에서만 특별하게 등장하는데 이 경우에는 더 큰 대표성(representative power)을 갖는다고 볼 수 있습니다. 그렇기 때문에 여기서는 단어 출현 빈도에 반비례하는 가중치를 도입하였습니다. 이 방법은 다음 논문에서 자세히 확인할 수 있습니다.

 

하지만 이러한 비지도 학습 방법은 문장에서의 단어 순서, 즉 어순을 고려하지는 않습니다. 이는 원하지 않는 모델 성능 하락을 가져올 수 있습니다. 이 문제점을 극복하기 위해서 저는 위키피디아(Wikipedia) 덤프를 훈련 데이터로 사용하여 Skip-Thought 문장 인코더를 지도 학습 방법으로 훈련하기로 하였습니다. Skip-Thoughts 모델은 다음 두 부분으로 나뉘어 집니다.

 

  • 인코더 네트워크

인코더는 일반적으로 입력 데이터에 있는 각 문장 $S(i)$에 대해 고정 길이 벡터 표현(fixed length vector representation), $h(i)$를 생성하는 GRU-RNN입니다. 인코드된 표현인 $h(i)$는 다중 밀집 층(multiple dense layers)까지 이어지는 GRU 셀의 최종 은닉 상태를 지나면서(즉, 모든 문장을 본 다음에) 생성됩니다.

  • 디코더 네트워크

디코더 네트워크는 위에서 만든 벡터 표현인 $h(i)$를 입력으로 받아 두 개의 문장을 생성하려고 합니다. 두 문장은 $S(i-1)$ 그리고 $S(i+1)$로 표현하겠습니다. 이는 입력된 문장의 앞과 뒤 각각에 나타날 수 있는 문장을 의미합니다. 분할된 디코더(Separate decoders)는 전후 문장을 생성합니다. 두 디코더 모두 GRU-RNN입니다. 벡터 표현인 $h(i)$는 디코더 네트워크의 GRU에 대한 초기 은닉 상태처럼 작동합니다.

 

연속된 문장(문서)을 포함한 데이터셋이 주어졌을 때, 디코더는 전후 문장을 단어 하나씩 만들어내려고 합니다. 인코더-디코더 네트워크는 문장 재구성 손실을 최소화하도록 훈련됩니다. 이 과정에서 인코더는 디코더를 위해 충분한 정보를 내포(encode)하는 벡터 표현을 생성하게 되고, 디코더는 주변 문장을 만들어낼 수 있게 됩니다. 이러한 학습된 표현은 의미가 유사한 문장들의 임베딩이 벡터 공간에서 서로 인접(close)하게 되고, 곧 군집(clustering)에 적합한 형태가 됩니다. 우리 예제인 메일 문서의 문장들은 인코더 네트워크의 입력 값으로 주어지는데, 그 결과로 우리가 원하는 형태의 벡터 표현을 얻을 수가 있습니다. 문장 임베딩을 얻기 위한 이 Skip-Thought 접근 방법은 다음 논문에 자세히 기술되어 있습니다. (쉽게 말해 Skip-Thought는 Sentence to Vector(Sent2Vec) 기법입니다.)

 

저는 Skip-Though를 수행하기 위해 skip-thought 저자가 공개한 오픈 소스 코드를 사용하였습니다. 이 코드는 Theano로 작성되었으며 여기에서 찾을 수 있습니다.

# The 'skipthoughts' module can be found at the root of the GitHub  repository linked above
import skipthoughts

# You would need to download pre-trained models first
model = skipthoughts.load_model()

encoder = skipthoughts.Encoder(model)
encoded =  encoder.encode(sentences)

5단계) 군집

메일 내의 각 문장에 대해 문장 임베딩을 생성한 후에는, 고차원의 벡터 공간에 있는 이러한 임베딩 결과들을 미리 정의된 개수의 군집(cluster)으로 묶어줍니다. 군집의 개수는 요약문에 사용될 문장의 개수로 지정하면 됩니다. 저는 메일 문서내의 전체 문장의 수를 제곱근한 값으로 정했습니다. 전체 문장 수의 30%로 정해도 됩니다. 저는 다음 코드를 사용하였습니다.

import numpy as np
from sklearn.cluster import KMeans

n_clusters = np.ceil(len(encoded)**0.5)
kmeans = KMeans(n_clusters=n_clusters)
kmeans = kmeans.fit(encoded)

6단계) 요약

문장 임베딩의 각 군집은 의미가 비슷한 문장의 집합으로 해석할 수 있기 때문에 하나의 군집은 요약문에서 하나의 대표 구문으로 표현된다고 볼 수 있습니다. 대표 구문은 각 군집의 중심점과 가장 가까운 벡터 표현을 갖는 문장으로 선택될 수 있습니다. 다음으로 각 군집에 대응되는 대표 문장들은 메일 문서 요약문을 만들기 위해 순서가 지정됩니다. 대표 문장들의 순서는 원본 메일의 해당 군집에 있는 문장의 위치에 따라 결정됩니다. 예를 들어, 어떤 대표 문장은 요약문의 첫 번째 문장으로 선택되는데 이는 그 문장이 속한 군집 내에있는 다른 대부분의 문장들이 메일 문서의 앞부분에 나타나기 때문입니다. 요약을 수행하기 위한 코드는 다음과 같습니다.

from sklearn.metrics import pairwise_distances_argmin_min

avg = []
for j in range(n_clusters):
    idx = np.where(kmeans.labels_ == j)[0]
    avg.append(np.mean(idx))
closest, _ = pairwise_distances_argmin_min(kmeans.cluster_centers_, encoded)
ordering = sorted(range(n_clusters), key=lambda k: avg[k])
summary = ' '.join([email[closest[idx]] for idx in ordering])

이 방법은 본질적으로 텍스트로부터 대표 문장을 추출하여 요약문을 형성하기 때문에, 추출 방식 요약(Extractive Summarization)이라고 합니다.

 

이 방식으로 얻어진 요약문 예제는 다음과 같습니다.

  • 영어 메일
I'm happy to hear that the issue got resolved after all and you can now use the app in its full functionality again. Also many thanks for your suggestions. In case you experience any further problems with the app, please don't hesitate to contact me again.
  • 네덜란드어 메일
Grunnet manglende dekning på deres kort for månedlig trekk, blir dere nå overført til årlig fakturering. I morgen vil dere motta faktura for hosting og drift av nettbutikk for perioden 05.03.2018-05.03.2019. Ta gjerne kontakt om dere har spørsmål.
  • 이탈리아어 메일
Apprezziamo molto che abbiate trovato il tempo per inviarci i vostri commenti e siamo lieti che vi piaccia l'App. Sentitevi liberi di parlare di con i vostri amici o di sostenerci lasciando una recensione nell'App Store.

트레이닝

미리 훈련된 모델로는 영문 문장을 인코딩한 것이 있습니다. (참고) 그러나 네덜란드어 문장에 대해서는 Skip-Thought 모델이 훈련된 자료가 없습니다. 네덜란드 위키피디아 덤프로부터 데이터를 모았습니다. (참고) .bz2 아카이브가 추출되었고 이를 .xml 형식으로 변환하여 HTML 구문을 제거하였습니다. 따라서 데이터는 순수한 텍스트만 남게 되었습니다. 위키피디아 덤프를 변환하는 다양한 도구가 존재하지만 완벽한건 없습니다. 변환 방식에 따라 변환 시간이 오래 걸릴 수도 있습니다. 제가 사용한 도구는 이것인데요, 아주 좋은건 아니지만 일단 무료이고 납득할만한 성능을 보입니다. 이후에는 위 결과로 얻은 순수 텍스트(plain-text)에서 개행문자를 지워주는 것 같은 간단한 전처리를 수행했습니다. 여기까지해서 대량의 훈련 데이터를 Skip-Thoughts 모델을 사용하여 며칠내에 훈련할 수 있도록 만들었습니다.

 

결과적으로 훈련 데이터가 생성되었습니다. 훈련 데이터는 위키피디아 문서로부터 2,712,935개의 네덜란드어 문장으로 구성되었습니다. 훈련 과정에서는 미리 훈련된 Word2Vec 단어 벡터가 필요합니다. 이를 위해서 Facebook fastText의 미리 훈련된 벡터(네덜란드어)를 사용했습니다. 여기에서는 wiki.da.vec 파일만 사용하였고, wiki.da.bin은 사용하지 않았기 때문에 어휘 확장 특정은 반영되지 않았습니다.

 

미리 훈련된 벡터는 312,956개의 단어를 갖는 어휘 크기(vocabulary size)를 갖고 있습니다. 이러한 단어 벡터 역시도 네덜란드어 위키피디아에서 훈련되었으므로 어휘 집합을 벗어나는 단어는 거의 없었습니다. 훈련을 위한 코드는 여기에서 찾을 수 있습니다.

세부 구현 내용

아래 코드는 영어 메일 문서를 지원하는 간단한 모듈입니다. 하지만 위에서 언급한 모든 단계가 포함되어 있으며 놀랍게도 잘 동작합니다. 아래 코드를 어떻게 사용하는지에 대한 설명과 모듈은 여기에서 찾을 수 있습니다. 편하게 Fork하고 코드를 마음껏 수정하세요!

 #!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Module for E-mail Summarization
*****************************************************************************
Input Parameters:
    emails: A list of strings containing the emails
Returns:
    summary: A list of strings containing the summaries.
*****************************************************************************
"""


# ***************************************************************************
import numpy as np
from talon.signature.bruteforce import extract_signature
from langdetect import detect
from nltk.tokenize import sent_tokenize
import skipthoughts
from sklearn.cluster import KMeans
from sklearn.metrics import pairwise_distances_argmin_min
# ***************************************************************************


def preprocess(emails):
    """
    Performs preprocessing operations such as:
        1. Removing signature lines (only English emails are supported)
        2. Removing new line characters.
    """
    n_emails = len(emails)
    for i in range(n_emails):
        email = emails[i]
        email, _ = extract_signature(email)
        lines = email.split('\n')
        for j in reversed(range(len(lines))):
            lines[j] = lines[j].strip()
            if lines[j] == '':
                lines.pop(j)
        emails[i] = ' '.join(lines)


def split_sentences(emails):
    """
    Splits the emails into individual sentences
    """
    n_emails = len(emails)
    for i in range(n_emails):
        email = emails[i]
        sentences = sent_tokenize(email)
        for j in reversed(range(len(sentences))):
            sent = sentences[j]
            sentences[j] = sent.strip()
            if sent == '':
                sentences.pop(j)
        emails[i] = sentences


def skipthought_encode(emails):
    """
    Obtains sentence embeddings for each sentence in the emails
    """
    enc_emails = [None]*len(emails)
    cum_sum_sentences = [0]
    sent_count = 0
    for email in emails:
        sent_count += len(email)
        cum_sum_sentences.append(sent_count)

    all_sentences = [sent for email in emails for sent in email]
    print('Loading pre-trained models...')
    model = skipthoughts.load_model()
    encoder = skipthoughts.Encoder(model)
    print('Encoding sentences...')
    enc_sentences = encoder.encode(all_sentences, verbose=False)

    for i in range(len(emails)):
        begin = cum_sum_sentences[i]
        end = cum_sum_sentences[i+1]
        enc_emails[i] = enc_sentences[begin:end]
    return enc_emails


def summarize(emails):
    """
    Performs summarization of emails
    """
    n_emails = len(emails)
    summary = [None]*n_emails
    print('Preprecesing...')
    preprocess(emails)
    print('Splitting into sentences...')
    split_sentences(emails)
    print('Starting to encode...')
    enc_emails = skipthought_encode(emails)
    print('Encoding Finished')
    for i in range(n_emails):
        enc_email = enc_emails[i]
        n_clusters = int(np.ceil(len(enc_email)**0.5))
        kmeans = KMeans(n_clusters=n_clusters, random_state=0)
        kmeans = kmeans.fit(enc_email)
        avg = []
        closest = []
        for j in range(n_clusters):
            idx = np.where(kmeans.labels_ == j)[0]
            avg.append(np.mean(idx))
        closest, _ = pairwise_distances_argmin_min(kmeans.cluster_centers_,\
                                                   enc_email)
        ordering = sorted(range(n_clusters), key=lambda k: avg[k])
        summary[i] = ' '.join([emails[i][closest[idx]] for idx in ordering])
    print('Clustering Finished')
    return summary

결론

여러분들도 눈치챘겠지만 2~3문장 대신에 여러 문장으로 구성된 이메일의 경우에 더 잘 작동합니다. 3문장으로된 메일은 요약문이 2문장으로 구성될텐데 이는 올바른 결과가 아닐 것입니다(shouldn’t be the case). 또한 이러한 세 문장은 각각 완전히 다른 내용을 전달할 수도 있으며, 한 문장의 정보를 생략하는 것이 바람직하지 않을 수 있습니다. 위와 같은 이유 때문에 추출 방식 요약(Extractive methods)은 일반적으로 짧은 문서에 대해 선호되는 방식이 아닙니다. Supervised Seq2Seq 모델이 이런 경우에는 더 나을 겁니다. 하지만 이 포스트의 경우, 메일 문서는 일반적으로 더 길기 때문에 추출 방식 요약이 놀랍도록 잘 작동합니다.

 

Skip-Thought 벡터를 이용하는 것에는 단점이 하나 있는데, 바로 훈련 시간이 오래 걸린다는 점입니다. 비록 납득할만한 결과가 2~3일간의 훈련을 통해 얻어진다고는 하지만, 네덜란드어에 대한 Skip-Thought 모델은 1주일가량 훈련했었습니다. 이 모델은 문장의 길이에 따라 정규화되기 때문에 반복 횟수에 따라 훈련 비용(cost)이 크게 변화합니다.

Skip-Thoughts 모델이 얼마나 잘 작동하는지 아래 결과로 확인해보세요!

I can assure you that our developers are already aware of the issue and are trying to solve it as soon as possible.
AND
I have already forwarded your problem report to our developers and they will now investigate this issue with the login page in further detail in order to detect the source of this problem.
--------------------------------------------
I am very sorry to hear that.
AND
We sincerely apologize for the inconvenience caused.
--------------------------------------------
Therefore, I would kindly ask you to tell me which operating system you are using the app on.
AND
Can you specify which device you are using as well as the Android or iOS version it currently has installed?

보시다시피 모델이 아주 잘 작동합니다! 그리고 문장 길이가 크게 달라도 비슷한 문장을 잘 표시해줍니다. 뿐만 아니라 완전히 다른 어휘를 사용해도요.

향후 과제

위 접근 방법은 잘 동작하지만 완벽하지는 않습니다. 이 모델의 복잡도를 증가시킴으로써 얻을 수 있는 개선점들은 다음과 같습니다.

  1. Quick-Thought Vector는 Skip-Thoughts 접근 방법을 개선한 최신 모델로 훈련 시간을 대폭 단축하였으며 더 나은 성능을 보여줍니다.

  2. Skip-Thought 모델로 인코드된 벡터 표현(Vector representations)은 4800차원이었습니다. 이러한 고차원 벡터는 차원의 저주(Curse of Dimensionality) 때문에 군집화에 적합한 형태가 아닙니다. 벡터의 차원을 군집화를 수행하기 전에 줄일 수가 있는데, 이는 압축된 표현내에서 추가적인 순서 정보를 전달(impart)하는 오토인코더 또는 LSTM-오토인코더를 사용하면 됩니다.

  3. 추출 방식 요약(Extractive approaches) 대신에 초록 방식 요약(abstractive summarization)으로 구현할 수 있습니다. 이 때에는 각 군집의 중앙에 위치한 인코드된 벡터 표현을 자연어 문장으로 변환할 수 있는 디코더 네트워크를 훈련하는 방식을 사용하면 됩니다. 이러한 디코더를 Skip-Thought 인코더로 생성될 수 있는 데이터로 훈련해도 됩니다. 하지만 아주 조심스러운 하이퍼 파라미터 튜닝(very careful hyper-parameter tuning)과 구조를 선정하는 것(atrchitecture decisions)을 구현해야만 디코더가 그럴듯하고(plausible) 문법적으로 문제없는 문장을 생성해낼 수가 있을 것입니다.

실행 환경

위의 모든 과정은 Octa-Core Intel(R) Xeon(R) CPU와 Nvidia Tesla K80 GPU with 52GB RAM을 자랑(sporting)하는 n1-highmem-8 Google Cloud에서 이루어졌습니다.

마치며

제게 조언과 유용한 제안을 해준 멘토 Rahul Kumar에게 특히 감사드립니다. 그가 없었다면 나올 수 없었던 결과입니다. 또한 저는 Jatana.ai에게 큰 신세를 졌습니다. 제게 이런 멋진 기화와 필요한 자원을 제공해주어 위와 같은 결과를 얻을 수 있었습니다.

댓글
  • 프로필사진 tubelog 개인 참고용으로 대충 번역한거라 번역 퀄이 좀 허접합니다만 공개하는게 의미있을 것 같아 공개합니다.
    이상한 점 있으면 수정반영해드리겠습니다.
    2019.06.23 04:00 신고
댓글쓰기 폼