이래저래 1시간 정도 걸려서 toy 한글 스펠러를 만들어 봤다. 약 50줄정도 되는 아주 간단한 코드로 만들어 본건데, KoNLP의 두가지 핵심 함수를 사용하고 KoNLP 패키지에 포함된 한나눔 분석기 시스템 사전을 활용했다.
다른 핵심 함수로 Edit Distance 계산을 위한 함수가 있는데, 이것은 직접 구현을 하려다가 R cba 패키지에 너무 구현이 잘 된 함수가 있어서 그것을 사용했다. 이 함수의 아주 큰 특장점은 모든 키 쌍에 대한 입력,삭제, 교체 연산 비용을 matrix로 입력할 수 있다는 것이다.
이렇게 모든 준비가 완료되니 50여줄 만으로 한글 스펠러를 만들 수 있었다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | setwd("c:/work/spellerR/") library(KoNLP) library(cba) dicPath <- paste(system.file(package="KoNLP"), "/dics/data/kE/dic_system.txt",sep="") userdicPath <- paste(system.file(package="KoNLP"), "/dics/data/kE/dic_user.txt",sep="") fp <- file(dicPath, encoding="UTF-8") sdicLine <- readLines(fp) close(fp) fp <- file(userdicPath, encoding="UTF-8") udicLine <- readLines(fp) close(fp) dicLine <- append(sdicLine, udicLine) hcorpus <- sapply(strsplit(dicLine, "\t"),function(x) { paste(convertHangulStringToJamos(x[1]), collapse="")} ) rm(dicLine) rm(udicLine) rm(sdicLine) candidatesRough <- function(word, dic){ wjamo <- paste(convertHangulStringToJamos(word), collapse="") wl <- nchar(wjamo) wf <- substr(wjamo, 1,1) #Huristic filtering candidates <- Filter(function(cand){ if(wl - 1 <= nchar(cand) && nchar(cand) <= wl + 1 && substr(cand,1,1) == wf){ return(TRUE) }else{ return(FALSE) } }, dic) return(candidates) } finalSugg <- function(word, candidates){ jamos <- paste(convertHangulStringToJamos(word), collapse="") editscores <- sdists(jamos, candidates)[1,] topscoreidx <- order(editscores, decreasing=F)[1] if(editscores[topscoreidx] <= 2 && editscores[topscoreidx] > 0){ return(paste("Do you mean? ", HangulAutomata(candidates[topscoreidx]))) } return(word) } HangulSpellingSugg <- function(word, corpus){ #first phase candidates <- candidatesRough(word,corpus) if(length(candidates) == 0) return(word) #second phase return(finalSugg(word, candidates)) } |
초반에 한나눔 분석기 시스템 사전과 사용자 사전을 로딩해서 자모로 변환을 모두 하게 된다. 물론 미리 자모로 변환된 사전을 구비해 놓으면 더 편할 것이다.
그리고 입력된 단어에 대해서 단어 추천 후보가 될만한 것들을 1차적으로 추리게 되고 2차에서는 이들 후보들에 대한 좀더 세밀한 진단을 하기 위해 edit distance를 기반으로 한 연산을 수행한다. 사실 edit distance계산이 O(n * m)의 복잡도를 가지기 때문에 모든 사전 데이터에 대해서 수행하는건 사용성 측면에서 바람직하지 못해서 첫번째 과정에서 1차적인 필터링을 하는것이다.
이를 수행하기 위해서 여러 휴리스틱한 숫자를 사용한다. 첫번째 필터링에서 그리고 두번째 추천에서 편집 거리를 어느 숫자 내의 것을 “do you mean?” 으로 할지 그런 것들에 대한 판단이 필요한데, 이런 부분에서 기계학습 모델링이 필요하다. 그리고 sdists 함수에서 편집 연산의 연산 비용을 정해서 넣어줄 수 있는데 이 역시 데이터를 기반으로 모델링 해야 되는 부분이다.
이것의 수행 결과는 아래와 같다.
1 2 3 4 5 6 7 8 9 10 | > HangulSpellingSugg("고감자", hcorpus) [1] "Do you mean? 고금자" > HangulSpellingSugg("고스돕", hcorpus) [1] "Do you mean? 고스톱" > HangulSpellingSugg("문뻠", hcorpus) [1] "Do you mean? 문범" > HangulSpellingSugg("내이버", hcorpus) [1] "Do you mean? 네이버" > HangulSpellingSugg("디음", hcorpus) [1] "Do you mean? 다음" |
더 많은 사전 단어들 그리고 단어의 빈도 혹은 중요도 정보가 있다면 더 정확한 추천이 가능할 것이고, 사용자들이 틀린 단어의 쌍을 안다면 입력, 삭제, 교체 비용에 대한 아주 합리적인 계산이 가능할 것이다. 한마디로 이 스펠러는 데이터가 많아진다면 점점 똑똑해 진다는 것이다.
특정 단어에 대해서 정확한 추천을 하고 싶다면 그 단어 정보를 사전에 추가하는 것만으로 다양한 케이스의 오타에 대한 추천이 가능해 진다.
Tags: R
I’d been testing with R and Python what the page rank score on each R packages.
Firstly, I need to scrape all package description pages and then parsing section “Depends”, “Imports”, “Reverse Depends” to know relation between packages. I was using Python with scrapemark for convenient.
This is “scrape.py”(no code optimization for easy understanding).
#coding=utf-8
from scrapemark import scrape
from urlparse import urljoin
import urllib2
indexpage = "http://cran.r-project.org/web/packages/available_packages_by_date.html"
def mainpage(url):
ret = scrape("""
<table border="1" summary="Available CRAN packages by date.">
<tr> <th align="left"> Date </th> <th align="left"> Package </th> <th align="left"> Title </th> </tr>
{*
<tr>
<td>{{ [date] }}</td><td><a href=' {{ [pkglinks] }}'> {{ [pkgname] }}</a></td><td>{{ [Title] }}</td>
</tr>
*}
</table>
""",
url=url)
return ret
def getDepRedep(pkglink):
html = urllib2.urlopen(pkglink).read()
imps = scrape("""
<table summary=''>
{*
<tr><td valign=top>Imports:</td>
<td>{* <a >{{ [imps] }}</a> *}</td>
</tr>
*}
</table>
""", html)
deps = scrape("""
<table summary=''>
{*
<tr><td valign=top>Depends:</td>
<td>{* <a>{{ [deps] }}</a> *}</td>
</tr>
*}
</table>
""", html)
revdeps = scrape("""
<h4>Reverse dependencies:</h4>
<table summary="">
{*
<tr><td valign=top>Reverse depends:</td>
<td>{* <a>{{ [revdeps] }}</a> *}</td>
</tr>
*}
</table>
""", html)
return [imps, deps, revdeps]
if __name__ == "__main__":
import sys
pkgdic = mainpage(indexpage)
pkglinks = map(lambda l:urljoin(indexpage, l), pkgdic["pkglinks"])
pkgnames = pkgdic["pkgname"]
pkginfos = dict([(pkglinks[i],pkgnames[i] ) for i in range(0, len(pkgnames) - 1, 1)])
for link, name in pkginfos.items():
ret = getDepRedep(link)
if ret[0] != None:
for imp in ret[0]["imps"]:
sys.stdout.write("%s\t%s\n" % (name, imp))
if ret[1] != None:
for deps in ret[1]["deps"]:
sys.stdout.write("%s\t%s\n" % (name, deps))
if ret[2] != None:
for revdeps in ret[2]["revdeps"]:
sys.stdout.write("%s\t%s\n" % (revdeps, name))
Just execute “python scrape.py > resultfile.txt”. “result.txt” will contain edge list of R cran packages.
This edge list will be easily used by most of SNA package. In my case, igraph.
R code.
library(igraph)
g <- graph.edgelist(
matrix(
scan(file="resultfile.txt",what=character(0), sep="\t"),
ncol=2))
pr <- data.frame(pkg=as.vector(V(g)$name), pkgindex=as.vector(V(g)),pagerank=page.rank(g)$vector)
pr<- pr[order(pr$pagerank, decreasing=T),]
head(pr, n=30)
“head” results will be shown like below.
pkg pkgindex pagerank 105 MASS 104 0.044617865 115 mvtnorm 114 0.014133438 168 Matrix 167 0.011896352 79 lattice 78 0.009421894 604 rJava 603 0.008692808 4 survival 3 0.008073552 595 Hmisc 594 0.006965379 490 nlme 489 0.006819586 256 lme4 255 0.006506075 339 e1071 338 0.006162781 457 XML 456 0.006084811 18 car 17 0.004911839 574 abind 573 0.004501252 809 sandwich 808 0.004372066 464 TSdbi 463 0.004189485 113 multcomp 112 0.004094053 22 cluster 21 0.003680852 350 mgcv 349 0.003574597 505 fields 504 0.003455358 463 zoo 462 0.003405279 196 maptools 195 0.002977273 397 Rcpp 396 0.002874943 721 digest 720 0.002849751 1097 esd4all 1096 0.002843424 847 pairwiseCI 846 0.002814689 1279 foreach 1278 0.002803063 997 KernSmooth 996 0.002791697 61 rgdal 60 0.002784822 748 pls 747 0.002768121 1154 akima 1153 0.002742152
In a R package world, MASS is most valuable package. It means, MASS is used by other valuable packages most frequently.
It will be good to try to plot SNA graph with R packages edge list. But, I expect, too many vertex will consume all spaces. Better plot with SNA components.
Tags: rbloggers
작년 한해는 둘째가 태어났으며, 달라진 생활 패턴과 새로운 직장 그리고 공부간의 균형을 어떻게든 이뤄 보려고 발버둥 쳤던 한해 였던거 같다. 그래서 나온 결론은 “가능하다!” 이다.
그러기 위해서는 선결 조건이 있는데, 바로 건강과 체력이다. 이 두 가지 혹은 ‘건강’ 하나가 완비 되지 못하면 위와 같은 생활 패턴은 유지할 수 조차 없다.
작년 한해 학문적으로는 그동안의 데이터 마이닝, 기계학습의 지식에 통계학을 결합하게 된 게 아주 큰 수확이였던거 같다. 사실 데이터 마이닝, 기계학습 중간 중간 통계학적인 지식을 요하는 부분이 상당히 많이 있었는데, 이들에 대한 이해를 위해 충분한 준비운동을 충분히 한것 같다는 생각을 해본다. 연말이 되면서 그리고 성적이 나오고 공부를 하며 느낀 점은 공부의 열정만 있으면 공부할 수 있는 기회는 얼마든지 있다는 생각이 들었다. 돈이 없어서… 시간이 없어서 공부를 못한다는 사람들을 앞으로 다시 만난다면 그건 변명이라고 아주 확실하게 판단할 수 있을 거 같다는 생각을 해본다.
앞으로 1년, 2012년은 역시 공부의 목적은 실무에서의 활용이며, 데이터를 이해 하기 위한 언어(통계학)를 공부하는데 심혈을 기울일 예정이다. 애 둘에다가 없는 시간에 욕심을 내고 싶은 생각은 없다. 단지 주어지는 이번 과목들을 충실히 마스터할 생각이다.
두 번째 목적은 건강을 위한 좋은 습관 하나를 가질 예정이다. 아마 구정때까지는 이게 무엇이 될지 고민을 해야 될 듯 하다.
세 번째 목적은 1년 정도 미뤄 두었던 한글 스펠체커 관련 새로운 리서치를 해보는 것이다. 작년 이맘때 루씬 스펠체커를 만드는 게 목적이기도 하였고, 실제 대부분의 코드를 만들어 두었지만 루씬 소스코드에 의존하게 되면서 너무 자주 변하는 API 덕분에 문제가 좀 있었다. 그래서 작년 말에 KoNLP라는 R 패키지를 자바 코드와 인터페이싱해서 만들었고 이를 기반으로 실제 리서치 가능하고 실용적인 스펠체커를 구성해 볼 생각이다. 실제 이 패키지 내에 있는 형태소 분석기 사전이 있어서 상당히 간단히 구현을 해볼 수 있을거라는 생각을 해보지만 역시 스펠체커는 데이터가 가장 중요한 요소라서 그 부분에서 데이터를 어떻게 더 구할 수 있을지가 리서치 성공의 요인이 될 듯 하다.
아무튼 올해가 어김없이 찾아왔고, 계획을 세워 보았다. 매년 새해엔 계획을 지킬 수 있을까 없을까 고민했으나 항상 만족하는 연말을 맞아 왔던거 같다. 올해도 그럴꺼라 믿어 의심치 않는다.
Tags: 새해 계획
아직 github에 정박중인 코드이지만 오늘 빡시게 작업해서 한글 오토마타 모듈 넣었다. 자바 코드를 객체지향적으로 만들었는데 덕분에 의외로 R에서 코드가 간단해 졌다.
좀더 테스트 해보고 새해 기념으로 cran에 submit 해야겠다.
이젠 아래와 같은 짓들도 가능해졌다.
> str <- convertHangulStringToJamos("배포 조건의 상세한것에 대해서는 'license()' 또는 'licence()' 라고 입력해주십시오") > str2 <-paste(str, collapse="") > str2 [1] "ㅂㅐㅍㅗ ㅈㅗㄱㅓㄴㅇㅢ ㅅㅏㅇㅅㅔㅎㅏㄴㄱㅓㅅㅇㅔ ㄷㅐㅎㅐㅅㅓㄴㅡㄴ 'license()' ㄸㅗㄴㅡㄴ 'licence()' ㄹㅏㄱㅗ ㅇㅣㅂㄹㅕㄱㅎㅐㅈㅜㅅㅣㅂㅅㅣㅇㅗ" > HangulAutomata(str2) [1] "배포 조건의 상세한것에 대해서는 'license()' 또는 'licence()' 라고 입력해주십시오"
올해 마지막 번역서 질문이 와서 아예 블로그에 정리하고자 한다. 물론 내일 또 올수도 있겠지만 빈도를 볼때 마지막 일듯 하다. ^^;
일단 글과 태그간의 행렬(표3.2)이 계산되어 있고, 사람과 태그간의 행렬이 (표 3.5)에 계산되어 있다. 이들을 가지고 사람과 글간의 유사도 행렬을 계산한게 표3.6이다.
간단히 유사도는 벡터 유사도 방법으로 책에서 진행을 했고, 유사도 행렬을 계산을 간단히 하기 위해 단순히 내적만으로 구할 수 있게끔 정규화까지 시켜놓은 상태이다. 이 상태에서 사람과 글간의 유사도 행렬을 구하기 위한 식은 아래와 같다.
(표 3.5) %*% t(표 3.2)
%*%는 행렬 곱을 의미하고 t는 전치행렬을 의미한다.
이 행렬식의 의미는 사람의 속성을 태그로 표현한게 (표 3.5)이고 (표 3.2)는 글을 태그로 표현했다고 하는 이해가 필요하며, 결과적으로 태그로 표현된 사람과 태그로 표현된 글간의 유사도를 계산하는 식이라고 생각하면 된다. 결국 사람과 글의 직접적인 유사도 비교가 불가능하기 때문에 중간 매개체인 태그를 활용한거라 이해하면 된다.
이를 R코드로 풀어서 보여주면 아래와 같다.
> namebytag <- matrix(data=c(.3536, .7071,.3536,.3536,.3536,0,0,.5773,0,.5773,0,.5773), byrow=T, ncol=6)
> namebytag
[,1] [,2] [,3] [,4] [,5] [,6]
[1,] 0.3536 0.7071 0.3536 0.3536 0.3536 0.0000
[2,] 0.0000 0.5773 0.0000 0.5773 0.0000 0.5773
> articlebytag <- matrix(data=c(.3578, .7156, .5367, .2683, 0, 0, 0,.4682,0,.7491,.4682,0,.0891,.3563,0,.2673,0,.891), byrow=T, ncol=6)
> articlebytag
[,1] [,2] [,3] [,4] [,5] [,6]
[1,] 0.3578 0.7156 0.5367 0.2683 0.0000 0.000
[2,] 0.0000 0.4682 0.0000 0.7491 0.4682 0.000
[3,] 0.0891 0.3563 0.0000 0.2673 0.0000 0.891
> t(articlebytag)
[,1] [,2] [,3]
[1,] 0.3578 0.0000 0.0891
[2,] 0.7156 0.4682 0.3563
[3,] 0.5367 0.0000 0.0000
[4,] 0.2683 0.7491 0.2673
[5,] 0.0000 0.4682 0.0000
[6,] 0.0000 0.0000 0.8910
> tagbyarticle <- t(articlebytag)
> namebytag %*% tagbyarticle
[,1] [,2] [,3]
[1,] 0.9171668 0.7615015 0.3779628
[2,] 0.5680055 0.7027473 0.8743786
namebytag 행렬이 (표 3.5)이며, articlebytag가 (표 3.2)이다.
아래와 같이 (표 3.2)의 전치 행렬을 구하고
tagbyarticle <- t(articlebytag)
행렬곱을 수행할 결과가
namebytag %*% tagbyarticle
(표 3.6)과 같음을 알 수 있다.
Tags: CIIA