본문 바로가기

Lucene

Lucene CustomScoreQuerySample #1

우선 아래의 소스는
http://blog.mono-koubou.net/wp-content/uploads/2008/01/valuesourcequerysample.txt
이 소스를 참고하여 좀 더 쉬운 내용으로 재구성 했음을 알려드립니다.

짐승님을 통해 알게 된 CustomScoreQuery. 이게 보면 볼 수록 매력적이다.

내일 공휴일이기도 하고..
이런거 한번 삘 받았을 때 좀 파보고 싶어서 위 링크의 소스를 보고
간단하게 이것 저것 테스트를 해보기 위한 소스를 만들어 보았다.

2개의 필드를 가진 데이터를 RamDirectory에 색인 해 놓고
검색 테스트를 하면서 score가 어떻게 변하는지 보았다.

위 예제에서는 ValueSource등을 거리 계산 값으로 계산하기 위한 Inner Class들이 몇 개 나오지만
일단 여기서는 필드에 있는 숫자로 정렬하기 위해서 기본적인 API만을 사용해 보았다.




메서드별로 하나씩 살펴 보겠습니다.

main 메서드에서는 샘플 데이터를 RamDirectory를 사용해 인덱싱 하고 "야구"라는 검색어로 검색을 수행 합니다.


샘플 데이터입니다.
[0][i]의 값은 주제라고 가정하고, [i][1]의 값은 해당 주제에 대한 조회수라고 가정하겠습니다.
즉, main 메서드에서 "야구"라고 검색을 할 경우 주제가 "야구"인 것들이 검색이 되는데 이것을 조회수 많은 순으로
검색을 하려고 하는 것 입니다.



makeIndex() 메서드에서는위 샘플 데이터를 value와 count라는 필드로 색인을 합니다.
딱히 특별한 것은 없습니다.


검색어를 받아서 search 메서드를 통해 검색을 수행합니다.


핵심 메서드인데요... 여기서 이것저것 바꿔보고 테스트를 해보려고 합니다.

일단, 위 소스를 그대로 실행하면 아래와 같은 결과가 나옵니다.

1482.12 : 야구 / 981
1371.8297 : 야구 / 908
787.14014 : 야구 / 521
732.7504 : 야구 / 485
420.00952 : 야구 / 278
특별히 정렬에 대한 로직이 없어도 원하던대로 count의 역순으로 정렬되어 나오게 됩니다.



이 3개 문장을 잘 봐주세요...
CustomScoreQuery를 사용해서 IndexSearcher를 통해 검색을 하게 되면, 검색되는 scoresDoc들의 score가 CustomScoreQuery의 customScore에 의해서 계산되고, 이 메서드는
이렇게 되어 있습니다.

이때, doc은 검색 된 document의 id이고, subQueryScore는 루씬에서 원래 검색 쿼리 (위 예제에서는 value:야구)에 의한
TF/IDF 점수입니다.
그리고, valSrcScore가 바로 ValueSourceQuery에서 source로 지정 된 FloatFieldSource의 값입니다.

위의 경우에는 각각의 데이터에 대해서 count 필드의 값인 981,908,521 등의 값이 됩니다.
기본적인 메서드의 구현이 subQueryScore * valSrcScore로 되어 있기 때문에 위 결과에서 score가 1482점 , 1371점으로
나오고 있는 것 입니다.

그럼 한번 coustomScore 메서드를 오버라이드 해보겠습니다.

이제 customScore 메서드에서는 기본 쿼리에 의한 정확도 점수(TF/IDF)와 FloatFieldSource의 count 필드 값을
곱한 결과 값과 각각의 subQueryScore와 valSrcScore 값을 찍어내고 있습니다.
그리고 실제 리턴 값은 count 필드의 값을 리턴하고 있습니다.

이렇게 수정해서 실행 한 결과는 아래와 같습니다.

doc:0 / subQueryScore:1.5108256 / valSrcScore:981.0 / result:1482.12
doc:2 / subQueryScore:1.5108256 / valSrcScore:521.0 / result:787.14014
doc:4 / subQueryScore:1.5108256 / valSrcScore:908.0 / result:1371.8297
doc:7 / subQueryScore:1.5108256 / valSrcScore:278.0 / result:420.00952
doc:8 / subQueryScore:1.5108256 / valSrcScore:485.0 / result:732.7504

981.0 : 야구 / 981
908.0 : 야구 / 908
521.0 : 야구 / 521
485.0 : 야구 / 485
278.0 : 야구 / 278

보면 각 검색 결과 된 document의 score는 count 필드의 값으로 대체 되었으며, 이 값 자체가 점수가 되어 결국 count 필드의 값
역순으로 정렬되어 나오게 됩니다. 결과에서 result부분을 보면, 처음에 실행한 결과의 score와 같은 것을 확인 할 수 있습니다.

subQueryScore의 점수는 모두 동일한데 , value 필드에 "야구"라는 단어 하나만 들어가 있기 때문에
TF/IDF에 의한 점수는 같을 수 밖에 없습니다.

여기서 몇가지 더 보도록 하겠습니다.

일단, FloatFieldSource인데요.. 이건 Type별로 ByteField.., Int등의 클래스가 더 존재하는데
필드의 값 크기에 따라서 골라서 사용하시면 됩니다. 이 클래스의 사용은 데이타 byte 크기 * maxDoc bytes 만큼의 ram이 필요하기 때문에 크기별로 클래스를 나누어 놓은 것 같습니다.

그리고,
이 구문은
으로 변경 될 수 있습니다.
Type에 따라서 내부적으로 FieldSource 클래스를 취사선택 하도록 되어있습니다.

마지막으로 또 한가지는
CustomScoreQuery 에는 setStrict라는 메서드가 있습니다.

기본적으로 이 값은 false로 되어 있는데 이는
FieldSource의  값에 대해 쿼리 정규화를 적용하느냐 안 하느냐 입니다.
false일 경우 정규화를 하고 아닐 경우는 그 값을 그대로 사용합니다.

위 같은 경우 count 필드의 값이 그대로 점수로 노출 되는 이유는
query.setStrict(true);
이 구문 때문입니다.

이 구문을 주석처리 하면 결과는

doc:0 / subQueryScore:1.2598537 / valSrcScore:541.4526 / result:682.15106
doc:2 / subQueryScore:1.2598537 / valSrcScore:287.56046 / result:362.28412
doc:4 / subQueryScore:1.2598537 / valSrcScore:501.161 / result:631.3896
doc:7 / subQueryScore:1.2598537 / valSrcScore:153.43916 / result:193.3109
doc:8 / subQueryScore:1.2598537 / valSrcScore:267.6906 / result:337.251

541.4526 : 야구 / 981
501.161 : 야구 / 908
287.56046 : 야구 / 521
267.6906 : 야구 / 485
153.43916 : 야구 / 278

이렇게 바뀝니다.

내부적으로 어떤 계산식에 의해서 정규화가 진행 되는 것을 알 수 있습니다...

또 한가지..

search 메서드 제일 하단에 주석으로 막혀있는
이 부분을 주석으로 풀어주면

541.4526 : 야구 / 981
682.15106 = (MATCH) custom(value:야구, float(count)), product of:
  682.15106 = custom score: product of:
    1.2598537 = (MATCH) weight(value:야구 in 0), product of:
      0.83388424 = queryWeight(value:야구), product of:
        1.5108256 = idf(docFreq=5, maxDocs=10)
        0.5519394 = queryNorm
      1.5108256 = (MATCH) fieldWeight(value:야구 in 0), product of:
        1.0 = tf(termFreq(value:야구)=1)
        1.5108256 = idf(docFreq=5, maxDocs=10)
        1.0 = fieldNorm(field=value, doc=0)
    541.4526 = (MATCH) float(count), product of:
      981.0 = float(count)=981.0
      1.0 = boost
      0.5519394 = queryNorm
  1.0 = queryBoost
이런식으로 하나의 검색 결과에 대한 점수 계산을 보실 수 있습니다.

위 bold 처리 한 부분의 queryNorm 부분을 보시면 0.5519394 라고 되어 있는데 바로 쿼리 정규화가 적용된 것 입니다.
이 부분은 CustomScoreQuery.setStrict 메서드를 통해 true로 바꿔주면
아래와 같이 변경 됩니다.

981.0 : 야구 / 981
1482.12 = (MATCH) custom(value:야구, float(count)) STRICT, product of:
  1482.12 = custom score: product of:
    1.5108256 = (MATCH) fieldWeight(value:야구 in 0), product of:
      1.0 = tf(termFreq(value:야구)=1)
      1.5108256 = idf(docFreq=5, maxDocs=10)
      1.0 = fieldNorm(field=value, doc=0)
    981.0 = (MATCH) float(count), product of:
      981.0 = float(count)=981.0
      1.0 = boost
      1.0 = queryNorm
  1.0 = queryBoost

더 자세한 것은 더 공부해봐야 겠지만..
제일 위 link 걸어놓은 예제를 보면 예전에 termVector를 사용해서 계산 하던 것을
간단하게 거리 공식을 계산해서 만들어 놓은 것을 보실 수 있습니다.

이 다음에는 어떤 것을 가지고 실험을 해볼까.. 또 고민해봐야겠습니다. ㅎㅎ