R Hangul toy speller

이래저래 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?  다음"

더 많은 사전 단어들 그리고 단어의 빈도 혹은 중요도 정보가 있다면 더 정확한 추천이 가능할 것이고, 사용자들이 틀린 단어의 쌍을 안다면 입력, 삭제, 교체 비용에 대한 아주 합리적인 계산이 가능할 것이다. 한마디로 이 스펠러는 데이터가 많아진다면 점점 똑똑해 진다는 것이다.

특정 단어에 대해서 정확한 추천을 하고 싶다면 그 단어 정보를 사전에 추가하는 것만으로 다양한 케이스의 오타에 대한 추천이 가능해 진다.  

CC BY-NC 4.0 R Hangul toy speller by from __future__ import dream is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.