앞에서 AttributeSource에 Attribute들 (term, offset, position, type 등) set 해주면
그 뒤에 오는 Filter나 Tokenizer가 해당 Attribute를 사용 할 수 있다고 했습니다.
각각의 Filter와 Tokenizer는 모두 TokenStream을 상속하고 있고 이 TokenStream은
AttributeSource를 상속받고 있습니다.

이 Filter와 Tokenizer가 서로 데코레이터 패턴으로 물려있기 때문에
앞에서 set한 결과를 뒤에 따라오는 Filter와 Tokenizer가 사용 할 수 있는 것 입니다.
(Java의 stream 관련 클래스들을 생각하시면 됩니다.)

소스를 보면
보통 맨 앞단에서 들어오는 String을 잘라내는 역할을 하는 Tokenizer는 아래와 같이
사용 됩니다.


보시면 생성자에서
TermAttribute등을 addAttribute 메서드를 사용해서 등록하고 있습니다
이 메서드는 attribute가 있으면 있는 attribute를 리턴하고, 없으면 새롭게 attribute를 AttributeSource에 set해주는
역할을 합니다.
그럼 그냥 setAttribute를 해주면 되지 왜 저런 역할 (있으면 return, 없으면 add)을 하는 메서드가 필요할까요?
루씬에서 사용되는 필터나 Tokenizer는 사실 맨 앞이나 맨 뒤나 아니며 중간이던 어느 위치에서도
사용 될 수 있습니다. 때문에 저런 메서드가 필요한 것입니다.
하나의 필터가 맨 앞에서 사용 될거라고 해서 setAttribute 메서드를 사용해서 구현을 했는데 다른 Analyzer에서
이 필터가 3번째 4번째에서 사용될 수도 있기 때문입니다.

그리고 boolean incrementToken 메서드를 보시면 결과 값을 attribute에 set해주고 있는 것을 보실 수 있습니다.

그러면 이 결과를 받아서 사용하는
다른 Filter를 한번 보겠습니다. 지금까지 Analyzer를 구현하면서 본 Filter들은 크게 두개의 패턴을 가지고 있었습니다.

하나는 Tokenizer 처럼 하나의 결과를 받아서 그 결과에 직접 작업을 하는 필터입니다.하나의 Term을 만들거나 받아서
하나의 Term을 리턴하는 경우죠. 자세한 방법은 나중에 Analyzer에 대해서 얘기 할 때 자세하게 작성하겠습니다.
이 경우 따로 결과를 저장해 놓을 필요 없이 그냥 바로 "결과 Attribute를 넣어주면 됩니다.

또 하나는 동의어 추출이나 복합명사 추출 처럼 하나의 결과를 받아서 그 결과로부터 다른 추가적인 term을 추출해내는
필터입니다.
"노트북" 이라는 term이 들어오면, 이 노트북이라는 단어와 동의어로 설정 된 notebook,노트PC등의 term을 추가로 리턴해주는
필터입니다. 보통, List나 Stack에 결과를 담아두고 값을 하나하나 리턴하게 됩니다.
모양새는 서로 비슷합니다.

incrementToken 메서드는 이전 버전에서의 next 메서드와 같은 역할을 합니다.

그런데, 이 메서드의 return type이 boolean입니다. 그럼 어떻게 후속 필터들이 이 결과를 받을 수 있을까요?

첫번째 경우는 앞어 있는 Tokenizer 소스를 보시면 되고
두번째 경우인 추가적인 term를 만들어내는 필터를 보겠습니다.
Stemming 필터로써 어미 사전을 사용해 앞 필터에서 만들어낸 Term에서 어미를 제거하여 리턴하는 것입니다.
그런데 왜 stack을 사용하냐면 원본 Term도 리턴을 해줘야 하기 때문입니다.
이것도 나중에 Analyzer에 대해서 작성 할 때 상세히 알아보겠습니다.

아무튼 아래 stemming 필터를 보겠습니다.

생성자에서 Attribute3개를 addAttribute 해주고 있습니다.
만약, 앞선 Filter나 Tokenizer가 있다면 이 3개의 Attribute에는 앞선 Filter와 Tokenizer의 결과가
들어 올 것입니다.

그리고, AttributeSource save라고 선언된 것이 있는데 현재 input (즉, 앞선 Filter와 Tokenizer)를 cloneAttributes 메서드를 사용해서 Attribute들을 복사하고 있습니다. (사실은 AttributeSource 한벌이 더 생기는 거겠죠..)
왜 이게 필요할까요..

boolean incrementToken() 메서드를 보면

이렇게 되어 있는데요..
우선 #4를 보시면 앞선 필터에서의 결과가 없으면 역시 false를 리턴해서 결과 없음을 표시합니다.
#5 는 term을 받아서 그것을 stem 메서드를 사용해 어미를 잘라내는 부분입니다.
#6 #5에서의 결과를 가지고 어미가 잘려나갔으면
#7 현재의 State를 capture하고 있는데요 , 이 부분이 중요합니다. AttributeSource는 state라는 것을 갖습니다.
그리고 이 State는 captureState 메서드를 통해 snapshot을 저장해 둘 수도 있고
restore메서드를 사용해 다시 되살릴 수도 있습니다. 왜 이 부분이 필요하냐면...
앞선 필터에서 받은 Term이 "사랑해서"라고 가정했을 때 이 Stem 필터에서는 "사랑" 이라고 어미를 잘라낸 후
AttributeSource에 해당 Attribute를 set 할 것 입니다. (#9 번)

그렇게 되면 앞선 필터로 받았던 "사랑해서"에 대한 Attribute가 모두 사라지게 됩니다.

나중에 이 "사랑해서"라는 Term을 가지고 작업을 하려면 다시 이 것을 retore 메서드를 사용해서 처음으로 돌려놓아야 하는데
이를 위해서 앞에서 AttributeSource를 clone해서 save라는 한벌을 더 가지고 있었고, 이 save AttributeSource의 State를
Attribute가 변하기 전에 capture해 놓는 것입니다.

#10을 보면
이전 버전에서는 결과 Token을 Stack에 넣어두고 Token을 리턴했지면
여기서는 해당 Attribute들이 저장되어 있는 AttributeSource의 State를 Stack에 넣어놓습니다.
이 AttributeSource로부터 stem된 결과 Attribute들을 받아낼 수 있기 때문입니다.

실제로 #1 에서 stack의 사이즈가 0 보다 크면
#2에서 state를 받아내고
#3에서 그 State를 restore한 후 true를 리턴해주고 있습니다.

어차피 다음 필터에서도
생성자에서 addAttribute 메서드를 사용해서 Attriute를 받아내고 있기 때문에
이렇게 restoreState메서드를 사용하면 이 Stem 필터에서 결과로 뽑아낸 Attribute들을
AttributeSource로부터 받아 쓸 수 있게 됩니다.
저작자 표시 비영리 변경 금지
신고
Posted by 용식
루씬이 버전업 되면서 바뀐 부분 중 하나가 이 부분인 것 같습니다.
이전에는 Tokenizer나 TokenFilter 클래스를 만들 때 TokenStream 클래스를 상속받아 만들고
이 TokenStream의 next 메서드를 사용해서 얻어지는 Token을 가지고
데코레이터 패턴으로 적용 된 Filter들이 Token을 처리하는 방식이었습니다.

예를 들어서,

Analyzer의 tokenStream 메서드가 위와 같이 정의되어 있다고 하고
분석해야 할 문장이 "검색엔진 개발자" 라고 했을 때
추출 되는 Token을 얻어내기 위해서 next 메서드를 실행했었습니다. 즉,


대략 위와 같은 방식이었죠.
next 메서드가 실행 되면 제일 상위의 Tokenizer에서 "검색엔진,(0,4)" 라는 Token을 만들어서 리턴하고
그것을 받아서 다음 Filter의 next 메서드에서 Token에서 원하는 작업을 하고
(뭐 검색엔진을 검색+엔진으로 분리하여 리턴한다던가..)
그 Token을 또 다음 Filter가 받아서 작업하는 방식이었습니다.

그런데 이번에 Analyzer를 lucene 3.0 버전으로 바꾸다보니
앞선 포스트에 잠깐 언급했던 것 처럼 이 가장 핵심이라고 할 수 있는
Token next() 메서드가 없어졌습니다. 그리고, boolean incrementToken() 이라는 메서드가 생겼습니다.
게다가 리턴타입이 boolean 입니다.

필터가 앞선 필터(즉, TokenStream)으로 부터 추출되는 Token을 받아야 자기 작업을 할텐데
리턴 타입이 boolean이니 이걸 어떻게 하라는건가 싶었습니다.

3.0 버전에서는 예전의 next() 메서드를 대신 전부 incrementToken() 메서드로 대체가 되었습니다.

그럼 3.0 버전에서는 어떻게 필터와 Tokenizer가 동작할까요..

이전버전에 있던 (3.0에도 있지만) Token 클래스는 문장을 나타내는 term과 위치정보를 나타내는 start/end offset
그리고 위치 이동에 대한 정보인 positionIncremental 또 Term의 타입을 지정 할 수 있는 Type을 그 속성으로 가지고 있었습니다.

이 속성들이 Attribute라는 클래스를 상속한 속성 클래스들로 전부 빠져나왔습니다.
즉,

이런식으로 ...
그래서, 이전에 Token에 넣어주던 정보들을 저 속성들에 넣어주게 됩니다.
그러면, 어떻게 각 필터와 Tokenizer가 저 값들을 공유하여 자신들의 작업 (동의어추출,stemming,복합명사 추출등)을
할 수 있는 것일까요..?

Lucene 3.0 (혹은 그 이전버전 부터인지..)에서는 TokenStream 클래스가 AttributeSource라는 클래스를 상속받고 있습니다.
AttributeSource는 내부적으로 Attribute들을 가지고 있고요.. 즉, 위에 나열된 저런 속성들을 가지고 있습니다.
결국 모든 필터들이 TokenStream을 상속받고 있기 때문에
저 AttributeSource를 사용해서 원하는 값들을 공유 할 수 있게 됩니다.

조금 자세하게 살펴보면
일반적으로 가장 앞단에서 Term을 잘라내는 Tokenizer를 살펴보면
생성자에서 아래와 같은 일을 하도록 정의 되어 있습니다.


addAttribute 메서드는 AttributeSource 클래스에 정의되어있는 메서드로
Attribute Class를 자기 속성에 add하고, 만약에 이미 있는 Attribute라면 그 Attribute를 return 하는 메서드입니다.
Tokenizer가 가장 앞단에서 실행 되므르 위 문장에 의해서 TermAttribute와 OffsetAttribute, TypeAttribute가 AttributeSource에
추가 될 것입니다.

그리고, 해당 Tokenizer가 incrementToken()메서드에서
읽어들인 문장에서 Term을 잘라내고나서는

해당 속성값을 Attribute에 넣어주고 true를 리턴하게 됩니다.

그렇게 되면 다음 필터에서는 저 AttributeSource의 Attribute들을 얻어 낼 수 있다면
앞에서 동작하는 Filter나 Tokenizer의 결과 값을 사용 할 수 있게 되는 것 입니다.

그럼 어떻게 얻어낼까요?

바로 addAttribute 혹은 getAttribute 메서드를 통해 얻어낼 수 있습니다.

 - 계속 -
저작자 표시 비영리 변경 금지
신고
Posted by 용식
예전에 만들었던 한글 명사 추출 Analyzer를 공개하려고 하다가
그 Analyzer의 core 버전이 2.4.0 base여서 이것을 최신 루씬 버전인
3.0으로 컨버팅 하려고 했는데... 바뀐게 한두가지가 아니라서.. 일단 바뀐 부분부터 공부를
해야겠다 싶었다. -_-;

제일 당황스러운 부분이...
TokenStream.next(Token token) 메서드가 사라진 것이었다. -_-;

이 부분이 TokenStream.incrementToken() 메서드를 대체가 되었는데 이건 조만간 다시 포스팅 하기로 하고..
일단, 기존에 사용되던 Token이란 클래스가 남아있기는 한데..
대부분 Attribute 클래스에 기초한 놈들로 변경이 된 것 같다.

예전에 하나의 String에서 Analyzer로 분석 된 Term 리스트를 얻으려는 소스는
아래와 같았다.


TokenStream의 next 메서드로 다음 Analyzer로 분석 된 Token들을 하나씩 얻어내고
거기서 Token의 term을 사용해서 단어를 얻거나 offset등의 데이터들을 모두 얻어낼 수 있었는데
이 next() 메서드가 사라진 것이다. -_-;

그 대신 Attribute 클래스를 상속한 TermAttribute 혹은 OffsetAttribute등의 클래스를 이용해서 위의 작업을 대신 할 수 있다.
다른 Attribute도 많이 존재한다.
결국, 예전에 Token 클래스가 가지고 있던 속성들이 각각의 클래스로 분리 되서 나온 것이라고 보면 정확 할 것 같다.


TokenStream으로부터 Attribute인 TermAttribute와 OffsetAttribute를 얻어내고 이것을 통해서 단어와 위치 정보를
보여주고 있다.
그리고, incrementToken 메서드를 사용해서 다음 Term으로의 접근을 하고 있다.

결국, 이전 버전의 Tokenizer나 TokenFilter에서 next메서드가 하는 일을
incrementToken 메서드가 대신 하고 있는 것이다. 다만, 리턴을 해주는 것이 아니라
내부적으로 Attribute에 set을 하고 있는 것.. 그리고 이것을 그냥 가져다 쓰는 방식.

이게 왜 이렇게 바뀌었나 살펴보면.. TokenStream 메서드 중에
현재 상태를 capture하는 등의 메서드가 추가 된 것이 보이는데 이것을 위해서
Attribute들이 전부 멤버로 들어갔나 싶다.. 아직 자세히 못 들여다봐서..

결국, 내가 만들었던 Analyzer의 Filter들과 모듈을 거의 전부
뜯어 고쳐야 한다는 얘기가 나온다.. 어휴.... -.-

그외 다른 것도 모르는 것 천지.. 하나하나 이것저것 해보는 수 밖에.. ㄷㄷ;;

이전에 사용되던 Token 클래스도
TokenStream.addAttributeImpl 메서드를 통해서 사용 할 수 있는 것 같기는 한데
이건 구현되어있는 Tokenizer에 따라서 다른 것 같다.

그 Tokenizer가ㅏ AttributeImpl에도 값을 set 해주면 값이 들어갈 것이고..
아니면 안 들어갈 것이고..

참고로 가장 많이 사용되는 (루씬 analyzer에서.)
charTokenizer는
incrementToken메서드가 아래와 같이 구현 되어 있다.


이전 버전과 비교해보면 하는 일은 거의 같다.
다만 메서드 이름이 바뀌고 Token에 추출 된 결과를 넣어 주는 것이 아니라
TermAttribute와 OffsetAttribute에 넣어주는 것...

그러면, 이 Tokenizer가 도대체 어떤 Attribute와 AttributeImpl을 가지고 있는지 알아야 할 텐데..
보니까 TokenStream에 두 클래스에 대한 Iterator를 얻을 수 있는 메서드가 있다.
그걸 사용하면 될 듯..
저작자 표시 비영리 변경 금지
신고
Posted by 용식
우선 이 포스트에 나오는 sample 소스는
http://blog.mono-koubou.net/wp-content/uploads/2008/01/valuesourcequerysample.txt
의 소스를 살짝 수정한 소스 입니다.

앞선 포스트에서는 루씬의 CustomScoreQuery 기초적인 사용법을 보았습니다.

이번에는 이 CustomeScoreQuery를 이용해서 조금 응용을 해보겠습니다.

아래 이미지를 봐주세요.



바로 집을 기준으로 거리가 5이내에 있는 스시 집을 찾아보도록 하겠습니다.
일단, 위와 같은 결과를 얻으려면 일단 스시인 집들을 찾고 그 안에서 각 좌표를 통해 거리를 계산해야 할 것 입니다.
거리를 구하는 공식은.. 피타고라스의 정리를 이용하면 아래와 같이 구해집니다.
(x'-x)^2 + (y' - y)^2 = 거리^2
에서 거리 = sqrt((x'-x)^2 + (y' - y)^2) 가 됩니다.

이것을 이용해 보겠습니다.

일단 코드입니다.


DistanceQuerySample.java
하나하나 살펴보겠습니다. 일단, main 메서드 입니다.


여기서는 "스시"집 중에서 집(5,5)를 기준으로 거리 5이내에 있는 스시집을 찾고자 하는 질의 입니다.
색인 데이터는

이렇게 각 가게의 이름과 좌표를 주고 있습니다.

색인하는 메서드는 이전 포스트에 있으니까 넘어가고 바로
search메서드를 살펴보겠습니다.


좌표와 거리를 받아서 쿼리 구문을 만듭니다.


이 쿼리에 의해서 "스시"집들이 검색이 될 것 입니다.
CustomQuery는 앞에서 말씀드렸지만 기본 질의 쿼리와 valueSource 쿼리를 사용해서 점수를 계산합니다.
기본질의 쿼리의 점수와 valueSourceQuery의 점수를 마음대로 사용 할 수 있지요.

그런데, 이상한 클래스가 하나 있습니다.
DistanceFunction 이라는 클래스입니다.
이 클래스는 아래와 같은 모양을 갖습니다.

보시면 아시겠지만 이 클래스는 FloatFieldSource를 상속받고 있습니다. 앞에서, CustomScoreQuery를 사용 할 때
FloatFieldSource를 사용해서 ValueSourceQuery를 만들었던 것 기억이 나시나요?



위 구문으로 ValueSourceQuery가 던져주는 score는 count필드의 값 그 자체가 되었었습니다.
현재 우리는 특정 필드의 값 자체가 필요한 것이 아니고
그 필드의 값 (좌표)를 통해 거리를 계산해야 하기 때문에 그것을 계산하기 위한 DistanceFunction 클래스를 만들었고
이것을 이용해 ValueSourceQuery를 만들어야하기 때문에 FloatFieldSource를 상속 받았습니다.

그럼 거리를 계산하는 메서드를 보겠습니다.
IndexReader를 통해서 해당 필드의 값을 모두 뽑아 내고 있습니다. 여기서는 좌표값들이 나오겠죠..

그런데 여기서 return값이 계산 된 거리 값일 것 같았는데 DocValues 입니다.
CustomScoreQuery내부적으로는 ValueSourceQuery를 사용해서 ValueSourceQuery의 score를 계산하는데, 이 ValueSourceQuery는
ValueSourceScore를 계산하고 이때 사용 되는 것이 DocValues입니다. 때문에, 위 메서드에서 DocValues를 상속한
DistnaceDocValues를 리턴해주고 있는 것 입니다.

실제적으로 IndexReader를 통해서 뽑아온 좌표들을 사용해서 기준점과 거리를 계산하고 그 값을
리턴해주는 클래스는 아래의 클래스입니다.

이 클래스에서는 기준점과 대상점을 받아서 거리를 계산하고 있습니다.
public float floatVal(int doc) 메서드의 이름으로 유추해볼때 i번째 검색된 document에 대한 거리 값을 리턴 할 수
있도록 되어 있는 것 같습니다.

distance = (float)d_ - distance;

가 들어가 있는 것은 기준 되는 거리(5) 에서 계산 된 거리를 뺄 경우 가장 가까울수록 큰 값이 나오기 때문입니다.
이 값 그 자체가 score가 되기 때문에 위와 같이 distance를 계산해서 넘겨주면 가장 가까운 가게가 가장 먼저
나오게 됩니다.

distance = distance > 0f ? distance : 0f;

이 부분이 들어가 있는 이유는 5이내의 가게를 찾는 것이기 때문에 거리가 5 이상이 되는 것들은 맨 밑으로 내려버리기 위함입니다.

위와 같이 DocValues를 상속받아 클래스를 만들면 원하는 값으로 점수를 마음대로 산정 할 수 있게 됩니다.

위 클래스 실행 결과는 아래와 같습니다.
1.1985767 : 스시 / 7,7
1.1038789 : 스시 / 5,2
0.48399264 : 스시 / 6,9
0.41801658 : 스시 / 2,8
0.0 : 스시 / 1,9

저작자 표시 비영리 변경 금지
신고
Posted by 용식
우선 아래의 소스는
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를 사용해서 계산 하던 것을
간단하게 거리 공식을 계산해서 만들어 놓은 것을 보실 수 있습니다.

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

저작자 표시 비영리 변경 금지
신고
Posted by 용식