Tuesday, December 10, 2013

Significant test (t-test)

오래만에 글을 게시합니다.

오늘은 significant test에 대해서 이야기를 하려고 합니다.
요즘은 논문을 쓰고 실험 결과를 비교할 때 단순히 성능만을 보이는 것이 아니라, 성능의 차이가 우연히 일어나지 않은 의미있는 차이를 만들었는지에 대한 평가를 해봐야 합니다.

이것을 significant test라고 하고 종류로는 여러가지가 있습니다.
그중 가장 많이 쓰이는 것이 t-test입니다.
이 테스트는 비교하려는 두 성능 분포가 정규분포라는 가정하에 평가를 진행하는 것이라서,
정규분포가 아니면 다른 방법을 써야 합니다.

t-test는 엑셀로도 계산을 할 수 있지만, 아래 사이트를 사용하면 쉽게 계산을 할 수 있습니다.

http://studentsttest.com/
http://www.graphpad.com/quickcalcs/ttest1.cfm

두 사이트를 보면 선택해야 될 옵션들이 있는데 두가지 옵션을 이해하시면 됩니다.

1. unpaired vs. paired : 비교하는 방식에 사용된 데이타가 같은지, 다른지의 구분입니다. 만약 두 기법에서 사용된 실험 데이터가 같다면 paired, 다르다면 unpaired를 선택하시면 됩니다. (위에 첫번째 사이트에서 groups are matched가 paired를 의미합니다.)
2. one-tailed (one-sided) vs. two-tailed (two-sided): 두 정규 분포의 차이를 볼때 양쪽 끝을 다 고려할 것이냐? 아니면 한쪽만을 고려할 것이냐? 구분입니다. 만약 제안한 기법이 기존 기법보다 더 성능이 좋다는 것을 알고 있다면 한쪽만 보면 되기 때문에 one-tailed를 선택하면 됩니다. (두번째 사이트의 단점은 two-tailed t-test 결과만을 제공한다는 점입니다.)

* 논문작성 시 baseline과 proposed method와 성능의 차이를 significant test를 한다면 보통 제안 기법이 더 성능이 좋고 같은 데이터 안에서 실험하는 경우가 대부분이기 때문에 one-tailed paired t-test를 선택하면 됩니다.

마지막으로 일반적으로 정보검색에서는 topic(query)별 성능 결과를 짝을 맞추어 입력하면 되고, 분류에서는 10/5 folds cross-validation을 했을 때는 폴더 별 성능을 짝을 맞추어 넣으면 되고, train과 test구분이 되어 있어서 cross-validation을 하지 않은 경우에는 category별 성능을 짝을 맞추어 입력하면 됩니다.

오늘은 여기까지 하고, 다른 significant test기법 등은 다음 기회에 다시 올리겠습니다.


Sunday, September 1, 2013

맵리듀스(Map-Reduce) 프로그래밍 - 공기정보(Co-occurrence) 구하기 두번째. 상대 빈도 구하기

지난번 포스팅에서 하나의 문장에서 두개의 단어가 동시 출현하는 빈도수를 계산하는 간단한 hadoop streaming 파이썬 프로그램을 소개하였다. 이번 포스팅에서는 이 프로그램이 가지는 문제점을 해결하기 위한 새로운 프로그램을 만들어 보도록 하겠다.

지난번에는 파이썬으로 mapper 프로그램만 작성하고, reducer는 -reduce aggregate라는 옵션으로 내장 reducer를 사용했지만, 이번에는 reducer를 직접 작성해야 한다. 또한, 여러개의 reducer에 분배되는 key를 제어하기 위한 partitioner 옵션도 사용하도록 하겠다.

1. 단순한 co-occur 빈도의 문제점과 해결 방법
예를 들어, '구글'이라는 단어의 앞에 자주 나오는 단어를 알고 싶은 상황이 있다고 치자. 말뭉치에서 구글 앞에 많이 출현한 단어들을 빈도순으로 뽑아 보면 다음과 같은 단어들이 나온다.

단어1-단어2 빈도수
==================
하는-구글 1365
수-구글 664
글-구글 530
다른-구글 525
카테고리의-구글 402
2-구글 394
있습니다-구글 389
0-구글 359
애드센스-구글 324
1-구글 249

우리의 목적은 '구글'이라는 단어와 상관 관계가 높은 단어를 알고 싶은 것인데, '하는', '수', '글', '다른' 이런 단어들은 '원래' 많이 나오는 단어들이다.
이렇게, co-occur 카운트에서 '절대 빈도'는 우리가 원하는 정보가 아니다. 우리는 절대 빈도 뿐만 아니라, '상대 빈도'도 사용하여 문제를 해결하고자 한다.
'하는' 이라는 단어와 '구글' 이라는 단어가 함께 출현하는 절대 빈도는
    f(구글, 하는)
이라고 표현한다. 반면 상대 빈도는
    f(구글 | 하는)
이라고 표현한다. 말로 풀어쓰면 '하는'이라는 단어가 출현한 상황에서(조건에서, 이후에...) '구글'이라는 단어가 출현한 비율을 의미한다. (맞다. 조건부 확률이다!) 계산식은 다음과 같다.
   f(구글|하는) = f(구글, 하는) / f(하는)
즉, 절대 빈도 f(구글, 하는)의 값이 크더라도, '하는'이라는 단어가 원래 많이 나오는 단어라면 상대 빈도 f(구글|하는)의 값은 작은 값이 될 수도 있는 것이다.
여기에 조금 더 욕심을 내서, 상대빈도와 절대빈도를 하이브리드하여 다음의 식을 사용을 할 것이다.
  hybrid_f(구글|하는) = log(f(구글)) * f(구글|하는)
이렇게 하면 같은 상대 빈도 0.9라고 하더라도 기왕이면 '구글'이라는 단어가 많이 출현한 쪽이 더 높은 점수를 받게 된다.

2. Mapper, Reducer 프로그램의 구현
먼저 쉽게 생각할 수 있는 방법은 두개의 Map-Reduce 프로그램을 만드는 것이다. 하나의 MR 프로그램은 f(단어1)를 구하는 프로그램, 두번째 프로그램은 f(단어1, 단어2) 를 구하는 프로그램을 만드는 것이다. 이 방법도 나쁘지는 않지만, 여기서는 하나의 MR 프로그램, 즉, 하나의 Mapper와 하나의 Reducer만으로 상대 빈도 값을 구하는 프로그램을 만들어 보려고 한다.

먼저, 절대 빈도만 구하는 프로그램의 Mapper라면 다음과 같은 key-value 쌍들을 출력할 것이다.
입력 문장: "맵리듀스 프로그래밍 결코 어렵지 않아요"
출력 key-value:
[맵리듀스-프로그래밍   1],
[맵리듀스-결코              1],
[맵리듀스-어렵지          1],
[맵리듀스-않아요          1]
여기에 우리는 단어의 단독 출현 횟수를 동시에 계산하기 위해서 [단어-*     빈도] 라는 특수 목적의 새로운 key-value를 추가한다. 예를 들면,
[맵리듀스-*                   4]
와 같은 식이다.

이를 위한 Mapper.py는 다음과 같다.
아래 프로그램의 출력은 위 설명의 표현 형식과는 약간 다르게 "단어1[탭]단어2[탭]빈도수"의 형식으로 되어있다. 설명의 편의성과 실행의 편의성 차이에 때문이니 양해 바랍니다.^^
"""
relative_count_map.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,.()'\"-]*")
    WINDOW_SIZE = 10
 
    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)) :
            sum = 0
            for j in range(1, WINDOW_SIZE) :
                if i + j >= len(words) :
                    break
                # 단어1[탭]단어2[탭]1 출력
                if words[i] != words[i+j] :
                    print "%s\t%s\t1" % (words[i], words[i+j])
                    sum += 1
            if sum > 0 :
                # 단어1[탭]*[탭]sum 출력
                print "%s\t*\t%d" % (words[i], sum)

이렇게 만들어진 Mapper의 key-value 쌍들은 하둡의 프레임웍이 알아서 섞고, 모으고 가지런히 정렬하여 (shuffle&sort) 아래와 같이 예쁜 순서대로 Reducer의 standard input 입력으로 도달하게 된다. (솔직히 말해서, 알아서가 아니고, partitioner 파라미터를 설정해주는 것이 필요한데, 이 설명은 뒤에 한다.)

# Reducer의 입력
하는[탭]*[탭]123451
하는[탭]*[탭]321212
하는[탭]*[탭]231231   ====> 여러 mapper에서 결과가 만들어져서 중복키가 연달아 들어옴
하는[탭]0[탭]223
하는[탭]0[탭]345
하는[탭]0[탭]311
하는[탭]00[탭]16
...
하는[탭]구글[탭]565
하는[탭]구글[탭]425
하는[탭]구글[탭]136
...

이제, 위와 같은 standard input 을 입력으로 받아서, 위에서 언급한 상대빈도와 절대빈도의 hybrid score를 계산하는 Reducer는 다음과 같다.

"""
relative_count_map.py
"""
import re
import sys
import math

"""
이 프로그램은 word1-word2 조합으로 이루어진 키의 입력 순서가 정렬되어 있다는 가정을 하고 있다.
그리고, 모든 word1-word2 맨앞에 word1-*가 반드시 있어야 한다.
또한, 같은 word1은 반드시 하나의 reducer로 모아서 입력되어야 한다.
이런 튜닝은 partitioner의 입력 파라미터로 이루어진다.
"""
if __name__ == "__main__" :
    prev_key = None
    key_count = 0
    for line in sys.stdin :
        word1, word2, val = line.split("\t")
        key = "%s\t%s" % (word1, word2)
        val = int(val)
        # word1-word2 조합 key가 바뀌면 바뀌기 전의 word1-word2에 대해 score 계산
        if prev_key != key :
            if prev_key :
                word1, word2 = prev_key.split("\t")
                if word2 == "*" :
                    word1_sum = key_count
                    print "%s\t%s\t%d" % (word1, word2, word1_sum)
                elif key_count >=1 :
                    hybrid_f = math.log(key_count) * key_count/word1_sum
                    print "%s\t%s\t%d\t%d\t%5.5f" % (word1, word2, key_count, word1_sum, hybrid_f)
            key_count = val
            prev_key = key
        # 동일한 word1-word2가 들어오는 동안은 빈도를 누적
        else :
            key_count += val

    word1, word2 = prev_key.split("\t")
    # 너무 빈도가 낮은 출력은 용량만 차지하고 쓸모가 없으므로 아예 출력을 안한다.
    if key_count >= 5 :
        print "%s\t%s\t%d\t%d\t%5.5f" % (word1, word2, key_count, word1_sum, float(key_count)/word1_sum)

여기까지의 내용을 다시 한번 요약하면, 단어1-단어2의 절대 빈도에, 단어1 만의 빈도를 의미하는 단어1-* 의 값을 이용하여 단어1의 출현 조건 하에서의 상대빈도를 구한다는 것이다.
Mapper에서 문서로부터 빈도를 만들어 출력하고, Reducer에서 빈도를 수집한다. 이런 패턴이 Map-Reduce의 기본 패턴이다.


3. 각각의 Reducer에 입력될 key를 할당하는 담당자 - Partitioner
이쯤에서 Map-Reduce의 실행 방식을 되돌아 보면, 여러개의 Mapper 프로그램이 key-value형식으로 출력을 하고, 여러개의 Reducer 프로그램이 또한, key-value 형식으로 입력을 받는다. 이때, Mapper의 key와 Reducer의 key는 반드시 일치할 필요는 없다.
여기서 질문. Mapper의 수많은 출력이 어떻게 섞이고 정렬되어 여러개의 Reducer에 분배, 할당 될까? 우리가 보통 코딩을 하지는 않지만(경우에 따라서는 custom으로 만들수도 있는) Partitioner라고 하는 프로그램이 있다.
Default Partitioner는 Mapper의 출력을 그냥 골고루 Reducer에 나눠주는 역할만 한다. 그래서, mapper 출력의 key의 hash값 % Reducer 개수로 계산하여, Reducer를 결정한다. 그러므로, 이 Default Partitioner는 Mapper의 출력 중 같은 key는 같은 Reducer로 할당하는 것까지만 보장된다. Hash값이 같을 태니까.

하지만, 우리가 만든 Reducer의 입력은 다음과 같은 조건이 반드시 지켜져야 한다.
1. 같은 단어1은 반드시 하나의 reducer로 가야 한다. 즉, '검색-구글' key는 1번 reducer로 가는데, '검색-네이버' key는 2번 reducer로 가면 안된다. 단어1이 '검색'인 단어를 모아서 계산해야 하기 때문이다. MR 프로그램은 reducer의 출력이 최종 출력이다. Reducer 이후 단계는 없다.
2. 단어1-단어2  에 대해서 오름 차순 정렬되어 있어야 하고, 그 맨위에 단어1-*가 있어야 한다. 순서가 뒤죽 박죽되면, 위에 소개한 Reducer 파이썬 프로그램은 정상 동작하지 않는다.

Partitioner가 이런 목적대로 동작하기 위해서, 우리는 Hadoop Streaming 실행 파라미터를 다음과 같이 한다.
hadoop jar $HADOOP_HOME/contrib/streaming/hadoop-streaming-1.0.4.jar \
-D mapred.output.key.comparator.class=org.apache.hadoop.mapred.lib.KeyFieldBasedComparator \
-D stream.num.map.output.key.fields=2 \
-D mapred.text.key.partitioner.options=-k1,1 \
-D mapred.text.key.comparator.options="-k1 -k2" \ 
-input /input -output /output \
-mapper relative_count_map.py \
-reducer relative_count_reduce.py \
-file relative_count_map.py -file relative_count_reduce.py \
-partitioner org.apache.hadoop.mapred.lib.KeyFieldBasedPartitioner


여기서, 주요 옵션에 대한 설명은 다음과 같다.
-D stream.num.map.output.key.fields=2 ---> mapper의 출력은 단어1, 단어2 두필드의 조합키
-D mapred.text.key.partitioner.options=-k1,1----> 단어1 만 partitioning key로 사용하려면 -k1,1로 해주어야 함
-D mapred.text.key.comparator.options="-k1 -k2" \  ---> 정렬은 단어1, 단어2 조합에 대해 해야 한다.
-partitioner org.apache.hadoop.mapred.lib.KeyFieldBasedPartitioner   --> Partitioning에 사용할 hadoop-streaming 내장 클래스

옵션에 대한 설명은 몹시 약하지만, 실제 개발시에는 매뉴얼과 구글을 뒤져가며 할 수 밖에 없을 것이다. 본인도 그렇게 찾아낸 옵션들이다.

4. 실행 결과와 결과 요약
이렇게 실행해서 찾아낸 '구글'과 상관 관계가 높은 단어들은 다음과 같다.


단어1-단어2 Hybrid-빈도( = log(f(단어2)) * f(단어2 | 단어1) )
===================================================
구글토그-구글 0.89414
스타트업기업과-구글 0.61769
타케팅으로-구글 0.53517
돈준다-구글 0.47337
다운로드계의-구글 0.44055
첫출시를-구글 0.39816
라이터로도-구글 0.39816
검색엔진만을-구글 0.39816
Megaphone-구글 0.39816
구글어스다운-구글 0.30951

보시는 것처럼, 포스팅 맨 앞에 소개한 단어들과는 다르게 '구글'과 상관 관계가 높은 단어들 만이 높은 점수를 받은 것을 알 수가 있다.

여기까지, 두서 없이 긴 내용을 읽어주신 분들께 감사드린다. 요약하자면,
1. 공기(co-occurrence) 빈도수를 구할 때, 절대 빈도의 대안으로 상대 빈도가 사용된다.
2. 이를 하나의 MR 프로그램에서 해결하기 위한 단어1-* 키를 도입하였다.
3. 각각의 Reducer에 적절한 key값 분배를 위하여 Partitioner 옵션을 설정하였다.
4. 2개의 key를 기준으로 Reducer 입력을 정렬하기 위한 옵션을 설정하였다.

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

Sunday, June 16, 2013

The online proceedings of NAACL-HLT 2013.

Do you want to get some papers on NAACL-HLT 2013? Currently, you can get all the papers from the ACL Anthology (http://aclweb.org/anthology-new/N/N13/). 

Thursday, June 13, 2013

Generative vs. Discriminative Models for Classification

Have you ever seen "Generative model" or "Discriminative model" in papers related NLP or ML? Can you explain what are their exact definitions and differences between them? I thought we need to understand and organize them well.  A few weeks ago, I made a talk about them at Sogang University and I'd like to post my presentation slide here.

http://web.donga.ac.kr/yjko/talks/GenerativeDiscriminative(Youngjoong%20Ko).pdf

Sunday, June 9, 2013

Deep Learning for NLP (tutorial materials by Richard Socher and Christopher Manning)

This is my first posting, which is about "Deep Learning". This tutorial (http://www-nlp.stanford.edu/courses/NAACL2013/) was held in NAACL 2013 by Socher and Manning. As you know, "deep learning" is a new issue in NLP research areas so I believe that you all are interested in this website, which contains codes and data for an experiment as well as slides.

Wednesday, June 5, 2013

This blog will share and talk about useful information and issues related to natural language processing, machine learning and information retrieval with my friends.

자연어처리, 기계학습과 정보검색에 관련된 일을 하는 분들이 공동으로 유익한 정보를 공유하는 blog입니다.

많은 관심 부탁드립니다.