본문 바로가기

Lucene

[Lucene] Lcuene Score

잘 정리된 링크
http://lucene.apache.org/java/2_9_1/api/core/org/apache/lucene/search/Similarity.html
http://www.lucenetutorial.com/advanced-topics/scoring.html

누군가 질문을 하셔서
전부터 한번 깊게 공부해보고 싶던
Lucene Score를 조금씩 보고 있습니다.

우선 루씬에서의 Score 공식을 아래와 같습니다.
 
Lucene의 Score 공식

score(q,d) = Sigma(t in q)(tf(t in d) * idf(t)^2 * getBoost(t in q) * getBoost(t.field in d) * lengthNorm(t.field in d))
                  * coord(q, d) * queryNorm(q)
 
lengthNorm(t.field in d) = 1/sqrt(numTerms) * f.getBoost * d.getBoost
queryNorm(q) = queryNorm(sumOfSquaredWeights)

sumOfSquaredWeights = Sigma(t in q)(idf(t) * getBoost(t in q))^2


각 항목을 살펴보면
TF : 문서에서 해당 Term이 나온 횟수입니다. 루씬에서는 sqrt(횟수)로 구현됩니다.
IDF : 해당 Term이 전체 Document의 Set에 얼마나 나왔는지를 측정합니다. 다수의 문서에서 나온 경우 이 Term은 Document Set에서 중요하지 않은 Term으로 구분 될 수 있습니다. 즉, 일반적으로 많이 사용되는 단어라는 뜻입니다. 루씬에서는 log(numDocs / (docFreq +  1)) + 1로 구현됩니다.
Coord : 검색된 문서에서 쿼리의 Term이 몇 개 들어있는지에 대한 값 입니다. 루씬에서는 overlap / maxOverlap 으로 구현됩니다. 예를 들어서 "나이키 운동화" 라는쿼리를 "나이키 정말 좋아요" 라는 Document에 검색을 할 경우 Coord의 값은 1/2 가 됩니다.
lengthNorm : Term의 중요도 측정을 위한 수치입니다. 즉, 해당 필드에 들어있는 Term의 갯수가 적을 때의 Term의 매치는 더 좋은 점수를 갖게 됩니다. 예를 들어서 "나이키"라는 검색어에 대해서 "나이키 운동화 정말 좋은데 어디서 사요" 라는 문서와 "나이키 운동화"라는 문서에 대해서 검색 될 경우 후자의 문서가 더 높은 점수를 갖게 됩니다. 루씬에서는  1/sqrt(numTerms) * f.getBoost * d.getBoost로 구현됩니다. (numTerms는 필드의 Term 개수) 
f.getBoost : 필드의 가중치 Indexing time에 결정됩니다.
d.getBoost : Document의 가중치 IndexingTime에 결정됩니다. 
queryNorm : 이 수치는 위 공식을 보시면 아시겠지만 문서간의 score(q, d)를 구하는데 직접적인 영향이 없습니다. 하나의 쿼리에 대해서 queryNorm은 동일한 값을 갖기 때문입니다. 이 수치는 쿼리간의 비교를 위한 정규화 값입니다.
예를 들어 "나이키 운동화" 와 "나이키 신발"의 score(q, d)를 구할 때의 비교값으로 사용 될 수 있습니다. 
boost(t in q) : 쿼리에 있는 Term의 가중치 Search Time에 설정됩니다. 쿼리 문법으로는 ^N 으로 표시합니다. 2개 이상의 Term이 있을때 의미가 있습니다.

 

Document
0 : 시크릿 가든
1 : 자바 구글앱엔진

Sample)
Query
: 시크릿
 

0.5 = (MATCH) fieldWeight(label:시크릿 in 0), product of:
  1.0 = tf(termFreq(label:시크릿)=1)
  1.0 = idf(docFreq=1, maxDocs=2)
  0.5 = fieldNorm(field=label, doc=0)

1. term in query는 [시크릿] 하나
2. TF = sqrt(freq) = sqrt(1) = 1 (document에 시크릿이 1번 나옴)
3. IDF = (log(numDocs/(docFreq+1)) +1)^2 = (log(2/1+1))+1)^2 = (log1 + 1)^2 = 1^2 = 1
4. lengthNorm =  1/sqrt(number of terms in field) * f.getBoost * d.getBoost = 1/sqrt(3) * 1 * 1 = 0.5
- 만약 색인시 field.setBoost(5), document.setBoost(5)를 주면 위 공식에 의해서 lengthNorm은 약 14가 됨
5. coord는 default 1
6. queryNorm 1
7. score(q, d) = 1 * 1 * 0.5 = 0.5
 
 Query
: 시크릿 가든 
 

0.70710677 = (MATCH) sum of:
  0.35355338 = (MATCH) weight(label:시크릿 in 0), product of:
    0.70710677 = queryWeight(label:시크릿), product of:
      1.0 = idf(docFreq=1, maxDocs=2)
      0.70710677 = queryNorm
    0.5 = (MATCH) fieldWeight(label:시크릿 in 0), product of:
      1.0 = tf(termFreq(label:시크릿)=1)
      1.0 = idf(docFreq=1, maxDocs=2)
      0.5 = fieldNorm(field=label, doc=0)
 
  0.35355338 = (MATCH) weight(label:가든 in 0), product of:
    0.70710677 = queryWeight(label:가든), product of:
      1.0 = idf(docFreq=1, maxDocs=2)
      0.70710677 = queryNorm
    0.5 = (MATCH) fieldWeight(label:가든 in 0), product of:
      1.0 = tf(termFreq(label:가든)=1)
      1.0 = idf(docFreq=1, maxDocs=2)
      0.5 = fieldNorm(field=label, doc=0)


루씬 Score 공식을 보면 각 Term에 대해서 weight를 구한 후 그 합에 Coord와 QueryNorm을 곱하는 구조로 되어 있으므로 
일단 QueryNorm을 먼저 구해보자.

QueryNorm
0. queryNorm (queryNorm(q) = queryNorm(sumOfSquaredWeights), sumOfSquaredWeights = Sigma(t in q)(idf(t) * getBoost(t in q))^2)
 - 
sumOfSqueredWeights = idf(t) * getBoost(t in q) = 1(시크릿) + 1(가든) = 2
 -  queryNorm = queryNorm(2) = 1/sqrt(sumOfSquaredWeights) = 1/sqrt(2) = 0.707106

각 TermWeight 
1. term in query는 [시크릿], [가든] 2
2. 각각의 term에 대해 (queryWeight * fieldWeight)의 합이 score(q, d)
 
- 시크릿의 fieldWeight
3. TF = sqrt(1) = 1
4. IDF = 1
5. lengthNorm = 0.5
6. fieldWeight = 0.5
 

- 가든의 fieldWeight
7. TF = 1 
8. IDF = 1
9. lengthNorm = 0.5
10.fieldWeight = 0.5
11.coord = 1
12. (0.5 + 0.5) * coord * querynorm = 1 * querynorm = 0.707106
13 score(q, d) = 0.707106

 
Explain에서는 각 Term에 대해서 queryNorm이 곱해져서 나오는 형태로 풀려있는데 이것이 결국 위의 lucene score 공식을 풀면 (A + B) * C = AC * BC 의 형태로 보여지는 것 같다.