본문 바로가기

Lucene

[lucene] Highlighter와 Fragmenter

루씬의 sand box 격으로 제공되는 라이브러리중
검색결과와 매칭되는 키워드에 하이라이트를 주기 위한 라이브러리가 있다.

그것이 바로 위의

Highlighter와 Fragmenter이다.

일단 기본적인 사용법은 간단하다.


public class HighlightIt {
  private static final String text = "my fox jump group org next fox spring health care book fox tape java fox fox shop world fox";

  public static void main(String[] args) throws IOException, ParseException {

TermQuery query = new TermQuery(new Term("f", "fox"));

QueryScorer scorer = new QueryScorer(query);

Highlighter highlighter = new Highlighter(scorer);

Fragmenter fragmenter = new SimpleFragmenter(5);
highlighter.setTextFragmenter(fragmenter);

TokenStream tokenStream = new KoreanAnalyzer()
        .tokenStream("f", new StringReader(text));

String result =
        highlighter.getBestFragments(tokenStream, text,2, "...");

}
}

여기서 내가 얘기하고 싶은 것은 위 파란부분이다.

무엇을 하기 위한 부분일까..

일단 저 상태로 돌려보면 결과는 아래와 같이 나온다.
result :  <b>fox</b>... <b>fox</b>

만약 아래의
highlighter.getBestFragments(tokenStream, text,5, "...");

이렇게 5로 변경하면 결과는 이렇게 나온다.
result :  <b>fox</b>... <b>fox</b>... <b>fox</b>... <b>fox</b> <b>fox</b>

그러면 위 SimpleFragmenter의 파라메터를 10으로 바꿔보자.
Fragmenter fragmenter = new SimpleFragmenter(10);

결과는 이렇다.
result :   my <b>fox</b>... <b>fox</b> spring... book <b>fox</b>... <b>fox</b> <b>fox</b>... <b>fox</b>

루씬의 API를 찾아보면
public SimpleFragmenter(int fragmentSize)
fragmenterSize - size in bytes of each fragment

라고 되어있다.

즉, 원문을 들어온 사이즈의 바이트만큼 자른다는 이야기인데...

결국,
Fragmenter fragmenter = new SimpleFragmenter(5);

highlighter.getBestFragments(tokenStream, text,2, "...");

의 뜻은 ..

원문을 일단 5바이트씩 조각을 내고
그 조각들 중에서 우선순위가 높은 조각 2개를 하이라이트하고 그 두 조각의 길이가
10바이트를 넘어갈 경우.. "..."으로 이어서 보여준다. 라는...
그렇게밖에 이해가 되지 않는다.

사실 10바이트씩 잘라낸다고 하는데 어떻게 저런식으로 나오는지 정확히 이해가 가지는
않는다. 한글을 적용해봐도 그렇고...

그래도 이것저것 테스트해본 결과를 저렇다.

highlighter.getBestFragments(tokenStream, text,2, "...");
에서

"2"의 의미는 키워드 2개까지만 하이라이트를 하겠다는 뜻이 아니라 위에서 조각낸
10바이트짜리의 조각들중 해당 키워드가 존재하는 2개의 조각에서 하이라이트하여
보여주겠다는 뜻..

만약 byte사이즈를 100으로 잡고
아래에서 getBestFragments(tokenStream,text,1,"...")
으로 하면

결과는
result : my <b>fox</b> jump group org next <b>fox</b> spring health care book <b>fox</b> tape java <b>fox</b> <b>fox</b> shop world <b>fox</b>

이렇게 나온다.
즉 100바이트로 끊었더니, 본문 전체가 하나의 조각이 되어버린 것이고
조각이 하나뿐이니 "fox"라는 단어에 전부 하이라이트가 되어버린 것이다.

위처럼 본문전체가 하나의 조각으로 잘린 경우에는
getBestFragments(tokenStream,text,1,"...")
에서 "1"을 "100"으로 바꿔도
결과는 변함이 없다.


그런데 이해가 안가는건..
SimpleFragmenter(1)로 주고
아래 getBestFragments(tokenStream, text, 100, "...")
으로 해도 결과가 바로 위랑 똑같이 나온다는 것이다.. ㅠㅠ

바이트로 자른다는 저 조각이 도대체 어떻게 잘리는 건지 .... 이해가 잘 가지 않는다..


......

소스를 조금 파보았다..
엄청 난해하다 -_-

일단 컨셉은 바이트 단위로 자른다기 보다는 토큰단위로 생각이 된다.

위 본문을 지정된 Analyzer로 분석하여 나온 토큰을 가지고
작업을 하는데, 새로운 조각이냐 아니냐의 판단이

token.endOffset() >= fragmentSize*currentNumFrags 이다.

currentNumFrags는 Highlighter 클래스에서 isNewFragment를 호출 할 때 마다
+1 씩 증가하고 fragmentSize가 우리가 지정한 값이 되는데

위 같은 경우 토큰이 분석된 결과는
1: [my:0->2:WORD] 2: [fox:3->6:WORD] 3: [jump:7->11:WORD]

이렇게 되고, fragmentSize가 1이라고 가정 할 경우

내부적으로 [my]를 tokenGroup에 넣어두고 [fox]토큰을 호출해놓은 상태에서

일단 [my] 요놈을 하이라이트 시킬건지 아닌지 판단 하여 저장해 두고

위 [fox] 토큰으로 isNewFragment(token)을 호출하게 되는 것이다.

이렇게 되면, currentNumFrags는 일단 1이고, fragmentSize가 1, 그리고

[fox]의 endOffSet은 6이기 때문에 [fox]는 새로운 조각으로 인식이 되는 것이다.

그리고 다시 currentNumFrags는 2가 된다.

만약 fragmentSize를 10으로 주었다면 [fox]까지는 하나의 조각으로 묶일 것이고, 그래서 두개의 조각이 생기게 될 것이다.
[my <b>fox</b>]와 [jump] 이렇게...

그렇다면 위에 내가 모르겠다고 했던

SimpleFragmenter(1)로 주고
아래 getBestFragments(tokenStream, text, 100, "...")

이부분은 뭘까....

여기까지 정확히 들여다보지 않았지만, 내부적으로 위 처럼 나온 각각의 조각들

[my <b>fox</b>]와 [jump] 이런것들을 하나의 큐에 집어넣는데, 이 큐의 사이즈가

최초에 getBestFragments(tokenStream, text, 100, "...")의 3번째 인자 크기로

생성이 된다.

즉, 나중에 다시 한번 살펴봐야겠지만

결과가 같긴 한데..

[my] [<b>fox</b>] [jump] 이것이

하나의 큐에 주루룩 들어가 있다가 한번에 나오는 바람에

결과가 같아 보이는게 아닐까 싶다..


"..." 이걸로 연결되는 경우는 아직 분석해 보지 못 했다..

비범한 사람이 짜놓은 소스를 평범한 내가 따라가려니 가랭이가 찢어진다.

저것도 제대로 따라갔는지 모르겠고...


더 이상 따라가기도 싫다. 그냥 저정도 이해하고 쓰고싶다...

이놈의 성격 진짜....ㅠㅠ