for textmining

형태소란 무엇인가

|

이번 포스팅에서는 언어학과 자연언어처리 분야 기본 중 하나인 형태소(形態素morpheme)에 대해 다뤄보려고 합니다. 형태소 정의와 분석방법 등에 대해 알아보겠습니다. 이번 글은 이선웅 경희대 교수, 최형용 이화여대 교수께서 쓰신 글들을 참고해 작성했음을 먼저 밝힙니다. 그럼 시작하겠습니다.

형태소의 정의

형태소란 의미를 가지는 최소 단위로 정의됩니다. 더 쪼개면 뜻을 잃어버리는 말의 단위라고 할 수 있죠. 이때의 ‘의미’는 어휘적인 것뿐 아니라 문법적인 것도 포함한다는 사실을 염두에 두고 아래 예문을 통해 개념을 살펴보기로 하죠. 예시에서 ‘철수’를 ‘철’과 ‘수’로 쪼개면 ‘철수’라는 사람을 지칭하는 의미가 없어집니다. 마찬가지로 ‘밥’을 ‘ㅂ’과 ‘압’으로 나누면 먹는 밥(rice)의 뜻이 사라지게 되죠. 이런 점에서 ‘철수’와 ‘밥’은 형태소 후보에 오를 수 있겠습니다.

철수가 밥을 먹었다.

형태소를 분석하는 기준으로는 계열관계(系列關係paradigmatic relation)통합관계(統合關係sytagmatic relatation)이 있습니다. 계열관계는 종적인 것으로서 그 자리에 다른 형태소가 ‘대치’될 수 있는가를 따지는 것이고, 통합관계는 횡적인 것으로서 그 형태소가 다른 형태소와 ‘결합’할 수 있는가를 고려하는 것입니다. 이 가운데 형태소 분석에서 더 중요한 역할을 담당하는 것은 계열관계인데요, 계열관계만으로도 형태소 자격을 부여할 수 있습니다.

일단 먼저 계열관계를 살펴볼까요? ‘철수’ 자리에 ‘영희’와 같은 말이 대치될 수 있습니다. ‘밥’ 대신에 ‘빵’을 쓸 수 있죠. 따라서 ‘철수’와 ‘밥’은 형태소 자격을 갖습니다. 그럼 ‘가’는 어떨까요? ‘가’는 그 자체로 어휘적 의미를 가지지는 못하지만 그 말이 결합한 말을 주어가 되게 하므로 문법적 의미를 갖습니다. 또 ‘가’는 ‘는’이나 ‘도’와 계열관계를 이루므로 형태소로서의 자격을 갖습니다.

그런데 ‘가’는 조금 더 세밀히 살펴야 합니다. 그 말을 주어가 되게 하는 것이 ‘가’만 있는 것은 아니기 때문인데요. 선행하는 말이 ‘철수’가 아니라 ‘책’과 같이 받침이 있는 말이라면 ‘가’ 대신 ‘이’가 선택됩니다. 이처럼 ‘이’가 되거나 ‘가’가 되는 것은 ‘이’나 ‘가’가 서로 다른 (문법적)의미를 가지기 때문이 아니라 단지 선행하는 말이 받침을 가지고 있느냐 여부에 있으므로 ‘이’나 ‘가’는 모양은 다르지만 서로 같은 형태소라고 할 수 있습니다. 바꿔 말하면 ‘이’와 ‘가’는 같은 형태소의 다른 형태라고 할 수 있는 셈이죠. 이처럼 같은 형태소의 다른 형태들을 이형태(異形態allomorph)라고 합니다.

‘밥’을 조금 더 살펴보겠습니다. 앞서 언급했듯 ‘밥’은 형태소이지만 ‘밥만’과 같은 예에서는 ‘밤’으로 실현됩니다. ‘밥’과 ‘밤’이 이형태라는 말이지요. 하지만 이것은 표기에서는 구분되지 않습니다. 이것은 한국어의 표기체계가 이형태 가운데 대표적인 기본형(基本形basic form)을 밝혀 적은 원칙을 따르고 있기 때문입니다. 한편 ‘을’은 ‘가’와 상황이 비슷합니다. 주로 선행하는 말과 결합하여 그 말을 목적어가 되게 해주고 ‘은’이나 ‘도’와 계열관계를 이루고 있으므로 하나의 형태소입니다. 그러나 선행어가 받침을 가지느냐 여부에 따라 ‘를’을 이형태로 갖습니다.

이제 ‘먹었다’를 분석해볼까요? 우선 ‘먹-‘은 그 자체로 어휘적인 의미를 가지면서 그 자리에 ‘잡-‘과 같은 말이 대치될 수 있으므로 하나의 형태소입니다. ‘-었-‘은 ‘과거’를 나타내주는 문법적 의미를 가지고 있으면서 ‘-겠-‘과 같은 말과 계열관계를 이루고 있으므로 역시 형태소입니다. 마지막으로 ‘-다’도 문법적인 의미를 가지면서 ‘-어’와 같은 다른 어미와 대치될 수 있으므로 형태소입니다. ‘먹-‘은 ‘먹는[멍는]’과 같은 예에서도 알 수 있듯 이형태로 ‘멍’과 같은 걸 가지지만 표기로는 반영되지 않습니다. ‘-었-‘은 선행모음이 음성모음이냐 양성모음이냐에 따라 ‘-았-‘이라는 이형태를, 선행어가 ‘하-‘일 경우에는 ‘-였-‘이라는 이형태를 갖습니다. ‘-다’는 ‘-더-‘와 결합할 때는 ‘-라’를 이형태로 가집니다.

형태소 정의에 관한 다양한 이견들

형태소는 앞서 언급한 바와 같이 의미를 가지는 최소 단위입니다. 더 쪼갤 수는 있으나 그렇게 하면 스스로 의미를 가지지 못한다는 걸 뜻합니다. 이때 의미는 어휘적 의미는 물론 문법적 의미를 포함합니다.

그런데 여기서 문제가 되는 것은 한국어의 ‘오솔길’, ‘아름답다’, ‘착하다’, 영어의 ‘cranberry’ 등에서 보이는 ‘오솔-‘, ‘아름-‘, ‘착-‘, ‘cran-‘과 같은 이른바 특이형태소(unique morpheme)라 불리는 존재들입니다. 이들은 분포가 극도로 제약되어 있기 때문에 원래 쓰이던 단어에서 분리하면 그 의미를 제대로 알기 어려운 것들입니다.

그러나 이들도 형태소임은 분명합니다. ‘오솔길’, ‘아름답다’, ‘착하다’, ‘cranberry’의 의미는 ‘오솔-‘, ‘아름-‘, ‘착-‘, ‘cran-‘을 빼면 확 달라지기 때문입니다. 다시 말해 ‘오솔-‘, ‘아름-‘ 같은 어구의 의미를 제대로 알기 어렵다고 해서 의미가 없는 것은 아니라는 얘기입니다. 또한 ‘오솔-‘을 ‘오’와 ‘솔’로 나누는 것처럼 이들을 더 쪼개면 분리하기 전 있었던 의미가 사라지기 때문에 이 자체로 ‘의미를 가지는 최소의 단위’, 즉 형태소가 됩니다.

이 때문에 최근의 국어학자들은 형태소를 ‘최소의 의미 단위’라는 굴레에서 벗어나 일정한 음운론적 특징을 가진 단위(소리가 비슷한 말들)까지 확대해 분석하는 경향이 있습니다. 의미를 잘 모르겠다고 해서 형태소 분석 대상에서 빠지는 말들이 없도록 하자는 취지입니다.

이보다 더 골치 아픈 개념이 바로 공형태(소) 개념입니다. 말 그대로 ‘의미가 없는 형태소’라는 뜻이죠. 최소의 의미 단위라는 정의 자체에 반하는 것이어서, 국어학자들 사이에서도 꽤나 논란이 되고 있습니다. 장윤희(1999)에 따르면 중세 한국어에서 ‘거맃-[濟]’과 동일한 의미의 ‘거리치-‘에서 나타나는 ‘-이-‘가 공형태라 볼 수 있습니다. 즉 ‘-이-‘가 없어도 뜻 변화에 영향이 없다면 그 의미가 없는(empty) 형태소라는 말이죠. 한국어에 공형태소가 있느냐, 있다면 그 개념을 채택할 수 있느냐에 이르기까지 수많은 논의가 이뤄지고 있습니다.

영형태(소)는 공형태(소)와 반대 개념입니다. 의미는 있으나 형태는 없는 형태(소)를 가리키는 말이죠. 영접사 등 체계적인 문법 기술(desciption)을 위해 도입된 개념으로, 이 역시 인정하는 쪽과 그렇지 않은 쪽 사이에 논란이 있습니다.

어기, 어근, 어간

컴퓨터에 한국어 단어를 가르쳐줄 때 어근이나 어간 위주로 알려주는 것이 여러모로 편리할 것입니다. 한국어는 조사나 어미 등이 붙어 수많은 의미 분화가 이뤄지는 교착어(첨가어)이기 때문입니다. 그럼 단어의 뿌리에 해당하는 어기, 어근, 어간에 대해 살펴보겠습니다.

어기(語基) : 어근과 어간을 아우르는 용어

어근(語根) : 복합어의 형성에 나타나는 실질형태소. 한편으로는 규칙어근과 불규칙어근으로, 다른 한편으로는 단순어근과 복합어근으로 나뉜다.

어간(語幹) : 활용어미가 직접 붙을 수 있는 부분. 문장 형성의 요소로서 단어형성의 요소인 ‘어근’과 층위를 달리한다.

규칙어근이란 어근의 품사가 분명하고 다른 말과 자유롭게 통합될 수 있는 말을 뜻합니다. ‘집’, ‘신’, ‘드높-‘에서의 ‘높-‘ 등이 여기에 해당합니다. 불규칙어근은 품사가 명백하지 않고 다른 말과의 통합에 제약이 있는 말입니다. ‘아름-‘과 ‘따뜻-‘은 단독형으로 말할 수 없고 조사와의 통합도 제약됩니다. (*아름, *아름이, *아름을;*따뜻, *따뜻이, *따뜻을) 단순어근복합어근은 어근의 종류가 단일한가 아닌가로 나뉘는 개념입니다. ‘신선(新鮮)하다’에서의 ‘신’과 ‘선’은 단순어근이며 ‘신선’은 복합어근입니다. 어간의 예를 들어보겠습니다. ‘높다, 높고, 높으니, 높습니다…‘에서의 ‘높-‘이 바로 어간입니다. 어미가 붙어 그 의미가 분화합니다.

말뭉치 분석 결과

이상 논의한 내용을 실제 말뭉치를 가지고 이야기해 보겠습니다. 왓챠의 영화 리뷰 657만2288건을 대상으로 분석을 했는데요. 우선 서울대 김현중 박사과정이 개발한 띄어쓰기 알고리즘로 띄어쓰기 교정한 뒤 띄어쓰기 기준으로 어절을 나누었습니다. 어절 첫 두 글자가 같으면 같은 단어군(群)으로 묶었는데요, 그 결과는 아래와 같습니다.

잊었다고, 잊었던, 잊었어도, 잊었다, 잊었나, 잊었지만

원동력, 원동력이란, 원동력만으로만, 원동력으로, 원동력이, 원동력을, 원동력은

쓸쓸, 쓸쓸한, 쓸쓸했다, 쓸쓸해졌다, 쓸쓸함, 쓸쓸하다, 쓸쓸함도, 쓸쓸하지만은, 쓸쓸히, 쓸쓸해진다, 쓸쓸해져온다, 쓸쓸하지, 쓸쓸해보이는, 쓸쓸하게, 쓸쓸하여, 쓸쓸함을, 쓸쓸함이란, 쓸쓸해서, 쓸쓸하고도, 쓸쓸했고, 쓸쓸함이, 쓸쓸하며, 쓸쓸해도, 쓸쓸했지만, 쓸쓸하고

단순 띄어쓰기, 어절 첫 두 글자로 분석한 내용이기에 절대 엄밀한 결과가 될 수는 없지만 어절 첫 두 글자를 단어의 뿌리로 보고 이를 같은 집단으로 보는 방법론이 어느 정도 활용 가능성이 있을 것이라는 희망을 갖게 됐습니다.

다만 이렇게 단어를 합쳐서 보는 데도 단어 수가 6~7만개 안팎으로 많아서(현재 연구용 PC로는 5만개 이상 단어는 메모리 한계로 분석 어려움) 단어 수를 효과적으로 줄이는 방법을 좀 더 연구해보려고 합니다. 단어를 합쳐서 보는 데 따른 정보량 손실을 최소화하면서도 계산효율성을 최대한 유지하는 방안을 고민 중입니다.

마치며

이상으로 형태소의 정의와 어기, 어근, 어간 등에 대해 살펴보았습니다. 형태소 분석은 자연언어처리 분야에서도 아주 중요하게 취급되는 분야인데요, 국어학에서 이룬 성취를 최대한 반영해 모델링을 하고 싶은 게 제 소망입니다. 현재는 어절 첫 두 글자만 잘라 보는 조악한 방법론을 쓰고 있는데요, 차차 개선해보려고 합니다. 질문이나 의견 있으시면 언제든 이메일이나 댓글로 알려주시기 바랍니다. 여기까지 읽어주셔서 진심으로 감사드립니다.

Comment  Read more

CNN으로 문장 분류하기

|

이번 포스팅에서는 Convolutional Neural Networks(CNN)로 문장을 분류하는 방법에 대해 살펴보겠습니다. 이번 포스팅의 아키텍처와 코드는 각각 Yoon Kim(2014)이곳을 참고했음을 먼저 밝힙니다. 그럼 시작하겠습니다.

자연어 처리에 특화된 네트워크 구조?

자연언어는 단어나 표현의 등장 순서가 중요한 sequential data입니다. 아래 예문을 볼까요?

{무거운 / *가벼운 / *큰} 침묵

침묵이 {*무겁다}.

Only I hit him in the eye yesterday. (No one else did)

I only hit him in the eye yesterday. (did not slap him)

‘무거운 침묵’은 고정된 형식으로 ‘정적이 흐르는 상태가 매우 심하다’는 뜻의 연어(collocation)으로 쓰입니다. 하지만 순서를 바꿔서 ‘침묵이 무겁다’는 표현은 문장 자체가 성립되지 않는 비문이라고 할 수 있죠. 영어의 경우 위 예시처럼 단어 등장 순서가 의미 차이를 만드는 경우가 많습니다. 이렇듯 단어나 표현의 순서는 그 의미 차이를 드러내거나 문장 성립 여부를 결정하는 등 중요한 정보를 함축하고 있습니다.

Recurrent Neural Networks(RNN)이나 이번 포스팅의 주제인 CNN은 등장 순서가 중요한 sequential data를 처리하는 데 강점을 지닌 아키텍처입니다. RNN의 경우 아래와 같이 입력값을 순차적으로 처리합니다. 입력값을 ‘단어’로 바꿔놓고 생각해보면 단어의 등장 순서를 보존하는 형태로 학습이 이뤄지게 됨을 알 수 있습니다.

그렇다면 CNN은 어떨까요? 본래 CNN은 이미지 처리를 하기 위해 만들어진 아키텍처입니다. 아래 움짤(출처)처럼 필터(filter)가 움직이면서 이미지의 지역적인 정보를 추출, 보존하는 형태로 학습이 이뤄지게 됩니다.

cnn

CNN을 텍스트 처리에 응용한 연구가 바로 Yoon Kim(2014)입니다. 이미지 처리를 위한 CNN의 필터(9칸짜리 노란색 박스)가 이미지의 지역적인 정보를 추출하는 역할을 한다면, 텍스트 CNN의 필터는 텍스트의 지역적인 정보, 즉 단어 등장순서/문맥 정보를 보존한다는 것이죠. 이를 도식화하면 제가 만든 아래 움짤과 같습니다.

위 움짤을 자세히 보시면 한 문장당 단어 수는 총 n개(아래 설명해 드릴 코드에서 변수명 : sequence_length)입니다. 이 단어들 각각은 p차원(변수명 : embedding_size)의 벡터이구요, 붉은색 박스는 필터를 의미합니다. 위 움짤의 경우 필터의 크기는 2로써 한번에 단어 2개씩을 보게 됩니다. 이 필터는 문장에 등장한 단어 순서대로 슬라이딩해가면서 문장의 지역적인 정보를 보존하게 됩니다. 필터의 크기가 1이라면 Unigram, 2라면 Bigram, 3이면 Trigram.. 이런 식으로 필터의 크기를 조절함으로써 다양한 N-gram 모델을 만들어낼 수 있습니다.

요컨대 RNN은 단어 입력값을 순서대로 처리함으로써, CNN은 문장의 지역 정보를 보존함으로써 단어/표현의 등장순서를 학습에 반영하는 아키텍처라 할 수 있겠습니다. RNN과 CNN이 자연언어처리 분야에서도 각광받고 있는 이유이기도 합니다. 이 글에서 설명하지 않았지만 Recursive Neural Networks도 단어/표현의 등장순서를 학습에 반영하는 구조인데요, Recursive Neural Networks의 자세한 내용을 보시려면 이곳을 참고하시면 좋을 것 같습니다. 아울러 CNN의 역전파(backpropagation) 등 학습방식에 관심이 있으시면 이곳을, RNN의 어플리케이션 사례를 보시려면 이곳을 방문하시길 권해드립니다.

CNN을 활용한 문장 분류 아키텍처

Yoon Kim(2014)의 아키텍처는 아래와 같습니다.

이 논문은 영화 리뷰 사이트에 게시된 댓글과 평점 정보를 이용해 각 리뷰가 긍정인지 부정인지 분류하는 모델을 만들고자 했습니다. n개의 단어로 이뤄진 리뷰 문장을 각 단어별로 k차원의 행벡터로 임베딩합니다. 단어를 벡터로 임베딩하기 위해서는 Word2Vec이나 GloVe처럼 distributed representation을 쓸 수도 있고요, 단어벡터의 초기값을 랜덤으로 준 뒤 이를 다른 파라메터들처럼 학습 과정에서 조금씩 업데이트해서 사용하는 방법도 있습니다. 한편 위 그림을 보면 CNN 필터의 크기는 2와 3인데요, 각각 Bigram, Trigram 모델을 뜻한다고 볼 수 있겠습니다. 이후 필터 개수만큼의 feature map을 만들고, Max-pooling 과정을 거쳐 클래스 개수(긍정 혹은 부정 : 2개)만큼의 스코어를 출력하는 네트워크 구조입니다.

영화리뷰를 숫자로 바꾸기

텐서플로우 코드로 구현된 이 아키텍처를 한국어 영화 리뷰에 적용해 보기로 했습니다. 우선 영화 리뷰 품질이 국내에서 가장 좋은 것으로 평가받는 왓챠에서 댓글과 평점 데이터 657만2288건을 모았습니다. 스크래핑한 데이터는 아래와 같습니다.

알프레도는 필름을 통해 인생과 사랑에 대해서 얘기하고자 했던것같다. 그 무수한 날들을 거치면서 항상 전진해야했던 토토에 비해 영화는 항상 향수를 자극시키며 그 자리에 계속 있었다. 많은 오고가는 흐릿한 생각들 끝에 내가 생각하는 영화는 정말 사랑과도 같다. 이해할수도 설명할수도 없는 그 느낌. 이 영화를 통해 영화를 더욱 사랑하게되었다, 5.0
고마워요 알프레도, 4.0
나머지 별 반개는 감독판을 보는 날에, 4.5
영화가 주는 감동이란..., 4.5
뭘 얘기하고 싶은건데?, 2.0

우선 CNN 문장분류 아키텍처의 입력값과 출력값을 만들어야 합니다. 우선 입력 데이터를 만들어 볼까요? 이 글에서는 Word2Vec 같은 distributed representation을 쓰지 않고, 단어벡터를 랜덤하게 초기화한 뒤 이를 학습과정에서 업데이트하면서 쓰는 방법을 채택했습니다. 이런 방식을 사용하기 위해서는 위 텍스트 문장을 숫자들(단어의 ID)의 나열로 변환해야 합니다.

그런데 단어들이 너무 많으면 메모리 문제로 학습 자체가 불가능해지므로 전체 단어 숫자를 좀 줄일 필요가 있습니다. 이 때문에 문장을 띄어쓰기 기준으로 어절로 나누고 어절 처음 두 글자만 잘랐습니다. 바꿔 말하면 아래 단어들을 모두 한 단어로 보는 방식입니다. 이 방식을 적용하면 말뭉치에 등장하는 전체 단어수(변수명 : vocab_size)를 아무런 처리를 하지 않았던 기존 대비 4분의 1로 줄일 수 있습니다.

잘어울리는 잘어울렸다 잘어울리네요 잘어울리는데 잘어울림 잘어울려요 잘어울리고 잘어울렸는데 잘어울렸음 잘어울린다

이후 단어별로 ID를 부여해 사전을 만들었습니다. 이 사전을 가지고 각 리뷰를 ID(숫자)들의 나열로 바꾸는 것입니다.

{‘가’: 0, ‘가가’: 1, ‘가감’: 2, (중략) ‘잘어’: 10004, (하략) }

이제 출력(정답) 데이터를 살펴볼까요? 왓챠 평점은 5점 만점인데 2.5점보다 높으면 긍정([1,0])을, 2.5점보다 낮으면 부정([0,1]) 범주를 부여했습니다. 지금까지 설명한 방식을 적용해 입력값과 출력값을 만든 결과의 예시는 아래와 같습니다.

고마워요 알프레도 (하략), 4.0                  [[502, 3101, ......], [1,0]]
영화가 주는 감동이란 (하략), 4.5            [[2022, 3043, 301, ...], [1,0]]
뭘 얘기하고 싶은건데? (하략), 2.0              [[1005, 2206, 1707, ...], [0,1]]

Lookup 테이블 구축

지금까지 우리가 만든 입력값은 아래 그림에서 빨간색 박스에 대응됩니다. 다시 말해 문장 길이(변수명 : sequence_length)에 해당하는 개수만큼의 단어 ID들의 나열이죠.

이미 설명드렸다시피 이번 글에서는 Word2Vec을 쓰지 않고 단어벡터를 만들기로 했었습니다. 대신 커다란 Lookup 테이블(아래 그림에서 파란색 박스)을 만들겁니다. 이 테이블의 크기는 전체 단어수(변수명 : vocab_size) ** * 사용자가 지정한 단어벡터 차원수(변수명 : embedding_size)**입니다. 파란색 박스의 행벡터들은 개별 단어 ID에 대응하는 벡터입니다. 초기값은 랜덤하게 설정하고요, 이후 학습 과정에서 문장 분류를 가장 잘하는 방식으로 조금씩 업데이트됩니다.

지금까지 리뷰를 단어 ID들의 나열로 바꿨고, ID에 해당하는 단어벡터도 만들었으니 이제 단어 ID들 각각을 벡터로 바꿔주기만 하면 되겠네요. 방법은 쉽습니다. 파란색 Lookup 테이블에서 단어 ID에 해당하는 행벡터를 참조해서 가져오기만 하면 됩니다. 그 결과는 바로 아래 그림의 보라색 박스입니다. 이 박스의 크기는 (문장길이 * 사용자가 지정한 단어벡터 차원수)가 됩니다.

cnn input

이상 논의한 내용을 텐서플로우 코드로 표현하면 다음과 같습니다. 최종 결과물인 보라색 박스에 대응하는 변수명은 embedded_chars인데요, 여기에 conv2d 함수가 요구하는 차원수로 만들어주기 위해 차원을 하나 추가해 줍니다.

W = tf.Variable(tf.random_uniform([vocab_size, embedding_size], -1.0, 1.0), name="W")
embedded_chars = tf.nn.embedding_lookup(W, input_x) # 차원수: [sequence length, embedding size]
embedded_chars_expanded = tf.expand_dims(embedded_chars, -1) # 차원수 : [sequence length * embedding size * 1]

Conv Layer 만들기

embedded_chars_expanded배치(batch) 단위로 CNN에 들어가게 됩니다. 배치를 반영한 입력값의 차원수는 [batch_size, sequence_length, embedding_size, channel_size]가 됩니다. 그런데 제가 실험한 CNN 구조에선 채널수를 1로 설정하여서 결과적으로 [batch_size, sequence_length, embedding_size, 1]이 됩니다.

자, 그러면 필터의 차원수를 살펴보겠습니다. 아래 그림을 보면 아시겠지만 필터의 너비는 embedding_size입니다. 높이는 filter_size인데요, 만약 2라면 Bigram, 3이라면 Trigram 모델이 될 겁니다. 채널수는 1로 고정했습니다.

filter

이를 종합해 텐서플로우가 요구하는 문법으로 필터 차원수를 정의하면 이렇습니다.

filter_shape = [filter_size, embedding_size, 1, num_filters]

자, 그러면 텐서플로우 구현의 핵심인 conv layer를 만드는 부분을 볼까요? 여기서 입력값과 필터의 가중치는 각각 embedded_chars_expanded와 W입니다. 필터가 입력값을 볼 때 그 보폭(strides)은 [1, 1, 1, 1]로 하고 엣지 패딩없이 문장을 슬라이딩하라는 뜻입니다.

conv = tf.nn.conv2d(self.embedded_chars_expanded,
                    W,
                    strides=[1, 1, 1, 1],
                    padding="VALID",
                    name="conv")
h = tf.nn.relu(tf.nn.bias_add(conv, b), name="relu")

그럼 [1, 1, 1, 1]의 의미는 무엇일까요? 텐서플로우 문서를 보면 strides값은 [batch_size, input_height, input_width, input_channels] 순서로 지정하라고 정의돼 있습니다. 보폭이 [1, 1, 1, 1]이라는 건 배치데이터 하나씩, 단어 하나씩 슬라이딩하면서 보라는 의미입니다.

그런데 여기서 input_width와 input_channels는 큰 의미가 없습니다. 왜냐하면 우리는 입력값의 너비와 필터의 너비를 embedding_size로 같게 설정했고, 채널수는 1로 모두 고정했기 때문입니다. 바꿔 말하면 보폭 [1, 1, 1, 1] 중 뒤의 두 개는 입력데이터와 필터가 같은 값을 지니기 때문에 슬라이딩할 수 있는 여유 공간이 없어 무의미하다는 뜻입니다.

이렇게 conv를 적용한 뒤의 텐서 차원수는 [batch_size, sequence_length - filter_size + 1, 1, num_filters]가 됩니다. 첫번째 요소 batch_size가 나오는 건 직관적으로 이해 가능하실 것 같고요, 나머지 차원수에 대해서는 텐서플로우 공식문서를 확인하시면 빠르게 이해하실 수 있으실 겁니다. 이후 ReLU를 써서 비선형성(non-linearity)을 확보합니다.

이제 Max-pooling하는 부분을 보겠습니다. 여기서 핵심은 ksize인데요, Max-pooling하는 영역의 크기가 되겠습니다. 예컨대 ksize가 [1, 2, 2, 1]이라면 배치데이터/채널별로 가로, 세로 두 칸씩 움직이면서 Max-pooling하라는 지시입니다. Max-pooling이 적용된 후의 텐서 차원수는 [batch_size, 1, 1, num_filters]가 됩니다.

pooled = tf.nn.max_pool(h,
                    ksize=[1, sequence_length - filter_size + 1, 1, 1],
                    strides=[1, 1, 1, 1],
                    padding='VALID',
                    name="pool")

이후엔 Max-pooling한 결과물을 합치고 Full-connected layer를 통과시켜 각 클래스에 해당하는 스코어를 낸 뒤 크로스엔트로피오차를 구하고 역전파(Backpropagation)를 수행해 필터의 가중치 등 파라메터들을 업데이트하는 일반적인 과정을 거칩니다. 여기서 특이한 점은 단어벡터의 모음인 Lookup 테이블도 학습과정에서 같이 업데이트한다는 사실입니다.

코드 공유

지금까지 이야기한 내용을 바탕으로 이곳을 참고해 커스터마이징한 코드는 아래와 같습니다.

위 코드에 쓰인 입력데이터 전처리용 코드는 다음과 같습니다.

제가 실험한 결과도 간략히 소개합니다. 제가 선택한 하이퍼파라메터 조합은 아래와 같습니다.

embedding_size = 128
filter_sizes = 3,4,5
num_filters = 128
dropout_keep_prob = 0.5
batch_size = 64
max_document_length = 100

우선 왓챠에서 모은 댓글과 평점 데이터 657만2288건 가운데 80%를 학습데이터, 20%를 검증데이터로 분리했습니다. 학습에 쓰지 않은 검증데이터에 대한 예측 결과(단순정확도)는 step수가 3만회를 넘어가면서 85% 내외로 수렴하는 모습을 보였습니다. 그 결과는 다음과 같습니다.

step 37600, loss 0.401777, acc 0.87
step 41500, loss 0.347608, acc 0.85
step 47000, loss 0.384077, acc 0.84
step 51000, loss 0.30739, acc 0.86
step 53800, loss 0.328676, acc 0.82

마치며

이상으로 CNN을 활용한 문장 분류 모델을 살펴보았습니다. CNN은 지역 정보를 보존한다는 점에서 순차적인 데이터 처리에 강점을 지닌 RNN과 더불어 자연언어처리 분야에서 주목을 받고 있습니다. 평점이라는 정답 데이터가 존재한다면 CNN을 가지고도 훌륭한 문장 분류기를 만들어낼 수 있다는 사실을 실험적으로 확인할 수 있었습니다. 제언이나 질문하실 것 있으시면 언제든지 댓글이나 이메일로 연락주시기 바랍니다. 여기까지 읽어주셔서 감사합니다.

Comment  Read more

은닉마코프모델(Hidden Markov Models)

|

이번 글에선 은닉마코프모델(Hidden Markov Models, HMMs)을 다루어 보도록 하겠습니다. 순차적인 데이터를 다루는 데 강점을 지녀 개체명 인식, 포스태깅 등 단어의 연쇄로 나타나는 언어구조 처리에 과거 많은 주목을 받았던 기법입니다. 이 글은 고려대 강필성 교수님 강의와 역시 같은 대학의 정순영 교수님 강의, 서울대 언어학과 신효필 교수님 저서, 위키피디아, Speech and Language Processing 3rd edition draft를 정리했음을 먼저 밝힙니다. 구체적인 계산 예시와 파이썬 코드 구현은 이곳을 참고하시면 좋을 것 같습니다. 그럼 시작하겠습니다.

마코프 체인

은닉마코프모델은 마코프 체인(Markov chain)을 전제로 한 모델입니다. 마코프 체인이란 마코프 성질(Markov Property)을 지닌 이산확률과정(discrete-time stochastic process)을 가리킵니다. 마코프 체인은 러시아 수학자 마코프가 1913년경에 러시아어 문헌에 나오는 글자들의 순서에 관한 모델을 구축하기 위해 제안된 개념입니다.

한 상태(state)의 확률은 단지 그 이전 상태에만 의존한다는 것이 마코프 체인의 핵심입니다. 즉 한 상태에서 다른 상태로의 전이(transition)는 그동안 상태 전이에 대한 긴 이력(history)을 필요로 하지 않고 바로 직전 상태에서의 전이로 추정할 수 있다는 이야기입니다. 마코프 체인은 아래와 같이 도식화됩니다.

[P({ q }_{ i } { q }{ 1 },…,{ q }{ i-1 })=P({ q }_{ i } { q }_{ i-1 })]

날씨를 마코프 체인으로 모델링한 예시는 다음 그림과 같습니다. 아래 마코프 체인에서 각 노드는 상태, 엣지는 전이를 가리킵니다. 엣지 위에 작게 써 있는 $a_{ij}​$는 $i​$번째 상태에서 $j​$번째 상태로 전이할 확률을 나타냅니다. 각 노드별로 전이확률의 합은 1입니다(예컨대 $a_{01}+a_{02}+a_{03}=1​$) 상태에는 일반적인 종류의 상태(예컨대 HOT, COLD, WARM) 이외에 시작(start)과 끝(end) 상태도 있습니다.

은닉 마코프 모델

은닉마코프모델은 각 상태가 마코프체인을 따르되 은닉(hidden)되어 있다고 가정합니다. 예컨대 당신이 100년 전 기후를 연구하는 학자인데, 주어진 정보는 당시 아이스크림 소비 기록뿐이라고 칩시다. 이 정보만으로 당시 날씨가 더웠는지, 추웠는지, 따뜻했는지를 알고 싶은 겁니다. 우리는 아이스크림 소비 기록의 연쇄를 관찰할 수 있지만, 해당 날짜의 날씨가 무엇인지는 직접적으로 관측하기 어렵습니다. 은닉마코프모델은 이처럼 관측치 뒤에 은닉되어 있는 상태(state)를 추정하고자 합니다. 날씨를 예시로 은닉마코프모델을 도식화한 그림은 다음과 같습니다.

위 그림에서 $B_1$은 날씨가 더울 때 아이스크림을 1개 소비할 확률이 0.2, 2개 내지 3개 먹을 확률은 각각 0.4라는 걸 나타냅니다. $B_1$은 날씨가 더울 때 조건부확률이므로 HOT이라는 은닉상태와 연관이 있습니다. $B$는 방출확률(emission probablity)이라고도 불립니다. 은닉된 상태로부터 관측치가 튀어나올 확률이라는 의미에서 이런 이름이 붙은 것 같습니다.

Likelihood

우선 우도(likelihood)부터 계산해 보겠습니다. 우도는 모델 $λ$가 주어졌을 때 관측치 $O$가 나타날 확률 $p(O$|$λ)$을 가리킵니다. 바꿔 말해 모델 $λ$이 관측치 하나를 뽑았는데 그 관측치가 $O$일 확률입니다. 이렇게 관측된 $O$가 아이스크림 [3개, 1개, 3개]라고 칩시다. 그럼 모델 $λ$가 위의 그림이라고 할 때 이 $O$가 뽑힐 확률은 얼마일까요? 이걸 계산해 보자는 겁니다. 아래 그림을 봅시다.

두번째 날짜를 중심으로 보겠습니다. 모델 $λ$를 보면 날씨가 더울 때(hot) 아이스크림을 1개 먹을 확률은 0.2입니다. 그런데 두번째 날이 전날에 이어 계속 더울 확률은 0.6이므로 이를 곱해주어야 둘째 날의 상태확률를 계산할 수 있습니다. 여기에서 마코프 체인을 따른다고 가정하므로 상태확률을 계산할 때는 직전 상태만을 고려합니다. 위 그림을 식으로 나타내면 다음과 같습니다.

[\begin{align} P(3\quad 1\quad 3,hot\quad hot\quad cold)=&P(hot|start)\times P(hot|hot)\times P(cold|hot)\ &\times P(3|hot)\times P(1|hot)\times P(3|cold)\=&0.8\times0.6\times0.3\&\times0.4\times0.2\times0.1 \end{align}]

각 날짜별로 날씨가 더울 수도 있고 추울 수도 있습니다. 따라서 $2^3$가지의 경우의 수가 존재합니다. 아래 표와 같습니다.

상태1 상태2 상태3
cold cold cold
cold cold hot
cold hot cold
hot cold cold
hot hot cold
cold hot hot
hot cold hot
hot hot hot

따라서 관측치 [3, 1, 3]에 대한 최종적인 우도는 다음과 같이 구합니다.

[\begin{align} P(3\quad 1\quad 3)=&P(3\quad 1\quad 3,cold\quad cold\quad cold)+\ &P(3\quad 1\quad 3,cold\quad cold\quad hot)+…\&P(3\quad 1\quad 3,hot\quad hot\quad hot) \end{align}]

Notation

여기에서 notation을 잠깐 정리하고 넘어가겠습니다. 다음과 같습니다. 저 또한 정리 용도로 남겨두는 것이니 헷갈릴 때만 다시 보시고 스킵하셔도 무방할 것 같습니다.

  • $Q={q_0, q_1, q_2, …, q_n, q_F}$ : 상태(state)의 집합(set). $q_0$은 시작상태, $q_F$는 종료상태, $n$은 상태의 개수를 나타낸다.
  • $A$ : 전이확률 행렬($n×n$). $a_{ij}$는 $i$번째 상태에서 $j$번째 상태로 전이할 확률($Σ_{j=1}^{n}a_{ij}=1$).
  • $B=b_i(o_t)$ : $i$번째 상태에서 $t$번째 관측치 $o_t$가 나타날 방출확률.
  • $O=[o_1,o_2,…,o_t,…,o_T]$ : 길이가 $T$인 관측치의 시퀀스.
  • $α_t(j)=P(o_1,o_2,…,o_t,q_t=j$|$λ)$ : 모델 $λ$가 주어졌을 때 $j$번째 상태와 $o_1,…,o_t$가 나타날 확률. 전방확률(Forward Probability)
  • $β_t(j)=P(o_{t+1},o_{t+2},…,o_T,q_t=j$|$λ)$ : 모델 $λ$가 주어졌을 때 $j$번째 상태와 $o_{t+1},…,o_T$가 나타날 확률. 후방확률(Backward Probability)

Compute Likelihood : Forward Algorithm

지금까지 은닉마코프모델의 우도(방출확률)를 계산하는 예시를 보여드렸습니다. 이제 이걸 전체 데이터에 대해 확대해 봅시다. 그런데 여기 문제가 하나 있습니다. 예시에서도 살펴봤듯 계산해야 할 경우의 수가 정말 많다는 겁니다. 가령 $N$개의 은닉상태가 있고 관측치 길이가 $T$라면 우도 계산시 고려해야 할 가짓수가 $N^T$개나 됩니다. 이러한 비효율성을 완화하기 위해 다이내믹 프로그래밍(dynamic programming) 기법을 씁니다. 다이내믹 프로그래밍은 중복되는 계산을 저장해 두었다가 푸는 것이 핵심 원리입니다. 다음 그림을 보겠습니다.

예컨대 아이스크림 3개($o_1$)와 1개($o_2$)가 연속으로 관측됐고 두 번째 시점($t=2$)의 날씨가 추웠을($q_1$) 확률은 $α_2(1)$입니다. 마찬가지로 아이스크림 3개($o_1$)가 관측됐고 첫 번째 시점($t=1$)의 날씨가 추웠을($q_1$) 확률은 $α_1(1)$입니다. 또한 아이스크림 3개($o_1$)가 관측됐고 첫 번째 시점($t=1$)의 날씨가 더웠을($q_2$) 확률은 $α_1(2)$입니다. 각각을 구하는 식은 다음과 같습니다.

[\begin{align} { \alpha }_{ 1 }(1)=&P(cold|start)\times P(3|cold)\ { \alpha }_{ 1 }(2)=&P(hot|start)\times P(3|hot)\ { \alpha }_{ 2 }(1)=&{ \alpha }_{ 1 }(1)\times P(cold|cold)\times P(1|cold)\ &+{ \alpha }_{ 1 }(2)\times P(cold|hot)\times P(1|cold) \end{align}]

Forward Algorithm의 핵심 아이디어는 이렇습니다. 중복되는 계산은 그 결과를 어딘가에 저장해 두었다가 필요할 때마다 불러서 쓰자는 겁니다. 위 그림과 수식을 보시다시피 $α_2(1)$를 구할 때 직전 단계의 계산 결과인 $α_1(1)$, $α_1(2)$을 활용하게 됩니다. 이해를 돕기 위한 예시여서 지금은 계산량 감소가 도드라져 보이지는 않지만 데이터가 조금만 커져도 그 효율성은 명백해집니다. $j$번째 상태에서 $o_1,…,o_t$가 나타날 전방확률 $α$는 다음과 같이 정의됩니다.

[{ \alpha }{ t }(j)=\sum _{ i=1 }^{ n }{ { \alpha }{ t-1 }(i)\times { a }{ ij } } \times { b }{ j }({ o }_{ t })]

$α_t(j)$는 $j$번째 상태와 $t$개의 관측치 시퀀스가 나타날 수 있는, 가능한 경우의 수가 모두 고려되어 합쳐진 확률입니다. 예컨대 위 그림에서 $α_3(2)$의 경우 세번째 시점에 HOT이 되려는데 가능한 경로는 [H, H, H], [H, C, H], [C, H, H], [C, C, H]입니다. 따라서 전방확률을 관측치 시퀀스 끝까지 계산하면 앞서 계산한 우도와 동치가 됩니다.

[\begin{align} P(O|\lambda )=&P\left( { o }_{ 1 },{ o }_{ 2 },…,{ o }_{ T }|\lambda \right) \ =&P\left( { o }_{ 1 },{ o }_{ 2 },…,{ o }_{ T },{ q }_{ t }={ q }_{ F }|\lambda \right) ={ \alpha }_{ T }\left( { q }_{ F } \right) \end{align}]

Decoding : Viterbi Algorithm

우리의 두 번째 관심은 모델 $λ$과 관측치 시퀀스 $O$가 주어졌을 때 가장 확률이 높은 은닉상태의 시퀀스 $Q$를 찾는 것입니다. 이를 디코딩(decoding)이라고 합니다. 포스태깅 문제로 예를 들면 단어의 연쇄를 가지고 품사 태그의 시퀀스를 찾는 것입니다. 우리가 은닉마코프모델을 만드려는 근본 목적에 닿아 있는 문제가 됩니다. 은닉마코프모델의 디코딩 과정엔 비터비 알고리즘(Viterbi Algorithm)이 주로 쓰입니다.

비터비 알고리즘의 계산 대상인 비터비 확률(Viterbi Probability) $v$는 다음과 같이 정의됩니다. $v_t(j)$는 $t$번째 시점의 $j$번째 은닉상태의 비터비 확률을 가리킵니다.

[{ v }{ t }(j)=\max _{ i } ^{n}{ \left[ { v }{ t-1 }(i)\times { a }{ ij }\times { b }{ j }({ o }_{ t }) \right] }]

자세히 보시면 Forward Algoritm에서 구하는 전방확률 $α$와 디코딩 과정에서 구하는 비터비 확률 $v$를 계산하는 과정이 거의 유사한 것을 확인할 수 있습니다. Forward Algorithm은 각 상태에서의 $α$를 구하기 위해 가능한 모든 경우의 수를 고려해 그 확률들을 더해줬다면(sum), 디코딩은 그 확률들 가운데 최대값(max)에 관심이 있습니다. 디코딩 과정을 설명한 예시 그림은 다음과 같습니다.

각 상태에서의 비터비 확률 $v$를 구하는 식은 다음과 같습니다. 전방확률을 계산하는 과정과 비교해서 보면 그 공통점(각 상태에서의 전이확률과 방출확률 간 누적 곱)과 차이점(sum vs max)을 분명하게 알 수 있습니다. 비터비 확률 역시 직전 단계의 계산 결과를 활용하는 다이내믹 프로그래밍 기법을 씁니다.

[\begin{align} v_{ 1 }(1)=&\max { \left[ P(cold|start)\times P(3|cold) \right] } \ =&P(cold|start)\times P(3|cold)\ { v }_{ 1 }(2)=&\max { \left[ P(hot|start)\times P(3|hot) \right] } \ =&P(hot|start)\times P(3|hot)\ { v }_{ 2 }(1)=&\max { \left[ { v }_{ 1 }(2)\times P(cold|hot)\times P(1|cold),\ { v }_{ 1 }(1)\times P(cold|cold)\times P(1|cold) \right] } \end{align}]

Forward Algorithm과 비터비 알고리즘 사이에 가장 큰 차이점은 비터비에 역추적(backtracking) 과정이 있다는 점입니다. 디코딩의 목적은 비터비 확률이 얼마인지보다 최적 상태열이 무엇인지에 관심이 있으므로 당연한 이치입니다. 위 그림에서 파란색 점선으로 된 역방향 화살표가 바로 역추적을 나타내고 있습니다.

예컨대 2번째 시점 2번째 상태 $q_2$(=HOT)의 backtrace $b_{t_2}(2)$는 $q_2$입니다. $q_2$를 거쳐서 온 우도값(0.32×0.12)이 $q_1$보다 크기 때문입니다. 2번째 시점의 1번째 상태 $q_1$(=COLD)의 backtrace $b_{t_2}(1)$는 $q_2$입니다. 최적상태열은 이렇게 구한 backtrace들이 리스트에 저장된 결과입니다. 예컨대 위 그림에서 아이스크림 [3개, 1개]가 관측됐을 때 가장 확률이 높은 은닉상태의 시퀀스는 [HOT, COLD]가 되는 것입니다.

$t$번째 시점 $j$번째 상태의 backtrace는 다음과 같이 정의됩니다.

[{ b }{ { t }{ t } }(j)=arg\max { i=1 }^n{ \left[ { v }{ t-1 }(i)\times { a }{ ij }\times { b }{ j }({ o }_{ t }) \right] }]

모델 학습을 위한 사전 지식

은닉마코프모델의 본격적인 학습에 앞서 학습 과정을 유도하는 데 필요한 용어와 개념 몇 가지 살펴보도록 하겠습니다.

전방확률과 후방확률

은닉마코프모델의 파라메터 학습을 위해서는 후방확률 $β$ 개념을 먼저 짚고 넘어가야 합니다. 전방확률 $α$와 반대 방향으로 계산한 것이 후방확률입니다. 그 식은 각각 다음과 같습니다.

[{ \alpha }{ t }(j)=\sum _{ i=1 }^{ n }{ { \alpha }{ t-1 }(i)\times { a }{ ij } } \times { b }{ j }({ o }{ t })\ { \beta }{ t }(i)=\sum { j=1 }^{ n }{ { a }{ ij } } \times { b }{ j }({ o }{ t+1 })\times { \beta }_{ t+1 }(j)]

우선 전방확률 $α$ 먼저 보겠습니다. $α_3(4)$는 다음과 같이 구합니다.

[{ \alpha }{ 3 }(4)=\sum _{ i=1 }^{ 4 }{ { \alpha }{ 2 }(i)\times { a }{ i4 } } \times { b }{ 4 }({ o }_{ 3 })]

이번엔 후방확률 $β$를 보겠습니다. 위와 같은 위치의 후방확률 $β_3(4)$는 다음과 같이 구합니다.

[{ \beta }{ 3 }(4)=\sum _{ j=1 }^{ 4 }{ { a }{ 4j } } \times { b }{ j }({ o }{ 4 })\times { \beta }_{ 4 }(j)]

따라서 $α_3(4)$와 $β_3(4)$를 곱하면 3번째 시점에 4번째 상태일 확률이라는 의미를 가지게 됩니다. 바꿔 말해 $α_3(4)×β_3(4)$는 3번째 시점에 4번째 상태를 지나는 모든 경로에 해당하는 확률의 합을 가리킨다는 겁니다. 이를 도식적으로 나타내면 다음과 같습니다.

[{ \alpha }{ t }\left( j \right) \times { \beta }{ t }\left( j \right) =P\left( { q }_{ t }=j,O \lambda \right)]

따라서 특정 시점 $t$의 전방확률과 후방확률을 곱한 값을 모든 상태에 대해 더해 주면 앞서 계산한 우도와 동치가 됩니다. 후방확률을 관측치 시퀀스 맨 끝부터 처음까지 계산하면 이 또한 앞서 계산한 우도와 같습니다.

[\begin{align} P(O|\lambda )=&P\left( { o }_{ 1 },{ o }_{ 2 },…,{ o }_{ T }|\lambda \right) \ =&P\left( { o }_{ 1 },{ o }_{ 2 },…,{ o }_{ T },{ q }_{ t }={ q }_{ 0 }|\lambda \right) =\beta _{ 0 }\left( { q }_{ 0 } \right) \ =&\sum _{ s=1 }^{ n }{ \alpha _{ t }\left( s \right) \times \beta _{ t }\left( s \right) } \end{align}]

베이즈 정리

베이즈 정리에 의해 다음과 같은 식이 성립합니다.

[\begin{align} P\left( X|Y,Z \right) =&\frac { P(X,Y,Z) }{ P(Y,Z) } \ =&\frac { P(X,Y)/P(Z) }{ P(Y,Z)/P(Z) } \ =&\frac { P(X,Y|Z) }{ P(Y|Z) } \end{align}]

방출확률 업데이트와 γ

방출확률 $b$를 업데이트하기 위해 $γ$ 개념을 살펴보도록 하겠습니다. $t$시점에 $j$번째 상태일 확률 $γ_t(j)$는 다음과 같이 정의됩니다. 이는 이미 정의된 식과 베이즈 정리에 의해 다음과 같이 다시 쓸 수 있습니다.

[\begin{align} { \gamma }_{ t }\left( j \right) =&P\left( { q }_{ t }=j|O,\lambda \right) \ =&\frac { P\left( { q }_{ t }=j,O|\lambda \right) }{ P\left( O|\lambda \right) } \ =&\frac { { \alpha }_{ t }\left( j \right) \times { \beta }_{ t }\left( j \right) }{ \sum _{ s=1 }^{ n }{ \alpha _{ t }\left( s \right) \times \beta _{ t }\left( s \right) } } \end{align}]

$j$번째 상태에서 관측치 $v_k$가 나타날 방출확률 $\hat{b}_j(v_k)$는 다음과 같이 정의됩니다.

[{ \hat { b } }{ j }\left( { v }{ k } \right) =\frac { \sum { t=1,s.t.{ o }{ t }={ v }{ k } }^{ T }{ { \gamma }{ t }\left( j \right) } }{ \sum { t=1 }^{ T }{ { \gamma }{ t }\left( j \right) } }]

위 식의 의미를 해석하면 이렇습니다. 방출확률 $\hat{b}_j(v_k)$의 분모는 $j$번째 상태가 나타날 확률입니다. 분자는 $j$번째 상태이면서 그 때 관측치($o_t$)가 $v_k$일 확률입니다($v_k$가 나타나지 않는 $γ$는 0으로 무시). 분모와 분자 모두에 시그마가 적용된 이유는 방출확률은 시점 $t$와는 무관한 값이기 때문입니다. 어떤 시점이든 $j$번째 상태가 나타날 확률 $γ$가 존재하므로 $T$개 관측치 시퀀스 전체에 걸쳐 모든 시점에 대해 $γ$를 더해주는 것입니다.

전이확률 업데이트와 ξ

전이확률 $a$를 업데이트하기 위해 $ξ$ 개념을 살펴보도록 하겠습니다. $t$시점에 $i$번째 상태이고 $t+1$시점에 $j$번째 상태일 확률 $ξ$는 다음과 같이 정의됩니다. 이 또한 베이즈 정리에 의해 다음과 같이 다시 쓸 수 있습니다.

[\begin{align} { \xi }_{ t }\left( i,j \right) =&P\left( { q }_{ t }=i,{ q }_{ t+1 }=j|O,\lambda \right) \ =&\frac { P\left( { q }_{ t }=i,{ q }_{ t+1 }=j,O|\lambda \right) }{ P\left( O|\lambda \right) } \end{align}]

위 식에서 분자 부분을 이해하기 위해 다음 그림을 보겠습니다. $t$시점에 $i$번째 상태이고 $t+1$시점에 $j$번째 상태인 경우를 도식화한 것입니다.

지금까지의 정의로 볼 때 $α_t(i)$는 $i$번째 상태 좌측의 모든 path에 해당하는 확률의 합입니다. $β_{t+1}(j)$는 $j$번째 상태 우측의 모든 path에 해당하는 확률의 합입니다. 그런데 이 두 가지 곱만으로는 $i$번째 상태와 $j$번째 상태를 이어주는 path가 존재하지 않습니다. 이를 연결해 주어야 합니다. $i$번째 상태에서 $j$번째 상태로 전이할 확률 $a_{ij}$, $j$번째 상태에서 관측치 $o_{t+1}$를 관측할 방출확률 $b_j(o_{t+1})$까지 곱해주어야 한다는 이야기입니다.

따라서 $t$시점에 $i$번째 상태이고 $t+1$시점에 $j$번째 상태일 확률 $ξ$은 다음과 같이 다시 쓸 수 있습니다.

[\begin{align} { \xi }_{ t }\left( i,j \right) =&\frac { P\left( { q }_{ t }=i,{ q }_{ t+1 }=j,O|\lambda \right) }{ P\left( O|\lambda \right) } \ =&\frac { { \alpha }_{ t }\times { a }_{ ij }\times { b }_{ j }\left( { o }_{ t+1 } \right) \times { \beta }_{ t+1 }\left( j \right) }{ \sum _{ s=1 }^{ n }{ \alpha _{ t }\left( s \right) \times \beta _{ t }\left( s \right) } } \end{align}]

$i$번째 상태에서 $j$번째 상태로 전이할 확률 $\hat{a}_{ij}$는 다음과 같이 정의됩니다.

[\hat { a } { ij }=\frac { \sum _{ t=1 }^{ T-1 }{ { \xi }{ t }\left( i,j \right) } }{ \sum { t=1 }^{ T-1 }{ \sum _{ k=1 }^{ N }{ { \xi }{ t }\left( i,k \right) } } }]

위 식의 의미는 이렇습니다. 분모는 $i$번째 상태에서 전이할 수 있는 모든 path들의 확률들을 더한 값입니다. 분자는 $i$번째 상태에서 $j$번째 상태로 전이할 확률을 가리킵니다. 분모와 분자 모두에 $Σ_{t=1}^{T-1}$가 적용된 이유는 방출확률은 시점 $t$와는 무관한 값이기 때문입니다. 어떤 시점이든 $i$번째 상태에서 다른 상태로 전이할 확률 $ξ_t$가 존재하므로 관측치 시퀀스 전체에 걸쳐 모든 시점에 대해 $ξ_t$를 더해주는 것입니다. 다만 시퀀스 마지막 $T$번째 시점에선 종료상태로 전이될 확률이 1이 되므로 $t$에 대해 1에서 $T-1$까지 더해줍니다.

위 식을 그림으로 도식화하면 아래와 같습니다. 위 식 분모는 굵은 적색 점선 화살표, 분자는 검은색 화살표입니다.

Training : EM Algorithm

은닉마코프모델의 파라메터는 전이확률 $A$와 방출확률 $B$입니다. 그런데 이 두 파라메터를 동시에 추정하기는 어렵습니다. 지금까지 설명한 날씨 예제를 기준으로 하면, 우리가 관측가능한 것은 아이스크림의 개수뿐이고 궁극적으로 알고 싶은 날씨는 숨겨져 있습니다. 따라서 HOT에서 COLD로 전이할 확률은 물론 날씨가 더울 때 아이스크림을 1개 먹을 방출확률 따위를 모두 한번에 알 수 없습니다. 이럴 때 주로 사용되는 것이 EM 알고리즘입니다. 은닉마코프모델에서는 이를 ‘바움-웰치 알고리즘’ 또는 ‘Forward, Backward Algorithm’이라고도 부릅니다.

E-step

모델 파라메터 $λ$, 즉 전이확률 $A$, 방출확률 $B$를 고정시킨 상태에서 관측치 $O$를 바탕으로 전방확률 $α$와 후방확률 $β$를 업데이트합니다. 이 $α$와 $β$를 바탕으로 $γ$와 $ξ$를 각각 계산합니다.

M-step

E-step에서 구한 $γ$와 $ζ$를 바탕으로 모델 파라메터 $A$, $B$를 업데이트합니다.

pesudo code

EM 알고리즘의 의사코드는 다음과 같습니다. 단 아래 코드에서 $γ,ξ$의 분모는 표기가 다를 뿐 해당 관측치의 우도를 나타냅니다.

Comment  Read more

Random Forest, Rotation Forest

|

이번 포스팅에서는 트리 기반의 대표적인 앙상블 기법인 랜덤포레스트(Random Forest)로테이션포레스트(Rotation Forest)에 대해 알아보고자 합니다. 관련 R패키지를 소개하고, 저희 연구실 김해동 석사과정이 만든 코드와 성능 실험도 함께 소개해보고자 합니다. 그럼 시작해보겠습니다.

Tree-based Ensemble 소개

의사결정나무(Decision Tree)는 한번에 하나씩의 설명변수를 사용하여 예측 가능한 규칙들의 집합을 생성하는 알고리즘입니다. 한번 분기 때마다 변수 영역을 두개로 구분해 구분 뒤 각 영역의 순도(homogeneity)가 증가/불확실성(엔트로피)가 감소하도록 하는 방향으로 학습을 진행합니다. 의사결정나무는 입력 변수 영역을 두 개로 구분하는 재귀적 분기(recursive partitioning)와 너무 자세하게 구분된 영역을 통합하는 가지치기(pruning) 두 가지 과정으로 나뉩니다. 자세한 내용은 이곳을 참고하세요.

랜덤포레스트는 같은 데이터에 의사결정나무 여러 개를 동시에 적용해서 학습성능을 높이는 앙상블 기법입니다. 나무(tree)가 여럿 있다고 하는 의미에서 forest라는 이름이 붙었습니다. 참 직관적인 작명이죠. 위 그림을 보시면 조금 더 쉽게 이해하실 수 있으실 겁니다. 어쨌든 랜덤포레스트는 동일한 데이터로부터 복원추출을 통해 30개 이상의 데이터 셋을 만들어 각각에 의사결정나무를 적용한 뒤 학습 결과를 취합하는 방식으로 작동합니다. 단 여기서 각각의 나무들은 전체 변수 중 일부만 학습을 하게 됩니다. 개별 트리들이 데이터를 바라보는 관점을 다르게 해 다양성을 높이려는 시도입니다.

로테이션포레스트는 학습데이터에 주성분분석(PCA)를 적용해 데이터 축을 회전(rotation)한 후 학습한다는 점을 제외하고는 랜덤포레스트와 같습니다. PCA로 회전한 축(아래 그림의 경우 핑크색 선을 지나는 축)은 학습데이터의 분산을 최대한 보존하면서도 학습성능 향상에도 유의미할 것이라는 전제에서 고안된 방법론으로 풀이됩니다. 아래 움짤을 보시면 PCA의 효과를 눈으로도 확인하실 수 있습니다.

PCA

** 출처 : [Making sense of principal component analysis, eigenvectors & eigenvalues]

기법 구현

R에서 의사결정나무를 수행하는 패키지는 rpart입니다. 아래와 같이 동작합니다. 의사결정나무는 범주를 예측하는 분류(classification)는 물론 연속형 숫자를 맞추는 회귀(regression) 모두 적용 가능한 모델인데요. 분류를 하고 싶다면 아래처럼 type 변수로 ‘class’를, 회귀를 하고 싶으면 ‘anova’를 쓰면 됩니다. 물론 이렇게 명시적으로 적어주지 않아도 예측해야 하는 변수 Y의 자료형이 범주에 해당하는 factor일 경우 분류를, 연속형 숫자에 해당하는 numeric일 경우 회귀를 자동 수행합니다.

# Decision Tree
library(rpart)
Fit1 <- rpart(fomula, data, type=anova) # regression
Fit2 <- rpart(fomula, data, type=class) # classification

랜덤포레스트와 관련된 패키지명은 말 그대로 randomForest입니다. rpart와 마찬가지로 Y의 자료형이 factor이면 분류, numeric이면 회귀를 자동 수행합니다. mrty는 앞서 설명드렸던 것처럼 변수의 부분집합을 만들 때 샘플링하는 변수 개수이며 ntree는 앙상블을 할 의사결정나무 개수가 됩니다.

# Random Forest
library(randomForest)
Fit <- randomForest(fomula, data, ntree, mtry,)

로테이션포레스트 관련 패키지명은 rotationForest입니다. 다른 패키지와 달리 명시적으로 입력변수(x)와 예측변수(y)를 나눠서 넣어주어야 하며 2범주 분류만 수행할 수 있습니다. PCA를 수행해야 하기 때문에 x의 자료형은 반드시 숫자여야 하며 y 역시 0 또는 1로 바꿔주어야 합니다. KL은 각각 randomForest 패키지의 mtry, ntree와 같습니다.

# Rotation Forest
library(rotationForest)
Fit <- rotationForest(x, y, K, L)

마지막으로 설명드릴 코드는 저희 연구실에서 만든겁니다. rotationForest 기존 패키지를 다소 보완했습니다. 범주형 변수는 자동으로 숫자로 바꾸어 더미변수화를 해주고 다범주 분류는 물론 회귀도 역시 가능하도록 개선했습니다. k는 학습데이터를 몇 개로 나눌지 선택하는 변수이며, bootstrapRate는 학습데이터 부분집합을 만들 때 레코드를 얼마나 선택할지 지정하는 변수입니다. numTrees는 트리 개수, type은 분류/회귀를 선택하는 변수입니다. 이 코드는 이 포스트 맨 마지막에 첨부를 했는데요. 패키지 형태가 아니기 때문에 실제 실행을 위해선 R에서 사용자 정의 함수를 호출하듯 사용하시면 됩니다.

# advanced Rotation Forest
Fit <- rotationForest(data, k, numTrees, bootstrapRate, type)

성능 비교 실험

2범주 분류 문제

UCI datasets 등 수집 가능한 데이터셋을 수집해 30회 반복실험했습니다. 지금부터 보여드릴 표의 성능 지표는 단순정확도(accuracy)입니다. 표에서도 알 수 있듯 단일 의사결정나무보다 앙상블 기법인 포레스트가 나은 성능을 보여주고 있습니다. 앙상블 기법 중엔 랜덤포레스트가 로테이션포레스트보다 나은 성능을 나타냈습니다. (2범주 분류 문제에 사용한 로테이션포레스트 함수는 R패키지인 ratationForest입니다)

다범주 분류 문제

다범주 분류 문제에 있어서도 랜덤포레스트가 나은 성능을 보여주고 있습니다. 다범주 분류 말고 회귀 문제에 있어서도 랜덤포레스트가 로테이션포레스트보다 성능이 좋은 것으로 파악됐습니다. (여기서 사용된 로테이션포레스트 함수는 저희 연구실에서 작성한 코드입니다)

randomForest 패키지의 파라메터 실험

앙상블 기법 가운데 그 성능이 강건하면서도 좋은 것으로 알려진 랜덤포레스트의 강점을 실험을 통해 확인할 수 있었습니다. 그럼 랜덤포레스트의 최적 하이퍼파라메터는 무엇일까요?

ntree (트리 개수)

분류 문제에서 트리 개수는 250개 이상부터 단순정확도 향상에 정체 현상이 나타났습니다(57개 데이터 30회 반복실험 평균 기준). 회귀 문제의 경우에도 트리 개수가 250개 이상부터 RMSE가 크게 줄지 않는 것으로 파악됐습니다. 패키지의 기본값인 500을 그대로 써도 무방할 것 같습니다.

mtry (변수 개수)

개별 트리 학습용 부분집합을 만들 때 선택한 변수의 비율은 전체의 30%를 기점으로 클 수록 되레 분류 성능(단순정확도)이 떨어지는 것으로 나타났습니다(57개 데이터 30회 반복실험 평균 기준). 회귀 문제의 경우에도 변수 비율이 30%를 전후로 RMSE가 줄지 않는 경향을 보였습니다. mtry도 역시 패키지 기본값을 써도 크게 관계 없을 것 같습니다.

마치며

이상으로 트리 기반 머신러닝 기법들에 대해 살펴보았습니다. 실험 결과에서 보셨던 것처럼 단일 의사결정나무보다 여러 개 트리를 동시에 적용한 앙상블 기법들이 강세를 보였습니다. 랜덤포레스트는 로테이션포레스트와 비교해 그 성능이 강건하고 우수한 것으로 파악됐습니다. 로테이션포레스트의 R패키지인 rotationForest를 보완한 코드는 아래에 게시했습니다. 여기까지 읽어주셔서 감사합니다.

Comment  Read more

한국어의 종결어미

|

이번 포스팅에선 한국어의 조사를 설명한 지난 글에 이어 어미(語眉)에 대해 설명해 보려고 합니다. 그중에서도 문장 끝에 붙는 종결어미를 중심으로 이야기해보고자 합니다. 한국어 문장은 크게 평서문, 의문문, 명령문, 청유문으로 나뉘는데, 종결어미는 문장형을 나누고 서법(敍法, mood)을 나타내며 경어법을 관장하는 역할을 합니다. 이번 글 역시 한국어 문법론 대가 이익섭 서울대 명예교수께서 쓰신 ‘한국어문법’을 참고로 했습니다.

이 글의 목차는 다음과 같습니다.

평서문 어미

-다/-는다-/-ㄴ다

진술

‘-다/는다’는 평서문 어미 중 가장 중립적인 어미입니다. 이 어미가 글에서 대표적인 어미로 쓰이는 이유이기도 합니다. 이 어미가 하는 일은 평서문을 진술(statement)해주는 역할입니다.

날씨가 좋다.

한국 사람들은 설날에 떡국을 먹는다.

놀라움

무엇을 새로 발견하고 스스로 감탄하거나 남에게 크게 외칠 때, 자신의 어떤 특별한 상황을 남에게 과시할 때 씁니다.

와, 사람 한번 많다!

우리 내일 소풍 간다!

의문

월드컵도 다 끝났으니 내일부터는 무슨 재미로 산다?

그럴 것 없이 내가 간다?

-구나/-는구나

새로운 지각

어떤 사실을 좀더 여실히 진술하거나 아니면 새로이 지각하였음을 드러내 주는 데 쓰입니다. 대개 감탄 내지 놀라움의 뜻이 동반됩니다.

오늘 날씨 정말 좋구나.

너도 커피를 마시는구나.

짐작

‘어디’, ‘무슨’과 같은 의문사와 함께 쓰이면 새로 지각한 사실을 ‘그런 줄 알겠다’ 또는 ‘그렇게 짐작된다’는 투로 나타냅니다.

너 어디 아프구나.

-군/는군

새로운 지각 - 혼잣말

새로 알게 된 일을 혼잣말로 할 때 쓰입니다. 놀라움이나 감탄의 뜻도 아울러 나타냅니다.

그놈 참 신통하군.

올 한 해도 다 가는군.

혼잣말 아닌 ‘군’

새로 지각한 사실이라는 의미를 지니면서도 혼잣말이 아닌 말로 쓰일 때도 있습니다.

누군가 했더니 바로 자네였군.

그놈 참 신통하군요.

-네

새로 지각한 사실을 놀라움이나 감탄을 섞어 혼잣말로 할 때 쓰입니다.

벌써 개구리가 나왔네.

어머나, 영수가 1등을 했네.

‘-네’와 ‘-군’

무엇을 새로 지각했다는 느낌, 놀라움이나 감탄도 ‘-네’ 쪽이 더 강합니다.

벌써 개구리가 나왔네.

벌써 개구리가 나왔군.

의문문의 ‘-네’

‘-겠-‘을 동반하고 그러리라고 추측되는 일에 대해 그것을 확인하기 위해 묻는 용법으로 쓰입니다. 이때에도 새로 지각했다는 의미는 유지됩니다.

그러면 이번에도 네가 1등이겠네?

-으마/-마

청자에게 어떤 일을 그렇게 하겠다고 약속을 하는 의미를 나타내 줍니다. 낯선 사람 사이에서는 쓰기 어렵고 대개 손아래 사람에게 씁니다.

내일 다시 오마.

-을걸/-ㄹ걸

추측이되 그러리라는 믿음이 있는 추측에 쓰입니다.

벌써 다들 모였을걸.

-을게/ㄹ게, -을래/-ㄹ래

-을게

화자가 자기가 할 일에 대한 의지를 나타내 줍니다. 그 의지의 표명이 일종의 약속인 경우가 많습니다.

내일 아침 일찍 올게.

-을래

화자가 어떤 일이 하고 싶다는 의사를 나타내 줍니다. 의지의 표현이기보다 희망을 나타낸다고 볼 수 있습니다. 의문문에 쓰일 때는 상대방의 의사를 묻거나 야단치는 용법으로 쓰입니다.

엄마, 나도 갈래.

나 좀 도와줄래?

정말 말 안들을래?

-을라/-ㄹ라, -는단다/-ㄴ단다/-단다/-란다

-을라

어떤 원하지 않는 사태가 벌어질 것을 염려하는 의미를 나타냅니다. 특히 손아래 상대방의 어떤 행동을 미리 경계하는 뜻으로 많이 쓰입니다. 대개 ‘-겠다’로 쉽게 바꿀 수 있으며 그렇게 해도 의미가 별로 달라지지 않습니다.

체할라. 천천히 먹어라.

-는단다

‘-다’가 풍기는 도식적인 분위기를 완화하여 가까운 손아래 사람에게 사근사근 얘기해주는 듯한 분위기를 만들어줍니다.

그때 할아버지들은 맨발로 학교를 다녔단다.

의문문 어미

-으냐/-냐/-느냐, -으니/-니

‘-냐’는 ‘-니’와 함께 의문문의 대표 어미입니다. 일반 대화에선 ‘-니’가 많이 쓰이고 글에까지 폭넓게 쓰이는 것은 ‘-냐’입니다. 전자는 청자를 가까이 두고 말하는 느낌인 반면 ‘-냐’는 어른이 점잖게 말한다는 분위기를 풍기기도 합니다. 둘은 자유롭게 넘나들면서 사용할 수 있지만 일부 제약이 있습니다.

너 몇 살이냐(/살이니)?

얼마나 오래 사느냐(*사니)가 중요한 것이 아니라 어떻게 사느냐(*사니)가 중요하다.

-을까/-ㄹ까

궁금한 것을 드러내는 데 쓰이되 혼잣말에 많이 사용됩니다. 상대방의 동의 여부를 물을 때에도 쓰입니다.

나는 왜 밤낮 이 모양일까?

(커피에)설탕 넣을까요?

-련

대개 ‘-겠니?’로 바꿔쓸 수 있는 자리에 쓰입니다. 청자에게 어떤 일을 해줄 의사가 있는지를 물으면서도 그러기를 권유하는 뜻도 있습니다.

거기 창문 좀 열어 주련?

-으랴/-랴, -을쏘냐/-ㄹ쏘냐

-으랴

화자가 청자를 위해 어떤 일을 하고자 하면서 청자에게 동의를 묻는 의미로 쓰입니다. 추측되는 상황을 의문문 형식으로 바꾸어 감정적으로 표현하는 데에 쓰이기도 합니다. 속담, 격언 등에서 질문이 아니라 수사적인 효과를 위해 사용되기도 합니다.

뭐 맛있는 것 사 주랴?

이제 와서 발버둥친들 무엇 하랴?

공든 탑이 무너지랴?

-을쏘냐

글에만 쓰이되 그것도 시적인 표현에 한정돼 반어적 용법으로 쓰입니다.

겉이 검다고 속조차 검을쏘냐?

-대, -담

-대

어떤 사태가 의외라는 느낌을 물을으로 나타내는 데 쓰입니다. 만족감이나 불만이 섞여 있습니다.

나 오늘 기분이 왜 이렇게 좋대?

얘들이 왜 아직도 못 온대요?

-담

어떤 사태가 마음에 들지 않아 불평조로 하는 말에 쓰입니다. 의문문에 쓰이나 자문하는 정도에 머무는 아주 가벼운 의문문입니다.

이렇게 낡은 걸 어디다 쓴담?

뭐가 우스워 그리 웃는담?

명령문 어미

-아라/-어라/-여라

명령문의 간판 어미라 할 만합니다. 가장 전형적인 명령문에 쓰입니다. 부탁이나 허락, 감탄을 나타내기도 합니다.

여기 좀 앉아라. (명령)

감기 조심하여라. (부탁)

(내일 또 와도 돼요?) 그래, 언제든지 오너라. (허락)

아이 추워라. (감탄)

-으려무나/려무나, -으렴/-렴

부드러운 명령, 즉 허락에 가까운 상황에 잘 쓰입니다.

그렇게 갖고 싶으면 가지려무나.

언니만 탓하지 말고 네가 좀 참으려무나.

-소서

기원을 나타내며 일상어로는 잘 쓰이지 않습니다.

부디 오래오래 행복을 누리소서.

-아/-어, -지

반말체 어미인 ‘-아’와 ‘-지’는 어느 한 문장형에 한정되어 쓰이지 않고 평서문, 의문문, 명령문, 청유문 네 문장형에 두루 쓰입니다. 어느 문장형에 쓰이나 공통된 의미를 유지하기는 하나 다른 한편으로는 어느 문장형에 쓰이느냐에 따라 의미의 차이가 생기기도 합니다.

-아/-어

반말체 어미 중 가장 중립적인 의미이면서 가장 널리 쓰이는 어미입니다. 청자를 가까이 두고 사용합니다. 놀라움 내지 긴박감을 나타내는 특별한 용법으로 쓰일 때도 있습니다.

나도 가겠어.

어디 아파?

아이구 깜짝이야.

-지

확인질문

말하려는 상황에 대해 화자나 청자가 이미 ‘알고 있다’, ‘짐작되는 바가 있다’, ‘그러리라 믿는다’와 같은 전제를 깔고 씁니다. 특히 의문문에서는 순수한 질문이라기보다는 동의해 줄 것을 기대하는 확인 질문의 성격을 띕니다.

냉장고에 있던 아이스크림 네가 먹었지?

당연한 이야기

단풍이야 설악산이 제일이지.

근거있는 짐작

편지가 내일에는 오겠지.(/오겠지?)

부드러움

부드러움을 얹어 권유하는 뜻으로도 쓰입니다. 물론 여기에도 ‘그렇게 해주리라는 걸 믿는다’는 전제가 깔림으로써 이러한 효과가 나타날 겁니다.

우리랑 같이 가지.

꼼짝 말고 여기 있어! // ??꼼짝 말고 여기 있지!

화자의 의지

다 싫다면 내가 가지.

아쉬움

나도 좀 부르지.

후종결어미

종결어미 뒤에 덧붙어 쓰이는 어미입니다.

-고

다른 종결어미에 덧붙어 그 종결어미로 끝난 문장에 제시된 내용을 반문하는 일에 쓰입니다. 좀 의외라는, 또는 불만을 가지고 따지는 듯한 반응을 보이면서 묻는 점이 특징입니다. 혹은 의문문이 아닌 문장에서 종결어미로 끝난 문장에 제시된 내용을 강조하고 다른 말을 못하도록 다그치는 듯한 기능을 합니다.

비가 온다고?

네가 반장이라고?

나도 한다면 하는 사람이라고.

-니까

‘-고’의 의미와 매우 흡사하며 두 어미는 자유롭게 넘나들 수 있습니다.

왜 벌써 돌아왔냐니까

-며, -면서

문장 내용을 청자에게 확인하기 위해 묻는 의미로 쓰입니다. ‘내가 그 사실을 아는데 사실이냐?’와 같은 의미입니다. 여러 문장형의 종결어미와 어울리면서도 의문문 어미와는 잘 어울리지 않습니다.

어제 진호와 싸웠다며(/싸웠다면서)?

*누가 진호와 싸웠냐며?

인용어미

-단다, -느냔다, -란다, -잔다

타인에게서 얻은 정보를 전달하는 데 쓰인다.

날씨가 주말부터 풀린단다.(/풀린답니다./풀린다니?/풀린답니까?)

-대

‘-고 한다’의 반말체 어미입니다. 자기가 그런 뜻으로 한 말이 아니었다는 뜻으로도 쓰입니다.

동대문 시장에 불이 났대.

누가 굶겠대?

1년치 조간신문의 종결어미 사용 빈도

이상으로 한국어의 종결어미에 관해 알아보았습니다. 조사와 마찬가지로 조선일보, 한겨레, 매일경제 등 10개 주요 조간신문의 2016년 1년치 기사 말뭉치에서 사용빈도를 따져봤습니다. 어미를 분석하려면 Konlpy 같은 기존 형태소 분석기로는 원천적으로 분석이 불가(동사 형용사 분석시 어미를 제거하고 기본형만 반환)합니다. 그래서 마침표(.)를 기준으로 문장을 나누고, 문장의 맨 마지막 단어의 맨 끝 음절부터 한글자씩 일일이 세었습니다. 분석 결과는 아래와 같습니다.

어미명 빈도
했다 870632
이다 628043
었다 232378
니다 193316
한다 165015
141173
았다 127454
혔다 107056
됐다 105218
졌다 92395
된다 91714
였다 87645
하다 81029
65256
는다 49518
왔다 44777
렸다 44658
났다 40734
인다 38357
온다 34805
진다 27153
26145
냈다 21457

보시다시피 뉴스 말뭉치의 종결어미는 ‘-다/-는다-/-ㄴ다’가 압도적으로 많습니다. 분석 대상인 2016년 1년치 기사 25만7973건 620만9892개 문장 가운데 ‘-다’로 끝나는 문장은 473만8770개로 전체의 76.3%나 됩니다. 말뭉치에 끼어있는 광고문구 등 일부 노이즈를 제거하면 그 비율은 더 높을 것으로 추정됩니다.

‘-다’와 ‘했다’는 엄밀히 말해 분석 수준이 다르지만 ‘-다’ 계열의 쓰임 양상을 구체적으로 드러내주기 위해 위와 같이 표를 작성했습니다. ‘-요’, ‘-까’와 같은 어미를 제외하면 모두 ‘-다’ 계열의 평서문 종결어미인 점을 확인할 수 있습니다.

조사 관련 글과 마찬가지로 형태소를 하나하나 정확히 분석한 결과는 아니니 경향성만 확인하는 용도로 보시면 좋을 것 같습니다. 의견이나 질문 있으시면 언제든 댓글이나 메일로 남겨주시기 바랍니다. 여기까지 읽어주셔서 감사드립니다.

Comment  Read more