Thursday, July 11, 2013

맵리듀스(Map-Reduce) 프로그래밍 - 공기정보(Co-occurrence) 구하기

맵 리듀스 프로그랭 세계의 Hello World! 라고 할 수 있는 wordcount 예제는 맵리듀스 프로그래밍의 원리와 구성요소를 이해하기 위해서는 딱이라고 할 수 있지만, 맵리듀스 프로그래밍의 필요성, 유용함을 느끼기에는 조금 부족하다.
그래서, 이 포스팅에서는 wordcount 예제를 마스터 한 사람이 읽을 수 있는, 한걸음 진보된, 실전에 가까운, 공기정보(co-occurrence) 구하기를 소개하기로 한다.

여기 소개되는 프로그램은 java가 아니라, 하둡 스트리밍 용 파이썬 스크립트이다.
실제 쓸 수 있게 끔 스크립트를 작성하려고 신경을 썼고, 필자의 경우는 AWS(아마존 웹 서비스) EMR(Elastic Map Reduce)을 이용하여 실행 테스트를 하였다.

1. 문제 소개 - 공기(co-occurrence) 정보
공기(起, air가 아님!)란 단어와 단어가 하나의 문서나 문장에서 함께 쓰이는 현상을 얘기한다. 야구와 박찬호는 공기할 확률이 높다. 세탁기와 짜장면은 공기할 확률이 작다.
굳이 자세히 설명하지 않아도, 말뭉치에서 단어들 간의 공기 횟수를 뽑아 놓으면 두루 두루 쓰이는 곳이 많다.
파이썬 스크립트 co_occur_count_local.py는 분산이 아닌 단일 머신 환경에서 한개의 파일로 부터 단어쌍의 공기 횟수를 수집하는 프로그램이다.

=================================================================
#!/usr/bin/python
#!-*- coding: utf-8 -*-

"""
co_occur_count_local.py
"""

import sys
import re

# 유효 단어 검사 패턴 - 숫자, 알파벳, 한글 자모
pattern = re.compile(ur"^[0-9a-zA-Z\uac00-\ud7a3]+$", re.UNICODE)

# 유효 단어 인지 검사
def is_valid_word(word) :
    if pattern.match(unicode(word, "utf-8")) :
        return True
    else :
        return False


if __name__ == "__main__" :
    # 토큰 구분 delimeter
    delimeter = re.compile(r"[ \t,.()'\"-]*")

    co_occur_count = {}
    for line in sys.stdin :
        words = []
        for word in delimeter.split(line.strip()) :
            if is_valid_word(word) :
                words.append(word)
       
        for i in range(len(words)) :
            # 10단어 윈도우 안에서 공기횟수를 카운트
            for j in range(1, 10) :
                if i + j >= len(words) :
                    break
                if words[i] == words[i+j] :
                    continue
                co_occur = "%s-%s" % (words[i], words[i+j])
                co_occur_count[co_occur] = \
                       co_occur_count.setdefault(co_occur,0)+1

    for co_occur in co_occur_count :
        print "%s\t%d" % (co_occur, co_occur_count[co_occur])
=================================================================

탭, 공백, 마침표, 쉼표, 따옴표 등등 ( \t,.()'\"-)을 기준으로 어절을 분리하고, 알파벳, 한글 음절, 숫자 이외의 특수 문자가 들어간 음절은 그냥 버린다. 하나의 문서가 아니라 10단어 윈도우 안에 함께 들어 오는 것을 공기 기준으로 삼았다.

2. 하둡, 맵리듀스가 필요한 순간
필자에게는 약 47만개의 블로그 포스팅 으로 이루어진 700메가 짜리 말뭉치가 하나 있다.
스크립트 co_occur_count_local.py를 다음과 같이 실행할 수 있다.

$ python co_occur_count_local.py < rss_document.txt

이 스크립트는 실행과 동시에 메모리 사용량이 힘차게 증가한다. 필자의 경우 4기가 메모리의 pc에서 약 1분 30초만에 2.6기가 정도 쓰고는 MemoryError를 뱉고 사망했다.
간단히 보면 저 스크립트는 어절 * 어절 매트릭스에 카운트를 기록하는 프로그램이다. 어절이 100만개만 된다고 처도 100만 * 100만 = 1,000,000,000,000!!!
물론, 모든 어절 * 어절 조합이 다 발생하지는 않겠지만, 어쨌거나 실행을 해보니 불가능하다!

이 순간이 맵리듀스가 필요한 순간이다. 이 세상에 존재 하는 어떤 값비싼 서버도 처리할 수 없을 만큼, 아주 많은 메모리 또는 디스크가 필요한 프로그램을 돌려야 할 때. 빅데이터 얘기 하는 사람들은 "데이터의 크기가 문제가 될 때" 라고 얘기한다. 이 때가 맵리듀스가 필요한 순간이다. 맵리듀스는 10분 걸리는 프로그램을 서버 10대로 1분 만에 실행하는 정도의 스케일에는 어울리지 않는다. 그 정도 스케일에는 차라리 돈을 들여 CPU 많고, SCSI 하드 많은 고사양 단일 서버가 더 좋은 해답일 것이다.
언제? 왜? 맵리듀스? 이런 얘기는 다른 데도 많으니 여기까지만.

그래서, 스크립트 co_occur_count_mr.py는 하둡 스트리밍을 이용하여 공기 정보를 카운트 하는 파이썬 스크립트이다.
=================================================================
#!/usr/bin/python
#!-*- coding: utf-8 -*-

"""
co_occur_count_mr.py 개선전 (문제 있음)
"""

import re
import sys

# 유효 단어 검사 패턴 - 숫자, 알파벳, 한글 자모
pattern = re.compile(ur"^[0-9a-zA-Z\uac00-\ud7a3]+$", re.UNICODE)

# 유효 단어 인지 검사
def is_valid_word(word) :
    if pattern.match(unicode(word, "utf-8")) :
        return True
    else :
        return False

if __name__ == "__main__" :
    # 토큰 구분 delimeter
    delimeter = re.compile(r"[ \t,.()'\"-]*")
    
    for line in sys.stdin :
        words = []
        for word in delimeter.split(line.strip()) :
            if is_valid_word(word) :
                words.append(word)
    
        for i in range(len(words)) :
            for j in range(1, 10) :
                if i + j >= len(words) :
                    break
                if words[i] == words[i+j] :
                    continue
                co_occur = "%s-%s" % (words[i], words[i+j])
                # LongValueSum:키[탭]숫자 는 hadoop-streaming의 규약
                print "LongValueSum:%s\t1" % co_occur
=================================================================
빨간색이 co_occur_count_local.py 와 다른 부분이다.
하둡 스트리밍의 개념과 파이썬으로 작성하는 하둡 스트리밍은 구글에 매우 많으므로 여기서는 생략.
내가 하둡을 가지고 있다면,

bin/hadoop jar contrib/streaming/hadoop-*streaming*.jar \
-file co_occur_count_mr.py -mapper co_occur_count_mr.py \ -reducer aggregate \ -input /user/me/input_data -output /user/me/output_data

와 같이 실행할 것이고,

AWS EMR을 이용해야 한다면 http://docs.aws.amazon.com/ElasticMapReduce/latest/DeveloperGuide/emr-get-started-count-words.html 를 참고하면 된다. AWS는 유료 서비스여서 그런지, 문서가 너무 잘되어 있어서, 여기서 더이상 쉽게 이야기 하기가 힘들다.

3. 맵리듀스 프로그램 co_occur_count_mr.py 개선
이 하둡 스트리밍 프로그램에는 두가지 큰 문제점이 있다. 먼저, 느리다. 두번째는 공기 정보를 카운트 순으로 정렬해서 보면 상위의 결과는 대부분 의미가 없다. 예를 들면, (실제 실행 결과는 아니지만) 상위에는 아마 아래와 같은 결과들이 있을 것이다.

수-있는
할수-있다
수-밖에
...

이런 단어쌍은 맵리듀스 실행의 중간 단계에

수-있는:1
할수-있다:1
수-밖에:1

처럼 무수히 많은 ?-?:1 형태의 데이터가 네트웍을 타고 이동하게 되고, 속도의 병목이 된다.
이를 해결하기 위해, '수', '있는', '밖에' 이렇게 흔한 단어들은 stopword로 제외를 하겠다.
말뭉치에서 빈도수 상위 300개(별 근거는 없는 숫자. 300)로 wc.txt라는 파일을 만들고 사용한다.

이 파일을 사용한 개선된 버전의 co_occur_count_mr.py는 다음과 같다.

=================================================================
#!/usr/bin/python
#!-*- coding: utf-8 -*-

"""
co_occur_count_mr.py 개선후
"""

import re
import sys

# 유효 단어 검사 패턴 - 숫자, 알파벳, 한글 자모
pattern = re.compile(ur"^[0-9a-zA-Z\uac00-\ud7a3]+$", re.UNICODE)

# 유효 단어 인지 검사
def is_valid_word(word) :
    if pattern.match(unicode(word, "utf-8")) :
        return True
    else :
        return False

if __name__ == "__main__" :
    stops = set([])
    for line in file("wc.txt") :
        stops.add(line.strip())

    # 토큰 구분 delimeter
    delimeter = re.compile(r"[ \t,.()'\"-]*")
    
    for line in sys.stdin :
        words = []
        for word in delimeter.split(line.strip()) :
            if is_valid_word(word) and word not in stops :
                words.append(word)
    
        for i in range(len(words)) :
            for j in range(1, 10) :
                if i + j >= len(words) :
                    break
                if words[i] == words[i+j] :
                    continue
                co_occur = "%s-%s" % (words[i], words[i+j])
                # LongValueSum:키[탭]숫자 는 hadoop-streaming의 규약
                print "LongValueSum:%s\t1" % co_occur
=================================================================

실행 방법은 내가 하둡을 가지고 있다면,

bin/hadoop jar contrib/streaming/hadoop-*streaming*.jar \
-file co_occur_count_mr.py -mapper co_occur_count_mr.py \ -reducer aggregate \ -input /user/me/input_data -output /user/me/output_data \
-file wc.txt

AWS EMR을 이용한다면, wc.txt 파일을 AWS S3 저장소 어딘가 저장해 놓고, Extra Args 필드에 다음을 입력해야 한다.

-cacheFile s3n://your-bucket/data/wc.txt#wc.txt

아래는 이렇게 수집한 공기 정보 카운트 중 일부이다.
===============================================
Creative-저작물은       1034
2178-O564       1031
없을-정도로     1011
댓글-사진을     983
보기-게시했습니다       965
박지성-김민지   941
식사-청소       938
You-can 924
댓글-트랙백     920
do-not  913
등에-관한       910
맨-오브 892
남자-여자       889
의료실비보험가입안내-인기의료실비보험추천       874
===============================================
박지성-김민지 처럼 의미 있는 쌍이 있는가 하면, 댓글-트랙백, Creative-저작물 처럼 문서 양식에서 나온 문구나, 스팸성 문서 역시 문제이다. (전처리가 필요해...)

이렇게 단순한 카운트 만으로는 쓸만한 공기 정보를 수집하는 것은 아무래도 많이 부족하다. 노력이 된다면 다음 기회에 역시 맵리듀스를 사용하되, 단순 카운트보다는 한단계 더 진화된 방법을  소개하도록 하겠다.


Monday, July 1, 2013

MEMM and CRF

Sequence classification is very important in NLP. One of representative methods for sequence classification is HMM as a generative model and it tried to be combined with MEM as a discriminative model: this is just called MEMM. CRF is finally developed by overcoming the achilles' heel of MEMM. You know. Currently, CRF is the state-of-the-art technique for sequence classification in many NLP applications. So I attempted to summarize the concepts of MEMM and CRF as follows:

http://web.donga.ac.kr/yjko/usefulthings/MEMM&CRF.pdf

How to use JCR on the web.

We can find out impact factor of SCIE journals from the JCR website. However, we need to know more specific information about impact factor of each SCIE journal these days, such as top 20% journals. You can learn how to use JCR on the web through the following slides (it's written by Korean).

http://web.donga.ac.kr/yjko/usefulthings/JCR.pdf