집단지성(collective intelligence)를 이용한 스팸 문서 필터링

14
집단지성(collective intelligence)이용한 스팸 문서 필터링 Written by 전희원 전희원 ([email protected] ) Yahoo! Korea에서 웹검색을 담당하고 있다. 웹검색을 하면서 검색영역의 방대함을 깨달았고 특히 문서 분류 팸처리 등의 마이닝 영역에 매우 관심이 많다. 검색, 마이닝 관련 블로그를 운영하고 있다. http://www.freesearch.pe.kr Scheme대해서 scheme간단한 Lisp이다. Lips 계열의 언어중에서 가장 성공한 방언중에 하나이며 세계의 대학에서 프로그래밍을 처음 가르칠 사용하는 언어이다. 얼마 전에 국내에 번역된 SICP(Structure and Interpretation of Computer Programs)라는 유명한 책이 scheme이라는 어를 사용한다. 외국 어떤 포럼에서는 Python이나 Ruby많은 부분이 Lisp같은 함수적 언어 특징을 경쟁적으로 도입하고 있어서 이런 스크립트 언어들이 Lisp향해 진화한다는 이야기까지 나올 정도이다. 자신이 algol계열의 C, C+, Java언어만을 써왔다면 한번쯤 함수형 언어를 배울 것을 추천한다. 이는 개발자 자신에게 문제를 바라보는 다른 시각을 것이다. 이런 scheme이라는 언어를 사용하는데 단순한 에디터를 사용하는 것도 좋지만 공개되어 있는 DrScheme이라는 IDE사용하길 추천한다. 초심자에게 변수 트래킹이라든지 편리한 GUI디버깅 환경을 제공하고 scheme대한 다른 책이 필요 없을 정도로 help파일을 정리해서 보여준다. IDEHelp Desk포함된 Teach Yourself Scheme in Fixnum Days”라는 문서를 scheme문자에게 추천한다. 참고자료 목록 Ending Spam, http://nostarch.com/frameset.php?startat=endingspam SICP, http://kangcom.com/common/bookinfo/bookinfo.asp?sku=200710100001 Wiki of Collective Intelligence, http://en.wikipedia.org/wiki/Collective_intelligence A plan for Spam, http://www.paulgraham.com/spam.html 순서 1. 집단지성(collective intelligence)이용한 스팸 문서 필터 만들기 2. 스팸 필터 개선하기 1집단지성(collective intelligence)이용한 스팸 문서 필터 만들기 집단지성(collective intelligence)이란? 최근 집단지성(collective intelligence)이라는 거창한 말로 웹을 설명하는 사람들이 많아졌다. 이는

Upload: heewon-jeon

Post on 27-Jul-2015

463 views

Category:

Documents


8 download

DESCRIPTION

룰 베이스의 문서 필터링은 한 사람이 스팸에 나올만한 경우에 대해서 정리하고 이것을 어떠한 룰로 정하면서 시작이 된다. 이런 작업을 사람이 하는건 혼을 빨아먹는(soul sucking) 작업이라는 표현으로 이야기 되기도 할 정도로 스팸의 세계는 복잡하다. 또한 이것의 큰 단점 중에 하나는 스팸의 정도를 가늠하기 힘들다는 문제가 있다. (단지 True와 False의 문제이기 때문이다.) 예를 들어 “특정 문서의 모든 문자가 대문자로 강조되어서 나오기 때문에 스팸이다” 라고 할 수 있는데 이는 특별히 대문자로 강조해 정상문서를 만들었을 경우 잘못 판단하게 될 것이다. 또한 이런 간단한 룰은 굉장히 명백해서 스패머가 간단하게 우회 할 수 있을 것이다. 그리고 요즘 스팸의 경향으로 봤을 때 복합적인 스팸 성향들을 효과적으로 결합해 필터를 구성해야 하는 필요성이 대두되고 있을 정도로 스팸의 정교함과 교활함은 성장에 성장을 거듭하고 있다. 이런 복잡한 작업을 일일이 할 정도로 우리 개발자들의 시간은 넉넉치 않다는게 문제다.

TRANSCRIPT

Page 1: 집단지성(collective intelligence)를 이용한 스팸 문서 필터링

집단지성(collective intelligence)를 이용한 스팸 문서 필터링

Written by 전희원

전희원 ([email protected])

Yahoo! Korea에서 웹검색을 담당하고 있다.

웹검색을 하면서 검색영역의 방대함을 깨달았고 특히 문서 분류 및 스

팸처리 등의 마이닝 영역에 매우 관심이 많다.

검색, 마이닝 관련 블로그를 운영하고 있다.

http://www.freesearch.pe.kr

Scheme에 대해서

scheme은 간단한 Lisp이다. Lips 계열의 언어중에서 가장 성공한 방언중에 하나이며 세계의 많

은 대학에서 프로그래밍을 처음 가르칠 때 사용하는 언어이다. 얼마 전에 국내에 번역된

SICP(Structure and Interpretation of Computer Programs)라는 유명한 책이 이 scheme이라는 언

어를 사용한다. 외국 어떤 포럼에서는 Python이나 Ruby의 많은 부분이 Lisp과 같은 함수적 언어

특징을 경쟁적으로 도입하고 있어서 이런 스크립트 언어들이 Lisp을 향해 진화한다는 이야기까지

나올 정도이다.

자신이 algol계열의 C, C+, Java언어만을 써왔다면 한번쯤 함수형 언어를 배울 것을 추천한다.

이는 개발자 자신에게 문제를 바라보는 다른 시각을 줄 것이다.

이런 scheme이라는 언어를 사용하는데 단순한 에디터를 사용하는 것도 좋지만 공개되어 있는

DrScheme이라는 IDE를 사용하길 추천한다. 초심자에게 변수 트래킹이라든지 편리한 GUI디버깅

환경을 제공하고 scheme에 대한 다른 책이 필요 없을 정도로 help파일을 잘 정리해서 보여준다.

이 IDE의 Help Desk에 포함된 “Teach Yourself Scheme in Fixnum Days”라는 문서를 scheme입

문자에게 추천한다.

참고자료 목록

Ending Spam, http://nostarch.com/frameset.php?startat=endingspam

SICP, http://kangcom.com/common/bookinfo/bookinfo.asp?sku=200710100001

Wiki of “Collective Intelligence”, http://en.wikipedia.org/wiki/Collective_intelligence

A plan for Spam, http://www.paulgraham.com/spam.html

순서

1. 집단지성(collective intelligence)를 이용한 스팸 문서 필터 만들기

2. 스팸 필터 개선하기

1부 집단지성(collective intelligence)를 이용한 스팸 문서 필터 만들기

집단지성(collective intelligence)이란?

최근 집단지성(collective intelligence)이라는 거창한 말로 웹을 설명하는 사람들이 많아졌다. 이는

Page 2: 집단지성(collective intelligence)를 이용한 스팸 문서 필터링

개인이 차세대 웹에서 중요한 의미로 대두되고 자신의 활동 여하에 따라 자신을 웹에 부각시킬

수 있는 그런 미디어의 다양화에 따라 부각이 되고 있다.

그럼 그렇게 부각되는 집단지성의 정확한 의미는 무엇일까?

이는 많은 개개인으로부터 협동과 경쟁에 의해서 생성되는 지능(intelligence)이라고 Wikipedia에

정의가 되어 있다.

이 개념을 실체화 시키기 위해 인공지능(artificial intelligence) 기계학습(machine learning)이라는

학문 분야에서 이미 수십 년간 연구가 되고 있다.

실제 Wikipedia나 Google은 이런 집단지성의 힘으로 인해 각자의 사업 영역에서 가장 많은 성공

을 거두었다.

사전을 공개해 많은 사람들의 지성을 흡수할 수 있게끔 만들어 세상에서 가장 정확하고 풍부한

사전 데이터를 포함하고 있고, 많은 사람들이 링크를 많이 건 문서를 좋은 문서로 검색 상위에

올라가게끔 해줌으로써 최상의 검색 퀄리티를 만들 수 있어서 최근 가장 성공한 기업으로 미디어

의 주목을 받고 있다.

스팸 문서 필터링

룰 베이스 스팸 문서 필터링의 문제점

룰 베이스의 문서 필터링은 한 사람이 스팸에 나올만한 경우에 대해서 정리하고 이것을 어떠한

룰로 정하면서 시작이 된다.

이런 작업을 사람이 하는건 혼을 빨아먹는(soul sucking) 작업이라는 표현으로 이야기 되기도 할

정도로 스팸의 세계는 복잡하다.

또한 이것의 큰 단점 중에 하나는 스팸의 정도를 가늠하기 힘들다는 문제가 있다. (단지 True와

False의 문제이기 때문이다.) 예를 들어 “특정 문서의 모든 문자가 대문자로 강조되어서 나오기

때문에 스팸이다” 라고 할 수 있는데 이는 특별히 대문자로 강조해 정상문서를 만들었을 경우 잘

못 판단하게 될 것이다. 또한 이런 간단한 룰은 굉장히 명백해서 스패머가 간단하게 우회 할 수

있을 것이다.

그리고 요즘 스팸의 경향으로 봤을 때 복합적인 스팸 성향들을 효과적으로 결합해 필터를 구성해

야 하는 필요성이 대두되고 있을 정도로 스팸의 정교함과 교활함은 성장에 성장을 거듭하고 있다.

이런 복잡한 작업을 일일이 할 정도로 우리 개발자들의 시간은 넉넉치 않다는게 문제다.

일단 룰 베이스의 필터를 예제를 들어 설명을 해보겠다.

단적인 예로 들어 “sex”라는 단어가 들어있는 문서를 스팸이라고 한다면

아래와 같은 sudo코드가 들어갈 것이다.

if “sex” in doc

return SPAM

그런데 모든 “sex” 단어를 가진 문서가 스팸일 수는 없을 것이다.

예를 들어 “sex discriminate” (성차별)을 이야기하는 문서일 경우는 어떨까?

이런 문서의 경우 아래와 같은 코드로 예외 상황을 변경하게 될 것이다.

if “sex” in doc

if “discriminate” in doc

return HAM

else

return SPAM

스팸이 발전할수록 위와 같은 if else 같은 코드나 데이터 추가가 기하급수적으로 늘어갈 것이다.

룰 베이스의 가장 어려운 부분은 저런 복합적인 스팸 속성들을 사람이 직접 뽑아야 된다는 사실

Page 3: 집단지성(collective intelligence)를 이용한 스팸 문서 필터링

과 시간이 갈수록 이해하기 힘들고 유지보수 하기 힘든 코드로 진화(?)된다는 사실이다.

집단지성을 이용한 스팸 필터링

위와 같은 스팸 필터링의 문제점을 인식해서 처음 나온 집단지성 알고리즘이 폴 그레이엄(Paul

Graham)의 베이지언 스팸 필터 알고리즘이다. 그는 2002년 룰 베이스의 스팸 필터를 만들다가

이런 작업이 혼을 빼놓는(soul sucking) 작업이라는 것을 깨닫고 스패머의 입장에서 스팸을 어떻

게 보내면 효율적으로 보낼 수 있을지를 고민했다.

이 알고리즘은 이것이 나오기 전의 룰 베이스의 알고리즘과는 확연히 다른 방법으로 스팸을 필터

링해 당시 획기적인 방법으로 각광을 받았다. 기존의 접근 방법에서 탈피해 학습(learning)과 테

스트(test)이라는 과정을 거쳐 현 시점에 가장 적합한 스팸 필터를 만들 수 있었다. 게다가 처리

퍼포먼스도 상대적으로 좋다.

위의 룰 베이스 방법이 그저 Spam 인지 Ham인지를 판단하는 0과 1의 문제였다면, 학습을 통해

나오는 데이터는 “Spam일 확률이 과연 얼마인가?”에 대한 결과를 도출하여 나름대로 임계값을

정해 스팸 필터의 정확도를 튜닝할 수 있다는 장점이 있다.(ex. 90% 이상이면 스팸이다.)

그럼 이런 결과를 얻기 위해 어떤 데이터를 얻어야 하나 생각해 보자.

먼저 룰 베이스 방법에서 문서의 스팸성을 판단하는데 단어가 가장 큰 영향을 미칠 것이라 생각

할 수 있다. 그렇다면 학습 기반의 방법에서도 판단 기준을 정할 수 있는 것으로 단어를 생각할

수 있는데 문서가 단어의 집합이니 문서의 스팸성을 판단하는데 단어의 스팸성을 먼저 가늠해 보

도록 하자.(물론 단어 말고도 다른 여러가지 속성들을 선택할 수 있다.)

구현

학습(Learning) 과정

먼저 가장 필요한 데이터는 특정 문서 집합을 스팸, 햄으로 분류한 문서 집합이 필요하다. 이런

데이터는 일반적으로 사람이 분류를 한 결과를 쓰게 된다. 우리가 수만건의 문서를 분류하기는

사실상 힘든 문제니 필자가 간단한 덧글 수준의 스팸 판정 데이터를 첨부하도록 하겠다.

그 문서 집합을 배경으로 아래와 같은 데이터를 뽑아 보자

SH = 특정 단어가 스팸집합에 출현한 횟수

IH = 특정 단어가 햄집합에 출현한 횟수

TS = 전체 문서집합에서 스팸문서의 개수

TI = 전체 문서집합에서 햄문서의 개수

사실 위 정보를 구하기 위한 과정이 학습(learning)과정이다.

이 정보를 저장하기 위해 아래와 같은 전역 Hash Table 2개와 변수 2개를 정의한다. (‘;’ 기호는

comment다.)

(define HamDic (make-hash-table 'equal)) ;IH

(define SpamDic (make-hash-table 'equal)) ;SH

(define Hamcnt 0) ;TI

(define Spamcnt 0) ;TS

이런 전역변수를 이용해 아래와 같은 학습 모듈을 작성한다.

;Training module (define (Training trainingset)

Page 4: 집단지성(collective intelligence)를 이용한 스팸 문서 필터링

(define i (open-input-file trainingset))

(define ERROR_CNT 0)

(define re-white (pregexp "\\W+"))

(define re-extract-doc-from-file (pregexp "<comment:(.+)><spam:(.+)>"))

;라인으로 들어온 단어를 파싱하고 사전에 넣는 모듈 (define (parsingLine line)

(if (boolean? line)

(set! ERROR_CNT (+ 1 ERROR_CNT))

(if (equal? (list-ref line 2) SPAM)

(begin

(set! Spamcnt (+ Spamcnt 1))

(makeDic (map string-downcase (pregexp-split re-white (car (cdr line))))

SpamDic)

)

(begin

(set! Hamcnt (+ Hamcnt 1))

(makeDic (map string-downcase (pregexp-split re-white (car (cdr line))))

HamDic)

)

)

)

)

;각각 키워드 스팸 단어 사전을 만드는 함수 (define (makeDic keywordlist Dic)

(let ((keylist '()))

(begin

(set! keylist (delete-duplicates keywordlist))

(for-each (lambda (i) (begin

(if (= -1 (hash-table-get Dic i -1))

(hash-table-put! Dic i 1)

(hash-table-put! Dic i (+ 1 (hash-table-get Dic i)))

)

)

) keylist )

)

)

)

;파일을 라인별로 읽어 들이는 함수 (define (read-lines iport)

(let loop ((i 0)(line (read-line iport)))

(if (eof-object? line)

(close-input-port iport)

(begin

Page 5: 집단지성(collective intelligence)를 이용한 스팸 문서 필터링

(parsingLine (pregexp-match re-extract-doc-from-file line)) ;t

(loop (+ i 1) (read-line iport))

)

)

)

)

(begin

(read-lines i)

(printf "~nTotal HAM : ~a, Total SPAM : ~a, Total Doc : ~a SpamDic : ~a HamDic : ~a

Error count : ~a.~n"

Hamcnt

Spamcnt

(+ Hamcnt Spamcnt)

(hash-table-count SpamDic)

(hash-table-count HamDic)

ERROR_CNT

)

)

)

코드는 복잡해 보이는데 아래 순서를 차근히 보면서 함수를 매칭시켜 보면 간단하다는 사실을 알

수 있다.

스팸성 테스트

폴 그레이엄은 위 정보와 아래와 같은 식으로 특정 단어의 스팸 확률을 구했다. (이 식이 어떻게

유도되는지 알고 싶으면 http://www.freesearch.pe.kr/732 를 참고하길 바란다.)

(1)

“sex”라는 단어의 스팸 스코어를 알고 싶다면 학습과정에서 만든 위 4개의 변수를 가지고 어떻게

해야할까?

라인별 문서입력

•read-lines

파싱 및 띄어쓰기로

term추출

•parsingLine

term 사전 만들기

•makeDic

Page 6: 집단지성(collective intelligence)를 이용한 스팸 문서 필터링

새로 입력되는 문서에 대해서 파싱하고 위의 과정을 거치면 문서 내 각 단어의 스팸 스코어가 나

올 것이다.

그럼 위에서 나온 스코어들의 리스트를 가지고 문서의 스코어를 구하기 위해서 어떻게 해야할까?

일단 아래와 같이 위 스코어를 내림차순 정렬하여 term 스코어 테이블을 만들어야 한다.

예를 들어 “sex school cat ipod buy” 라는 문서가 입력되었다고 하자.

Term P(Term)

sex 0.86

buy 0.65

cat 0.30

school 0.16

ipod 0.05

문서의 길이는 다 다를 것이니 이중에서 스코어가 가장 높은 2개의 텀만 뽑아서 문서의 스팸 스

코어 계산을 하도록 하자.

단어의 스팸 스코어들의 집합으로 문서의 스코어를 구하는 식은 아래와 같다.

(2)

따라서 위 테이블을 이용한 문서의 스팸 스코어는 아래와 같이 계산된다.

위 문서는 약 93%의 스팸성이 있다.

아래 코드는 스팸 키워드 하나를 입력받아 위 (1)식을 이용 단어 하나의 스팸 확률을 구하는 식

이다.

(define (calc-word-spam-score keyword)

(let ((b 0) (g 0))

(if (> threshHapaxes (+ (hash-table-get SpamDic keyword 0) (hash-table-get HamDic

keyword 0)))

(cons keyword valueHapaxes)

(begin

(set! b (/ (hash-table-get SpamDic keyword 0) Spamcnt))

(set! g (/ (hash-table-get HamDic keyword 0) Hamcnt))

;(printf "keyword : ~a :score : ~a.\n" keyword (/ b (+ b g)))

(cons keyword (/ b (+ b g)))

)

)

)

)

Page 7: 집단지성(collective intelligence)를 이용한 스팸 문서 필터링

위에서 보이는 valueHapaxes 변수는 키워드가 우리가 학습시킨 학습셋에 없는 단어 이거나 거의

출현하지 않은 단어일 경우 기본 확률 수치를 준다. 이 value가 없으면 어떤 해당 확률이 0이 되

어버려서 아래 확률곱을 할 때 0으로 나눠주는 상황이 발생한다. 일반적으로 0.4를 주고, 이 값

은 충분히 바뀔 수 있다.

또 아래의 코드는 (2)번식을 구체화 한 함수이다.

(define (calc-combine-probability topTable)

(let ((C 1)(B 1))

(begin

(set! C (apply * (map (lambda (x) (cdr x)) topTable)))

(set! B (apply * (map (lambda (x) (- 1.0 (cdr x))) topTable)))

(/ C (+ C B))

)

)

)

덧붙여서 …. 스팸 메일 클라이언트를 사용하다 보면 “스팸 신고”버튼이 있다. 최근 구글의 지메일에서는 이

스팸 신고 버튼을 눌러 달라고 UCC로 호소를 할 정도로 메일 클라이언트에서 이 기능은 효율적

인 스팸 필터링에 중요한 부분이다.

그럼 스팸 리포팅하는 기능이 왜 그렇게 필요할까?

이는 바로 스팸 필터를 학습 시키기 위해 필요하기 때문이다. 스팸 신고가 들어왔을 때 스팸 본

문을 파싱해 필터링에 필요한 텀(우리가 위에서 추출한)추출 및 메일 방송 주소 등등 많은 스팸

정보를 뽑아 클라이언트가 가지고 있는 필터를 강화하는 학습을 시킨다. 그렇게 함으로써 비슷한

패턴의 스팸을 자동으로 필터링 할 수 있게 된다.

1부 강좌를 마치며

여기서 첨부된 소스파일은 DrScheme(http://download.plt-scheme.org/drscheme/)에서 바로 실

행이 가능하다. 직접 돌려보고 데이터를 보면서 현재의 초보적인 스팸 필터가 어떤 문제가 있는

지 확인해 보는 작업도 개인적으로 의미있을 거라 생각한다.

실행을 위해 언어설정을 아래와 같이 해주면 된다.

- choose language/PLT/Textual (Mzscheme, include R5RS)

이 다음 강좌에서는 이번 강좌에서 작성했던 기본적인 필터를 기반으로 성능 개선을 위해 해야될

몇몇 작업들에 대한 소개를 해보겠다.

Page 8: 집단지성(collective intelligence)를 이용한 스팸 문서 필터링

2부 스팸 필터 개선하기

이전의 칼럼에서 제안한 기본적인 스팸필터의 성능을 향상시키기 위해 무엇을 해야 하는지 이 장

에서 알아보기로 하겠다.

전처리(preprocessing) 작업

스팸 필터를 만드는 것도 중요하지만 데이터를 가지고 하는 작업들은 데이터의 정제가 필수적이

다. 그래서 많은 데이터 마이닝 책에서 가장 처음에 데이터를 정제하는 전처리(preprocessing)작

업을 넣는다. 또한 그 데이터 전처리 작업이 마이닝 작업의 60% 이상의 리소스를 차지한다고 언

급하고 있다.

(intorduction to data mining:http://www-users.cs.umn.edu/~kumar/dmbook/index.php)

먼저 전처리 작업에 대해서 알아보자!

대표적인 전처리 작업들은 아래와 같은데…

1. 불용어(stopword)제거작업

2. 숫자 필터링

3. 소문자 통일 작업

4. 스테밍 (단어의 어근을 뽑아내는 작업)

5. 시소러스 사전 작업

일단 이전 칼럼에서 띄어쓰기 단위로 단어를 추출해 그들을 소문자로 통일시키는 작업을 했었다.

이렇게 함으로써 "BOY"와 "boy"를 같은 단어로 처리해 카운팅 할 수 있었다.

아래 프로시저에서 사전 입력 전에 string-downcase 프로시저로 매핑한 부분을 볼 수 있을 것이

다.

;라인으로 들어온 단어를 파싱하고 사전에 집어 넣는 모듈 (define (parsingLine line)

(if (boolean? line)

(set! ERROR_CNT (+ 1 ERROR_CNT))

(if (equal? (list-ref line 2) SPAM)

(begin

(set! Spamcnt (+ Spamcnt 1))

(makeDic

(map string-downcase (pregexp-split re-white (car (cdr line))))

SpamDic)

)

(begin

(set! Hamcnt (+ Hamcnt 1))

(makeDic

(map string-downcase (pregexp-split re-white (car (cdr line))))

HamDic)

)

)

)

)

불용어 제거 작업은 필자가 임의로 제작한 243건의 불용어 사전을 활용해서 필터링 해보겠다. 예

를 들어 "the"나 "a"같은 거의 대부분의 문서에서 빈번하게 출현하는 단어를 필터링 하게 되는데,

이런 단어들은 판정에 쓸데없는 노이즈를 강력하게 발생시킬 수 있기 때문이다. (별로 중요하지

Page 9: 집단지성(collective intelligence)를 이용한 스팸 문서 필터링

않은 단어임에도 불구하고 말이다.)

소스파일에 포함된 “load-stop-word-file” 프로시저는 지정된 ifile을 읽어 들여 StopWordDic이라

는 List 전역변수에 저장을 시키는 로직이다. (물론 단어개수가 많다면 다른 자료구조를 사용해

효율성을 높일 수 있겠지만 간단하게 구현하기 위해 List를 사용했다.)

위 불용어 사전을 통한 필터링 작업을 위해 전 강좌에서 만들어 둔 makeDic프로시저를 아래와

같이 고쳐 보겠다.(볼드체 부분이 바뀐 부분이다.)

;각각 키워드 스팸 단어 사전을 만드는 함수 (define (makeDic keywordlist Dic)

(let ((keylist '()))

(begin

;(set! keylist (delete-duplicates keywordlist))

;dedup and stopword process

(set! keylist (remove stop-word? (delete-duplicates keywordlist)))

(for-each (lambda (i) (begin

(if (= -1 (hash-table-get Dic i -1))

(hash-table-put! Dic i 1)

(hash-table-put! Dic i (+ 1 (hash-table-get Dic i)))

)

)

) keylist )

)

)

)

사용된 불용어 단어 사전은 제공되는 예제파일의 “english/spam.stopword”경로에 존재한다. (한

글 불용어도 몇 개 있는데 무시해도 좋다.)

그리고 위에서 사용된 “stop-word?” 프로시저는 아래와 같다.

함수 안에 isstopword? 프로시저를 재정의 해서 tail-recursion을 사용해 구현했다.

;delete term in stop-word dic

(define (stop-word? term)

(define (isstopword? term dic)

(if (null? dic)

#f

(if (equal? term (car dic))

#t

(isstopword? term (cdr dic))

)

)

)

(isstopword? term StopWordDic)

)

숫자 필터링은 하나의 단어가 숫자로만 이루어졌을 경우 제거하도록 하겠다.

코드는 위의 makeDic프로시저에서 볼드체로 되어있는 부분을 아래와 같이 바꾸면 된다.

Page 10: 집단지성(collective intelligence)를 이용한 스팸 문서 필터링

(set! keylist (remove string->number (remove stop-word? (delete-duplicates keywordlist))))

스테머는 주로 어근과 어미가 있을 때 어근을 추출하는 작업을 하게 된다. 예를 들어 "fishing",

"fished", "fish", "fisher"가 입력이 되면 동일 어근인 "fish"를 추출할 수 있게 된다.

스테밍 작업은 현재 영문으로 작업을 하기 때문에 영문 스테머를 사용해야 한다. 그리고 만일 한

글이라면 한글 색인어 추출기를 사용해야 하는데 마땅히 공개된 추출기가 없기 때문에 국민대학

교 강승식 교수님의 형태소 분석기(http://nlp.kookmin.ac.kr/HAM/kor/index.html)를 추천한다.

분석기를 사용해서 특정 품사를 추출하는 식으로 분석 대상을 한정하면 될 것이다. 아무래도 명

사 정도가 스팸처리에서는 가장 의미 있는 품사이지 않을까 한다.

영문 스테머는 여러 종류가 있다.

대표적으로 가장 오래되었고 가장 많이 쓰였던 Porter Stemmer가 있고 최근에 나온 가장 어그레

시브한 스테머로 알려진 Lovins Stemmer가 있는데 룰 갯수로 따지면 Porter Stemmer가 37개의

룰이 적용되어 있고 Lovins Stemmer는 294개의 룰이 존재한다. 아무래도 룰이 많은 스테머가 정

확도가 높지 않을까 생각한다.

두 개의 스테머는 scheme로 구현된것이 없다. 하지만 굳이 하자면 C로 구현된 스테머를

scheme용으로 래핑해서 사용하는 방법이 있겠지만 이 부분은 본지의 의도에 벋어나는 분야라

생략하도록 하겠다.

마지막으로 같은 의미를 가진 형태가 다른 단어들의 의미를 확인하기 위해 시소러스 사전이라는

게 필요한데, 이 작업을 함으로써 효과적인 단어 추출작업을 할 수 있다. 이 작업은 사전의 완성

도가 가장 큰 변수로 차지하게 된다.

공개된 시소러스 사전이 없을뿐더러 만들기 힘들기 때문에 이 부분 역시 더 정밀한 작업을 원하

는 독자들을 위해 남겨두겠다.

베이지언 룰 보강하기

이러한 전처리 작업은 이쯤에서 마치고 베이지언 알고리즘의 약점을 보강하기 위해 몇 가지 알고

리즘 보강 작업을 해보겠다.

1. Robinson의 희소단어 확률식 적용

2. Fisher의 역 카이제곱 적용

앞서 베이지언 룰에서는 스팸을 판단하는데 수집된 단어셋을 이용해 전적으로 확률 정보만을 사

용해 확률을 결정했다. 매우 효과적으로 보이고 논리적으로 보이지만 이것에도 약점은 있다.

바로 드물게 나오는 단어들에 대해서 압도적으로 적거나 많은 확률을 부여한다는 것이다.

예를들어 "scheme"이라는 단어가 스팸에서 단 한번 나와서 100%의 스팸 확률을 부여 받을 때

이것은 별다른 설득력이 없다는 것을 독자는 알 수 있을 것이다. 사람이 판단할 때 "scheme"이

라는 단어는 스팸성이 적다고 일반적으로 인식하고 있으니 실제 사람이 판단할 때에는 큰 문제가

없지만 우리가 구현한 베이지언 필터에서는 큰 약점으로 작용한다.

따라서 이런 문제를 해결하기 위해 Robinson은 초기 확률값과 신뢰도(degree of belif)값을 이용

해 드물게 나오는 단어에 대해서 확률 보정을 하게 했다.

)

N : 데이터 셋에서 단어가 나오는 빈도수(스팸, 햄 데이터 셋 모두)

X : 단어에 대한 백그라운드 신뢰도. 0.5가 시작점으로 적당하다.

Page 11: 집단지성(collective intelligence)를 이용한 스팸 문서 필터링

S : 퍼포먼스를 위해 튜닝될 수 있는 고정값 (일반적으로 1)

P(w) : 베이지언 룰 방법으로 나온 확률값

위 식은 N 값에 대해서 임계치를 두고 적용을 하는 방법을 추천한다.

본 강좌에서는 편의상 "N / TotalDocNumber < 0.1" 일 때 적용하도록 하겠다.

;degree of belif calculation by robinson ;f(w) = ((s * x) + (n * p(w))/(s + n) (define (calc-degree-of-belif result)

(let* ((keyword (car result)) (proba (cdr result))

(n (getN keyword)))

(if (> 0.1 (/ n (+ Hamcnt Spamcnt))) ;"(N / TotalDocNumber) < 0.1" 일때

(set! proba (/

(+

(* initial-belif initial-proba)

(* n proba )

)

(+ initial-belif n)

)

)

)

(printf "keyword : ~a :score : ~a.\n" keyword proba)

(cons keyword proba)

)

)

Fisher의 역 카이제곱 방법은 Robinsson이 스팸에 대해서 리서치 하는 과정에서 발견된 방법이

다. 알고리즘의 이름은 Fisher가 개별 확률을 결합하는 과정에서 카이제곱(chi-square)방법을 사

용 한데서 기인하며 불확실성에 대한 민감도를 적용한 결과를 보여주게 된다.

베이지언 룰이 굉장히 익스트림한 값의 결과를 가지고 스팸성을 판단할 수 있었는데, (예 90%

이상만 스팸으로) 카이제곱 분포를 이용해 우연성에 대한 고려를 함으로써 순화된(?) 값들을 던

져 준다. 따라서 이 값을 이용해 심지어는 grey area를 뽑아내는 작업도 가능하게 된다.

식을 가만히 관찰해 보면 0~1 사이의 확률값이 곱해질수록 작아지며 그것의 log값은 음수영역으

로 방향으로 급감한다. 거기에다가 -2를 곱해주면 양수로 엄청나게 큰 값이 나오게 된다.

그런 값이 C-1함수에 입력이 되며 또한 degree-of-freedom 값으로 2N이 인자로 들어가게 되는

데, 이는 단순히 곱에 의해서 확률 결합식 결과가 작아지는 것을 방지하기 위해서이며 이에 따라

단순히 여러 확률값의 곱에 의해서 0으로 수렴한 결과보다 적은 개수의 확률식이 결합해 나오는

결과에 대해서 더 많은 신뢰도를 부여하게 되어 더 납득 가능한 확률 결과값을 도출하게 된다.

또한가지 베이지언과 다른점을 이야기 하자면.

우리가 첫번째 강좌에서 스팸스코어에 따라 정렬한 결과를 가지고 top N개를 추출해 판정에 사용

했지만 여기서는 스코어가 0 ~ 0.1 그리고 0.9 ~ 1 사이의 점수를 가지는 텀을 추출해 알고리즘

을 적용하게 된다.

N : 판단 대상이 되는 단어의 총 개수

F1F2…Fn : 단어 확률들의 곱

H : C-1(-2ln(F1F2…Fn), 2N)

S : C-1(-2ln((1.0 – F1)(1.0 – F2)…(1.0 - Fn)), 2N)

Page 12: 집단지성(collective intelligence)를 이용한 스팸 문서 필터링

그리고 역 카이제곱 함수의 결과로 도출된 값은 본질적으로 스팸에 큰 영향을 미치는 단어들의

확률을 1에 가까운 값으로 계산하는 것이 아니고, 햄에 큰 영향을 미치는 단어들의 확률 결합을

0에 가깝게 만들기 때문에 아래와 같은 식을 가지고 또 다른 척도를 구하는 S를 Robinson은 제

안했다.

위 두 가지 식을 사용해 새로운 척도 I를 구할 수 있게 된다.

I = (1 + H - S)/2

여기서는 I 값의 판정 범위를 아래와 같이 정하겠다.

I >= 0.55 일때 스팸

I <= 0.45 일때 햄

0.45 < I < 0.55 판정불능 (grey area)

여기서 나온 C-1의 역 카이제곱 함수의 수식은 초기 Tim Peter라는 SpamBayes를 만든 사람에

의해 간단한 함수 하나로 구현이 되었고 그 뒤로 많은 언어로 구현이 되었다. 그에 의해 만들어

진 함수를 간단하게 scheme으로 구현해 봤다.

;inverse chi-square function (define (chi2Q chi df)

(let* ((m (/ chi 2.0)) (sum (exp (- m))) (term sum))

(begin

(let loop ((i 1))

(if (< i (/ df 2))

(begin

(set! term (* term (/ m i)))

(set! sum (+ sum term))

(loop (+ i 1))

)

)

)

(cond ((< sum 1.0) sum)

(else 1.0)

)

)

)

)

팁! : 위 함수를 초기 Robinson이 Python으로 구현한 식은 아래 링크에서 찾을 수 있다. http://www.linuxjournal.com/files/linuxjournal.com/linuxjournal/articles/064/6467/6467s2.html

그리고 위 함수를 이용한 I 결합식의 구현은 아래와 같다.

;fisher algorithm using chi2Q ;I = (1 + H - S)/2 (define (fisher-combine-probability topTable)

(let ((H 0) (S 0))

(begin

(set! H (chi2Q

(* -2 (log (accumulate * 1 (map (lambda(x)(cdr x)) topTable))))

Page 13: 집단지성(collective intelligence)를 이용한 스팸 문서 필터링

(* 2 (length topTable)))

)

(set! S (chi2Q

(* -2 (log (accumulate * 1 (map (lambda(x)(- 1.0 (cdr x))) topTable))))

(* 2 (length topTable)))

)

(/ (+ 1 (- H S)) 2)

)

)

)

이제 역카이제곱 방법을 이용해서 스팸, 햄, Grey Area 영역으로 나누어 볼 수 있게 되었고, 스팸

의 정도와 햄의 정도를 좀더 세밀하게 관찰 할 수 있게 되었다. 또한 Grey Area에 걸린 데이터를

분석하거나 사람이 직접 분류하는 작업을 거쳐 다시 스팸 필터로 학습을 시키는 과정을 반복함으

로써 스팸 필터 강화에 큰 도움을 받게 되었다.

아래 함수가 전체적인 흐름을 나타내는 함수인데, 괄호를 따라가서 처음 처리되는 프로시저를 거

꾸로 찾아보자

;input doc and calc spam score

;use fisher inverse-chi-square

(define (doc-spam-score inputDoc)

(fisher-combine-probability

(filter fisher-range? (make-keyword-score-pair-list (parsing-to-list inputDoc)))

)

)

parsing-to-list : 입력된 문서를 단어별로 파싱, 학습시와 마찬가지로 전처리를 수행하여 쓸만한

키워드 리스트를 리턴한다.

make-keyword-score-pair-list : 그리고 기본적인 베이지언 룰과 희소단어에 대해서 스코어를 계

산해 (keyword probability) 패어의 리스트를 리턴한다.

filter fisher-range? : fisher-range?에서 참인 결과를 리턴하는 것들만 리스트로 넘긴다.(위에서 0

~ 0.1 그리고 0.9 ~ 1 사이의 점수를 가지는 텀을 추출해 알고리즘을 적용한다고 언급했다.)

그리고 마지막으로 fisher-combine-probability 프로시저로 대상 확률 값들을 결합해 결합 확률을

도출한다.

이렇게 문서가 입력되었을 때 어떻게 스팸 확률을 도출하는지 확인해 봤다.

대부분 커다란 알고리즘의 얼개는 이와 같다.

그런 도출된 확률값을 기반으로 원하는 수준의 임계값으로 스팸을 판정하면 된다.

그 다음 작업은?

이것으로 이번 칼럼에서 스팸 필터의 정확도 향상을 위해 해야 될 작업들을 몇 개 정리 해 봤다.

그럼 이런 스팸 필터에 대해서 좋아 졌는지 나빠졌는지 알 수 있는 방법들이 필요한데, 이것을

위해서 테스트셋이라는 실험 셋이 필요하다. 물론 어떤 독자는 우리가 가지고 있는 실험셋을 적

용해 보면 되지 않겠느냐 할 수도 있지만 이미 학습기에 들어간 데이터는 앞으로 우리가 맞닥뜨

릴 새로운 스팸들에 대해서 어느 정도 성능이 좋아졌는지 보여주지 못한다. 그래서 테스트 셋을

따로 돌려 나온 결과를 실제 판정결과와 비교해 에러율 같은 척도를 사용, 이전 필터와 비교해

볼 수 있겠다.

테스트셋의 준비가 선행이 되어야 하는 것 말고도 필터의 목적에 맞는 판정 기준을 가지고 성능

을 평가할 수 있는데, 스팸 필터의 경우 햄인 문서를 스팸으로 오판할 때 발생하는 비용이 스팸

을 햄으로 오판할 때 비용보다 더 크기 때문에 필터의 목적에 따라 여러 다른 척도를 적용하기도

Page 14: 집단지성(collective intelligence)를 이용한 스팸 문서 필터링

한다. 그러한 여러가지 평가 방법을 확인하고 싶다면 본인의 블로그

(http://www.freesearch.pe.kr/754)를 참고해 보길 바란다.

마치며

약 2회의 걸친 스팸 문서 필터링 칼럼을 마치면서 되도록 실용적인 내용 위주로 하고 학술적인

내용을 최대한 배제하려고 노력했지만 그 노력의 결과가 잘 나타났는지 모르겠다. 하지만 예제로

만든 스팸 필터 소스는 아마 다른 분들이 기계학습 기반 스팸처리에 대해서 처음 시작할 때 참고

하기 좋은 소스일거라는 건 자신 있게 이야기 할 수 있다.

논문 쓰면서 만든 소스보다 더 간결하고 깔끔하게 소스를 뽑았다고 생각하는데, 이것은 아마도

scheme언어를 쓰면서 최대한 지저분하지 않고 명확하고, 간결하게 소스를 만들어 보려고 노력한

결과인 거 같다. (아마도 scheme언어의 특징을 살리려 많이 노력한 듯 하다.)

베이지언을 응용한 스팸 필터는 굉장히 제너럴한 방법으로 쓰이고 있으며, 최근에는

SVM(support vector machine)이라는 기계학습 알고리즘을 응용한 방법이 유행하고 있다.

하지만 기계학습이라는게 알고리즘도 중요하지만 어떠한 데이터를 입력하느냐가 알고리즘보다 더

중요한 포인트라 생각한다. 잘 분류되고, 세밀하게 전처리된 데이터는 좋은 학습에 필수 불가결

한 조건이자 기계학습을 이용한 어플리케이션 활용에서 가장 어려운 부분이다.

또한 학습기에 어떤 데이터를 넣어야만이 성능이 좋아질지 알아볼 수 있는 데이터를 보는 있는

혜안이 필요한데, 이는 많이 학습시켜보고 수많은 데이터를 테스트해보는 과정을 수없이 해보면

서 늘릴 수 있는 능력이자 이런 학습 기반의 소프트웨어의 유지보수와 성능향상에 가장 필요한

감각이라 생각한다.

ps. 본 강좌에 소개된 소스코드 및 데이터는 본 저자의 블로그(http://www.freesearch.pe.kr/873)

에서 다운 받을 수 있다.

참고 문헌

A Statistical Approach to the Spam Problem : http://www.linuxjournal.com/article/6467

Ending Spam : http://books.google.co.kr/books?id=kqwn8KEKYOwC