for textmining

베이지안 추론(1)

|

이번 글에서는 베이지안 추론에 대해 살펴보도록 하겠습니다. 이 글은 ‘밑바탁부터 시작하는 데이터과학(조엘 그루스 지음, 인사이트 펴냄)’과 ‘Think Bayes(앨런 B. 다우니 지음, 권정민 옮김, 한빛미디어 펴냄)’을 정리했음을 먼저 밝힙니다. 그럼 시작하겠습니다.

문제 정의와 베타분포

동전이 하나 있습니다. 우리는 이 동전이 공평한 동전인지 아닌지 알아보고 싶습니다. 이 동전을 던졌을 때 앞면이 나올 확률이 $p$라고 한다면 이 $p$가 어떤 값인지 추정해보고 싶은 겁니다.

동전던지기 실험은 이항분포를 따릅니다. 이항분포란 성공확률이 $p$이고, 그 결과가 성공 혹은 실패뿐인 실험을 $n$번 반복시행할 때 성공횟수의 분포를 가리킵니다.

이항분포 파라메터 $p$의 사전확률베타분포를 따릅니다. 여기에서 사전확률이란 데이터를 관측하기 전 가설로 세운 확률을 뜻합니다.

이 때문에 우리가 알고 싶은 앞면이 나올 확률 $p$는 베타분포를 사전분포로 사용하게 됩니다. 베타분포도 정규분포처럼 중심이 높은 포물선 형태를 갖는데요. 베타분포의 파라메터는 $α$와 $β$이며 그 중심은 다음과 같습니다.

$α$ / ($α + β$)

$p$의 사전확률과 관련해 $α$는 성공(앞면), $β$는 실패(뒷면)와 연관이 있고 $α, β$의 크기는 믿음의 크기와 관련이 있습니다. 만약 동전에 대해 어떤 선입견도 취하고 싶지 않다면 $α, β$를 모두 1로 정하면 됩니다. 앞면이 55%의 경우로 나타난다고 ‘굳게’ 믿고 있다면 $α$를 55로, $β$를 45로 가정할 수도 있습니다.

이항분포 파라메터 $p$의 사후확률 역시 베타분포를 따릅니다. 여기에서 사후확률이란 데이터를 확인한 이후의 가설 확률을 가리킵니다. 바로 우리가 알고 싶은 값이죠. 사후확률은 사전확률의 업데이트 버전 정도로 이해하면 좋을 것 같습니다.

추론 과정

동전을 여러번 던져서 앞면이 $h$번, 뒷면이 $t$번 나왔다고 칩시다. 베이즈 규칙을 활용해 유도하면 $p$의 사후확률 분포는 파라메터가 $α+h, β+t$인 베타분포를 따른다고 합니다.

이제 동전을 10번 던져서 앞면을 3번 관측했다고 가정해 보겠습니다. 실험 대상 동전이 공평한 동전이라고 생각해 $α, β$를 모두 1로 두었다고 하면, 앞면이 나올 확률 $p$의 사후분포는 파라메터가 1+3, 1+7인 베타분포가 될 겁니다.

공평한 동전이라는 생각이 다소 강하게 들어 $α, β$를 각각 20으로 정했었다면 $p$의 사후분포는 Beta(20+3, 20+7)이 됩니다. 즉, 동전이 뒷면으로 살짝 편향되었다는 것으로 믿음이 바뀔 것을 의미합니다.

앞면이 잘 나오는 동전이라는 확신 때문에 $p$의 사전분포를 Beta(30, 10)로 두었다면 사후분포는 Beta(30+3, 10+7)이 됩니다. 이 경우 동전이 아직도 앞면이 편향되어 있다고 믿지만, 처음 생각했던 것보다는 덜 강하게 믿는다는 걸 뜻합니다.

데이터 관측 후 $α, β$ 변화에 따른 $p$의 사후분포 변화는 다음 그림과 같습니다. 예컨대 Beta(4,8)의 경우 중심이 0.33인데요, $p$의 사후확률이 0.33일 가능성이 제일 높다는 뜻으로 받아들일 수 있습니다.

동전을 많이 던져볼 수록 관측 전 가정한 사전분포는 점점 의미가 없어집니다. 예를 들어 편향된 동전이라는 사전 믿음이 아무리 강했을지라도 동전을 2000번 던져서 앞면에 1000번 나왔다면 생각을 바꿀 수밖에 없기 때문입니다. 데이터가 충분하다면 서로 다른 사전확률을 가지고 시작한다고 해도 동일한 사후확률로 수렴하는 경향이 있습니다.

이러한 과정을 통해 $p$가 가지는 값을 확률적으로 추정할 수 있게 됩니다. 이를 베이지안 추론이라고 합니다. 이는 p-value을 이용한 가설검정 결과와는 접근 방식이 근본적으로 다릅니다. 예컨대 다음과 같습니다.

베이지안 추론 : 관측한 데이터와 사전분포를 고려해볼 때 동전의 앞면이 나올 확률이 49~51%일 경우는 5%밖에 되지 않는다

가설검정 : 동전이 공평하다면 이렇게 편향된 데이터를 관측할 경우는 5%밖에 되지 않는다

베이지안 추론 과정은 사전에 가설을 세운 뒤 실험(데이터 관측) 결과로 가설을 조금씩 업데이트하면서 완성됩니다. 평소 우리가 경험적으로 체감하고 있는 확률들(예컨대 전철에서 자리가 날 확률, 휴강을 할 확률 등등)은 사실 베이지안 추론과 본질상 다르지 않은 것 같습니다.

시각화

위 그림을 만드는 데 사용한 파이썬 코드는 다음과 같습니다.

import math
from matplotlib import pyplot as plt
def B(alpha, beta):
    return math.gamma(alpha) * math.gamma(beta) / math.gamma(alpha + beta)
def beta_pdf(x, alpha, beta):
    # [0, 1] 구간 밖에서는 밀도가 없음
    if x < 0 or x > 1:
        return 0
    return x ** (alpha - 1) * (1 - x) ** (beta - 1) / B(alpha, beta)

xs = [x / 100.0 for x in range(0,100)]
plt.plot(xs,[beta_pdf(x,alpha=4,beta=8) for x in xs],'-',label='Beta(4,8)')
plt.plot(xs,[beta_pdf(x,alpha=23,beta=27) for x in xs],'--',label='Beta(23,27)')
plt.plot(xs,[beta_pdf(x,alpha=33,beta=17) for x in xs],':',label='Beta(33,17)')
plt.legend()
plt.title('Beta pdfs')
plt.show()

구현

동전을 250번 던졌는데 앞면은 140번, 뒷면은 110번 나왔다고 칩시다. 앞면이 나온 확률 x 100을 $x$라고 할 때 코드는 다음과 같습니다. thinkbayes.py는 이곳에서 내려받을 수 있습니다.

import thinkbayes as tb

class Euro(tb.Suite):
    """Represents hypotheses 
    about the probability of heads."""

    def Likelihood(self, data, hypo):
        """Computes the likelihood of the data 
        under the hypothesis.
        hypo: integer value of x, 
        the probability of heads (0-100)
        data: tuple of (number of heads, number of tails)
        """
        x = hypo / 100.0
        heads, tails = data
        # 이항분포의 우도함수
        like = x**heads * (1-x)**tails
        return like

Euro 클래스는 앞면과 뒷면이 나올 확률이 동일하다는 가정에서 출발해 관측 결과를 업데이트한 값을 반환합니다. 다음과 같이 관측 결과를 업데이트한 뒤 suite 객체에 포함된 MaximumLikelihood 함수를 호출하면 56(=140/250*100)이 반환됩니다.

한편 suite.Mean() 함수는 가설(x)에 사후확률을 곱한 값을 모두 더한 평균값을 반환하는데요. 호출 결과 55.95238095가 나왔습니다.

# 가설 설정(x=1~100) 및 객체 선언
suite = Euro(xrange(0,101))
# 관측치 업데이트
data = (140, 110)
suite.Update(data)
# 최대 우도에 해당하는 x 반환
suite.MaximumLikelihood()
# 평균 반환
suite.Mean()

$x$의 사전분포는 베타분포입니다. 베타분포의 모양은 $α, β$ 에 따라 달라지는데요. 만약 사전분포가 $α, β$에 대한 베타분포이고, 앞면 $h$와 뒷면 $t$의 데이터를 가지고 있다면 사후확률은 $α+h, β+t$에 대한 베타분포가 됩니다. 이를 구현한 코드는 다음과 같습니다.

# 베타분포 선언, alpha=beta=1
beta = tb.Beta()
# 관측치 업데이트
# alpha = 1 + 140
# beta = 1 + 110
beta.Update(data)
# 베타분포의 중심(평균) 반환
print beta.Mean()

beta.Mean()은 베타분포의 중심을 출력해줍니다. 그 결과는 0.5595238로 suite.Mean()의 결과와 동일합니다.

Comment  Read more

유한문법 기반의 문장 생성

|

이번 글에서는 유한문법(finite grammar)을 바탕으로 문장을 생성하는 모델에 대해 살펴보도록 하겠습니다. 이 글은 ‘밑바닥부터 시작하는 데이터 과학(조엘 그루스 지음, 인사이트 펴냄)’을 정리하였음을 먼저 밝힙니다. 지금은 저 스스로 정리할 목적으로 올리는 포스트인데요, 한국어를 기반으로 한 실험 결과를 조만간 업데이트하겠습니다.

아이디어

문법에 기반해 말이 되는 문장을 생성해보자는 게 핵심 아이디어입니다.

예컨대 영어에서 문장(S)은 명사구(NP)와 동사구(VP)로 분석할 수 있습니다. 동사구(VP)에는 동사 하나(V)로만 구성될 수 있지만 동사(V)와 명사구(NP)가 결합해 있는 구로도 구성될 수 있습니다. 그런데 이 명사구(NP)에는 다시 명사(N) 하나로만 구성될 수도 있고, 형용사(A)+명사구(NP)+전치사(P)+형용사(A)+명사(N)로 이뤄진 구일 수도 있습니다.

다시 말해 유한한 수의 문법규칙만으로도 무한한 개수의 문장을 만들어낼 수 있다는 이야기입니다.

grammar = {
    "_S" : ["_NP _VP"],
    "_NP" : ["_N",
             "_A _NP _P _A _N"],
    "_VP" : ["_V",
             "_V _NP"],
    "_N" : ["data science", "Python", "regression"],
    "_A" : ["big", "linear", "logistic"],
    "_P" : ["about", "near"],
    "_V" : ["learns", "trains", "tests", "is"]
}

코드

그렇다면 유한문법 규칙을 활용해 어떻게 문장을 생성할 수 있을까요? 문장(S)로 시작한다고 했을 때 모든 항목이 종결어(teminal)가 될 때까지 유한문법규칙에 맞는 구나 단어를 재귀적으로 선택함으로써 만들어낼 수 있습니다. 그 코드는 다음과 같습니다.

import random

def is_terminal(token):
    return token[0] != "_"

def expand(grammar, tokens):
    for i, token in enumerate(tokens):

        # ignore terminals
        if is_terminal(token): continue

        # choose a replacement at random
        replacement = random.choice(grammar[token])

        if is_terminal(replacement):
            tokens[i] = replacement
        else:
            tokens = tokens[:i] + replacement.split() + tokens[(i+1):]
        return expand(grammar, tokens)

    # if we get here we had all terminals and are done
    return tokens

def generate_sentence(grammar):
    return expand(grammar, ["_S"])

Comment  Read more

관형사란 무엇인가

|

이번 글에서는 한국어의 9품사 가운데 하나인 관형사에 대해 살펴보도록 하겠습니다. 이번 글은 경희대 이선웅 교수님 강의와 ‘왜 다시 품사론인가(남기심 외, 커뮤니케이션북스)’, ‘표준국어문법론(남기심&고영근, 탑출판사)’을 정리하였음을 먼저 밝힙니다. 그럼 시작하겠습니다.

정의 및 핵심 기능

학교문법에 따르면 관형사(冠形詞)란 체언 앞에서 그 체언의 뜻을 분명하게 제한하는 품사입니다. 국어 관형사 가운데는 (1)과 같은 고유어는 얼마되지 않고 (2)와 같은 한자어가 많은 부분을 차지하고 있다고 합니다.

(1) 이 거리에는 집과 집이 서로 이웃해 있다.

(2) 서울대학교는 구(舊) 경성제국대학을 모태로 하여 발족되었다.

관형사는 체언 이외의 품사는 꾸미는 일이 없습니다. 관형사가 나란히 놓여 있을 때는 다음처럼 앞의 관형사가 뒤의 관형사를 꾸미는 것처럼 보일 때가 있습니다. 하지만 아래의 예에서 ‘저’, ‘이’는 명사구 ‘새 책’과 ‘헌 구두’를 꾸미므로 관형사의 궁극적인 수식대상은 명사라고 말할 수 있습니다.

책이 누구의 책이냐?

구두가 제 것입니다.

종류

학교문법에서 규정하는 관형사에는 성상관형사(性狀冠形詞), 수관형사(數冠形詞), 지시관형사(指示冠形詞) 세 가지 종류가 있습니다. 성상관형사는 꾸밈을 받는 명사의 모양, 성질이나 상태를 나타내는 관형사를 가리킵니다. 수관형사는 주로 단위성 의존명사와 결합하여 사물의 수나 양을 나타내는 관형사입니다. 지시관형사는 특정한 대상을 지시하여 가리키는 관형사입니다. 각각의 예시는 다음과 같습니다.

성상관형사 : (1) 고유어계 : (집/옷/해..), (집/옷/책…), (말/고생/생각…), (집/말/사람…) (2) 한자어계 : 순(純)(이익/한국말/유럽산…), 호(好)(결과/영향…), 구(舊)(관립 한성고등학교/국제우체국…), 대(大)(사건/건축/문제…), 장(長)(거리/기간…), 고(高)(물가/비행…), 주(主)(세력/원인…), 정(正)(교수/교사…), 이(異)(민족…)

수관형사 : (1) 한, 두, 세(석/서), 네(넉/너), 다섯(닷), 여섯(엿), 일곱, 여덟, 아홉, 열, 열 한, 열 두, 열 세(석/서), 열 네(넉/너),…,스무… (2) 한두, 두세, 서너, 두서너… (3) 일이(一二), 이삼(二三), 삼사(三四)… (4) 여러, 모든, 온, 온갖, 갖은, 반(半), 전(全)

지시관형사 : (1) 고유어계 : 이, 그, 저, 요, 고, 조, 이런, 그런, 저런, 다른(他) (2) 한자어계 : 귀(貴)(가족…), 본(本)(연구소…), 동(同)(시험장…), 현(現)(국무총리…), 전(前)(교육부장관…), 모(某)(년/월/일)

특성

관형사로 분류되는 형태 가운데에는 접두사, 어근 등 다른 형태 범주뿐만 아니라 명사, 수사, 형용사, 부사 등 다른 품사와도 쉽게 구별되지 않는 경우가 많습니다. 게다가 그 수도 다른 품사 대비 적습니다. 한국어 이외의 다른 언어에서 나타나지 않는 독특한 단어 부류이기도 하고요. 이 때문에 관형사를 독자 품사로 분리하는 게 적절치 않다는 견해를 가지고 있는 학자들도 꽤 있습니다.

어쨌든 학교문법에서는 관형사를 독자 품사로 설정하고 있는데요. 관형사는 다음과 같은 특징을 가집니다.

형태적 : 조사나 어미가 결합할 수 없는 불변어이다.

통사적 : 명사를 수식하는 기능을 하지만, 자립성이 약해서 문장 안에서 단독으로 쓰이지 못한다.

의미적 : 피수식어인 체언의 뜻을 분명하게 제한한다.

하지만 명사라고 해서 모두 관형사의 꾸밈을 받는 것은 아닙니다. 관형사가 고유명사, 의존명사, 추상명사, 대명사 따위를 수식하는 데는 다음과 같이 제약이 따릅니다.

*새 철수

*헌 데

*그 너

단어형성의 기본 원리

단어는 다음 예시와 같이 그 짜임새가 단일할 수도 있고 복합적일 수도 있습니다.

(가) 단일어 : 집, 신, 높다…

(나) 파생어 : 지붕, 덧신, 드높다…

(다) 합성어 : 집안, 짚신, 높푸르다…

(가)와 같이 그 짜임새가 단일한 단어를 단일어라고 하고, (나) (다)와 같이 그 짜임새가 복합적인 말을 복합어라고 합니다. 복합어의 형성에 나타나는 실질형태소어근(root)이라고 하고 형식형태소접사(affix)라고 합니다. 중심적인 의미가 어휘적인 형태소를 실질형태소, 실질적이지 않고 문법적이면 형식형태소라고 합니다. (나)처럼 실질형태소에 형식형태소가 붙어서 만들어진 말을 파생어(derived word), (다)처럼 실질형태소들의 결합으로 이루어진 말을 합성어(compound word)라고 합니다.

관형사 vs 접사

그렇다면 다음의 ‘맨’은 관형사로 분류해야 할까요? 아니면 접사로 해야할까요? 관형사와 접사 모두 자립성이 약해 다른 단어와 동시에 쓰이고, 같이 나타나는 다른 형태소의 의미를 분명하게 제한한다는 점에서 알쏭달쏭합니다.

(ㄱ) 맨손, 맨주먹, 맨발, 맨머리, 맨몸, 맨밥, 맨입…

(ㄴ) 맨 꼭대기, 맨 위, 맨 밑, 맨 아래, 맨 끝, 맨 꼬리, 맨 나중, 맨 뒤, 맨 앞, 맨 처음…

외솔 최현배 선생(1894~1970)께서는 어휘적 의미에 집중해 (ㄱ)을 접사, (ㄴ)을 관형사로 분류했습니다. (ㄱ)처럼 ‘의관이나 또 다른 것으로 꾸미지 아니하다’로 쓰였을 경우 접두사, (ㄴ)처럼 ‘가장(最)’으로 사용했다면 관형사에 해당한다는 논리입니다. 하지만 이러한 기준은 불분명할 뿐더러 ‘맨’에만 쓸 수 있어 모든 단어에 공통적으로 적용할 수 없다는 단점이 있습니다.

어휘적 의미 이외에 생각해볼 수 있는 건 ‘결합제약’입니다. 즉 관형사는 문장에서 다른 명사들과 비교적 자유롭게 결합할 수 있으나 접사는 제한된 어근과만 결합한다는 얘기입니다.

위 예시에서 (ㄱ)의 의미로 ‘맨’을 쓸 경우 ‘맨눈’, ‘맨팔’은 되지만 *맨배, *맨코는 말이 성립하지 않는다는 걸 알 수 있습니다. ‘맨’은 아무 명사에나 붙는 것이 아니어서 결합제약이 크다는 말입니다. 그런데 (ㄴ)의 의미로 ‘맨’을 사용한다면 ‘맨 먼저’와 같이 대부분의 명사에 ‘맨’을 쓸 수 있어 결합제약이 상대적으로 작습니다.

관형사와 접사를 가르는 또다른 기준은 ‘중간에 다른 단어를 삽입할 수 있는지’ 여부가 될 수 있습니다. 중간 삽입이 가능하다면 관형사, 불가능하다면 접사로 나누자는 의견입니다. 아래 예시에서 (ㄱ)은 중간 삽입이 불가능하지만 (ㄴ)은 가능합니다.

(ㄱ) 맨눈 / *맨(좋은)눈

(ㄴ) 맨 먼저 / 맨 (처음 온 사람) 먼저

한편 ‘맨 먼저’를 발음할 때는 ‘맨’과 ‘먼저’ 사이에 휴지(休止)를 둘 수 있지만 ‘맨눈’을 발음할 땐 하나의 단위로 소리내는 경우가 많습니다. 이는 중간 삽입 기준과 밀접한 연관을 맺고 있습니다.

요컨대 결합제약과 중간삽입 기준 두 가지를 고려할 때 (ㄱ)은 접사, (ㄴ)은 관형사로 분류할 수 있습니다. 그러나 이러한 기준을 모든 상황에 일률적으로 적용하기는 쉽지 않아서 관형사와 접사의 차이는 결국 국어 화자의 직관에 의존할 수밖에 없는 것 아니냐는 비관적 의견도 일부 있기는 합니다.

관형사 vs 어근

관형사와 어근을 나누는 것도 그리 분명하지 않습니다. 표준국어대사전을 참고해 ‘새’와 관련된 표제어를 조사해 보았습니다.

새-것, 새-해, 새-집, 새-색시, 새-봄

사전 편집자는 위 예시에서 ‘새’와 ‘것’, ‘해’, ‘집’, ‘색시’, ‘봄’은 그보다 큰 단위의 명사를 형성하는 데 참여하는 어근의 하나로 분석한 것입니다. 바꿔 말해 위 예시들을 ‘어근(새) + 어근’ 구조인 합성어로 보고 표제어로 등록했다는 이야기입니다.

그런데 아래 예시는 표준국어대사전에서 표제어로 등록돼 있지 않은 걸 확인할 수 있습니다. 다시 말해 사전 편집자는 아래 예시들을 ‘관형사(새) + 명사’의 일반적인 결합구조로 보고 별도 표제어로 등록하지 않은 셈이죠.

새 신발, 새 구두, 새 책, 새 옷, 새 시계

실제로 문법서나 사전마다 관형사와 어근 구분이 제각기 다르다고 합니다. 그만큼 관형사와 어근을 가리는 게 어렵다는 얘기입니다. 다만 ‘새해’, ‘새색시’처럼 언중들이 해당 단어를 대부분 한 단위로 인식하고 사용한다면 이때의 ‘새’를 어근으로 분석하는 것이 그리 비합리적인 처리는 아닐 것입니다.

관형사 vs 용언의 활용형

관형사는 통시적으로 보면 용언 어간과 관형사형 어미의 결합에서 발달한 것으로 보이는 예들이 있습니다. ‘갖은’이 대표적인 사례입니다.

중세국어에서 동사 ‘갖-‘은 현대국어의 ‘갖추어지다’라는 뜻으로 사용됐습니다. 여기에 관형사형 어미 ‘-은’이 붙으면 ‘갖은’이 됩니다. 만약 중세 시기에 활동하는 국어학자가 있다면 ‘갖은’은 동사 ‘갖-‘에 관형사형 어미 ‘-은’ 두 개로 분석하고, 사전에는 이 둘만 실었을 것입니다.

그런데 시간이 흘러 현대로 오면서 동사 ‘갖-‘에 ‘갖추어지다’라는 본래 의미가 사라지고 have의 의미만 남았습니다. 이와 더불어 ‘골고루 다 갖춘’이라는 뜻을 지닌 ‘갖은’이라는 단어가 화석처럼 전해졌죠.

현대 국어 화자들은 이러한 ‘갖은’을 ‘갖추어지다’는 뜻의 동사 ‘갖-‘과 관형사형 어미 ‘-은’을 더 이상 구별해낼 수 없습니다. 현대 국어를 연구하는 국어학자들은 이 때문에 관형사로 사전에 등재할 수밖에 없게 된 것이죠. 이때 ‘갖은’은 조사나 어미가 붙지 않고 활용을 하지 않으며 명사를 수식하는 기능을 갖기 때문에 그 품사는 관형사로 분류되게 됩니다.

‘갖은’ 이외의 비슷한 사례들이 많습니다. 다음과 같습니다.

몹쓸, 고얀, 헌, 어떤, 어쩐, 긴긴, 어쩐, 허튼, 괜한, 외딴, 아무런, 오랜, 바른, 지난, 이/그/저런…

그러나 이들 중에서도 ‘헌-헐다’, ‘어떤-어떻다’, ‘아무런-아무렇다’, ‘오랜-오래다’, ‘바른-바르다’, ‘지난-지나다’, ‘이/그/저런-이/그/저렇다’처럼 현대 국어 화자들이 보기에 용언의 기본형이 예상되는 경우가 많아서 이들을 정말 관형사로 분류해야 하는지, 용언의 관형사형으로 봐야 하는지 판단이 쉽지가 않기는 합니다.

관형사 vs 부사

관형사와 부사를 구분해야 할 경우가 있습니다. 예문을 보겠습니다.

30일이 지났다.

위 예문에서 ‘꼭’이 수식하는 대상은 ‘30일’이라는 명사입니다. ‘꼭 지났다’는 말은 성립하지 않아 동사를 수식했다고 보기는 어렵기 때문입니다. 따라서 위 예문에서 ‘꼭’은 부사가 아니라 관형사 역할을 했다고 볼 수가 있습니다.

표준국어대사전에 따르면 ‘조금도 어김없이’ 등의 의미로 쓰이는 ‘꼭’의 품사는 부사입니다. 그러면 예문에서 ‘꼭’의 품사는 무엇일까요? 이와 관련해 국어학계에서는 통일된 의견이 존재하지 않는 상황이라고 합니다.

이번엔 ‘바로’를 살펴보겠습니다. 다음 문장을 보겠습니다.

내가 좋아하는 사람은 바로 너이다.

위 예문에서 ‘바로’가 수식하는 대상은 분명하지 않습니다. 한국어를 모국어로 하는 화자라도 ‘너’라는 명사를 꾸며주는지, ‘너이다’ 서술어를 꾸며주는지 직관적으로 구분해낼 수 없을 정도입니다. 전자에 해당하는게 분명하다면 이때 ‘바로’는 관형사, 후자라면 부사로 분류할 수 있을텐데 말이죠.

‘바로’의 품사를 세밀하게 따져보기 위해 다음과 같이 명사(구)로만 해석될 수 있는 통사적 환경을 만들어 봅시다.

바로 너를 만났다.

어떤 분은 위 예시를 보고, ‘너를 바로 만났다’라고도 해석할 수 있지 않느냐고 반문하실지 모르겠습니다. 하지만 이렇게 되면 위 예시의 문장과 그 의미가 달라지게 됩니다. 다시 말해 예문은 ‘다른 사람 말고 바로 너’를 만났다는 뜻이 되고, ‘너를 바로 만났다’는 ‘오래 전도 아니고 지금 바로’ 너를 만났다는 뜻이기 때문입니다.

어쨌든 예문의 통사적 환경에서 ‘바로’가 큰 무리없이 쓰일 수 있는 점을 확인할 수 있습니다. 따라서 예문에서의 ‘바로’는 관형사라고 해도 큰 무리가 없을 겁니다.

이번에는 정도(degree)를 나타내는 부사와 관형사를 구분해 보겠습니다. 다음 예문은 한국어에서 자연스럽습니다.

(A) 그 사람은 아주/매우/꽤 부자이다.

그러면 ‘아주/매우/꽤’는 부사로 분류해야 할까요? 관형사로 분류해야 할까요? 위 예문에서 이들이 ‘부자’를 꾸민다면 관형사, ‘부자이다’라는 서술어를 꾸민다면 부사로 분류하면 됩니다. 하지만 이 예문으로는 명확하지 않습니다. 정확한 판단을 위해 명사(구) 통사환경을 다음과 같이 만들어 봅시다.

내가 어제 아주 부자를 만났어

내가 어제 *매우 부자를 만났어

내가 어제 *꽤 부자를 만났어

‘아주 부자’는 말이 되지만 ‘매우’와 ‘꽤’는 그렇지 않음을 확인할 수 있습니다. 그러면 역으로 (A) 예시의 ‘매우’와 ‘꽤’는 ‘부자이다’라는 서술어를 수식하는 부사로 보는 것이 합리적입니다.

아울러 ‘아주’는 관형사로 보는 것이 타당합니다. ‘아주’ 같은 경우에는 주로 부사로 쓰이나 워낙 자주 쓰이는 단어이기 때문에 체언을 꾸며주는 역할(관형사)까지 하게 된 것으로 보입니다.

이와 비슷하게 통시적인 변화를 겪어서 관형사 역할도 일부 담당하는 부사의 사례로는 ‘오직’이 있습니다. 다음과 같은 예시가 이 경우에 해당합니다.

오직 너만 사랑한다.

Comment  Read more

정규분포 누적분포함수와 중심극한정리

|

이번 글에서는 정규분포(Normal Distribution)중심극한정리(Central Limit Theorem)을 간단한 파이썬 코드 중심으로 살펴보도록 하겠습니다. 이 글은 ‘밑바닥부터 시작하는 데이터과학(조엘 그루스, 인사이트 펴냄)’과 ‘일반통계학(김우철 외, 영지문화사)’ 두 책과 고려대 한성원 교수님 강의를 정리했음을 먼저 밝힙니다. 그럼 시작하겠습니다.

정규분포

정규분포는 가우스(Gauss, 1777-1855)에 의해 제시된 분포로서 일명 가우스분포(Gauss Distribution)라고 불리며 물리학 실험 등에서 오차에 대한 확률분포를 연구하는 과정에서 발견되었다고 합니다. 가우스 이후 이 분포는 여러 학문 분야에서 이용되었으며, 초기의 통계학자들은 모든 자료의 히스토그램이 정규분포의 형태와 유사하지 않으면 비정상적인 자료라고까지 생각하였다고 합니다. 이러한 이유로 이 분포에 ‘정규(normal)’라는 이름이 붙게 된 것입니다.

정규분포는 특성값이 연속적인 무한모집단 분포의 일종으로서 평균이 $μ$이고 표준편차가 $σ$인 경우 정규분포의 확률밀도함수(Probability Density Function)는 다음과 같습니다.

[f(x \mu ,\sigma )=\frac { 1 }{ \sqrt { 2\pi } \sigma } exp\left( -\frac { { (x-\mu ) }^{ 2 } }{ 2{ \sigma }^{ 2 } } \right)]

평균, 편차에 따른 분포의 변화

정규분포의 파라메터는 평균과 표준편차입니다. 파라메터가 변하면 분포 또한 바뀌게 되는데요. 아래 그림과 같습니다.

위 그림을 생성하는 데 필요한 파이썬 코드는 다음과 같습니다.

import math
from matplotlib import pyplot as plt
def normal_pdf(x, mu=0, sigma=1):
    sqrt_two_pi = math.sqrt(2 * math.pi)
    return (math.exp(-(x-mu)**2 / 2 / sigma**2) / (sqrt_two_pi * sigma))
xs = [x / 10.0 for x in range(-50,50)]
plt.plot(xs,[normal_pdf(x,sigma=1) for x in xs],'-',label='mu=0,sigma=1')
plt.plot(xs,[normal_pdf(x,sigma=2) for x in xs],'--',label='mu=0,sigma=2')
plt.plot(xs,[normal_pdf(x,sigma=0.5) for x in xs],':',label='mu=0,sigma=0.5')
plt.plot(xs,[normal_pdf(x,mu=-1) for x in xs],'-.',label='mu=-1,sigma=1')
plt.legend()
plt.title('Various Normal pdfs')
plt.show()

정규분포의 누적분포함수

누적분포함수(Cumulative Distribution Function, CDF)는 어떤 확률분포에 대해 확률변수가 특정 값보다 작거나 같은 확률을 나타냅니다. 아래 표는 평균이 0이고 표준편차가 1인 표준정규분포의 누적분포함수를 표로 정리한 것인데요. 빨간색 영역에 해당하는 확률이 바로 CDF에 해당합니다.

표준정규분포의 확률변수를 $Z$라고 할 때 $Z$값(위 표에서 행과 열의 이름에 해당)의 변화에 따른 누적분포함수 값의 변화를 나타낸 그림은 다음과 같습니다. 정규분포의 누적분포함수 또한 평균, 분산이 달라지면 그 모양도 달라지는걸 확인할 수 있습니다.

위 그림을 만드는 데 사용한 파이썬 코드는 다음과 같습니다.

import math
from matplotlib import pyplot as plt
def normal_cdf(x, mu=0, sigma=1):
    return (1 + math.erf((x - mu) / math.sqrt(2) / sigma)) / 2
xs = [x / 10.0 for x in range(-50,50)]
plt.plot(xs,[normal_cdf(x,sigma=1) for x in xs],'-',label='mu=0,sigma=1')
plt.plot(xs,[normal_cdf(x,sigma=2) for x in xs],'--',label='mu=0,sigma=2')
plt.plot(xs,[normal_cdf(x,sigma=0.5) for x in xs],':',label='mu=0,sigma=0.5')
plt.plot(xs,[normal_cdf(x,mu=-1) for x in xs],'-.',label='mu=-1,sigma=1')
plt.legend(loc=4)
plt.title('Various Normal cdfs')
plt.show()

표준정규분포 누적분포함수의 역함수

여러 통계학 문제를 풀다보면 특정 확률에 해당하는 표준정규분포의 $Z$값을 알고 싶은 경우가 많습니다. 예컨대 앞선 예시의 표에서 누적확률이 0.9990에 해당하는 $Z$값 3.09를 찾아보자는 것이죠. 이를 이진검색 기법을 활용해 근사하는 파이썬 코드는 다음과 같습니다.

# 정규분포 누적분포함수의 역함수
def inverse_normal_cdf(p, mu=0, sigma=1, tolerance=0.00001):
    '''이진검색을 사용해서 역함수 근사'''

    # 표준정규분포가 아니라면 표준정규분포로 변환
    if mu != 0 or sigma != 1:
        return mu + sigma * inverse_normal_cdf(p, tolerance=tolerance)

    low_z, low_p = -10.0, 0 # normal_cdf(-10)는 0에 근접
    hi_z, hi_p = 10.0, 1 # normal_cdf(10)는 1에 근접

    while hi_z - low_z > tolerance:
        mid_z = (low_z + hi_z) / 2 # 중간 값
        mid_p = normal_cdf(mid_z) # 중간 값의 누적분포 값을 계산
        if mid_p < p:
            # 중간 값이 너무 작다면 더 큰 값들을 검색
            low_z, low_p = mid_z, mid_p
        elif mid_p > p:
            # 중간 값이 너무 크다면 더 작은 값들을 검색
            hi_z, hi_p = mid_z, mid_p
        else:
            break

    return mid_z

‘inverse_normal_cdf(p=0.9990)’을 실행하면 ‘3.090238571166992’라는 값이 반환됩니다.

중심극한정리

모집단의 분포가 정규분포를 따를 경우에는 모집단에서 뽑은 표본 또한 정규분포를 따릅니다. 모집단 평균이 $μ$이고 표준편차가 $σ$, 표본의 크기가 $n$일 때 다음이 성립합니다.

[X\sim N(\mu ,{ \sigma }^{ 2 })\quad \rightarrow \quad \overline { X } \sim N(\mu ,\frac { { \sigma }^{ 2 } }{ n } )]

모집단의 분포가 정규분포가 아닌 경우에는 이 사실이 성립하지 않습니다. 그러나 표본의 크기 $n$이 충분히 클 때에는 정규분포를 따르지 않는 임의의 모집단으로부터의 표본이라 하더라도 그 분포가 정규분포에 가깝다는 사실이 알려져 있으며 이것을 중심극한정리라고 합니다. 다시 말해 모집단의 분포가 어떤 형태이든 간에 표본의 크기가 충분히 크기만 하면 해당 표본이 근사적으로 정규분포를 따른다는 것입니다.

중심극한정리 예시

보다 쉽게 이해하기 위해 이항분포(Binomial Distribution)를 예시로 설명해보겠습니다.

입시에서 합격과 불합격, 스포츠 경기에서 승리와 패배 같이 어떤 실험이 두 가지 가능한 결과만을 가질 경우 이를 베르누이시행(Bernoulli)이라고 합니다. 예를 들어 동전을 던지는 실험은 그 결과가 앞면, 또는 뒷면인 베르누이시행이 됩니다.

성공확률이 $p$인 베르누이시행을 $n$번 반복시행할 때 성공횟수 $X$의 분포를 이항분포라고 합니다. 이때 이항분포의 평균과 분산은 각각 $np$, $np(1-p)$가 되는데요. 중심극한정리는 $n$이 적당히 크다면 $X$가 정규분포를 따르지 않지만 표본의 분포가 평균이 $np$이고 분산이 $np(1-p)$인 정규분포와 유사해진다는 점을 알려줍니다.

아래 그림은 성공확률이 0.5인 베르누이시행을 100번 반복시행했을 때 성공횟수의 분포를 히스토그램으로 그린 것입니다. 실선은 평균이 50, 분산이 25인 정규분포를 그 확률밀도함수로부터 도출한 것입니다. 두 모양이 비슷한 것을 알 수 있습니다.

위 그림을 만드는 데 쓴 파이썬 코드는 다음과 같습니다.

import math
import random
from collections import Counter
from matplotlib import pyplot as plt
def bernoulli_trial(p):
    return 1 if random.random() < p else 0
def binomial(n, p):
    return sum(bernoulli_trial(p) for _ in range(n))
def make_hist(p, n, num_points):
    data = [binomial(n,p) for _ in range(num_points)]

    # 이항분포의 표본을 막대 그래프로 표현
    histrogram = Counter(data)
    plt.bar([x - 0.4 for x in histrogram.keys()],
            [v / num_points for v in histrogram.values()],
            0.8,
            color='0.75')
    mu = p * n
    sigma = math.sqrt(n * p * (1 - p))

    # 근사된 정규분포를 라인 차트로 표현
    xs = range(min(data), max(data) + 1)
    ys = [normal_cdf(i + 0.5, mu, sigma) - normal_cdf(i - 0.5, mu, sigma) for i in xs]
    plt.plot(xs,ys)
    plt.title("Binomial Distribution vs. Normal Approximation")
    plt.show()

Comment  Read more

RNN과 Beam search

|

이번 글에서는 Recursive Neural Network(RNN)의 학습 과정에서 트리 탐색 기법으로 쓰이는 Beam seach에 대해 살펴보도록 하겠습니다. beam search는 RNN 말고도 자연언어처리 분야에서 자주 쓰인다고 하니 이 참에 정리해 두면 유용할 듯합니다. 이번 글은 Socher et al.(2011)과 미국 스탠포드 대학 NLP 강의 자료를 참고해 만들었음을 먼저 밝힙니다. Beam Search 예시 부분은 제가 직접 만든 것이니 혹시 오류가 있다면 언제든 알려주시면 감사하겠습니다. 그럼 시작하겠습니다.

RNN과 Tree

RNN은 응집성이 높은 입력값을 트리 형태로 결합해 가면서 입력값의 구조를 추상화하는 기법입니다. Simple RNN에 대해서는 이곳을, 발전된 형태의 RNN 모델에 대해 살펴보시려면 이곳을 참고하면 좋을 것 같습니다.

어쨌든 RNN과 트리는 뗄려야 뗄 수 없는 관계를 가집니다. 이미지와 텍스트에 대해 RNN을 적용해보고자 했던 Socher et al.(2011)에도 입력값이 주어졌을 때 트리를 구축하는 방안에 대해 논문의 상당 부분을 할애하고 있습니다. 다음 그림을 볼까요?

Socher et al.(2011)은 우선 입력값의 이웃끼리 결합해 트리를 만들기로 합니다. 이미지를 예로 들면 1번 영역의 이웃은 2번과 3번입니다. 이를 인접행렬(Adjacency Matrix)로 나타내면 (1,2), (2,1), (1,3), (3,1) 위치의 요소값이 1이 되어야 할 겁니다. 이렇게 이웃이 될 수 있는 가능한 모든 경우의 수를 고려해 행렬로 나타낸 것이 좌측 두번째 그림이 됩니다.

본 블로그의 관심 주제인 텍스트는 이미지보다는 간단한 편입니다. 어떤 단어의 이웃은 왼쪽 하나, 오른쪽 하나 두개뿐입니다. 예컨대 house의 이웃은 The, has이고 a의 이웃은 has, window입니다. 이를 인접행렬로 그리면 대각성분 위, 아래만 1이고 나머지는 0인 규칙적인 모양이 됩니다. 이웃이 될 수 있는 모든 경우의 수에서 그 가짓수를 하나씩 제거해 정답 트리 구조(Correct Tree Structure)로 나아가자는 것이 트리 탐색의 핵심이 됩니다.

트리 탐색의 범위를 이웃으로 한정하긴 했지만 Socher et al.(2011) 방식의 본질은 탐욕적인 탐색이라고 말할 수 있겠습니다. 논문의 예를 확장시켜 그림으로 나타내 보았습니다. 아래 그림에서 $a_i$는 i번째 단어(노드)를 의미합니다. 첫번째 단계에서의 인접행렬은 이웃이 될 수 있는 모든 경우의 수가 포함되어 있습니다. $C$는 이웃들의 쌍으로 이루어진 집합입니다.

RNN 모델이 내놓은 score가 $[a_4,a_5]$가 가장 높았다고 가정해보겠습니다. 다시 말해 모델이 a와 window라는 단어가 응집성이 가장 높다고 판단한 것이지요. 이 둘을 이어서 트리를 만드는 것이 첫번째 과정입니다. 다음 그림처럼요. 아래 그림을 보시면 $[a_4,a_5]$가 결합해 parent node $p_{(4,5)}$가 되었습니다. 여기에 맞춰서 인접행렬과 $C$가 업데이트된 것도 확인가능합니다.

이번엔 RNN 모델이 출력한 score가 The, house가 가장 높았다고 가정해보겠습니다. 그러면 인접행렬과 $C$는 다음과 같이 업데이트됩니다.

이번엔 RNN 모델이 has라는 단어와 ‘a, window’라는 구의 응집성이 가장 높다고 판단했다고 칩시다. 다음 그림과 같이 업데이트됩니다.

이제는 연결해야 하는 노드가 딱 두 개뿐이므로 이 둘만 연결해주면 아래 그림처럼 트리 탐색 과정이 종료됩니다.

보시다시피 Socher et al.(2011)의 방식은 탐욕적입니다. 이게 찔려선지 Socher et al.(2011)은 텍스트에 대해서는 Beam Search를 적용할 수 있다고 언급했습니다. 논문의 일부를 인용해봤습니다.

Since in a sentence each word only has 2 neighbors, less-greedy search algorithms such as a bottom-up beam search can be used.

Beam Search란 최고우선탐색(Best-First Search) 기법을 기본으로 하되 기억해야 하는 노드 수를 제한해 효율성을 높인 방식입니다. Socher et al.(2011)이 언급했듯 여전히 탐욕적이지만 기존 방식보다는 조금 나은 기법입니다. 다음 예제 그림을 볼까요?

사용자가 기억해야 하는 노드 수(Beam)를 3으로 정했다고 가정해 봅시다. 그러면 $i$번째 step에서 다음 step에 선택될 수 있는 가능한 모든 경우의 수를 계산해본 뒤 Beam Search의 기본이 되는 알고리즘(예컨대 RNN)이 내놓는 최상위 3개 결과만 취해서 $i+1$번째 step의 결과물로 반환하는 방식입니다. 위 그림의 예시는 상위 노드에서 하위 노드로 분기해 나가는 Top-Down Beam Search입니다.

파싱과 같은 자연언어처리 분야에서는 Top-Down보다는 Bottom-Up Beam Search 기법이 자주 쓰인다고 합니다. 이 기법은 하위 노드에서 상위 노드로 결합해 나가는 방식입니다. 임의의 7개 단어가 있고 이로부터 파싱 트리를 구축해야 하는 상황을 가정해보겠습니다. 사용자가 지정한 Beam은 2라고 두겠습니다.

초기 상태는 다음 그림과 같습니다. 각 단어들은 왼쪽과 오른쪽 이웃 두 개씩만 있기 때문에 다음 step에 선택될 수 있는 가능한 모든 경우의 수는 빨간색 점선과 같습니다. 이 가운데 첫번째-세번째 단어, 세번째-네번째 단어를 결합하는 것이 모델의 판단결과라고 가정해 보겠습니다.

두번째 step에선 이들을 잇고 나서 세번째 step에 선택될 수 있는 가능한 모든 경우의 수를 계산해 본 뒤 최적 두 개 결과를 선택합니다.

세번째 step에서도 지금까지 수행한 작업을 반복합니다.

그런데 다섯번째 단어는 왼쪽 트리, 오른쪽 트리 둘 모두에 속할 수는 없습니다. 이 가운데 score가 좀 더 높은 쪽에 할당되게 됩니다.

이제 남은 경우의 수는 단 하나뿐이므로 모델이 내놓는 score를 반영할 필요도 없이 트리를 완성하기만 하면 됩니다.

Comment  Read more