StandardAnalyzer in Lucene, and Korean
Lucene에서 Analyzer의 역할은 Field
의 텍스트를 TokenStream
즉, 토큰들의 스트림으로 만들어주는 역할입니다. 이 역할을 하는 abstract 메서드가 바로 Analyzer.tokenStream()
메서드인데, signature는 다음과 같습니다.
public abstract TokenStream tokenStream(String fieldName, Reader reader);
Lucene이 제공하는 Analyzer들에서 이 메서드의 구현은 하나의 Tokenizer
와 다수의 TokenFilter들
을 사용합니다. Tokenizer
는 Field
의 텍스트를 토큰들로 만들어주고, 이 토큰들이 TokenFilter
들에 의해 변형되거나 걸러집니다. StandardAnalyzer.tokenStream()
메서드의 구현은 다음과 같습니다.
public TokenStream tokenStream(String fieldName, Reader reader) {
TokenStream result = new StandardTokenizer(reader);
result = new StandardFilter(result);
result = new LowerCaseFilter(result);
result = new StopFilter(result, stopSet);
return result;}
일단 TokenFilter
들에 대해서 설명하면, StandardFilter
는 영어에서 소유격을 나타내는 apostrophe 또는 apostrophe s를 제거해주거나 acronym에서 period(.)를 제거해주는 역할을 합니다. LowerCaseFilter
는 이름이 의미하듯이 토큰들을 소문자로 만들어주는 TokenFilter
고, StopFilter
는 지정된 stop word들을 토큰에서 걸러내는 역할을 합니다. StandardAnalyzer
가 지정하는 stop word들은 StopAnalyzer.ENGLISH_STOP_WORDS
로, 영어에서의 관사나 전치사, 대명사에 해당하는 단어들입니다.
결국 StandardAnalyzer의 한국어 처리에서 중요한 것은 바로 StandardTokenizer인데, 일종의 parser generator에서 생성된 것으로 보입니다. Lucene 소스에서 lucene-2.0.0/src/java/org/apache/lucene/analysis/standard/StandardTokenizer.jj를 보면, 토큰 정의들을 볼 수 있는데, 한국어와 관련해서 다음과 같은 부분이 있습니다.
// basic word: a sequence of digits & letters
<ALPHANUM: (<LETTER>|<DIGIT>|<KOREAN>)+ ><중략 />
| < CJ: // Chinese, Japanese
[
“u3040”-“u318f”,
“u3300”-“u337f”,
“u3400”-“u3d2d”,
“u4e00”-“u9fff”,
“uf900”-“ufaff”
]
>| < KOREAN: // Korean
[
“uac00”-“ud7af”
]
>
이것을 보면 한국어 문자들을 유니코드 범위로 표현하고, ALPHANUM이라는 토큰이 한국어 문자를 포함하도록 처리하고 있는데, 따라서, StandardAnalyzer를 사용하면, “911사태”, “Bush대통령”과 같은 것들이 하나의 토큰으로 처리됨을 알 수 있습니다. 중국어와 일본어 문자들은 CJ 토큰으로 따로 분리해놓은 반면 KOREAN은 왜 ALPHANUM으로 들어갔는지는 모르겠지만, 궁극적으로는 모든 언어의 문자들이 ALPHANUM에 들어가는 것이 정확하겠죠.
이렇게 해서, Lucene 2.0의 StandardAnalyzer
에서 한국어 문자들로 이루어진 단어들이 제대로 인덱싱된다는 것은 알았지만, 이들을 이용해서 잘 검색할 수 있느냐는 다른 문제입니다. Analyzer.tokenStream()
메서드에서 돌려준 TokenStream
에 담긴 토큰들을 인덱스에 그대로 저장하기 때문에, (이들을 다른 식으로 가공하기 위한 별다른 레이어를 발견하지 못했습니다.) 쿼리의 토큰들이 문서의 토큰들과 정확히 match되는 문서만 검색 결과에 포함됩니다. 즉, “Bush대통령”이라는 토큰으로 인덱스에 들어간다면, “Bush”나 “대통령”, “대통” 등으로는 이 토큰이 들어간 Document
를 검색할 수 없습니다.
이 문제를 해결하기 위해서는, n-gram 분석이나 형태소 분석을 통해 토큰들을 적절하게 분리한 후, 인덱싱해야할 것입니다. lucene-2.0.0/contrib/analyzers/src/java/org/apache/lucene/analysis/cjk/에 있는 CJKAnalyzer
와 CJKTokenizer
는 2문자 단위로 토큰들을 생성하는 극히 단순한 Bigram 분석을 통해 이 문제를 해결하고 있는 것 같습니다. 이 경우의 문제점은 한국어에 존재하는 조사나 용언 어미등이 들어가는 토큰들을 전혀 배제하지 못하는 것인데, 검색의 품질을 상당히 낮출 가능성이 있습니다.
홍태희님은 루씬과 한글이라는 페이지(via 루씬에 대한 한국개발자의 고민들)에서 이 문제를 조사에 해당하는 부분을 토큰에서 잘라내는 TokenFilter
를 도입해서 해결하고 계시긴 하지만, 조사에 해당하는 문자로 끝나는 단어들을 검색하지 못하는 문제가 있습니다.
제대로 된 오픈소스 한국어 Analyzer
의 수요가 많음에도 불구하고 없는 것이 아쉽습니다. 짧은 생각으로는 단순한 휴리스틱으로는 힘들 것 같고, 충분히 많은 데이터를 이용한 분석이 필요할 듯 한데, 아무래도 오픈소스 활동에 전념하기 힘든 개인의 수준에서는 힘들지 않을까 싶군요. 좀 더 조사해 봐야겠습니다.