[자연어 처리 스터디 기록] 4월 5일~4월 13일(마지막 주차)

[자연어 처리 입문과정 공부하는 기록입니다.]

전문가가 아닌 학부 1학년이 혼자 공부하고 남기는 기록이다 보니 잘못된 정보가 다소 포함되어 있을수도 있습니다. 잘못된 부분이 있다면 댓글로 알려주시면 감사하겠습니다.

PC환경에서 읽어주시면 감사하겠습니다.

NLP코드 및 기타 자료를 올려둔 "Github" 주소입니다*
[NLPtest Github]

이 글은 대부분의 내용이 huggingface내의 tokenizer 종류에 대해 설명 및 예제를 사용하고 있습니다.

[WEB_Huggingface]

배경지식

OOV (Out Of Vocabulary) & UNK (Unknown word)

  • OOV, 기존에 만들어진 사전에 없어 기계가 배우지 못한 단어로 자연어 처리에서 OOV이슈는 심각한 문제로 받아진다. 또한 OOV이슈를 완벽하게 해결할수있는 문제는 아니라 자연어처리를 잘 했냐 못 했냐를 판단할때 이이슈를 얼마나 잘 완화 시켰는가를 보는것이 기준의 척도가 된다.

    (OOV이슈는 이름,장소, 외래어등에서 자주 발생한다고 한다.)

  • UNK, Unknown의 약자로 자연어처리에선 OOV이슈가 발생하면 원래 단어가 들어가는 자리에 공백으로 비우는 것이 아닌 UNK토큰의 형태로 들어가게 되어 이후에 단어를 예측하는데 어려움을 주기에 UNK토큰은 최소한으로 줄이는것이 좋다.

N-gram 언어모델

  • 전통적인 방법으로 SLM의 일종인데, 카운트기반에 기반한 통계적 접근을 말한다. 여기서 말하는 카운트 기반이란. 문자를 숫자로 수치화 하기 위해 사용되는 단어표현 방법이라고 한다. 그럼 N-gram은 무엇이냐 ? 코퍼스에서 시퀀스를 카운트할 확률을 높이는 방법인데ㅡ 가지고 있는 코퍼스단위를 N개뭉치로 끊어 이를 하나의 토큰 형태로 저장하는 방법이다. N의 개수가 1,2,3...에 따라 uni, big, tri, N...으로 흘러간다.

  • 예시를 통해 이해하면 더 좋을듯 하다.

예시문장. "An adorable little boy is spreading smiles"
unigrams (N=1) : an, adorable, little, boy, is, spreading, smiles
bigrams (N=2) : an adorable, adorable little, little boy, boy is, is spreading, spreading smiles
trigrams (N=3): an adorable little, adorable little boy, little boy is, boy is spreading, is spreading smiles
4-grams (N=4) : an adorable little boy, adorable little boy is, little boy is spreading, boy is spreading smiles


0. what is HuggingFace.

[huggingface Transformers 사이트]

  • 자연어처리 스타트업이 개발한 트랜스포머 모델 및 학습스크립트를 제공하는 모듈
  • 대표적으로 포함된 모듈 : BPE, WPM, SPM
  • 개인적인 생각, 사실 공부를 하기 이전에는 huggingface의 존재도 모르고 있었는데 여기에 자연어처리 전반의 자료와 소스코드가 올라와 있다. 따라서 나와 같은 입문자들은 이 사이트를 참고하면 좋을듯하다.
    (다만 영어만 지원하기에 이해하기에 시간이 다소 걸리긴 한다.)

따라서 아래의 내용은 위의 대표적 tokenize 기법에 대해 다루도록 하겠다.


1. BPE (Byte Pair Encording)

[BPE관련 강의 - 국민대 강승식교수]

[BPE 참고자료 1] [BPE 참고자료 2]

BPE가 OOV비율을 낮출수 있는 Tokenize방법중 하나이다.

BPE를 한마디로 설명하라 라고 한다면 연속하는 글자의 쌍을 찾아 하나의 글자로 병합한다.
라는 말이 가장 적절한 한마디라고 생각한다. 뒤에서 설명하는 내용은 BPE의 한마디를 부연설명이라고 생각하면 좋다.

직접 보면서 이해를 하는것이 좋을것 같다.

aaabdaaabac #'aa'를 'z'로 치환
zabdzabac #'ab'를 'y'로 치환
zydzyac #'zy'를 'x'로 치환
xdxac #최종결과, 최다 빈출 글자의 쌍바이트를 병합.

*이때 혼자 했던 생각이 가장 처음에 'aaabdaaabac' 문자열에서 왜 'aa'로 묶는거지 ? 'aaa'로 묶으면 안되는건가? 라는 의문을 가지게 되었는데 더 찾아보니 이웃한 두 바이트를 병합하는 것이기 때문에 내가 생각한 방법은 올바르지 않는 방법이 된다.

1-1. Neural Machine Translation of Rare Words with Subword Units- (BPE)

* 제대로 읽어보고 싶은 논문 리뷰 (추후수정) *
[link_archive][paper_link]


2. WPM(Word Piece Model)

  • 하나의 단어를 내부 단어(Subword Unit)들로 분리하는 단어 분리 모델이다.
WPM을 수행하기 이전의 문장: Jet makers feud over seat width with big orders at stake
WPM을 수행한 결과(wordpieces):_J et _makers _fe ud _over _seat _width _with _big _orders _at _stake

띄어쓰기를 언더바(_)처리하고 나머지 단어들은 통계에 따라 띄어쓰기로 분류한다.


3. SPM(SentencePiece Model)

3-1. 왜 쓰는가?

내부 단어 분리 알고리즘을 사용하기 위해선 단어들이 토큰화가 필수적이다. 하지만 영어와 한국어의 경우 단어분리가 쉽지 않기때문에 사전 토큰화 작업 없이 바로 RAWDATA에서 바로 단어 토크나이징을 할수있기때문이다.

3-2. unigram language model

SPM에 사용되는 언어모델중 하나이다. 확률 분포를 통해 텍스트를 생성하는 원리

  • 매우 많은 subword sequence중 하나의 subword를 선택했을때 문장 생성 확률이 가장 큰 X*값을 선택하고 그 값을 통해 텍스트를 생성한다.

  • X*은 생성확률이 가장 큰 값인 subword 시퀀스중 한개.

  • Viterbi algorithm을 통해 X*를 얻게 된다.


4. BERT

임베딩(Embedding)과정에 사용되는 "사전 훈련 언어 모델"
BERT가 나오기 이전엔 Word2Vec, Glove, Fasttext 등등을 사용

Pretraining 과정중 ..두가지 방식 설명

  • MLM (Masked Language Model), 문장에서 일부를 [Mask]토큰으로 바꾼 뒤, 가려진 단어를 예측하도록 학습한다.
  • NSP (Next sentence prediction), 다음 문장이 연관성이 있는 문장인지 맞추는 문제로 이 문제를 통해 두 문장의 관계를 학습한다.

4-1. BERT(Eng)

[Google BERT Github]

2018년 구글에서 BERT 모델을 공개하였는데 이는 NLP업계에서 최고 성능을 보여줬다고 한다. 이 모델은 문맥의 특성을 활용하고, 대용량 코퍼스로 사전 학습을 통해 언어의 이해도가 높다고 한다. 영어에 대한 이해도는 높지만 아직 한국어에 대한 이해도는 그리 높지 않다고 한다.

Token embeddings -> segment embeddings -> position embeddings
-> pretraining
-> transfer learning


4-2. KoBERT(Kor)

[SKT KoBERT Github]

기존의 BERT를 보완하기 위하여 SKT에서 한국어 위키 5백만건과 한국어 뉴스 2천만 문장을 학습시킨 모델로 한국어 학습이 잘 되어있는 장점 뿐만 아니라, 분석 목적에 따라 output layer를 추가로 달아 개발 목적에 맞는 결과를 도출할수있다.

4-3. KLUE-BERT

4-4. tokenization 성능비교. (KoBERT vs KLUE-BERT)

- 비교 데이터(한국어 위키피디아)


5. Knolpy

한국어 정보 처리 파이썬 패키지

5-1. tagging *option

  • Hannanum
import konlpy
from konlpy.tag import Hannanum
hannanum = Hannanum()
print(hannanum.analyze(u'롯데마트의 흑마늘 양념 치킨이 논란이 되고 있다.'))
print(hannanum.morphs(u'롯데마트의 흑마늘 양념 치킨이 논란이 되고 있다.'))
print(hannanum.nouns(u'다람쥐 헌 쳇바퀴에 타고파'))
print(hannanum.pos(u'웃으면 더 행복합니다!'))
[[[('롯데마트', 'ncn'), ('의', 'jcm')], [('롯데마트의', 'ncn')], [('롯데마트', 'nqq'), ('의', 'jcm')], [('롯데마트의', 'nqq')]], [[('흑마늘', 'ncn')], [('흑마늘', 'nqq')]], [[('양념', 'ncn')]], [[('치킨', 'ncn'), ('이', 'jcc')], [('치킨', 'ncn'), ('이', 'jcs')], [('치킨', 'ncn'), ('이', 'ncn')]], [[('논란', 'ncpa'), ('이', 'jcc')], [('논란', 'ncpa'), ('이', 'jcs')], [('논란', 'ncpa'), ('이', 'ncn')]], [[('되', 'nbu'), ('고', 'jcj')], [('되', 'nbu'), ('이', 'jp'), ('고', 'ecc')], [('되', 'nbu'), ('이', 'jp'), ('고', 'ecs')], [('되', 'nbu'), ('이', 'jp'), ('고', 'ecx')], [('되', 'paa'), ('고', 'ecc')], [('되', 'paa'), ('고', 'ecs')], [('되', 'paa'), ('고', 'ecx')], [('되', 'pvg'), ('고', 'ecc')], [('되', 'pvg'), ('고', 'ecs')], [('되', 'pvg'), ('고', 'ecx')], [('되', 'px'), ('고', 'ecc')], [('되', 'px'), ('고', 'ecs')], [('되', 'px'), ('고', 'ecx')]], [[('있', 'paa'), ('다', 'ef')], [('있', 'px'), ('다', 'ef')]], [[('.', 'sf')], [('.', 'sy')]]]
['롯데마트', '의', '흑마늘', '양념', '치킨', '이', '논란', '이', '되', '고', '있', '다', '.']
['다람쥐', '쳇바퀴', '타고파']
[('웃', 'P'), ('으면', 'E'), ('더', 'M'), ('행복', 'N'), ('하', 'X'), ('ㅂ니다', 'E'), ('!', 'S')]
  • Kkma
from konlpy.tag import Kkma
kkma = Kkma()
print(kkma.morphs(u'공부를 하면할수록 모르는게 많다는 것을 알게 됩니다.'))
print(kkma.nouns(u'대학에서 DB, 통계학, 이산수학 등을 배웠지만...'))
print(kkma.pos(u'다 까먹어버렸네요?ㅋㅋ'))
print(kkma.sentences(u'그래도 계속 공부합니다. 재밌으니까!'))
['공부', '를', '하', '면', '하', 'ㄹ수록', '모르', '는', '것', '이', '많', '다는', '것', '을', '알', '게', '되', 'ㅂ니다', '.']
['대학', '통계학', '이산', '이산수학', '수학', '등']
[('다', 'MAG'), ('까먹', 'VV'), ('어', 'ECD'), ('버리', 'VXV'), ('었', 'EPT'), ('네요', 'EFN'), ('?', 'SF'), ('ㅋㅋ', 'EMO')]
['그래도 계속 공부합니다.', '재밌으니까!']
  • Komoran
from konlpy.tag import Komoran 
komoran = Komoran() 
print(komoran.morphs(u'우왕 코모란도 오픈소스가 되었어요')) 
print(komoran.nouns(u'오픈소스에 관심 많은 멋진 개발자님들!')) 
print(komoran.pos(u'한글형태소분석기 코모란 테스트 중 입니다.'))
['우왕', '코', '모란', '도', '오픈', '소스', '가', '되', '었', '어요']
['오픈', '소스', '관심', '개발자']
[('한글', 'NNP'), ('형태소', 'NNP'), ('분석기', 'NNG'), ('코모', 'NNP'), ('란', 'JX'), ('테스트', 'NNP'), ('중', 'NNB'), ('이', 'VCP'), ('ㅂ니다', 'EF'), ('.', 'SF')]

5-2. 차이점 정리 (Hannanum vs Kkma vs Komoran)

[한국어 품사 태그 비교표]

from time import time

from konlpy import tag
from konlpy.corpus import kolaw
from konlpy.utils import csvwrite, pprint


def tagging(tagger, text):
    r = []
    try:
        r = getattr(tag, tagger)().pos(text)
    except Exception as e:
        print "Uhoh,", e
    return r


def measure_time(taggers, mult=6):
    doc = kolaw.open('constitution.txt').read()*6
    data = [['n'] + taggers]
    for i in range(mult):
        doclen = 10**i
        times = [time()]
        diffs = [doclen]
        for tagger in taggers:
            r = tagging(tagger, doc[:doclen])
            times.append(time())
            diffs.append(times[-1] - times[-2])
            print '%s\t%s\t%s' % (tagger[:5], doclen, diffs[-1])
            pprint(r[:5])
        data.append(diffs)
        print
    return data


def measure_accuracy(taggers, text):
    print '\n%s' % text
    result = []
    for tagger in taggers:
        print tagger,
        r = tagging(tagger, text)
        pprint(r)
        result.append([tagger] + map(lambda s: ' / '.join(s), r))
    return result


def plot(result):

    from matplotlib import pylab as pl
    import scipy as sp

    if not result:
        result = sp.loadtxt('morph.csv', delimiter=',', skiprows=1).T

    x, y = result[0], result[1:]

    for i in y:
        pl.plot(x, i)

    pl.xlabel('Number of characters')
    pl.ylabel('Time (sec)')
    pl.xscale('log')
    pl.grid(True)
    pl.savefig("images/time.png")
    pl.show()


if __name__=='__main__':

    PLOT = False
    MULT = 6

    examples = [u'아버지가방에들어가신다',  # 띄어쓰기
            u'나는 밥을 먹는다', u'하늘을 나는 자동차', # 중의성 해소
            u'아이폰 기다리다 지쳐 애플공홈에서 언락폰질러버렸다 6+ 128기가실버ㅋ'] # 속어

    taggers = [t for t in dir(tag) if t[0].isupper()]

    # Time
    data = measure_time(taggers, mult=MULT)
    with open('morph.csv', 'w') as f:
        csvwrite(data, f)

    # Accuracy
    for i, example in enumerate(examples):
        result = measure_accuracy(taggers, example)
        result = map(lambda *row: [i or '' for i in row], *result)
        with open('morph-%s.csv' % i, 'w') as f:
            csvwrite(result, f)

    # Plot
    if PLOT:
        plot(result)

- 로딩시간 비교 (사전 데이터 및 클래스 로딩)
1. Hannanum: 0.6591 secs
2. Kkma: 5.6988 secs
3. Komoran: 5.4866 secs

- 실행시간 비교 (10만 문자의 pos메소드를 실행하는데 소요된 시간)
1. Hannanum: 8.8251 secs
2. Kkma: 35.7163 secs
3. Komoran: 25.6008 secs

- 정리
로딩시간: Hannanum < Komoran < Kkma
실행시간: Hannanum < Komoran < Kkma


6. generated sentence Score

  • 기계가 번역한 문장과 사람이 직접 번역한 문장의 유사도를 측정하여 모델을 평가하게 된다.

6-1. BLUE Score

  • BLUE Score는 input과 output 모두가 sequence형태로 이루어져 있는 문장의 Score를 계산할때 사용된다. 또한, BLUE Score는 N-gram의 precison에 기반하여 계산된다.

(BLUE의 식은 왜이렇게 많은걸까 ?.. 정말 최종적인 식을 잘 모르겠다.)
내가 찾은 BLUE는 결과값이 높을수록 좋은 성능을 가졌다. 라고 평가된다.

6-2. Rouge

Rouge는 BLUE와 같은 맥락으로 문장 분석이 끝난후 얼마나 분석이 잘되었나를 나타내는 지표로 사용되는 Metrix인데 BLUE가 N-gram의 precison에 기반하여 계산된다면 Rouge는 N-gram의 recall을 기반으로 계산된다.

[Rouge Score 관련 논문]

아래에 보이는 Rouge의 종류는 N-gram에서 N의 수, 문장의 길이, 단어 중복수 등.. 에 따라 다르게 사용되는 Metrix이다.
(각 Rouge종류마다 복잡한 수식이 적용이 되는데 이해하기에 어려워,, 시도하고 포기했다.)

  • Rouge-N
  • Rouge-L
  • Rouge-W
  • Rouge-S
  • Rouge-SU

6-3. 추가 평가방법.

가장 많이 사용되는 평가 방법중 산술 평균과 기하평균에 대해 알아보도록 하겠다.

  • F-Measure, 흔히 가중치를 적용한 조화평균이라고도 부르는데 정확도와 재현율에대한 조화평균에 가중치를 적용하는것이다.

조화평균의 식은 아래와 같다.

F-Measure의 경우 조화평균에서 가중치 알파값을 적용할수있는데 예를들어 Precision과 Recall에 동일하게 0.5라는 가중치를 적용하게 된다면 오른쪽 식의 베타값은 1이 된다.

이것은 흔히 F1 Measure이라고도 부른다.

  • G-Measure, F-Measure이 산술 평균이라고 한다면 G-Measure은 기하평균에 해당한다.

이 외에도 모델을 평가하는 방법은 여러가지로 어떤 모델이냐에 따라 어떻게 평가 하느냐에 따라 성능지표가 천차만별로 나타나게 된다. 따라서 알맞는 평가 모델을 선택하는것도 중요하다.


7. 한 달 동안의 스터디를 끝으로..

개인적으로 많은 그것도 혼자서 새로운 분야를 공부한다는것은 상당히 쉽지 않은 일이라고 생각한다. 허나 기존에 프로그래밍을 자주 접해왔기에 완전 처음보는 사람보다는 조금 더 아주 조금 더,, 순탄하게 했다고 생각한다. 4주간 많은것들을 했다고 생각하지만, 전문가의 입장에서 보면 분명 '맛'도 안 본 수준이라고 생각할거같다. 하지만 자연어처리라는 분야를 그것도 일개 학부 1학년 학생이 스스로 공부를 했다고 하면 절대 못한건 아니라고 자부한다. 인공지능 분야는 지금도 미래에도 계속해서 발전해나갈 분야중 하나이기에 도전했던 시간이 헛된 시간은 아니라고 생각하고 충분히 많은것을 배울수있었던 시간들이었다. 이해하기 어려웠던 부분 ? 충분히 많았지만 관련 자료를 찾고 영상을 보고 이해를 하고자 투자했던 노력이 나에겐 배움이었다.

과연 지금 내가 했던 도전들이 나의 미래(가깝게는 연구실, 멀게는 인공지능공학자)에 실질적으로 도움이 될지는 아무도 모르지만 내가 했던 노력에 의의를 두는것이 지금 나의 상황, 나이, 능력에서 최선을 다했다는 생각에 내 자신이 너무 뿌듯하고 기특하다.

-긴 글 읽어주셔서 감사합니다.-

좋은 웹페이지 즐겨찾기